/**
 * Copyright (C) 2019 - 2020, 2022 Xilinx, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may
 * not use this file except in compliance with the License. A copy of the
 * License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

#include "DTC.h"

#include "XclBinUtilities.h"
#include <boost/format.hpp>

namespace XUtil = XclBinUtilities;

#ifdef _WIN32
  #pragma comment(lib, "wsock32.lib")
  #include <winsock2.h>
#else
  #include <arpa/inet.h>
#endif

DTC::DTC()
    : m_pTopFDTNode(NULL)
{
  // Empty
}

DTC::DTC(const char* _pBuffer,
         const unsigned int _size,
         const FDTProperty::PropertyNameFormat& _propertyNameFormat)
    : DTC()
{
  marshalFromDTCImage(_pBuffer, _size, _propertyNameFormat);
}

DTC::DTC(const boost::property_tree::ptree& _ptDTC,
         const FDTProperty::PropertyNameFormat& _propertyNameFormat)
    : DTC()
{
  marshalFromJSON(_ptDTC, _propertyNameFormat);
}


DTC::~DTC()
{
  if (m_pTopFDTNode != NULL) {
    delete m_pTopFDTNode;
    m_pTopFDTNode = NULL;
  }
}

// DTC Header
struct fdt_header {
  uint32_t magic;
  uint32_t totalsize;
  uint32_t off_dt_struct;
  uint32_t off_dt_strings;
  uint32_t off_mem_rsvmap;
  uint32_t version;
  uint32_t last_comp_version;
  uint32_t boot_cpuid_phys;
  uint32_t size_dt_strings;
  uint32_t size_dt_struct;
};

void
DTC::marshalFromDTCImage(const char* _pBuffer,
                         const unsigned int _size,
                         const FDTProperty::PropertyNameFormat& _propertyNameFormat)
{
  XUtil::TRACE("Marshalling from DTC Image");

  // -- Validate the buffer --
  if (_pBuffer == NULL) {
    throw std::runtime_error("ERROR: The given buffer pointer is NULL.");
  }

  static const uint32_t FDT_HEADER_SIZE = sizeof(fdt_header);

  // Check the header size
  if (_size < FDT_HEADER_SIZE) {
    auto errMsg = boost::format("ERROR: The given DTC buffer's header size (%d bytes) is smaller then the expected size (%d bytes).") %  _size % FDT_HEADER_SIZE;
    throw std::runtime_error(errMsg.str());
  }

  const fdt_header* pHdr = (const fdt_header*)_pBuffer;

  // Look for the magic number
  static const uint32_t MAGIC = 0xd00dfeed;

  if (ntohl(pHdr->magic) != MAGIC) {
    auto errMsg = boost::format("ERROR: Missing DTC magic number.  Expected: 0x%x, Found: 0x%x.") % MAGIC % ntohl(pHdr->magic);
    throw std::runtime_error(errMsg.str());
  }

  // Validate Size
  if (ntohl(pHdr->totalsize) != _size) {
    auto errMsg = boost::format("ERROR: The expected size (%d bytes) does not match actual (%d bytes)") %  ntohl(pHdr->totalsize) % _size;
    throw std::runtime_error(errMsg.str());
  }

  // -- Get and validate the strings offset --
  if (ntohl(pHdr->off_dt_strings) > _size) {
    auto errMsg = boost::format("ERROR: The string block offset (0x%x) exceeds then image size (0x%x)") % ntohl(pHdr->off_dt_strings) % _size;
    throw std::runtime_error(errMsg.str());
  }

  if ((ntohl(pHdr->off_dt_strings) + ntohl(pHdr->size_dt_strings)) > _size) {
    auto errMsg = boost::format("ERROR: The string block offset and size (0x%x) exceeds then image size (0x%x)") % (ntohl(pHdr->off_dt_strings) + ntohl(pHdr->size_dt_strings)) % _size;
    throw std::runtime_error(errMsg.str());
  }

  // -- Get the strings block --
  const char* pStringBuffer = _pBuffer + ntohl(pHdr->off_dt_strings);
  unsigned int stringBufferSize = ntohl(pHdr->size_dt_strings);
  m_DTCStringsBlock.parseDTCStringsBlock(pStringBuffer, stringBufferSize);

  // -- Get the validate the structure nodes offset --
  if (ntohl(pHdr->off_dt_struct) > _size) {
    auto errMsg = boost::format("ERROR: The structure block offset (0x%x) exceeds then image size (0x%x)") % ntohl(pHdr->off_dt_struct) % _size;
    throw std::runtime_error(errMsg.str());
  }

  if ((ntohl(pHdr->off_dt_struct) + ntohl(pHdr->size_dt_struct)) > _size) {
    auto errMsg = boost::format("ERROR: The structure block offset and size (0x%x) exceeds then image size (0x%x)") % (ntohl(pHdr->off_dt_struct) + ntohl(pHdr->size_dt_struct)) % _size;
    throw std::runtime_error(errMsg.str());
  }

  // -- Get the top FDT Note (which will include the node tree) --
  const char* pStructureBuffer = _pBuffer + ntohl(pHdr->off_dt_struct);
  unsigned int structureBufferSize = ntohl(pHdr->size_dt_struct);
  m_pTopFDTNode = FDTNode::marshalFromDTC(pStructureBuffer, structureBufferSize, m_DTCStringsBlock, _propertyNameFormat);

  XUtil::TRACE("Marshalling complete");
}


void
DTC::marshalToJSON(boost::property_tree::ptree& _dtcTree,
                   const FDTProperty::PropertyNameFormat& _propertyNameFormat) const
{
  XUtil::TRACE("");

  if (m_pTopFDTNode == nullptr) {
    throw std::runtime_error("ERROR: There are no structure nodes in this design.");
  }

  m_pTopFDTNode->marshalToJSON(_dtcTree, _propertyNameFormat);
}


void
DTC::marshalFromJSON(const boost::property_tree::ptree& _ptDTC,
                     const FDTProperty::PropertyNameFormat& _propertyNameFormat)
{
  XUtil::TRACE("Marshalling from JSON Image");

  m_pTopFDTNode = FDTNode::marshalFromJSON(_ptDTC, _propertyNameFormat);
}


#define FDT_END         0x00000009
#define FDT_MAGIC       0xd00dfeed

void
DTC::marshalToDTC(std::ostringstream& _buf) const
{
  XUtil::TRACE("");

  if (m_pTopFDTNode == NULL) {
    throw std::runtime_error("ERROR: There are no structure nodes in this design.");
  }

  // Note roll over is checked at the end of this method
  uint64_t runningOffset = 0;

  // -- Create header --
  fdt_header header = { 0 };
  header.magic = htonl(FDT_MAGIC);
  header.version = htonl(0x11);
  header.boot_cpuid_phys = htonl(0);
  runningOffset += sizeof(fdt_header);

  // -- Create dummy "memory block" --
  std::ostringstream memoryBlock;
  static const unsigned int FDTReservedEntrySize = 16;
  for (unsigned index = 0; index < FDTReservedEntrySize; ++index) {
    char emptyByte = '\0';
    memoryBlock.write(&emptyByte, sizeof(char));
  }
  std::string sMemoryBlock = memoryBlock.str();
  header.off_mem_rsvmap = htonl((uint32_t)runningOffset);
  runningOffset += sMemoryBlock.size();

  // -- Create structure node image --
  std::ostringstream structureNodesBuf;
  DTCStringsBlock stringsBlock;
  m_pTopFDTNode->marshalToDTC(stringsBlock, structureNodesBuf);
  XUtil::write_htonl(structureNodesBuf, FDT_END);
  std::string sStructureNodes = structureNodesBuf.str();
  header.off_dt_struct = htonl((uint32_t)runningOffset);
  header.size_dt_struct = htonl((uint32_t)sStructureNodes.size());
  runningOffset += sStructureNodes.size();

  // -- Create strings block --
  std::ostringstream stringBlock;
  stringsBlock.marshalToDTC(stringBlock);
  std::string sStringBlock = stringBlock.str();
  header.off_dt_strings = htonl((uint32_t)runningOffset);
  header.size_dt_strings = htonl((uint32_t)sStringBlock.size());
  runningOffset += sStringBlock.size();

  // Complete the header
  header.totalsize = htonl((uint32_t)runningOffset);

  // -- Put it all together --
  _buf.write((char*)&header, sizeof(fdt_header));
  _buf.write(sMemoryBlock.c_str(), sMemoryBlock.size());
  _buf.write(sStructureNodes.c_str(), sStructureNodes.size());
  _buf.write(sStringBlock.c_str(), sStringBlock.size());

  // -- Integrity check --
  if (runningOffset >= UINT32_MAX) {
    throw std::runtime_error("ERROR: DTC block exceeds 4 GBytes (uint32_t max size)");
  }
}
