public void Decompress(Stream source, Stream sink) { if (source == null) throw new ArgumentNullException("source"); if (sink == null) throw new ArgumentNullException("sink"); Invariant.Assert(source.CanRead); Invariant.Assert(sink.CanWrite, "Logic Error - Cannot decompress into a read-only stream"); // remember this for later long storedPosition = -1; try { if (source.CanSeek) { storedPosition = source.Position; source.Position = 0; } if (sink.CanSeek) sink.Position = 0; // zlib state UnsafeNativeMethods.ZStream zStream = new UnsafeNativeMethods.ZStream(); // initialize the zlib library UnsafeNativeMethods.ZLib.ErrorCode retVal = 0; retVal = UnsafeNativeMethods.ZLib.ums_inflate_init(ref zStream, UnsafeNativeMethods.ZLib.ZLibVersion, Marshal.SizeOf(zStream)); ThrowIfZLibError(retVal); byte[] sourceBuf = null; // source buffer byte[] sinkBuf = null; // destination buffer - where to write data GCHandle gcSourceBuf = new GCHandle(); // Preallocate these so we can safely access them GCHandle gcSinkBuf = new GCHandle(); // in the next finally block. try { // read all available data // each block is preceded by a header that is 3 ulongs int uncompressedSize, compressedSize; long destStreamLength = 0; // keep track of decompressed size while (ReadBlockHeader(source, out uncompressedSize, out compressedSize)) { // ensure we have space AllocOrRealloc(compressedSize, ref sourceBuf, ref gcSourceBuf); AllocOrRealloc(uncompressedSize, ref sinkBuf, ref gcSinkBuf); // read the data into the sourceBuf int bytesRead = PackagingUtilities.ReliableRead(source, sourceBuf, 0, compressedSize); if (bytesRead > 0) { if (compressedSize != bytesRead) throw new FileFormatException(SR.Get(SRID.CorruptStream)); // prepare structure // The buffer pointers must be reset for every call // because ums_inflate modifies them zStream.pInBuf = gcSourceBuf.AddrOfPinnedObject(); zStream.pOutBuf = gcSinkBuf.AddrOfPinnedObject(); zStream.cbIn = (uint)bytesRead; // this is number of bytes available for decompression at pInBuf and is updated by ums_deflate call zStream.cbOut = (uint)sinkBuf.Length; // this is the number of bytes free in pOutBuf and is updated by ums_deflate call // InvokeZLib does the actual interop. It updates zStream, and sinkBuf (sourceBuf passed by ref to avoid copying) // and leaves the decompressed data in sinkBuf. // int decompressedSize = InvokeZLib(bytesRead, ref zStream, ref sourceBuf, ref sinkBuf, pSource, pSink, false); retVal = UnsafeNativeMethods.ZLib.ums_inflate(ref zStream, (int)UnsafeNativeMethods.ZLib.FlushCodes.SyncFlush); ThrowIfZLibError(retVal); checked { int decompressedSize = sinkBuf.Length - (int)zStream.cbOut; // verify that data matches header if (decompressedSize != uncompressedSize) throw new FileFormatException(SR.Get(SRID.CorruptStream)); destStreamLength += decompressedSize; // write to the base stream sink.Write(sinkBuf, 0, decompressedSize); } } else { // block header but no block data if (compressedSize != 0) throw new FileFormatException(SR.Get(SRID.CorruptStream)); } } // make sure we truncate if the destination stream was longer than this current decompress if (sink.CanSeek) sink.SetLength(destStreamLength); } finally { if (gcSourceBuf.IsAllocated) gcSourceBuf.Free(); if (gcSinkBuf.IsAllocated) gcSinkBuf.Free(); } } finally { // seek to the current logical position before returning if (source.CanSeek) source.Position = storedPosition; } }
public void Compress(Stream source, Stream sink) { if (source == null) throw new ArgumentNullException("source"); if (sink == null) throw new ArgumentNullException("sink"); Invariant.Assert(source.CanRead); Invariant.Assert(sink.CanWrite, "Logic Error - Cannot compress into a read-only stream"); // remember this for later if possible long storedPosition = -1; // default to illegal value to catch any logic errors try { int sourceBufferSize; // don't allocate 4k for really tiny source streams if (source.CanSeek) { storedPosition = source.Position; source.Position = 0; // Casting result to int is safe because _defaultBlockSize is very small and the result // of Math.Min(x, _defaultBlockSize) must be no larger than _defaultBlockSize. sourceBufferSize = (int)(Math.Min(source.Length, (long)_defaultBlockSize)); } else sourceBufferSize = _defaultBlockSize; // can't call Length so fallback to default if (sink.CanSeek) sink.Position = 0; // zlib state UnsafeNativeMethods.ZStream zStream = new UnsafeNativeMethods.ZStream(); // initialize the zlib library UnsafeNativeMethods.ZLib.ErrorCode retVal = UnsafeNativeMethods.ZLib.ums_deflate_init(ref zStream, _compressionLevel, UnsafeNativeMethods.ZLib.ZLibVersion, Marshal.SizeOf(zStream)); ThrowIfZLibError(retVal); // where to write data - can actually grow if data is uncompressible long destStreamLength = 0; byte[] sourceBuf = null; // source buffer byte[] sinkBuf = null; // destination buffer GCHandle gcSourceBuf = new GCHandle(); GCHandle gcSinkBuf = new GCHandle(); try { // allocate managed buffers AllocOrRealloc(sourceBufferSize, ref sourceBuf, ref gcSourceBuf); AllocOrRealloc(_defaultBlockSize + (_defaultBlockSize >> 1), ref sinkBuf, ref gcSinkBuf); // while (more data is available) // - read into the sourceBuf // - compress into the sinkBuf // - emit the header // - write out to the _baseStream // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by us, therefore we cannot // close the BinaryWriter as it will Close the stream underneath. #pragma warning disable 6518 BinaryWriter writer = new BinaryWriter(sink); int bytesRead; while ((bytesRead = PackagingUtilities.ReliableRead(source, sourceBuf, 0, sourceBuf.Length)) > 0) { Invariant.Assert(bytesRead <= sourceBufferSize); // prepare structure // these pointers must be re-assigned for each loop because // ums_deflate modifies them zStream.pInBuf = gcSourceBuf.AddrOfPinnedObject(); zStream.pOutBuf = gcSinkBuf.AddrOfPinnedObject(); zStream.cbIn = (uint)bytesRead; // this is number of bytes available for compression at pInBuf and is updated by ums_deflate call zStream.cbOut = (uint)sinkBuf.Length; // this is the number of bytes free in pOutBuf and is updated by ums_deflate call // cast is safe because SyncFlush is a constant retVal = UnsafeNativeMethods.ZLib.ums_deflate(ref zStream, (int)UnsafeNativeMethods.ZLib.FlushCodes.SyncFlush); ThrowIfZLibError(retVal); checked { int compressedSize = sinkBuf.Length - (int)zStream.cbOut; Invariant.Assert(compressedSize > 0, "compressing non-zero bytes creates a non-empty block"); // This should never happen because our destination buffer // is twice as large as our source buffer Invariant.Assert(zStream.cbIn == 0, "Expecting all data to be compressed!"); // write the header writer.Write(_blockHeaderToken); // token writer.Write((UInt32)bytesRead); writer.Write((UInt32)compressedSize); destStreamLength += _headerBuf.Length; // write to the base stream sink.Write(sinkBuf, 0, compressedSize); destStreamLength += compressedSize; } } // post-compression // truncate if necessary if (sink.CanSeek) sink.SetLength(destStreamLength); } finally { if (gcSourceBuf.IsAllocated) gcSourceBuf.Free(); if (gcSinkBuf.IsAllocated) gcSinkBuf.Free(); } #pragma warning restore 6518 } finally { // seek to the current logical position before returning if (sink.CanSeek) source.Position = storedPosition; } }