/*************************************************************************
* 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.
//     SMS4 encryption/decryption
//
//  Contents:
//        cpProcessSMS4_ctr()
//
*/

#include "owncp.h"
#include "sms4/pcpsms4.h"
#include "pcptool.h"

/*
// SMS4-CTR processing.
//
// Returns:                Reason:
//    ippStsNullPtrErr        pCtx == NULL
//                            pSrc == NULL
//                            pDst == NULL
//                            pCtrValue ==NULL
//    ippStsContextMatchErr   !VALID_SMS4_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 SMS4 context
//    pCtrValue      pointer to the counter block
//    ctrNumBitSize  counter block size (bits)
//
// Note:
//    counter will updated on return
//
*/
/* clang-format off */
IPP_OWN_DEFN(IppStatus, cpProcessSMS4_ctr, (const Ipp8u* pSrc,
                                            Ipp8u* pDst,
                                            int dataLen,
                                            const IppsSMS4Spec* pCtx,
                                            Ipp8u* pCtrValue,
                                            int ctrNumBitSize))
/* clang-format on */
{
    /* test context */
    IPP_BAD_PTR1_RET(pCtx);
    /* test the context ID */
    IPP_BADARG_RET(!VALID_SMS4_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_SMS4 * 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_SMS4 = 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);
    }

    {
        __ALIGN16 Ipp8u TMP[2 * MBS_SMS4 + 1];

        /*
         maskIV    size = MBS_SMS4
         output    size = MBS_SMS4
         counter   size = MBS_SMS4
         maskValue size = 1
      */

        Ipp8u* output  = TMP;
        Ipp8u* counter = TMP + MBS_SMS4;

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

#if (_IPP32E >= _IPP32E_L9)
        if (IsFeatureEnabled(ippCPUID_AVX2SM4)) { /* can be optimized further */
            /* block-by-block processing */
            while (dataLen >= MBS_SMS4) {
                /* encrypt counter block */
                cpSMS4_ECB_ni((Ipp8u*)output, (Ipp8u*)counter, SMS4_RK(pCtx));
                /* compute ciphertext block */
                XorBlock16(pSrc, output, pDst);
                /* increment counter block */
                StdIncrement(counter, MBS_SMS4 * 8, ctrNumBitSize);

                pSrc += MBS_SMS4;
                pDst += MBS_SMS4;
                dataLen -= MBS_SMS4;
            }
        } else
#endif
        {
/* do CTR processing */
#if (_IPP >= _IPP_P8) || (_IPP32E >= _IPP32E_Y8)

            Ipp8u* maskIV = TMP;
            /* output is not used together with maskIV, so this is why buffer for both values is the same */
            Ipp8u* maskValue = TMP + 2 * MBS_SMS4;

            if (dataLen >= 4 * MBS_SMS4) {
                /* construct ctr mask */
                int n;
                int maskPosition = (MBS_SMS4 * 8 - ctrNumBitSize) / 8;
                *maskValue       = (Ipp8u)(0xFF >> (MBS_SMS4 * 8 - ctrNumBitSize) % 8);

                for (n = 0; n < maskPosition; n++)
                    maskIV[n] = 0;
                maskIV[maskPosition] = *maskValue;
                for (n = maskPosition + 1; n < MBS_SMS4; n++)
                    maskIV[n] = 0xFF;

                int processedLen = 0;

#if (_IPP32E >= _IPP32E_K1)
#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER) || !defined(_MSC_VER) || \
    (_MSC_VER >= 1920)
                if (IsFeatureEnabled(ippCPUID_AVX512GFNI)) {
                    processedLen =
                        cpSMS4_CTR_gfni512(pDst, pSrc, dataLen, SMS4_RK(pCtx), maskIV, counter);
                } else
#endif /* #if defined (__INTEL_COMPILER) || defined (__INTEL_LLVM_COMPILER) || !defined (_MSC_VER) || (_MSC_VER >= 1920) */
#endif /* (_IPP32E>=_IPP32E_K1) */
                    if (IsFeatureEnabled(ippCPUID_AES) || IsFeatureEnabled(ippCPUID_AVX2VAES)) {
                        processedLen =
                            cpSMS4_CTR_aesni(pDst, pSrc, dataLen, SMS4_RK(pCtx), maskIV, counter);
                    }

                pSrc += processedLen;
                pDst += processedLen;
                dataLen -= processedLen;
            }
#endif
        }

        {

            /* block-by-block processing */
            while (dataLen >= MBS_SMS4) {
                /* encrypt counter block */
                cpSMS4_Cipher((Ipp8u*)output, (Ipp8u*)counter, SMS4_RK(pCtx));
                /* compute ciphertext block */
                XorBlock16(pSrc, output, pDst);
                /* increment counter block */
                StdIncrement(counter, MBS_SMS4 * 8, ctrNumBitSize);

                pSrc += MBS_SMS4;
                pDst += MBS_SMS4;
                dataLen -= MBS_SMS4;
            }

            /* last data block processing */
            if (dataLen) {
                /* encrypt counter block */
                cpSMS4_Cipher((Ipp8u*)output, (Ipp8u*)counter, SMS4_RK(pCtx));
                /* compute ciphertext block */
                XorBlock(pSrc, output, pDst, dataLen);
                /* increment counter block */
                StdIncrement((Ipp8u*)counter, MBS_SMS4 * 8, ctrNumBitSize);
            }
        }

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

        /* clear secret data */
        PurgeBlock(TMP, sizeof(TMP));

        return ippStsNoErr;
    }
}
