// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 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 <mobius/imagefile/ewf/imagefile_impl.h>
#include <mobius/imagefile/ewf/segment_decoder.h>
#include <mobius/imagefile/ewf/reader_impl.h>
#include <mobius/imagefile/ewf/writer_impl.h>
#include <mobius/string_functions.h>
#include <mobius/io/file.h>
#include <mobius/exception.inc>
#include <stdexcept>

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \see https://github.com/libyal/libewf/blob/master/documentation/Expert%20Witness%20Compression%20Format%20(EWF).asciidoc
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
namespace mobius
{
namespace imagefile
{
namespace ewf
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get compression level description
//! \param c Compression code
//! \return Description
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
compression_level_to_string (std::uint32_t c)
{
  if (c == 0)
    return "no compression";

  else if (c == 1)
    return "fast";

  else if (c == 2)
    return "best";

  else
    return "unknown";
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get segment file extension
//! \param idx Segment idx
//! \return File extension
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
get_segment_extension (segment_array::idx_type idx)
{
  if (idx < 100)
    return std::string ("E") + mobius::string::to_string (idx + 1, 2);

  else if (idx < 776)
    {
      auto value = idx - 100;
      return std::string ("E") + char ('A' + value / 26) + char ('A' + value % 26);
    }

  else
    throw std::out_of_range (MOBIUS_EXCEPTION_MSG ("Segment index out of range"));
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Check if file is an instance of imagefile EWF
//! \param f File object
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
imagefile_impl::is_instance (mobius::io::file f)
{
  bool instance = false;

  if (f && f.exists ())
    {
      auto reader = f.new_reader ();

      if (reader)
        {
          segment_decoder decoder (reader);
          instance = bool (decoder);
        }
    }

  return instance;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Construct object
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
imagefile_impl::imagefile_impl (mobius::io::file f)
  : file_ (f),
    segments_ (f, get_segment_extension)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Create new reader for imagefile
//! \return Reader object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::io::reader
imagefile_impl::new_reader () const
{
  return mobius::io::reader (std::make_shared <reader_impl> (*this));
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Create new writer for imagefile
//! \return Writer object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::io::writer
imagefile_impl::new_writer () const
{
  return mobius::io::writer (std::make_shared <writer_impl> (*this));
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get metadata
//! \return imagefile metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::metadata
imagefile_impl::get_metadata () const
{
  return mobius::metadata
  {
    {
      "type",
      "type",
      "std::string",
      get_type ()
    },
    {
      "size",
      "size",
      "size_type",
      std::to_string (get_size ()) + " bytes"
    },
    {
      "sectors",
      "number of sectors",
      "size_type",
      std::to_string (get_sectors ())
    },
    {
      "sector_size",
      "sector size",
      "size_type",
      std::to_string (get_sector_size ()) + " bytes"
    },
    {
      "chunk_size",
      "chunk size",
      "std::uint32_t",
      std::to_string (get_chunk_size ()) + " bytes"
    },
    {
      "chunk_count",
      "chunk count",
      "std::uint64_t",
      std::to_string (get_chunk_count ())
    },
    {
      "compression_level",
      "compression level",
      "std::uint32_t",
      compression_level_to_string (get_compression_level ())
    },
    {
      "segments",
      "segments",
      "size_type",
      std::to_string (get_segments ())
    },
    {
      "segment_size",
      "segment_size",
      "size_type",
      std::to_string (get_segment_size ()) + " bytes"
    },
    {
      "drive_vendor",
      "drive vendor",
      "std::string",
      get_drive_vendor ()
    },
    {
      "drive_model",
      "drive model",
      "std::string",
      get_drive_model ()
    },
    {
      "drive_serial_number",
      "drive serial number",
      "std::string",
      get_drive_serial_number ()
    },
    {
      "acquisition_user",
      "acquisition user name",
      "std::string",
      get_acquisition_user ()
    },
    {
      "acquisition_time",
      "acquisition date/time",
      "mobius::datetime::datetime",
      to_string (get_acquisition_time ())
    },
    {
      "acquisition_tool",
      "acquisition tool",
      "std::string",
      get_acquisition_tool ()
    },
    {
      "acquisition_platform",
      "acquisition platform",
      "std::string",
      get_acquisition_platform ()
    },
    {
      "hash_md5",
      "MD5 hash",
      "std::string",
      get_hash_md5 ()
    },
  };
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Load metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
imagefile_impl::_load_metadata () const
{
  if (metadata_loaded_)
    return;

  // Scan segment files
  segments_.scan ();

  if (segments_.get_size () == 0)
    return;

  // Set metadata
  acquisition_user_ = file_.get_user_name ();
  acquisition_time_ = file_.get_modification_time ();

  // walk through segment files
  bool header_loaded = false;

  for (segment_array::idx_type i = 0;i < segments_.get_size ();i++)
    {
      segment_decoder decoder (segments_.new_reader (i));

      // walk through sections, retrieving imagefile metadata
      for (const auto& section : decoder)
        {
          if (section.get_name () == "hash")
            {
              auto hash_section = decoder.decode_hash_section (section);
              hash_md5_ = hash_section.get_md5_hash ();
            }

          else if (section.get_name () == "volume" || section.get_name () == "disk" || section.get_name () == "data")
            {
              auto volume_section = decoder.decode_volume_section (section);
              sectors_ = volume_section.get_sectors ();
              sector_size_ = volume_section.get_sector_size ();
              size_ = sectors_ * sector_size_;
              chunk_size_ = volume_section.get_chunk_sectors () * sector_size_;
              chunk_count_ = (size_ + chunk_size_ - 1) / chunk_size_;
              compression_level_ = volume_section.get_compression_level ();
            }

          else if (!header_loaded && (section.get_name () == "header2" || section.get_name () == "header"))
            {
              auto header_section = decoder.decode_header_section (section);
              drive_model_ = header_section.get_drive_model ();
              drive_serial_number_ = header_section.get_drive_serial_number ();
              acquisition_user_ = header_section.get_acquisition_user ();
              acquisition_time_ = header_section.get_acquisition_time ();
              acquisition_tool_ = header_section.get_acquisition_tool ();
              acquisition_platform_ = header_section.get_acquisition_platform ();
              header_loaded = true;
            }
        }
    }

  normalize_drive_info (drive_vendor_, drive_model_, drive_serial_number_);

  // if there is only one segment, segment_size equals to size
  if (segments_.get_size () == 1)
    segment_size_ = size_;

  // set metadata loaded
  metadata_loaded_ = true;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Load chunk offset table on demand
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
imagefile_impl::_load_chunk_offset_table () const
{
  if (chunk_offset_table_loaded_)
    return;

  // scan segment files
  if (segments_.get_size () == 0)
    return;

  // walk through segment files
  size_type next_offset = 0;

  for (segment_array::idx_type i = 0;i < segments_.get_size ();i++)
    {
      segment_decoder decoder (segments_.new_reader (i));

      chunk_offset_table offset_table;
      offset_table.start = next_offset;
      offset_table.end = 0;

      // walk through sections, retrieving imagefile metadata
      for (const auto& section : decoder)
        {
          if (section.get_name () == "table")
            {
              auto table_section = decoder.decode_table_section (section);
              size_type size = chunk_size_ * static_cast <size_type> (table_section.get_chunk_count ());

	      if (offset_table.end)
                offset_table.end += size;
              else
                offset_table.end = offset_table.start + size - 1;

              auto table_offset_list = table_section.get_chunk_offset_list ();

              offset_table.offsets.insert (
                offset_table.offsets.end (),
                std::make_move_iterator (table_offset_list.begin ()),
                std::make_move_iterator (table_offset_list.end ())
              );

              next_offset += size;
            }
        }

      chunk_offset_table_.push_back (offset_table);
    }

  // set chunk offset table loaded
  chunk_offset_table_loaded_ = true;
}

} // namespace ewf
} // namespace imagefile
} // namespace mobius
