1 ///----------- ----------- ----------- ----------- ----------- ----------- ----------- 2 /// <copyright file="DeflaterZLib.cs" company="Microsoft"> 3 /// Copyright (c) Microsoft Corporation. All rights reserved. 4 /// </copyright> 5 /// 6 /// <owner>gpaperin</owner> 7 ///----------- ----------- ----------- ----------- ----------- ----------- ----------- 8 9 using System.Diagnostics; 10 using System.Diagnostics.Contracts; 11 using System.Runtime.InteropServices; 12 using System.Security; 13 14 using ZErrorCode = System.IO.Compression.ZLibNative.ErrorCode; 15 using ZFlushCode = System.IO.Compression.ZLibNative.FlushCode; 16 17 18 namespace System.IO.Compression { 19 20 #if FEATURE_NETCORE 21 [SecuritySafeCritical] 22 #endif 23 internal class DeflaterZLib : IDeflater { 24 25 private ZLibNative.ZLibStreamHandle _zlibStream; 26 private GCHandle? _inputBufferHandle; 27 private bool _isDisposed; 28 29 // Note, DeflateStream or the deflater do not try to be thread safe. 30 // The lock is just used to make writing to unmanaged structures atomic to make sure 31 // that they do not get inconsistent fields that may lead to an unmanaged memory violation. 32 // To prevent *managed* buffer corruption or other weird behaviour users need to synchronise 33 // on the stream explicitly. 34 private readonly Object syncLock = new Object(); 35 36 #region exposed members 37 DeflaterZLib()38 internal DeflaterZLib() 39 40 : this(CompressionLevel.Optimal) { 41 } 42 DeflaterZLib(CompressionLevel compressionLevel)43 internal DeflaterZLib(CompressionLevel compressionLevel) { 44 45 ZLibNative.CompressionLevel zlibCompressionLevel; 46 int windowBits; 47 int memLevel; 48 ZLibNative.CompressionStrategy strategy; 49 50 switch (compressionLevel) { 51 52 // Note that ZLib currently exactly correspond to the optimal values. 53 // However, we have determined the optimal values by intependent measurements across 54 // a range of all possible ZLib parameters and over a set of different data. 55 // We stress that by using explicitly the values obtained by the measurements rather than 56 // ZLib defaults even if they happened to be the same. 57 // For ZLib 1.2.3 we have (copied from ZLibNative.cs): 58 // ZLibNative.CompressionLevel.DefaultCompression = 6; 59 // ZLibNative.Deflate_DefaultWindowBits = -15; 60 // ZLibNative.Deflate_DefaultMemLevel = 8; 61 62 63 case CompressionLevel.Optimal: 64 zlibCompressionLevel = (ZLibNative.CompressionLevel) 6; 65 windowBits = -15; 66 memLevel = 8; 67 strategy = ZLibNative.CompressionStrategy.DefaultStrategy; 68 break; 69 70 case CompressionLevel.Fastest: 71 zlibCompressionLevel = (ZLibNative.CompressionLevel) 1; 72 windowBits = -15; 73 memLevel = 8; 74 strategy = ZLibNative.CompressionStrategy.DefaultStrategy; 75 break; 76 77 case CompressionLevel.NoCompression: 78 zlibCompressionLevel = (ZLibNative.CompressionLevel) 0; 79 windowBits = -15; 80 memLevel = 7; 81 strategy = ZLibNative.CompressionStrategy.DefaultStrategy; 82 break; 83 84 default: 85 throw new ArgumentOutOfRangeException("compressionLevel"); 86 } 87 88 _isDisposed = false; 89 DeflateInit(zlibCompressionLevel, windowBits, memLevel, strategy); 90 } 91 IDisposable.Dispose()92 void IDisposable.Dispose() { 93 Dispose(true); 94 } 95 96 [SecuritySafeCritical] Dispose(bool disposing)97 protected virtual void Dispose(bool disposing) { 98 99 if (disposing && !_isDisposed) { 100 101 if (_inputBufferHandle.HasValue) 102 DeallocateInputBufferHandle(); 103 104 _zlibStream.Dispose(); 105 _isDisposed = true; 106 } 107 } 108 NeedsInput()109 private bool NeedsInput() { 110 // Convenience method to call NeedsInput privately without a cast. 111 return ((IDeflater) this).NeedsInput(); 112 } 113 114 [SecuritySafeCritical] IDeflater.NeedsInput()115 bool IDeflater.NeedsInput() { 116 return 0 == _zlibStream.AvailIn; 117 } 118 119 [SecuritySafeCritical] IDeflater.SetInput(byte[] inputBuffer, int startIndex, int count)120 void IDeflater.SetInput(byte[] inputBuffer, int startIndex, int count) { 121 122 Contract.Assert(NeedsInput(), "We have something left in previous input!"); 123 Contract.Assert(null != inputBuffer); 124 Contract.Assert(startIndex >= 0 && count >= 0 && count + startIndex <= inputBuffer.Length); 125 Contract.Assert(!_inputBufferHandle.HasValue); 126 127 if (0 == count) 128 return; 129 130 lock (syncLock) { 131 132 _inputBufferHandle = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned); 133 134 _zlibStream.NextIn = _inputBufferHandle.Value.AddrOfPinnedObject() + startIndex; 135 _zlibStream.AvailIn = (uint) count; 136 } 137 } 138 139 [SecuritySafeCritical] IDeflater.GetDeflateOutput(byte[] outputBuffer)140 int IDeflater.GetDeflateOutput(byte[] outputBuffer) { 141 142 Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= outputBuffer.Length); 143 144 Contract.Assert(null != outputBuffer, "Can't pass in a null output buffer!"); 145 Contract.Assert(!NeedsInput(), "GetDeflateOutput should only be called after providing input"); 146 Contract.Assert(_inputBufferHandle.HasValue); 147 Contract.Assert(_inputBufferHandle.Value.IsAllocated); 148 149 try { 150 int bytesRead; 151 ReadDeflateOutput(outputBuffer, ZFlushCode.NoFlush, out bytesRead); 152 return bytesRead; 153 154 } finally { 155 // Before returning, make sure to release input buffer if necesary: 156 if (0 == _zlibStream.AvailIn && _inputBufferHandle.HasValue) 157 DeallocateInputBufferHandle(); 158 } 159 } 160 ReadDeflateOutput(byte[] outputBuffer, ZFlushCode flushCode, out int bytesRead)161 private ZErrorCode ReadDeflateOutput(byte[] outputBuffer, ZFlushCode flushCode, out int bytesRead) { 162 163 lock (syncLock) { 164 165 GCHandle outputBufferHndl = GCHandle.Alloc(outputBuffer, GCHandleType.Pinned); 166 167 try { 168 169 170 _zlibStream.NextOut = outputBufferHndl.AddrOfPinnedObject(); 171 _zlibStream.AvailOut = (uint) outputBuffer.Length; 172 173 ZErrorCode errC = Deflate(flushCode); 174 bytesRead = outputBuffer.Length - (int) _zlibStream.AvailOut; 175 176 return errC; 177 178 } finally { 179 outputBufferHndl.Free(); 180 } 181 } 182 } 183 IDeflater.Finish(byte[] outputBuffer, out int bytesRead)184 bool IDeflater.Finish(byte[] outputBuffer, out int bytesRead) { 185 186 Contract.Assert(null != outputBuffer, "Can't pass in a null output buffer!"); 187 Contract.Assert(NeedsInput(), "We have something left in previous input!"); 188 Contract.Assert(!_inputBufferHandle.HasValue); 189 190 // Note: we require that NeedsInput() == true, i.e. that 0 == _zlibStream.AvailIn. 191 // If there is still input left we should never be getting here; instead we 192 // should be calling GetDeflateOutput. 193 194 ZErrorCode errC = ReadDeflateOutput(outputBuffer, ZFlushCode.Finish, out bytesRead); 195 return errC == ZErrorCode.StreamEnd; 196 } 197 198 #endregion // exposed functions 199 200 201 #region helpers & native call wrappers 202 DeallocateInputBufferHandle()203 private void DeallocateInputBufferHandle() { 204 205 Contract.Assert(_inputBufferHandle.HasValue); 206 Contract.Assert(_inputBufferHandle.Value.IsAllocated); 207 208 lock(syncLock) { 209 _zlibStream.AvailIn = 0; 210 _zlibStream.NextIn = ZLibNative.ZNullPtr; 211 _inputBufferHandle.Value.Free(); 212 _inputBufferHandle = null; 213 } 214 } 215 216 [SecuritySafeCritical] DeflateInit(ZLibNative.CompressionLevel compressionLevel, int windowBits, int memLevel, ZLibNative.CompressionStrategy strategy)217 private void DeflateInit(ZLibNative.CompressionLevel compressionLevel, int windowBits, int memLevel, 218 ZLibNative.CompressionStrategy strategy) { 219 220 ZErrorCode errC; 221 try { 222 errC = ZLibNative.CreateZLibStreamForDeflate(out _zlibStream, compressionLevel, 223 windowBits, memLevel, strategy); 224 } catch (Exception cause) { 225 throw new ZLibException(SR.GetString(SR.ZLibErrorDLLLoadError), cause); 226 } 227 228 switch (errC) { 229 230 case ZErrorCode.Ok: 231 return; 232 233 case ZErrorCode.MemError: 234 throw new ZLibException(SR.GetString(SR.ZLibErrorNotEnoughMemory), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); 235 236 case ZErrorCode.VersionError: 237 throw new ZLibException(SR.GetString(SR.ZLibErrorVersionMismatch), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); 238 239 case ZErrorCode.StreamError: 240 throw new ZLibException(SR.GetString(SR.ZLibErrorIncorrectInitParameters), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); 241 242 default: 243 throw new ZLibException(SR.GetString(SR.ZLibErrorUnexpected), "deflateInit2_", (int) errC, _zlibStream.GetErrorMessage()); 244 } 245 } 246 247 248 [SecuritySafeCritical] Deflate(ZFlushCode flushCode)249 private ZErrorCode Deflate(ZFlushCode flushCode) { 250 251 ZErrorCode errC; 252 try { 253 errC = _zlibStream.Deflate(flushCode); 254 } catch (Exception cause) { 255 throw new ZLibException(SR.GetString(SR.ZLibErrorDLLLoadError), cause); 256 } 257 258 switch (errC) { 259 260 case ZErrorCode.Ok: 261 case ZErrorCode.StreamEnd: 262 return errC; 263 264 case ZErrorCode.BufError: 265 return errC; // This is a recoverable error 266 267 case ZErrorCode.StreamError: 268 throw new ZLibException(SR.GetString(SR.ZLibErrorInconsistentStream), "deflate", (int) errC, _zlibStream.GetErrorMessage()); 269 270 default: 271 throw new ZLibException(SR.GetString(SR.ZLibErrorUnexpected), "deflate", (int) errC, _zlibStream.GetErrorMessage()); 272 } 273 } 274 275 #endregion // helpers & native call wrappers 276 277 } // internal class DeflaterZLib 278 } // namespace System.IO.Compression 279 280 // file DeflaterZLib.cs 281