/* -*- c++ -*-
 *
 * SOCLIB_LGPL_HEADER_BEGIN
 * 
 * This file is part of SoCLib, GNU LGPLv2.1.
 * 
 * SoCLib is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 of the License.
 * 
 * SoCLib is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with SoCLib; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * SOCLIB_LGPL_HEADER_END
 *
 * Copyright (c) UPMC, Lip6, Asim
 *         Nicolas Pouillon <nipo@ssji.net>, 2007
 *
 * Maintainers: nipo
 */

#include "vci_secbus_ram.h"
#include "soclib_endian.h"
#include "loader.h"
#include <cstring>


namespace soclib {
  namespace caba {
    using namespace std;
    using namespace soclib;

#define tmpl(x) template<typename vci_param> x VciSecbusRam<vci_param>

    tmpl(/**/)::VciSecbusRam(
        sc_module_name insname,
        const IntTab &index,
        const MappingTable &mt,
        const common::Loader &loader, 
        int sp_index)
      : caba::BaseModule(insname),
      m_vci_fsm(p_vci, mt.getSegmentList(index)),
      m_loader(loader),
      p_resetn("resetn"),
      p_clk("clk"),
      p_vci("vci")
    {
      m_vci_fsm.on_read_write(on_read, on_write);

      SC_METHOD(transition);
      dont_initialize();
      sensitive << p_clk.pos();

      SC_METHOD(genMoore);
      dont_initialize();
      sensitive << p_clk.neg();

      m_contents = new ram_t*[m_vci_fsm.nbSegments()];
      std::cout << "policy request " << sp_index << std::endl;
      this->sp_index = sp_index;


      size_t word_size = vci_param::B; // B is VCI's cell size
      for ( size_t i=0; i<m_vci_fsm.nbSegments(); ++i ) {
        m_contents[i] = new ram_t[(m_vci_fsm.getSize(i)+word_size-1)/word_size];
      }
      dumper.open("dump.txt");
      //dumper.open("/dev/null");


      mb_segment = 1;
      SP_nbr = 9;
      cipherSP = new int [SP_SIZE];
      SP = new int* [SP_nbr];

      for (unsigned int i=0;i<SP_nbr;i++)
        SP[i] = new int [SP_SIZE];

      reference = new int[SIZE_64_IN_WORDS];
      block = new int[SIZE_64_IN_WORDS];
      tmp = new int[SIZE_64_IN_WORDS];
      set = new int [LAST_HS_SIZE];
      pspe = new int [PSPE_SIZE];
      //when kernel is protected in integrity or when standalone application 
      
      free_mac_page   = 0x7f800000;//:MHT_END_ADDRESS;
      free_hash_page  = 0x7f800000 + 0x400;//free_mac_page + 0x800000 + 0x400; //MHT_END_ADDRESS + 0x800000 + 0x400;

      tCAS = 3;
      tPRECHARGE = 3;
      tACTIVE = 3;    
    }

    tmpl(/**/)::~VciSecbusRam()
    {
      for (size_t i=0; i<m_vci_fsm.nbSegments(); ++i)
        delete [] m_contents[i];
      delete [] m_contents;
    }

