/*************************************************************************
* Copyright (C) 2019 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 GFMUL and GHASH (GCM mode)
//
//  Contents:
//
*/

#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#pragma warning(disable : 4206) // empty translation unit in MSVC
#endif

#if 0                           // Not used

#include "owncp.h"
#include "pcpaesm.h"
#include "pcpaes_encrypt_vaes512.h"
#include "pcpaes_gcm_vaes512.h"
#include "pcpaesauthgcm.h"

#if (_IPP32E >= _IPP32E_K1)
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
#pragma warning(disable : 4310) // cast truncates constant value in MSVC
#endif

/* The function calculates multiplication of two 2x128-bit polynomials C = A*B with
   polynomial reduction. 2 polynomials can be processed at one call.
   The inputs are bit-reflected. The result is bit-reflected.
 */
IPPCP_INLINE void AesGcmGhash2(const __m256i* const src1,
                                 const __m256i* const src2,
                                 __m256i * const result)
{
    __m256i tmp1, tmp2, tmp3, tmp4;

#if 0
    /* School-book multiplication */
    tmp1 = _mm256_clmulepi64_epi128(src2, src1, 0x00); // [C1:C0]
    tmp2 = _mm256_clmulepi64_epi128(src2, src1, 0x11); // [D1:D0]
    tmp3 = _mm256_clmulepi64_epi128(src2, src1, 0x10); // [F1:F0]
    tmp4 = _mm256_clmulepi64_epi128(src2, src1, 0x01); // [E1:E0]

    tmp3 = _mm256_xor_si256(tmp4, tmp3);

    tmp4 = _mm256_bslli_epi128(tmp3, 8);
    tmp3 = _mm256_bsrli_epi128(tmp3, 8);
    tmp1 = _mm256_xor_si256(tmp4, tmp1);
    tmp4 = _mm256_xor_si256(tmp3, tmp2);
#endif

    /* Karatsuba multiplication */
    tmp1 = _mm256_clmulepi64_epi128(*src2, *src1, 0x00);
    tmp4 = _mm256_clmulepi64_epi128(*src2, *src1, 0x11);

    tmp2 = _mm256_shuffle_epi32(*src2, 78); // 78 = 01001110b
    tmp3 = _mm256_shuffle_epi32(*src1, 78); // 78 = 01001110b
    tmp2 = _mm256_xor_si256(tmp2, *src2);
    tmp3 = _mm256_xor_si256(tmp3, *src1);

    tmp2 = _mm256_clmulepi64_epi128(tmp2, tmp3, 0x00);
    tmp2 = _mm256_xor_si256(tmp2, tmp1);
    tmp2 = _mm256_xor_si256(tmp2, tmp4);

    tmp3 = _mm256_bslli_epi128(tmp2, 8);
    tmp2 = _mm256_bsrli_epi128(tmp2, 8);

    tmp1 = _mm256_xor_si256(tmp1, tmp3);
    tmp4 = _mm256_xor_si256(tmp4, tmp2);

   /* Montgomery reduction */
    tmp2 = _mm256_clmulepi64_epi128(tmp1, M256(POLY2), 0x10);
    tmp3 = _mm256_shuffle_epi32(tmp1, 78); // 78 = 01001110b
    tmp1 = _mm256_xor_si256(tmp2, tmp3);

    tmp2 = _mm256_clmulepi64_epi128(tmp1, M256(POLY2), 0x10);
    tmp3 = _mm256_shuffle_epi32(tmp1, 78); // 78 = 01001110b
    tmp1 = _mm256_xor_si256(tmp2, tmp3);

    *result = _mm256_xor_si256(tmp1, tmp4);
}

/* The function calculates multiplication of two 128-bit polynomials C = A*B with
   polynomial reduction.
   The inputs are bit-reflected. The result is bit-reflected.
 */
IPPCP_INLINE void AesGcmGhash(const __m128i* const a,
                                const __m128i* const b,
                                __m128i * const result)
{
    __m256i res256;

    __m256i src1 = _mm256_set_m128i(_mm_setzero_si128(), *a);
    __m256i src2 = _mm256_set_m128i(_mm_setzero_si128(), *b);

    AesGcmGhash2(&src1, &src2, &res256);

    *result = _mm256_castsi256_si128(res256);
}

/* The function calculates multiplication of 128-bit polynomials C = A*B with
   polynomial reduction. 4 polynomials can be processed at one call.
   The inputs are bit-reflected. The result is bit-reflected.
 */
IPPCP_INLINE void AesGcmGhash4(const __m512i* const src1,
                                 const __m512i* const src2,
                                 __m512i * const result)
{
   __m512i tmp1, tmp2, tmp3, tmp4;

   /* Karatsuba multiplication */
    tmp1 = _mm512_clmulepi64_epi128(*src2, *src1, 0x00);
    tmp4 = _mm512_clmulepi64_epi128(*src2, *src1, 0x11);

    tmp2 = _mm512_shuffle_epi32(*src2, 78); // 78 = 01001110b
    tmp3 = _mm512_shuffle_epi32(*src1, 78); // 78 = 01001110b
    tmp2 = _mm512_xor_si512(tmp2, *src2);
    tmp3 = _mm512_xor_si512(tmp3, *src1);

    tmp2 = _mm512_clmulepi64_epi128(tmp2, tmp3, 0x00);
    tmp2 = _mm512_xor_si512(tmp2, tmp1);
    tmp2 = _mm512_xor_si512(tmp2, tmp4);

    tmp3 = _mm512_bslli_epi128(tmp2, 8);
    tmp2 = _mm512_bsrli_epi128(tmp2, 8);

    tmp1 = _mm512_xor_si512(tmp1, tmp3);
    tmp4 = _mm512_xor_si512(tmp4, tmp2);

    /* Montgomery reduction */
    tmp2 = _mm512_clmulepi64_epi128(tmp1, M512(POLY2), 0x10);
    tmp3 = _mm512_shuffle_epi32(tmp1, 78); // 78 = 01001110b
    tmp1 = _mm512_xor_si512(tmp2, tmp3);

    tmp2 = _mm512_clmulepi64_epi128(tmp1, M512(POLY2), 0x10);
    tmp3 = _mm512_shuffle_epi32(tmp1, 78); // 78 = 01001110b
    tmp1 = _mm512_xor_si512(tmp2, tmp3);

    *result = _mm512_xor_si512(tmp1, tmp4);
}

