/*
 * Copyright 2024 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 <openssl/indicator.h>
#include <openssl/params.h>
#include <openssl/core_names.h>
#include "internal/common.h" /* for ossl_assert() */
#include "fips/fipsindicator.h"

void ossl_FIPS_IND_init(OSSL_FIPS_IND *ind)
{
    int i;

    ossl_FIPS_IND_set_approved(ind); /* Assume we are approved by default */
    for (i = 0; i < OSSL_FIPS_IND_SETTABLE_MAX; i++)
        ind->settable[i] = OSSL_FIPS_IND_STATE_UNKNOWN;
}

void ossl_FIPS_IND_set_approved(OSSL_FIPS_IND *ind)
{
    ind->approved = 1;
}

void ossl_FIPS_IND_copy(OSSL_FIPS_IND *dst, const OSSL_FIPS_IND *src)
{
    *dst = *src;
}

void ossl_FIPS_IND_set_settable(OSSL_FIPS_IND *ind, int id, int state)
{
    if (!ossl_assert(id < OSSL_FIPS_IND_SETTABLE_MAX))
        return;
    if (!ossl_assert(state == OSSL_FIPS_IND_STATE_STRICT
            || state == OSSL_FIPS_IND_STATE_TOLERANT))
        return;
    ind->settable[id] = state;
}

int ossl_FIPS_IND_get_settable(const OSSL_FIPS_IND *ind, int id)
{
    if (!ossl_assert(id < OSSL_FIPS_IND_SETTABLE_MAX))
        return OSSL_FIPS_IND_STATE_UNKNOWN;
    return ind->settable[id];
}

/*
 * This should only be called when a strict FIPS algorithm check fails.
 * It assumes that we are in strict mode by default.
 * If the logic here is not sufficient for all cases, then additional
 * ossl_FIPS_IND_on_unapproved() functions may be required.
 */
int ossl_FIPS_IND_on_unapproved(OSSL_FIPS_IND *ind, int id,
    OSSL_LIB_CTX *libctx,
    const char *algname, const char *opname,
    OSSL_FIPS_IND_CHECK_CB *config_check_fn)
{
    /* Set to unapproved. Once unapproved mode is set this will not be reset */
    ind->approved = 0;

    /*
     * We only trigger the indicator callback if the ctx variable is cleared OR
     * the configurable item is cleared. If the values are unknown they are
     * assumed to be strict.
     */
    if (ossl_FIPS_IND_get_settable(ind, id) == OSSL_FIPS_IND_STATE_TOLERANT
        || (config_check_fn != NULL
            && config_check_fn(libctx) == OSSL_FIPS_IND_STATE_TOLERANT)) {
        return ossl_FIPS_IND_callback(libctx, algname, opname);
    }
    /* Strict mode gets here: This returns an error */
    return 0;
}

int ossl_FIPS_IND_set_ctx_param(OSSL_FIPS_IND *ind, int id,
    const OSSL_PARAM params[], const char *name)
{
    int in = 0;
    const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, name);

    if (p != NULL) {
        if (!OSSL_PARAM_get_int(p, &in))
            return 0;
        ossl_FIPS_IND_set_settable(ind, id, in);
    }
    return 1;
}

int ossl_FIPS_IND_get_ctx_param(const OSSL_FIPS_IND *ind, OSSL_PARAM params[])
{
    OSSL_PARAM *p = OSSL_PARAM_locate(params, OSSL_ALG_PARAM_FIPS_APPROVED_INDICATOR);

    return p == NULL || OSSL_PARAM_set_int(p, ind->approved);
}

/*
 * Can be used during application testing to log that an indicator was
 * triggered. The callback will return 1 if the application wants an error
 * to occur based on the indicator type and description.
 */
int ossl_FIPS_IND_callback(OSSL_LIB_CTX *libctx, const char *type,
    const char *desc)
{
    OSSL_INDICATOR_CALLBACK *cb = NULL;

    OSSL_INDICATOR_get_callback(libctx, &cb);
    if (cb == NULL)
        return 1;

    return cb(type, desc, NULL);
}