    tmpl(void)::reload()
    {
      unsigned int boundary, j;


      for ( size_t i=0; i<m_vci_fsm.nbSegments(); ++i ) 
      {
            m_loader.load(&m_contents[i][0], m_vci_fsm.getBase(i), m_vci_fsm.getSize(i));

            for ( size_t addr = 0; addr < m_vci_fsm.getSize(i)/vci_param::B; ++addr )	
                m_contents[i][addr] = le_to_machine(m_contents[i][addr]);
      }
      //security policies
      sp_init();

      //text pages
/*      std::cout << "init pspe with " << sp_index << std::endl;
      for ( size_t i=0; i<1; i++ ) 
      {
            std::cout << std::setw(8)<<std::hex<< "segment " << m_vci_fsm.getBase(i) << " size " << m_vci_fsm.getSize(i) << std::endl;
            boundary = get4kPageAddress(m_vci_fsm.getBase(i) + m_vci_fsm.getSize(i));
            boundary = m_vci_fsm.getSize(i) % 0x400000 == 0 ?  boundary : boundary + 0x400000;

            //ro pages
            std::cout << "start=" << m_vci_fsm.getBase(i) << " end="<<boundary<<std::endl;
            for (j=m_vci_fsm.getBase(i);j<boundary;j=j+0x400000)
            {
	            std::cout << i << " " << j << std::endl;
                page_first_init(i,j,boundary);
            }
            }*/

      //reset_cpt segment is not protected
   //   page_init(mb_segment+1, m_vci_fsm.getBase(mb_segment+1), SP[0], 0);
      mbht_init2(PSPE_BASE_ADDRESS, 1, SIZE_OTHER, (SP_BLOCK_SIZE + PSPE_BLOCK_SIZE)/HS_SIZE_IN_BYTES);
//      mbht_init();
      
    }

    tmpl(void)::reset()
    {
      /*for ( size_t i=0; i<m_vci_fsm.nbSegments(); ++i ) {
        std::memset(&m_contents[i][0], 0, m_vci_fsm.getSize(i));
        }*/
      m_cpt_read = 0;
      m_cpt_write = 0;
      m_cpt_idle = 0;
      m_write_latency_cpt = 0; 
      m_read_latency_cpt = 0;
      previous_row = 0;
    }

    tmpl(bool)::on_write(size_t seg, vci_addr_t addr, vci_data_t data, int be)
    {
      int index = addr / vci_param::B;
      ram_t *tab = m_contents[seg];
      unsigned int cur = tab[index];
      uint32_t mask = vci_param::be2mask(be);

      tab[index] = (cur & ~mask) | (data & mask);
 
      if (strcmp(m_vci_fsm.getName(seg), "reset_cpt") == 0)
      {
        _secbus_debug(std::cout, TRACE, "Reset RAM counters"<<"\n");
        reset(); 
      }  	

      dump(87, seg, addr, data, be);
#ifdef SOCLIB_MODULE_DEBUG
      std::cout << "[" << name() << "] write " << m_cpt_write << " address = " << std::hex <<m_vci_fsm.getBase(seg) +  addr << " data = " << data << std::dec << " tab[" << index << "] = " << std::hex << tab[index] << std::dec << std::endl;
#endif

      setWLatencies(m_vci_fsm.getBase(seg) +  addr);
      m_cpt_write++;

      return true;
    }

    tmpl(bool)::on_read(size_t seg, vci_addr_t addr, vci_data_t &data )
    {
      data = m_contents[seg][addr / vci_param::B];

      dump(82, seg, addr, data, 0xF);
#ifdef SOCLIB_MODULE_DEBUG
      std::cout << "[" << name() << "] read " << m_cpt_read << " address = " << std::hex <<m_vci_fsm.getBase(seg) +  addr << " data = " << data << std::dec << std::endl;
#endif

      //Update counters
      setRLatencies(m_vci_fsm.getBase(seg) +  addr);
      m_cpt_read++;
      return true;
    }

    tmpl(void)::transition()
    {
      if (!p_resetn) {
        m_vci_fsm.reset();
        reset();
        // In an ideal world, rams reset to 0, not to previous
        // contents
        reload();
        return;
      }
      m_vci_fsm.transition();
    }

    tmpl(void)::genMoore()
    {
      m_vci_fsm.genMoore();
    }

    tmpl(void):: dump(char W, size_t seg, addr_t addr, int data, int be)
    {	
      dumper<<W<<""<<std::setw(8)<<std::hex<<m_vci_fsm.getBase(seg)+ addr<<"	"<<std::setw(8)<<data<<"	"<<be<<std::endl;	
    }


