/*************************************************************************
* 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 encryption/decryption (CTR mode)
//
//  Contents:
//     cpProcessAES_ctr()
//     cpProcessAES_ctr128()
//
*/

#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

/*
// AES-CTR processing.
//
// Returns:                Reason:
//    ippStsNullPtrErr        pCtx == NULL
//                            pSrc == NULL
//                            pDst == NULL
//                            pCtrValue ==NULL
//    ippStsContextMatchErr   !VALID_AES_ID()
//    ippStsLengthErr         len <1
//    ippStsCTRSizeErr        128 < ctrNumBitSize < 1
//    ippStsCTRSizeErr        data blocks number > 2^ctrNumBitSize
//    ippStsNoErr             no errors
//
// Parameters:
//    pSrc           pointer to the source data buffer
//    pDst           pointer to the target data buffer
//    dataLen        input/output buffer length (in bytes)
//    pCtx           pointer to the AES context
//    pCtrValue      pointer to the counter block
//    ctrNumBitSize  counter block size (bits)
//
// Note:
//    counter will updated on return
//
*/
IPPCP_INLINE void MaskCounter128(Ipp8u* pMaskIV, int ctrBtSize)
{
    /* construct ctr mask */
    int maskPosition = (MBS_RIJ128 * 8 - ctrBtSize) / 8;
    Ipp8u maskValue  = (Ipp8u)(0xFF >> (MBS_RIJ128 * 8 - ctrBtSize) % 8);

    //Ipp8u maskIV[MBS_RIJ128];
    int n;
    for (n = 0; n < MBS_RIJ128; n++) {
        int d                 = n - maskPosition;
        Ipp8u storedMaskValue = maskValue & ~cpIsMsb_ct((BNU_CHUNK_T)d);
        pMaskIV[n]            = storedMaskValue;
        maskValue |= ~cpIsMsb_ct((BNU_CHUNK_T)d);
    }
}

static IppStatus cpProcessAES_ctr(const Ipp8u* pSrc,
                                  Ipp8u* pDst,
                                  int dataLen,
                                  const IppsAESSpec* pCtx,
                                  Ipp8u* pCtrValue,
                                  int ctrNumBitSize)
{
    /* test context */
    IPP_BAD_PTR1_RET(pCtx);
    /* test the context ID */
    IPP_BADARG_RET(!VALID_AES_ID(pCtx), ippStsContextMatchErr);

    /* test source, target and counter block pointers */
    IPP_BAD_PTR3_RET(pSrc, pDst, pCtrValue);
    /* test stream length */
    IPP_BADARG_RET((dataLen < 1), ippStsLengthErr);

    /* test counter block size */
    IPP_BADARG_RET(((MBS_RIJ128 * 8) < ctrNumBitSize) || (ctrNumBitSize < 1), ippStsCTRSizeErr);

    /* test counter overflow */
    if (ctrNumBitSize < (8 * (int)sizeof(int) - 5)) {
        /*
      // dataLen is int, and it is always positive
      // data blocks number compute from dataLen
      // by dividing it to MBS_RIJ128 = 16
      // and additing 1 if dataLen % 16 != 0
      // so if ctrNumBitSize >= 8 * sizeof(int) - 5
      // function can process data with any possible
      // passed dataLen without counter overflow
      */

        int dataBlocksNum = dataLen >> 4;
        if (dataLen & 15) {
            dataBlocksNum++;
        }

        IPP_BADARG_RET(dataBlocksNum > (1 << ctrNumBitSize), ippStsCTRSizeErr);
    }

#if (_IPP >= _IPP_P8) || (_IPP32E >= _IPP32E_Y8)
    /* use pipelined version if possible */
    if (AES_NI_ENABLED == RIJ_AESNI(pCtx)) {
        /* construct ctr mask */
        Ipp8u maskIV[MBS_RIJ128];
        MaskCounter128(maskIV, ctrNumBitSize); /* const-exe-time version */

#if (_IPP32E >= _IPP32E_K1)
        if (IsFeatureEnabled(ippCPUID_AVX512VAES)) {
            EncryptCTR_RIJ128pipe_VAES_NI(pSrc,
                                          pDst,
                                          RIJ_NR(pCtx),
                                          RIJ_EKEYS(pCtx),
                                          dataLen,
                                          pCtrValue,
                                          maskIV);
        } else
#endif
        {
            EncryptCTR_RIJ128pipe_AES_NI(pSrc,
                                         pDst,
                                         RIJ_NR(pCtx),
                                         RIJ_EKEYS(pCtx),
                                         dataLen,
                                         pCtrValue,
                                         maskIV);
        }
        return ippStsNoErr;
    } else
#endif
    {
        Ipp32u counter[NB(128)];
        Ipp32u output[NB(128)];

        /* setup encoder method */
        RijnCipher encoder = RIJ_ENCODER(pCtx);

        /* copy counter */
        CopyBlock16(pCtrValue, counter);

        /*
      // encrypt block-by-block aligned streams
      */
        while (dataLen >= MBS_RIJ128) {
/* encrypt counter block */
//encoder(counter, output, RIJ_NR(pCtx), RIJ_EKEYS(pCtx), (const Ipp32u (*)[256])RIJ_ENC_SBOX(pCtx));
#if (_ALG_AES_SAFE_ == _ALG_AES_SAFE_COMPACT_SBOX_)
            encoder((Ipp8u*)counter,
                    (Ipp8u*)output,
                    RIJ_NR(pCtx),
                    RIJ_EKEYS(pCtx),
                    RijEncSbox /*NULL*/);
#else
            encoder((Ipp8u*)counter, (Ipp8u*)output, RIJ_NR(pCtx), RIJ_EKEYS(pCtx), NULL);
#endif

            /* compute ciphertext block */
            if (!(IPP_UINT_PTR(pSrc) & 0x3) && !(IPP_UINT_PTR(pDst) & 0x3)) {
                ((Ipp32u*)pDst)[0] = output[0] ^ ((Ipp32u*)pSrc)[0];
                ((Ipp32u*)pDst)[1] = output[1] ^ ((Ipp32u*)pSrc)[1];
                ((Ipp32u*)pDst)[2] = output[2] ^ ((Ipp32u*)pSrc)[2];
                ((Ipp32u*)pDst)[3] = output[3] ^ ((Ipp32u*)pSrc)[3];
            } else
                XorBlock16(pSrc, output, pDst);
            /* encrement counter block */
            StdIncrement((Ipp8u*)counter, MBS_RIJ128 * 8, ctrNumBitSize);

            pSrc += MBS_RIJ128;
            pDst += MBS_RIJ128;
            dataLen -= MBS_RIJ128;
        }
        /*
      // encrypt last data block
      */
        if (dataLen) {
/* encrypt counter block */
//encoder(counter, output, RIJ_NR(pCtx), RIJ_EKEYS(pCtx), (const Ipp32u (*)[256])RIJ_ENC_SBOX(pCtx));
#if (_ALG_AES_SAFE_ == _ALG_AES_SAFE_COMPACT_SBOX_)
            encoder((Ipp8u*)counter,
                    (Ipp8u*)output,
                    RIJ_NR(pCtx),
                    RIJ_EKEYS(pCtx),
                    RijEncSbox /*NULL*/);
#else
            encoder((Ipp8u*)counter, (Ipp8u*)output, RIJ_NR(pCtx), RIJ_EKEYS(pCtx), NULL);
#endif

            /* compute ciphertext block */
            XorBlock(pSrc, output, pDst, dataLen);
            /* encrement counter block */
            StdIncrement((Ipp8u*)counter, MBS_RIJ128 * 8, ctrNumBitSize);
        }

        /* update counter */
        CopyBlock16(counter, pCtrValue);

        return ippStsNoErr;
    }
}

