// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024 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 "filesystem_impl.h"
#include <mobius/decoder/data_decoder.h>
#include <mobius/string_functions.h>

namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Constants
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
constexpr std::uint32_t SECTOR_SIZE = 512;
constexpr std::uint32_t MAX_CLUSTERS_FAT12 = 0xff4;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Check if reader contains a VFAT boot sector
//! \param reader Reader object
//! \param offset Offset from the beginning of the stream
//! \return True/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static bool
check_vfat_boot_sector (mobius::io::reader reader, std::uint64_t offset)
{
  reader.seek (offset);
  auto data = reader.read (512);

  constexpr int SECTOR_SIZE_OFFSET = 11;
  constexpr int RESERVED_SECTORS_OFFSET = 14;
  constexpr int FATS_OFFSET = 16;
  constexpr int MEDIA_TYPE_OFFSET = 21;
  constexpr int VFAT_SIGNATURE_OFFSET = 0x1fe;

  return
    data.size () >= 512 &&

    // Signature
    data[VFAT_SIGNATURE_OFFSET] == 0x55 &&
    data[VFAT_SIGNATURE_OFFSET + 1] == 0xaa &&

    // Sector size must be multiple of 512 bytes
    (data[SECTOR_SIZE_OFFSET] == 0x00 &&
     !(data[SECTOR_SIZE_OFFSET + 1] & 0x01) &&
      (data[SECTOR_SIZE_OFFSET + 1] & 0xfe)) &&

    // Reserved sectors must be != 0
    (data[RESERVED_SECTORS_OFFSET] ||
     data[RESERVED_SECTORS_OFFSET + 1]) &&

    // Number of FATs
    data[FATS_OFFSET] == 2 &&

    // Media type
    (data[MEDIA_TYPE_OFFSET] == 0xf0 ||
     data[MEDIA_TYPE_OFFSET] >= 0xf8);
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Check if stream contains an instance of vfat filesystem
//! \param reader Reader object
//! \param offset Offset from the beginning of the stream
//! \return True/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
filesystem_impl::is_instance (mobius::io::reader reader, std::uint64_t offset)
{
  // check sector 0
  bool rc = check_vfat_boot_sector (reader, offset);

  // check sector 6 (FAT-32 usually has a backup boot sector there)
  if (!rc)
    rc = check_vfat_boot_sector (reader, offset + 6 * 512);

  // return result
  return rc;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Constructor
//! \param reader Reader object
//! \param offset Offset from the beginning of volume
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
filesystem_impl::filesystem_impl (
  const mobius::io::reader& reader,
  size_type offset
)
 : reader_ (reader),
   offset_ (offset),
   tsk_adaptor_ (reader, offset)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get metadata item
//! \param name Item name
//! \return Data object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::pod::data
filesystem_impl::get_metadata (const std::string& name) const
{
  _load_data ();
  return metadata_.get (name);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get root folder
//! \return Root folder
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::io::folder
filesystem_impl::get_root_folder () const
{
  return tsk_adaptor_.get_root_folder ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Load data on demand
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
filesystem_impl::_load_data () const
{
  if (data_loaded_)
    return;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Get boot sector offset
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  mobius::io::reader reader = reader_;
  std::uint64_t boot_offset = 0;

  if (check_vfat_boot_sector (reader, offset_))
    boot_offset = offset_;

  else if (check_vfat_boot_sector (reader, offset_ + 6 * SECTOR_SIZE))
    boot_offset = offset_ + 6 * SECTOR_SIZE;

  else          // force data decoding anyway
    boot_offset = offset_;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Decode FDC descriptor (ECMA 9.1)
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  reader.seek (boot_offset);
  mobius::decoder::data_decoder decoder (reader);

  decoder.skip (3);
  auto oem_name = mobius::string::rstrip (decoder.get_string_by_size (8));
  auto sector_size = decoder.get_uint16_le ();
  auto sectors_per_cluster = decoder.get_uint8 ();
  auto reserved_sectors = decoder.get_uint16_le ();
  auto fats = decoder.get_uint8 ();
  auto root_dir_entries = decoder.get_uint16_le ();
  std::uint32_t sectors = decoder.get_uint16_le ();
  auto media_descriptor = decoder.get_uint8 ();
  std::uint32_t sectors_per_fat = decoder.get_uint16_le ();
  auto sectors_per_track = decoder.get_uint16_le ();
  auto heads = decoder.get_uint16_le ();
  auto hidden_sectors = decoder.get_uint32_le ();
  auto sectors_32bit = decoder.get_uint32_le ();

  if (sectors == 0)
    sectors = sectors_32bit;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Calculate FAT entry size in bits
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  std::uint16_t fat_entry_size = 0;
  std::uint32_t total_clusters = (sectors - reserved_sectors) / sectors_per_cluster;

  if (sectors_per_fat)		// Either FAT-12 or FAT-16
    fat_entry_size = total_clusters > MAX_CLUSTERS_FAT12 ? 16 : 12;

  else				// FAT-32
    fat_entry_size = 32;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Get FAT-32 info
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  std::uint16_t flags = 0;
  std::uint16_t version = 0;
  std::uint32_t root_dir_cluster = 0;
  std::uint16_t fs_info_sector = 0;
  std::uint16_t backup_boot_sector = 0;

  if (fat_entry_size == 32)
    {
      sectors_per_fat = decoder.get_uint32_le ();
      flags = decoder.get_uint16_le ();
      version = decoder.get_uint16_le ();
      root_dir_cluster = decoder.get_uint32_le ();
      fs_info_sector = decoder.get_uint16_le ();
      backup_boot_sector = decoder.get_uint16_le ();
      decoder.skip (12);
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Decode Extended BPB fields
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  std::string volume_id;
  std::string volume_label;
  std::string filesystem_type;

  auto logical_drive_number = decoder.get_uint8 ();
  bool is_dirty = (decoder.get_uint8 () == 0x01);
  auto extended_signature = decoder.get_uint8 ();

  if (extended_signature)
    {
      volume_id = "0x" + mobius::string::to_hex (decoder.get_uint32_le (), 8);
      volume_label = mobius::string::rstrip (decoder.get_string_by_size (11));
      filesystem_type = mobius::string::rstrip (decoder.get_string_by_size (8));
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Set metadata
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  metadata_.set ("oem_name", oem_name);
  metadata_.set ("volume_id", volume_id);
  metadata_.set ("volume_label", volume_label);
  metadata_.set ("sector_size", sector_size);
  metadata_.set ("sectors_per_cluster", sectors_per_cluster);
  metadata_.set ("reserved_sectors", reserved_sectors);
  metadata_.set ("total_clusters", total_clusters);
  metadata_.set ("fats", fats);
  metadata_.set ("fat_entry_size", fat_entry_size);
  metadata_.set ("root_dir_entries", root_dir_entries);
  metadata_.set ("sectors", sectors);
  metadata_.set ("media_descriptor", media_descriptor);
  metadata_.set ("sectors_per_fat", sectors_per_fat);
  metadata_.set ("sectors_per_track", sectors_per_track);
  metadata_.set ("heads", heads);
  metadata_.set ("hidden_sectors", hidden_sectors);
  metadata_.set ("logical_drive_number", logical_drive_number);
  metadata_.set ("extended_signature", extended_signature);
  metadata_.set ("filesystem_type", filesystem_type);
  metadata_.set ("is_dirty", is_dirty);

  if (fat_entry_size == 32)
    {
      metadata_.set ("flags", flags);
      metadata_.set ("version", version);
      metadata_.set ("root_dir_cluster", root_dir_cluster);
      metadata_.set ("fs_info_sector", fs_info_sector);
      metadata_.set ("backup_boot_sector", backup_boot_sector);
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Set derived information
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  type_ = "fat" + std::to_string (fat_entry_size);
  size_ = static_cast <std::uint64_t> (sectors) * sector_size;

  name_ = "VFAT-" + std::to_string (fat_entry_size);

  if (!volume_label.empty ())
    name_ += " (" + volume_label + ")";

  else if (!oem_name.empty ())
    name_ += " (" + oem_name + ")";

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Decode FSINFO
  //! \see https://thestarman.pcministry.com/asm/mbr/MSWIN41.htm#FSINFO
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  if (fs_info_sector)
    {
      reader.seek (fs_info_sector * SECTOR_SIZE);
      auto signature_1 = decoder.get_bytearray_by_size (4);

      if (signature_1 == "RRaA")
        {
          reader.skip (0x1e4);
          metadata_.set ("free_clusters", decoder.get_uint32_le ());
          metadata_.set ("next_available_cluster", decoder.get_uint32_le ());
        }
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Set data loaded
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  data_loaded_ = true;
}
