// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016 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/imagefile_impl_ewf.h>
#include <mobius/imagefile/segment_array_base.h>
#include <mobius/io/file.h>
#include <mobius/data_decoder.h>
#include <mobius/datetime/conv_unix_timestamp.h>
#include <mobius/exception.inc>
#include <mobius/uri.h>
#include <mobius/charset.h>
#include <mobius/bytearray.h>
#include <mobius/string_functions.h>
#include <mobius/zlib_functions.h>
#include <stdexcept>
#include <string>
#include <vector>

namespace mobius
{
namespace imagefile
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief segment_array subclass
//! \author Eduardo Aguiar
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=        
class segment_array_ewf : public segment_array_base
{
public:
  explicit segment_array_ewf (const std::string&);
  segment_array_ewf (const segment_array_ewf&) = default;
  segment_array_ewf (segment_array_ewf&&) = default;
  segment_array_ewf& operator= (const segment_array_ewf&) = default;
  segment_array_ewf& operator= (segment_array_ewf&&) = default;
  
private:
  virtual const std::string get_next_extension (const std::string&) const override;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create segment_array_ewf
//! \param url URL
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
segment_array_ewf::segment_array_ewf (const std::string& url)
 : segment_array_base (url)
{
}
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief return next extension
//! \param extension
//! \return next extension
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const std::string
segment_array_ewf::get_next_extension (const std::string& extension) const
{
  std::string tmp_extension;

  if (extension == "E99")
    tmp_extension = "EAA";

  else
    {
      tmp_extension = extension;
      int pos = extension.length () - 1;
      bool carry = true;

      while (pos >= 0 && carry)
        {
          if (tmp_extension[pos] == '9')
            tmp_extension[pos] = '0';

          else if (tmp_extension[pos] == 'Z')
            tmp_extension[pos] = 'A';

          else
            {
              ++tmp_extension[pos];
              carry = false;
            }

          if (carry)
            --pos;
        }
    }

  return tmp_extension;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief <hash> section metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
struct hash_metadata
{
  std::string md5;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief <volume> and <disk> sections metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
struct volume_metadata
{
  std::uint32_t chunk_count;
  std::uint32_t chunk_sectors;
  std::uint32_t sector_size;
  std::uint32_t sectors;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief <table> section metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
struct table_metadata
{
  std::uint32_t chunk_count;
  std::uint64_t base_offset;
  std::vector <std::uint32_t> offset_table;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief <header> and <header2> sections metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
struct header_metadata
{
  std::string drive_model;
  std::string drive_serial_number;
  std::string acquisition_user;
  std::string acquisition_tool;
  std::string acquisition_platform;
  mobius::datetime::datetime acquisition_time;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief ewf section decoder class
//! \author Eduardo Aguiar
//! \see EWCF v0.0.80
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class section
{
public:
  using offset_type = mobius::io::reader::offset_type;
  using size_type = mobius::io::reader::size_type;

  section () = default;
  section (mobius::io::reader, offset_type);

  const hash_metadata get_hash_metadata ();
  const volume_metadata get_volume_metadata ();
  const table_metadata get_table_metadata ();
  const header_metadata get_header_metadata ();

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get section size
  //! \return size in bytes
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  size_type
  get_size () const
  {
    return size_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get offset from the beginning of data
  //! \return offset in bytes
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  offset_type
  get_offset () const
  {
    return offset_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get next section's offset
  //! \return offset in bytes
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  offset_type
  get_next_offset () const
  {
    return next_offset_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get section name
  //! \return section name
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const std::string
  get_name () const
  {
    return name_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get section CRC-32
  //! \return CRC-32 as 32 bit integer
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  std::uint32_t
  get_crc32 () const
  {
    return crc32_;
  }

private:
  mobius::io::reader reader_;
  offset_type offset_;
  size_type size_ = 0;
  std::string name_;
  offset_type next_offset_ = 0;
  std::uint32_t crc32_ = 0;
};

static constexpr int SECTION_HEADER_SIZE = 76;
static constexpr int FILE_HEADER_SIZE = 13;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief construct EWF section
//! \param reader reader object
//! \param offset offset from the beginning of data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
section::section (mobius::io::reader reader, offset_type offset)
 : reader_ (reader), offset_ (offset)
{
  mobius::data_decoder decoder (reader_);
  decoder.seek (offset_);

  name_ = decoder.get_string_by_size (16);
  next_offset_ = decoder.get_uint64_le ();
  size_ = decoder.get_uint64_le ();
  decoder.skip (40); // padding
  crc32_ = decoder.get_uint32_le ();        
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief decode <hash> section
//! \return metadata
//! \see EWCF 3.18
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const hash_metadata
section::get_hash_metadata ()
{
  mobius::data_decoder decoder (reader_);
  decoder.seek (offset_ + SECTION_HEADER_SIZE);

  hash_metadata section;
  section.md5 = decoder.get_bytearray_by_size (16).to_hexstring ();
  
  return section;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief decode <volume> and <disk> sections
//! \return metadata
//! \see EWCF 3.5
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const volume_metadata
section::get_volume_metadata ()
{
  mobius::data_decoder decoder (reader_);
  decoder.seek (offset_ + SECTION_HEADER_SIZE + 4);

  volume_metadata section;
  section.chunk_count = decoder.get_uint32_le ();
  section.chunk_sectors = decoder.get_uint32_le ();
  section.sector_size = decoder.get_uint32_le ();
  section.sectors = decoder.get_uint32_le ();
  
  return section;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief decode <table> section
//! \return metadata
//! \see EWCF 3.9
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const table_metadata
section::get_table_metadata ()
{
  mobius::data_decoder decoder (reader_);
  decoder.seek (offset_ + SECTION_HEADER_SIZE);

  table_metadata section;
  section.chunk_count = decoder.get_uint32_le ();
  decoder.skip (4);
  section.base_offset = decoder.get_uint64_le ();
  decoder.skip (8);

  for (std::uint32_t i = 0;i < section.chunk_count;i++)
    {
      auto offset = decoder.get_uint32_le ();
      section.offset_table.push_back (offset);
    }

  return section;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief decode <header> and <header2> sections
//! \return metadata
//! \see EWCF 3.4
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const header_metadata
section::get_header_metadata ()
{
  header_metadata section;

  // set reading pos
  mobius::data_decoder decoder (reader_);
  decoder.seek (offset_ + SECTION_HEADER_SIZE);

  // get data from header section  
  mobius::bytearray data = decoder.get_bytearray_by_size (size_ - SECTION_HEADER_SIZE);
  data = zlib_decompress (data);
  const std::string text = conv_charset_to_utf8 (data, name_ == "header2" ? "UTF-16" : "ASCII");

  // format header metadata lines
  auto lines = string_split (text, "\n");
  for (auto& line : lines)
    line = string_rstrip (line, "\r ");

  // check valid header
  if (lines[1] == "main" && lines.size () > 3)
    {
      auto vars = string_split (lines[2], "\t");
      auto values = string_split (lines[3], "\t");

      for (std::size_t i = 0;i < vars.size ();i++)
        {
          auto var = vars[i];
          auto value = values[i];

          if (var == "ov")
            section.acquisition_platform = value;
          
          else if (var == "e")
            section.acquisition_user = value;

          else if (var == "md")
            section.drive_model = value;

          else if (var == "sn")
            section.drive_serial_number = value;

          else if (var == "av")
            {
              if (value.length () > 0 && isdigit (value[0]))
                section.acquisition_tool = "Encase v" + value;
              else
                section.acquisition_tool = value;
            }

          else if (var == "m")
            {
              if (value.find (' ') != std::string::npos)
                {
                  auto d = string_split (value);
                  section.acquisition_time = mobius::datetime::datetime (
                        stoi (d[0]), stoi (d[1]), stoi (d[2]), stoi (d[3]), stoi (d[4]), stoi (d[5]));
                }
              else
                section.acquisition_time = mobius::datetime::datetime_from_unix_timestamp (stol (value));
            }
        }
    }
  else
    for (auto line : lines)
        printf ("INVALID HEADER<%s>\n", line.c_str ());

  return section;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief ewf decoder class
//! \author Eduardo Aguiar
//! \see https://github.com/libyal/libewf/blob/master/documentation/Expert%20
//       Witness%20Compression%20Format%20(EWF).asciidoc
//       (version 0.0.80, visited in 2016-08-25)
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class decoder
{
public:
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief iterator for EWF sections
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  class const_iterator
  {
  public:
    using offset_type = mobius::io::reader::offset_type;

    const_iterator () = default;          
    explicit const_iterator (mobius::io::reader);
    const_iterator& operator++ ();
    
    // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    //! \brief check if two iterators are different
    //! \param i iterator
    //! \return true/false
    // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    bool
    operator!= (const_iterator& i)
    {
      return is_null_ != i.is_null_;
    }

    // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    //! \brief return current section
    //! \return section object
    // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    const section&
    operator* ()
    {
      return section_;
    }

  private:
    bool is_null_ = true;
    mobius::io::reader reader_;
    section section_;
    offset_type offset_ = FILE_HEADER_SIZE;
  };

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief check whether EWF is valid
  //! \return true/false
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  operator bool () const
  {
    return !is_null_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // prototypes
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  decoder (mobius::io::reader reader);
  const_iterator begin () const;
  const_iterator end () const;

private:
  mobius::io::reader reader_;
  bool is_null_ = true;
  std::uint16_t segment_ = 0;
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief construct iterator
//! \param reader reader object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
decoder::const_iterator::const_iterator (mobius::io::reader reader)
 : is_null_ (false),
   reader_ (reader),
   section_ (reader, FILE_HEADER_SIZE)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief operator++
//! \return reference to object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
decoder::const_iterator&
decoder::const_iterator::operator++ ()
{
  auto next_offset = section_.get_next_offset ();
  
  if (offset_ == next_offset)
    is_null_ = true;
  
  else
    {
      offset_ = next_offset;
      section_ = section (reader_, offset_);
    }

  return *this;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \see EWCF 2.1.1
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
decoder::decoder (mobius::io::reader reader)
 : reader_ (reader)
{
  const mobius::bytearray EWF_SIGNATURE = {'E', 'V', 'F', 0x09, 0x0d, 0x0a, 0xff, 0x00};

  mobius::data_decoder decoder (reader);
  mobius::bytearray signature = decoder.get_bytearray_by_size (EWF_SIGNATURE.size ());

  if (signature == EWF_SIGNATURE)
    {
      decoder.skip (1);
      segment_ = decoder.get_uint32_le ();
      is_null_ = false;
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get first section
//! \return section
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
decoder::const_iterator
decoder::begin () const
{
  return const_iterator (reader_);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get ending section
//! \return section
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
decoder::const_iterator
decoder::end () const
{
  return const_iterator ();
}

} // internal linkage namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief ewf imagefile reader implementation class
//! \author Eduardo Aguiar
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class reader_impl_ewf : public mobius::io::reader_impl_base
{
public:
  reader_impl_ewf (const std::string&, size_type);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief check if reader is seekable
  //! \return true/false
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual bool
  is_seekable () const override
  {
    return true;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief check if <b>reader.get_size</b> is available
  //! \return true/false
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual bool
  is_sizeable () const override
  {
    return true;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get data size
  //! \return data size in bytes
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual size_type
  get_size () const override
  {
    return size_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get read position
  //! \return read position in bytes from the beginning of data
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual offset_type
  tell () const override
  {
    return pos_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  //! \brief get read position
  //! \return read position in bytes from the beginning of data
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual bool
  eof () const override
  {
    return pos_ >= size_;
  }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // virtual methods
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  virtual const mobius::bytearray read (size_type) override;
  virtual void seek (offset_type, whence_type = whence_type::beginning) override;

private:
  struct segment_info
  {
    size_type start_offset;
    size_type end_offset;
    size_type segment_idx;
    size_type base_offset;
    std::vector <std::uint32_t> offset_table;
  };

  size_type size_;
  size_type pos_ = 0;
  size_type chunk_size_ = 0;

  segment_array_ewf segments_;
  std::vector <segment_info> segment_info_;

  size_type segment_idx_;        //!< current segment index
  size_type chunk_idx_;          //!< current chunk index
  mobius::io::reader stream_;
  mobius::bytearray chunk_data_;

  void _retrieve_current_chunk ();
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \param url URL
//! \param size imagefile size
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
reader_impl_ewf::reader_impl_ewf (const std::string& url, size_type size)
  : size_ (size), segments_ (url)
{
  segments_.scan ();
  size_type next_offset = 0;
  size_type chunk_count = 0;

  // walk through segment files
  for (auto segment : segments_)
    {
      decoder decoder (segment.new_reader ());
  
      // walk through sections, retrieving imagefile metadata
      for (auto section : decoder)
        {
          if (section.get_name () == "volume" || section.get_name () == "disk")
            {
              auto metadata = section.get_volume_metadata ();                
              chunk_size_ = metadata.chunk_sectors * metadata.sector_size;
            }
        
          else if (section.get_name () == "table")
            {
              auto metadata = section.get_table_metadata ();
              chunk_count += metadata.chunk_count;
              size_type size = metadata.chunk_count * chunk_size_;

              segment_info info;
              info.segment_idx = segment.get_idx ();
              info.start_offset = next_offset;
              info.end_offset = info.start_offset + size - 1;
              info.base_offset = metadata.base_offset;
              info.offset_table = metadata.offset_table;
              segment_info_.push_back (info);
              
              next_offset += size;
            }
        }
    }

  segment_idx_ = segments_.get_size ();
  chunk_idx_ = chunk_count;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief set read position
//! \param offset offset in bytes
//! \param w either beginning, current or end
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
reader_impl_ewf::seek (offset_type offset, whence_type w)
{
  // calculate offset from the beginning of data
  offset_type abs_offset;

  if (w == whence_type::beginning)
    abs_offset = offset;

  else if (w == whence_type::current)
    abs_offset = pos_ + offset;

  else if (w == whence_type::end)
    abs_offset = size_ - 1 + offset;

  else
    throw std::invalid_argument (MOBIUS_EXCEPTION_MSG ("invalid whence_type"));

  // update current pos, if possible
  if (abs_offset >= 0 && size_type (abs_offset) < size_)
    pos_ = abs_offset;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief read bytes from reader
//! \param size size in bytes
//! \return bytearray containing data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const mobius::bytearray
reader_impl_ewf::read (size_type size)
{
  size = std::min (size_ - pos_, size);
  mobius::bytearray data;
 
  while (size > 0)
    {
      _retrieve_current_chunk ();

      size_type slice_start = pos_ % chunk_size_;
      size_type slice_end = std::min (slice_start + size - 1, chunk_data_.size () - 1);
      mobius::bytearray tmp = chunk_data_.slice (slice_start, slice_end);
      data += tmp;
      pos_ += tmp.size ();
      size -= tmp.size ();
    }

  return data;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief retrieve current data chunk
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
reader_impl_ewf::_retrieve_current_chunk ()
{
  size_type chunk_idx = pos_ / chunk_size_;
  
  // if chunk is loaded, return
  if (chunk_idx == chunk_idx_)
    return;
  
  // find segment info for pos_ offset (binary search)
  size_type hi = segment_info_.size ();
  size_type lo = 0;
  size_type mid;
  bool found = false;
  
  while (lo < hi && !found)
    {
      mid = (lo + hi) / 2;
      
      if (segment_info_[mid].start_offset > pos_)
        hi = mid;
      
      else if (segment_info_[mid].end_offset < pos_)
        lo = mid;
      
      else
        found = true;
    }

  // if segment info not found, return
  if (!found)
    return;

  // set stream
  auto segment_idx = segment_info_[mid].segment_idx;

  if (segment_idx != segment_idx_)
    {
      stream_ = segments_[segment_idx].new_reader ();
      segment_idx_ = segment_idx;
    }

  // get chunk data offset
  const auto& info = segment_info_[mid];
  size_type table_idx = (pos_ - info.start_offset) / chunk_size_;
  auto offset = info.offset_table[table_idx];
  bool compressed = offset & 0x80000000;
  offset = offset & 0x7fffffff;
      
  // get data size
  size_type data_size;
     
  if (table_idx + 1 < info.offset_table.size ())
    data_size = (info.offset_table[table_idx+1] & 0x7fffffff) - offset;
  else
    data_size = info.end_offset - offset;

  // read chunk data
  stream_.seek (info.base_offset + offset);

  if (compressed)
    chunk_data_ = zlib_decompress (stream_.read (data_size));

  else
    {
      chunk_data_ = stream_.read (data_size - 4);
      stream_.skip (4);         // skip CRC of uncompressed data
    }

  // set new current chunk index
  chunk_idx_ = chunk_idx;      
}

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

  mobius::io::file f (url);
  
  if (f && f.exists ())
    {
      auto reader = f.new_reader ();
      decoder decoder (reader);
      instance = bool (decoder);
    }

  return instance;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief construct object
//! \param url imagefile URL
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
imagefile_impl_ewf::imagefile_impl_ewf (const std::string& url)
  : imagefile_impl_base (url)
{
  // scan segment files        
  segment_array_ewf segments (url);
  segments.scan ();

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

  // fill metadata
  const auto& first_segment = segments[0];
  set_acquisition_user (first_segment.get_user_name ());
  set_acquisition_time (first_segment.get_last_modification_time ());
  set_segments (segments.get_size ());
  
  // walk through segment files
  bool header_loaded = false;

  for (auto segment : segments)
    {
      decoder decoder (segment.new_reader ());
  
      // walk through sections, retrieving imagefile metadata
      for (auto section : decoder)
        {
          if (section.get_name () == "hash")
            {
              auto metadata = section.get_hash_metadata ();
              set_hash_md5 (metadata.md5);
            }
      
          else if (section.get_name () == "volume" || section.get_name () == "disk")
            {
              auto metadata = section.get_volume_metadata ();                
              set_sectors (metadata.sectors);
              set_sector_size (metadata.sector_size);
              set_size (get_sectors () * get_sector_size ());
            }
        
          else if (!header_loaded && (section.get_name () == "header2" || section.get_name () == "header"))
            {
              auto metadata = section.get_header_metadata ();  
              set_drive_model (metadata.drive_model);
              set_drive_serial_number (metadata.drive_serial_number);

              if (!metadata.acquisition_user.empty ())
                set_acquisition_user (metadata.acquisition_user);
              
              if (metadata.acquisition_time)
                set_acquisition_time (metadata.acquisition_time);

              if (!metadata.acquisition_tool.empty ())
                set_acquisition_tool (metadata.acquisition_tool);

              set_acquisition_platform (metadata.acquisition_platform);
              header_loaded = true;
            }
        }
    }

  // if there is only one segment, segment_size equals to size
  if (get_segments () == 1)
    set_segment_size (get_size ());
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new reader for imagefile
//! \return reader
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const mobius::io::reader
imagefile_impl_ewf::new_reader () const
{
  return mobius::io::reader (
           std::make_shared <reader_impl_ewf> (
             get_url (),
             get_size ())
         );
}

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

} // namespace imagefile
} // namespace mobius
