/*
 * Copyright 2020 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/core_names.h>
#include <openssl/bio.h>
#include <openssl/params.h>
#include <openssl/provider.h>
#include "internal/passphrase.h"
#include "encoder_local.h"
#include "e_os.h"

struct decoder_process_data_st {
    OSSL_DECODER_CTX *ctx;

    /* Current BIO */
    BIO *bio;

    /* Index of the current decoder instance to be processed */
    size_t current_deser_inst_index;
};

static int decoder_process(const OSSL_PARAM params[], void *arg);

int OSSL_DECODER_from_bio(OSSL_DECODER_CTX *ctx, BIO *in)
{
    struct decoder_process_data_st data;
    int ok = 0;

    memset(&data, 0, sizeof(data));
    data.ctx = ctx;
    data.bio = in;

    /* Enable passphrase caching */
    (void)ossl_pw_enable_passphrase_caching(&ctx->pwdata);

    ok = decoder_process(NULL, &data);

    /* Clear any internally cached passphrase */
    (void)ossl_pw_clear_passphrase_cache(&ctx->pwdata);

    return ok;
}

#ifndef OPENSSL_NO_STDIO
static BIO *bio_from_file(FILE *fp)
{
    BIO *b;

    if ((b = BIO_new(BIO_s_file())) == NULL) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_BIO_LIB);
        return NULL;
    }
    BIO_set_fp(b, fp, BIO_NOCLOSE);
    return b;
}

int OSSL_DECODER_from_fp(OSSL_DECODER_CTX *ctx, FILE *fp)
{
    BIO *b = bio_from_file(fp);
    int ret = 0;

    if (b != NULL)
        ret = OSSL_DECODER_from_bio(ctx, b);

    BIO_free(b);
    return ret;
}
#endif

int OSSL_DECODER_CTX_set_input_type(OSSL_DECODER_CTX *ctx,
                                    const char *input_type)
{
    if (!ossl_assert(ctx != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }

    /*
     * NULL is a valid starting input type, and means that the caller leaves
     * it to code to discover what the starting input type is.
     */
    ctx->start_input_type = input_type;
    return 1;
}

int OSSL_DECODER_CTX_add_decoder(OSSL_DECODER_CTX *ctx, OSSL_DECODER *decoder)
{
    OSSL_DECODER_INSTANCE *decoder_inst = NULL;
    const OSSL_PROVIDER *prov = NULL;
    OSSL_PARAM params[2];
    void *provctx = NULL;

    if (!ossl_assert(ctx != NULL) || !ossl_assert(decoder != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }

    if (decoder->get_params == NULL) {
        ERR_raise(ERR_LIB_OSSL_DECODER,
                  OSSL_DECODER_R_MISSING_GET_PARAMS);
        return 0;
    }

    if (ctx->decoder_insts == NULL
        && (ctx->decoder_insts =
            sk_OSSL_DECODER_INSTANCE_new_null()) == NULL) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_MALLOC_FAILURE);
        return 0;
    }
    if ((decoder_inst = OPENSSL_zalloc(sizeof(*decoder_inst))) == NULL) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_MALLOC_FAILURE);
        return 0;
    }
    if (!OSSL_DECODER_up_ref(decoder)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    decoder_inst->decoder = decoder;

    prov = OSSL_DECODER_provider(decoder_inst->decoder);
    provctx = OSSL_PROVIDER_get0_provider_ctx(prov);

    /* Cache the input type for this encoder */
    params[0] =
        OSSL_PARAM_construct_utf8_ptr(OSSL_DECODER_PARAM_INPUT_TYPE,
                                      (char **)&decoder_inst->input_type, 0);
    params[1] = OSSL_PARAM_construct_end();

    if (!decoder_inst->decoder->get_params(params)
        || !OSSL_PARAM_modified(&params[0]))
        goto err;

    if ((decoder_inst->deserctx = decoder_inst->decoder->newctx(provctx))
        == NULL)
        goto err;

    if (sk_OSSL_DECODER_INSTANCE_push(ctx->decoder_insts, decoder_inst) <= 0)
        goto err;

    return 1;
 err:
    if (decoder_inst != NULL) {
        if (decoder_inst->decoder != NULL)
            decoder_inst->decoder->freectx(decoder_inst->deserctx);
        OSSL_DECODER_free(decoder_inst->decoder);
        OPENSSL_free(decoder_inst);
    }
    return 0;
}

