/*
 * SecBus - This file is part of SecBus
 * Copyright (C) 2012 - Telecom ParisTech
 * 
 * This file must be used under the terms of the CeCILL.
 * This source file is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at    
 * http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.txt
 *
 */

#include "../include/crypto_engine.h"

CryptoEngine_conf::CryptoEngine_conf(sc_module_name name, size_t nreg):
  sc_module(name),
  p_clk("clk"),
  p_resetn("resetn"),
  m_nreg(nreg)
{
  SC_METHOD(transition);
  dont_initialize();
  sensitive << p_clk.pos();
  
  SC_METHOD(genMoore);
  dont_initialize();
  sensitive << p_clk.neg();

  r_register = new DATA_REGISTER_READ_IN[m_nreg];
  w_register = new DATA_REGISTER_WRITE_OUT[m_nreg];

  DEBUG(NONE, STD, "[SECBUS] Building CryptoEngine_conf (%s) with %u registers\n", 
	basename(), nreg);
}

CryptoEngine_conf::~CryptoEngine_conf()
{
  delete r_register;
  delete w_register;
}

void CryptoEngine_conf::transition()
{
  if (!p_resetn.read()) 
  {
    ack_s   = false;
    write_reg_s = false;
    count_s = 0;

    c_fsm_s = C_IDLE;

    return;
  }

  DEBUG(TRACE, FSM, "[%s]\t\tFSM\t%u\n",
	basename(), (uint32_t)c_fsm_s.read());

  ack_s = false;
  val_s = false;
  write_reg_s = false;

  switch(c_fsm_s.read())
  {
  case C_IDLE:
    if (p_crypto.cmdval.read() && p_crypto.mode.read() != crypto_mac && p_crypto.enable)
    {
      ack_s = true;

      for (size_t i=0;i<DES_KEY_SIZE_IN_WORDS;i++)
        key[i] = p_crypto.key[i].read();

      for (size_t i=0;i<IV_SIZE_IN_WORDS;i++)
	iv_s[i] = p_crypto.iv[i].read();

      address_s = p_crypto.address.read();
      src_s     = p_crypto.id_src.read();
      dest_s    = p_crypto.id_dest.read();
      idx_s     = index(p_crypto.address.read(), SIZE_256_IN_BYTES);
      size_s    = index(p_crypto.address.read(), SIZE_256_IN_BYTES) + p_crypto.size.read()*2;

      count_s = DES_L;
				
      if (p_crypto.mode.read() == crypto_cbc)
      {
	c_fsm_s = C_STAGE_0;
      }
      else if (p_crypto.mode.read() == crypto_ctr)
      {
	if (!p_crypto.reg_ready.read())
	{
	  c_fsm_s = C_WAIT_FOR_DATA;
	  break;
	}

	if (p_crypto.cipher.read())
	  c_fsm_s = C_CIPHER_DATA;
	else
	  c_fsm_s = C_DECIPHER_DATA;
      }
    }
    break;

  case C_STAGE_0:
    if (count_s.read() == DES_L)
    {
      count_s = count_s.read() - 1;

      ci[0] = address_s.read() ^ iv_s[0];
      ci[1] = 0 ^ iv_s[1];

      DES(ci, ci, key, true);
    }
    else if (count_s.read() > 1)
    {
      count_s = count_s.read() - 1;
    }
    else
    {
      if (p_crypto.reg_ready.read())
      {
	count_s = DES_L;
	if (p_crypto.cipher.read())
	  c_fsm_s = C_CIPHER_DATA;
	else
	  c_fsm_s = C_DECIPHER_DATA;
      }
      else
      {
	count_s = DES_L;
	c_fsm_s = C_WAIT_FOR_DATA;
      }
    }
    break;

  case C_WAIT_FOR_DATA:
    if (p_crypto.reg_ready.read())
    {
      if (p_crypto.cipher.read())
	c_fsm_s = C_CIPHER_DATA;
      else
	c_fsm_s = C_DECIPHER_DATA;
    }
    break;
    
  case C_CIPHER_DATA:
    if (count_s.read() == DES_L)
    {
      count_s = count_s.read() - 1;

      if (p_crypto.mode.read() == crypto_cbc)
      {
	ci[0] = r_register[src_s.read()].data[idx_s.read()].read() ^ ci[0];
	ci[1] = r_register[src_s.read()].data[idx_s.read()+1].read() ^ ci[1];

	DES(ci, ci, key, true);

	idx_s = idx_s.read() + 2;

	write_reg_s = true;
      }
      else
      {
	ci[0] = address_s.read() + 4*(idx_s);
	ci[1] = 0;

	DES(ci, ci, key, true);

	ci[0] = ci[0] ^ r_register[src_s.read()].data[idx_s.read()].read();
	ci[1] = ci[1] ^ r_register[src_s.read()].data[idx_s.read()+1].read();

	idx_s = idx_s.read() + 2;

	write_reg_s = true;
      }
    }
    else if (count_s.read() > 1)
    {
      count_s = count_s.read() - 1;
    }
    else
    {
      if (idx_s.read() == size_s.read()) // whole block computed
      {
	val_s = true;
	c_fsm_s = C_IDLE;
      }
      else
      {
	count_s = DES_L;
      }
    }
    break;

  case C_DECIPHER_DATA:
    if (count_s.read() == DES_L)
    {
      count_s = count_s.read() - 1;

      if (p_crypto.mode.read() == crypto_cbc)
      {
	uint32_t in_dec[2] = {(uint32_t)r_register[src_s.read()].data[idx_s.read()].read(),
			      (uint32_t)r_register[src_s.read()].data[idx_s.read()+1].read()};

        DES(in_dec, in_dec, key, false);

	if (idx_s.read() == 0)
	{
	  ci[0] = in_dec[0] ^ ci[0];
	  ci[1] = in_dec[1] ^ ci[1];
	}
	else
	{
	  ci[0] = in_dec[0] ^ r_register[src_s.read()].data[idx_s.read()-2].read();
	  ci[1] = in_dec[1] ^ r_register[src_s.read()].data[idx_s.read()-1].read();
	}

	idx_s = idx_s.read() + 2;

	write_reg_s = true;
      }
      else
      {
	ci[0] = address_s.read() + 4*(idx_s);
	ci[1] = 0;

	DES(ci, ci, key, true);

	ci[0] = ci[0] ^ r_register[src_s.read()].data[idx_s.read()].read();
	ci[1] = ci[1] ^ r_register[src_s.read()].data[idx_s.read()+1].read();

	idx_s = idx_s.read() + 2;

	write_reg_s = true;
      }
    }
    else if (count_s.read() > 1)
    {
      count_s = count_s.read() - 1;
    }
    else
    {
      if (idx_s.read() == size_s.read()) // whole block computed
      {
	val_s = true;
	c_fsm_s = C_IDLE;
      }
      else
      {
	count_s = DES_L;
      }
    }
    break;

  default:
    break;
  }
}

