/*************************************************************************
* 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_GCMProcessAAD()
//
*/

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

#if (_ALG_AES_SAFE_ == _ALG_AES_SAFE_COMPACT_SBOX_)
#include "pcprijtables.h"
#endif

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

/*F*
//    Name: ippsAES_GCMProcessAAD
//
// Purpose: AAD processing.
//
// Returns:                Reason:
//    ippStsNullPtrErr        pState == NULL
//                            pAAD == NULL, aadLen>0
//    ippStsContextMatchErr   !AESGCM_VALID_ID()
//    ippStsLengthErr         aadLen <0
//    ippStsBadArgErr         illegal sequence call
//    ippStsNoErr             no errors
//
// Parameters:
//    pAAD        pointer to the AAD
//    aadlen      length of AAD (it could be 0)
//    pState      pointer to the context
//
*F*/
IPPFUN(IppStatus, ippsAES_GCMProcessAAD, (const Ipp8u* pAAD, int aadLen, IppsAES_GCMState* pState))
{
    /* test pState pointer */
    IPP_BAD_PTR1_RET(pState);
    /* use aligned context */
    pState = (IppsAES_GCMState*)(IPP_ALIGNED_PTR(pState, AESGCM_ALIGNMENT));
    /* test if context is valid */
    IPP_BADARG_RET(!AESGCM_VALID_ID(pState), ippStsContextMatchErr);

    /* test AAD pointer and length */
    IPP_BADARG_RET(aadLen && !pAAD, ippStsNullPtrErr);
    IPP_BADARG_RET(aadLen < 0, ippStsLengthErr);

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

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

    if (GcmIVprocessing == AESGCM_STATE(pState)) {
        IPP_BADARG_RET(0 == AESGCM_IV_LEN(pState), ippStsBadArgErr);

#if (_IPP32E >= _IPP32E_K0)
        IvFinalize_ ivHashFinalize = AES_GCM_IV_FINALIZE(pState);

        /* complete IV processing */
        ivHashFinalize(&AES_GCM_KEY_DATA(pState),
                       &AES_GCM_CONTEXT_DATA(pState),
                       AESGCM_COUNTER(pState),
                       (Ipp64u)AESGCM_BUFLEN(pState),
                       AESGCM_IV_LEN(pState));
#else
        /* complete IV processing */
        if (CTR_POS == AESGCM_IV_LEN(pState)) {
            /* apply special format if IV length is 12 bytes */
            AESGCM_COUNTER(pState)[12] = 0;
            AESGCM_COUNTER(pState)[13] = 0;
            AESGCM_COUNTER(pState)[14] = 0;
            AESGCM_COUNTER(pState)[15] = 1;
        } else {
            /* get method */
            MulGcm_ hashFunc = AESGCM_HASH(pState);

            /* process the rest of IV */
            if (AESGCM_BUFLEN(pState))
                hashFunc(AESGCM_COUNTER(pState), AESGCM_HKEY(pState), AesGcmConst_table);

            /* add IV bit length */
            {
                Ipp64u ivBitLen = AESGCM_IV_LEN(pState) * BYTESIZE;
                Ipp8u tmp[BLOCK_SIZE];
                PadBlock(0, tmp, BLOCK_SIZE - 8);
                U32_TO_HSTRING(tmp + 8, IPP_HIDWORD(ivBitLen));
                U32_TO_HSTRING(tmp + 12, IPP_LODWORD(ivBitLen));
                XorBlock16(tmp, AESGCM_COUNTER(pState), AESGCM_COUNTER(pState));
                hashFunc(AESGCM_COUNTER(pState), AESGCM_HKEY(pState), AesGcmConst_table);
            }
        }

        /* prepare initial counter */
        {
            IppsAESSpec* pAES  = AESGCM_CIPHER(pState);
            RijnCipher encoder = RIJ_ENCODER(pAES);
//encoder((Ipp32u*)AESGCM_COUNTER(pState), (Ipp32u*)AESGCM_ECOUNTER0(pState), RIJ_NR(pAES), RIJ_EKEYS(pAES), (const Ipp32u (*)[256])RIJ_ENC_SBOX(pAES));
#if (_ALG_AES_SAFE_ == _ALG_AES_SAFE_COMPACT_SBOX_)
            encoder(AESGCM_COUNTER(pState),
                    AESGCM_ECOUNTER0(pState),
                    RIJ_NR(pAES),
                    RIJ_EKEYS(pAES),
                    RijEncSbox /*NULL*/);
#else
            encoder(AESGCM_COUNTER(pState),
                    AESGCM_ECOUNTER0(pState),
                    RIJ_NR(pAES),
                    RIJ_EKEYS(pAES),
                    NULL);
#endif
        }
#endif /* #if(_IPP32E>=_IPP32E_K0) */

        /* switch mode and init counters */
        AESGCM_STATE(pState)   = GcmAADprocessing;
        AESGCM_AAD_LEN(pState) = CONST_64(0);
        AESGCM_BUFLEN(pState)  = 0;
    }

    /*
   // AAD processing
   */

#if (_IPP32E >= _IPP32E_K0)
    /* 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 bufCapacity = BLOCK_SIZE - (int)AESGCM_BUFLEN(pState);
        int locLen      = IPP_MIN(aadLen, bufCapacity);
        XorBlockMirror(pAAD, AESGCM_GHASH(pState), AESGCM_GHASH(pState), bufCapacity, locLen);
        AESGCM_BUFLEN(pState) += (Ipp64u)locLen;

        /* if buffer full */
        if (BLOCK_SIZE == AESGCM_BUFLEN(pState)) {
            MulGcm_ ghashFunc = AES_GCM_GMUL(pState);
            ghashFunc(&AES_GCM_KEY_DATA(pState), AESGCM_GHASH(pState));
            AESGCM_BUFLEN(pState) = 0;
        }

        AESGCM_AAD_LEN(pState) += (Ipp64u)locLen;
        pAAD += locLen;
        aadLen -= locLen;
    }

    /* process main part of AAD */
    int lenBlks = aadLen & (-BLOCK_SIZE);
    if (lenBlks) {
        AadUpdate_ aadHashUpdate = AES_GCM_AAD_UPDATE(pState);

        aadHashUpdate(&AES_GCM_KEY_DATA(pState),
                      &AES_GCM_CONTEXT_DATA(pState),
                      pAAD,
                      (Ipp64u)lenBlks);

        AESGCM_AAD_LEN(pState) += (Ipp64u)lenBlks;
        pAAD += lenBlks;
        aadLen -= lenBlks;
    }

    /* copy the rest of AAD into the buffer */
    if (aadLen) {
        /* Note: GHASH in the IPsec context is byte-reflected */
        XorBlockMirror(pAAD, AESGCM_GHASH(pState), AESGCM_GHASH(pState), BLOCK_SIZE, aadLen);
        AESGCM_AAD_LEN(pState) += (Ipp64u)aadLen;
        AESGCM_BUFLEN(pState) = (Ipp64u)aadLen;
    }
