//****************************************************************************************************** // TsscEncoder.cs - Gbtc // // Copyright © 2016, 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 MIT License (MIT), 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/MIT // // 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: // ---------------------------------------------------------------------------------------------------- // 12/02/2016 - Steven E. Chisholm // Generated original version of source code. // //****************************************************************************************************** using System; using GSF.Collections; namespace GSF.TimeSeries.Transport.TSSC { /// /// An encoder for the TSSC protocol. /// public class TsscEncoder { const uint Bits28 = 0xFFFFFFFu; const uint Bits24 = 0xFFFFFFu; const uint Bits20 = 0xFFFFFu; const uint Bits16 = 0xFFFFu; const uint Bits12 = 0xFFFu; const uint Bits8 = 0xFFu; const uint Bits4 = 0xFu; const uint Bits0 = 0x0u; private byte[] m_data; private int m_position; private int m_lastPosition; private long m_prevTimestamp1; private long m_prevTimestamp2; private long m_prevTimeDelta1; private long m_prevTimeDelta2; private long m_prevTimeDelta3; private long m_prevTimeDelta4; private TsscPointMetadata m_lastPoint; private IndexedArray m_points; /// /// Creates a encoder for the TSSC protocol. /// public TsscEncoder() { Reset(); } /// /// Resets the TSSC Encoder to the initial state. /// /// /// TSSC is a stateful encoder that requires a state /// of the previous data to be maintained. Therefore, if /// the state ever becomes corrupt (out of order, dropped, corrupted, or duplicated) /// the state must be reset on both ends. /// public void Reset() { m_points = new IndexedArray(); m_lastPoint = new TsscPointMetadata(WriteBits, null, null); m_data = EmptyArray.Empty; m_position = 0; m_lastPosition = 0; ClearBitStream(); m_prevTimeDelta1 = long.MaxValue; m_prevTimeDelta2 = long.MaxValue; m_prevTimeDelta3 = long.MaxValue; m_prevTimeDelta4 = long.MaxValue; m_prevTimestamp1 = 0; m_prevTimestamp2 = 0; } /// /// Sets the internal buffer to write data to. /// /// /// /// public void SetBuffer(byte[] data, int startingPosition, int length) { data.ValidateParameters(startingPosition, length); ClearBitStream(); m_data = data; m_position = startingPosition; m_lastPosition = startingPosition + length; } /// /// Finishes the current block and returns position after the last byte written. /// /// public int FinishBlock() { BitStreamFlush(); return m_position; } /// /// Adds the supplied measurement to the stream. If the stream is full, /// this method returns false. /// /// the id /// the timestamp in ticks /// the quality /// the value /// true if successful, false otherwise. public unsafe bool TryAddMeasurement(ushort id, long timestamp, uint quality, float value) { //if there are fewer than 100 bytes available on the buffer //assume that we cannot add any more. if (m_lastPosition - m_position < 100) return false; TsscPointMetadata point = m_points[id]; if (point == null) { point = new TsscPointMetadata(WriteBits, null, null); point.PrevNextPointId1 = (ushort)(id + 1); m_points[id] = point; } //Note: since I will not know the incoming pointID. The most recent // measurement received will be the one that contains the // coding algorithm for this measurement. Since for the more part // measurements generally have some sort of sequence to them, // this still ends up being a good enough assumption. if (m_lastPoint.PrevNextPointId1 != id) { WritePointIdChange(id); } if (m_prevTimestamp1 != timestamp) { WriteTimestampChange(timestamp); } if (point.PrevQuality1 != quality) { WriteQualityChange(quality, point); } uint valueRaw = *(uint*)&value; if (point.PrevValue1 == valueRaw) { m_lastPoint.WriteCode(TsscCodeWords.Value1); } else if (point.PrevValue2 == valueRaw) { m_lastPoint.WriteCode(TsscCodeWords.Value2); point.PrevValue2 = point.PrevValue1; point.PrevValue1 = valueRaw; } else if (point.PrevValue3 == valueRaw) { m_lastPoint.WriteCode(TsscCodeWords.Value3); point.PrevValue3 = point.PrevValue2; point.PrevValue2 = point.PrevValue1; point.PrevValue1 = valueRaw; } else if (valueRaw == 0) { m_lastPoint.WriteCode(TsscCodeWords.ValueZero); point.PrevValue3 = point.PrevValue2; point.PrevValue2 = point.PrevValue1; point.PrevValue1 = 0; } else { uint bitsChanged = valueRaw ^ point.PrevValue1; if (bitsChanged <= Bits4) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR4); WriteBits((byte)bitsChanged & 15, 4); } else if (bitsChanged <= Bits8) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR8); m_data[m_position] = (byte)bitsChanged; m_position++; } else if (bitsChanged <= Bits12) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR12); WriteBits((byte)bitsChanged & 15, 4); m_data[m_position] = (byte)(bitsChanged >> 4); m_position++; } else if (bitsChanged <= Bits16) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR16); m_data[m_position] = (byte)bitsChanged; m_data[m_position + 1] = (byte)(bitsChanged >> 8); m_position = m_position + 2; } else if (bitsChanged <= Bits20) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR20); WriteBits((byte)bitsChanged & 15, 4); m_data[m_position] = (byte)(bitsChanged >> 4); m_data[m_position + 1] = (byte)(bitsChanged >> 12); m_position = m_position + 2; } else if (bitsChanged <= Bits24) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR24); m_data[m_position] = (byte)bitsChanged; m_data[m_position + 1] = (byte)(bitsChanged >> 8); m_data[m_position + 2] = (byte)(bitsChanged >> 16); m_position = m_position + 3; } else if (bitsChanged <= Bits28) { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR28); WriteBits((byte)bitsChanged & 15, 4); m_data[m_position] = (byte)(bitsChanged >> 4); m_data[m_position + 1] = (byte)(bitsChanged >> 12); m_data[m_position + 2] = (byte)(bitsChanged >> 20); m_position = m_position + 3; } else { m_lastPoint.WriteCode(TsscCodeWords.ValueXOR32); m_data[m_position] = (byte)bitsChanged; m_data[m_position + 1] = (byte)(bitsChanged >> 8); m_data[m_position + 2] = (byte)(bitsChanged >> 16); m_data[m_position + 3] = (byte)(bitsChanged >> 24); m_position = m_position + 4; } point.PrevValue3 = point.PrevValue2; point.PrevValue2 = point.PrevValue1; point.PrevValue1 = valueRaw; } m_lastPoint = point; return true; } private void WritePointIdChange(ushort id) { uint bitsChanged = (uint)(id ^ m_lastPoint.PrevNextPointId1); if (bitsChanged <= Bits4) { m_lastPoint.WriteCode(TsscCodeWords.PointIDXOR4); WriteBits((byte)bitsChanged & 15, 4); } else if (bitsChanged <= Bits8) { m_lastPoint.WriteCode(TsscCodeWords.PointIDXOR8); m_data[m_position] = (byte)bitsChanged; m_position++; } else if (bitsChanged <= Bits12) { m_lastPoint.WriteCode(TsscCodeWords.PointIDXOR12); WriteBits((byte)bitsChanged & 15, 4); m_data[m_position] = (byte)(bitsChanged >> 4); m_position++; } else { m_lastPoint.WriteCode(TsscCodeWords.PointIDXOR16); m_data[m_position] = (byte)bitsChanged; m_data[m_position + 1] = (byte)(bitsChanged >> 8); m_position += 2; } m_lastPoint.PrevNextPointId1 = id; } private void WriteTimestampChange(long timestamp) { if (m_prevTimestamp2 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.Timestamp2); } else if (m_prevTimestamp1 < timestamp) { if (m_prevTimestamp1 + m_prevTimeDelta1 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta1Forward); } else if (m_prevTimestamp1 + m_prevTimeDelta2 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta2Forward); } else if (m_prevTimestamp1 + m_prevTimeDelta3 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta3Forward); } else if (m_prevTimestamp1 + m_prevTimeDelta4 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta4Forward); } else { m_lastPoint.WriteCode(TsscCodeWords.TimeXOR7Bit); Encoding7Bit.Write(m_data, ref m_position, (ulong)(timestamp ^ m_prevTimestamp1)); } } else { if (m_prevTimestamp1 - m_prevTimeDelta1 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta1Reverse); } else if (m_prevTimestamp1 - m_prevTimeDelta2 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta2Reverse); } else if (m_prevTimestamp1 - m_prevTimeDelta3 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta3Reverse); } else if (m_prevTimestamp1 - m_prevTimeDelta4 == timestamp) { m_lastPoint.WriteCode(TsscCodeWords.TimeDelta4Reverse); } else { m_lastPoint.WriteCode(TsscCodeWords.TimeXOR7Bit); Encoding7Bit.Write(m_data, ref m_position, (ulong)(timestamp ^ m_prevTimestamp1)); } } //Save the smallest delta time long minDelta = Math.Abs(m_prevTimestamp1 - timestamp); if (minDelta < m_prevTimeDelta4 && minDelta != m_prevTimeDelta1 && minDelta != m_prevTimeDelta2 && minDelta != m_prevTimeDelta3) { if (minDelta < m_prevTimeDelta1) { m_prevTimeDelta4 = m_prevTimeDelta3; m_prevTimeDelta3 = m_prevTimeDelta2; m_prevTimeDelta2 = m_prevTimeDelta1; m_prevTimeDelta1 = minDelta; } else if (minDelta < m_prevTimeDelta2) { m_prevTimeDelta4 = m_prevTimeDelta3; m_prevTimeDelta3 = m_prevTimeDelta2; m_prevTimeDelta2 = minDelta; } else if (minDelta < m_prevTimeDelta3) { m_prevTimeDelta4 = m_prevTimeDelta3; m_prevTimeDelta3 = minDelta; } else { m_prevTimeDelta4 = minDelta; } } m_prevTimestamp2 = m_prevTimestamp1; m_prevTimestamp1 = timestamp; } private void WriteQualityChange(uint quality, TsscPointMetadata point) { if (point.PrevQuality2 == quality) { m_lastPoint.WriteCode(TsscCodeWords.Quality2); } else { m_lastPoint.WriteCode(TsscCodeWords.Quality7Bit32); Encoding7Bit.Write(m_data, ref m_position, quality); } point.PrevQuality2 = point.PrevQuality1; point.PrevQuality1 = quality; } #region [ Bit Stream ] /// /// The position in m_buffer where the bit stream should be flushed /// -1 means no bit stream position has been assigned. /// private int m_bitStreamBufferIndex; /// /// The number of bits in m_bitStreamCache that are valid. 0 Means the bitstream is empty. /// private int m_bitStreamCacheBitCount; /// /// A cache of bits that need to be flushed to m_buffer when full. Bits filled starting from the right moving left. /// private int m_bitStreamCache; /// /// Resets the stream so it can be reused. All measurements must be registered again. /// private void ClearBitStream() { m_bitStreamBufferIndex = -1; m_bitStreamCacheBitCount = 0; m_bitStreamCache = 0; } private void WriteBits(int code, int len) { if (m_bitStreamBufferIndex < 0) { m_bitStreamBufferIndex = m_position++; } m_bitStreamCache = (m_bitStreamCache << len) | code; m_bitStreamCacheBitCount += len; if (m_bitStreamCacheBitCount > 7) { BitStreamEnd(); } } private void BitStreamFlush() { if (m_bitStreamCacheBitCount > 0) { if (m_bitStreamBufferIndex < 0) { m_bitStreamBufferIndex = m_position++; } m_lastPoint.WriteCode(TsscCodeWords.EndOfStream); if (m_bitStreamCacheBitCount > 7) { BitStreamEnd(); } if (m_bitStreamCacheBitCount > 0) { //Make up 8 bits by padding. m_bitStreamCache <<= 8 - m_bitStreamCacheBitCount; m_data[m_bitStreamBufferIndex] = (byte)m_bitStreamCache; m_bitStreamCache = 0; m_bitStreamBufferIndex = -1; m_bitStreamCacheBitCount = 0; } } } private void BitStreamEnd() { while (m_bitStreamCacheBitCount > 7) { m_data[m_bitStreamBufferIndex] = (byte)(m_bitStreamCache >> (m_bitStreamCacheBitCount - 8)); m_bitStreamCacheBitCount -= 8; if (m_bitStreamCacheBitCount > 0) { m_bitStreamBufferIndex = m_position++; } else { m_bitStreamBufferIndex = -1; } } } #endregion } }