/*
 * 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
 *
 */

#ifndef _CryptoLib_H_
#define _CryptoLib_H_

#include "systemc.h"
#include "my_interfaces.h"
#include "global.h"
#include "types.h"
#include "secbus_lib.h"
#include <assert.h>
#include <fstream>
#include <iostream>
using namespace std;
using std::ofstream;

SC_MODULE(CBEngine)
{
  sc_in<bool> clk;
  sc_in<bool> reset;

  CRYPTOGRAPHIC_ENGINE_CMD_IN in_cmd;
  DATA_IN<2>	in_data;
  sc_out<bool>	out_ack;
  DATA_OUT<2>	out_data;

  sc_signal<int> count_s;
  sc_signal<bool> ack_s;

  int *out;
  int *in;
  int *key;

  uint64_t stream_cipher_count;
  uint64_t block_cipher_count;

  void ps()
  {
    if (!reset) 
    {
      block_cipher_count	= 0;
      stream_cipher_count = 0;
      count_s				= 0;
      return;
    }

    if(in_cmd.enable)			
    {
      assert(in_cmd.cmode != NO_CONFIDENTIALITY);

      ack_s = false;
      count_s = DES_L;

      if (in_cmd.cmode == BC)
      {
        block_cipher_count++;
        in[0]	= in_data.data[0];
        in[1]	= in_data.data[1];
      } 
      else
      {
        stream_cipher_count++;
        in[0]	= in_cmd.address;
        in[1]	= in_cmd.address + 4;
      }

      key		= in_cmd.key.read()	;
      DES(in, out, key);	
    }
    else
    {
      count_s = count_s - 1;
      if(count_s == 0)
        ack_s = true;
    }
  }
  void pc()
  {
    out_ack = ack_s;
    out_data.data[0] = out[0];
    out_data.data[1] = out[1];
  }

  void print_stats(ofstream & file)
  {	
    /*    	_secbus_debug(file, NONE, "\n- BLOCK CIPHER OPERATION	= " <<block_cipher_count<< "\n");
            _secbus_debug(file, NONE, "- STREAM CIPHER OPERATION	= " <<stream_cipher_count<< "\n");*/
  }
  void reset_stats()
  {	
    block_cipher_count = 0;
    stream_cipher_count = 0;
  }


  SC_CTOR(CBEngine)
  {

    _secbus_debug(cout, TRACE, "BUILDING CB ..."<<"\n");

    SC_METHOD(ps);
    sensitive << clk.pos();
    SC_METHOD(pc);
    sensitive << clk.neg();

    out = new int [SIZE_64_IN_WORDS];
    in  = new int [SIZE_64_IN_WORDS];

    _secbus_debug(cout, TRACE, "CB BUILT"<<"\n");
  }

};



SC_MODULE(CBCEngine)
{
  public:

    sc_in<bool> clk;
    sc_in<bool> reset;

    CRYPTOGRAPHIC_ENGINE_CMD_IN in_cmd;
    DATA_IN<12>					in_data;
    DATA_IN<2>					in_iv;
    sc_out<bool>				out_ack;
    DATA_OUT<2>					out_data;

    sc_signal<int>				count_s;
    sc_signal<bool>				ack_s;
    sc_signal<unsigned int>		size_s;
    sc_signal<unsigned int>		index_s;

    int *ci;
    int *tmp;
    int *des_in;
    int *pi;
    int *key;

    void ps()
    {

      if (!reset) 
      {
        count_s		= 0;
        index_s		= 0;
        return;
      }

      if(in_cmd.enable)
      {
        assert(in_cmd.imode != NO_INTEGRITY);

        count_s		= DES_L;
        ack_s		= false;
        size_s		= in_cmd.size;
        for (int i=0;i<12;i++)
          pi[i]		= in_data.data[i];

        if (in_cmd.imode == MAC)
          key			= in_cmd.key.read();
        else
          //Same Key for each tree
          key			= MASTER_KEY;

        if (in_cmd.stage_0)
          //address stage of cbc
        {
          index_s		= 0;
          tmp[0]		= in_iv.data[0];
          tmp[1]		= in_iv.data[1];
          des_in[0]	= tmp[0] ^ in_cmd.address;
          des_in[1]	= (tmp[1] ^ in_cmd.address) + 4;
        }
        else
        {
          index_s		= SIZE_64_IN_WORDS;
          tmp[0]		= in_iv.data[0];
          tmp[1]		= in_iv.data[1];		
          des_in[0]	= tmp[0] ^ pi[0];
          des_in[1]	= tmp[1] ^ pi[1];
        }
        DES(des_in, ci, key);	
      }
      else
      {
        count_s = count_s - 1;

        if(count_s == 0)
        {
          if (index_s == size_s)
            //end of operation
            ack_s = true;
          else
          {
            index_s		= index_s + SIZE_64_IN_WORDS;
            count_s		= DES_L;
            tmp[0]		= ci[0];
            tmp[1]		= ci[1];
            des_in[0]	= tmp[0] ^ pi[index_s];
            des_in[1]	= tmp[1] ^ pi[index_s + 1];
            DES(des_in, ci, key);	
          }
        }
      }
    }

    void pc()
    {
      out_ack				= ack_s;
      out_data.data[0]	= ci[0];
      out_data.data[1]	= ci[1];
    }



    SC_CTOR(CBCEngine)
    {
      _secbus_debug(cout, TRACE, "BUILDING CBC ..."<<"\n");

      SC_METHOD(ps);
      sensitive << clk.pos();
      SC_METHOD(pc);
      sensitive << clk.neg();

      pi		= new int [12];
      ci		= new int [SIZE_64_IN_WORDS];
      tmp		= new int [SIZE_64_IN_WORDS];
      des_in	= new int [SIZE_64_IN_WORDS];

      _secbus_debug(cout, TRACE, "CBC BUILT"<<"\n");
    }

};


#endif

