/*
 * Copyright 2006 - 2018, Werner Dittmann
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"

#define SKEIN_ERR_CHECK 1
#include <cryptcommon/skeinApi.h>
#include <string.h>

int skeinCtxPrepare(SkeinCtx_t* ctx, SkeinSize_t size)
{
    Skein_Assert(ctx && size, SKEIN_FAIL);

    memset(ctx ,0, sizeof(SkeinCtx_t));
    ctx->skeinSize = size;

    return SKEIN_SUCCESS;
}

int skeinInit(SkeinCtx_t* ctx, size_t hashBitLen)
{
    int ret = SKEIN_FAIL;
    size_t Xlen = 0;
    u64b_t*  X = NULL;
    uint64_t treeInfo = SKEIN_CFG_TREE_INFO_SEQUENTIAL;

    Skein_Assert(ctx, SKEIN_FAIL);
    /*
     * The following two lines rely of the fact that the real Skein contexts are
     * a union in out context and thus have tha maximum memory available.
     * The beauty of C :-) .
     */
    X = ctx->m.s256.X;
    Xlen = (size_t)(ctx->skeinSize/8);
    /*
     * If size is the same and hash bit length is zero then reuse
     * the save chaining variables.
     */
    switch (ctx->skeinSize) {
    case Skein256:
        ret = Skein_256_InitExt(&ctx->m.s256, hashBitLen,
                                treeInfo, NULL, 0);
        break;
    case Skein512:
        ret = Skein_512_InitExt(&ctx->m.s512, hashBitLen,
                                treeInfo, NULL, 0);
        break;
    case Skein1024:
        ret = Skein1024_InitExt(&ctx->m.s1024, hashBitLen,
                                treeInfo, NULL, 0);
        break;

    default:
        break;
    }

    if (ret == SKEIN_SUCCESS) {
        /* Save chaining variables for this combination of size and hashBitLen */
        memcpy(ctx->XSave, X, Xlen);
    }
    return ret;
}

int skeinMacInit(SkeinCtx_t* ctx, const uint8_t *key, size_t keyLen,
                 size_t hashBitLen)
{
    int ret = SKEIN_FAIL;
    u64b_t*  X = NULL;
    size_t Xlen = 0;
    uint64_t treeInfo = SKEIN_CFG_TREE_INFO_SEQUENTIAL;

    Skein_Assert(ctx, SKEIN_FAIL);

    X = ctx->m.s256.X;
    Xlen = (size_t)(ctx->skeinSize/8);

    Skein_Assert(hashBitLen, SKEIN_BAD_HASHLEN);

    switch (ctx->skeinSize) {
    case Skein256:
        ret = Skein_256_InitExt(&ctx->m.s256, hashBitLen,
                                treeInfo,
                                key, keyLen);

        break;
    case Skein512:
        ret = Skein_512_InitExt(&ctx->m.s512, hashBitLen,
                                treeInfo,
                                key, keyLen);
        break;
    case Skein1024:
        ret = Skein1024_InitExt(&ctx->m.s1024, hashBitLen,
                                treeInfo,
                                key, keyLen);

        break;

    default:
        break;
    }
    if (ret == SKEIN_SUCCESS) {
        /* Save chaining variables for this combination of key, keyLen, hashBitLen */
        memcpy(ctx->XSave, X, Xlen);
    }
    return ret;
}

void skeinReset(SkeinCtx_t* ctx)
{
    size_t Xlen = 0;
    u64b_t*  X = NULL;

    /*
     * The following two lines rely of the fact that the real Skein contexts are
     * a union in out context and thus have tha maximum memory available.
     * The beauty of C :-) .
     */
    X = ctx->m.s256.X;
    Xlen = (size_t)(ctx->skeinSize/8);
    /*
     * If size is the same and hash bit length is zero then reuse
     * the save chaining variables.
     */
    /* Restore the chaining variable, reset byte counter */
    memcpy(X, ctx->XSave, Xlen);

    /* Setup context to process the message */
    Skein_Start_New_Type(&ctx->m, MSG);
}

int skeinUpdate(SkeinCtx_t *ctx, const uint8_t *msg,
                size_t msgByteCnt)
{
    int ret = SKEIN_FAIL;
    Skein_Assert(ctx, SKEIN_FAIL);

    switch (ctx->skeinSize) {
    case Skein256:
        ret = Skein_256_Update(&ctx->m.s256, msg, msgByteCnt);
        break;
    case Skein512:
        ret = Skein_512_Update(&ctx->m.s512, msg, msgByteCnt);
        break;
    case Skein1024:
        ret = Skein1024_Update(&ctx->m.s1024, msg, msgByteCnt);
        break;

    default:
        break;
    }
    return ret;

}

int skeinUpdateBits(SkeinCtx_t *ctx, const uint8_t *msg,
                    size_t msgBitCnt)
{
    /*
     * I've used the bit pad implementation from skein_test.c (see NIST CD)
     * and modified it to use the convenience functions and added some pointer
     * arithmetic.
     */
    size_t length;
    uint8_t mask;
    uint8_t* up;

    /* only the final Update() call is allowed do partial bytes, else assert an error */
    Skein_Assert((ctx->m.h.T[1] & SKEIN_T1_FLAG_BIT_PAD) == 0 || msgBitCnt == 0, SKEIN_FAIL);

    /* if number of bits is a multiple of bytes - that's easy */
    if ((msgBitCnt & 0x7) == 0) {
        return skeinUpdate(ctx, msg, msgBitCnt >> 3);
    }
    skeinUpdate(ctx, msg, (msgBitCnt >> 3) + 1);

    /*
     * The next line rely on the fact that the real Skein contexts
     * are a union in our context. After the addition the pointer points to
     * Skein's real partial block buffer.
     * If this layout ever changes we have to adapt this as well.
     */
    up = (uint8_t*)ctx->m.s256.X + ctx->skeinSize / 8;

    Skein_Set_Bit_Pad_Flag(ctx->m.h);                       /* set tweak flag for the skeinFinal call */

    /* now "pad" the final partial byte the way NIST likes */
    length = ctx->m.h.bCnt;                                 /* get the bCnt value (same location for all block sizes) */
    Skein_assert(length != 0);                              /* internal sanity check: there IS a partial byte in the buffer! */
    mask = (uint8_t) (1u << (7 - (msgBitCnt & 7)));         /* partial byte bit mask */
    up[length-1]  = (uint8_t)((up[length-1] & (0-mask))|mask);   /* apply bit padding on final byte (in the buffer) */

    return SKEIN_SUCCESS;
}

int skeinFinal(SkeinCtx_t* ctx, uint8_t* hash)
{
    int ret = SKEIN_FAIL;
    Skein_Assert(ctx, SKEIN_FAIL);

    switch (ctx->skeinSize) {
    case Skein256:
        ret = Skein_256_Final(&ctx->m.s256, hash);
        break;
    case Skein512:
        ret = Skein_512_Final(&ctx->m.s512, hash);
        break;
    case Skein1024:
        ret = Skein1024_Final(&ctx->m.s1024, hash);
        break;

    default:
        break;
    }
    return ret;
}

#pragma clang diagnostic pop