    tmpl(bool):: isRowOpened(addr_t address)
    {
      //row of 4096 bytes
      addr_t current_row = address & 0xFFFFF000;
      return  (previous_row == current_row); 
    }

    tmpl(void):: setRLatencies(addr_t address)
    {
      if (isRowOpened(address))
        //row already opened
        m_read_latency_cpt += tCAS;
      else
        //close previous row and open a new one
      {
        previous_row = address & 0xFFFFF000;
        m_read_latency_cpt += tCAS + tPRECHARGE + tACTIVE;
      }
    }

    tmpl(void):: setWLatencies(addr_t address)
    {
      if (!isRowOpened(address))
        //close previous row and open a new one
      {
        previous_row = address & 0xFFFFF000;
        m_write_latency_cpt += tPRECHARGE + tACTIVE;
      }
    }


    tmpl(void)::print_stats(){
      std::cout << name() << std::endl;

      std::cout << "- READ               = " << m_cpt_read << std::endl;
      std::cout << "- WRITE              = " << m_cpt_write << std::endl;
      std::cout << "- WRITE LATENCIES    = " << m_write_latency_cpt<< std::endl;
      std::cout << "- READ  LATENCIES    = " << m_read_latency_cpt<< std::endl;
      std::cout << "- LATENCIES          = " << m_read_latency_cpt + m_write_latency_cpt<< std::endl;



    }


    tmpl(void)::sp_init()
    {
      unsigned int i,j;
      for (i=0;i<SP_nbr;i++)
      {
        for (j=0;j<SP_SIZE;j++)
          //Secret Key is a random number
          SP[i][j] = (j == 0)? (rand() & 0xFFFFFFF0) : rand();
      }
      SP[0][0] = SP[0][0] | NO_CONFIDENTIALITY | (NO_INTEGRITY<<2);
      SP[1][0] = SP[1][0] | NO_CONFIDENTIALITY | (MAC<< 2);
      SP[2][0] = SP[2][0] | NO_CONFIDENTIALITY | (HASH_TREE<< 2);
      SP[3][0] = SP[3][0] | OTP | (NO_INTEGRITY << 2);
      SP[4][0] = SP[4][0] | OTP | (MAC << 2);
      SP[5][0] = SP[5][0] | OTP | (HASH_TREE<< 2);
      SP[6][0] = SP[6][0] | BC | (NO_INTEGRITY << 2);
      SP[7][0] = SP[7][0] | BC | (MAC << 2);
      SP[8][0] = SP[8][0] | BC | (HASH_TREE << 2);

      for (i=0;i<SP_nbr;i++)
      {
        //Cipher
        for (j=0;j<(SP_SIZE/SIZE_64_IN_WORDS);j++)
          bc_debug(SP[i] + j*SIZE_64_IN_WORDS, cipherSP + j*SIZE_64_IN_WORDS, MASTER_KEY);
        //Update Memory

        on_write_debug(mb_segment, SP_BASE_ADDRESS + i*SP_SIZE_IN_BYTES, cipherSP, SP_SIZE);
      }
    }
    tmpl(void)::mbht_init()
    {
      //std::cout << (SP_BLOCK_SIZE + PSPE_BLOCK_SIZE)/HS_SIZE_IN_BYTES << std::endl;
      for (unsigned int i=0;i<(SP_BLOCK_SIZE + PSPE_BLOCK_SIZE)/HS_SIZE_IN_BYTES;i++)
      {
        //set address
        set_address = PSPE_BASE_ADDRESS + i*HS_SIZE_IN_BYTES;
        //read hash set
        on_read_debug(mb_segment, set_address, set, HS_SIZE);
        //parent address of the set
        parent_address = getParentLeavesAddress(set_address, MHT_BASE_ADDRESS, SIZE_OTHER);
        //tree update
        ht_update(MHT_BASE_ADDRESS, SIZE_OTHER, true);	
      }
    }

