//******************************************************************************************************
// TsscDecoder.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
{
///
/// The decoder for the TSSC protocol.
///
public class TsscDecoder
{
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 decoder for the TSSC protocol.
///
public TsscDecoder()
{
Reset();
}
///
/// Resets the TSSC Decoder 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(null, ReadBit, ReadBits5);
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 read data from.
///
///
///
///
public void SetBuffer(byte[] data, int startingPosition, int length)
{
data.ValidateParameters(startingPosition, length);
ClearBitStream();
m_data = data;
m_position = startingPosition;
m_lastPosition = startingPosition + length;
}
///
/// Reads the next measurement from the stream. If the end of the stream has been encountered,
/// return false.
///
/// the id
/// the timestamp in ticks
/// the quality
/// the value
/// true if successful, false otherwise.
public unsafe bool TryGetMeasurement(out ushort id, out long timestamp, out uint quality, out float value)
{
TsscPointMetadata nextPoint = null;
if (m_position == m_lastPosition && BitStreamIsEmpty)
{
ClearBitStream();
id = 0;
timestamp = 0;
quality = 0;
value = 0;
return false;
}
//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.
int code = m_lastPoint.ReadCode();
if (code == TsscCodeWords.EndOfStream)
{
ClearBitStream();
id = 0;
timestamp = 0;
quality = 0;
value = 0;
return false;
}
if (code <= TsscCodeWords.PointIDXOR16)
{
DecodePointID(code, m_lastPoint);
code = m_lastPoint.ReadCode();
if (code < TsscCodeWords.TimeDelta1Forward)
throw new Exception($"Expecting code >= {TsscCodeWords.TimeDelta1Forward} Received {code} at position {m_position} with last position { m_lastPosition}");
}
id = m_lastPoint.PrevNextPointId1;
nextPoint = m_points[m_lastPoint.PrevNextPointId1];
if (nextPoint == null)
{
nextPoint = new TsscPointMetadata(null, ReadBit, ReadBits5);
m_points[id] = nextPoint;
nextPoint.PrevNextPointId1 = (ushort)(id + 1);
}
if (code <= TsscCodeWords.TimeXOR7Bit)
{
timestamp = DecodeTimestamp(code);
code = m_lastPoint.ReadCode();
if (code < TsscCodeWords.Quality2)
throw new Exception($"Expecting code >= {TsscCodeWords.Quality2} Received {code} at position {m_position} with last position { m_lastPosition}");
}
else
{
timestamp = m_prevTimestamp1;
}
if (code <= TsscCodeWords.Quality7Bit32)
{
quality = DecodeQuality(code, nextPoint);
code = m_lastPoint.ReadCode();
if (code < TsscCodeWords.Value1)
throw new Exception($"Expecting code >= {TsscCodeWords.Value1} Received {code} at position {m_position} with last position { m_lastPosition}");
}
else
{
quality = nextPoint.PrevQuality1;
}
//Since value will almost always change,
//This is not put inside a function call.
uint valueRaw = 0;
if (code == TsscCodeWords.Value1)
{
valueRaw = nextPoint.PrevValue1;
}
else if (code == TsscCodeWords.Value2)
{
valueRaw = nextPoint.PrevValue2;
nextPoint.PrevValue2 = nextPoint.PrevValue1;
nextPoint.PrevValue1 = valueRaw;
}
else if (code == TsscCodeWords.Value3)
{
valueRaw = nextPoint.PrevValue3;
nextPoint.PrevValue3 = nextPoint.PrevValue2;
nextPoint.PrevValue2 = nextPoint.PrevValue1;
nextPoint.PrevValue1 = valueRaw;
}
else if (code == TsscCodeWords.ValueZero)
{
valueRaw = 0;
nextPoint.PrevValue3 = nextPoint.PrevValue2;
nextPoint.PrevValue2 = nextPoint.PrevValue1;
nextPoint.PrevValue1 = valueRaw;
}
else
{
switch (code)
{
case TsscCodeWords.ValueXOR4:
valueRaw = (uint)ReadBits4() ^ nextPoint.PrevValue1;
break;
case TsscCodeWords.ValueXOR8:
valueRaw = m_data[m_position] ^ nextPoint.PrevValue1;
m_position = m_position + 1;
break;
case TsscCodeWords.ValueXOR12:
valueRaw = (uint)ReadBits4() ^ (uint)(m_data[m_position] << 4) ^ nextPoint.PrevValue1;
m_position = m_position + 1;
break;
case TsscCodeWords.ValueXOR16:
valueRaw = m_data[m_position] ^ (uint)(m_data[m_position + 1] << 8) ^ nextPoint.PrevValue1;
m_position = m_position + 2;
break;
case TsscCodeWords.ValueXOR20:
valueRaw = (uint)ReadBits4() ^ (uint)(m_data[m_position] << 4) ^ (uint)(m_data[m_position + 1] << 12) ^ nextPoint.PrevValue1;
m_position = m_position + 2;
break;
case TsscCodeWords.ValueXOR24:
valueRaw = m_data[m_position] ^ (uint)(m_data[m_position + 1] << 8) ^ (uint)(m_data[m_position + 2] << 16) ^ nextPoint.PrevValue1;
m_position = m_position + 3;
break;
case TsscCodeWords.ValueXOR28:
valueRaw = (uint)ReadBits4() ^ (uint)(m_data[m_position] << 4) ^ (uint)(m_data[m_position + 1] << 12) ^ (uint)(m_data[m_position + 2] << 20) ^ nextPoint.PrevValue1;
m_position = m_position + 3;
break;
case TsscCodeWords.ValueXOR32:
valueRaw = m_data[m_position] ^ (uint)(m_data[m_position + 1] << 8) ^ (uint)(m_data[m_position + 2] << 16) ^ (uint)(m_data[m_position + 3] << 24) ^ nextPoint.PrevValue1;
m_position = m_position + 4;
break;
default:
throw new Exception($"Invalid code received {code} at position {m_position} with last position { m_lastPosition}");
}
nextPoint.PrevValue3 = nextPoint.PrevValue2;
nextPoint.PrevValue2 = nextPoint.PrevValue1;
nextPoint.PrevValue1 = valueRaw;
}
value = *(float*)&valueRaw;
m_lastPoint = nextPoint;
return true;
}
private void DecodePointID(int code, TsscPointMetadata lastPoint)
{
if (code == TsscCodeWords.PointIDXOR4)
{
lastPoint.PrevNextPointId1 ^= (ushort)ReadBits4();
}
else if (code == TsscCodeWords.PointIDXOR8)
{
lastPoint.PrevNextPointId1 ^= m_data[m_position++];
}
else if (code == TsscCodeWords.PointIDXOR12)
{
lastPoint.PrevNextPointId1 ^= (ushort)ReadBits4();
lastPoint.PrevNextPointId1 ^= (ushort)(m_data[m_position++] << 4);
}
else
{
lastPoint.PrevNextPointId1 ^= m_data[m_position++];
lastPoint.PrevNextPointId1 ^= (ushort)(m_data[m_position++] << 8);
}
}
private long DecodeTimestamp(int code)
{
long timestamp;
if (code == TsscCodeWords.TimeDelta1Forward)
{
timestamp = m_prevTimestamp1 + m_prevTimeDelta1;
}
else if (code == TsscCodeWords.TimeDelta2Forward)
{
timestamp = m_prevTimestamp1 + m_prevTimeDelta2;
}
else if (code == TsscCodeWords.TimeDelta3Forward)
{
timestamp = m_prevTimestamp1 + m_prevTimeDelta3;
}
else if (code == TsscCodeWords.TimeDelta4Forward)
{
timestamp = m_prevTimestamp1 + m_prevTimeDelta4;
}
else if (code == TsscCodeWords.TimeDelta1Reverse)
{
timestamp = m_prevTimestamp1 - m_prevTimeDelta1;
}
else if (code == TsscCodeWords.TimeDelta2Reverse)
{
timestamp = m_prevTimestamp1 - m_prevTimeDelta2;
}
else if (code == TsscCodeWords.TimeDelta3Reverse)
{
timestamp = m_prevTimestamp1 - m_prevTimeDelta3;
}
else if (code == TsscCodeWords.TimeDelta4Reverse)
{
timestamp = m_prevTimestamp1 - m_prevTimeDelta4;
}
else if (code == TsscCodeWords.Timestamp2)
{
timestamp = m_prevTimestamp2;
}
else
{
timestamp = m_prevTimestamp1 ^ (long)Encoding7Bit.ReadUInt64(m_data, ref m_position);
}
//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;
return timestamp;
}
private uint DecodeQuality(int code, TsscPointMetadata nextPoint)
{
uint quality;
if (code == TsscCodeWords.Quality2)
{
quality = nextPoint.PrevQuality2;
}
else
{
quality = Encoding7Bit.ReadUInt32(m_data, ref m_position);
}
nextPoint.PrevQuality2 = nextPoint.PrevQuality1;
nextPoint.PrevQuality1 = quality;
return quality;
}
#region [ BitStream ]
///
/// The number of bits in m_bitStreamCache that are valid. 0 Means the bitstream is empty.
///
private int m_bitStreamCount;
///
/// 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;
private bool BitStreamIsEmpty => m_bitStreamCount == 0;
///
/// Resets the stream so it can be reused. All measurements must be registered again.
///
private void ClearBitStream()
{
m_bitStreamCount = 0;
m_bitStreamCache = 0;
}
private int ReadBit()
{
if (m_bitStreamCount == 0)
{
m_bitStreamCount = 8;
m_bitStreamCache = m_data[m_position++];
}
m_bitStreamCount--;
return (m_bitStreamCache >> m_bitStreamCount) & 1;
}
private int ReadBits4()
{
return ReadBit() << 3 | ReadBit() << 2 | ReadBit() << 1 | ReadBit();
//if (m_bitCount < 4)
//{
// m_bitCount += 8;
// m_cache = m_cache << 8 | m_parent.m_buffer.Data[m_parent.m_buffer.Position++];
//}
//m_bitCount -= 4;
//return (m_cache >> m_bitCount) & 15;
}
private int ReadBits5()
{
return ReadBit() << 4 | ReadBit() << 3 | ReadBit() << 2 | ReadBit() << 1 | ReadBit();
//if (m_bitCount < 5)
//{
// m_bitCount += 8;
// m_cache = m_cache << 8 | m_parent.m_buffer.Data[m_parent.m_buffer.Position++];
//}
//m_bitCount -= 5;
//return (m_cache >> m_bitCount) & 31;
}
#endregion
}
}