void CryptoEngine_conf::genMoore()
{
  p_crypto.cmdack = ack_s;
  p_crypto.rspval = val_s;
  
  for (size_t i=0;i<m_nreg;i++)
  {
    w_register[i].enable = false;
    w_register[i].flush = false;
  }
  
  if (write_reg_s.read())
  {
    w_register[dest_s.read()].enable  = true;
    w_register[dest_s.read()].be      = 0xFF << ((idx_s.read()-2) * 4);
    w_register[dest_s.read()].address = address_s.read();

    w_register[dest_s.read()].data[idx_s.read()-2] = ci[0];
    w_register[dest_s.read()].data[idx_s.read()-1] = ci[1];
  }
}

CryptoEngine_int::CryptoEngine_int(sc_module_name name):
  sc_module(name),
  p_clk("clk"),
  p_resetn("resetn")
{
  SC_METHOD(transition);
  dont_initialize();
  sensitive << p_clk.pos();
  
  SC_METHOD(genMoore);
  dont_initialize();
  sensitive << p_clk.neg();

  DEBUG(NONE, STD, "[SECBUS] Building CryptoEngine_int (%s)\n", 
	basename());
}

CryptoEngine_int::~CryptoEngine_int()
{
}

void CryptoEngine_int::transition()
{
  if (!p_resetn.read()) 
  {
    ack_s   = false;
    write_reg_s = false;
    count_s = 0;

    i_fsm_s = I_IDLE;

    return;
  }

  DEBUG(TRACE, FSM, "[%s]\t\tFSM\t%u\n",
	basename(), (uint32_t)i_fsm_s.read());

  ack_s = false;
  val_s = false;
  write_reg_s = false;

  if (direct_data.enable)
  {
    for (uint32_t i;i<LAST_HS_SIZE_IN_WORDS;i++)
    {
      wr_data_s[i] = direct_data.data[i].read();
    }
  }

  switch(i_fsm_s.read())
  {
  case I_IDLE:
    if (p_crypto.cmdval.read() && p_crypto.mode.read() == crypto_mac && p_crypto.enable)
    {
      for (size_t i=0;i<DES_KEY_SIZE_IN_WORDS;i++)
      {
        key[i] = p_crypto.key[i].read();
      }

      for (size_t i=0;i<IV_SIZE_IN_WORDS;i++)
      {
	iv_s[i] = p_crypto.iv[i].read();
      }

      count_s	= DES_L; // 4

      address_s = p_crypto.address.read();
      src_s     = p_crypto.id_src.read();
      idx_s     = 0;
      size_s    = p_crypto.size.read();
      direct_data_s = p_crypto.direct_data.read();
      no_init_s = p_crypto.no_init.read();

      ack_s = true;

      i_fsm_s = I_STAGE_0;
    }
    break;

  case I_STAGE_0:
    if (count_s.read() == DES_L)
    {
      count_s = count_s.read() - 1;

      if (no_init_s.read())
      {
	ci[0] = iv_s[0]^wr_data_s[0];
	ci[1] = iv_s[1]^wr_data_s[1];
	idx_s = idx_s.read() + 2;
      }
      else
      {
	ci[0] = address_s.read();
	ci[1] = 0;
      }
      DES(ci, ci, key, 1);

    }
    else if (count_s.read() > 1)
    {
      count_s = count_s.read() - 1;
    }
    else
    {
      if (0 == size_s.read() || (no_init_s.read() && size_s.read() == 2))
      {
	val_s = true;

	i_fsm_s = I_IDLE;
      }
      else if (p_crypto.reg_ready.read())
      {
	count_s = DES_L;
	
	i_fsm_s = I_CIPHER_DATA;
      }
      else
      {
	count_s = DES_L;
	
	i_fsm_s = I_WAIT_FOR_DATA;
      }
    }
    break;

  case I_CIPHER_DATA:
    if (count_s.read() == DES_L)
    {
      count_s = count_s.read() - 1;

      if (direct_data_s.read())
      {
	ci[0] = ci[0] ^ wr_data_s[idx_s.read()];
	ci[1] = ci[1] ^ wr_data_s[idx_s.read()+1];
      }
      else
      {
/*	ci[0] = ci[0] ^ r_register[src_s.read()].data[idx_s.read()-2];
	ci[1] = ci[1] ^ r_register[src_s.read()].data[idx_s.read()-1];*/
      }

      DES(ci, ci, key, 1);

      idx_s = idx_s.read() + 2;
    }
    else if (count_s.read() > 1)
    {
      count_s = count_s.read() - 1;
    }
    else
    {
      if (idx_s.read() == size_s.read())
      {
	val_s = true;

	i_fsm_s = I_IDLE;
      }
      else
      {
	count_s = DES_L;
      }
    }
    break;

  case I_WAIT_FOR_DATA:
    if (p_crypto.reg_ready.read())
      i_fsm_s = I_CIPHER_DATA;
    break;

  default:
    break;
  }
}

void CryptoEngine_int::genMoore()
{
  p_crypto.cmdack = ack_s;
  p_crypto.rspval = val_s;

  if (val_s.read())
  {
    out_mac.data[0] = ci[0];
    out_mac.data[1] = ci[1];
  }
}
