//******************************************************************************************************
// PatternDecompressor.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 01/27/2012 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
namespace GSF.IO.Compression
{
///
/// Defines functions for decompression of data compressed by the PatternCompressor.
///
public class PatternDecompressor
{
#region [ Members ]
// Fields
private byte[] m_dataBuffer;
private int m_dataBufferOffset;
private int m_dataBufferEnd;
private uint[] m_backBuffer;
private int m_backBufferStart;
private int m_backBufferLength;
private byte m_compressionStrength;
#endregion
#region [ Properties ]
///
/// Gets the compression strength defined by the compressed stream.
///
public byte CompressionStrength
{
get
{
return m_compressionStrength;
}
private set
{
if (value > 31)
throw new ArgumentOutOfRangeException("value", "Compression strength must be 0 to 31");
if (m_backBuffer == null || m_backBuffer.Length < value + 1)
m_backBuffer = new uint[value + 1];
m_backBufferStart = 0;
m_backBufferLength = 0;
m_compressionStrength = value;
}
}
///
/// Gets the length of the data left in the buffer.
///
public int DataBufferLength
{
get
{
return m_dataBufferEnd - m_dataBufferOffset;
}
}
#endregion
#region [ Methods ]
///
/// Inserts the given data into the data buffer for decompression.
///
/// The data to be inserted into the data buffer.
public void AugmentBuffer(byte[] data)
{
AugmentBuffer(data, 0, data.Length);
}
///
/// Inserts the given data into the data buffer for decompression.
///
/// The data to be inserted into the data buffer.
/// The amount of data to be taken from the given buffer and placed in the data buffer.
public void AugmentBuffer(byte[] data, int dataLength)
{
AugmentBuffer(data, 0, dataLength);
}
///
/// Inserts the given data into the data buffer for decompression.
///
/// The data to be inserted into the data buffer.
/// The amount of data to be ignored at the beginning of the buffer.
/// The amount of data to be taken from the given buffer and placed in the data buffer.
public void AugmentBuffer(byte[] data, int offset, int dataLength)
{
int dataBufferLength = DataBufferLength;
int neededLength = dataBufferLength + dataLength;
byte[] temp;
int desiredLength;
// First check to see if the data will fit in the remaining available buffer space.
if (m_dataBuffer != null && dataLength < m_dataBuffer.Length - m_dataBufferEnd)
{
Buffer.BlockCopy(data, offset, m_dataBuffer, m_dataBufferEnd, dataLength);
m_dataBufferEnd += dataLength;
}
// Next, check to see if the data will fit in the full buffer space.
if (m_dataBuffer != null && neededLength < m_dataBuffer.Length)
{
Buffer.BlockCopy(m_dataBuffer, m_dataBufferOffset, m_dataBuffer, 0, dataBufferLength);
Buffer.BlockCopy(data, offset, m_dataBuffer, dataBufferLength, dataLength);
m_dataBufferOffset = 0;
m_dataBufferEnd = neededLength;
}
// Buffer is not large enough: reallocate.
if (m_dataBuffer != null && m_dataBuffer.Length < neededLength)
{
// Assume that the buffer will grow rather than shrink.
// Hopefully, this reduces the need for reallocation.
desiredLength = (int)(neededLength * 1.2);
temp = new byte[desiredLength];
Buffer.BlockCopy(m_dataBuffer, m_dataBufferOffset, temp, 0, dataBufferLength);
Buffer.BlockCopy(data, offset, temp, dataBufferLength, dataLength);
m_dataBuffer = temp;
m_dataBufferOffset = 0;
m_dataBufferEnd = neededLength;
}
// Buffer does not exist: allocate.
if (m_dataBuffer == null)
{
// Assume that the buffer will grow rather than shrink.
// Hopefully, this reduces the need for reallocation.
desiredLength = (int)(neededLength * 1.2);
m_dataBuffer = new byte[desiredLength];
Buffer.BlockCopy(data, offset, m_dataBuffer, dataBufferLength, dataLength);
m_dataBufferOffset = 0;
m_dataBufferEnd = dataLength;
}
}
///
/// Decompresses enough bytes of data to fill up the .
///
/// The buffer that holds the data.
/// cannot be null.
/// length must be a multiple of four.
public void Decompress(byte[] buffer)
{
Decompress(buffer, 0, buffer.Length);
}
///
/// Decompresses bytes of data and places it in the .
///
/// The buffer that holds the data.
/// The amount of data to be decompressed and written to the buffer. The value of this parameter must be a multiple of four.
/// cannot be null.
/// must be a multiple of four.
/// must be greater than or equal to zero.
/// exceeds array boundaries.
public void Decompress(byte[] buffer, int length)
{
Decompress(buffer, 0, length);
}
///
/// Decompresses bytes of data and places it in the starting at .
///
/// The buffer that holds the data.
/// The amount of data at the beginning of the buffer that will not be overwritten.
/// The amount of data to be decompressed and written to the buffer. The value of this parameter must be a multiple of four.
/// cannot be null.
/// must be a multiple of four.
/// must be greater than or equal to zero.
/// must be greater than or equal to zero.
/// exceeds array boundaries.
public unsafe void Decompress(byte[] buffer, int offset, int length)
{
const int SizeOf32Bits = sizeof(uint);
byte* start, iter, end;
if (buffer == null)
throw new ArgumentNullException("buffer", "Cannot write data to null buffer");
if (length % SizeOf32Bits != 0)
throw new ArgumentException("Buffer length must be a multiple of four", "length");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", "offset must be greater than or equal to zero");
if (length < 0)
throw new ArgumentOutOfRangeException("length", "length must be greater than or equal to zero");
if (buffer.Length < offset + length)
throw new ArgumentOutOfRangeException("length", "length exceeds buffer array boundaries");
fixed (byte* pBuffer = buffer)
{
start = pBuffer + offset;
end = start + length;
for (iter = start; iter < end; iter += 4)
*(uint*)iter = DecompressValue();
}
}
///
/// Decompresses eight bytes of data and writes the data into a 64-bit floating point number.
///
/// The 64-bit floating point number that the data is to be written to.
public unsafe void Decompress(out double value)
{
fixed (double* pValue = &value)
{
// High word and low word on big endian systems
uint* highWord = (uint*)pValue;
uint* lowWord = highWord + 1;
*highWord = DecompressValue();
*lowWord = DecompressValue();
}
}
///
/// Decompresses four bytes of data and writes the data into a 32-bit floating point number.
///
/// The 32-bit floating point number that the data is to be written to.
public unsafe void Decompress(out float value)
{
fixed (float* pValue = &value)
*(uint*)pValue = DecompressValue();
}
///
/// Decompresses eight bytes of data and writes the data into a 64-bit signed integer.
///
/// The 64-bit integer that the data is to be written to.
public unsafe void Decompress(out long value)
{
fixed (long* pValue = &value)
{
// High word and low word on big endian systems
uint* highWord = (uint*)pValue;
uint* lowWord = highWord + 1;
*highWord = DecompressValue();
*lowWord = DecompressValue();
}
}
///
/// Decompresses eight bytes of data and writes the data into a 64-bit unsigned integer.
///
/// The 64-bit integer that the data is to be written to.
[CLSCompliant(false)]
public unsafe void Decompress(out ulong value)
{
fixed (ulong* pValue = &value)
{
// High word and low word on big endian systems
uint* highWord = (uint*)pValue;
uint* lowWord = highWord + 1;
*highWord = DecompressValue();
*lowWord = DecompressValue();
}
}
///
/// Decompresses four bytes of data and writes the data into a 32-bit signed integer.
///
/// The 32-bit integer that the data is to be written to.
public unsafe void Decompress(out int value)
{
fixed (int* pValue = &value)
*(uint*)pValue = DecompressValue();
}
///
/// Decompresses four bytes of data and writes the data into a 32-bit unsigned integer.
///
/// The 32-bit integer that the data is to be written to.
[CLSCompliant(false)]
public void Decompress(out uint value)
{
value = DecompressValue();
}
///
/// Decompresses four bytes of data and writes the data into a 16-bit signed integer. The high-order bytes are discarded.
///
/// The 16-bit integer that the data is to be written to.
public void Decompress(out short value)
{
value = (short)DecompressValue();
}
///
/// Decompresses four bytes of data and writes the data into a 16-bit unsigned integer. The high-order bytes are discarded.
///
/// The 16-bit integer that the data is to be written to.
[CLSCompliant(false)]
public void Decompress(out ushort value)
{
value = (ushort)DecompressValue();
}
///
/// Decompresses four bytes of data and writes the data into an 8-bit integer. The high-order bytes are discarded.
///
/// The 8-bit integer that the data is to be written to.
public void Decompress(out byte value)
{
value = (byte)DecompressValue();
}
///
/// Resets the decompressor by flushing the back buffer. Subsequent calls
/// to the Decompress methods will not be decompressed using previously
/// decompressed values.
///
public void Reset()
{
m_backBufferStart = 0;
m_backBufferLength = 0;
}
///
/// Clears out the data buffer so that subsequent calls to the Decompress
/// methods do not use the data that was previously in the data buffer.
///
///
/// This method should not be called during normal operation, as data will
/// be lost and the compressed stream will likely be corrupted. Only call
/// this method if the stream has already been corrupted.
///
public void EmptyBuffer()
{
m_dataBufferOffset = 0;
m_dataBufferEnd = 0;
}
// Decompresses four bytes and returns them as a 32-bit unsigned integer.
private uint DecompressValue()
{
const int KeyMask = 0xE0;
const int Uncompressed = 0xE0;
const int ResetStream = 0xC0;
byte decompressionKey;
uint decompressedValue;
if (DataBufferLength < 1)
throw new InvalidOperationException("Unable to decompress: insufficient data in the buffer. Augment the data buffer.");
decompressionKey = m_dataBuffer[m_dataBufferOffset];
if ((decompressionKey & KeyMask) == Uncompressed)
throw new NotSupportedException("Stream decompression of uncompressed data");
if ((decompressionKey & KeyMask) == ResetStream)
{
// Decompression key indicates that this is
// the first, uncompressed value in the stream
Reset();
decompressedValue = GetFirstValue();
}
else
{
// Decompression key indicates that this value is compressed
decompressedValue = GetDecompressedValue();
}
// Insert the decompressed value into
// the back buffer for future lookups
InsertIntoBackBuffer(decompressedValue);
return decompressedValue;
}
// Gets the first four data bytes in the stream and returns them as a 32-bit unsigned integer.
private unsafe uint GetFirstValue()
{
// Size of the first value includes the 1-byte stream header
const int FirstValueSize = 1 + sizeof(uint);
const int CompressionStrengthMask = 0x1F;
byte* iter;
if (DataBufferLength < FirstValueSize)
throw new InvalidOperationException("Unable to decompress: insufficient data in the buffer. Augment the data buffer.");
fixed (byte* pDataBuffer = m_dataBuffer)
{
// Move the iterator up to the start of the data
iter = pDataBuffer + m_dataBufferOffset;
// Compression strength is found in the decompression key.
// This is necessary to properly initialize the back buffer.
CompressionStrength = (byte)(*iter & CompressionStrengthMask);
// Advance the buffer offset
m_dataBufferOffset += FirstValueSize;
// First value is uncompressed
return *(uint*)(iter + 1);
}
}
// Decompresses four bytes of data and returns them as a 32-bit unsigned integer.
private unsafe uint GetDecompressedValue()
{
const int SizeOf32Bits = sizeof(uint);
const int BackReferenceMask = 0x1F;
byte* iter;
byte decompressionKey;
byte difference, backBufferIndex;
byte* pValue;
uint value;
fixed (byte* pDataBuffer = m_dataBuffer)
{
// Move the iterator up to the start of the data
iter = pDataBuffer + m_dataBufferOffset;
pValue = (byte*)&value;
// Get the decompression key for this value
decompressionKey = *iter;
difference = (byte)(decompressionKey >> 5);
backBufferIndex = (byte)(decompressionKey & BackReferenceMask);
iter++;
if (DataBufferLength < 1 + difference)
throw new InvalidOperationException("Unable to decompress: insufficient data in the buffer. Augment the data buffer.");
// Initialize the value with data from the back buffer
value = m_backBuffer[backBufferIndex];
// If bytes are in big endian order, then low order bytes are right most in memory so skip ahead
if (!BitConverter.IsLittleEndian)
pValue += SizeOf32Bits - difference;
// Overwrite low order bytes with compressed data
for (int i = 0; i < difference; i++, pValue++, iter++)
*pValue = *iter;
// Advance the buffer offset and return the decompressed value
m_dataBufferOffset += 1 + difference;
return value;
}
}
// Inserts the value into the back buffer.
private void InsertIntoBackBuffer(uint value)
{
if (m_backBufferLength < m_compressionStrength + 1)
{
m_backBuffer[m_backBufferLength] = value;
m_backBufferLength++;
}
else
{
m_backBuffer[m_backBufferStart] = value;
m_backBufferStart++;
if (m_backBufferStart >= m_backBufferLength)
m_backBufferStart = 0;
}
}
#endregion
#region [ Static ]
// Static Methods
///
/// Given the size of a compressed buffer, provides the maximum possible size of the decompressed data.
///
/// Size of the compressed buffer.
/// The maximum possible size of the data after decompression.
public static int MaximumSizeDecompressed(int compressedLength)
{
return 4 * compressedLength;
}
///
/// Decompress a byte array containing a sequential list of compressed 32-bit structures (e.g., floating point numbers, integers or unsigned integers) using a patterned compression method.
///
/// The array containing compressed 32-bit values to be decompressed. Decompression will happen inline on this buffer.
/// An representing the start index of the byte array.
/// The number of bytes in the buffer that represents actual data.
/// The number of bytes available for use in the buffer; actual buffer length must be at least large enough to fit the maximum size of the decompressed data. See .
/// The new length of the buffer after compression, unless the data cannot be compressed. If the data cannot be compressed, the buffer will remain unchanged and zero will be returned.
/// buffer cannot be null.
/// must be greater than or equal to one.
/// must be at least as large as .
/// must be at least as large as is necessary to fit the maximum possible size of the decompressed data.
/// Actual length of buffer is less than specified .
///
///
/// Decompression is performed inline. The source buffer must be large enough
/// to contain the maximum possible size of the decompressed buffer. This
/// maximum size can be obtained by using the
/// method.
///
///
///
/// As an optimization this function is using pointers to native structures,
/// as such the endian order decoding and encoding of the values will always
/// be in the native endian order of the operating system.
///
///
public unsafe static int DecompressBuffer(byte[] source, int startIndex, int dataLength, int bufferLength)
{
const int SizeOf32Bits = sizeof(uint);
const int BackReferenceMask = 0x1F;
int maxSize = MaximumSizeDecompressed(dataLength);
byte compressionStrength = 5;
if (source == null)
throw new ArgumentNullException("source");
if (dataLength < 1)
throw new ArgumentOutOfRangeException("dataLength", "Data length must be greater than or equal to one");
if (bufferLength < dataLength)
throw new ArgumentOutOfRangeException("bufferLength", "Buffer length must be at least as large as original data length");
if (bufferLength < maxSize)
throw new ArgumentOutOfRangeException("bufferLength", "Buffer length must be at least as large as is necessary to fit the maximum possible size of the decompressed data");
if (source.Length < bufferLength)
throw new ArgumentOutOfRangeException("source", "Actual length of source buffer is less than specified buffer length");
if (source[0] < 0xE0)
{
// Data is compressed. Get the compression strength
compressionStrength = (byte)(source[0] & BackReferenceMask);
}
else
{
// Data is uncompressed. Remove the header and return
Buffer.BlockCopy(source, startIndex + 1, source, startIndex, dataLength - 1);
return dataLength - 1;
}
byte[] buffer = null;
int maxQueueLength = compressionStrength + 1;
uint* queue = stackalloc uint[maxQueueLength];
int queueLength = 0;
int usedLength = 0;
int queueStartIndex = 0;
try
{
// Grab a working buffer from the pool large enough to fit maximum possible decompressed size
buffer = BufferPool.TakeBuffer(maxSize);
// Pin buffers to be navigated so that .NET doesn't move them around
fixed (byte* pSource = source, pBuffer = buffer)
{
byte* sourceIndex = pSource + 1;
byte* bufferIndex = pBuffer;
uint firstValue = *(uint*)sourceIndex;
// Always add first value to the buffer and the back buffer as-is
*(uint*)bufferIndex = firstValue;
queue[0] = firstValue;
queueLength++;
// Advance pointers
bufferIndex += SizeOf32Bits;
sourceIndex += SizeOf32Bits;
// Starting with second item, begin decompression sequence
while ((int)(sourceIndex - pSource) < dataLength)
{
byte* workingIndex = bufferIndex;
byte decompressionKey = 0;
byte backReferenceIndex = 0;
int smallestDifference = 0;
// Get decompression key for the next value
decompressionKey = *sourceIndex;
sourceIndex++;
// Obtain smallest difference from bits 5 through 7 and back reference index from bits 0 through 4
smallestDifference = decompressionKey >> 5;
backReferenceIndex = (byte)(decompressionKey & BackReferenceMask);
// Ensure that we have enough remaining data in the source buffer to decompress the next value
if ((int)(sourceIndex - pSource) + smallestDifference > dataLength)
throw new IndexOutOfRangeException("Source buffer does not end on a value boundary");
// Copy value from back buffer
*(uint*)workingIndex = queue[backReferenceIndex];
// If bytes are in big endian order, then low order bytes are right most in memory so skip ahead
if (!BitConverter.IsLittleEndian)
workingIndex += SizeOf32Bits - smallestDifference;
// Add only needed bytes to the output buffer (maybe none!)
for (int j = 0; j < smallestDifference; j++, workingIndex++, sourceIndex++)
{
*workingIndex = *sourceIndex;
}
// Place decompressed value into the queue
if (queueLength < maxQueueLength)
{
queue[queueLength] = *(uint*)bufferIndex;
queueLength++;
}
else
{
queue[queueStartIndex] = *(uint*)bufferIndex;
queueStartIndex++;
if (queueStartIndex >= queueLength)
queueStartIndex = 0;
}
// Setup to decompress the next value
bufferIndex += SizeOf32Bits;
}
usedLength = (int)(bufferIndex - pBuffer);
// Overwrite source buffer with new compressed buffer
Buffer.BlockCopy(buffer, 0, source, startIndex, usedLength);
}
}
finally
{
// Return buffer to queue so it can be reused
if (buffer != null)
BufferPool.ReturnBuffer(buffer);
}
return usedLength;
}
#endregion
/////
///// Decompresses a series of bytes and parses a binary image using the decompressed data.
/////
///// The object whose binary image is to be decompressed and parsed.
//public unsafe void Decompress(ISupportBinaryImage value)
//{
// int binaryLength = value.BinaryLength;
// int bufferLength = Word.AlignDoubleWord(binaryLength);
// byte[] buffer = new byte[bufferLength];
// byte* iter, end;
// fixed (byte* start = buffer)
// {
// end = start + bufferLength;
// for (iter = start; iter < end; iter += 4)
// *(uint*)iter = DecompressValue();
// }
// value.ParseBinaryImage(buffer, 0, binaryLength);
//}
}
}