#if (_IPP32E >= _IPP32E_Y8)

/*
// special version: 128-bit counter
*/
static IppStatus cpProcessAES_ctr128(const Ipp8u* pSrc,
                                     Ipp8u* pDst,
                                     int dataLen,
                                     const IppsAESSpec* pCtx,
                                     Ipp8u* pCtrValue)
{
    /* test context */
    IPP_BAD_PTR1_RET(pCtx);
    /* test the context ID */
    IPP_BADARG_RET(!VALID_AES_ID(pCtx), ippStsContextMatchErr);

    /* test source, target and counter block pointers */
    IPP_BAD_PTR3_RET(pSrc, pDst, pCtrValue);
    /* test stream length */
    IPP_BADARG_RET((dataLen < 1), ippStsLengthErr);

    {
        while (dataLen >= MBS_RIJ128) {
            Ipp32u blocks = (Ipp32u)(dataLen >> 4); /* number of blocks per loop processing */

            /* low LE 32 bit of counter */
            Ipp32u ctr32 = ((Ipp32u*)(pCtrValue))[3];
            ctr32        = ENDIANNESS32(ctr32);

            /* compute number of locks being processed without ctr32 overflow */
            ctr32 += blocks;
            if (ctr32 < blocks)
                blocks -= ctr32;

#if (_IPP32E >= _IPP32E_K1)
            if (IsFeatureEnabled(ippCPUID_AVX512VAES)) {
                EncryptStreamCTR32_VAES_NI(pSrc,
                                           pDst,
                                           RIJ_NR(pCtx),
                                           RIJ_EKEYS(pCtx),
                                           (Ipp32s)blocks * MBS_RIJ128,
                                           pCtrValue);
            } else
#endif
                EncryptStreamCTR32_AES_NI(pSrc,
                                          pDst,
                                          RIJ_NR(pCtx),
                                          RIJ_EKEYS(pCtx),
                                          (Ipp32s)blocks * MBS_RIJ128,
                                          pCtrValue);

            pSrc += blocks * MBS_RIJ128;
            pDst += blocks * MBS_RIJ128;
            dataLen -= blocks * MBS_RIJ128;
        }

        if (dataLen) {
            EncryptStreamCTR32_AES_NI(pSrc,
                                      pDst,
                                      RIJ_NR(pCtx),
                                      RIJ_EKEYS(pCtx),
                                      dataLen,
                                      pCtrValue);
        }

        return ippStsNoErr;
    }
}

#endif /* #if (_IPP32E>=_IPP32E_Y8) */
