///----------- ----------- ----------- ----------- ----------- ----------- ----------- /// /// Copyright (c) Microsoft Corporation. All rights reserved. /// /// /// gpaperin ///----------- ----------- ----------- ----------- ----------- ----------- ----------- using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Security; using ZErrorCode = System.IO.Compression.ZLibNative.ErrorCode; using ZFlushCode = System.IO.Compression.ZLibNative.FlushCode; namespace System.IO.Compression { #if FEATURE_NETCORE [SecuritySafeCritical] #endif internal class DeflaterZLib : IDeflater { private ZLibNative.ZLibStreamHandle _zlibStream; private GCHandle? _inputBufferHandle; private bool _isDisposed; // Note, DeflateStream or the deflater do not try to be thread safe. // The lock is just used to make writing to unmanaged structures atomic to make sure // that they do not get inconsistent fields that may lead to an unmanaged memory violation. // To prevent *managed* buffer corruption or other weird behaviour users need to synchronise // on the stream explicitly. private readonly Object syncLock = new Object(); #region exposed members internal DeflaterZLib() : this(CompressionLevel.Optimal) { } internal DeflaterZLib(CompressionLevel compressionLevel) { ZLibNative.CompressionLevel zlibCompressionLevel; int windowBits; int memLevel; ZLibNative.CompressionStrategy strategy; switch (compressionLevel) { // Note that ZLib currently exactly correspond to the optimal values. // However, we have determined the optimal values by intependent measurements across // a range of all possible ZLib parameters and over a set of different data. // We stress that by using explicitly the values obtained by the measurements rather than // ZLib defaults even if they happened to be the same. // For ZLib 1.2.3 we have (copied from ZLibNative.cs): // ZLibNative.CompressionLevel.DefaultCompression = 6; // ZLibNative.Deflate_DefaultWindowBits = -15; // ZLibNative.Deflate_DefaultMemLevel = 8; case CompressionLevel.Optimal: zlibCompressionLevel = (ZLibNative.CompressionLevel) 6; windowBits = -15; memLevel = 8; strategy = ZLibNative.CompressionStrategy.DefaultStrategy; break; case CompressionLevel.Fastest: zlibCompressionLevel = (ZLibNative.CompressionLevel) 1; windowBits = -15; memLevel = 8; strategy = ZLibNative.CompressionStrategy.DefaultStrategy; break; case CompressionLevel.NoCompression: zlibCompressionLevel = (ZLibNative.CompressionLevel) 0; windowBits = -15; memLevel = 7; strategy = ZLibNative.CompressionStrategy.DefaultStrategy; break; default: throw new ArgumentOutOfRangeException("compressionLevel"); } _isDisposed = false; DeflateInit(zlibCompressionLevel, windowBits, memLevel, strategy); } void IDisposable.Dispose() { Dispose(true); } [SecuritySafeCritical] protected virtual void Dispose(bool disposing) { if (disposing && !_isDisposed) { if (_inputBufferHandle.HasValue) DeallocateInputBufferHandle(); _zlibStream.Dispose(); _isDisposed = true; } } private bool NeedsInput() { // Convenience method to call NeedsInput privately without a cast. return ((IDeflater) this).NeedsInput(); } [SecuritySafeCritical] bool IDeflater.NeedsInput() { return 0 == _zlibStream.AvailIn; } [SecuritySafeCritical] void IDeflater.SetInput(byte[] inputBuffer, int startIndex, int count) { Contract.Assert(NeedsInput(), "We have something left in previous input!"); Contract.Assert(null != inputBuffer); Contract.Assert(startIndex >= 0 && count >= 0 && count + startIndex <= inputBuffer.Length); Contract.Assert(!_inputBufferHandle.HasValue); if (0 == count) return; lock (syncLock) { _inputBufferHandle = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned); _zlibStream.NextIn = _inputBufferHandle.Value.AddrOfPinnedObject() + startIndex; _zlibStream.AvailIn = (uint) count; } } [SecuritySafeCritical] int IDeflater.GetDeflateOutput(byte[] outputBuffer) { Contract.Ensures(Contract.Result() >= 0 && Contract.Result() <= outputBuffer.Length); Contract.Assert(null != outputBuffer, "Can't pass in a null output buffer!"); Contract.Assert(!NeedsInput(), "GetDeflateOutput should only be called after providing input"); Contract.Assert(_inputBufferHandle.HasValue); Contract.Assert(_inputBufferHandle.Value.IsAllocated); try { int bytesRead; ReadDeflateOutput(outputBuffer, ZFlushCode.NoFlush, out bytesRead); return bytesRead; } finally { // Before returning, make sure to release input buffer if necesary: if (0 == _zlibStream.AvailIn && _inputBufferHandle.HasValue) DeallocateInputBufferHandle(); } } private ZErrorCode ReadDeflateOutput(byte[] outputBuffer, ZFlushCode flushCode, out int bytesRead) { lock (syncLock) { GCHandle outputBufferHndl = GCHandle.Alloc(outputBuffer, GCHandleType.Pinned); try { _zlibStream.NextOut = outputBufferHndl.AddrOfPinnedObject(); _zlibStream.AvailOut = (uint) outputBuffer.Length; ZErrorCode errC = Deflate(flushCode); bytesRead = outputBuffer.Length - (int) _zlibStream.AvailOut; return errC; } finally { outputBufferHndl.Free(); } } } bool IDeflater.Finish(byte[] outputBuffer, out int bytesRead) { Contract.Assert(null != outputBuffer, "Can't pass in a null output buffer!"); Contract.Assert(NeedsInput(), "We have something left in previous input!"); Contract.Assert(!_inputBufferHandle.HasValue); // Note: we require that NeedsInput() == true, i.e. that 0 == _zlibStream.AvailIn. // If there is still input left we should never be getting here; instead we // should be calling GetDeflateOutput. ZErrorCode errC = ReadDeflateOutput(outputBuffer, ZFlushCode.Finish, out bytesRead); return errC == ZErrorCode.StreamEnd; } #endregion // exposed functions #region helpers & native call wrappers private void DeallocateInputBufferHandle() { Contract.Assert(_inputBufferHandle.HasValue); Contract.Assert(_inputBufferHandle.Value.IsAllocated); lock(syncLock) { _zlibStream.AvailIn = 0; _zlibStream.NextIn = ZLibNative.ZNullPtr; _inputBufferHandle.Value.Free(); _inputBufferHandle = null; } } [SecuritySafeCritical] private void DeflateInit(ZLibNative.CompressionLevel compressionLevel, int windowBits, int memLevel, ZLibNative.CompressionStrategy strategy) { ZErrorCode errC; try { errC = ZLibNative.CreateZLibStreamForDeflate(out _zlibStream, compressionLevel, windowBits, memLevel, strategy); } catch (Exception cause) { throw new ZLibException(SR.GetString(SR.ZLibErrorDLLLoadError), cause); } switch (errC) { case ZErrorCode.Ok: return; case ZErrorCode.MemError: throw new ZLibException(SR.GetString(SR.ZLibErrorNotEnoughMemory), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); case ZErrorCode.VersionError: throw new ZLibException(SR.GetString(SR.ZLibErrorVersionMismatch), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); case ZErrorCode.StreamError: throw new ZLibException(SR.GetString(SR.ZLibErrorIncorrectInitParameters), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); default: throw new ZLibException(SR.GetString(SR.ZLibErrorUnexpected), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); } } [SecuritySafeCritical] private ZErrorCode Deflate(ZFlushCode flushCode) { ZErrorCode errC; try { errC = _zlibStream.Deflate(flushCode); } catch (Exception cause) { throw new ZLibException(SR.GetString(SR.ZLibErrorDLLLoadError), cause); } switch (errC) { case ZErrorCode.Ok: case ZErrorCode.StreamEnd: return errC; case ZErrorCode.BufError: return errC; // This is a recoverable error case ZErrorCode.StreamError: throw new ZLibException(SR.GetString(SR.ZLibErrorInconsistentStream), "deflate", (int) errC, _zlibStream.GetErrorMessage()); default: throw new ZLibException(SR.GetString(SR.ZLibErrorUnexpected), "deflate", (int) errC, _zlibStream.GetErrorMessage()); } } #endregion // helpers & native call wrappers } // internal class DeflaterZLib } // namespace System.IO.Compression // file DeflaterZLib.cs