From 6e2c7d28573549d243b860625bf1c2665c080f0b Mon Sep 17 00:00:00 2001 From: frabar666 Date: Mon, 26 Feb 2018 23:49:09 +0100 Subject: [PATCH] support Deflate64 decompression --- FORMATS.md | 5 +- src/SharpCompress/Common/CompressionType.cs | 3 +- src/SharpCompress/Common/Zip/ZipEntry.cs | 4 + src/SharpCompress/Common/Zip/ZipFilePart.cs | 5 + .../Compressors/Deflate64/BlockType.cs | 13 + .../Compressors/Deflate64/Deflate64Stream.cs | 257 ++++++ .../Compressors/Deflate64/DeflateInput.cs | 43 + .../Deflate64/FastEncoderStatus.cs | 245 ++++++ .../Compressors/Deflate64/HuffmanTree.cs | 311 ++++++++ .../Compressors/Deflate64/InflaterManaged.cs | 738 ++++++++++++++++++ .../Compressors/Deflate64/InflaterState.cs | 42 + .../Compressors/Deflate64/InputBuffer.cs | 202 +++++ .../Compressors/Deflate64/Match.cs | 17 + .../Compressors/Deflate64/MatchState.cs | 13 + .../Compressors/Deflate64/OutputWindow.cs | 151 ++++ tests/SharpCompress.Test/TestBase.cs | 1 + .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 10 + .../SharpCompress.Test/Zip/ZipReaderTests.cs | 5 + tests/TestArchives/Archives/Zip.deflate64.zip | Bin 0 -> 60747 bytes 19 files changed, 2062 insertions(+), 3 deletions(-) create mode 100644 src/SharpCompress/Compressors/Deflate64/BlockType.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/DeflateInput.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/InflaterState.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/InputBuffer.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/Match.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/MatchState.cs create mode 100644 src/SharpCompress/Compressors/Deflate64/OutputWindow.cs create mode 100644 tests/TestArchives/Archives/Zip.deflate64.zip diff --git a/FORMATS.md b/FORMATS.md index 09d5ae63..2b1d3b34 100644 --- a/FORMATS.md +++ b/FORMATS.md @@ -11,7 +11,7 @@ | Archive Format | Compression Format(s) | Compress/Decompress | Archive API | Reader API | Writer API | | --- | --- | --- | --- | --- | --- | | Rar | Rar | Decompress (1) | RarArchive | RarReader | N/A | -| Zip (2) | None, DEFLATE, BZip2, LZMA/LZMA2, PPMd | Both | ZipArchive | ZipReader | ZipWriter | +| Zip (2) | None, DEFLATE, Deflate64, BZip2, LZMA/LZMA2, PPMd | Both | ZipArchive | ZipReader | ZipWriter | | Tar | None | Both | TarArchive | TarReader | TarWriter (3) | | Tar.GZip | DEFLATE | Both | TarArchive | TarReader | TarWriter (3) | | Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) | @@ -22,7 +22,7 @@ | LZip (single file) (5) | LZip (LZMA) | Both | LZipArchive | LZipReader | LZipWriter | 1. SOLID Rars are only supported in the RarReader API. - 2. Zip format supports pkware and WinzipAES encryption. However, encrypted LZMA is not supported. Zip64 reading/writing is supported but only with seekable streams as the Zip spec doesn't support Zip64 data in post data descriptors. + 2. Zip format supports pkware and WinzipAES encryption. However, encrypted LZMA is not supported. Zip64 reading/writing is supported but only with seekable streams as the Zip spec doesn't support Zip64 data in post data descriptors. Deflate64 is only supported for reading. 3. The Tar format requires a file size in the header. If no size is specified to the TarWriter and the stream is not seekable, then an exception will be thrown. 4. The 7Zip format doesn't allow for reading as a forward-only stream so 7Zip is only supported through the Archive API 5. LZip has no support for extra data like the file name or timestamp. There is a default filename used when looking at the entry Key on the archive. @@ -36,6 +36,7 @@ For those who want to directly compress/decompress bits. The single file format | BZip2Stream | Both | | GZipStream | Both | | DeflateStream | Both | +| Deflate64Stream | Decompress | | LZMAStream | Both | | PPMdStream | Both | | ADCStream | Decompress | diff --git a/src/SharpCompress/Common/CompressionType.cs b/src/SharpCompress/Common/CompressionType.cs index e2774385..23ed354f 100644 --- a/src/SharpCompress/Common/CompressionType.cs +++ b/src/SharpCompress/Common/CompressionType.cs @@ -13,6 +13,7 @@ BCJ2, LZip, Xz, - Unknown + Unknown, + Deflate64 } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index 8b8c7337..111c8a20 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -32,6 +32,10 @@ namespace SharpCompress.Common.Zip { return CompressionType.Deflate; } + case ZipCompressionMethod.Deflate64: + { + return CompressionType.Deflate64; + } case ZipCompressionMethod.LZMA: { return CompressionType.LZMA; diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 1f626b0a..9b2e74ca 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -5,6 +5,7 @@ using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; using SharpCompress.Compressors.Deflate; +using SharpCompress.Compressors.Deflate64; using SharpCompress.Compressors.LZMA; using SharpCompress.Compressors.PPMd; using SharpCompress.Converters; @@ -66,6 +67,10 @@ namespace SharpCompress.Common.Zip { return new DeflateStream(stream, CompressionMode.Decompress); } + case ZipCompressionMethod.Deflate64: + { + return new Deflate64Stream(stream, CompressionMode.Decompress); + } case ZipCompressionMethod.BZip2: { return new BZip2Stream(stream, CompressionMode.Decompress); diff --git a/src/SharpCompress/Compressors/Deflate64/BlockType.cs b/src/SharpCompress/Compressors/Deflate64/BlockType.cs new file mode 100644 index 00000000..c8938079 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/BlockType.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace SharpCompress.Compressors.Deflate64 +{ + internal enum BlockType + { + Uncompressed = 0, + Static = 1, + Dynamic = 2 + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs new file mode 100644 index 00000000..1c8a5d9f --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/Deflate64Stream.cs @@ -0,0 +1,257 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using SharpCompress.Common.Zip; +using SharpCompress.Compressors.Deflate; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; + +namespace SharpCompress.Compressors.Deflate64 +{ + public sealed partial class Deflate64Stream : Stream + { + internal const int DefaultBufferSize = 8192; + + private Stream _stream; + private CompressionMode _mode; + private bool _leaveOpen; + private InflaterManaged _inflater; + private byte[] _buffer; + + public Deflate64Stream(Stream stream, CompressionMode mode, + CompressionLevel level = CompressionLevel.Default, + bool leaveOpen = false) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + if (mode != CompressionMode.Decompress) + throw new NotImplementedException("Deflate64: this implementation only supports decompression"); + if (!stream.CanRead) + throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); + + InitializeInflater(stream, leaveOpen, ZipCompressionMethod.Deflate64); + } + + /// + /// Sets up this DeflateManagedStream to be used for Inflation/Decompression + /// + internal void InitializeInflater(Stream stream, bool leaveOpen, ZipCompressionMethod method = ZipCompressionMethod.Deflate) + { + Debug.Assert(stream != null); + Debug.Assert(method == ZipCompressionMethod.Deflate || method == ZipCompressionMethod.Deflate64); + if (!stream.CanRead) + throw new ArgumentException("Deflate64: input stream is not readable", nameof(stream)); + + _inflater = new InflaterManaged(method == ZipCompressionMethod.Deflate64); + + _stream = stream; + _mode = CompressionMode.Decompress; + _leaveOpen = leaveOpen; + _buffer = new byte[DefaultBufferSize]; + } + + public override bool CanRead + { + get + { + if (_stream == null) + { + return false; + } + + return (_mode == CompressionMode.Decompress && _stream.CanRead); + } + } + + public override bool CanWrite + { + get + { + if (_stream == null) + { + return false; + } + + return (_mode == CompressionMode.Compress && _stream.CanWrite); + } + } + + public override bool CanSeek => false; + + public override long Length + { + get { throw new NotSupportedException("Deflate64: not supported"); } + } + + public override long Position + { + get { throw new NotSupportedException("Deflate64: not supported"); } + set { throw new NotSupportedException("Deflate64: not supported"); } + } + + public override void Flush() + { + EnsureNotDisposed(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Deflate64: not supported"); + } + + public override void SetLength(long value) + { + throw new NotSupportedException("Deflate64: not supported"); + } + + public override int Read(byte[] array, int offset, int count) + { + EnsureDecompressionMode(); + ValidateParameters(array, offset, count); + EnsureNotDisposed(); + + int bytesRead; + int currentOffset = offset; + int remainingCount = count; + + while (true) + { + bytesRead = _inflater.Inflate(array, currentOffset, remainingCount); + currentOffset += bytesRead; + remainingCount -= bytesRead; + + if (remainingCount == 0) + { + break; + } + + if (_inflater.Finished()) + { + // if we finished decompressing, we can't have anything left in the outputwindow. + Debug.Assert(_inflater.AvailableOutput == 0, "We should have copied all stuff out!"); + break; + } + + int bytes = _stream.Read(_buffer, 0, _buffer.Length); + if (bytes <= 0) + { + break; + } + else if (bytes > _buffer.Length) + { + // The stream is either malicious or poorly implemented and returned a number of + // bytes larger than the buffer supplied to it. + throw new InvalidDataException("Deflate64: invalid data"); + } + + _inflater.SetInput(_buffer, 0, bytes); + } + + return count - remainingCount; + } + + private void ValidateParameters(byte[] array, int offset, int count) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset)); + + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + if (array.Length - offset < count) + throw new ArgumentException("Deflate64: invalid offset/count combination"); + } + + private void EnsureNotDisposed() + { + if (_stream == null) + ThrowStreamClosedException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowStreamClosedException() + { + throw new ObjectDisposedException(null, "Deflate64: stream has been disposed"); + } + + private void EnsureDecompressionMode() + { + if (_mode != CompressionMode.Decompress) + ThrowCannotReadFromDeflateManagedStreamException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotReadFromDeflateManagedStreamException() + { + throw new InvalidOperationException("Deflate64: cannot read from this stream"); + } + + private void EnsureCompressionMode() + { + if (_mode != CompressionMode.Compress) + ThrowCannotWriteToDeflateManagedStreamException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotWriteToDeflateManagedStreamException() + { + throw new InvalidOperationException("Deflate64: cannot write to this stream"); + } + + public override void Write(byte[] array, int offset, int count) + { + ThrowCannotWriteToDeflateManagedStreamException(); + } + + // This is called by Dispose: + private void PurgeBuffers(bool disposing) + { + if (!disposing) + return; + + if (_stream == null) + return; + + Flush(); + } + + protected override void Dispose(bool disposing) + { + try + { + PurgeBuffers(disposing); + } + finally + { + // Close the underlying stream even if PurgeBuffers threw. + // Stream.Close() may throw here (may or may not be due to the same error). + // In this case, we still need to clean up internal resources, hence the inner finally blocks. + try + { + if (disposing && !_leaveOpen && _stream != null) + _stream.Dispose(); + } + finally + { + _stream = null; + + try + { + _inflater?.Dispose(); + } + finally + { + _inflater = null; + base.Dispose(disposing); + } + } + } + } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs b/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs new file mode 100644 index 00000000..faf26edb --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/DeflateInput.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace SharpCompress.Compressors.Deflate64 +{ + internal sealed class DeflateInput + { + internal byte[] Buffer { get; set; } + internal int Count { get; set; } + internal int StartIndex { get; set; } + + internal void ConsumeBytes(int n) + { + Debug.Assert(n <= Count, "Should use more bytes than what we have in the buffer"); + StartIndex += n; + Count -= n; + Debug.Assert(StartIndex + Count <= Buffer.Length, "Input buffer is in invalid state!"); + } + + internal InputState DumpState() => new InputState(Count, StartIndex); + + internal void RestoreState(InputState state) + { + Count = state._count; + StartIndex = state._startIndex; + } + + internal /*readonly */struct InputState + { + internal readonly int _count; + internal readonly int _startIndex; + + internal InputState(int count, int startIndex) + { + _count = count; + _startIndex = startIndex; + } + } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs b/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs new file mode 100644 index 00000000..1e455a46 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/FastEncoderStatus.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace SharpCompress.Compressors.Deflate64 +{ + internal static class FastEncoderStatics + { + // static information for encoding, DO NOT MODIFY + + internal static readonly byte[] FastEncoderTreeStructureData = + { + 0xec,0xbd,0x07,0x60,0x1c,0x49,0x96,0x25,0x26,0x2f,0x6d,0xca, + 0x7b,0x7f,0x4a,0xf5,0x4a,0xd7,0xe0,0x74,0xa1,0x08,0x80,0x60, + 0x13,0x24,0xd8,0x90,0x40,0x10,0xec,0xc1,0x88,0xcd,0xe6,0x92, + 0xec,0x1d,0x69,0x47,0x23,0x29,0xab,0x2a,0x81,0xca,0x65,0x56, + 0x65,0x5d,0x66,0x16,0x40,0xcc,0xed,0x9d,0xbc,0xf7,0xde,0x7b, + 0xef,0xbd,0xf7,0xde,0x7b,0xef,0xbd,0xf7,0xba,0x3b,0x9d,0x4e, + 0x27,0xf7,0xdf,0xff,0x3f,0x5c,0x66,0x64,0x01,0x6c,0xf6,0xce, + 0x4a,0xda,0xc9,0x9e,0x21,0x80,0xaa,0xc8,0x1f,0x3f,0x7e,0x7c, + 0x1f,0x3f + }; + + internal static readonly byte[] BFinalFastEncoderTreeStructureData = + { + 0xed,0xbd,0x07,0x60,0x1c,0x49,0x96,0x25,0x26,0x2f,0x6d,0xca, + 0x7b,0x7f,0x4a,0xf5,0x4a,0xd7,0xe0,0x74,0xa1,0x08,0x80,0x60, + 0x13,0x24,0xd8,0x90,0x40,0x10,0xec,0xc1,0x88,0xcd,0xe6,0x92, + 0xec,0x1d,0x69,0x47,0x23,0x29,0xab,0x2a,0x81,0xca,0x65,0x56, + 0x65,0x5d,0x66,0x16,0x40,0xcc,0xed,0x9d,0xbc,0xf7,0xde,0x7b, + 0xef,0xbd,0xf7,0xde,0x7b,0xef,0xbd,0xf7,0xba,0x3b,0x9d,0x4e, + 0x27,0xf7,0xdf,0xff,0x3f,0x5c,0x66,0x64,0x01,0x6c,0xf6,0xce, + 0x4a,0xda,0xc9,0x9e,0x21,0x80,0xaa,0xc8,0x1f,0x3f,0x7e,0x7c, + 0x1f,0x3f + }; + + // Output a currentMatch with length matchLen (>= MIN_MATCH) and displacement matchPos + // + // Optimisation: unlike the other encoders, here we have an array of codes for each currentMatch + // length (not just each currentMatch length slot), complete with all the extra bits filled in, in + // a single array element. + // + // There are many advantages to doing this: + // + // 1. A single array lookup on g_FastEncoderLiteralCodeInfo, instead of separate array lookups + // on g_LengthLookup (to get the length slot), g_FastEncoderLiteralTreeLength, + // g_FastEncoderLiteralTreeCode, g_ExtraLengthBits, and g_BitMask + // + // 2. The array is an array of ULONGs, so no access penalty, unlike for accessing those USHORT + // code arrays in the other encoders (although they could be made into ULONGs with some + // modifications to the source). + // + // Note, if we could guarantee that codeLen <= 16 always, then we could skip an if statement here. + // + // A completely different optimisation is used for the distance codes since, obviously, a table for + // all 8192 distances combining their extra bits is not feasible. The distance codeinfo table is + // made up of code[], len[] and # extraBits for this code. + // + // The advantages are similar to the above; a ULONG array instead of a USHORT and BYTE array, better + // cache locality, fewer memory operations. + // + + + // Encoding information for literal and Length. + // The least 5 significant bits are the length + // and the rest is the code bits. + + internal static readonly uint[] FastEncoderLiteralCodeInfo = + { + 0x0000d7ee,0x0004d7ee,0x0002d7ee,0x0006d7ee,0x0001d7ee,0x0005d7ee,0x0003d7ee, + 0x0007d7ee,0x000037ee,0x0000c7ec,0x00000126,0x000437ee,0x000237ee,0x000637ee, + 0x000137ee,0x000537ee,0x000337ee,0x000737ee,0x0000b7ee,0x0004b7ee,0x0002b7ee, + 0x0006b7ee,0x0001b7ee,0x0005b7ee,0x0003b7ee,0x0007b7ee,0x000077ee,0x000477ee, + 0x000277ee,0x000677ee,0x000017ed,0x000177ee,0x00000526,0x000577ee,0x000023ea, + 0x0001c7ec,0x000377ee,0x000777ee,0x000217ed,0x000063ea,0x00000b68,0x00000ee9, + 0x00005beb,0x000013ea,0x00000467,0x00001b68,0x00000c67,0x00002ee9,0x00000768, + 0x00001768,0x00000f68,0x00001ee9,0x00001f68,0x00003ee9,0x000053ea,0x000001e9, + 0x000000e8,0x000021e9,0x000011e9,0x000010e8,0x000031e9,0x000033ea,0x000008e8, + 0x0000f7ee,0x0004f7ee,0x000018e8,0x000009e9,0x000004e8,0x000029e9,0x000014e8, + 0x000019e9,0x000073ea,0x0000dbeb,0x00000ce8,0x00003beb,0x0002f7ee,0x000039e9, + 0x00000bea,0x000005e9,0x00004bea,0x000025e9,0x000027ec,0x000015e9,0x000035e9, + 0x00000de9,0x00002bea,0x000127ec,0x0000bbeb,0x0006f7ee,0x0001f7ee,0x0000a7ec, + 0x00007beb,0x0005f7ee,0x0000fbeb,0x0003f7ee,0x0007f7ee,0x00000fee,0x00000326, + 0x00000267,0x00000a67,0x00000667,0x00000726,0x00001ce8,0x000002e8,0x00000e67, + 0x000000a6,0x0001a7ec,0x00002de9,0x000004a6,0x00000167,0x00000967,0x000002a6, + 0x00000567,0x000117ed,0x000006a6,0x000001a6,0x000005a6,0x00000d67,0x000012e8, + 0x00000ae8,0x00001de9,0x00001ae8,0x000007eb,0x000317ed,0x000067ec,0x000097ed, + 0x000297ed,0x00040fee,0x00020fee,0x00060fee,0x00010fee,0x00050fee,0x00030fee, + 0x00070fee,0x00008fee,0x00048fee,0x00028fee,0x00068fee,0x00018fee,0x00058fee, + 0x00038fee,0x00078fee,0x00004fee,0x00044fee,0x00024fee,0x00064fee,0x00014fee, + 0x00054fee,0x00034fee,0x00074fee,0x0000cfee,0x0004cfee,0x0002cfee,0x0006cfee, + 0x0001cfee,0x0005cfee,0x0003cfee,0x0007cfee,0x00002fee,0x00042fee,0x00022fee, + 0x00062fee,0x00012fee,0x00052fee,0x00032fee,0x00072fee,0x0000afee,0x0004afee, + 0x0002afee,0x0006afee,0x0001afee,0x0005afee,0x0003afee,0x0007afee,0x00006fee, + 0x00046fee,0x00026fee,0x00066fee,0x00016fee,0x00056fee,0x00036fee,0x00076fee, + 0x0000efee,0x0004efee,0x0002efee,0x0006efee,0x0001efee,0x0005efee,0x0003efee, + 0x0007efee,0x00001fee,0x00041fee,0x00021fee,0x00061fee,0x00011fee,0x00051fee, + 0x00031fee,0x00071fee,0x00009fee,0x00049fee,0x00029fee,0x00069fee,0x00019fee, + 0x00059fee,0x00039fee,0x00079fee,0x00005fee,0x00045fee,0x00025fee,0x00065fee, + 0x00015fee,0x00055fee,0x00035fee,0x00075fee,0x0000dfee,0x0004dfee,0x0002dfee, + 0x0006dfee,0x0001dfee,0x0005dfee,0x0003dfee,0x0007dfee,0x00003fee,0x00043fee, + 0x00023fee,0x00063fee,0x00013fee,0x00053fee,0x00033fee,0x00073fee,0x0000bfee, + 0x0004bfee,0x0002bfee,0x0006bfee,0x0001bfee,0x0005bfee,0x0003bfee,0x0007bfee, + 0x00007fee,0x00047fee,0x00027fee,0x00067fee,0x00017fee,0x000197ed,0x000397ed, + 0x000057ed,0x00057fee,0x000257ed,0x00037fee,0x000157ed,0x00077fee,0x000357ed, + 0x0000ffee,0x0004ffee,0x0002ffee,0x0006ffee,0x0001ffee,0x00000084,0x00000003, + 0x00000184,0x00000044,0x00000144,0x000000c5,0x000002c5,0x000001c5,0x000003c6, + 0x000007c6,0x00000026,0x00000426,0x000003a7,0x00000ba7,0x000007a7,0x00000fa7, + 0x00000227,0x00000627,0x00000a27,0x00000e27,0x00000068,0x00000868,0x00001068, + 0x00001868,0x00000369,0x00001369,0x00002369,0x00003369,0x000006ea,0x000026ea, + 0x000046ea,0x000066ea,0x000016eb,0x000036eb,0x000056eb,0x000076eb,0x000096eb, + 0x0000b6eb,0x0000d6eb,0x0000f6eb,0x00003dec,0x00007dec,0x0000bdec,0x0000fdec, + 0x00013dec,0x00017dec,0x0001bdec,0x0001fdec,0x00006bed,0x0000ebed,0x00016bed, + 0x0001ebed,0x00026bed,0x0002ebed,0x00036bed,0x0003ebed,0x000003ec,0x000043ec, + 0x000083ec,0x0000c3ec,0x000103ec,0x000143ec,0x000183ec,0x0001c3ec,0x00001bee, + 0x00009bee,0x00011bee,0x00019bee,0x00021bee,0x00029bee,0x00031bee,0x00039bee, + 0x00041bee,0x00049bee,0x00051bee,0x00059bee,0x00061bee,0x00069bee,0x00071bee, + 0x00079bee,0x000167f0,0x000367f0,0x000567f0,0x000767f0,0x000967f0,0x000b67f0, + 0x000d67f0,0x000f67f0,0x001167f0,0x001367f0,0x001567f0,0x001767f0,0x001967f0, + 0x001b67f0,0x001d67f0,0x001f67f0,0x000087ef,0x000187ef,0x000287ef,0x000387ef, + 0x000487ef,0x000587ef,0x000687ef,0x000787ef,0x000887ef,0x000987ef,0x000a87ef, + 0x000b87ef,0x000c87ef,0x000d87ef,0x000e87ef,0x000f87ef,0x0000e7f0,0x0002e7f0, + 0x0004e7f0,0x0006e7f0,0x0008e7f0,0x000ae7f0,0x000ce7f0,0x000ee7f0,0x0010e7f0, + 0x0012e7f0,0x0014e7f0,0x0016e7f0,0x0018e7f0,0x001ae7f0,0x001ce7f0,0x001ee7f0, + 0x0005fff3,0x000dfff3,0x0015fff3,0x001dfff3,0x0025fff3,0x002dfff3,0x0035fff3, + 0x003dfff3,0x0045fff3,0x004dfff3,0x0055fff3,0x005dfff3,0x0065fff3,0x006dfff3, + 0x0075fff3,0x007dfff3,0x0085fff3,0x008dfff3,0x0095fff3,0x009dfff3,0x00a5fff3, + 0x00adfff3,0x00b5fff3,0x00bdfff3,0x00c5fff3,0x00cdfff3,0x00d5fff3,0x00ddfff3, + 0x00e5fff3,0x00edfff3,0x00f5fff3,0x00fdfff3,0x0003fff3,0x000bfff3,0x0013fff3, + 0x001bfff3,0x0023fff3,0x002bfff3,0x0033fff3,0x003bfff3,0x0043fff3,0x004bfff3, + 0x0053fff3,0x005bfff3,0x0063fff3,0x006bfff3,0x0073fff3,0x007bfff3,0x0083fff3, + 0x008bfff3,0x0093fff3,0x009bfff3,0x00a3fff3,0x00abfff3,0x00b3fff3,0x00bbfff3, + 0x00c3fff3,0x00cbfff3,0x00d3fff3,0x00dbfff3,0x00e3fff3,0x00ebfff3,0x00f3fff3, + 0x00fbfff3,0x0007fff3,0x000ffff3,0x0017fff3,0x001ffff3,0x0027fff3,0x002ffff3, + 0x0037fff3,0x003ffff3,0x0047fff3,0x004ffff3,0x0057fff3,0x005ffff3,0x0067fff3, + 0x006ffff3,0x0077fff3,0x007ffff3,0x0087fff3,0x008ffff3,0x0097fff3,0x009ffff3, + 0x00a7fff3,0x00affff3,0x00b7fff3,0x00bffff3,0x00c7fff3,0x00cffff3,0x00d7fff3, + 0x00dffff3,0x00e7fff3,0x00effff3,0x00f7fff3,0x00fffff3,0x0001e7f1,0x0003e7f1, + 0x0005e7f1,0x0007e7f1,0x0009e7f1,0x000be7f1,0x000de7f1,0x000fe7f1,0x0011e7f1, + 0x0013e7f1,0x0015e7f1,0x0017e7f1,0x0019e7f1,0x001be7f1,0x001de7f1,0x001fe7f1, + 0x0021e7f1,0x0023e7f1,0x0025e7f1,0x0027e7f1,0x0029e7f1,0x002be7f1,0x002de7f1, + 0x002fe7f1,0x0031e7f1,0x0033e7f1,0x0035e7f1,0x0037e7f1,0x0039e7f1,0x003be7f1, + 0x003de7f1,0x000047eb + }; + + internal static readonly uint[] FastEncoderDistanceCodeInfo = + { + 0x00000f06,0x0001ff0a,0x0003ff0b,0x0007ff0b,0x0000ff19,0x00003f18,0x0000bf28, + 0x00007f28,0x00001f37,0x00005f37,0x00000d45,0x00002f46,0x00000054,0x00001d55, + 0x00000864,0x00000365,0x00000474,0x00001375,0x00000c84,0x00000284,0x00000a94, + 0x00000694,0x00000ea4,0x000001a4,0x000009b4,0x00000bb5,0x000005c4,0x00001bc5, + 0x000007d5,0x000017d5,0x00000000,0x00000100 + }; + + internal static readonly uint[] BitMask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 }; + internal static readonly byte[] ExtraLengthBits = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + internal static readonly byte[] ExtraDistanceBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 }; + internal const int NumChars = 256; + internal const int NumLengthBaseCodes = 29; + internal const int NumDistBaseCodes = 30; + + internal const uint FastEncoderPostTreeBitBuf = 0x0022; + internal const int FastEncoderPostTreeBitCount = 9; + + internal const uint NoCompressionHeader = 0x0; + internal const int NoCompressionHeaderBitCount = 3; + internal const uint BFinalNoCompressionHeader = 0x1; + internal const int BFinalNoCompressionHeaderBitCount = 3; + internal const int MaxCodeLen = 16; + + private static readonly byte[] s_distLookup = CreateDistanceLookup(); + + private static byte[] CreateDistanceLookup() + { + byte[] result = new byte[512]; + + // Generate the global slot tables which allow us to convert a distance + // (0..32K) to a distance slot (0..29) + // + // Distance table + // Extra Extra Extra + // Code Bits Dist Code Bits Dist Code Bits Distance + // ---- ---- ---- ---- ---- ------ ---- ---- -------- + // 0 0 1 10 4 33-48 20 9 1025-1536 + // 1 0 2 11 4 49-64 21 9 1537-2048 + // 2 0 3 12 5 65-96 22 10 2049-3072 + // 3 0 4 13 5 97-128 23 10 3073-4096 + // 4 1 5,6 14 6 129-192 24 11 4097-6144 + // 5 1 7,8 15 6 193-256 25 11 6145-8192 + // 6 2 9-12 16 7 257-384 26 12 8193-12288 + // 7 2 13-16 17 7 385-512 27 12 12289-16384 + // 8 3 17-24 18 8 513-768 28 13 16385-24576 + // 9 3 25-32 19 8 769-1024 29 13 24577-32768 + + // Initialize the mapping length (0..255) -> length code (0..28) + //int length = 0; + //for (code = 0; code < FastEncoderStatics.NumLengthBaseCodes-1; code++) { + // for (int n = 0; n < (1 << FastEncoderStatics.ExtraLengthBits[code]); n++) + // lengthLookup[length++] = (byte) code; + //} + //lengthLookup[length-1] = (byte) code; + + // Initialize the mapping dist (0..32K) -> dist code (0..29) + int dist = 0; + int code; + for (code = 0; code < 16; code++) + { + for (int n = 0; n < (1 << ExtraDistanceBits[code]); n++) + result[dist++] = (byte)code; + } + + dist >>= 7; // from now on, all distances are divided by 128 + + for (; code < NumDistBaseCodes; code++) + { + for (int n = 0; n < (1 << (ExtraDistanceBits[code] - 7)); n++) + result[256 + dist++] = (byte)code; + } + + return result; + } + + // Return the position slot (0...29) of a match offset (0...32767) + internal static int GetSlot(int pos) => + s_distLookup[((pos) < 256) ? (pos) : (256 + ((pos) >> 7))]; + + // Reverse 'length' of the bits in code + public static uint BitReverse(uint code, int length) + { + uint new_code = 0; + + Debug.Assert(length > 0 && length <= 16, "Invalid len"); + do + { + new_code |= (code & 1); + new_code <<= 1; + code >>= 1; + } while (--length > 0); + + return new_code >> 1; + } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs b/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs new file mode 100644 index 00000000..b57219a2 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/HuffmanTree.cs @@ -0,0 +1,311 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.IO; + +namespace SharpCompress.Compressors.Deflate64 +{ + // Strictly speaking this class is not a HuffmanTree, this class is + // a lookup table combined with a HuffmanTree. The idea is to speed up + // the lookup for short symbols (they should appear more frequently ideally.) + // However we don't want to create a huge table since it might take longer to + // build the table than decoding (Deflate usually generates new tables frequently.) + // + // Jean-loup Gailly and Mark Adler gave a very good explanation about this. + // The full text (algorithm.txt) can be found inside + // ftp://ftp.uu.net/pub/archiving/zip/zlib/zlib.zip. + // + // Following paper explains decoding in details: + // Hirschberg and Lelewer, "Efficient decoding of prefix codes," + // Comm. ACM, 33,4, April 1990, pp. 449-459. + // + + internal sealed class HuffmanTree + { + internal const int MaxLiteralTreeElements = 288; + internal const int MaxDistTreeElements = 32; + internal const int EndOfBlockCode = 256; + internal const int NumberOfCodeLengthTreeElements = 19; + + private readonly int _tableBits; + private readonly short[] _table; + private readonly short[] _left; + private readonly short[] _right; + private readonly byte[] _codeLengthArray; +#if DEBUG + private uint[] _codeArrayDebug; +#endif + + private readonly int _tableMask; + + // huffman tree for static block + public static HuffmanTree StaticLiteralLengthTree { get; } = new HuffmanTree(GetStaticLiteralTreeLength()); + + public static HuffmanTree StaticDistanceTree { get; } = new HuffmanTree(GetStaticDistanceTreeLength()); + + public HuffmanTree(byte[] codeLengths) + { + Debug.Assert( + codeLengths.Length == MaxLiteralTreeElements || + codeLengths.Length == MaxDistTreeElements || + codeLengths.Length == NumberOfCodeLengthTreeElements, + "we only expect three kinds of Length here"); + _codeLengthArray = codeLengths; + + if (_codeLengthArray.Length == MaxLiteralTreeElements) + { + // bits for Literal/Length tree table + _tableBits = 9; + } + else + { + // bits for distance tree table and code length tree table + _tableBits = 7; + } + _tableMask = (1 << _tableBits) - 1; + + _table = new short[1 << _tableBits]; + + // I need to find proof that left and right array will always be + // enough. I think they are. + _left = new short[2 * _codeLengthArray.Length]; + _right = new short[2 * _codeLengthArray.Length]; + + CreateTable(); + } + + // Generate the array contains huffman codes lengths for static huffman tree. + // The data is in RFC 1951. + private static byte[] GetStaticLiteralTreeLength() + { + byte[] literalTreeLength = new byte[MaxLiteralTreeElements]; + for (int i = 0; i <= 143; i++) + literalTreeLength[i] = 8; + + for (int i = 144; i <= 255; i++) + literalTreeLength[i] = 9; + + for (int i = 256; i <= 279; i++) + literalTreeLength[i] = 7; + + for (int i = 280; i <= 287; i++) + literalTreeLength[i] = 8; + + return literalTreeLength; + } + + private static byte[] GetStaticDistanceTreeLength() + { + byte[] staticDistanceTreeLength = new byte[MaxDistTreeElements]; + for (int i = 0; i < MaxDistTreeElements; i++) + { + staticDistanceTreeLength[i] = 5; + } + return staticDistanceTreeLength; + } + + // Calculate the huffman code for each character based on the code length for each character. + // This algorithm is described in standard RFC 1951 + private uint[] CalculateHuffmanCode() + { + uint[] bitLengthCount = new uint[17]; + foreach (int codeLength in _codeLengthArray) + { + bitLengthCount[codeLength]++; + } + bitLengthCount[0] = 0; // clear count for length 0 + + uint[] nextCode = new uint[17]; + uint tempCode = 0; + for (int bits = 1; bits <= 16; bits++) + { + tempCode = (tempCode + bitLengthCount[bits - 1]) << 1; + nextCode[bits] = tempCode; + } + + uint[] code = new uint[MaxLiteralTreeElements]; + for (int i = 0; i < _codeLengthArray.Length; i++) + { + int len = _codeLengthArray[i]; + + if (len > 0) + { + code[i] = FastEncoderStatics.BitReverse(nextCode[len], len); + nextCode[len]++; + } + } + return code; + } + + private void CreateTable() + { + uint[] codeArray = CalculateHuffmanCode(); +#if DEBUG + _codeArrayDebug = codeArray; +#endif + + short avail = (short)_codeLengthArray.Length; + + for (int ch = 0; ch < _codeLengthArray.Length; ch++) + { + // length of this code + int len = _codeLengthArray[ch]; + if (len > 0) + { + // start value (bit reversed) + int start = (int)codeArray[ch]; + + if (len <= _tableBits) + { + // If a particular symbol is shorter than nine bits, + // then that symbol's translation is duplicated + // in all those entries that start with that symbol's bits. + // For example, if the symbol is four bits, then it's duplicated + // 32 times in a nine-bit table. If a symbol is nine bits long, + // it appears in the table once. + // + // Make sure that in the loop below, code is always + // less than table_size. + // + // On last iteration we store at array index: + // initial_start_at + (locs-1)*increment + // = initial_start_at + locs*increment - increment + // = initial_start_at + (1 << tableBits) - increment + // = initial_start_at + table_size - increment + // + // Therefore we must ensure: + // initial_start_at + table_size - increment < table_size + // or: initial_start_at < increment + // + int increment = 1 << len; + if (start >= increment) + { + throw new InvalidDataException("Deflate64: invalid Huffman data"); + } + + // Note the bits in the table are reverted. + int locs = 1 << (_tableBits - len); + for (int j = 0; j < locs; j++) + { + _table[start] = (short)ch; + start += increment; + } + } + else + { + // For any code which has length longer than num_elements, + // build a binary tree. + + int overflowBits = len - _tableBits; // the nodes we need to respent the data. + int codeBitMask = 1 << _tableBits; // mask to get current bit (the bits can't fit in the table) + + // the left, right table is used to repesent the + // the rest bits. When we got the first part (number bits.) and look at + // tbe table, we will need to follow the tree to find the real character. + // This is in place to avoid bloating the table if there are + // a few ones with long code. + int index = start & ((1 << _tableBits) - 1); + short[] array = _table; + + do + { + short value = array[index]; + + if (value == 0) + { + // set up next pointer if this node is not used before. + array[index] = (short)-avail; // use next available slot. + value = (short)-avail; + avail++; + } + + if (value > 0) + { + // prevent an IndexOutOfRangeException from array[index] + throw new InvalidDataException("Deflate64: invalid Huffman data"); + } + + Debug.Assert(value < 0, "CreateTable: Only negative numbers are used for tree pointers!"); + + if ((start & codeBitMask) == 0) + { + // if current bit is 0, go change the left array + array = _left; + } + else + { + // if current bit is 1, set value in the right array + array = _right; + } + index = -value; // go to next node + + codeBitMask <<= 1; + overflowBits--; + } while (overflowBits != 0); + + array[index] = (short)ch; + } + } + } + } + + // + // This function will try to get enough bits from input and + // try to decode the bits. + // If there are no enought bits in the input, this function will return -1. + // + public int GetNextSymbol(InputBuffer input) + { + // Try to load 16 bits into input buffer if possible and get the bitBuffer value. + // If there aren't 16 bits available we will return all we have in the + // input buffer. + uint bitBuffer = input.TryLoad16Bits(); + if (input.AvailableBits == 0) + { // running out of input. + return -1; + } + + // decode an element + int symbol = _table[bitBuffer & _tableMask]; + if (symbol < 0) + { // this will be the start of the binary tree + // navigate the tree + uint mask = (uint)1 << _tableBits; + do + { + symbol = -symbol; + if ((bitBuffer & mask) == 0) + symbol = _left[symbol]; + else + symbol = _right[symbol]; + mask <<= 1; + } while (symbol < 0); + } + + int codeLength = _codeLengthArray[symbol]; + + // huffman code lengths must be at least 1 bit long + if (codeLength <= 0) + { + throw new InvalidDataException("Deflate64: invalid Huffman data"); + } + + // + // If this code is longer than the # bits we had in the bit buffer (i.e. + // we read only part of the code), we can hit the entry in the table or the tree + // for another symbol. However the length of another symbol will not match the + // available bits count. + if (codeLength > input.AvailableBits) + { + // We already tried to load 16 bits and maximum length is 15, + // so this means we are running out of input. + return -1; + } + + input.SkipBits(codeLength); + return symbol; + } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs b/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs new file mode 100644 index 00000000..32194d78 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/InflaterManaged.cs @@ -0,0 +1,738 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// zlib.h -- interface of the 'zlib' general purpose compression library +// version 1.2.1, November 17th, 2003 +// +// Copyright (C) 1995-2003 Jean-loup Gailly and Mark Adler +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// + +using System; +using System.Diagnostics; +using System.IO; + +namespace SharpCompress.Compressors.Deflate64 +{ + internal sealed class InflaterManaged + { + // const tables used in decoding: + + // Extra bits for length code 257 - 285. + private static readonly byte[] s_extraLengthBits = + { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,16 }; + + // The base length for length code 257 - 285. + // The formula to get the real length for a length code is lengthBase[code - 257] + (value stored in extraBits) + private static readonly int[] s_lengthBase = + { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,3}; + + // The base distance for distance code 0 - 31 + // The real distance for a distance code is distanceBasePosition[code] + (value stored in extraBits) + private static readonly int[] s_distanceBasePosition = + { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,32769,49153 }; + + // code lengths for code length alphabet is stored in following order + private static readonly byte[] s_codeOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static readonly byte[] s_staticDistanceTreeTable = + { + 0x00,0x10,0x08,0x18,0x04,0x14,0x0c,0x1c,0x02,0x12,0x0a,0x1a, + 0x06,0x16,0x0e,0x1e,0x01,0x11,0x09,0x19,0x05,0x15,0x0d,0x1d, + 0x03,0x13,0x0b,0x1b,0x07,0x17,0x0f,0x1f + }; + + private readonly OutputWindow _output; + private readonly InputBuffer _input; + private HuffmanTree _literalLengthTree; + private HuffmanTree _distanceTree; + + private InflaterState _state; + //private bool _hasFormatReader; + private int _bfinal; + private BlockType _blockType; + + // uncompressed block + private readonly byte[] _blockLengthBuffer = new byte[4]; + private int _blockLength; + + // compressed block + private int _length; + private int _distanceCode; + private int _extraBits; + + private int _loopCounter; + private int _literalLengthCodeCount; + private int _distanceCodeCount; + private int _codeLengthCodeCount; + private int _codeArraySize; + private int _lengthCode; + + private readonly byte[] _codeList; // temporary array to store the code length for literal/Length and distance + private readonly byte[] _codeLengthTreeCodeLength; + private readonly bool _deflate64; + private HuffmanTree _codeLengthTree; + + //private IFileFormatReader _formatReader; // class to decode header and footer (e.g. gzip) + + internal InflaterManaged(/*IFileFormatReader reader, */bool deflate64) + { + _output = new OutputWindow(); + _input = new InputBuffer(); + + _codeList = new byte[HuffmanTree.MaxLiteralTreeElements + HuffmanTree.MaxDistTreeElements]; + _codeLengthTreeCodeLength = new byte[HuffmanTree.NumberOfCodeLengthTreeElements]; + _deflate64 = deflate64; + //if (reader != null) + //{ + // _formatReader = reader; + // _hasFormatReader = true; + //} + Reset(); + } + + private void Reset() + { + _state = //_hasFormatReader ? + //InflaterState.ReadingHeader : // start by reading Header info + InflaterState.ReadingBFinal; // start by reading BFinal bit + } + + public void SetInput(byte[] inputBytes, int offset, int length) => + _input.SetInput(inputBytes, offset, length); // append the bytes + + public bool Finished() => _state == InflaterState.Done || _state == InflaterState.VerifyingFooter; + + public int AvailableOutput => _output.AvailableBytes; + + public int Inflate(byte[] bytes, int offset, int length) + { + // copy bytes from output to outputbytes if we have available bytes + // if buffer is not filled up. keep decoding until no input are available + // if decodeBlock returns false. Throw an exception. + int count = 0; + do + { + int copied = _output.CopyTo(bytes, offset, length); + if (copied > 0) + { + //if (_hasFormatReader) + //{ + // _formatReader.UpdateWithBytesRead(bytes, offset, copied); + //} + + offset += copied; + count += copied; + length -= copied; + } + + if (length == 0) + { // filled in the bytes array + break; + } + // Decode will return false when more input is needed + } while (!Finished() && Decode()); + + if (_state == InflaterState.VerifyingFooter) + { // finished reading CRC + // In this case finished is true and output window has all the data. + // But some data in output window might not be copied out. + if (_output.AvailableBytes == 0) + { + //_formatReader.Validate(); + } + } + + return count; + } + + //Each block of compressed data begins with 3 header bits + // containing the following data: + // first bit BFINAL + // next 2 bits BTYPE + // Note that the header bits do not necessarily begin on a byte + // boundary, since a block does not necessarily occupy an integral + // number of bytes. + // BFINAL is set if and only if this is the last block of the data + // set. + // BTYPE specifies how the data are compressed, as follows: + // 00 - no compression + // 01 - compressed with fixed Huffman codes + // 10 - compressed with dynamic Huffman codes + // 11 - reserved (error) + // The only difference between the two compressed cases is how the + // Huffman codes for the literal/length and distance alphabets are + // defined. + // + // This function returns true for success (end of block or output window is full,) + // false if we are short of input + // + private bool Decode() + { + bool eob = false; + bool result = false; + + if (Finished()) + { + return true; + } + + //if (_hasFormatReader) + //{ + // if (_state == InflaterState.ReadingHeader) + // { + // if (!_formatReader.ReadHeader(_input)) + // { + // return false; + // } + // _state = InflaterState.ReadingBFinal; + // } + // else if (_state == InflaterState.StartReadingFooter || _state == InflaterState.ReadingFooter) + // { + // if (!_formatReader.ReadFooter(_input)) + // return false; + + // _state = InflaterState.VerifyingFooter; + // return true; + // } + //} + + if (_state == InflaterState.ReadingBFinal) + { + // reading bfinal bit + // Need 1 bit + if (!_input.EnsureBitsAvailable(1)) + return false; + + _bfinal = _input.GetBits(1); + _state = InflaterState.ReadingBType; + } + + if (_state == InflaterState.ReadingBType) + { + // Need 2 bits + if (!_input.EnsureBitsAvailable(2)) + { + _state = InflaterState.ReadingBType; + return false; + } + + _blockType = (BlockType)_input.GetBits(2); + if (_blockType == BlockType.Dynamic) + { + _state = InflaterState.ReadingNumLitCodes; + } + else if (_blockType == BlockType.Static) + { + _literalLengthTree = HuffmanTree.StaticLiteralLengthTree; + _distanceTree = HuffmanTree.StaticDistanceTree; + _state = InflaterState.DecodeTop; + } + else if (_blockType == BlockType.Uncompressed) + { + _state = InflaterState.UncompressedAligning; + } + else + { + throw new InvalidDataException("Deflate64: unknown block type"); + } + } + + if (_blockType == BlockType.Dynamic) + { + if (_state < InflaterState.DecodeTop) + { + // we are reading the header + result = DecodeDynamicBlockHeader(); + } + else + { + result = DecodeBlock(out eob); // this can returns true when output is full + } + } + else if (_blockType == BlockType.Static) + { + result = DecodeBlock(out eob); + } + else if (_blockType == BlockType.Uncompressed) + { + result = DecodeUncompressedBlock(out eob); + } + else + { + throw new InvalidDataException("Deflate64: unknown block type"); + } + + // + // If we reached the end of the block and the block we were decoding had + // bfinal=1 (final block) + // + if (eob && (_bfinal != 0)) + { + //if (_hasFormatReader) + // _state = InflaterState.StartReadingFooter; + //else + _state = InflaterState.Done; + } + return result; + } + + + // Format of Non-compressed blocks (BTYPE=00): + // + // Any bits of input up to the next byte boundary are ignored. + // The rest of the block consists of the following information: + // + // 0 1 2 3 4... + // +---+---+---+---+================================+ + // | LEN | NLEN |... LEN bytes of literal data...| + // +---+---+---+---+================================+ + // + // LEN is the number of data bytes in the block. NLEN is the + // one's complement of LEN. + private bool DecodeUncompressedBlock(out bool end_of_block) + { + end_of_block = false; + while (true) + { + switch (_state) + { + case InflaterState.UncompressedAligning: // initial state when calling this function + // we must skip to a byte boundary + _input.SkipToByteBoundary(); + _state = InflaterState.UncompressedByte1; + goto case InflaterState.UncompressedByte1; + + case InflaterState.UncompressedByte1: // decoding block length + case InflaterState.UncompressedByte2: + case InflaterState.UncompressedByte3: + case InflaterState.UncompressedByte4: + int bits = _input.GetBits(8); + if (bits < 0) + { + return false; + } + + _blockLengthBuffer[_state - InflaterState.UncompressedByte1] = (byte)bits; + if (_state == InflaterState.UncompressedByte4) + { + _blockLength = _blockLengthBuffer[0] + ((int)_blockLengthBuffer[1]) * 256; + int blockLengthComplement = _blockLengthBuffer[2] + ((int)_blockLengthBuffer[3]) * 256; + + // make sure complement matches + if ((ushort)_blockLength != (ushort)(~blockLengthComplement)) + { + throw new InvalidDataException("Deflate64: invalid block length"); + } + } + + _state += 1; + break; + + case InflaterState.DecodingUncompressed: // copying block data + + // Directly copy bytes from input to output. + int bytesCopied = _output.CopyFrom(_input, _blockLength); + _blockLength -= bytesCopied; + + if (_blockLength == 0) + { + // Done with this block, need to re-init bit buffer for next block + _state = InflaterState.ReadingBFinal; + end_of_block = true; + return true; + } + + // We can fail to copy all bytes for two reasons: + // Running out of Input + // running out of free space in output window + if (_output.FreeBytes == 0) + { + return true; + } + + return false; + + default: + Debug./*Fail*/Assert(false, "check why we are here!"); + throw new InvalidDataException("Deflate64: unknown state"); + } + } + } + + private bool DecodeBlock(out bool end_of_block_code_seen) + { + end_of_block_code_seen = false; + + int freeBytes = _output.FreeBytes; // it is a little bit faster than frequently accessing the property + while (freeBytes > 65536) + { + // With Deflate64 we can have up to a 64kb length, so we ensure at least that much space is available + // in the OutputWindow to avoid overwriting previous unflushed output data. + + int symbol; + switch (_state) + { + case InflaterState.DecodeTop: + // decode an element from the literal tree + + // TODO: optimize this!!! + symbol = _literalLengthTree.GetNextSymbol(_input); + if (symbol < 0) + { + // running out of input + return false; + } + + if (symbol < 256) + { + // literal + _output.Write((byte)symbol); + --freeBytes; + } + else if (symbol == 256) + { + // end of block + end_of_block_code_seen = true; + // Reset state + _state = InflaterState.ReadingBFinal; + return true; + } + else + { + // length/distance pair + symbol -= 257; // length code started at 257 + if (symbol < 8) + { + symbol += 3; // match length = 3,4,5,6,7,8,9,10 + _extraBits = 0; + } + else if (!_deflate64 && symbol == 28) + { + // extra bits for code 285 is 0 + symbol = 258; // code 285 means length 258 + _extraBits = 0; + } + else + { + if (symbol < 0 || symbol >= s_extraLengthBits.Length) + { + throw new InvalidDataException("Deflate64: invalid data"); + } + _extraBits = s_extraLengthBits[symbol]; + Debug.Assert(_extraBits != 0, "We handle other cases separately!"); + } + _length = symbol; + goto case InflaterState.HaveInitialLength; + } + break; + + case InflaterState.HaveInitialLength: + if (_extraBits > 0) + { + _state = InflaterState.HaveInitialLength; + int bits = _input.GetBits(_extraBits); + if (bits < 0) + { + return false; + } + + if (_length < 0 || _length >= s_lengthBase.Length) + { + throw new InvalidDataException("Deflate64: invalid data"); + } + _length = s_lengthBase[_length] + bits; + } + _state = InflaterState.HaveFullLength; + goto case InflaterState.HaveFullLength; + + case InflaterState.HaveFullLength: + if (_blockType == BlockType.Dynamic) + { + _distanceCode = _distanceTree.GetNextSymbol(_input); + } + else + { + // get distance code directly for static block + _distanceCode = _input.GetBits(5); + if (_distanceCode >= 0) + { + _distanceCode = s_staticDistanceTreeTable[_distanceCode]; + } + } + + if (_distanceCode < 0) + { + // running out input + return false; + } + + _state = InflaterState.HaveDistCode; + goto case InflaterState.HaveDistCode; + + case InflaterState.HaveDistCode: + // To avoid a table lookup we note that for distanceCode > 3, + // extra_bits = (distanceCode-2) >> 1 + int offset; + if (_distanceCode > 3) + { + _extraBits = (_distanceCode - 2) >> 1; + int bits = _input.GetBits(_extraBits); + if (bits < 0) + { + return false; + } + offset = s_distanceBasePosition[_distanceCode] + bits; + } + else + { + offset = _distanceCode + 1; + } + + _output.WriteLengthDistance(_length, offset); + freeBytes -= _length; + _state = InflaterState.DecodeTop; + break; + + default: + Debug./*Fail*/Assert(false, "check why we are here!"); + throw new InvalidDataException("Deflate64: unknown state"); + } + } + + return true; + } + + + // Format of the dynamic block header: + // 5 Bits: HLIT, # of Literal/Length codes - 257 (257 - 286) + // 5 Bits: HDIST, # of Distance codes - 1 (1 - 32) + // 4 Bits: HCLEN, # of Code Length codes - 4 (4 - 19) + // + // (HCLEN + 4) x 3 bits: code lengths for the code length + // alphabet given just above, in the order: 16, 17, 18, + // 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + // + // These code lengths are interpreted as 3-bit integers + // (0-7); as above, a code length of 0 means the + // corresponding symbol (literal/length or distance code + // length) is not used. + // + // HLIT + 257 code lengths for the literal/length alphabet, + // encoded using the code length Huffman code + // + // HDIST + 1 code lengths for the distance alphabet, + // encoded using the code length Huffman code + // + // The code length repeat codes can cross from HLIT + 257 to the + // HDIST + 1 code lengths. In other words, all code lengths form + // a single sequence of HLIT + HDIST + 258 values. + private bool DecodeDynamicBlockHeader() + { + switch (_state) + { + case InflaterState.ReadingNumLitCodes: + _literalLengthCodeCount = _input.GetBits(5); + if (_literalLengthCodeCount < 0) + { + return false; + } + _literalLengthCodeCount += 257; + _state = InflaterState.ReadingNumDistCodes; + goto case InflaterState.ReadingNumDistCodes; + + case InflaterState.ReadingNumDistCodes: + _distanceCodeCount = _input.GetBits(5); + if (_distanceCodeCount < 0) + { + return false; + } + _distanceCodeCount += 1; + _state = InflaterState.ReadingNumCodeLengthCodes; + goto case InflaterState.ReadingNumCodeLengthCodes; + + case InflaterState.ReadingNumCodeLengthCodes: + _codeLengthCodeCount = _input.GetBits(4); + if (_codeLengthCodeCount < 0) + { + return false; + } + _codeLengthCodeCount += 4; + _loopCounter = 0; + _state = InflaterState.ReadingCodeLengthCodes; + goto case InflaterState.ReadingCodeLengthCodes; + + case InflaterState.ReadingCodeLengthCodes: + while (_loopCounter < _codeLengthCodeCount) + { + int bits = _input.GetBits(3); + if (bits < 0) + { + return false; + } + _codeLengthTreeCodeLength[s_codeOrder[_loopCounter]] = (byte)bits; + ++_loopCounter; + } + + for (int i = _codeLengthCodeCount; i < s_codeOrder.Length; i++) + { + _codeLengthTreeCodeLength[s_codeOrder[i]] = 0; + } + + // create huffman tree for code length + _codeLengthTree = new HuffmanTree(_codeLengthTreeCodeLength); + _codeArraySize = _literalLengthCodeCount + _distanceCodeCount; + _loopCounter = 0; // reset loop count + + _state = InflaterState.ReadingTreeCodesBefore; + goto case InflaterState.ReadingTreeCodesBefore; + + case InflaterState.ReadingTreeCodesBefore: + case InflaterState.ReadingTreeCodesAfter: + while (_loopCounter < _codeArraySize) + { + if (_state == InflaterState.ReadingTreeCodesBefore) + { + if ((_lengthCode = _codeLengthTree.GetNextSymbol(_input)) < 0) + { + return false; + } + } + + // The alphabet for code lengths is as follows: + // 0 - 15: Represent code lengths of 0 - 15 + // 16: Copy the previous code length 3 - 6 times. + // The next 2 bits indicate repeat length + // (0 = 3, ... , 3 = 6) + // Example: Codes 8, 16 (+2 bits 11), + // 16 (+2 bits 10) will expand to + // 12 code lengths of 8 (1 + 6 + 5) + // 17: Repeat a code length of 0 for 3 - 10 times. + // (3 bits of length) + // 18: Repeat a code length of 0 for 11 - 138 times + // (7 bits of length) + if (_lengthCode <= 15) + { + _codeList[_loopCounter++] = (byte)_lengthCode; + } + else + { + int repeatCount; + if (_lengthCode == 16) + { + if (!_input.EnsureBitsAvailable(2)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + if (_loopCounter == 0) + { + // can't have "prev code" on first code + throw new InvalidDataException(); + } + + byte previousCode = _codeList[_loopCounter - 1]; + repeatCount = _input.GetBits(2) + 3; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (int j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = previousCode; + } + } + else if (_lengthCode == 17) + { + if (!_input.EnsureBitsAvailable(3)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + repeatCount = _input.GetBits(3) + 3; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (int j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = 0; + } + } + else + { + // code == 18 + if (!_input.EnsureBitsAvailable(7)) + { + _state = InflaterState.ReadingTreeCodesAfter; + return false; + } + + repeatCount = _input.GetBits(7) + 11; + + if (_loopCounter + repeatCount > _codeArraySize) + { + throw new InvalidDataException(); + } + + for (int j = 0; j < repeatCount; j++) + { + _codeList[_loopCounter++] = 0; + } + } + } + _state = InflaterState.ReadingTreeCodesBefore; // we want to read the next code. + } + break; + + default: + Debug./*Fail*/Assert(false, "check why we are here!"); + throw new InvalidDataException("Deflate64: unknown state"); + } + + byte[] literalTreeCodeLength = new byte[HuffmanTree.MaxLiteralTreeElements]; + byte[] distanceTreeCodeLength = new byte[HuffmanTree.MaxDistTreeElements]; + + // Create literal and distance tables + Array.Copy(_codeList, 0, literalTreeCodeLength, 0, _literalLengthCodeCount); + Array.Copy(_codeList, _literalLengthCodeCount, distanceTreeCodeLength, 0, _distanceCodeCount); + + // Make sure there is an end-of-block code, otherwise how could we ever end? + if (literalTreeCodeLength[HuffmanTree.EndOfBlockCode] == 0) + { + throw new InvalidDataException(); + } + + _literalLengthTree = new HuffmanTree(literalTreeCodeLength); + _distanceTree = new HuffmanTree(distanceTreeCodeLength); + _state = InflaterState.DecodeTop; + return true; + } + + public void Dispose() { } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/InflaterState.cs b/src/SharpCompress/Compressors/Deflate64/InflaterState.cs new file mode 100644 index 00000000..356ea88f --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/InflaterState.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace SharpCompress.Compressors.Deflate64 +{ + // Do not rearrange the enum values. + internal enum InflaterState + { + ReadingHeader = 0, // Only applies to GZIP + + ReadingBFinal = 2, // About to read bfinal bit + ReadingBType = 3, // About to read blockType bits + + ReadingNumLitCodes = 4, // About to read # literal codes + ReadingNumDistCodes = 5, // About to read # dist codes + ReadingNumCodeLengthCodes = 6, // About to read # code length codes + ReadingCodeLengthCodes = 7, // In the middle of reading the code length codes + ReadingTreeCodesBefore = 8, // In the middle of reading tree codes (loop top) + ReadingTreeCodesAfter = 9, // In the middle of reading tree codes (extension; code > 15) + + DecodeTop = 10, // About to decode a literal (char/match) in a compressed block + HaveInitialLength = 11, // Decoding a match, have the literal code (base length) + HaveFullLength = 12, // Ditto, now have the full match length (incl. extra length bits) + HaveDistCode = 13, // Ditto, now have the distance code also, need extra dist bits + + /* uncompressed blocks */ + UncompressedAligning = 15, + UncompressedByte1 = 16, + UncompressedByte2 = 17, + UncompressedByte3 = 18, + UncompressedByte4 = 19, + DecodingUncompressed = 20, + + // These three apply only to GZIP + StartReadingFooter = 21, // (Initialisation for reading footer) + ReadingFooter = 22, + VerifyingFooter = 23, + + Done = 24 // Finished + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs b/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs new file mode 100644 index 00000000..0f585388 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/InputBuffer.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; + +namespace SharpCompress.Compressors.Deflate64 +{ + // This class can be used to read bits from an byte array quickly. + // Normally we get bits from 'bitBuffer' field and bitsInBuffer stores + // the number of bits available in 'BitBuffer'. + // When we used up the bits in bitBuffer, we will try to get byte from + // the byte array and copy the byte to appropiate position in bitBuffer. + // + // The byte array is not reused. We will go from 'start' to 'end'. + // When we reach the end, most read operations will return -1, + // which means we are running out of input. + + internal sealed class InputBuffer + { + private byte[] _buffer; // byte array to store input + private int _start; // start poisition of the buffer + private int _end; // end position of the buffer + private uint _bitBuffer = 0; // store the bits here, we can quickly shift in this buffer + private int _bitsInBuffer = 0; // number of bits available in bitBuffer + + /// Total bits available in the input buffer. + public int AvailableBits => _bitsInBuffer; + + /// Total bytes available in the input buffer. + public int AvailableBytes => (_end - _start) + (_bitsInBuffer / 8); + + /// Ensure that count bits are in the bit buffer. + /// Can be up to 16. + /// Returns false if input is not sufficient to make this true. + public bool EnsureBitsAvailable(int count) + { + Debug.Assert(0 < count && count <= 16, "count is invalid."); + + // manual inlining to improve perf + if (_bitsInBuffer < count) + { + if (NeedsInput()) + { + return false; + } + // insert a byte to bitbuffer + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + + if (_bitsInBuffer < count) + { + if (NeedsInput()) + { + return false; + } + // insert a byte to bitbuffer + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + } + } + + return true; + } + + /// + /// This function will try to load 16 or more bits into bitBuffer. + /// It returns whatever is contained in bitBuffer after loading. + /// The main difference between this and GetBits is that this will + /// never return -1. So the caller needs to check AvailableBits to + /// see how many bits are available. + /// + public uint TryLoad16Bits() + { + if (_bitsInBuffer < 8) + { + if (_start < _end) + { + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + } + + if (_start < _end) + { + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + } + } + else if (_bitsInBuffer < 16) + { + if (_start < _end) + { + _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer; + _bitsInBuffer += 8; + } + } + + return _bitBuffer; + } + + private uint GetBitMask(int count) => ((uint)1 << count) - 1; + + /// Gets count bits from the input buffer. Returns -1 if not enough bits available. + public int GetBits(int count) + { + Debug.Assert(0 < count && count <= 16, "count is invalid."); + + if (!EnsureBitsAvailable(count)) + { + return -1; + } + + int result = (int)(_bitBuffer & GetBitMask(count)); + _bitBuffer >>= count; + _bitsInBuffer -= count; + return result; + } + + /// + /// Copies length bytes from input buffer to output buffer starting at output[offset]. + /// You have to make sure, that the buffer is byte aligned. If not enough bytes are + /// available, copies fewer bytes. + /// + /// Returns the number of bytes copied, 0 if no byte is available. + public int CopyTo(byte[] output, int offset, int length) + { + Debug.Assert(output != null); + Debug.Assert(offset >= 0); + Debug.Assert(length >= 0); + Debug.Assert(offset <= output.Length - length); + Debug.Assert((_bitsInBuffer % 8) == 0); + + // Copy the bytes in bitBuffer first. + int bytesFromBitBuffer = 0; + while (_bitsInBuffer > 0 && length > 0) + { + output[offset++] = (byte)_bitBuffer; + _bitBuffer >>= 8; + _bitsInBuffer -= 8; + length--; + bytesFromBitBuffer++; + } + + if (length == 0) + { + return bytesFromBitBuffer; + } + + int avail = _end - _start; + if (length > avail) + { + length = avail; + } + + Array.Copy(_buffer, _start, output, offset, length); + _start += length; + return bytesFromBitBuffer + length; + } + + /// + /// Return true is all input bytes are used. + /// This means the caller can call SetInput to add more input. + /// + public bool NeedsInput() => _start == _end; + + /// + /// Set the byte array to be processed. + /// All the bits remained in bitBuffer will be processed before the new bytes. + /// We don't clone the byte array here since it is expensive. + /// The caller should make sure after a buffer is passed in. + /// It will not be changed before calling this function again. + /// + public void SetInput(byte[] buffer, int offset, int length) + { + Debug.Assert(buffer != null); + Debug.Assert(offset >= 0); + Debug.Assert(length >= 0); + Debug.Assert(offset <= buffer.Length - length); + Debug.Assert(_start == _end); + + _buffer = buffer; + _start = offset; + _end = offset + length; + } + + /// Skip n bits in the buffer. + public void SkipBits(int n) + { + Debug.Assert(_bitsInBuffer >= n, "No enough bits in the buffer, Did you call EnsureBitsAvailable?"); + _bitBuffer >>= n; + _bitsInBuffer -= n; + } + + /// Skips to the next byte boundary. + public void SkipToByteBoundary() + { + _bitBuffer >>= (_bitsInBuffer % 8); + _bitsInBuffer = _bitsInBuffer - (_bitsInBuffer % 8); + } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/Match.cs b/src/SharpCompress/Compressors/Deflate64/Match.cs new file mode 100644 index 00000000..4d5ce54b --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/Match.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace SharpCompress.Compressors.Deflate64 +{ + /// + /// This class represents a match in the history window. + /// + internal sealed class Match + { + internal MatchState State { get; set; } + internal int Position { get; set; } + internal int Length { get; set; } + internal byte Symbol { get; set; } + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/MatchState.cs b/src/SharpCompress/Compressors/Deflate64/MatchState.cs new file mode 100644 index 00000000..f88913bc --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/MatchState.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace SharpCompress.Compressors.Deflate64 +{ + internal enum MatchState + { + HasSymbol = 1, + HasMatch = 2, + HasSymbolAndMatch = 3 + } +} diff --git a/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs b/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs new file mode 100644 index 00000000..b5889319 --- /dev/null +++ b/src/SharpCompress/Compressors/Deflate64/OutputWindow.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; + +namespace SharpCompress.Compressors.Deflate64 +{ + /// + /// This class maintains a window for decompressed output. + /// We need to keep this because the decompressed information can be + /// a literal or a length/distance pair. For length/distance pair, + /// we need to look back in the output window and copy bytes from there. + /// We use a byte array of WindowSize circularly. + /// + internal sealed class OutputWindow + { + // With Deflate64 we can have up to a 65536 length as well as up to a 65538 distance. This means we need a Window that is at + // least 131074 bytes long so we have space to retrieve up to a full 64kb in lookback and place it in our buffer without + // overwriting existing data. OutputWindow requires that the WindowSize be an exponent of 2, so we round up to 2^18. + private const int WindowSize = 262144; + private const int WindowMask = 262143; + + private readonly byte[] _window = new byte[WindowSize]; // The window is 2^18 bytes + private int _end; // this is the position to where we should write next byte + private int _bytesUsed; // The number of bytes in the output window which is not consumed. + + /// Add a byte to output window. + public void Write(byte b) + { + Debug.Assert(_bytesUsed < WindowSize, "Can't add byte when window is full!"); + _window[_end++] = b; + _end &= WindowMask; + ++_bytesUsed; + } + + public void WriteLengthDistance(int length, int distance) + { + Debug.Assert((_bytesUsed + length) <= WindowSize, "No Enough space"); + + // move backwards distance bytes in the output stream, + // and copy length bytes from this position to the output stream. + _bytesUsed += length; + int copyStart = (_end - distance) & WindowMask; // start position for coping. + + int border = WindowSize - length; + if (copyStart <= border && _end < border) + { + if (length <= distance) + { + Array.Copy(_window, copyStart, _window, _end, length); + _end += length; + } + else + { + // The referenced string may overlap the current + // position; for example, if the last 2 bytes decoded have values + // X and Y, a string reference with + // adds X,Y,X,Y,X to the output stream. + while (length-- > 0) + { + _window[_end++] = _window[copyStart++]; + } + } + } + else + { + // copy byte by byte + while (length-- > 0) + { + _window[_end++] = _window[copyStart++]; + _end &= WindowMask; + copyStart &= WindowMask; + } + } + } + + /// + /// Copy up to length of bytes from input directly. + /// This is used for uncompressed block. + /// + public int CopyFrom(InputBuffer input, int length) + { + length = Math.Min(Math.Min(length, WindowSize - _bytesUsed), input.AvailableBytes); + int copied; + + // We might need wrap around to copy all bytes. + int tailLen = WindowSize - _end; + if (length > tailLen) + { + // copy the first part + copied = input.CopyTo(_window, _end, tailLen); + if (copied == tailLen) + { + // only try to copy the second part if we have enough bytes in input + copied += input.CopyTo(_window, 0, length - tailLen); + } + } + else + { + // only one copy is needed if there is no wrap around. + copied = input.CopyTo(_window, _end, length); + } + + _end = (_end + copied) & WindowMask; + _bytesUsed += copied; + return copied; + } + + /// Free space in output window. + public int FreeBytes => WindowSize - _bytesUsed; + + /// Bytes not consumed in output window. + public int AvailableBytes => _bytesUsed; + + /// Copy the decompressed bytes to output array. + public int CopyTo(byte[] output, int offset, int length) + { + int copy_end; + + if (length > _bytesUsed) + { + // we can copy all the decompressed bytes out + copy_end = _end; + length = _bytesUsed; + } + else + { + copy_end = (_end - _bytesUsed + length) & WindowMask; // copy length of bytes + } + + int copied = length; + + int tailLen = length - copy_end; + if (tailLen > 0) + { + // this means we need to copy two parts separately + // copy tailLen bytes from the end of output window + Array.Copy(_window, WindowSize - tailLen, + output, offset, tailLen); + offset += tailLen; + length = copy_end; + } + Array.Copy(_window, copy_end - length, output, offset, length); + _bytesUsed -= copied; + Debug.Assert(_bytesUsed >= 0, "check this function and find why we copied more bytes than we have"); + return copied; + } + } +} diff --git a/tests/SharpCompress.Test/TestBase.cs b/tests/SharpCompress.Test/TestBase.cs index 4547ad76..4cf938ec 100644 --- a/tests/SharpCompress.Test/TestBase.cs +++ b/tests/SharpCompress.Test/TestBase.cs @@ -30,6 +30,7 @@ namespace SharpCompress.Test yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd-.zip"); yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"); yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"); + yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate64.zip"); yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.dd.zip"); yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.zip"); yield return Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.zip"); diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 031698d8..a613abad 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -49,6 +49,11 @@ namespace SharpCompress.Test.Zip { ArchiveStreamRead("Zip.deflate.zip"); } + [Fact] + public void Zip_Deflate64_ArchiveStreamRead() + { + ArchiveStreamRead("Zip.deflate64.zip"); + } [Fact] public void Zip_LZMA_Streamed_ArchiveStreamRead() @@ -101,6 +106,11 @@ namespace SharpCompress.Test.Zip { ArchiveFileRead("Zip.deflate.zip"); } + [Fact] + public void Zip_Deflate64_ArchiveFileRead() + { + ArchiveFileRead("Zip.deflate64.zip"); + } [Fact] public void Zip_LZMA_Streamed_ArchiveFileRead() diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 4e264dcc..c1f7f701 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -101,6 +101,11 @@ namespace SharpCompress.Test.Zip { Read("Zip.deflate.zip", CompressionType.Deflate); } + [Fact] + public void Zip_Deflate64_Read() + { + Read("Zip.deflate64.zip", CompressionType.Deflate64); + } [Fact] public void Zip_LZMA_Streamed_Read() diff --git a/tests/TestArchives/Archives/Zip.deflate64.zip b/tests/TestArchives/Archives/Zip.deflate64.zip new file mode 100644 index 0000000000000000000000000000000000000000..2c2e89050361492e81b3404258348bf0a742c853 GIT binary patch literal 60747 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