/*
 * Copyright 2018 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (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 <string.h>
#include <stdarg.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/ossl_typ.h>
#include "internal/nelem.h"
#include "internal/evp_int.h"
#include "evp_locl.h"

EVP_MAC_CTX *EVP_MAC_CTX_new_id(int id)
{
    const EVP_MAC *mac = EVP_get_macbynid(id);

    if (mac == NULL)
        return NULL;
    return EVP_MAC_CTX_new(mac);
}

EVP_MAC_CTX *EVP_MAC_CTX_new(const EVP_MAC *mac)
{
    EVP_MAC_CTX *ctx = OPENSSL_zalloc(sizeof(EVP_MAC_CTX));

    if (ctx == NULL || (ctx->data = mac->new()) == NULL) {
        EVPerr(EVP_F_EVP_MAC_CTX_NEW, ERR_R_MALLOC_FAILURE);
        OPENSSL_free(ctx);
        ctx = NULL;
    } else {
        ctx->meth = mac;
    }
    return ctx;
}

void EVP_MAC_CTX_free(EVP_MAC_CTX *ctx)
{
    if (ctx != NULL && ctx->data != NULL) {
        ctx->meth->free(ctx->data);
        ctx->data = NULL;
    }
    OPENSSL_free(ctx);
}

int EVP_MAC_CTX_copy(EVP_MAC_CTX *dst, EVP_MAC_CTX *src)
{
    EVP_MAC_IMPL *macdata;

    if (src->data != NULL && !dst->meth->copy(dst->data, src->data))
        return 0;

    macdata = dst->data;
    *dst = *src;
    dst->data = macdata;

    return 1;
}

const EVP_MAC *EVP_MAC_CTX_mac(EVP_MAC_CTX *ctx)
{
    return ctx->meth;
}

size_t EVP_MAC_size(EVP_MAC_CTX *ctx)
{
    if (ctx->data != NULL)
        return ctx->meth->size(ctx->data);
    /* If the MAC hasn't been initialized yet, we return zero */
    return 0;
}

int EVP_MAC_init(EVP_MAC_CTX *ctx)
{
    return ctx->meth->init(ctx->data);
}

int EVP_MAC_update(EVP_MAC_CTX *ctx, const unsigned char *data, size_t datalen)
{
    return ctx->meth->update(ctx->data, data, datalen);
}

int EVP_MAC_final(EVP_MAC_CTX *ctx, unsigned char *out, size_t *poutlen)
{
    int l = ctx->meth->size(ctx->data);

    if (l < 0)
        return 0;
    if (poutlen != NULL)
        *poutlen = l;
    if (out == NULL)
        return 1;
    return ctx->meth->final(ctx->data, out);
}

int EVP_MAC_ctrl(EVP_MAC_CTX *ctx, int cmd, ...)
{
    int ok = -1;
    va_list args;

    va_start(args, cmd);
    ok = EVP_MAC_vctrl(ctx, cmd, args);
    va_end(args);

    if (ok == -2)
        EVPerr(EVP_F_EVP_MAC_CTRL, EVP_R_COMMAND_NOT_SUPPORTED);

    return ok;
}

int EVP_MAC_vctrl(EVP_MAC_CTX *ctx, int cmd, va_list args)
{
    int ok = 1;

    if (ctx == NULL || ctx->meth == NULL)
        return -2;

    switch (cmd) {
#if 0
    case ...:
        /* code */
        ok = 1;
        break;
#endif
    default:
        if (ctx->meth->ctrl != NULL)
            ok = ctx->meth->ctrl(ctx->data, cmd, args);
        else
            ok = -2;
        break;
    }

    return ok;
}

int EVP_MAC_ctrl_str(EVP_MAC_CTX *ctx, const char *type, const char *value)
{
    int ok = 1;

    if (ctx == NULL || ctx->meth == NULL || ctx->meth->ctrl_str == NULL) {
        EVPerr(EVP_F_EVP_MAC_CTRL_STR, EVP_R_COMMAND_NOT_SUPPORTED);
        return -2;
    }

    ok = ctx->meth->ctrl_str(ctx->data, type, value);

    if (ok == -2)
        EVPerr(EVP_F_EVP_MAC_CTRL_STR, EVP_R_COMMAND_NOT_SUPPORTED);
    return ok;
}

int EVP_MAC_str2ctrl(EVP_MAC_CTX *ctx, int cmd, const char *value)
{
    size_t len;

    len = strlen(value);
    if (len > INT_MAX)
        return -1;
    return EVP_MAC_ctrl(ctx, cmd, value, len);
}

int EVP_MAC_hex2ctrl(EVP_MAC_CTX *ctx, int cmd, const char *hex)
{
    unsigned char *bin;
    long binlen;
    int rv = -1;

    bin = OPENSSL_hexstr2buf(hex, &binlen);
    if (bin == NULL)
        return 0;
    if (binlen <= INT_MAX)
        rv = EVP_MAC_ctrl(ctx, cmd, bin, (size_t)binlen);
    OPENSSL_free(bin);
    return rv;
}

int EVP_MAC_nid(const EVP_MAC *mac)
{
    return mac->type;
}
