// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // // Based on "libbzip2", Copyright (C) 1996-2007 Julian R Seward. // using System; using System.IO; using DiscUtils.Internal; using DiscUtils.Streams; namespace DiscUtils.Compression { /// /// Implementation of a BZip2 decoder. /// public sealed class BZip2DecoderStream : Stream { private readonly BitStream _bitstream; private readonly byte[] _blockBuffer; private uint _blockCrc; private readonly BZip2BlockDecoder _blockDecoder; private Crc32 _calcBlockCrc; private uint _calcCompoundCrc; private uint _compoundCrc; private Stream _compressedStream; private bool _eof; private readonly Ownership _ownsCompressed; private long _position; private BZip2RleStream _rleStream; /// /// Initializes a new instance of the BZip2DecoderStream class. /// /// The compressed input stream. /// Whether ownership of stream passes to the new instance. public BZip2DecoderStream(Stream stream, Ownership ownsStream) { _compressedStream = stream; _ownsCompressed = ownsStream; _bitstream = new BigEndianBitStream(new BufferedStream(stream)); // The Magic BZh byte[] magic = new byte[3]; magic[0] = (byte)_bitstream.Read(8); magic[1] = (byte)_bitstream.Read(8); magic[2] = (byte)_bitstream.Read(8); if (magic[0] != 0x42 || magic[1] != 0x5A || magic[2] != 0x68) { throw new InvalidDataException("Bad magic at start of stream"); } // The size of the decompression blocks in multiples of 100,000 int blockSize = (int)_bitstream.Read(8) - 0x30; if (blockSize < 1 || blockSize > 9) { throw new InvalidDataException("Unexpected block size in header: " + blockSize); } blockSize *= 100000; _rleStream = new BZip2RleStream(); _blockDecoder = new BZip2BlockDecoder(blockSize); _blockBuffer = new byte[blockSize]; if (ReadBlock() == 0) { _eof = true; } } /// /// Gets an indication of whether read access is permitted. /// public override bool CanRead { get { return true; } } /// /// Gets an indication of whether seeking is permitted. /// public override bool CanSeek { get { return false; } } /// /// Gets an indication of whether write access is permitted. /// public override bool CanWrite { get { return false; } } /// /// Gets the length of the stream (the capacity of the underlying buffer). /// public override long Length { get { throw new NotSupportedException(); } } /// /// Gets and sets the current position within the stream. /// public override long Position { get { return _position; } set { throw new NotSupportedException(); } } /// /// Flushes all data to the underlying storage. /// public override void Flush() { throw new NotSupportedException(); } /// /// Reads a number of bytes from the stream. /// /// The destination buffer. /// The start offset within the destination buffer. /// The number of bytes to read. /// The number of bytes read. public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (buffer.Length < offset + count) { throw new ArgumentException("Buffer smaller than declared"); } if (offset < 0) { throw new ArgumentException("Offset less than zero", nameof(offset)); } if (count < 0) { throw new ArgumentException("Count less than zero", nameof(count)); } if (_eof) { throw new IOException("Attempt to read beyond end of stream"); } if (count == 0) { return 0; } int numRead = _rleStream.Read(buffer, offset, count); if (numRead == 0) { // If there was an existing block, check it's crc. if (_calcBlockCrc != null) { if (_blockCrc != _calcBlockCrc.Value) { throw new InvalidDataException("Decompression failed - block CRC mismatch"); } _calcCompoundCrc = ((_calcCompoundCrc << 1) | (_calcCompoundCrc >> 31)) ^ _blockCrc; } // Read a new block (if any), if none - check the overall CRC before returning if (ReadBlock() == 0) { _eof = true; if (_calcCompoundCrc != _compoundCrc) { throw new InvalidDataException("Decompression failed - compound CRC"); } return 0; } numRead = _rleStream.Read(buffer, offset, count); } _calcBlockCrc.Process(buffer, offset, numRead); // Pre-read next block, so a client that knows the decompressed length will still // have the overall CRC calculated. if (_rleStream.AtEof) { // If there was an existing block, check it's crc. if (_calcBlockCrc != null) { if (_blockCrc != _calcBlockCrc.Value) { throw new InvalidDataException("Decompression failed - block CRC mismatch"); } } _calcCompoundCrc = ((_calcCompoundCrc << 1) | (_calcCompoundCrc >> 31)) ^ _blockCrc; if (ReadBlock() == 0) { _eof = true; if (_calcCompoundCrc != _compoundCrc) { throw new InvalidDataException("Decompression failed - compound CRC mismatch"); } return numRead; } } _position += numRead; return numRead; } /// /// Changes the current stream position. /// /// The origin-relative stream position. /// The origin for the stream position. /// The new stream position. public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } /// /// Sets the length of the stream (the underlying buffer's capacity). /// /// The new length of the stream. public override void SetLength(long value) { throw new NotSupportedException(); } /// /// Writes a buffer to the stream. /// /// The buffer to write. /// The starting offset within buffer. /// The number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } /// /// Releases underlying resources. /// /// Whether this method is called from Dispose. protected override void Dispose(bool disposing) { try { if (disposing) { if (_compressedStream != null && _ownsCompressed == Ownership.Dispose) { _compressedStream.Dispose(); } _compressedStream = null; if (_rleStream != null) { _rleStream.Dispose(); _rleStream = null; } } } finally { base.Dispose(disposing); } } private int ReadBlock() { ulong marker = ReadMarker(); if (marker == 0x314159265359) { int blockSize = _blockDecoder.Process(_bitstream, _blockBuffer, 0); _rleStream.Reset(_blockBuffer, 0, blockSize); _blockCrc = _blockDecoder.Crc; _calcBlockCrc = new Crc32BigEndian(Crc32Algorithm.Common); return blockSize; } if (marker == 0x177245385090) { _compoundCrc = ReadUint(); return 0; } throw new InvalidDataException("Found invalid marker in stream"); } private uint ReadUint() { uint val = 0; for (int i = 0; i < 4; ++i) { val = (val << 8) | _bitstream.Read(8); } return val; } private ulong ReadMarker() { ulong marker = 0; for (int i = 0; i < 6; ++i) { marker = (marker << 8) | _bitstream.Read(8); } return marker; } } }