    tmpl(void)::mbht_init2(addr_t first_addr, bool isMasterBlock, PageSize_e ht_page_size, unsigned int n_elems)
    {
      if(isLastLevel(first_addr, ht_page_size))
      {
        set_address = getSetAddress(first_addr, ht_page_size);
        on_read_debug(mb_segment, set_address, set, LAST_HS_SIZE);
        hash_debug(set_address, set, reference, LAST_HS_SIZE);
        memcpy(MASTER_HV, reference, SIZE_64_IN_BYTES);
        return;
      }
      addr_t next_addr = first_addr;
      for (unsigned int i=0;i<n_elems;i++)
      {
        if (isMasterBlock)
        {
          set_address = first_addr + i*HS_SIZE_IN_BYTES;
          parent_address = getParentLeavesAddress(set_address, MHT_BASE_ADDRESS, SIZE_OTHER);
        }
        else
        {
          set_address = getSetAddress(next_addr, ht_page_size);
          parent_address = getParentAddress(set_address, ht_page_size);
          next_addr += HS_SIZE_IN_BYTES;
        } 
        on_read_debug(mb_segment, set_address, set, HS_SIZE);
        hash_debug(set_address, set, reference, HS_SIZE);
        on_write_debug(mb_segment, parent_address, reference, SIZE_64_IN_WORDS);
      }
     
      if (isMasterBlock)
        parent_address = getParentLeavesAddress(first_addr, MHT_BASE_ADDRESS, SIZE_OTHER);
      else
        parent_address = getParentAddress(getSetAddress(first_addr, ht_page_size), ht_page_size);
 
      mbht_init2(parent_address, 0, ht_page_size, n_elems/HASH_ARITY);
    }

    tmpl(void)::ht_update(addr_t ht_start_address, PageSize_e ht_page_size, bool master_tree)
    {
      //hash generation
      hash_debug(set_address, set, reference, HS_SIZE);
      //write reference to memory
      on_write_debug(/*mb_segment*/0, parent_address, reference, SIZE_64_IN_WORDS);
      set_address = getSetAddress(parent_address, ht_page_size);
      if (isLastLevel(parent_address, ht_page_size))
      {
        //read last set
        on_read_debug(/*mb_segment*/0, set_address, set, LAST_HS_SIZE);

        //start hash generation
        hash_debug(set_address, set, reference, LAST_HS_SIZE);
        //Save new hash root value
        if (master_tree) 
        {
          memcpy(MASTER_HV, reference, SIZE_64_IN_BYTES);
        }
      }
      else
      {
        //read last set
        on_read_debug(/*mb_segment*/0, set_address, set, HS_SIZE);
        //Compute next parent address
        parent_address = getParentAddress(set_address, ht_page_size);
        //iterative function
        ht_update(ht_start_address, ht_page_size, master_tree);
      }
    }

    tmpl(void)::page_first_init(size_t segment, addr_t address, unsigned int boundary)
    {
      pspe_address = getFirstLevelAddress(PSPE_BASE_ADDRESS, address);
      std::cout<<"---------- pspe_address first level address : " << pspe_address << endl;
      pspe_init(SIZE_4K, sp_index, PSPE_BASE_ADDRESS+0x2000,MASTER_PSPE);

      if (address < 0x7f800000)
      {
          for (unsigned int i=0;i<0x400000;i+=0x1000)
          {
              page_init(segment, address+i, SP[sp_index], sp_index);
          }
      }
    }
    