/* The function performs single A*(Hash<<1 mod poly) multiplication */
IPP_OWN_DEFN(void, AesGcmMulGcm_vaes, (Ipp8u* pGHash, const Ipp8u* pHKey, const void* pParam))
{
    IPP_UNREFERENCED_PARAMETER(pParam);

    __m128i ghash = _mm_maskz_loadu_epi64(0x03, pGHash);
    __m128i hkey  = _mm_maskz_loadu_epi64(0x03, pHKey + 16*3); // NB: hKey is at index 3 in the array

    ghash = _mm_maskz_shuffle_epi8((Ipp16u)(-1), ghash, M128(swapBytes));
    AesGcmGhash(&ghash, &hkey, &ghash);
    ghash = _mm_maskz_shuffle_epi8((Ipp16u)(-1), ghash, M128(swapBytes));

    _mm_mask_storeu_epi64(pGHash, (Ipp8u)(-1), ghash);
}

/* The function computes reflected hKey<<1, hKey^2<<1, hKey^3<<1, ..., hKey^16<<1 - all mod poly
 * to use in batch GHASH calculation.
 * It also pre-computes parts of Karatsuba multipliers that are fixed and derived from the hKey.
 */
IPP_OWN_DEFN (void, AesGcmPrecompute_vaes, (Ipp8u* const pHtbl, const Ipp8u* const hKey))
{
    /* Initial hKey = E_ctr(key, 0^16) */
    __m128i* pDst = (__m128i*)pHtbl;

    __m128i xmm0, xmm1;
    __m256i ymm0, ymm1, ymm3, ymm4, ymm5;
    __m512i zmm0, zmm1, zmm2;

    /* Load initial hKey = E_ctr(key,0^16) */
    ymm0 = _mm256_maskz_loadu_epi64(0x03, hKey);
    /* Reflect hKey */
    ymm0 = _mm256_shuffle_epi8(ymm0, M256(swapBytes));

    /* Compute reflected hKey<<1 mod poly */
    ymm3 = _mm256_srai_epi32(ymm0, 31);
    ymm3 = _mm256_shuffle_epi32(ymm3, 0xFF);
    ymm5 = _mm256_and_si256(ymm3, M256(POLY2));
    ymm3 = _mm256_srli_epi32(ymm0, 31);
    ymm4 = _mm256_slli_epi32(ymm0, 1);
    ymm3 = _mm256_bslli_epi128(ymm3, 4);
    ymm0 = _mm256_xor_si256(ymm4, ymm3);
    ymm0 = _mm256_xor_si256(ymm0,ymm5);

    xmm0 = _mm256_castsi256_si128(ymm0);

    AesGcmGhash(&xmm0, &xmm0, &xmm1);          /* xmm1 = hKey^2                      */

    ymm0 = _mm256_broadcast_i64x2(xmm1);       /* ymm0 = hKey^2 hKey^2               */
    ymm1 = _mm256_set_m128i(xmm0, xmm1);       /* ymm1 = hKey   hKey^2               */
    AesGcmGhash2(&ymm0, &ymm1, &ymm0);         /* ymm0 = hKey^3 hKey^4               */

    xmm1 = _mm256_extracti64x2_epi64(ymm0, 0); /* xmm1 = hKey^4                      */

    zmm0 = _mm512_setzero_si512();
    zmm0 = _mm512_inserti64x4(zmm0, ymm1, 1);  /* zmm0 = hKey   hKey^2 0      0      */
    zmm0 = _mm512_inserti64x4(zmm0, ymm0, 0);  /* zmm0 = hKey   hKey^2 hKey^3 hKey^4 */
    zmm1 = _mm512_broadcast_i64x2(xmm1);       /* zmm1 = hKey^4 hKey^4 hKey^4 hKey^4 */

    /* Store 4xhKey<<1 mod poly degrees in precompute table */
    _mm512_storeu_si512(pDst, zmm0);

    /* Prepare constant multipliers for Karatsuba (Bh^Bl) */
    zmm2 = _mm512_shuffle_epi32(zmm0, 78);
    zmm2 = _mm512_xor_si512(zmm0, zmm2);
    _mm512_storeu_si512(pDst + 16, zmm2);

    for (int i = 1; i < 4; i++)
    {
       AesGcmGhash4(&zmm0, &zmm1, &zmm0);

      /* Store 4xhKey<<1 mod poly degrees in precompute table */
       _mm512_storeu_si512(pDst + 4*i, zmm0);

       /* Prepare constant multipliers for Karatsuba (Bh^Bl) */
       zmm2 = _mm512_shuffle_epi32(zmm0, 78);
       zmm2 = _mm512_xor_si512(zmm0, zmm2);
       _mm512_storeu_si512(pDst + 4*i + 16, zmm2);
    }
}

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

#endif