#else
    /* test if buffer is not empty */
    if (AESGCM_BUFLEN(pState)) {
        int locLen = IPP_MIN(aadLen, BLOCK_SIZE - AESGCM_BUFLEN(pState));
        XorBlock(pAAD,
                 AESGCM_GHASH(pState) + AESGCM_BUFLEN(pState),
                 AESGCM_GHASH(pState) + AESGCM_BUFLEN(pState),
                 locLen);
        AESGCM_BUFLEN(pState) += locLen;

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

        AESGCM_AAD_LEN(pState) += (Ipp64u)locLen;
        pAAD += locLen;
        aadLen -= locLen;
    }

    /* process main part of AAD */
    {
        int lenBlks = aadLen & (-BLOCK_SIZE);
        if (lenBlks) {
            Auth_ authFunc = AESGCM_AUTH(pState);

            authFunc(AESGCM_GHASH(pState), pAAD, lenBlks, AESGCM_HKEY(pState), AesGcmConst_table);

            AESGCM_AAD_LEN(pState) += (Ipp64u)lenBlks;
            pAAD += lenBlks;
            aadLen -= lenBlks;
        }
    }

    /* copy the rest of AAD into the buffer */
    if (aadLen) {
        int locLen = IPP_MIN(aadLen, BLOCK_SIZE);
        XorBlock(pAAD, AESGCM_GHASH(pState), AESGCM_GHASH(pState), locLen);
        AESGCM_AAD_LEN(pState) += (Ipp64u)locLen;
        AESGCM_BUFLEN(pState) = locLen;
    }
#endif /* #if(_IPP32E>=_IPP32E_K0) */

    return ippStsNoErr;
}
