/*************************************************************************
* Copyright (C) 2013 Intel Corporation
*
* 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.
*************************************************************************/

/*
//
//  Purpose:
//     Cryptography Primitive.
//     AES-GCM
//
//  Contents:
//        ippsAES_GCMProcessIV()
//
*/

#include "owndefs.h"
#include "owncp.h"
#include "pcpaesm.h"
#include "pcptool.h"

#if (_IPP32E >= _IPP32E_K0)
#include "pcpaesauthgcm_avx512.h"
#else
#include "pcpaesauthgcm.h"
#endif /* #if(_IPP32E>=_IPP32E_K0) */

/*F*
//    Name: ippsAES_GCMProcessIV
//
// Purpose: IV processing.
//
// Returns:                Reason:
//    ippStsNullPtrErr        pState == NULL
//                            pIV ==NULL && ivLen>0
//    ippStsContextMatchErr   !AESGCM_VALID_ID()
//    ippStsLengthErr         ivLen <0
//    ippStsBadArgErr         illegal sequence call
//    ippStsNoErr             no errors
//
// Parameters:
//    pIV         pointer to the IV
//    ivLen       length of IV (it could be 0)
//    pState      pointer to the context
//
*F*/
IPPFUN(IppStatus, ippsAES_GCMProcessIV, (const Ipp8u* pIV, int ivLen, IppsAES_GCMState* pState))
{
    /* test pState pointer */
    IPP_BAD_PTR1_RET(pState);

    /* test IV pointer and length */
    IPP_BADARG_RET(ivLen && !pIV, ippStsNullPtrErr);
    IPP_BADARG_RET(ivLen < 0, ippStsLengthErr);

    /* use aligned context */
    pState = (IppsAES_GCMState*)(IPP_ALIGNED_PTR(pState, AESGCM_ALIGNMENT));
    /* test context validity */
    IPP_BADARG_RET(!AESGCM_VALID_ID(pState), ippStsContextMatchErr);

    /* According to the NIST Special Publication 800-38D (Recommendation for GCM
    * mode, p.5.2.1.1 Input Data) the IV shall be between 1 and 2^64 bits. */
    IPP_BADARG_RET(((AESGCM_IV_LEN(pState) + (Ipp64u)ivLen) < (Ipp64u)ivLen), ippStsScaleRangeErr);

    IPP_BADARG_RET(!(GcmInit == AESGCM_STATE(pState) || GcmIVprocessing == AESGCM_STATE(pState)),
                   ippStsBadArgErr);

    /* switch IVprocessing on */
    AESGCM_STATE(pState) = GcmIVprocessing;

#if (_IPP32E >= _IPP32E_K0)
    IvUpdate_ ivHashUpdate = AES_GCM_IV_UPDATE(pState);

    /* test if buffer is not empty */
    if (AESGCM_BUFLEN(pState)) {
        /* Cast to int here does not produce loss of data as AESGCM_BUFLEN <= BLOCK_SIZE, which is 16 bytes */
        int locLen = IPP_MIN(ivLen, BLOCK_SIZE - (int)AESGCM_BUFLEN(pState));
        CopyBlock((void*)pIV, (void*)(AESGCM_COUNTER(pState) + AESGCM_BUFLEN(pState)), locLen);
        AESGCM_BUFLEN(pState) += (Ipp64u)locLen;

        /* if buffer full */
        if (BLOCK_SIZE == AESGCM_BUFLEN(pState)) {
            ivHashUpdate(&AES_GCM_KEY_DATA(pState),
                         &AES_GCM_CONTEXT_DATA(pState),
                         AESGCM_COUNTER(pState),
                         BLOCK_SIZE);
            AESGCM_BUFLEN(pState) = 0;
        }

        AESGCM_IV_LEN(pState) += (Ipp64u)locLen;
        pIV += locLen;
        ivLen -= locLen;
    }

    /* process main part of IV */
    int lenBlks = ivLen & (-BLOCK_SIZE);
    if (lenBlks) {
        ivHashUpdate(&AES_GCM_KEY_DATA(pState),
                     &AES_GCM_CONTEXT_DATA(pState),
                     pIV,
                     (Ipp64u)lenBlks);
        AESGCM_IV_LEN(pState) += (Ipp64u)lenBlks;
        pIV += lenBlks;
        ivLen -= lenBlks;
    }

    /* copy the rest of IV into the buffer */
    if (ivLen) {
        CopyBlock((void*)pIV, (void*)(AESGCM_COUNTER(pState)), ivLen);
        AESGCM_IV_LEN(pState) += (Ipp64u)ivLen;
        AESGCM_BUFLEN(pState) = (Ipp64u)ivLen;
    }
#else
    /* test if buffer is not empty */
    if (AESGCM_BUFLEN(pState)) {
        int locLen = IPP_MIN(ivLen, BLOCK_SIZE - AESGCM_BUFLEN(pState));
        XorBlock(pIV,
                 AESGCM_COUNTER(pState) + AESGCM_BUFLEN(pState),
                 AESGCM_COUNTER(pState) + AESGCM_BUFLEN(pState),
                 locLen);
        AESGCM_BUFLEN(pState) += locLen;

        /* if buffer full */
        if (BLOCK_SIZE == AESGCM_BUFLEN(pState)) {
            MulGcm_ ghashFunc = AESGCM_HASH(pState);
            ghashFunc(AESGCM_COUNTER(pState), AESGCM_HKEY(pState), AesGcmConst_table);
            AESGCM_BUFLEN(pState) = 0;
        }

        AESGCM_IV_LEN(pState) += (Ipp64u)locLen;
        pIV += locLen;
        ivLen -= locLen;
    }

    /* process main part of IV */
    {
        int lenBlks = ivLen & (-BLOCK_SIZE);
        if (lenBlks) {
            Auth_ authFunc = AESGCM_AUTH(pState);
            authFunc(AESGCM_COUNTER(pState), pIV, lenBlks, AESGCM_HKEY(pState), AesGcmConst_table);
            AESGCM_IV_LEN(pState) += (Ipp64u)lenBlks;
            pIV += lenBlks;
            ivLen -= lenBlks;
        }
    }

    /* copy the rest of IV into the buffer */
    if (ivLen) {
        int locLen = IPP_MIN(ivLen, BLOCK_SIZE);
        XorBlock(pIV, AESGCM_COUNTER(pState), AESGCM_COUNTER(pState), locLen);
        AESGCM_IV_LEN(pState) += (Ipp64u)locLen;
        AESGCM_BUFLEN(pState) += locLen;
    }
#endif /* #if(_IPP32E>=_IPP32E_K0) */

    return ippStsNoErr;
}
