#include "cbcmac.h"

int CBCMAC_Init(CBCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k)
{
    int i, bl;

    EVP_EncryptInit(&(mctx->cctx), c, (unsigned char *)k, 0);
    if (EVP_CIPHER_CTX_mode(&(mctx->cctx)) != EVP_CIPH_ECB_MODE) 
        return -1;
    mctx->worklen = 0;
    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));
    for (i = 0;  i < bl;  i++)
        mctx->cbcstate[i] = 0;
    return 0;
}

/* Ō̃ubN̈̂߂ɁACBC[h
 * Iȃ蓖Ă
 */
int CBCMAC_Update(CBCMAC_CTX *mctx, const char *data, int len)
{
    int bl, i, n = 0, outl;

    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));

    if (mctx->worklen)
    {
        n = bl - mctx->worklen;
        if (n > len) /* ubNɎȂƂAc𖄂߂ */
        {
            for (i = 0;  i < len;  i++)
                mctx->workspace[mctx->worklen + i] = data[i];
            mctx->worklen += len;
            return 0;
        }
        else
        {
            for (i = 0;  i < n;  i++)
                mctx->workspace[mctx->worklen + i] = data[i] ^ mctx->cbcstate[i];
            EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl, 
                             mctx->workspace, bl);
        }
    }
    while (n < len)
    {
        for (i = 0;  i < bl;  i++)
            mctx->workspace[i] = data[n + i] ^ mctx->cbcstate[i];
        n = n + bl;
        EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl, 
                         mctx->workspace, bl);
    }
    mctx->worklen = len - n;
    for (i = 0;  i < mctx->worklen;  i++)
        mctx->workspace[i] = data[n + i];
    return 0;
}

int CBCMAC_Final(CBCMAC_CTX *mctx, unsigned char *out, int *outl)
{
    int i, bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));

    /* KvłNULLoCggăpfBO
     * x ^ 0 = xȂ̂ŁAۂɂCBC̏ԂRs[΂悢 
     */
    if (mctx->worklen)
    {
        for (i = mctx->worklen;  i < bl;  i++)
            mctx->workspace[i] = mctx->cbcstate[i];
        EVP_EncryptUpdate(&(mctx->cctx), out, outl, mctx->workspace, bl);
    }
    else
    {
        for (i = 0;  i < bl;  i++)
            out[i] = mctx->cbcstate[i];
        *outl = bl;
    }
    return 0;
}

int CBCMAC(EVP_CIPHER *c, const char *key, int key_len, unsigned char *str,
       int sz, unsigned char *out, int *outlen)
{
    CBCMAC_CTX x;
    int        e;

    if ((e = CBCMAC_Init(&x, c, key)))
        return e;
    if ((e = CBCMAC_Update(&x, str, sz)))
        return e;
    return CBCMAC_Final(&x, out, outlen);
}
