/* quic.c
 *
 * Copyright (C) 2006-2025 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

#include <wolfssl/wolfcrypt/libwolfssl_sources.h>

  /* Name change compatibility layer no longer needs to be included here */

#ifdef NO_INLINE
    #include <wolfssl/wolfcrypt/misc.h>
#else
    #define WOLFSSL_MISC_INCLUDED
    #include <wolfcrypt/src/misc.c>
#endif

#ifndef WOLFCRYPT_ONLY
#ifdef WOLFSSL_QUIC

#include <wolfssl/error-ssl.h>
#include <wolfssl/ssl.h>
#include <wolfssl/internal.h>

#include <wolfssl/openssl/buffer.h>
#include <wolfssl/openssl/ecdsa.h>
#include <wolfssl/openssl/evp.h>
#include <wolfssl/openssl/kdf.h>


static int qr_length(const uint8_t *data, size_t len)
{
    word32 rlen;
    if (len < 4) {
        return 0;
    }
    c24to32(&data[1], &rlen);
    return (int)rlen + 4;
}

static void quic_record_free(WOLFSSL *ssl, QuicRecord *r)
{
    (void)ssl;
    if (r->data) {
        ForceZero(r->data, r->capacity);
        XFREE(r->data, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
    }
    XFREE(r, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
}


static QuicRecord *quic_record_make(WOLFSSL *ssl,
                                    WOLFSSL_ENCRYPTION_LEVEL level,
                                    const uint8_t *data, size_t len)
{
    QuicRecord *qr;

    qr = (QuicRecord*)XMALLOC(sizeof(*qr), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
    if (qr) {
        memset(qr, 0, sizeof(*qr));
        qr->level = level;
        if (level == wolfssl_encryption_early_data) {
            qr->capacity = qr->len = (word32)len;
        }
        else {
            qr->capacity = qr->len = (word32) qr_length(data, len);
            if (qr->capacity > WOLFSSL_QUIC_MAX_RECORD_CAPACITY) {
                WOLFSSL_MSG("QUIC length read larger than expected");
                quic_record_free(ssl, qr);
                return NULL;
            }
        }
        if (qr->capacity == 0) {
            qr->capacity = 2*1024;
        }
        qr->data = (uint8_t*)XMALLOC(qr->capacity, ssl->heap,
                                     DYNAMIC_TYPE_TMP_BUFFER);
        if (!qr->data) {
            quic_record_free(ssl, qr);
            return NULL;
        }
    }
    return qr;
}

static int quic_record_complete(QuicRecord *r)
{
    return r->len && r->end >= r->len;
}

static int quic_record_done(QuicRecord *r)
{
    return r->len && r->end >= r->len && r->start >= r->end;
}

static int quic_record_append(WOLFSSL *ssl, QuicRecord *qr, const uint8_t *data,
                              size_t len, size_t *pconsumed)
{
    size_t missing, consumed = 0;
    int ret = WOLFSSL_SUCCESS;

    (void)ssl;
    if (!qr->len && len) {
        missing = 4 - qr->end;
        if (len < missing) {
            XMEMCPY(qr->data + qr->end, data, len);
            qr->end += (word32)len;
            consumed = len;
            goto cleanup; /* len consumed, but qr->len still unknown */
        }
        XMEMCPY(qr->data + qr->end, data, missing);
        qr->end += (word32)missing;
        len -= missing;
        data += missing;
        consumed = missing;

        qr->len = (word32)qr_length(qr->data, qr->end);

        /* sanity check on length read from wire before use */
        if (qr->len > WOLFSSL_QUIC_MAX_RECORD_CAPACITY) {
            WOLFSSL_MSG("Length read for quic is larger than expected");
            ret = BUFFER_E;
            goto cleanup;
        }

        if (qr->len > qr->capacity) {
            uint8_t *ndata = (uint8_t*)XREALLOC(qr->data, qr->len, ssl->heap,
                                                DYNAMIC_TYPE_TMP_BUFFER);
            if (!ndata) {
                ret = WOLFSSL_FAILURE;
                goto cleanup;
            }
            qr->data = ndata;
            qr->capacity = qr->len;
        }
    }

    if (!quic_record_complete(qr) && len != 0) {
        missing = qr->len - qr->end;
        if (len > missing) {
            len = missing;
        }
        XMEMCPY(qr->data + qr->end, data, len);
        qr->end += (word32)len;
        consumed += len;
    }

cleanup:
    *pconsumed = (ret == WOLFSSL_SUCCESS) ? consumed : 0;
    return ret;
}


static word32 add_rec_header(byte* output, word32 length, byte type)
{
    RecordLayerHeader* rl;

    /* record layer header */
    rl = (RecordLayerHeader*)output;
    if (rl == NULL) {
        return 0;
    }
    rl->type    = type;
    rl->pvMajor = SSLv3_MAJOR;
    rl->pvMinor = TLSv1_2_MINOR;
    c16toa((word16)length, rl->length);
    return RECORD_HEADER_SZ;
}

static sword32 quic_record_transfer(QuicRecord* qr, byte* buf, word32 sz)
{
    word32 len = qr->end - qr->start;
    word32 offset = 0;
    word32 rlen;

    if (len <= 0) {
        return 0;
    }

    /* We check if the buf is at least RECORD_HEADER_SZ */
    if (sz < RECORD_HEADER_SZ) {
        return WOLFSSL_FATAL_ERROR;
    }

    if (qr->rec_hdr_remain == 0) {
        /* start a new TLS record */
        rlen = (qr->len <= (word32)MAX_RECORD_SIZE) ?
                qr->len : (word32)MAX_RECORD_SIZE;
        offset += add_rec_header(buf, rlen,
                                 (qr->level == wolfssl_encryption_early_data) ?
                                  application_data : handshake);
        qr->rec_hdr_remain = rlen;
        sz -= offset;
    }
    if (len > qr->rec_hdr_remain) {
        len = qr->rec_hdr_remain;
    }
    if (len > sz) {
        len = sz;
    }
    if (len > 0) {
        XMEMCPY(buf + offset, qr->data + qr->start, len);
        qr->start += len;
        qr->rec_hdr_remain -= len;
    }
    return (sword32)(len + offset);
}


const QuicTransportParam* QuicTransportParam_new(const uint8_t* data,
                                                 size_t len, void* heap)
{
    QuicTransportParam* tp;

    if (len > 65353) return NULL;
    tp = (QuicTransportParam*)XMALLOC(sizeof(*tp), heap, DYNAMIC_TYPE_TLSX);
    if (!tp) return NULL;
    tp->data = (uint8_t*)XMALLOC(len, heap, DYNAMIC_TYPE_TLSX);
    if (!tp->data) {
        XFREE(tp, heap, DYNAMIC_TYPE_TLSX);
        return NULL;
    }
    XMEMCPY((uint8_t*)tp->data, data, len);
    tp->len = (word16)len;
    return tp;
}

const QuicTransportParam* QuicTransportParam_dup(const QuicTransportParam* tp,
                                                 void* heap)
{
    QuicTransportParam* tp2;
    tp2 = (QuicTransportParam*)XMALLOC(sizeof(*tp2), heap, DYNAMIC_TYPE_TLSX);
    if (!tp2) return NULL;
    tp2->data = (uint8_t*)XMALLOC(tp->len, heap, DYNAMIC_TYPE_TLSX);
    if (!tp2->data) {
        XFREE(tp2, heap, DYNAMIC_TYPE_TLSX);
        return NULL;
    }
    XMEMCPY((uint8_t*)tp2->data, tp->data, tp->len);
    tp2->len = tp->len;
    return tp2;
}

void QuicTransportParam_free(const QuicTransportParam* tp, void* heap)
{
    (void)heap;
    if (tp) {
        if (tp->data) XFREE((uint8_t*)tp->data, heap, DYNAMIC_TYPE_TLSX);
        XFREE((void*)tp, heap, DYNAMIC_TYPE_TLSX);
    }
}


void wolfSSL_quic_clear(WOLFSSL* ssl)
{
    QuicEncData* qd;

    /* keep
     * - ssl->quic.transport_local
     * - ssl->quic.method
     * - ssl->quic.transport_version
     * reset/free everything else
     */
    if (ssl->quic.transport_peer) {
        QTP_FREE(ssl->quic.transport_peer, ssl->heap);
        ssl->quic.transport_peer = NULL;
    }
    if (ssl->quic.transport_peer_draft) {
        QTP_FREE(ssl->quic.transport_peer_draft, ssl->heap);
        ssl->quic.transport_peer_draft = NULL;
    }
    ssl->quic.enc_level_write = wolfssl_encryption_initial;
    ssl->quic.enc_level_latest_recvd = wolfssl_encryption_initial;

    while ((qd = ssl->quic.input_head)) {
        ssl->quic.input_head = qd->next;
        quic_record_free(ssl, qd);
    }
    ssl->quic.input_tail = NULL;
    ssl->quic.output_rec_remain = 0;

    if (ssl->quic.scratch) {
        quic_record_free(ssl, ssl->quic.scratch);
        ssl->quic.scratch = NULL;
    }
}


void wolfSSL_quic_free(WOLFSSL* ssl)
{
    wolfSSL_quic_clear(ssl);
    if (ssl->quic.transport_local) {
        QTP_FREE(ssl->quic.transport_local, ssl->heap);
        ssl->quic.transport_local = NULL;
    }

    ssl->quic.method = NULL;
}


static int ctx_check_quic_compat(const WOLFSSL_CTX* ctx)
{
    WOLFSSL_ENTER("ctx_check_quic_compat");
    if (ctx->method->version.major != SSLv3_MAJOR
        || ctx->method->version.minor != TLSv1_3_MINOR
        || (ctx->method->downgrade && ctx->minDowngrade < TLSv1_3_MINOR)) {
        WOLFSSL_MSG_EX("ctx not quic compatible: vmajor=%d, vminor=%d, downgrade=%d",
                       ctx->method->version.major,
                       ctx->method->version.minor,
                       ctx->method->downgrade
                      );
        return WOLFSSL_FAILURE;
    }
    return WOLFSSL_SUCCESS;
}

static int check_method_sanity(const WOLFSSL_QUIC_METHOD* m)
{
    WOLFSSL_ENTER("check_method_sanity");
    if (m && m->set_encryption_secrets
        && m->add_handshake_data
        && m->flush_flight
        && m->send_alert) {
        return WOLFSSL_SUCCESS;
    }
    return WOLFSSL_FAILURE;
}

int wolfSSL_CTX_set_quic_method(WOLFSSL_CTX* ctx,
                                const WOLFSSL_QUIC_METHOD* quic_method)
{
    WOLFSSL_ENTER("wolfSSL_CTX_set_quic_method");
    if (ctx_check_quic_compat(ctx) != WOLFSSL_SUCCESS
        || check_method_sanity(quic_method) != WOLFSSL_SUCCESS) {
        return WOLFSSL_FAILURE;
    }
    ctx->quic.method = quic_method;
    return WOLFSSL_SUCCESS;
}


int wolfSSL_set_quic_method(WOLFSSL* ssl,
                            const WOLFSSL_QUIC_METHOD* quic_method)
{
    WOLFSSL_ENTER("wolfSSL_set_quic_method");
    if (ctx_check_quic_compat(ssl->ctx) != WOLFSSL_SUCCESS
        || check_method_sanity(quic_method) != WOLFSSL_SUCCESS) {
        return WOLFSSL_FAILURE;
    }
    ssl->quic.method = quic_method;
    return WOLFSSL_SUCCESS;
}


int wolfSSL_is_quic(WOLFSSL* ssl)
{
    return WOLFSSL_IS_QUIC(ssl);
}


WOLFSSL_ENCRYPTION_LEVEL wolfSSL_quic_read_level(const WOLFSSL* ssl)
{
    return ssl->quic.enc_level_read;
}


WOLFSSL_ENCRYPTION_LEVEL wolfSSL_quic_write_level(const WOLFSSL* ssl)
{
    return ssl->quic.enc_level_write;
}


int wolfSSL_set_quic_transport_params(WOLFSSL* ssl,
                                      const uint8_t* params,
                                      size_t params_len)
{
    const QuicTransportParam* tp;
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_set_quic_transport_params");

    if (!params || params_len == 0) {
        tp = NULL;
    }
    else {
        tp = QuicTransportParam_new(params, params_len, ssl->heap);
        if (!tp) {
            ret = WOLFSSL_FAILURE;
            goto cleanup;
        }
    }
    if (ssl->quic.transport_local)
        QTP_FREE(ssl->quic.transport_local, ssl->heap);
    ssl->quic.transport_local = tp;

cleanup:
    WOLFSSL_LEAVE("wolfSSL_set_quic_transport_params", ret);
    return ret;
}


void wolfSSL_get_peer_quic_transport_params(const WOLFSSL* ssl,
                                            const uint8_t** out_params,
                                            size_t* out_params_len)
{
    const QuicTransportParam* tp = ssl->quic.transport_peer ?
        ssl->quic.transport_peer : ssl->quic.transport_peer_draft;

    *out_params = tp ? tp->data : NULL;
    *out_params_len = tp ? tp->len : 0;
}


int wolfSSL_get_peer_quic_transport_version(const WOLFSSL* ssl)
{
    return ssl->quic.transport_peer ?
        TLSX_KEY_QUIC_TP_PARAMS : (ssl->quic.transport_peer_draft ?
        TLSX_KEY_QUIC_TP_PARAMS : -1);
}


void wolfSSL_set_quic_use_legacy_codepoint(WOLFSSL* ssl, int use_legacy)
{
    ssl->quic.transport_version = use_legacy ? TLSX_KEY_QUIC_TP_PARAMS_DRAFT
        : TLSX_KEY_QUIC_TP_PARAMS;
}

void wolfSSL_set_quic_transport_version(WOLFSSL* ssl, int version)
{
    if (version == TLSX_KEY_QUIC_TP_PARAMS
        || version == TLSX_KEY_QUIC_TP_PARAMS_DRAFT
        || !version) {
        ssl->quic.transport_version = version;
    }
    else {
        WOLFSSL_MSG("wolfSSL_set_quic_transport_version: invalid version");
    }
}


int wolfSSL_get_quic_transport_version(const WOLFSSL* ssl)
{
    return ssl->quic.transport_version;
}


int wolfSSL_quic_add_transport_extensions(WOLFSSL* ssl, int msg_type)
{
    /* RFC 9001, ch. 8.2: "The quic_transport_parameters extension is carried
     * in the ClientHello and the EncryptedExtensions messages during the
     * handshake. Endpoints MUST send the quic_transport_parameters extension;"
     * Which means, at least one. There can be more to signal compatibility to
     * older/newer versions.
     */
    int ret = 0, is_resp = (msg_type == encrypted_extensions);

    if (ssl->quic.transport_local == NULL) {
        return QUIC_TP_MISSING_E;
    }

    if (is_resp) {
        /* server response: time to decide which version to use */
        if (ssl->quic.transport_peer && ssl->quic.transport_peer_draft) {
            if (ssl->quic.transport_version == TLSX_KEY_QUIC_TP_PARAMS_DRAFT) {
                ret = TLSX_QuicTP_Use(ssl,
                                      TLSX_KEY_QUIC_TP_PARAMS_DRAFT, is_resp);
                QTP_FREE(ssl->quic.transport_peer, ssl->heap);
                ssl->quic.transport_peer = NULL;
            }
            else {
                ret = TLSX_QuicTP_Use(ssl, TLSX_KEY_QUIC_TP_PARAMS, is_resp);
                QTP_FREE(ssl->quic.transport_peer_draft,
                                        ssl->heap);
                ssl->quic.transport_peer_draft = NULL;
            }
        }
        else {
            if (ssl->quic.transport_version == TLSX_KEY_QUIC_TP_PARAMS_DRAFT
                && ssl->quic.transport_peer_draft) {
                ret = TLSX_QuicTP_Use(ssl, TLSX_KEY_QUIC_TP_PARAMS_DRAFT,
                                      is_resp);
            }
            else if (ssl->quic.transport_peer) {
                ret = TLSX_QuicTP_Use(ssl, TLSX_KEY_QUIC_TP_PARAMS, is_resp);
            }
            else {
                /* no match, send none, will let the client fail */
            }
        }
    }
    else {
        /* client hello */
        if (ssl->quic.transport_version == 0) {
            /* not being set to a particular id, we send both draft+v1 */
            ret = TLSX_QuicTP_Use(ssl, TLSX_KEY_QUIC_TP_PARAMS, is_resp)
                || TLSX_QuicTP_Use(ssl, TLSX_KEY_QUIC_TP_PARAMS_DRAFT, is_resp);
        }
        else {
            /* otherwise, send the version configured */
            ret = TLSX_QuicTP_Use(ssl, (TLSX_Type)ssl->quic.transport_version,
                                  is_resp);
        }
    }
    return ret;
}


#define QUIC_HS_FLIGHT_LIMIT_DEFAULT      (16*  1024)

size_t wolfSSL_quic_max_handshake_flight_len(const WOLFSSL* ssl,
                                             WOLFSSL_ENCRYPTION_LEVEL level)
{
    switch (level) {
        case wolfssl_encryption_initial:
        case wolfssl_encryption_application:
                return QUIC_HS_FLIGHT_LIMIT_DEFAULT;
        case wolfssl_encryption_early_data:
            return 0; /* QUIC does not send at this level */
        case wolfssl_encryption_handshake:
            /* during handshake itself, certificates may be exchanged which
             * exceed our default limit, advise a higher limit one.
             */
            if (ssl->options.side == WOLFSSL_SERVER_END) {
                if (ssl->options.verifyPeer
                    && MAX_CERTIFICATE_SZ > QUIC_HS_FLIGHT_LIMIT_DEFAULT)
                    return MAX_CERTIFICATE_SZ;
            }
            else {
                /* clients may receive the server cert chain
                 */
                if (2*MAX_CERTIFICATE_SZ > QUIC_HS_FLIGHT_LIMIT_DEFAULT)
                    return 2*MAX_CERTIFICATE_SZ;
            }
            return QUIC_HS_FLIGHT_LIMIT_DEFAULT;
    }
    return 0;
}


#ifdef WOLFSSL_EARLY_DATA
void wolfSSL_set_quic_early_data_enabled(WOLFSSL* ssl, int enabled)
{
    /* This only has effect on server and when the handshake has
     * not started yet.
     * This function is part of the quictls/openssl API and does
     * not return any error, sadly. So we just ignore any
     * unsuccessful use. But we can produce some warnings.
     */
    if (!WOLFSSL_IS_QUIC(ssl)) {
        WOLFSSL_MSG("wolfSSL_set_quic_early_data_enabled: not a QUIC SSL");
    }
    else if (ssl->options.handShakeState != NULL_STATE) {
        WOLFSSL_MSG("wolfSSL_set_quic_early_data_enabled: handshake started");
    }
    else {
        wolfSSL_set_max_early_data(ssl, enabled ? UINT32_MAX : 0);
    }
}
#endif /* WOLFSSL_EARLY_DATA */

int wolfSSL_quic_do_handshake(WOLFSSL* ssl)
{
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_quic_do_handshake");

    if (!wolfSSL_is_quic(ssl)) {
        WOLFSSL_MSG("WOLFSSL_QUIC_DO_HANDSHAKE not a QUIC SSL");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    while (ssl->options.handShakeState != HANDSHAKE_DONE) {
        /* Peculiar: do_handshake() is successful, but the state
         * indicates that we are not DONE. This seems to happen
         * when resuming sessions and an EARLY_DATA indicator
         * is presented by the client.
         * Theory: wolfSSL expects the APP to read the early data
         * and silently continues the handshake when the EndOfEarlyData
         * and the client Finished arrives.
         * This confuses the QUIC state handling.
         */
#ifdef WOLFSSL_EARLY_DATA
        if (ssl->options.maxEarlyDataSz) {
            byte tmpbuffer[256];
            int len;

            if (ssl->options.side == WOLFSSL_CLIENT_END) {
                if (ssl->options.resuming) {
                    ret = wolfSSL_write_early_data(ssl, tmpbuffer, 0, &len);
                }
            }
            else {
                ret = wolfSSL_read_early_data(ssl, tmpbuffer,
                                              sizeof(tmpbuffer), &len);
                if (ret < 0 && ssl->error == WC_NO_ERR_TRACE(ZERO_RETURN)) {
                    /* this is expected, since QUIC handles the actual early
                     * data separately. */
                    ret = WOLFSSL_SUCCESS;
                }
            }
            if (ret < 0) {
                goto cleanup;
            }
        }
#endif /* WOLFSSL_EARLY_DATA */

        ret = wolfSSL_SSL_do_handshake_internal(ssl);
        if (ret <= 0)
            goto cleanup;
    }

cleanup:
    if (ret <= 0
        && ssl->options.handShakeState == HANDSHAKE_DONE
        && (ssl->error == WC_NO_ERR_TRACE(ZERO_RETURN) ||
            ssl->error == WC_NO_ERR_TRACE(WANT_READ)))
    {
        ret = WOLFSSL_SUCCESS;
    }
    if (ret == WOLFSSL_SUCCESS) {
        ssl->error = WOLFSSL_ERROR_NONE;
    }
    WOLFSSL_LEAVE("wolfSSL_quic_do_handshake", ret);
    return ret;
}

int wolfSSL_quic_read_write(WOLFSSL* ssl)
{
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_quic_read_write");

    if (!wolfSSL_is_quic(ssl)) {
        WOLFSSL_MSG("WOLFSSL_QUIC_READ_WRITE not a QUIC SSL");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (ssl->options.handShakeState != HANDSHAKE_DONE) {
        ret = wolfSSL_quic_do_handshake(ssl);
        if (ret != WOLFSSL_SUCCESS)
            goto cleanup;
    }

    ret = wolfSSL_process_quic_post_handshake(ssl);

cleanup:
    WOLFSSL_LEAVE("wolfSSL_quic_read_write", ret);
    return ret;
}

int wolfSSL_process_quic_post_handshake(WOLFSSL* ssl)
{
    int ret = WOLFSSL_SUCCESS, nret;

    WOLFSSL_ENTER("wolfSSL_process_quic_post_handshake");

    if (!wolfSSL_is_quic(ssl)) {
        WOLFSSL_MSG("WOLFSSL_QUIC_POST_HS not a QUIC SSL");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (ssl->options.handShakeState != HANDSHAKE_DONE) {
        WOLFSSL_MSG("WOLFSSL_QUIC_POST_HS handshake is not done yet");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    while (ssl->quic.input_head != NULL
           || ssl->buffers.inputBuffer.length > 0) {
        if ((nret = ProcessReply(ssl)) < 0) {
            ret = nret;
            break;
        }
    }
    while (ssl->buffers.outputBuffer.length > 0) {
        SendBuffered(ssl);
    }

cleanup:
    WOLFSSL_LEAVE("wolfSSL_process_quic_post_handshake", ret);
    return ret;
}


int wolfSSL_provide_quic_data(WOLFSSL* ssl, WOLFSSL_ENCRYPTION_LEVEL level,
                              const uint8_t* data, size_t len)
{
    int ret = WOLFSSL_SUCCESS;
    size_t l;

    WOLFSSL_ENTER("wolfSSL_provide_quic_data");
    if (!wolfSSL_is_quic(ssl)) {
        WOLFSSL_MSG("WOLFSSL_QUIC_PROVIDE_DATA not a QUIC SSL");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (level < wolfSSL_quic_read_level(ssl)
        || (ssl->quic.input_tail && level < ssl->quic.input_tail->level)
        || level < ssl->quic.enc_level_latest_recvd) {
        WOLFSSL_MSG("WOLFSSL_QUIC_PROVIDE_DATA wrong encryption level");
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    while (len > 0) {
        if (ssl->quic.scratch) {
            if (ssl->quic.scratch->level != level) {
                WOLFSSL_MSG("WOLFSSL_QUIC_PROVIDE_DATA wrong encryption level");
                ret = WOLFSSL_FAILURE;
                goto cleanup;
            }

            ret = quic_record_append(ssl, ssl->quic.scratch, data, len, &l);
            if (ret != WOLFSSL_SUCCESS) {
                goto cleanup;
            }
            data += l;
            len -= l;
            if (quic_record_complete(ssl->quic.scratch)) {
                if (ssl->quic.input_tail) {
                    ssl->quic.input_tail->next = ssl->quic.scratch;
                    ssl->quic.input_tail = ssl->quic.scratch;
                }
                else {
                    ssl->quic.input_head = ssl->quic.input_tail =
                        ssl->quic.scratch;
                }
                ssl->quic.scratch = NULL;
            }
        }
        else {
            /* start of next record with all bytes for the header */
            ssl->quic.scratch = quic_record_make(ssl, level, data, len);
            if (!ssl->quic.scratch) {
                ret = WOLFSSL_FAILURE;
                goto cleanup;
            }
        }
    }

    ssl->quic.enc_level_latest_recvd = level;

cleanup:
    WOLFSSL_LEAVE("wolfSSL_provide_quic_data", ret);
    return ret;
}


/* Called internally when SSL wants a certain amount of input. */
int wolfSSL_quic_receive(WOLFSSL* ssl, byte* buf, word32 sz)
{
    sword32 n = 0;
    int transferred = 0;

    WOLFSSL_ENTER("wolfSSL_quic_receive");
    while (sz > 0) {
        n = 0;
        if (ssl->quic.input_head) {
            n = quic_record_transfer(ssl->quic.input_head, buf, sz);

            /* record too small to be fit into a RecordLayerHeader struct. */
            if (n == -1) {
                return WOLFSSL_FATAL_ERROR;
            }
            if (quic_record_done(ssl->quic.input_head)) {
                QuicRecord* qr = ssl->quic.input_head;
                ssl->quic.input_head = qr->next;
                if (!qr->next) {
                    ssl->quic.input_tail = NULL;
                }
                quic_record_free(ssl, qr);
            }
        }

        if (n == 0) {
            if (transferred > 0) {
                goto cleanup;
            }
            ssl->error = transferred = WANT_READ;
            goto cleanup;
        }
        sz -= (word32)n;
        buf += n;
        transferred += (int)n;
    }
cleanup:
    WOLFSSL_LEAVE("wolfSSL_quic_receive", transferred);
    return transferred;
}

/**
 * We need to forward the HANDSHAKE messages to the QUIC protocol stack
 * via ssl->quic.method->add_handshake_data().
 * The messages in the output buffer are unencrypted TLS records. We need
 * to forward the content of those records.
 */
static int wolfSSL_quic_send_internal(WOLFSSL* ssl)
{
    int ret = 0, aret;
    size_t len;
    RecordLayerHeader* rl;
    word16 rlen;
    word32 idx, length;
    byte* output;

    WOLFSSL_ENTER("wolfSSL_quic_send");

    idx = ssl->buffers.outputBuffer.idx;
    length = ssl->buffers.outputBuffer.length;
    output = ssl->buffers.outputBuffer.buffer + idx;
    while (length > 0) {
        if (ssl->quic.output_rec_remain > 0) {
            len = ssl->quic.output_rec_remain;
            if (len > length) {
                len = length;
            }

            aret = ssl->quic.method->add_handshake_data(ssl,
                ssl->quic.output_rec_level, (const uint8_t*)output, len);
            if (aret != 1) {
                /* The application has an error. General disaster. */
                WOLFSSL_MSG("WOLFSSL_QUIC_SEND application failed");
                ret = FWRITE_ERROR;
                goto cleanup;
            }
            output += len;
            length -= (word32)len;
            ssl->quic.output_rec_remain -= (word32)len;
        }
        else {
            /* at start of a TLS Record */
            rl = (RecordLayerHeader*)output;
            ato16(rl->length, &rlen);
            output += RECORD_HEADER_SZ;
            length -= RECORD_HEADER_SZ;
            ssl->quic.output_rec_remain = rlen;
            ssl->quic.output_rec_level = ssl->quic.enc_level_write;
            if (rl->type == application_data) {
                if (ssl->options.handShakeState != HANDSHAKE_DONE) {
                    ssl->quic.output_rec_level = wolfssl_encryption_early_data;
                }
                else {
                    WOLFSSL_MSG("WOLFSSL_QUIC_SEND app data after handshake");
                    ret = FWRITE_ERROR;
                    goto cleanup;
                }
            }
        }
    }

    ssl->buffers.outputBuffer.idx = 0;
    ssl->buffers.outputBuffer.length = 0;

cleanup:
    WOLFSSL_LEAVE("wolfSSL_quic_send", ret);
    return ret;
}

int wolfSSL_quic_send(WOLFSSL* ssl)
{
    return wolfSSL_quic_send_internal(ssl);
}

int wolfSSL_quic_forward_secrets(WOLFSSL* ssl, int ktype, int side)
{
    const uint8_t* rx_secret = NULL, *tx_secret = NULL;
    WOLFSSL_ENCRYPTION_LEVEL level;
    int ret = 0;

    WOLFSSL_ENTER("wolfSSL_quic_forward_secrets");
    switch (ktype) {
        case early_data_key:
            level = wolfssl_encryption_early_data;
            break;
        case handshake_key:
            level = wolfssl_encryption_handshake;
            break;
        case traffic_key:
            FALL_THROUGH;
        case update_traffic_key:
            level = wolfssl_encryption_application;
            break;
        case no_key:
            FALL_THROUGH;
        default:
            /* ignore */
            goto cleanup;
    }

    if (side == ENCRYPT_AND_DECRYPT_SIDE || side == ENCRYPT_SIDE_ONLY) {
        tx_secret = (ssl->options.side == WOLFSSL_CLIENT_END) ?
            ssl->clientSecret : ssl->serverSecret;
    }
    if (side == ENCRYPT_AND_DECRYPT_SIDE || side == DECRYPT_SIDE_ONLY) {
        rx_secret = (ssl->options.side == WOLFSSL_CLIENT_END) ?
            ssl->serverSecret : ssl->clientSecret;
    }

    if (!tx_secret && !rx_secret) {
        WOLFSSL_MSG("WOLFSSL_QUIC_FORWARD_SECRETS neither "
                    "enc- nor decrypt specified");
        goto cleanup;
    }

    if(!ssl->quic.method->set_encryption_secrets(
        ssl, level, rx_secret, tx_secret, ssl->specs.hash_size)) {
        WOLFSSL_MSG("WOLFSSL_QUIC_FORWARD_SECRETS failed");
        ret = WOLFSSL_FATAL_ERROR;
        goto cleanup;
    }

    /* Having installed the secrets, any future read/write will happen
     * at the level. Except early data, which is detected on the record
     * type and the handshake state. */
     if (ktype == early_data_key) {
        goto cleanup;
     }

     if (tx_secret && ssl->quic.enc_level_write != level) {
        ssl->quic.enc_level_write_next = level;
     }
     if (rx_secret && ssl->quic.enc_level_read != level) {
        ssl->quic.enc_level_read_next = level;
     }

cleanup:
    WOLFSSL_LEAVE("wolfSSL_quic_forward_secrets", ret);
    return ret;
}

int wolfSSL_quic_keys_active(WOLFSSL* ssl, enum encrypt_side side)
{
    int ret = 0;

    WOLFSSL_ENTER("wolfSSL_quic_keys_active");
    /* Keys derived from recent secrets have been activated */
    if (side == ENCRYPT_AND_DECRYPT_SIDE || side == ENCRYPT_SIDE_ONLY) {
        /* If there is data in the output buffers, it was supposed to be
         * encrypted at the previous level. We need to remember that when
         * forwarding this data to the QUIC protocol application. */
        if (ssl->buffers.outputBuffer.length > 0) {
            ret = wolfSSL_quic_send_internal(ssl);
            if (ret)
                goto cleanup;
        }
        ssl->quic.enc_level_write = ssl->quic.enc_level_write_next;
    }
    if (side == ENCRYPT_AND_DECRYPT_SIDE || side == DECRYPT_SIDE_ONLY) {
        ssl->quic.enc_level_read = ssl->quic.enc_level_read_next;
    }
cleanup:
    WOLFSSL_LEAVE("wolfSSL_quic_keys_active", ret);
    return ret;
}

const WOLFSSL_EVP_CIPHER* wolfSSL_quic_get_aead(WOLFSSL* ssl)
{
    WOLFSSL_CIPHER* cipher = NULL;
    const WOLFSSL_EVP_CIPHER* evp_cipher = NULL;

    if (ssl == NULL) {
        return NULL;
    }

    cipher = wolfSSL_get_current_cipher(ssl);

    if (cipher == NULL) {
        return NULL;
    }

    switch (cipher->cipherSuite) {
#if !defined(NO_AES) && defined(HAVE_AESGCM)
        case TLS_AES_128_GCM_SHA256:
            evp_cipher = wolfSSL_EVP_aes_128_gcm();
            break;
        case TLS_AES_256_GCM_SHA384:
            evp_cipher = wolfSSL_EVP_aes_256_gcm();
            break;
#endif
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
        case TLS_CHACHA20_POLY1305_SHA256:
            evp_cipher = wolfSSL_EVP_chacha20_poly1305();
            break;
#endif
#if !defined(NO_AES) && defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128)
        case TLS_AES_128_CCM_SHA256:
            evp_cipher = wolfSSL_EVP_aes_128_ccm();
            break;
        case TLS_AES_128_CCM_8_SHA256:
            WOLFSSL_MSG("wolfSSL_quic_get_aead: no CCM-8 support in EVP layer");
            evp_cipher = NULL;
            break;
#endif

        default:
            evp_cipher = NULL;
            break;
    }

    if (!evp_cipher) {
        /* should not happen, as SSL* should not have negotiated it? */
        WOLFSSL_MSG("wolfSSL_quic_get_aead: current cipher not supported");
        return NULL;
    }
    return evp_cipher;
}

/* currently only used if HAVE_CHACHA && HAVE_POLY1305. */
WC_MAYBE_UNUSED static int evp_cipher_eq(const WOLFSSL_EVP_CIPHER* c1,
                         const WOLFSSL_EVP_CIPHER* c2)
{
    /* We could check on nid equality, but we seem to have singulars */
    return c1 == c2;
}

const WOLFSSL_EVP_CIPHER* wolfSSL_quic_get_hp(WOLFSSL* ssl)
{
    WOLFSSL_CIPHER* cipher = NULL;
    const WOLFSSL_EVP_CIPHER* evp_cipher = NULL;

    if (ssl == NULL) {
        return NULL;
    }

    cipher = wolfSSL_get_current_cipher(ssl);

    if (cipher == NULL) {
        return NULL;
    }

    switch (cipher->cipherSuite) {
#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_COUNTER)
        /* This has to be CTR even though the spec says that ECB is used for
         * mask generation. ngtcp2_crypto_hp_mask uses a hack where they pass
         * in the "ECB" input as the IV for the CTR cipher and then the input
         * is just a cleared buffer. They do this so that the EVP
         * init-update-final cycle can be used without the padding that is added
         * for EVP_aes_(128|256)_ecb. */
#if defined(WOLFSSL_AES_128)
        case TLS_AES_128_GCM_SHA256:
            evp_cipher = wolfSSL_EVP_aes_128_ctr();
            break;
#endif
#if defined(WOLFSSL_AES_256)
        case TLS_AES_256_GCM_SHA384:
            evp_cipher = wolfSSL_EVP_aes_256_ctr();
            break;
#endif
#endif
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
        case TLS_CHACHA20_POLY1305_SHA256:
            evp_cipher = wolfSSL_EVP_chacha20();
            break;
#endif
#if !defined(NO_AES) && defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128) && \
        defined(WOLFSSL_AES_COUNTER)
        /* This has to be CTR. See comment above. */
        case TLS_AES_128_CCM_SHA256:
            evp_cipher = wolfSSL_EVP_aes_128_ctr();
            break;
        case TLS_AES_128_CCM_8_SHA256:
            WOLFSSL_MSG("wolfSSL_quic_get_hp: no CCM-8 support in EVP layer");
            evp_cipher = NULL;
            break;
#endif
        default:
            evp_cipher = NULL;
            break;
    }

    if (!evp_cipher) {
        /* should not happen, as SSL* should not have negotiated it? */
        WOLFSSL_MSG("wolfSSL_quic_get_hp: current cipher not supported");
        return NULL;
    }
    return evp_cipher;
}

size_t wolfSSL_quic_get_aead_tag_len(const WOLFSSL_EVP_CIPHER* aead_cipher)
{
    size_t ret;
#ifdef WOLFSSL_SMALL_STACK
    WOLFSSL_EVP_CIPHER_CTX *ctx = wolfSSL_EVP_CIPHER_CTX_new();
    if (ctx == NULL)
        return 0;
#else
    WOLFSSL_EVP_CIPHER_CTX ctx[1];
#endif

    XMEMSET(ctx, 0, sizeof(*ctx));
    if (wolfSSL_EVP_CipherInit(ctx, aead_cipher, NULL, NULL, 0)
        == WOLFSSL_SUCCESS) {
        ret = (size_t)ctx->authTagSz;
    } else {
        ret = 0;
    }

    (void)wolfSSL_EVP_CIPHER_CTX_cleanup(ctx);
#ifdef WOLFSSL_SMALL_STACK
    XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
#endif

    return ret;
}

int wolfSSL_quic_aead_is_gcm(const WOLFSSL_EVP_CIPHER* aead_cipher)
{
    return WOLFSSL_EVP_CIPHER_mode(aead_cipher) == WOLFSSL_EVP_CIPH_GCM_MODE;
}

int wolfSSL_quic_aead_is_ccm(const WOLFSSL_EVP_CIPHER* aead_cipher)
{
    return WOLFSSL_EVP_CIPHER_mode(aead_cipher) == WOLFSSL_EVP_CIPH_CCM_MODE;
}

int wolfSSL_quic_aead_is_chacha20(const WOLFSSL_EVP_CIPHER* aead_cipher)
{
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
    return evp_cipher_eq(aead_cipher, wolfSSL_EVP_chacha20_poly1305());
#else
    (void)aead_cipher;
    return 0;
#endif
}

const WOLFSSL_EVP_MD* wolfSSL_quic_get_md(WOLFSSL* ssl)
{
    /* a copy from the handshake md setup */
    switch(ssl->specs.mac_algorithm) {
        case no_mac:
    #ifndef NO_MD5
        case md5_mac:
            return wolfSSL_EVP_md5();
    #endif
    #ifndef NO_SHA
        case sha_mac:
            return wolfSSL_EVP_sha1();
    #endif
    #ifdef WOLFSSL_SHA224
        case sha224_mac:
            return wolfSSL_EVP_sha224();
    #endif
        case sha256_mac:
            return wolfSSL_EVP_sha256();
    #ifdef WOLFSSL_SHA384
        case sha384_mac:
            return wolfSSL_EVP_sha384();
    #endif
    #ifdef WOLFSSL_SHA512
        case sha512_mac:
            return wolfSSL_EVP_sha512();
    #endif
        case rmd_mac:
        case blake2b_mac:
            WOLFSSL_MSG("no suitable EVP_MD");
            return NULL;
        default:
            WOLFSSL_MSG("Unknown mac algorithm");
            return NULL;
    }
}

#ifdef OPENSSL_EXTRA

int wolfSSL_quic_hkdf_extract(uint8_t* dest, const WOLFSSL_EVP_MD* md,
                              const uint8_t* secret, size_t secretlen,
                              const uint8_t* salt, size_t saltlen)
{
    WOLFSSL_EVP_PKEY_CTX* pctx = NULL;
    size_t destlen = (size_t)wolfSSL_EVP_MD_size(md);
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_quic_hkdf_extract");

    pctx = wolfSSL_EVP_PKEY_CTX_new_id(WC_NID_hkdf, NULL);
    if (pctx == NULL) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (wolfSSL_EVP_PKEY_derive_init(pctx) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_hkdf_mode(
                pctx, WOLFSSL_EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set_hkdf_md(pctx, md) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_salt(
                pctx, (byte*)salt, (int)saltlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_key(
                pctx, (byte*)secret, (int)secretlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_derive(pctx, dest, &destlen) != WOLFSSL_SUCCESS) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

cleanup:
    if (pctx)
        wolfSSL_EVP_PKEY_CTX_free(pctx);
    WOLFSSL_LEAVE("wolfSSL_quic_hkdf_extract", ret);
    return ret;
}


int wolfSSL_quic_hkdf_expand(uint8_t* dest, size_t destlen,
                             const WOLFSSL_EVP_MD* md,
                             const uint8_t* secret, size_t secretlen,
                             const uint8_t* info, size_t infolen)
{
    WOLFSSL_EVP_PKEY_CTX* pctx = NULL;
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_quic_hkdf_expand");

    pctx = wolfSSL_EVP_PKEY_CTX_new_id(WC_NID_hkdf, NULL);
    if (pctx == NULL) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (wolfSSL_EVP_PKEY_derive_init(pctx) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_hkdf_mode(
                pctx, WOLFSSL_EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set_hkdf_md(pctx, md) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_salt(
                pctx, (byte*)"", 0) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_key(
                pctx, (byte*)secret, (int)secretlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_add1_hkdf_info(
                pctx, (byte*)info, (int)infolen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_derive(pctx, dest, &destlen) != WOLFSSL_SUCCESS) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

cleanup:
    if (pctx)
        wolfSSL_EVP_PKEY_CTX_free(pctx);
    WOLFSSL_LEAVE("wolfSSL_quic_hkdf_expand", ret);
    return ret;
}


int wolfSSL_quic_hkdf(uint8_t* dest, size_t destlen,
                      const WOLFSSL_EVP_MD* md,
                      const uint8_t* secret, size_t secretlen,
                      const uint8_t* salt, size_t saltlen,
                      const uint8_t* info, size_t infolen)
{
    WOLFSSL_EVP_PKEY_CTX* pctx = NULL;
    int ret = WOLFSSL_SUCCESS;

    WOLFSSL_ENTER("wolfSSL_quic_hkdf");

    pctx = wolfSSL_EVP_PKEY_CTX_new_id(WC_NID_hkdf, NULL);
    if (pctx == NULL) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

    if (wolfSSL_EVP_PKEY_derive_init(pctx) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_hkdf_mode(
                pctx, WOLFSSL_EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set_hkdf_md(pctx, md) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_salt(
                pctx, (byte*)salt, (int)saltlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_set1_hkdf_key(
                pctx, (byte*)secret, (int)secretlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_CTX_add1_hkdf_info(
                pctx, (byte*)info, (int)infolen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_PKEY_derive(pctx, dest, &destlen) != WOLFSSL_SUCCESS) {
        ret = WOLFSSL_FAILURE;
        goto cleanup;
    }

cleanup:
    if (pctx)
        wolfSSL_EVP_PKEY_CTX_free(pctx);
    WOLFSSL_LEAVE("wolfSSL_quic_hkdf", ret);
    return ret;
}

#endif /* OPENSSL_EXTRA */


WOLFSSL_EVP_CIPHER_CTX* wolfSSL_quic_crypt_new(const WOLFSSL_EVP_CIPHER* cipher,
                                               const uint8_t* key,
                                               const uint8_t* iv,
                                               int encrypt)
{
    WOLFSSL_EVP_CIPHER_CTX* ctx;

    ctx = wolfSSL_EVP_CIPHER_CTX_new();
    if (ctx == NULL) {
        return NULL;
    }

    if (wolfSSL_EVP_CipherInit(ctx, cipher, key, iv, encrypt)
            != WOLFSSL_SUCCESS) {
        wolfSSL_EVP_CIPHER_CTX_free(ctx);
        return NULL;
    }

    return ctx;
}


int wolfSSL_quic_aead_encrypt(uint8_t* dest, WOLFSSL_EVP_CIPHER_CTX* ctx,
                              const uint8_t* plain, size_t plainlen,
                              const uint8_t* iv, const uint8_t* aad,
                              size_t aadlen)
{
    int len;

    /* A case can be made if this really should be a function in wolfSSL, since
     * the same should be doable from the API by a QUIC protocol stack.
     * What speaks for this:
     * - it gives us a decent testing point
     * - API users do not have to re-invent (it fits into ngtcp2 use).
     *   picotls offers a similar abstraction level for AEAD.
     * TODO: there is some fiddling in OpenSSL+quic in regard to CCM ciphers
     *       which we need to check.
     */
    if (wolfSSL_EVP_CipherInit(ctx, NULL, NULL, iv, 1) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherUpdate(
                ctx, NULL, &len, aad, (int)aadlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherUpdate(
                ctx, dest, &len, plain, (int)plainlen) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherFinal(ctx, dest + len, &len) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CIPHER_CTX_ctrl(
                ctx, WOLFSSL_EVP_CTRL_AEAD_GET_TAG, ctx->authTagSz, dest + plainlen)
           != WOLFSSL_SUCCESS) {
        return WOLFSSL_FAILURE;
    }

    return WOLFSSL_SUCCESS;
}


int wolfSSL_quic_aead_decrypt(uint8_t* dest, WOLFSSL_EVP_CIPHER_CTX* ctx,
                              const uint8_t* enc, size_t enclen,
                              const uint8_t* iv, const uint8_t* aad,
                              size_t aadlen)
{
    int len;
    const uint8_t* tag;

    /* See rationale for wolfSSL_quic_aead_encrypt() on why this is here */
    if (enclen > INT_MAX || ctx->authTagSz > (int)enclen) {
        return WOLFSSL_FAILURE;
    }

    enclen -= (size_t)ctx->authTagSz;
    tag = enc + enclen;

    if (wolfSSL_EVP_CipherInit(ctx, NULL, NULL, iv, 0) != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CIPHER_CTX_ctrl(
                ctx, WOLFSSL_EVP_CTRL_AEAD_SET_TAG, ctx->authTagSz, (uint8_t*)tag)
            != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherUpdate(ctx, NULL, &len, aad, (int)aadlen)
            != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherUpdate(ctx, dest, &len, enc, (int)enclen)
            != WOLFSSL_SUCCESS
        || wolfSSL_EVP_CipherFinal(ctx, dest, &len) != WOLFSSL_SUCCESS) {
        return WOLFSSL_FAILURE;
    }

    return WOLFSSL_SUCCESS;
}


#endif /* WOLFSSL_QUIC */
#endif /* WOLFCRYPT_ONLY */

