281 lines
9.7 KiB
C#
281 lines
9.7 KiB
C#
|
///----------- ----------- ----------- ----------- ----------- ----------- -----------
|
||
|
/// <copyright file="DeflaterZLib.cs" company="Microsoft">
|
||
|
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
/// </copyright>
|
||
|
///
|
||
|
/// <owner>gpaperin</owner>
|
||
|
///----------- ----------- ----------- ----------- ----------- ----------- -----------
|
||
|
|
||
|
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<int>() >= 0 && Contract.Result<int>() <= 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
|