// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 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 "imagefile_impl_solo.h"
#include <mobius/io/file.h>
#include <mobius/charset.h>
#include <mobius/exception.inc>
#include <mobius/string_functions.h>
#include <mobius/regex_impl.h>
#include <stdexcept>
#include <cctype>

namespace mobius
{
namespace imagefile
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get URL for first segment file
//! \param url imagefile URL
//! \return first segment file URL
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static const std::string
get_first_segment_url (const std::string& url)
{
  return url.substr (0, url.length () - 4) + ".001";
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check if URL is an instance of imagefile solo
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
imagefile_impl_solo::is_instance (const std::string& url)
{
  bool instance = false;

  mobius::io::file f (url);

  if (f && f.exists ())
    {
      auto reader = f.new_reader ();
      mobius::bytearray data = reader.read (14);

      instance = data == "[SEIZE HEADER]";
    }

  return instance;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief construct object
//! \param url imagefile URL
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
imagefile_impl_solo::imagefile_impl_solo (const std::string& url)
  : url_ (url),
    split_imagefile_ (get_first_segment_url (url))
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new reader for imagefile
//! \return reader
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::io::reader
imagefile_impl_solo::new_reader () const
{
  return split_imagefile_.new_reader ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new writer for imagefile
//! \return writer
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::io::writer
imagefile_impl_solo::new_writer () const
{
  throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("writer not implemented"));
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get metadata
//! \return imagefile metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::metadata
imagefile_impl_solo::get_metadata () const
{
  return mobius::metadata
  {
    {
      "url",
      "URL",
      "std::string",
      get_url ()
    },
    {
      "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"
    },
    {
      "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 on demand
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
imagefile_impl_solo::_load_metadata () const
{
  if (metadata_loaded_)
    return;

  // load metadata
  mobius::io::file f (url_);
  constexpr mobius::io::file::size_type LOG_MAX_SIZE = 1048576;  // 1 MiB

  if (f.exists () && f.get_size () < LOG_MAX_SIZE)
    {
      mobius::regex REGEX_SEIZE_TIME ("^Seize Time = *([0-9]+):([0-9]+):([0-9]+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_SEIZE_DATE ("^Seize Date = *([0-9]+)/([0-9]+)/([0-9]+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_TOTAL_SECTORS ("^Seize Size = ([0-9]+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_MD5 ("^MD5: (.+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_SUSPECT ("Suspect: Model: ([^.]+?)\\. Serial Number: ([^.]+?)\\..*Block size: ([0-9]+)\\..*\\. Evidence");
      mobius::regex REGEX_SOLO_PRODUCT ("^Product = (.+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_SOLO_SERIAL ("^Serial #: (.+)$", REG_NEWLINE | REG_EXTENDED);
      mobius::regex REGEX_SOLO_SOFTWARE ("^Software Version (.+)$", REG_NEWLINE | REG_EXTENDED);

      // parse .txt file
      auto reader = f.new_reader ();
      mobius::bytearray data = reader.read (f.get_size ());
      const std::string text = conv_charset_to_utf8 (data, "ASCII");

      if (REGEX_SUSPECT.match (text))
        {
          drive_model_ = REGEX_SUSPECT[1];
          drive_serial_number_ = REGEX_SUSPECT[2];
          sector_size_ = stoi (REGEX_SUSPECT[3]);
        }

      if (REGEX_SEIZE_DATE.match (text) && REGEX_SEIZE_TIME.match (text))
        {
          mobius::datetime::date date = mobius::datetime::date (
                                          stoi (REGEX_SEIZE_DATE[3]),
                                          stoi (REGEX_SEIZE_DATE[1]),
                                          stoi (REGEX_SEIZE_DATE[2]));

          mobius::datetime::time time = mobius::datetime::time (
                                          stoi (REGEX_SEIZE_TIME[1]),
                                          stoi (REGEX_SEIZE_TIME[2]),
                                          stoi (REGEX_SEIZE_TIME[3]));

          acquisition_time_ = mobius::datetime::datetime (date, time);
        }

      if (REGEX_TOTAL_SECTORS.match (text))
        {
          sectors_ = stoll (REGEX_TOTAL_SECTORS[1]);
          size_ = sectors_ * sector_size_;
        }

      if (REGEX_MD5.match (text))
        {
          std::string value = mobius::string::remove_char (REGEX_MD5[1], ' ');
          hash_md5_ = mobius::string::tolower (value);
        }

      if (REGEX_SOLO_PRODUCT.match (text) && REGEX_SOLO_SERIAL.match (text))
        acquisition_platform_ = REGEX_SOLO_PRODUCT[1] + " (s/n: " + REGEX_SOLO_SERIAL[1] + ')';

      if (REGEX_SOLO_SOFTWARE.match (text))
        acquisition_tool_ = "Solo software v" + REGEX_SOLO_SOFTWARE[1];

      segments_ = split_imagefile_.get_segments ();
      segment_size_ = split_imagefile_.get_segment_size ();
      acquisition_user_ = f.get_user_name ();

      normalize_drive_info (drive_vendor_, drive_model_, drive_serial_number_);
    }

  // set metadata loaded
  metadata_loaded_ = true;
}

} // namespace imagefile
} // namespace mobius
