/*
 * Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include "internal/namemap.h"
#include <openssl/lhash.h>
#include <openssl/safestack.h>

/* The namemap entry */
typedef struct {
    int number;
    const char *name;
    char body[1];        /* Sized appropriately to contain the name */
} NAMEMAP_ENTRY;

DEFINE_LHASH_OF(NAMEMAP_ENTRY);
DEFINE_STACK_OF(NAMEMAP_ENTRY)

/* The namemap, which provides for bidirectional indexing */

struct ossl_namemap_st {
    /* Flags */
    unsigned int stored:1; /* If 1, it's stored in a library context */

    CRYPTO_RWLOCK *lock;
    LHASH_OF(NAMEMAP_ENTRY) *namenum;  /* Name->number mapping */
    STACK_OF(NAMEMAP_ENTRY) *numname;  /* Number->name mapping */
};

/* LHASH callbacks */

static unsigned long namemap_hash(const NAMEMAP_ENTRY *n)
{
    return OPENSSL_LH_strhash(n->name);
}

static int namemap_cmp(const NAMEMAP_ENTRY *a, const NAMEMAP_ENTRY *b)
{
    return strcmp(a->name, b->name);
}

static void namemap_free(NAMEMAP_ENTRY *n)
{
    OPENSSL_free(n);
}

/* OPENSSL_CTX_METHOD functions for a namemap stored in a library context */

static void *stored_namemap_new(OPENSSL_CTX *libctx)
{
    OSSL_NAMEMAP *namemap = ossl_namemap_new();

    if (namemap != NULL)
        namemap->stored = 1;

    return namemap;
}

static void stored_namemap_free(void *vnamemap)
{
    OSSL_NAMEMAP *namemap = vnamemap;

    /* Pretend it isn't stored, or ossl_namemap_free() will do nothing */
    namemap->stored = 0;
    ossl_namemap_free(namemap);
}

static const OPENSSL_CTX_METHOD stored_namemap_method = {
    stored_namemap_new,
    stored_namemap_free,
};

/* API functions */

OSSL_NAMEMAP *ossl_namemap_stored(OPENSSL_CTX *libctx)
{
    return openssl_ctx_get_data(libctx, OPENSSL_CTX_NAMEMAP_INDEX,
                                &stored_namemap_method);
}

OSSL_NAMEMAP *ossl_namemap_new(void)
{
    OSSL_NAMEMAP *namemap;

    if ((namemap = OPENSSL_zalloc(sizeof(*namemap))) != NULL
        && (namemap->lock = CRYPTO_THREAD_lock_new()) != NULL
        && (namemap->numname = sk_NAMEMAP_ENTRY_new_null()) != NULL
        && (namemap->namenum =
            lh_NAMEMAP_ENTRY_new(namemap_hash, namemap_cmp)) != NULL) {
        return namemap;
    }

    ossl_namemap_free(namemap);
    return NULL;
}

void ossl_namemap_free(OSSL_NAMEMAP *namemap)
{
    if (namemap == NULL || namemap->stored)
        return;

     /* The elements will be freed by sk_NAMEMAP_ENTRY_pop_free() */
    lh_NAMEMAP_ENTRY_free(namemap->namenum);

    sk_NAMEMAP_ENTRY_pop_free(namemap->numname, namemap_free);

    CRYPTO_THREAD_lock_free(namemap->lock);
    OPENSSL_free(namemap);
}

/*
 * TODO(3.0) It isn't currently possible to have a default namemap in the
 * FIPS module because if init and cleanup constraints, so we currently
 * disable the code that would allow it when FIPS_MODE is defined.
 */

const char *ossl_namemap_name(const OSSL_NAMEMAP *namemap, int number)
{
    NAMEMAP_ENTRY *entry;

#ifndef FIPS_MODE
    if (namemap == NULL)
        namemap = ossl_namemap_stored(NULL);
#endif

    if (namemap == NULL || number == 0)
        return NULL;

    CRYPTO_THREAD_read_lock(namemap->lock);
    entry = sk_NAMEMAP_ENTRY_value(namemap->numname, number);
    CRYPTO_THREAD_unlock(namemap->lock);

    if (entry != NULL)
        return entry->name;
    return NULL;
}

int ossl_namemap_number(const OSSL_NAMEMAP *namemap, const char *name)
{
    NAMEMAP_ENTRY *entry, template;

#ifndef FIPS_MODE
    if (namemap == NULL)
        namemap = ossl_namemap_stored(NULL);
#endif

    if (namemap == NULL)
        return 0;

    template.name = name;
    CRYPTO_THREAD_read_lock(namemap->lock);
    entry = lh_NAMEMAP_ENTRY_retrieve(namemap->namenum, &template);
    CRYPTO_THREAD_unlock(namemap->lock);

    if (entry == NULL)
        return 0;

    return entry->number;
}

int ossl_namemap_add(OSSL_NAMEMAP *namemap, const char *name)
{
    NAMEMAP_ENTRY *entry;
    int number;

#ifndef FIPS_MODE
    if (namemap == NULL)
        namemap = ossl_namemap_stored(NULL);
#endif

    if (name == NULL || namemap == NULL)
        return 0;

    if ((number = ossl_namemap_number(namemap, name)) != 0)
        return number;           /* Pretend success */

    if ((entry = OPENSSL_zalloc(sizeof(*entry) + strlen(name))) == NULL)
        goto err;

    strcpy(entry->body, name);
    entry->name = entry->body;

    CRYPTO_THREAD_write_lock(namemap->lock);

    entry->number = sk_NAMEMAP_ENTRY_push(namemap->numname, entry);

    if (entry->number == 0)
        goto err;

    (void)lh_NAMEMAP_ENTRY_insert(namemap->namenum, entry);
    if (lh_NAMEMAP_ENTRY_error(namemap->namenum))
        goto err;

    CRYPTO_THREAD_unlock(namemap->lock);

    return entry->number;

 err:
    if (entry != NULL) {
        if (entry->number != 0)
            (void)sk_NAMEMAP_ENTRY_pop(namemap->numname);
        lh_NAMEMAP_ENTRY_delete(namemap->namenum, entry);
        CRYPTO_THREAD_unlock(namemap->lock);
    }
    return 0;
}