    tmpl(void)::page_init(size_t segment, addr_t address, int *sp, unsigned sp_index)
    {
      //master pspe address
      pspe_address = getSecondLevelAddress(PSPE_BASE_ADDRESS+0x2000, address);

      //confidentiality initialization
      if (getPM(sp)!= NO_CONFIDENTIALITY)
        cipher_init(segment, address, 0x1000, sp, getPM(sp));

      //integrity initialization
      if (getIM(sp)== MAC)
      {
        mac_init(segment, address, 0x1000, sp);

        //master pspe initialization
        pspe_init(SIZE_4K, sp_index, free_mac_page, MASTER_PSPE);
        pspe_address = getFirstLevelAddress(PSPE_BASE_ADDRESS, free_mac_page);
        pspe_init(SIZE_4K, sp_index, PSPE_BASE_ADDRESS+0x2000, SLAVE_PSPE, true);
        pspe_address = getSecondLevelAddress(PSPE_BASE_ADDRESS+0x2000, free_mac_page);
        pspe_init(SIZE_4K, sp_index, free_mac_page, SLAVE_PSPE);

        //update free mac page
        free_mac_page = free_mac_page + 0x1000/MAC_ARITY;
      }
      else if (getIM(sp)== HASH_TREE)
      {
        //integrity initialization
        ht_init(segment, address, 0x1000);
        //first level slave pspe address
        pspe_address = getFirstLevelAddress(PSPE_BASE_ADDRESS, free_hash_page);
            //std::cout << " --- FirstLevelAddress free hash : " << pspe_address << endl;
        pspe_init(SIZE_4K, sp_index, PSPE_BASE_ADDRESS+0x2000, SLAVE_PSPE,true);
        //slave pspe address
        pspe_address = getSecondLevelAddress(PSPE_BASE_ADDRESS+0x2000, free_hash_page);
        //slave pspe initialization
        pspe_init(SIZE_4K, sp_index, free_hash_page, SLAVE_PSPE);
        //master pspe address
        pspe_address = getSecondLevelAddress(PSPE_BASE_ADDRESS+0x2000, address);
        //master pspe initialization
        pspe_init(SIZE_4K, sp_index, free_hash_page, MASTER_PSPE);//1
        //update free hash page
        if ((free_hash_page & 0xFFF) == 0xC00)
        {
          //page is full
          free_hash_page = get4kPageAddress(free_hash_page) + 0x1400;
        }
        else
        {
          free_hash_page = free_hash_page + 0x400;
        }
      }
      else
      {
        //master pspe initialization
        pspe_init(SIZE_4K, sp_index, free_mac_page, MASTER_PSPE);
      }
    }

    tmpl(void)::cipher_init(size_t segment, addr_t address, unsigned int size_in_bytes, int *key, confidentiality_mode_e confidentiality)
    {
      addr_t block_address;
      //Privacy initialisation
      for (unsigned int i=0;i<(size_in_bytes/SIZE_64_IN_BYTES);i++)
      {
        block_address = address + i*SIZE_64_IN_BYTES;
        //Read OTP block
        on_read_debug(segment, block_address, block, SIZE_64_IN_WORDS);
        //Cipher block
        if (confidentiality == OTP) otp_debug(block_address, block, block, key);
        if (confidentiality == BC) bc_debug(block, block, key);
        //Write Ciphered/deciphered block
        on_write_debug(segment, block_address, block, SIZE_64_IN_WORDS);
      }
    }
 
    tmpl(void)::pspe_first_init(PageSize_e size, PSPEtype_e type, addr_t base_address)
    {
      memset(pspe, 0, PSPE_SIZE_IN_BYTES);
      setValidBit(pspe, true);
      setTypeBit(pspe, type);
      setSizeBit(pspe,size);
      setBaseAddress(pspe, base_address);

      on_write_debug(mb_segment, pspe_address, pspe, PSPE_SIZE);
    }

    tmpl(void)::pspe_init(PageSize_e size, unsigned int sp_index, addr_t base_address, PSPEtype_e type, bool first = false)
    {
      memset(pspe, 0, PSPE_SIZE_IN_BYTES);
      setValidBit(pspe, true);
      setTypeBit(pspe, type);
      setSizeBit(pspe, size);
      if (type == MASTER_PSPE || first)
      {
        setSPindex(pspe, sp_index);
        setBaseAddress(pspe, base_address);
      }
      else
      {
        setRHV(pspe, reference);
      }
      //Write in memory
      on_write_debug(mb_segment, pspe_address, pspe, PSPE_SIZE);
    }

