// QcowHandler.cpp

#include "StdAfx.h"

// #include <stdio.h>

#include "../../../C/CpuArch.h"

#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"
#include "../../Common/MyBuffer2.h"

#include "../../Windows/PropVariant.h"
#include "../../Windows/PropVariantUtils.h"

#include "../Common/RegisterArc.h"
#include "../Common/StreamObjects.h"
#include "../Common/StreamUtils.h"

#include "../Compress/DeflateDecoder.h"

#include "HandlerCont.h"

#define Get32(p) GetBe32a(p)
#define Get64(p) GetBe64a(p)

using namespace NWindows;

namespace NArchive {
namespace NQcow {

static const Byte k_Signature[] =  { 'Q', 'F', 'I', 0xFB, 0, 0, 0 };

/*
VA to PA maps:
  high bits (L1) :              : index in L1 (_dir) : _dir[high_index] points to Table.
  mid bits  (L2) : _numMidBits  : index in Table, Table[index] points to cluster start offset in arc file.
  low bits       : _clusterBits : offset inside cluster.
*/

Z7_class_CHandler_final: public CHandlerImg
{
  Z7_IFACE_COM7_IMP(IInArchive_Img)
  Z7_IFACE_COM7_IMP(IInArchiveGetStream)
  Z7_IFACE_COM7_IMP(ISequentialInStream)

  unsigned _clusterBits;
  unsigned _numMidBits;
  UInt64 _compressedFlag;

  CObjArray2<UInt32> _dir;
  CAlignedBuffer _table;
  CByteBuffer _cache;
  CByteBuffer _cacheCompressed;
  UInt64 _cacheCluster;

  UInt64 _comprPos;
  size_t _comprSize;

  bool _needCompression;
  bool _isArc;
  bool _unsupported;
  Byte _compressionType;

  UInt64 _phySize;

  CMyComPtr2<ISequentialInStream, CBufInStream> _bufInStream;
  CMyComPtr2<ISequentialOutStream, CBufPtrSeqOutStream> _bufOutStream;
  CMyComPtr2<ICompressCoder, NCompress::NDeflate::NDecoder::CCOMCoder> _deflateDecoder;

  UInt32 _version;
  UInt32 _cryptMethod;
  UInt64 _incompatFlags;
  
  HRESULT Seek2(UInt64 offset)
  {
    _posInArc = offset;
    return InStream_SeekSet(Stream, offset);
  }

  HRESULT InitAndSeek()
  {
    _virtPos = 0;
    return Seek2(0);
  }

  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override;
};


static const UInt32 kEmptyDirItem = (UInt32)0 - 1;

Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize))
{
  if (processedSize)
    *processedSize = 0;
  // printf("\nRead _virtPos = %6d  size = %6d\n", (UInt32)_virtPos, size);
  if (_virtPos >= _size)
    return S_OK;
  {
    const UInt64 rem = _size - _virtPos;
    if (size > rem)
      size = (UInt32)rem;
    if (size == 0)
      return S_OK;
  }
 
  for (;;)
  {
    const UInt64 cluster = _virtPos >> _clusterBits;
    const size_t clusterSize = (size_t)1 << _clusterBits;
    const size_t lowBits = (size_t)_virtPos & (clusterSize - 1);
    {
      const size_t rem = clusterSize - lowBits;
      if (size > rem)
        size = (UInt32)rem;
    }
    if (cluster == _cacheCluster)
    {
      memcpy(data, _cache + lowBits, size);
      break;
    }
   
    const UInt64 high = cluster >> _numMidBits;
 
    if (high < _dir.Size())
    {
      const UInt32 tabl = _dir[(size_t)high];
      if (tabl != kEmptyDirItem)
      {
        const size_t midBits = (size_t)cluster & (((size_t)1 << _numMidBits) - 1);
        const Byte *p = _table + ((((size_t)tabl << _numMidBits) + midBits) << 3);
        UInt64 v = Get64(p);
        
        if (v)
        {
          if (v & _compressedFlag)
          {
            if (_version <= 1)
              return E_FAIL;
            /*
            the example of table record for 12-bit clusters (4KB uncompressed):
              2 bits : isCompressed status
              (4 == _clusterBits - 8) bits : (num_sectors - 1)
                  packSize = num_sectors * 512;
                  it uses one additional bit over unpacked cluster_bits.
              (49 == 61 - _clusterBits) bits : offset of 512-byte sector
              9 bits : offset in 512-byte sector
            */
            const unsigned numOffsetBits = 62 - (_clusterBits - 8);
            const UInt64 offset = v & (((UInt64)1 << 62) - 1);
            const size_t dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9;
            UInt64 sectorOffset = offset & (((UInt64)1 << numOffsetBits) - (1 << 9));
            const UInt64 offset2inCache = sectorOffset - _comprPos;
            
            // _comprPos is aligned for 512-bytes
            // we try to use previous _cacheCompressed that contains compressed data
            // that was read for previous unpacking

            if (sectorOffset >= _comprPos && offset2inCache < _comprSize)
            {
              if (offset2inCache)
              {
                _comprSize -= (size_t)offset2inCache;
                memmove(_cacheCompressed, _cacheCompressed + (size_t)offset2inCache, _comprSize);
                _comprPos = sectorOffset;
              }
              sectorOffset += _comprSize;
            }
            else
            {
              _comprPos = sectorOffset;
              _comprSize = 0;
            }
            
            if (dataSize > _comprSize)
            {
              if (sectorOffset != _posInArc)
              {
                // printf("\nDeflate-Seek %12I64x %12I64x\n", sectorOffset, sectorOffset - _posInArc);
                RINOK(Seek2(sectorOffset))
              }
              if (_cacheCompressed.Size() < dataSize)
                return E_FAIL;
              const size_t dataSize3 = dataSize - _comprSize;
              size_t dataSize2 = dataSize3;
              // printf("\n\n=======\nReadStream = %6d _comprPos = %6d \n", (UInt32)dataSize2, (UInt32)_comprPos);
              const HRESULT hres = ReadStream(Stream, _cacheCompressed + _comprSize, &dataSize2);
              _posInArc += dataSize2;
              RINOK(hres)
              if (dataSize2 != dataSize3)
                return E_FAIL;
              _comprSize += dataSize2;
            }
            
            const size_t kSectorMask = (1 << 9) - 1;
            const size_t offsetInSector = (size_t)offset & kSectorMask;
            _bufInStream->Init(_cacheCompressed + offsetInSector, dataSize - offsetInSector);
            _cacheCluster = (UInt64)(Int64)-1;
            if (_cache.Size() < clusterSize)
              return E_FAIL;
            _bufOutStream->Init(_cache, clusterSize);
            // Do we need to use smaller block than clusterSize for last cluster?
            const UInt64 blockSize64 = clusterSize;
            HRESULT res = _deflateDecoder.Interface()->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL);
            /*
            if (_bufOutStreamSpec->GetPos() != clusterSize)
              memset(_cache + _bufOutStreamSpec->GetPos(), 0, clusterSize - _bufOutStreamSpec->GetPos());
            */
            if (res == S_OK)
              if (!_deflateDecoder->IsFinished()
                  || _bufOutStream->GetPos() != clusterSize)
                res = S_FALSE;
            RINOK(res)
            _cacheCluster = cluster;
            continue;
            /*
            memcpy(data, _cache + lowBits, size);
            break;
            */
          }

          // version_3 supports zero clusters
          if (((UInt32)v & 511) != 1)
          {
            v &= _compressedFlag - 1;
            v += lowBits;
            if (v != _posInArc)
            {
              // printf("\n%12I64x\n", v - _posInArc);
              RINOK(Seek2(v))
            }
            const HRESULT res = Stream->Read(data, size, &size);
            _posInArc += size;
            _virtPos += size;
            if (processedSize)
              *processedSize = size;
            return res;
          }
        }
      }
    }
    
    memset(data, 0, size);
    break;
  }

  _virtPos += size;
  if (processedSize)
    *processedSize = size;
  return S_OK;
}


