private static byte[] LZ77Compress(byte[] inputStream)
{
if (inputStream == null)
{
throw new ArgumentNullException("inputStream");
}
#region Consts
// The minimum match is 3 bytes. [MS-OXCRPC], section 3.1.4.11.1.2.1.4.
const int SizeOfMinimumMatch = 3;
// The maximum window size restricted by the metadata offset length (13 bytes). [MS-OXCRPC], section 3.1.4.11.1.2.2.3.
const int MaximumWindowSize = 8193;
// The size of bitmask. [MS-OXCRPC], section 3.1.4.11.1.2.2.1.
const int SizeOfBitMask = sizeof(uint);
// Indicates unused bits in bitmask are filled with 1.
const uint DefaultBitMaskFilling = 0xFFFFFFFF;
// Means the first 31 bytes are actual data. (1000 0000 0000 0000 0000 0000 0000 0000). Uses as the beginning of checking bitmask.
const uint BitMaskOf31ActualData = 0x80000000;
// The size of metadata. [MS-OXCRPC], section 3.1.4.11.1.2.2.3.
const int SizeOfMetadata = sizeof(short);
// The low-order three bits are the length. [MS-OXCRPC], section 3.1.4.11.1.2.2.4.
const int MetadataLengthBitLength = 3;
// The maximum value when all low-order three bits are "1". [MS-OXCRPC], section 3.1.4.11.1.2.2.4.
const int MetadataLengthFullBitsValue = 7;
// The shared byte with value 1111.
const byte SharedByteSetLow4Bits = 0xF;
// The next byte with value 11111111.
const byte NextByteSetAllBits = 0xFF;
// The size of final two bytes which is used to calculate the match length equal or greater than 280.
const int SizeOfFinalTwoBytes = sizeof(short);
// The increase of the length of output stream each time stream length is insufficient.
const int OutStreamLengthIncrease = 128;
// The maximum metadata length. It would be the first 2 bytes, 1 shared byte, 1 additional byte, and the final 2 bytes, section 3.1.4.11.1.2.2.4.
const int MaximumMetadataLength = 6;
#endregion
#region Variables
// The position of the byte in the input stream that is currently being coded (the beginning of the lookahead buffer).
int codingPosition;
// The starting position of the window in the input stream.
int windowStartingPosition;
// The position of the byte in the output stream where the data byte or metadata is being written.
int outBytesPosition;
// To distinguish data from metadata in the compressed byte stream. [MS-OXCRPC], section 3.1.4.11.1.2.2.1.
uint bitMask;
// Indicates the bit representing the next byte to be processed is "1".
uint bitMaskPointer;
// The position of the bitmask in the output stream.
int outBitMaskPostion;
// The position of the shared byte in the output stream. After the high-order nibble of the byte is used, this value is set to "-1" to indicate it needs to be set to a new position next time.
int sharedBytePosition;
#endregion
int size = inputStream.Length;
byte[] outStream = new byte[size];
// Set the coding position to the beginning of the input stream.
codingPosition = 0;
windowStartingPosition = 0;
outBytesPosition = 0;
outBitMaskPostion = 0;
bitMaskPointer = 0;
sharedBytePosition = -1;
while (codingPosition < size)
{
// Enlarge output stream length to ensure it's sufficient.
if (outBytesPosition + MaximumMetadataLength + SizeOfBitMask > outStream.Length)
{
Array.Resize<byte>(ref outStream, outStream.Length + OutStreamLengthIncrease);
}
// Move to the next bitmask if all bits in current bitmask are set.
if (bitMaskPointer == 0)
{
outBitMaskPostion = outBytesPosition;
Array.Copy(BitConverter.GetBytes(DefaultBitMaskFilling), 0, outStream, outBitMaskPostion, SizeOfBitMask);
outBytesPosition += SizeOfBitMask;
bitMaskPointer = BitMaskOf31ActualData;
}
// Find the longest match in the window for the lookahead buffer.
int matchLength = 0;
int matchOffset = 0;
for (int matchOffsetPosition = windowStartingPosition; matchOffsetPosition < codingPosition - matchLength; matchOffsetPosition++)
{
if (inputStream[codingPosition] == inputStream[matchOffsetPosition])
{
int currentMatchLength = 1;
while (currentMatchLength < size - codingPosition - 1)
{
if (inputStream[codingPosition + currentMatchLength] == inputStream[matchOffsetPosition + currentMatchLength])
{
currentMatchLength++;
}
else
{
break;
}
}
if (currentMatchLength >= matchLength)
{
matchLength = currentMatchLength;
matchOffset = codingPosition - matchOffsetPosition;
}
}
}
// Output the data byte or metadata with DIRECT2 encoding.
if (matchLength < SizeOfMinimumMatch)
{
// Next one is not metadata. Set bitmask bit to '0'.
bitMask = BitConverter.ToUInt32(outStream, outBitMaskPostion);
bitMask &= ~bitMaskPointer;
Array.Copy(BitConverter.GetBytes(bitMask), 0, outStream, outBitMaskPostion, sizeof(uint));
bitMaskPointer >>= 1;
// Fill data byte in output stream.
outStream[outBytesPosition] = inputStream[codingPosition];
outBytesPosition++;
codingPosition++;
}
else
{
// Next one is metadata. Set bitmask bit to '1'.
bitMask = BitConverter.ToUInt32(outStream, outBitMaskPostion);
bitMask |= bitMaskPointer;
Array.Copy(BitConverter.GetBytes(bitMask), 0, outStream, outBitMaskPostion, sizeof(uint));
bitMaskPointer >>= 1;
// Fill metadata offset and length in output stream.
// Use the high-order 13 bits in metadata bytes to store metadata offset.
matchOffset--;
int remainningMatchLength = matchLength - SizeOfMinimumMatch;
if (remainningMatchLength < MetadataLengthFullBitsValue)
{
// Use the low-order bits in metadata bytes to represent metadata length.
short metadata = (short)((matchOffset << MetadataLengthBitLength) + remainningMatchLength);
Array.Copy(BitConverter.GetBytes(metadata), 0, outStream, outBytesPosition, SizeOfMetadata);
outBytesPosition += SizeOfMetadata;
}
else
{
short metadata = (short)((matchOffset << MetadataLengthBitLength) + MetadataLengthFullBitsValue);
Array.Copy(BitConverter.GetBytes(metadata), 0, outStream, outBytesPosition, SizeOfMetadata);
outBytesPosition += SizeOfMetadata;
remainningMatchLength -= MetadataLengthFullBitsValue;
if (remainningMatchLength < (int)SharedByteSetLow4Bits)
{
// Additionally use the low-order or high-order nibble in shared byte to represent metadata length.
if (sharedBytePosition < 0)
{
sharedBytePosition = outBytesPosition++;
outStream[sharedBytePosition] = (byte)remainningMatchLength;
}
else
{
outStream[sharedBytePosition] += (byte)(remainningMatchLength << 4);
sharedBytePosition = -1;
}
}
else
{
if (sharedBytePosition < 0)
{
sharedBytePosition = outBytesPosition++;
outStream[sharedBytePosition] = SharedByteSetLow4Bits;
}
else
{
outStream[sharedBytePosition] += SharedByteSetLow4Bits << 4;
sharedBytePosition = -1;
}
remainningMatchLength -= (int)SharedByteSetLow4Bits;
if (remainningMatchLength < (int)NextByteSetAllBits)
{
// Additionally use another byte to represent metadata length.
outStream[outBytesPosition++] = (byte)remainningMatchLength;
}
else
{
outStream[outBytesPosition++] = NextByteSetAllBits;
// Use the final two bytes to represent metadata length.
Array.Copy(BitConverter.GetBytes((short)(matchLength - SizeOfMinimumMatch)), 0, outStream, outBytesPosition, SizeOfFinalTwoBytes);
outBytesPosition += SizeOfFinalTwoBytes;
}
}
}
// If the lookahead buffer is not empty, move the coding position (and the window) L bytes forward.
codingPosition += matchLength;
if (codingPosition - windowStartingPosition > MaximumWindowSize)
{
windowStartingPosition = codingPosition - MaximumWindowSize;
}
}
}
// Move to the next bitmask if all bits in current bitmask are set because additional bit "1" is needed as EOF.
if (bitMaskPointer == 0)
{
outBitMaskPostion = outBytesPosition;
Array.Copy(BitConverter.GetBytes(DefaultBitMaskFilling), 0, outStream, outBitMaskPostion, SizeOfBitMask);
outBytesPosition += SizeOfBitMask;
}
// Resize output stream
Array.Resize<byte>(ref outStream, outBytesPosition);
return outStream;
}