// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// This program 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "partition_system_impl.h"
#include <mobius/decoder/data_decoder.h>
#include <mobius/io/bytearray_io.h>

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Check if stream has an instance of APM partition system
//! \param reader Reader object
//! \param sector_size Sector size in bytes
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
partition_system_impl::is_instance (
  const mobius::io::reader& reader,
  std::uint32_t sector_size
)
{
  const mobius::bytearray SIGNATURE = {'P', 'M', 0x00, 0x00};

  auto r = reader;
  r.seek (1 * sector_size);
  return r.read (4) == SIGNATURE;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Constructor
//! \param reader Reader object
//! \param sector_size Sector size in bytes
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
partition_system_impl::partition_system_impl (
  const mobius::io::reader& reader,
  std::uint32_t sector_size
)
  : reader_ (reader),
    sector_size_ (sector_size)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Load data
//! \see File System Forensic Analysis, "Apple Partitions" section
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
partition_system_impl::_load () const
{
  if (is_loaded_)
    return;

  // add driver descriptor map entry
  mobius::vfs::partition_system_entry e_dmm;
  e_dmm.set_starting_sector (0);
  e_dmm.set_ending_sector (0);
  e_dmm.set_sectors (1);
  e_dmm.set_type ("ddm");
  e_dmm.set_description ("Driver Descriptor Map");
  entries_.push_back (e_dmm);

  // partition map
  auto reader = reader_;
  reader.seek (1 * sector_size_);
  mobius::decoder::data_decoder decoder (reader);

  decoder.skip (4);
  auto partitions = decoder.get_uint32_be ();

  // add partition map entry
  mobius::vfs::partition_system_entry e_pm;
  e_pm.set_starting_sector (1);
  e_pm.set_ending_sector (partitions + 1);
  e_pm.set_sectors (partitions + 1);
  e_pm.set_type ("pmap");
  e_pm.set_description ("Partition Map");
  entries_.push_back (e_pm);

  // read each partition
  for (std::uint32_t i = 0; i < partitions - 1; i++)
    {
      reader.seek ((i + 2) * sector_size_);

      decoder.skip (8);
      auto starting_sector = decoder.get_uint32_be ();
      auto sectors = decoder.get_uint32_be ();
      auto name = decoder.get_string_by_size (32);
      auto type = decoder.get_string_by_size (32);
      auto data_starting_sector = decoder.get_uint32_be ();
      auto data_sectors = decoder.get_uint32_be ();
      auto flags = decoder.get_uint32_be ();
      auto bootcode_starting_sector = decoder.get_uint32_be ();
      auto bootcode_size = decoder.get_uint32_be ();
      auto bootcode_address = decoder.get_uint32_be ();
      decoder.skip (4);  // reserved
      auto bootcode_entry_point = decoder.get_uint32_be ();
      decoder.skip (4);  // reserved
      auto bootcode_checksum = decoder.get_uint32_be ();
      auto processor_type = decoder.get_string_by_size (16);

      if (type != "Apple_Free")
        {
          // create partition
          mobius::vfs::partition p;
          p.set_starting_sector (starting_sector);
          p.set_ending_sector (starting_sector + sectors - 1);
          p.set_sectors (sectors);
          p.set_starting_address (starting_sector * sector_size_);
          p.set_ending_address ((p.get_ending_sector () + 1) * sector_size_ - 1);
          p.set_size (sectors * sector_size_);
          p.set_name (name);
          p.set_type (type);
          p.is_bootable (flags & 0x00000008);
          p.is_readable (flags & 0x00000010);
          p.is_writeable (flags & 0x00000020);
          p.set_attribute ("data_starting_sector", data_starting_sector);
          p.set_attribute ("data_sectors", data_sectors);
          p.set_attribute ("bootcode_starting_sector", bootcode_starting_sector);
          p.set_attribute ("bootcode_size", bootcode_size);
          p.set_attribute ("bootcode_address", bootcode_address);
          p.set_attribute ("bootcode_entry_point", bootcode_entry_point);
          p.set_attribute ("bootcode_checksum", bootcode_checksum);
          p.set_attribute ("processor_type", processor_type);
          partitions_.push_back (p);

          // create partition table entry
          mobius::vfs::partition_system_entry e;
          e.set_starting_sector (starting_sector);
          e.set_ending_sector (starting_sector + sectors - 1);
          e.set_sectors (sectors);
          e.set_type ("partition");
          e.set_description (p.get_name () + " partition");

	  std::string flags;
          if (p.is_bootable ()) flags += 'B';
          if (p.is_readable ()) flags += 'R';
          if (p.is_writeable ()) flags += 'W';
          e.set_flags (flags);

          e.set_attribute ("is_bootable", p.is_bootable ());
          e.set_attribute ("is_primary", p.is_primary ());
          e.set_attribute ("is_extended", p.is_extended ());
          e.set_attribute ("is_logical", p.is_logical ());
          e.set_attribute ("is_hidden", p.is_hidden ());
          e.set_attribute ("is_readable", p.is_readable ());
          e.set_attribute ("is_writeable", p.is_writeable ());

          for (const auto& [name, value] : p.get_attributes ())
            e.set_attribute (name, value);

          entries_.push_back (e);
        }
    }

  // add freespaces between non-contiguous partitions
  sector_type sectors = (reader_.get_size () + sector_size_ - 1) / sector_size_;
  _add_freespaces (entries_, sectors);
  _set_addresses (entries_, sector_size_);
  attributes_.set ("number_of_partitions", partitions_.size ());

  is_loaded_ = true;
}
