962 lines · 43.0 KB · C#
RawDownload
//******************************************************************************************************
//  PatternCompressor.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:
//  ----------------------------------------------------------------------------------------------------
//  10/27/2011 - J. Ritchie Carroll
//       Initial version of source generated.
//  01/25/2012 - Stephen C. Wills
//       Removed 64-bit compression routine, added 32-bit decompression routine.
//  01/26/2012 - Stephen C. Wills
//       Changed class to non-static so that compressor instances
//       can be created to handle stream compression.
//  12/14/2012 - Starlynn Danyelle Gilliam
//       Modified Header.
//
//******************************************************************************************************


using System;
using GSF.Parsing;

namespace GSF.IO.Compression
{
    /// <summary>
    /// Defines functions used for high-speed pattern compression against native types.
    /// </summary>
    public class PatternCompressor
    {
        #region [ Members ]

        // Fields
        private byte[] m_compressedBuffer;
        private int m_compressedBufferLength;
        private int m_maxCompressedBufferLength;

        private uint[] m_backBuffer;
        private int m_backBufferStart;
        private int m_backBufferLength;

        private byte m_compressionStrength;

        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Creates a new instance of the PatternCompressor with the given compression strength.
        /// </summary>
        /// <param name="compressionStrength">The compression strength of the PatternCompressor.</param>
        public PatternCompressor(byte compressionStrength = 5)
        {
            CompressionStrength = compressionStrength;
            m_maxCompressedBufferLength = -1;
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets or sets the buffer into which compressed values are written.
        /// </summary>
        /// <remarks>
        /// When setting the compressed buffer, the new buffer is assumed to be empty
        /// (<see cref="CompressedBufferLength"/> is set to zero).
        /// </remarks>
        public byte[] CompressedBuffer
        {
            get
            {
                return m_compressedBuffer;
            }
            set
            {
                m_compressedBuffer = value;
                m_compressedBufferLength = 0;
            }
        }

        /// <summary>
        /// Gets the amount of compressed data in the compressed buffer.
        /// </summary>
        public int CompressedBufferLength
        {
            get
            {
                return m_compressedBufferLength;
            }
        }

        /// <summary>
        /// Gets or sets the maximum compressed buffer length.
        /// </summary>
        /// <remarks>
        /// The compressor will not write beyond the MaxCompressedBufferLength.
        /// Defaults to the full length of the compressed buffer.
        /// Set to -1 to return to the default.
        /// </remarks>
        public int MaxCompressedBufferLength
        {
            get
            {
                if (m_maxCompressedBufferLength > 0)
                    return m_maxCompressedBufferLength;
                else if (m_compressedBuffer != null)
                    return m_compressedBuffer.Length;
                else
                    return 0;
            }
            set
            {
                m_maxCompressedBufferLength = value;
            }
        }

        /// <summary>
        /// Gets or sets the compression strength of the PatternCompressor.
        /// </summary>
        /// <remarks>
        /// When setting the compression strength, the PatternCompressor
        /// is automatically reset (the back buffer is emptied).
        /// </remarks>
        public byte CompressionStrength
        {
            get
            {
                return m_compressionStrength;
            }
            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;
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> cannot be null.</exception>
        public unsafe int Compress(ISupportBinaryImage value)
        {
            byte[] buffer;
            int bufferLength;

            int compressedSize = 0;
            byte* iter, end;

            if ((object)value == null)
                throw new ArgumentNullException("value cannot be null", "value");

            bufferLength = value.BinaryLength.AlignDoubleWord();
            buffer = new byte[bufferLength];

            fixed (byte* start = buffer)
            {
                value.GenerateBinaryImage(buffer, 0);
                end = start + bufferLength;

                for (iter = start; iter < end; iter += 4)
                    compressedSize += Compress(iter);
            }

            return compressedSize;
        }

        /// <summary>
        /// Compresses all of the data in the given <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">The buffer to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        /// <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 int Compress(byte[] buffer)
        {
            return Compress(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Compresses <paramref name="length"/> bytes of data in the given <paramref name="buffer"/>.
        /// </summary>
        /// <param name="buffer">The buffer to be compressed.</param>
        /// <param name="length">The amount of data to be compressed. Must be a multiple of four.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        /// <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 int Compress(byte[] buffer, int length)
        {
            return Compress(buffer, 0, length);
        }

        /// <summary>
        /// Compresses <paramref name="length"/> bytes of data in the given <paramref name="buffer"/>, starting at <paramref name="offset"/>.
        /// </summary>
        /// <param name="buffer">The buffer to be compressed.</param>
        /// <param name="offset">The amount of data to ignore at the start of the buffer.</param>
        /// <param name="length">The amount of data to be compressed. Must be a multiple of four.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        /// <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 int Compress(byte[] buffer, int offset, int length)
        {
            const int SizeOf32Bits = sizeof(uint);
            int compressedSize = 0;
            byte* start, iter, end;

            if ((object)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)
                    compressedSize += Compress(iter);
            }

            return compressedSize;
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(double value)
        {
            uint* pItem = (uint*)&value;
            int compressedSize = 0;

            compressedSize += Compress((byte*)pItem);
            compressedSize += Compress((byte*)(pItem + 1));

            return compressedSize;
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(float value)
        {
            return Compress((byte*)&value);
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(long value)
        {
            uint* pItem = (uint*)&value;
            int compressedSize = 0;

            compressedSize += Compress((byte*)pItem);
            compressedSize += Compress((byte*)(pItem + 1));

            return compressedSize;
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(ulong value)
        {
            uint* pItem = (uint*)&value;
            int compressedSize = 0;

            compressedSize += Compress((byte*)pItem);
            compressedSize += Compress((byte*)(pItem + 1));

            return compressedSize;
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(int value)
        {
            return Compress((byte*)&value);
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public unsafe int Compress(uint value)
        {
            return Compress((byte*)&value);
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public int Compress(short value)
        {
            return Compress((int)value);
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public int Compress(ushort value)
        {
            return Compress((uint)value);
        }

        /// <summary>
        /// Compresses the given value and places it in the compressed buffer.
        /// </summary>
        /// <param name="value">The value to be compressed.</param>
        /// <returns>The size, in bytes, of the compressed value.</returns>
        public int Compress(byte value)
        {
            return Compress((uint)value);
        }

        /// <summary>
        /// Sets <see cref="CompressedBufferLength"/> to zero and allows the
        /// compressor to write over the values in the compressed buffer.
        /// </summary>
        public void EmptyCompressedBuffer()
        {
            m_compressedBufferLength = 0;
        }

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

        // Helper method to compress a generic set of four
        // bytes, given a pointer to the first byte.
        private unsafe int Compress(byte* pValue)
        {
            uint value = *(uint*)pValue;
            int compressedSize;

            if (m_compressedBuffer == null)
                throw new InvalidOperationException("Cannot write compressed value to a null buffer");

            if (m_backBufferLength == 0)
                compressedSize = InsertFirstValue(value);
            else
                compressedSize = InsertCompressedValue(value);

            InsertIntoBackBuffer(value);
            return compressedSize;
        }

        // Inserts the given value into the compressed buffer as the first,
        // uncompressed value in the stream. This is called when the back
        // buffer is empty.
        private unsafe int InsertFirstValue(uint value)
        {
            // Size of the first value includes the 1-byte stream header
            const int FirstValueSize = 1 + sizeof(uint);

            // Ensure that the value will fit in the compressed buffer
            if (MaxCompressedBufferLength - m_compressedBufferLength < FirstValueSize)
                throw new InvalidOperationException("Value cannot fit in compressed buffer. Empty compressed buffer.");

            // Insert value into compressed buffer
            fixed (byte* pCompressedBuffer = m_compressedBuffer)
            {
                byte* compressedBufferEnd = pCompressedBuffer + m_compressedBufferLength;
                *compressedBufferEnd = (byte)(0xC0 | m_compressionStrength);
                *(uint*)(compressedBufferEnd + 1) = value;
                m_compressedBufferLength += FirstValueSize;
            }

            return FirstValueSize;
        }

        // Compresses the given value and inserts it into the compressed buffer.
        private unsafe int InsertCompressedValue(uint value)
        {
            byte* compressedBufferEnd;

            byte* pValue = (byte*)&value;
            byte decompressionKey = GetDecompressionKey(value);
            byte difference = (byte)(decompressionKey >> 5);

            int compressedSize = 1 + difference;

            if (MaxCompressedBufferLength - m_compressedBufferLength < compressedSize)
                throw new InvalidOperationException("Value cannot fit in compressed buffer. Empty compressed buffer.");

            fixed (byte* pCompressedBuffer = m_compressedBuffer)
            {
                compressedBufferEnd = pCompressedBuffer + m_compressedBufferLength;
                *compressedBufferEnd = decompressionKey;
                compressedBufferEnd++;

                if (!BitConverter.IsLittleEndian)
                    pValue += sizeof(uint) - difference;

                for (int i = 0; i < difference; i++, compressedBufferEnd++, pValue++)
                    *compressedBufferEnd = *pValue;

                m_compressedBufferLength += compressedSize;
            }

            return compressedSize;
        }

        // 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;
            }
        }

        // Gets the 8-bit decompression key for the given value
        // based on the values that are in the back buffer.
        private unsafe byte GetDecompressionKey(uint value)
        {
            uint* backBufferIterator;

            uint result;
            byte difference;

            byte smallestDifference = 5;
            int backBufferIndex = 0;

            // Pin back buffer to navigate using pointer
            fixed (uint* pBackBuffer = m_backBuffer)
            {

Lines 1–500 of 962.

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