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

namespace mobius
{
namespace filesystem
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check if reader contains a VFAT boot sector
//! \param reader stream reader
//! \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 VFAT_SIGNATURE_OFFSET = 0x1fe;

  return data[VFAT_SIGNATURE_OFFSET] == 0x55 &&
         data[VFAT_SIGNATURE_OFFSET + 1] == 0xaa &&
         (data.slice (54, 58) == "FAT12" ||
          data.slice (54, 58) == "FAT16" ||
          data.slice (82, 86) == "FAT32" ||
          data.slice (11, 18) == "\x00\x02\x01\x01\x00\x02\xe0\x00");
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check if stream contains an instance of vfat filesystem
//! \param reader stream reader
//! \param offset offset from the beginning of the stream
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
filesystem_impl_vfat::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 stream reader
//! \param offset offset from the beginning of the stream
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
filesystem_impl_vfat::filesystem_impl_vfat (mobius::io::reader reader, std::uint64_t offset)
  : filesystem_impl_base (reader, offset),
    tsk_adaptor (reader, offset)
{
  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 * 512))
    boot_offset = offset + 6 * 512;

  else          // force data decoding anyway
    boot_offset = offset;

  // get VFAT type
  reader.seek (boot_offset);
  auto data = reader.read (512);

  if (data.slice (54, 58) == "FAT12")
    fat_entry_size_ = 12;

  else if (data.slice (54, 58) == "FAT16")
    fat_entry_size_ = 16;

  else if (data.slice (82, 86) == "FAT32")
    fat_entry_size_ = 32;

  // decode data
  reader.seek (boot_offset);
  mobius::decoder::data_decoder decoder (reader);

  // FDC descriptor (ECMA 9.1)
  decoder.skip (3);
  oem_name_ = mobius::string::rstrip (decoder.get_string_by_size (8));
  sector_size_ = decoder.get_uint16_le ();
  sectors_per_cluster_ = decoder.get_uint8 ();
  reserved_sectors_ = decoder.get_uint16_le ();
  fats_ = decoder.get_uint8 ();
  root_dir_entries_ = decoder.get_uint16_le ();
  sectors_ = decoder.get_uint16_le ();
  media_descriptor_ = decoder.get_uint8 ();
  sectors_per_fat_ = decoder.get_uint16_le ();
  sectors_per_track_ = decoder.get_uint16_le ();
  heads_ = decoder.get_uint16_le ();

  // common metadata
  hidden_sectors_ = decoder.get_uint32_le ();

  auto sectors = decoder.get_uint32_le ();
  if (sectors_ == 0)
    sectors_ = sectors;

  // FAT-32 specific metadata
  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);
    }

  // extended signature
  logical_drive_number_ = decoder.get_uint8 ();
  decoder.skip (1);             // reserved

  auto extended_signature = decoder.get_uint8 ();
  if (extended_signature)
    {
      volume_id_ = decoder.get_uint32_le ();
      volume_label_ = mobius::string::rstrip (decoder.get_string_by_size (11));
      set_fs_type (decoder.get_string_by_size (8));
    }
  else
    set_fs_type ("FAT-" + std::to_string (fat_entry_size_));

  // derived information
  set_size (sectors_ * sector_size_);

  std::string name = "VFAT-" + std::to_string (fat_entry_size_);

  if (!volume_label_.empty ())
    name += " (" + volume_label_ + ")";

  else if (!oem_name_.empty ())
    name += " (" + oem_name_ + ")";

  set_name (name);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get metadata
//! \return metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const mobius::metadata
filesystem_impl_vfat::get_metadata () const
{
  return mobius::metadata
  {
    {
      "type",
      "type",
      "std::string",
      get_type ()
    },
    {
      "offset",
      "offset in bytes",
      "std::uint64_t",
      std::to_string (get_offset ())
    },
    {
      "size",
      "size",
      "size_type",
      std::to_string (get_size ()) + " bytes"
    },
    {
      "name",
      "name",
      "std::string",
      get_name ()
    },
    {
      "fs_type",
      "filesystem type",
      "std::string",
      get_fs_type ()
    },
    {
      "oem_name",
      "OEM name",
      "std::string",
      get_oem_name ()
    },
    {
      "sector_size",
      "sector size",
      "std::uint32_t",
      std::to_string (get_sector_size ()) + " bytes"
    },
    {
      "sectors_per_cluster",
      "sectors per cluster",
      "std::uint32_t",
      std::to_string (get_sectors_per_cluster ())
    },
    {
      "reserved_sectors",
      "number of reserved sectors",
      "std::uint32_t",
      std::to_string (get_reserved_sectors ())
    },
    {
      "fats",
      "number of FATs",
      "std::uint32_t",
      std::to_string (get_fats ())
    },
    {
      "root_dir_entries",
      "number of entries in root directory",
      "std::uint32_t",
      std::to_string (get_root_dir_entries ())
    },
    {
      "sectors",
      "number of sectors",
      "std::uint64_t",
      std::to_string (get_sectors ())
    },
    {
      "media_descriptor",
      "media descriptor type",
      "std::uint16_t",
      std::to_string (get_media_descriptor ())
    },
    {
      "sectors_per_fat",
      "sectors per FAT",
      "std::uint64_t",
      std::to_string (get_sectors_per_fat ())
    },
    {
      "sectors_per_track",
      "sectors per track",
      "std::uint32_t",
      std::to_string (get_sectors_per_track ())
    },
    {
      "heads",
      "number of heads",
      "std::uint32_t",
      std::to_string (get_heads ())
    },
    {
      "hidden_sectors",
      "number of hidden sectors",
      "std::uint64_t",
      std::to_string (get_hidden_sectors ())
    },
    {
      "logical_drive_number",
      "logical drive number",
      "std::uint16_t",
      std::to_string (get_logical_drive_number ())
    },
    {
      "volume_id",
      "volume ID",
      "std::uint32_t",
      "0x" + mobius::string::to_hex (get_volume_id (), 8)
    },
    {
      "volume_label",
      "volume label",
      "std::string",
      get_volume_label ()
    },
    {
      "flags",
      "flags",
      "std::uint32_t",
      std::to_string (get_flags ())
    },
    {
      "version",
      "filesystem version",
      "std::uint32_t",
      std::to_string (get_version ())
    },
    {
      "root_dir_cluster",
      "root directory cluster",
      "std::uint32_t",
      std::to_string (get_root_dir_cluster ())
    },
    {
      "fs_info_sector",
      "filesystem information sector",
      "std::uint64_t",
      std::to_string (get_fs_info_sector ())
    },
    {
      "backup_boot_sector",
      "backup boot sector",
      "std::uint64_t",
      std::to_string (get_backup_boot_sector ())
    },
    {
      "fat_entry_size",
      "FAT entry size",
      "std::uint32_t",
      std::to_string (get_fat_entry_size ()) + " bits"
    },
  };
}

} // namespace filesystem
} // namespace mobius