    tmpl(void)::ht_init(size_t segment, addr_t address, unsigned int size_in_bytes)
    {
      //Integrity initialization
      for (unsigned int i=0;i<(size_in_bytes/HS_SIZE_IN_BYTES);i++)
      {
        set_address = address + i*HS_SIZE_IN_BYTES;
        //read hash set
        on_read_debug(segment, set_address, set, HS_SIZE);
        //parent address of the set in tree
        parent_address = getParentLeavesAddress(set_address, free_hash_page, SIZE_4K);
        //update tree
        ht_update(free_hash_page, SIZE_4K, false);
      }
    }
    tmpl(void)::mac_init(size_t segment, addr_t address, unsigned int size_in_bytes, int* key)
    {
        //std::cout << "size_in_bytes/MS_SIZE_IN_BYTES : " << size_in_bytes/MS_SIZE_IN_BYTES << endl;
      for (unsigned int i=0;i<(size_in_bytes/MS_SIZE_IN_BYTES);i++)
      {
        set_address = address + i*MS_SIZE_IN_BYTES;
        //Read Mac set
        on_read_debug(segment, set_address, set, MS_SIZE);
        //generate mac
        mac_debug(set_address, set, reference, key);
        //Compute mac address
        parent_address = getMacAddress(set_address , free_mac_page, SIZE_4K);
        //write reference value
        on_write_debug(segment, parent_address, reference, SIZE_64_IN_WORDS);
      }
    } 

    tmpl(void)::on_write_debug(size_t segment, addr_t address, int *data, unsigned int size)
    {
      count_w++;
      unsigned int index = (address - m_vci_fsm.getBase(segment))/ vci_param::B;
      for (unsigned int i=0;i<size;i++)
        m_contents[segment][index + i] = data[i];
    }

    tmpl(void)::on_read_debug(size_t segment, addr_t address, int *data, unsigned int size)
    {
      count_r++;
      unsigned int index = (address - m_vci_fsm.getBase(segment))/ vci_param::B;
      for (unsigned int i=0;i<size;i++)
        data[i] = m_contents[segment][index + i];
    }
    //Cryptographic functions in debug mode
    tmpl(void)::bc_debug(int * input, int *output, int * key)
    {
      DES(input, output, key);
    }
    tmpl(void)::otp_debug(addr_t address, int * input, int *output, int * key)
    {
      tmp[0]	= address;
      tmp[1]	= address + 4;
      DES(tmp, tmp, key);
      output[0] = tmp[0] ^ input[0];
      output[1] = tmp[1] ^ input[1];
    }
    tmpl(void)::mac_debug(addr_t address, int * input, int *output, int * key)
    {
      tmp[0]	= address;
      tmp[1]	= address + 4;
      DES(tmp, tmp, key);

      for (unsigned int i = 0; i < MS_SIZE; i = i + SIZE_64_IN_WORDS)
      {
        tmp[0]	= input[i]		^ tmp[0];
        tmp[1]	= input[i + 1]	^ tmp[1];

        DES(tmp, tmp, key);
      }
      output[0] = tmp[0];
      output[1] = tmp[1];
    }
    tmpl(void)::hash_debug(addr_t address, int * input, int *output, unsigned int size)
    {
      count_h++;
      tmp[0]	= address;
      tmp[1]	= address + 4;
      DES(tmp, tmp, MASTER_KEY);

      for (unsigned int i = 0; i < size; i = i + SIZE_64_IN_WORDS)
      {
        tmp[0]	= input[i]		^ tmp[0];
        tmp[1]	= input[i + 1]	^ tmp[1];

        DES(tmp, tmp, MASTER_KEY);
      }
      output[0] = tmp[0];
      output[1] = tmp[1];
    }
  }}

// Local Variables:
// tab-width: 4
// c-basic-offset: 4
// c-file-offsets:((innamespace . 0)(inline-open . 0))
// indent-tabs-mode: nil
// End:

// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4

