/**********************************************************************
 *                 gost_prov.c - The provider itself                  *
 *                                                                    *
 *      Copyright (c) 2021 Richard Levitte <richard@levitte.org>      *
 *     This file is distributed under the same license as OpenSSL     *
 *                                                                    *
 *                Requires OpenSSL 3.0 for compilation                *
 **********************************************************************/

#include <openssl/core_dispatch.h>
#include <openssl/core_names.h>
#include "gost_prov.h"
#include "gost_prov_tls.h"
#include "gost_lcl.h"
#include "prov/err.h"           /* libprov err functions */

/*********************************************************************
 *
 *  Errors
 *
 *****/

/*
 * Ugly hack, to get the errors generated by mkerr.pl.  This should ideally
 * be replaced with a local OSSL_ITEM list of < number, string > pairs as
 * reason strings, but for now, we will simply use GOST_str_reasons.
 * Fortunately, the ERR_STRING_DATA structure is compatible with OSSL_ITEM,
 * so we can return it directly.
 */
static struct proverr_functions_st *err_handle;
#define GOST_PROV
#include "e_gost_err.c"
void ERR_GOST_error(int function, int reason, char *file, int line)
{
    proverr_new_error(err_handle);
    proverr_set_error_debug(err_handle, file, line, NULL);
    proverr_set_error(err_handle, reason, NULL);
}

/*********************************************************************
 *
 *  Provider context
 *
 *****/

static void provider_ctx_free(PROV_CTX *ctx)
{
    if (ctx != NULL) {
        ENGINE_free(ctx->e);
        proverr_free_handle(ctx->proverr_handle);
        OSSL_LIB_CTX_free(ctx->libctx);
    }
    OPENSSL_free(ctx);
}

extern int populate_gost_engine(ENGINE *e);
static PROV_CTX *provider_ctx_new(const OSSL_CORE_HANDLE *core,
                                  const OSSL_DISPATCH *in)
{
    PROV_CTX *ctx;

    if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) != NULL
        && (ctx->proverr_handle = proverr_new_handle(core, in)) != NULL
        && (ctx->libctx = OSSL_LIB_CTX_new_child(core, in)) != NULL
        && (ctx->e = ENGINE_new()) != NULL
        && populate_gost_engine(ctx->e)) {
        ctx->core_handle = core;

        /* Ugly hack */
        err_handle = ctx->proverr_handle;
    } else {
        provider_ctx_free(ctx);
        ctx = NULL;
    }
    return ctx;
}

/*********************************************************************
 *
 *  Setup
 *
 *****/

typedef void (*fptr_t)(void);

/* The function that returns the appropriate algorithm table per operation */
static const OSSL_ALGORITHM *gost_operation(void *vprovctx,
                                                int operation_id,
                                                const int *no_cache)
{
    switch (operation_id) {
    case OSSL_OP_CIPHER:
        return GOST_prov_ciphers;
    case OSSL_OP_DIGEST:
        return GOST_prov_digests;
    case OSSL_OP_MAC:
        return GOST_prov_macs;
    case OSSL_OP_KEYMGMT:
        return GOST_prov_keymgmt;
    case OSSL_OP_ENCODER:
        return GOST_prov_encoder;
    case OSSL_OP_SIGNATURE:
        return GOST_prov_signature;
    case OSSL_OP_DECODER:
        return GOST_prov_decoder;
    case OSSL_OP_KEYEXCH:
        return GOST_prov_keyexch;
    }
    return NULL;
}

static int gost_get_params(void *provctx, OSSL_PARAM *params)
{
    OSSL_PARAM *p;

    p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_NAME);
    if (p != NULL && !OSSL_PARAM_set_utf8_ptr(p, "OpenSSL GOST Provider"))
        return 0;
    p = OSSL_PARAM_locate(params, OSSL_PROV_PARAM_STATUS);
    if (p != NULL && !OSSL_PARAM_set_int(p, 1)) /* We never fail. */
        return 0;

    return 1;
}

static const OSSL_ITEM *gost_get_reason_strings(void *provctx)
{
#if 0
    return reason_strings;
#endif
    return (OSSL_ITEM *)GOST_str_reasons;
}

/* The function that tears down this provider */
static void gost_teardown(void *vprovctx)
{
    GOST_prov_deinit_ciphers();
    GOST_prov_deinit_digests();
    GOST_prov_deinit_mac_digests();
    provider_ctx_free(vprovctx);
}

int gost_prov_get_capabilities(void *provctx, const char *capability,
                               OSSL_CALLBACK *cb, void *arg)
{
    if (!cb)
        return 0;

    if (OPENSSL_strcasecmp(capability, "TLS-GROUP") == 0)
        return gost_prov_get_tls_group_capability(cb, arg);

    if (OPENSSL_strcasecmp(capability, "TLS-SIGALG") == 0)
        return gost_prov_get_tls_sigalg_capability(cb, arg);

    return 0;
}

/* The base dispatch table */
static const OSSL_DISPATCH provider_functions[] = {
    { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (fptr_t)gost_operation },
    { OSSL_FUNC_PROVIDER_GET_REASON_STRINGS, (fptr_t)gost_get_reason_strings },
    { OSSL_FUNC_PROVIDER_GET_PARAMS, (fptr_t)gost_get_params },
    { OSSL_FUNC_PROVIDER_TEARDOWN, (fptr_t)gost_teardown },
    { OSSL_FUNC_PROVIDER_GET_CAPABILITIES, (fptr_t)gost_prov_get_capabilities },
    { 0, NULL }
};

#ifdef BUILDING_PROVIDER_AS_LIBRARY
/*
 * This allows the provider to be built in library form.  In this case, the
 * application must add it explicitly like this:
 *
 * OSSL_PROVIDER_add_builtin(NULL, "gost", GOST_provider_init);
 */
# define OSSL_provider_init GOST_provider_init
#endif

OPENSSL_EXPORT
int OSSL_provider_init(const OSSL_CORE_HANDLE *core,
                       const OSSL_DISPATCH *in,
                       const OSSL_DISPATCH **out,
                       void **vprovctx)
{
    if ((*vprovctx = provider_ctx_new(core, in)) == NULL)
        return 0;
    *out = provider_functions;
    return 1;
}
