sdgilliammodified headerscd7231413 years agoHistory
701 lines · 30.6 KB · C#
RawDownload
//******************************************************************************************************
//  PatternDecompressor.cs - Gbtc
//
//  Copyright © 2012, 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.
//  12/14/2012 - Starlynn Danyelle Gilliam
//       Modified Header.
//
//******************************************************************************************************

using System;

namespace GSF.IO.Compression
{
    /// <summary>
    /// Defines functions for decompression of data compressed by the PatternCompressor.
    /// </summary>
    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 ]

        /// <summary>
        /// Gets the compression strength defined by the compressed stream.
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// Gets the length of the data left in the buffer.
        /// </summary>
        public int DataBufferLength
        {
            get
            {
                return m_dataBufferEnd - m_dataBufferOffset;
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// Inserts the given data into the data buffer for decompression.
        /// </summary>
        /// <param name="data">The data to be inserted into the data buffer.</param>
        public void AugmentBuffer(byte[] data)
        {
            AugmentBuffer(data, 0, data.Length);
        }

        /// <summary>
        /// Inserts the given data into the data buffer for decompression.
        /// </summary>
        /// <param name="data">The data to be inserted into the data buffer.</param>
        /// <param name="dataLength">The amount of data to be taken from the given buffer and placed in the data buffer.</param>
        public void AugmentBuffer(byte[] data, int dataLength)
        {
            AugmentBuffer(data, 0, dataLength);
        }

        /// <summary>
        /// Inserts the given data into the data buffer for decompression.
        /// </summary>
        /// <param name="data">The data to be inserted into the data buffer.</param>
        /// <param name="offset">The amount of data to be ignored at the beginning of the buffer.</param>
        /// <param name="dataLength">The amount of data to be taken from the given buffer and placed in the data buffer.</param>
        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;
            }
        }

        /// <summary>
        /// Decompresses enough bytes of data to fill up the <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">The buffer that holds the data.</param>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> cannot be null.</exception>
        /// <exception cref="ArgumentException"><paramref name="buffer"/> length must be a multiple of four.</exception>
        public void Decompress(byte[] buffer)
        {
            Decompress(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Decompresses <paramref name="length"/> bytes of data and places it in the <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">The buffer that holds the data.</param>
        /// <param name="length">The amount of data to be decompressed and written to the buffer. The value of this parameter must be a multiple of four.</param>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> cannot be null.</exception>
        /// <exception cref="ArgumentException"><paramref name="length"/> must be a multiple of four.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> must be greater than or equal to zero.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> exceeds <paramref name="buffer"/> array boundaries.</exception>
        public void Decompress(byte[] buffer, int length)
        {
            Decompress(buffer, 0, length);
        }

        /// <summary>
        /// Decompresses <paramref name="length"/> bytes of data and places it in the <paramref name="buffer"/> starting at <paramref name="offset"/>.
        /// </summary>
        /// <param name="buffer">The buffer that holds the data.</param>
        /// <param name="offset">The amount of data at the beginning of the buffer that will not be overwritten.</param>
        /// <param name="length">The amount of data to be decompressed and written to the buffer. The value of this parameter must be a multiple of four.</param>
        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> cannot be null.</exception>
        /// <exception cref="ArgumentException"><paramref name="length"/> must be a multiple of four.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> must be greater than or equal to zero.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> must be greater than or equal to zero.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> exceeds <paramref name="buffer"/> array boundaries.</exception>
        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();
            }
        }

        /// <summary>
        /// Decompresses eight bytes of data and writes the data into a 64-bit floating point number.
        /// </summary>
        /// <param name="value">The 64-bit floating point number that the data is to be written to.</param>
        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();
            }
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into a 32-bit floating point number.
        /// </summary>
        /// <param name="value">The 32-bit floating point number that the data is to be written to.</param>
        public unsafe void Decompress(out float value)
        {
            fixed (float* pValue = &value)
                *(uint*)pValue = DecompressValue();
        }

        /// <summary>
        /// Decompresses eight bytes of data and writes the data into a 64-bit signed integer.
        /// </summary>
        /// <param name="value">The 64-bit integer that the data is to be written to.</param>
        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();
            }
        }

        /// <summary>
        /// Decompresses eight bytes of data and writes the data into a 64-bit unsigned integer.
        /// </summary>
        /// <param name="value">The 64-bit integer that the data is to be written to.</param>
        [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();
            }
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into a 32-bit signed integer.
        /// </summary>
        /// <param name="value">The 32-bit integer that the data is to be written to.</param>
        public unsafe void Decompress(out int value)
        {
            fixed (int* pValue = &value)
                *(uint*)pValue = DecompressValue();
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into a 32-bit unsigned integer.
        /// </summary>
        /// <param name="value">The 32-bit integer that the data is to be written to.</param>
        [CLSCompliant(false)]
        public void Decompress(out uint value)
        {
            value = DecompressValue();
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into a 16-bit signed integer. The high-order bytes are discarded.
        /// </summary>
        /// <param name="value">The 16-bit integer that the data is to be written to.</param>
        public void Decompress(out short value)
        {
            value = (short)DecompressValue();
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into a 16-bit unsigned integer. The high-order bytes are discarded.
        /// </summary>
        /// <param name="value">The 16-bit integer that the data is to be written to.</param>
        [CLSCompliant(false)]
        public void Decompress(out ushort value)
        {
            value = (ushort)DecompressValue();
        }

        /// <summary>
        /// Decompresses four bytes of data and writes the data into an 8-bit integer. The high-order bytes are discarded.
        /// </summary>
        /// <param name="value">The 8-bit integer that the data is to be written to.</param>
        public void Decompress(out byte value)
        {
            value = (byte)DecompressValue();
        }

        /// <summary>
        /// Resets the decompressor by flushing the back buffer. Subsequent calls
        /// to the Decompress methods will not be decompressed using previously
        /// decompressed values.
        /// </summary>
        public void Reset()
        {
            m_backBufferStart = 0;
            m_backBufferLength = 0;
        }

        /// <summary>
        /// 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.
        /// </summary>
        /// <remarks>
        /// 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.
        /// </remarks>
        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;
            }
        }

Lines 1–500 of 701.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t