int OSSL_DECODER_CTX_add_extra(OSSL_DECODER_CTX *ctx,
                               OPENSSL_CTX *libctx, const char *propq)
{
    /*
     * This function goes through existing decoder methods in
     * |ctx->decoder_insts|, and tries to fetch new decoders that produce
     * what the existing ones want as input, and push those newly fetched
     * decoders on top of the same stack.
     * Then it does the same again, but looping over the newly fetched
     * decoders, until there are no more encoders to be fetched, or
     * when we have done this 10 times.
     *
     * we do this with sliding windows on the stack by keeping track of indexes
     * and of the end.
     *
     * +----------------+
     * |   DER to RSA   | <--- w_prev_start
     * +----------------+
     * |   DER to DSA   |
     * +----------------+
     * |   DER to DH    |
     * +----------------+
     * |   PEM to DER   | <--- w_prev_end, w_new_start
     * +----------------+
     *                    <--- w_new_end
     */
    size_t w_prev_start, w_prev_end; /* "previous" decoders */
    size_t w_new_start, w_new_end;   /* "new" decoders */
    size_t count = 0; /* Calculates how many were added in each iteration */
    size_t depth = 0; /* Counts the number of iterations */

    if (!ossl_assert(ctx != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }

    /*
     * If there is no stack of OSSL_DECODER_INSTANCE, we have nothing
     * more to add.  That's fine.
     */
    if (ctx->decoder_insts == NULL)
        return 1;

    w_prev_start = 0;
    w_prev_end = sk_OSSL_DECODER_INSTANCE_num(ctx->decoder_insts);
    do {
        size_t i;

        w_new_start = w_new_end = w_prev_end;

        for (i = w_prev_start; i < w_prev_end; i++) {
            OSSL_DECODER_INSTANCE *decoder_inst =
                sk_OSSL_DECODER_INSTANCE_value(ctx->decoder_insts, i);
            const char *name = decoder_inst->input_type;
            OSSL_DECODER *decoder = NULL;

            /*
             * If the caller has specified what the initial input should be,
             * and the decoder implementation we're looking at has that
             * input type, there's no point adding on more implementations
             * on top of this one, so we don't.
             */
            if (ctx->start_input_type != NULL
                && strcasecmp(ctx->start_input_type,
                              decoder_inst->input_type) != 0)
                continue;

            ERR_set_mark();
            decoder = OSSL_DECODER_fetch(libctx, name, propq);
            ERR_pop_to_mark();

            if (decoder != NULL) {
                size_t j;

                /*
                 * Check that we don't already have this decoder in our
                 * stack We only need to check among the newly added ones.
                 */
                for (j = w_new_start; j < w_new_end; j++) {
                    OSSL_DECODER_INSTANCE *check_inst =
                        sk_OSSL_DECODER_INSTANCE_value(ctx->decoder_insts, j);

                    if (decoder == check_inst->decoder) {
                        /* We found it, so drop the new fetch */
                        OSSL_DECODER_free(decoder);
                        decoder = NULL;
                        break;
                    }
                }
            }

            if (decoder == NULL)
                continue;

            /*
             * Apart from keeping w_new_end up to date, We don't care about
             * errors here.  If it doesn't collect, then it doesn't...
             */
            if (OSSL_DECODER_CTX_add_decoder(ctx, decoder)) /* ref++ */
                w_new_end++;
            OSSL_DECODER_free(decoder); /* ref-- */
        }
        /* How many were added in this iteration */
        count = w_new_end - w_new_start;

        /* Slide the "previous decoder" windows */
        w_prev_start = w_new_start;
        w_prev_end = w_new_end;

        depth++;
    } while (count != 0 && depth <= 10);

    return 1;
}

int OSSL_DECODER_CTX_num_decoders(OSSL_DECODER_CTX *ctx)
{
    if (ctx == NULL || ctx->decoder_insts == NULL)
        return 0;
    return sk_OSSL_DECODER_INSTANCE_num(ctx->decoder_insts);
}

int OSSL_DECODER_CTX_set_construct(OSSL_DECODER_CTX *ctx,
                                   OSSL_DECODER_CONSTRUCT *construct)
{
    if (!ossl_assert(ctx != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    ctx->construct = construct;
    return 1;
}

int OSSL_DECODER_CTX_set_construct_data(OSSL_DECODER_CTX *ctx,
                                        void *construct_data)
{
    if (!ossl_assert(ctx != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    ctx->construct_data = construct_data;
    return 1;
}

int OSSL_DECODER_CTX_set_cleanup(OSSL_DECODER_CTX *ctx,
                                 OSSL_DECODER_CLEANUP *cleanup)
{
    if (!ossl_assert(ctx != NULL)) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    ctx->cleanup = cleanup;
    return 1;
}

OSSL_DECODER_CONSTRUCT *
OSSL_DECODER_CTX_get_construct(OSSL_DECODER_CTX *ctx)
{
    if (ctx == NULL)
        return NULL;
    return ctx->construct;
}

void *OSSL_DECODER_CTX_get_construct_data(OSSL_DECODER_CTX *ctx)
{
    if (ctx == NULL)
        return NULL;
    return ctx->construct_data;
}

OSSL_DECODER_CLEANUP *
OSSL_DECODER_CTX_get_cleanup(OSSL_DECODER_CTX *ctx)
{
    if (ctx == NULL)
        return NULL;
    return ctx->cleanup;
}

int OSSL_DECODER_export(OSSL_DECODER_INSTANCE *decoder_inst,
                        void *reference, size_t reference_sz,
                        OSSL_CALLBACK *export_cb, void *export_cbarg)
{
    if (!(ossl_assert(decoder_inst != NULL)
          && ossl_assert(reference != NULL)
          && ossl_assert(export_cb != NULL)
          && ossl_assert(export_cbarg != NULL))) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }

    return decoder_inst->decoder->export_object(decoder_inst->deserctx,
                                                reference, reference_sz,
                                                export_cb, export_cbarg);
}

OSSL_DECODER *OSSL_DECODER_INSTANCE_decoder(OSSL_DECODER_INSTANCE *decoder_inst)
{
    if (decoder_inst == NULL)
        return NULL;
    return decoder_inst->decoder;
}

void *OSSL_DECODER_INSTANCE_decoder_ctx(OSSL_DECODER_INSTANCE *decoder_inst)
{
    if (decoder_inst == NULL)
        return NULL;
    return decoder_inst->deserctx;
}

static int decoder_process(const OSSL_PARAM params[], void *arg)
{
    struct decoder_process_data_st *data = arg;
    OSSL_DECODER_CTX *ctx = data->ctx;
    OSSL_DECODER_INSTANCE *decoder_inst = NULL;
    OSSL_DECODER *decoder = NULL;
    BIO *bio = data->bio;
    long loc;
    size_t i;
    int ok = 0;
    /* For recursions */
    struct decoder_process_data_st new_data;

    memset(&new_data, 0, sizeof(new_data));
    new_data.ctx = data->ctx;

    if (params == NULL) {
        /* First iteration, where we prepare for what is to come */

        data->current_deser_inst_index =
            OSSL_DECODER_CTX_num_decoders(ctx);

        bio = data->bio;
    } else {
        const OSSL_PARAM *p;

        decoder_inst =
            sk_OSSL_DECODER_INSTANCE_value(ctx->decoder_insts,
                                           data->current_deser_inst_index);
        decoder = OSSL_DECODER_INSTANCE_decoder(decoder_inst);

        if (ctx->construct != NULL
            && ctx->construct(decoder_inst, params, ctx->construct_data)) {
            ok = 1;
            goto end;
        }

        /* The constructor didn't return success */

        /*
         * so we try to use the object we got and feed it to any next
         * decoder that will take it.  Object references are not
         * allowed for this.
         * If this data isn't present, decoding has failed.
         */

        p = OSSL_PARAM_locate_const(params, OSSL_OBJECT_PARAM_DATA);
        if (p == NULL || p->data_type != OSSL_PARAM_OCTET_STRING)
            goto end;
        new_data.bio = BIO_new_mem_buf(p->data, (int)p->data_size);
        if (new_data.bio == NULL)
            goto end;
        bio = new_data.bio;
    }

    /*
     * If we have no more decoders to look through at this point,
     * we failed
     */
    if (data->current_deser_inst_index == 0)
        goto end;

    if ((loc = BIO_tell(bio)) < 0) {
        ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_BIO_LIB);
        goto end;
    }

    for (i = data->current_deser_inst_index; i-- > 0;) {
        OSSL_DECODER_INSTANCE *new_deser_inst =
            sk_OSSL_DECODER_INSTANCE_value(ctx->decoder_insts, i);
        OSSL_DECODER *new_deser =
            OSSL_DECODER_INSTANCE_decoder(new_deser_inst);

        /*
         * If |decoder| is NULL, it means we've just started, and the caller
         * may have specified what it expects the initial input to be.  If
         * that's the case, we do this extra check.
         */
        if (decoder == NULL && ctx->start_input_type != NULL
            && strcasecmp(ctx->start_input_type,
                          new_deser_inst->input_type) != 0)
            continue;

        /*
         * If we have a previous decoder, we check that the input type
         * of the next to be used matches the type of this previous one.
         * decoder_inst->input_type is a cache of the parameter "input-type"
         * value for that decoder.
         */
        if (decoder != NULL
            && !OSSL_DECODER_is_a(decoder, new_deser_inst->input_type))
            continue;

        /*
         * Checking the return value of BIO_reset() or BIO_seek() is unsafe.
         * Furthermore, BIO_reset() is unsafe to use if the source BIO happens
         * to be a BIO_s_mem(), because the earlier BIO_tell() gives us zero
         * no matter where we are in the underlying buffer we're reading from.
         *
         * So, we simply do a BIO_seek(), and use BIO_tell() that we're back
         * at the same position.  This is a best effort attempt, but BIO_seek()
         * and BIO_tell() should come as a pair...
         */
        (void)BIO_seek(bio, loc);
        if (BIO_tell(bio) != loc)
            goto end;

        /* Recurse */
        new_data.current_deser_inst_index = i;
        ok = new_deser->decode(new_deser_inst->deserctx, (OSSL_CORE_BIO *)bio,
                               decoder_process, &new_data,
                               ossl_pw_passphrase_callback_dec,
                               &new_data.ctx->pwdata);
        if (ok)
            break;
    }

 end:
    BIO_free(new_data.bio);
    return ok;
}
