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