/// <summary>Reads the next chunk from stream.</summary> /// <returns> /// <c>true</c> if next has been read, or <c>false</c> if it is legitimate end of file. /// Throws <see cref="EndOfStreamException" /> if end of stream was unexpected. /// </returns> private bool AcquireNextChunk() { do { ulong varint; if (!TryReadVarInt(out varint)) { return(false); } var flags = (ChunkFlags)varint; bool isCompressed = (flags & ChunkFlags.Compressed) != 0; var originalLength = (int)ReadVarInt(); int compressedLength = isCompressed ? (int)ReadVarInt() : originalLength; if (compressedLength > originalLength) { throw EndOfStream(); // corrupted } var compressed = new byte[compressedLength]; int chunk = ReadBlock(compressed, 0, compressedLength); if (chunk != compressedLength) { throw EndOfStream(); // corrupted } if (!isCompressed) { _buffer = compressed; // no compression on this chunk _bufferLength = compressedLength; } else { if (_buffer == null || _buffer.Length < originalLength) { _buffer = new byte[originalLength]; } int passes = (int)flags >> 2; if (passes != 0) { throw new NotSupportedException("Chunks with multiple passes are not supported."); } LZ4Codec.Decode(compressed, 0, compressedLength, _buffer, 0, originalLength, true); _bufferLength = originalLength; } _bufferOffset = 0; } while (_bufferLength == 0); // skip empty block (shouldn't happen but...) return(true); }
/// <summary> /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the /// position within the stream by the number of bytes read. /// </summary> /// <param name="buffer"> /// An array of bytes. When this method returns, the buffer contains the specified byte array with the /// values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced /// by the bytes read from the current source. /// </param> /// <param name="offset"> /// The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read /// from the current stream. /// </param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns> /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that /// many bytes are not currently available, or zero (0) if the end of the stream has been reached. /// </returns> public override int Read(byte[] buffer, int offset, int count) { if (!CanRead) { throw NotSupported("Read"); } int total = 0; while (count > 0) { int chunk = Math.Min(count, _bufferLength - _bufferOffset); if (chunk > 0) { #if INCLUDE_UNSAFE unsafe { fixed(byte *srcPtr = _buffer) { fixed(byte *dstPtr = buffer) { LZ4Codec.BlockCopy(srcPtr + _bufferOffset, dstPtr + offset, chunk); } } } #else Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, chunk); #endif _bufferOffset += chunk; offset += chunk; count -= chunk; total += chunk; } else { if (!AcquireNextChunk()) { break; } } } return(total); }
/// <summary> /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current /// position within this stream by the number of bytes written. /// </summary> /// <param name="buffer"> /// An array of bytes. This method copies <paramref name="count" /> bytes from /// <paramref name="buffer" /> to the current stream. /// </param> /// <param name="offset"> /// The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the /// current stream. /// </param> /// <param name="count">The number of bytes to be written to the current stream.</param> public override void Write(byte[] buffer, int offset, int count) { if (!CanWrite) { throw NotSupported("Write"); } if (_buffer == null) { _buffer = new byte[_blockSize]; _bufferLength = _blockSize; _bufferOffset = 0; } while (count > 0) { int chunk = Math.Min(count, _bufferLength - _bufferOffset); if (chunk > 0) { #if INCLUDE_UNSAFE unsafe { fixed(byte *srcPtr = buffer) { fixed(byte *dstPtr = _buffer) { LZ4Codec.BlockCopy(srcPtr + offset, dstPtr + _bufferOffset, chunk); } } } #else Buffer.BlockCopy(buffer, offset, _buffer, _bufferOffset, chunk); #endif offset += chunk; count -= chunk; _bufferOffset += chunk; } else { FlushCurrentChunk(); } } }
/// <summary>Flushes current chunk.</summary> private void FlushCurrentChunk() { if (_bufferOffset <= 0) { return; } var compressed = new byte[_bufferOffset]; int compressedLength = LZ4Codec.Encode(_buffer, 0, _bufferOffset, compressed, 0, _bufferOffset, _highCompression); if (compressedLength <= 0 || compressedLength >= _bufferOffset) { // incompressible block compressed = _buffer; compressedLength = _bufferOffset; } bool isCompressed = compressedLength < _bufferOffset; var flags = ChunkFlags.None; if (isCompressed) { flags |= ChunkFlags.Compressed; } if (_highCompression) { flags |= ChunkFlags.HighCompression; } WriteVarInt((ulong)flags); WriteVarInt((ulong)_bufferOffset); if (isCompressed) { WriteVarInt((ulong)compressedLength); } _innerStream.Write(compressed, 0, compressedLength); _bufferOffset = 0; }