#include "xcbcmac.h"

/* ȉRogawayĂ */
static char g1[XCBC_MAX_BYTES] = 
{
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01 
};

static char g2[XCBC_MAX_BYTES] = 
{
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02 
};

static char g3[XCBC_MAX_BYTES] = 
{
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03 
};

/* ubN̒̔ꍇɁA
 * 2Ԗڂ̌̔𐶐ƂɕKvȒǉ̃eLXg
 */
static char g4[XCBC_MAX_BYTES] = 
{
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04
};

int XCMAC_Init(XCMAC_CTX *mctx, EVP_CIPHER *c, const unsigned char *k)
{ 
    EVP_CIPHER_CTX tctx;
    int            i, outl, bl, kl;

    EVP_EncryptInit(&tctx, c, (unsigned char *)k, 0);

    kl = EVP_CIPHER_CTX_key_length(&tctx);
    bl = EVP_CIPHER_CTX_block_size(&tctx);

    if (kl != bl && bl * 2 != kl)
        return -1;
    EVP_EncryptUpdate(&tctx, mctx->dk1, &outl, g1, bl);

    if (kl != bl)
        EVP_EncryptUpdate(&tctx, &(mctx->dk1[bl]), &outl, g4, bl);
    EVP_EncryptUpdate(&tctx, mctx->dk2, &outl, g2, bl);
    EVP_EncryptUpdate(&tctx, mctx->dk3, &outl, g3, bl);
  
    EVP_EncryptInit(&(mctx->cctx), c, mctx->dk1, 0);

    if (EVP_CIPHER_CTX_mode(&(mctx->cctx)) != EVP_CIPH_ECB_MODE) 
        return -2;

    mctx->worklen = 0;
    mctx->started = 0;
    for (i = 0;  i < bl;  i++) 
        mctx->cbcstate[i] = 0;
    return 0;
}

int XCMAC_Update(XCMAC_CTX *mctx, const char *data, int len) 
{
    int bl, i, n = 0, outl;

    if (!len) 
        return 0;

    bl = EVP_CIPHER_CTX_block_size(&(mctx->cctx));
    for (i = 0;  i < len;  i++) 
    {
        if (!mctx->worklen && mctx->started) 
            EVP_EncryptUpdate(&(mctx->cctx), mctx->cbcstate, &outl,
                             mctx->workspace, bl);
        else 
            mctx->started = 1;
        mctx->workspace[mctx->worklen] = data[n++] ^ mctx->cbcstate[mctx->worklen];
        mctx->worklen++;
        mctx->worklen %= bl;
    }
    return 0;
}

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

    if (!mctx->started) 
        return -1;
    if (mctx->worklen) 
    {
        /* pfBOAK2XORĂÍ */
        mctx->workspace[mctx->worklen] = 0x90 ^ mctx->cbcstate[mctx->worklen];
        for (i = mctx->worklen + 1;  i < bl;  i++) 
            mctx->workspace[i] = mctx->cbcstate[mctx->worklen]; /* ^ 0 */
        for (i = 0;  i < bl;  i++) 
            mctx->workspace[i] ^= mctx->dk2[i];
    } 
    else 
    {
        /* K3XORĂÍ */
        for (i = 0;  i < bl;  i++) 
            mctx->workspace[i] ^= mctx->dk3[i];
    }
    EVP_EncryptUpdate(&(mctx->cctx), out, outl, mctx->workspace, bl);
    return 0;
}

int XCMAC(EVP_CIPHER *c, const char *key, unsigned char *str, int sz, 
      unsigned char *out, int *outlen) 
{
    XCMAC_CTX x;
    int       e;

    if ((e = XCMAC_Init(&x, c, key))) 
        return e; 
    if ((e = XCMAC_Update(&x, str, sz))) 
        return e; 
    return XCMAC_Final(&x, out, outlen);
}
