// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "scrambled_ecc32_mem_area.h"

#include <algorithm>
#include <cassert>
#include <iostream>
#include <sstream>

#include "scramble_model.h"
#include "sv_scoped.h"

// This is the maximum width of a nonce that's supported by the code in
// prim_util_get_scramble_key_nonce.svh
static const uint32_t kScrMaxNonceWidth = 320;
static const uint32_t kScrMaxNonceWidthByte = (kScrMaxNonceWidth + 7) / 8;

// Functions to convert from integer address to/from a little-endian vector of
// bytes, addr_width is given in bits
static std::vector<uint8_t> AddrIntToBytes(uint32_t addr, uint32_t addr_width) {
  uint32_t addr_width_bytes = (addr_width + 7) / 8;
  std::vector<uint8_t> addr_bytes(addr_width_bytes);

  for (uint32_t i = 0; i < addr_width_bytes; ++i) {
    addr_bytes[i] = addr & 0xff;
    addr >>= 8;
  }

  return addr_bytes;
}

static uint32_t AddrBytesToInt(const std::vector<uint8_t> &addr) {
  assert(addr.size() <= 4);

  uint32_t addr_out = 0;
  int cur_shift = 0;

  for (uint8_t byte : addr) {
    addr_out |= ((uint32_t)byte << cur_shift);
    cur_shift += 8;
  }

  return addr_out;
}

// Converts svBitVecVal (bit[m:n] SV type) into a byte vector
static std::vector<uint8_t> ByteVecFromSV(svBitVecVal sv_val[],
                                          uint32_t bytes) {
  int shift = 0;
  std::vector<uint8_t> vec(bytes);
  for (uint32_t i = 0; i < bytes; ++i) {
    vec[i] = (sv_val[i / 4] >> shift) & 0xff;

    shift += 8;
    if (shift == 32) {
      shift = 0;
    }
  }

  return vec;
}

// Analogous to the vbits SystemVerilog function from prim_util_pkg.sv. It
// calculates the number of bits needed to address size items.
static uint32_t vbits(uint32_t size) {
  assert(size > 0);

  if (size == 1) {
    return 1;
  }

  size -= 1;
  uint32_t width = 0;
  while (size) {
    width++;
    size /= 2;
  }

  return width;
}

extern "C" {
int simutil_get_scramble_key(svBitVecVal *key);
int simutil_get_scramble_nonce(svBitVecVal *nonce);
}

std::vector<uint8_t> ScrambledEcc32MemArea::GetScrambleKey() const {
  SVScoped scoped(scr_scope_);
  svBitVecVal key_minibuf[((kPrinceWidthByte * 2) + 3) / 4];

  if (!simutil_get_scramble_key(key_minibuf)) {
    std::ostringstream oss;
    oss << "Could not read key at scope " << scr_scope_;
    throw std::runtime_error(oss.str());
  }

  return ByteVecFromSV(key_minibuf, kPrinceWidthByte * 2);
}

std::vector<uint8_t> ScrambledEcc32MemArea::GetScrambleNonce() const {
  assert(GetNonceWidthByte() <= kScrMaxNonceWidthByte);

  SVScoped scoped(scr_scope_);
  svBitVecVal nonce_minibuf[(kScrMaxNonceWidthByte + 3) / 4];

  if (!simutil_get_scramble_nonce((svBitVecVal *)nonce_minibuf)) {
    std::ostringstream oss;
    oss << "Could not read nonce at scope " << scr_scope_;
    throw std::runtime_error(oss.str());
  }

  return ByteVecFromSV(nonce_minibuf, GetNonceWidthByte());
}

ScrambledEcc32MemArea::ScrambledEcc32MemArea(const std::string &scope,
                                             uint32_t size, uint32_t width_32,
                                             bool repeat_keystream)
    : Ecc32MemArea(
          SVScoped::join_sv_scopes(
              scope, "u_prim_ram_1p_adv.u_mem.gen_generic.u_impl_generic"),
          size, width_32),
      scr_scope_(scope) {
  addr_width_ = vbits(size);
  repeat_keystream_ = repeat_keystream;
}