static const Byte kProps[] =
{
  kpidSize,
  kpidPackSize
};

static const Byte kArcProps[] =
{
  kpidClusterSize,
  kpidSectorSize, // actually we need variable to show table size
  kpidHeadersSize,
  kpidUnpackVer,
  kpidMethod,
  kpidCharacts
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps

static const CUInt32PCharPair g_IncompatFlags_Characts[] =
{
  {  0, "Dirty" },
  {  1, "Corrupt" },
  {  2, "External_Data_File" },
  {  3, "Compression" },
  {  4, "Extended_L2" }
};

Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;

  switch (propID)
  {
    case kpidMainSubfile: prop = (UInt32)0; break;
    case kpidClusterSize: prop = (UInt32)1 << _clusterBits; break;
    case kpidSectorSize: prop = (UInt32)1 << (_numMidBits + 3); break;
    case kpidHeadersSize: prop = _table.Size() + (UInt64)_dir.Size() * 8; break;
    case kpidPhySize: if (_phySize) prop = _phySize; break;
    case kpidUnpackVer: prop = _version; break;
    case kpidCharacts:
    {
      if (_incompatFlags)
      {
        AString s ("incompatible: ");
        // we need to show also high 32-bits.
        s += FlagsToString(g_IncompatFlags_Characts,
            Z7_ARRAY_SIZE(g_IncompatFlags_Characts), (UInt32)_incompatFlags);
        prop = s;
      }
      break;
    }
    case kpidMethod:
    {
      AString s;

      if (_compressionType)
      {
        if (_compressionType == 1)
          s += "ZSTD";
        else
        {
          s += "Compression:";
          s.Add_UInt32(_compressionType);
        }
      }
      else if (_needCompression)
        s.Add_OptSpaced("Deflate");

      if (_cryptMethod)
      {
        s.Add_Space_if_NotEmpty();
        if (_cryptMethod == 1)
          s += "AES";
        if (_cryptMethod == 2)
          s += "LUKS";
        else
        {
          s += "Encryption:";
          s.Add_UInt32(_cryptMethod);
        }
      }
      if (!s.IsEmpty())
        prop = s;
      break;
    }

    case kpidErrorFlags:
    {
      UInt32 v = 0;
      if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
      if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod;
      // if (_headerError) v |= kpv_ErrorFlags_HeadersError;
      if (!Stream && v == 0)
        v = kpv_ErrorFlags_HeadersError;
      if (v)
        prop = v;
      break;
    }
  }
  
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}


Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value))
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;

  switch (propID)
  {
    case kpidSize: prop = _size; break;
    case kpidPackSize: prop = _phySize; break;
    case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
  }
  
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}


HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback)
{
  UInt64 buf64[0x70 / 8];
  RINOK(ReadStream_FALSE(stream, buf64, sizeof(buf64)))
  const void *buf = (const void *)buf64;
  // signature: { 'Q', 'F', 'I', 0xFB }
  if (*(const UInt32 *)buf != Z7_CONV_BE_TO_NATIVE_CONST32(0x514649fb))
    return S_FALSE;
  _version = Get32((const Byte *)(const void *)buf64 + 4);
  if (_version < 1 || _version > 3)
    return S_FALSE;
  
  const UInt64 k_UncompressedSize_MAX = (UInt64)1 << 60;
  const UInt64 k_CompressedSize_MAX   = (UInt64)1 << 60;

  _size = Get64((const Byte *)(const void *)buf64 + 0x18);
  if (_size > k_UncompressedSize_MAX)
    return S_FALSE;
  size_t l1Size;
  UInt32 headerSize;

  if (_version == 1)
  {
    // _mTime = Get32((const Byte *)(const void *)buf64 + 0x14); // is unused in most images
    _clusterBits = ((const Byte *)(const void *)buf64)[0x20];
    _numMidBits  = ((const Byte *)(const void *)buf64)[0x21];
    if (_clusterBits < 9 || _clusterBits > 30)
      return S_FALSE;
    if (_numMidBits < 1 || _numMidBits > 28)
      return S_FALSE;
    _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x24);
    const unsigned numBits2 = _clusterBits + _numMidBits;
    const UInt64 l1Size64 = (_size + (((UInt64)1 << numBits2) - 1)) >> numBits2;
    if (l1Size64 > ((UInt32)1 << 31))
      return S_FALSE;
    l1Size = (size_t)l1Size64;
    headerSize = 0x30;
  }
  else
  {
    _clusterBits = Get32((const Byte *)(const void *)buf64 + 0x14);
    if (_clusterBits < 9 || _clusterBits > 30)
      return S_FALSE;
    _numMidBits = _clusterBits - 3;
    _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x20);
    l1Size = Get32((const Byte *)(const void *)buf64 + 0x24);
    headerSize = 0x48;
    if (_version >= 3)
    {
      _incompatFlags = Get64((const Byte *)(const void *)buf64 + 0x48);
      // const UInt64 CompatFlags    = Get64((const Byte *)(const void *)buf64 + 0x50);
      // const UInt64 AutoClearFlags = Get64((const Byte *)(const void *)buf64 + 0x58);
      // const UInt32 RefCountOrder = Get32((const Byte *)(const void *)buf64 + 0x60);
      headerSize = 0x68;
      const UInt32 headerSize2  = Get32((const Byte *)(const void *)buf64 + 0x64);
      if (headerSize2 > (1u << 30))
        return S_FALSE;
      if (headerSize < headerSize2)
          headerSize = headerSize2;
      if (headerSize2 >= 0x68 + 1)
        _compressionType = ((const Byte *)(const void *)buf64)[0x68];
    }

    const UInt64 refOffset = Get64((const Byte *)(const void *)buf64 + 0x30); // must be aligned for cluster
    const UInt32 refClusters = Get32((const Byte *)(const void *)buf64 + 0x38);
    // UInt32 numSnapshots = Get32((const Byte *)(const void *)buf64 + 0x3C);
    // UInt64 snapshotsOffset = Get64((const Byte *)(const void *)buf64 + 0x40); // must be aligned for cluster
    /*
    if (numSnapshots)
      return S_FALSE;
    */
    if (refClusters)
    {
      if (refOffset > k_CompressedSize_MAX)
        return S_FALSE;
      const UInt64 numBytes = (UInt64)refClusters << _clusterBits;
      const UInt64 end = refOffset + numBytes;
      if (end > k_CompressedSize_MAX)
        return S_FALSE;
      /*
      CByteBuffer refs;
      refs.Alloc(numBytes);
      RINOK(InStream_SeekSet(stream, refOffset))
      RINOK(ReadStream_FALSE(stream, refs, numBytes));
      */
      if (_phySize < end)
          _phySize = end;
      /*
      for (size_t i = 0; i < numBytes; i += 2)
      {
        UInt32 v = GetBe16((const Byte *)refs + (size_t)i);
        if (v == 0)
          continue;
      }
      */
    }
  }

  const UInt64 l1Offset = Get64((const Byte *)(const void *)buf64 + 0x28); // must be aligned for cluster ?
  if (l1Offset < headerSize || l1Offset > k_CompressedSize_MAX)
    return S_FALSE;
  if (_phySize < headerSize)
      _phySize = headerSize;

  // we use 32 MiB limit for L1 size, as QEMU with QCOW_MAX_L1_SIZE limit.
  if (l1Size > (1u << 22)) // if (l1Size > (1u << (sizeof(size_t) * 8 - 4)))
    return S_FALSE;

  _isArc = true;
  {
    const UInt64 backOffset = Get64((const Byte *)(const void *)buf64 + 8);
    // UInt32 backSize = Get32((const Byte *)(const void *)buf64 + 0x10);
    if (backOffset)
    {
      _unsupported = true;
      return S_FALSE;
    }
  }

  UInt64 fileSize = 0;
  RINOK(InStream_GetSize_SeekToBegin(stream, fileSize))

  const size_t clusterSize = (size_t)1 << _clusterBits;
  const size_t t1SizeBytes = (size_t)l1Size << 3;
  {
    const UInt64 end = l1Offset + t1SizeBytes;
    if (end > k_CompressedSize_MAX)
      return S_FALSE;
    // we need to use align end for empty qcow files
    // some files has no cluster alignment padding at the end
    // but has sector alignment
    // end = (end + clusterSize - 1) >> _clusterBits << _clusterBits;
    if (_phySize < end)
        _phySize = end;
    if (end > fileSize)
      return S_FALSE;
    if (_phySize < fileSize)
    {
      const UInt64 end2 = (end + 511) & ~(UInt64)511;
      if (end2 == fileSize)
        _phySize = end2;
    }
  }
  CObjArray<UInt64> table64(l1Size);
  {
    RINOK(InStream_SeekSet(stream, l1Offset))
    RINOK(ReadStream_FALSE(stream, table64, t1SizeBytes))
  }

  _compressedFlag = (_version <= 1) ? ((UInt64)1 << 63) : ((UInt64)1 << 62);
  const UInt64 offsetMask = _compressedFlag - 1;
  const size_t midSize = (size_t)1 << (_numMidBits + 3);
  size_t numTables = 0;
  size_t i;

  for (i = 0; i < l1Size; i++)
  {
    const UInt64 v = Get64(table64 + (size_t)i) & offsetMask;
    if (!v)
      continue;
    numTables++;
    const UInt64 end = v + midSize;
    if (end > k_CompressedSize_MAX)
      return S_FALSE;
    if (_phySize < end)
        _phySize = end;
    if (end > fileSize)
      return S_FALSE;
  }

  if (numTables)
  {
    const size_t size = (size_t)numTables << (_numMidBits + 3);
    if (size >> (_numMidBits + 3) != numTables)
      return E_OUTOFMEMORY;
    _table.Alloc(size);
    if (!_table.IsAllocated())
      return E_OUTOFMEMORY;
    if (openCallback)
    {
      const UInt64 totalBytes = size;
      RINOK(openCallback->SetTotal(NULL, &totalBytes))
    }
  }

  _dir.SetSize((unsigned)l1Size);

  UInt32 curTable = 0;

  for (i = 0; i < l1Size; i++)
  {
    Byte *buf2;
    {
      const UInt64 v = Get64(table64 + (size_t)i) & offsetMask;
      if (v == 0)
      {
        _dir[i] = kEmptyDirItem;
        continue;
      }
      _dir[i] = curTable;
      const size_t tableOffset = (size_t)curTable << (_numMidBits + 3);
      buf2 = (Byte *)_table + tableOffset;
      curTable++;
      if (openCallback && (tableOffset & 0xFFFFF) == 0)
      {
        const UInt64 numBytes = tableOffset;
        RINOK(openCallback->SetCompleted(NULL, &numBytes))
      }
      RINOK(InStream_SeekSet(stream, v))
      RINOK(ReadStream_FALSE(stream, buf2, midSize))
    }

    for (size_t k = 0; k < midSize; k += 8)
    {
      const UInt64 v = Get64((const Byte *)buf2 + (size_t)k);
      if (v == 0)
        continue;
      UInt64 offset = v & offsetMask;
      size_t dataSize = clusterSize;
      
      if (v & _compressedFlag)
      {
        if (_version <= 1)
        {
          const unsigned numOffsetBits = 63 - _clusterBits;
          dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9;
          offset &= ((UInt64)1 << numOffsetBits) - 1;
          dataSize = 0; // why ?
          // offset &= ~(((UInt64)1 << 9) - 1);
        }
        else
        {
          const unsigned numOffsetBits = 62 - (_clusterBits - 8);
          dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9;
          offset &= ((UInt64)1 << numOffsetBits) - (1 << 9);
        }
        _needCompression = true;
      }
      else
      {
        const UInt32 low = (UInt32)v & 511;
        if (low)
        {
          // version_3 supports zero clusters
          if (_version < 3 || low != 1)
          {
            _unsupported = true;
            return S_FALSE;
          }
        }
      }
      
      const UInt64 end = offset + dataSize;
      if (_phySize < end)
          _phySize = end;
    }
  }

  if (curTable != numTables)
    return E_FAIL;

  if (_cryptMethod)
    _unsupported = true;
  if (_needCompression && _version <= 1) // that case was not implemented
    _unsupported = true;
  if (_compressionType)
    _unsupported = true;

  Stream = stream;
  return S_OK;
}


