// Crypto/MyAes.cpp

#include "StdAfx.h"

#include "CpuArch.h"

#include "7zip/Crypto/MyAes.h"

namespace NCrypto {

static struct CAesTabInit { CAesTabInit() { AesGenTables();} } g_AesTabInit;

CAesCbcCoder::CAesCbcCoder(bool encodeMode, unsigned keySize):
  _keySize(keySize),
  _keyIsSet(false),
  _encodeMode(encodeMode)
{
  _offset = ((0 - (unsigned)(ptrdiff_t)_aes) & 0xF) / sizeof(UInt32);
  memset(_iv, 0, AES_BLOCK_SIZE);
  SetFunctions(0);
}

STDMETHODIMP CAesCbcCoder::Init()
{
  AesCbc_Init(_aes + _offset, _iv);
  return _keyIsSet ? S_OK : E_FAIL;
}

STDMETHODIMP_(UInt32) CAesCbcCoder::Filter(Byte *data, UInt32 size)
{
  if (!_keyIsSet)
    return 0;
  if (size == 0)
    return 0;
  if (size < AES_BLOCK_SIZE)
    return AES_BLOCK_SIZE;
  size >>= 4;
  _codeFunc(_aes + _offset, data, size);
  return size << 4;
}

STDMETHODIMP CAesCbcCoder::SetKey(const Byte *data, UInt32 size)
{
  if ((size & 0x7) != 0 || size < 16 || size > 32)
    return E_INVALIDARG;
  if (_keySize != 0 && size != _keySize)
    return E_INVALIDARG;
  AES_SET_KEY_FUNC setKeyFunc = _encodeMode ? Aes_SetKey_Enc : Aes_SetKey_Dec;
  setKeyFunc(_aes + _offset + 4, data, size);
  _keyIsSet = true;
  return S_OK;
}

STDMETHODIMP CAesCbcCoder::SetInitVector(const Byte *data, UInt32 size)
{
  if (size != AES_BLOCK_SIZE)
    return E_INVALIDARG;
  memcpy(_iv, data, size);
  CAesCbcCoder::Init(); // don't call virtual function here !!!
  return S_OK;
}

EXTERN_C_BEGIN

void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks);
void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks);
void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks);

void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks);
void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks);
void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks);

EXTERN_C_END

bool CAesCbcCoder::SetFunctions(UInt32 algo)
{
  _codeFunc = _encodeMode ?
      g_AesCbc_Encode :
      g_AesCbc_Decode;
  if (algo == 1)
  {
    _codeFunc = _encodeMode ?
        AesCbc_Encode:
        AesCbc_Decode;
  }
  if (algo == 2)
  {
    #ifdef MY_CPU_X86_OR_AMD64
    #ifdef _7ZIP_ASM
    if (g_AesCbc_Encode != AesCbc_Encode_Intel)
    #endif
    #endif
      return false;
  }
  return true;
}

STDMETHODIMP CAesCbcCoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)
{
  for (UInt32 i = 0; i < numProps; i++)
  {
    const PROPVARIANT &prop = coderProps[i];
    if (propIDs[i] == NCoderPropID::kDefaultProp)
    {
      if (prop.vt != VT_UI4)
        return E_INVALIDARG;
      if (!SetFunctions(prop.ulVal))
        return E_NOTIMPL;
    }
  }
  return S_OK;
}

}