uint32_t ScrambledEcc32MemArea::GetPhysWidth() const {
  return (GetWidthByte() / 4) * 39;
}

uint32_t ScrambledEcc32MemArea::GetPhysWidthByte() const {
  return (GetPhysWidth() + 7) / 8;
}

uint32_t ScrambledEcc32MemArea::GetPrinceReplications() const {
  if (repeat_keystream_) {
    return 1;
  } else {
    return (GetPhysWidthByte() + 7) / 8;
  }
}

uint32_t ScrambledEcc32MemArea::GetNonceWidth() const {
  return GetPrinceReplications() * 64;
}

uint32_t ScrambledEcc32MemArea::GetNonceWidthByte() const {
  return GetPrinceReplications() * 8;
}

void ScrambledEcc32MemArea::WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
                                        const std::vector<uint8_t> &data,
                                        size_t start_idx,
                                        uint32_t dst_word) const {
  // Compute integrity
  Ecc32MemArea::WriteBuffer(buf, data, start_idx, dst_word);
  ScrambleBuffer(buf, dst_word);
}

std::vector<uint8_t> ScrambledEcc32MemArea::ReadUnscrambled(
    const uint8_t buf[SV_MEM_WIDTH_BYTES], uint32_t src_word) const {
  std::vector<uint8_t> scrambled_data(buf, buf + GetPhysWidthByte());
  return scramble_decrypt_data(
      scrambled_data, GetPhysWidth(), 39, AddrIntToBytes(src_word, addr_width_),
      addr_width_, GetScrambleNonce(), GetScrambleKey(), repeat_keystream_);
}

void ScrambledEcc32MemArea::ReadBuffer(std::vector<uint8_t> &data,
                                       const uint8_t buf[SV_MEM_WIDTH_BYTES],
                                       uint32_t src_word) const {
  std::vector<uint8_t> unscrambled_data = ReadUnscrambled(buf, src_word);
  // Strip integrity to give final result
  Ecc32MemArea::ReadBuffer(data, &unscrambled_data[0], src_word);
}

void ScrambledEcc32MemArea::ReadBufferWithIntegrity(
    EccWords &data, const uint8_t buf[SV_MEM_WIDTH_BYTES],
    uint32_t src_word) const {
  std::vector<uint8_t> unscrambled_data = ReadUnscrambled(buf, src_word);
  Ecc32MemArea::ReadBufferWithIntegrity(data, &unscrambled_data[0], src_word);
}

void ScrambledEcc32MemArea::WriteBufferWithIntegrity(
    uint8_t buf[SV_MEM_WIDTH_BYTES], const EccWords &data, size_t start_idx,
    uint32_t dst_word) const {
  Ecc32MemArea::WriteBufferWithIntegrity(buf, data, start_idx, dst_word);
  ScrambleBuffer(buf, dst_word);
}

void ScrambledEcc32MemArea::ScrambleBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
                                           uint32_t dst_word) const {
  std::vector<uint8_t> scramble_buf(buf, buf + GetPhysWidthByte());

  // Scramble data with integrity
  scramble_buf = scramble_encrypt_data(
      scramble_buf, GetPhysWidth(), 39, AddrIntToBytes(dst_word, addr_width_),
      addr_width_, GetScrambleNonce(), GetScrambleKey(), repeat_keystream_);

  // Copy scrambled data to write buffer
  std::copy(scramble_buf.begin(), scramble_buf.end(), &buf[0]);
}

uint32_t ScrambledEcc32MemArea::ToPhysAddr(uint32_t logical_addr) const {
  // Scramble logical address to get physical address
  return AddrBytesToInt(scramble_addr(AddrIntToBytes(logical_addr, addr_width_),
                                      addr_width_, GetScrambleNonce(),
                                      GetNonceWidth()));
}