Z7_COM7F_IMF(CHandler::Close())
{
  _table.Free();
  _dir.Free();
  // _cache.Free();
  // _cacheCompressed.Free();
  _phySize = 0;

  _cacheCluster = (UInt64)(Int64)-1;
  _comprPos = 0;
  _comprSize = 0;

  _needCompression = false;
  _isArc = false;
  _unsupported = false;

  _compressionType = 0;
  _incompatFlags = 0;

  // CHandlerImg:
  Clear_HandlerImg_Vars();
  Stream.Release();
  return S_OK;
}


Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream))
{
  COM_TRY_BEGIN
  *stream = NULL;
  if (_unsupported || !Stream)
    return S_FALSE;
  if (_needCompression)
  {
    if (_version <= 1 || _compressionType)
      return S_FALSE;
    _bufInStream.Create_if_Empty();
    _bufOutStream.Create_if_Empty();
    _deflateDecoder.Create_if_Empty();
    _deflateDecoder->Set_NeedFinishInput(true);
    const size_t clusterSize = (size_t)1 << _clusterBits;
    _cache.AllocAtLeast(clusterSize);
    _cacheCompressed.AllocAtLeast(clusterSize * 2);
  }
  CMyComPtr<ISequentialInStream> streamTemp = this;
  RINOK(InitAndSeek())
  *stream = streamTemp.Detach();
  return S_OK;
  COM_TRY_END
}


REGISTER_ARC_I(
  "QCOW", "qcow qcow2 qcow2c", NULL, 0xCA,
  k_Signature,
  0,
  0,
  NULL)

}}
