using System.Buffers.Binary; using AVEVA.Historian.Client.Models; using AVEVA.Historian.Client.Wcf; namespace AVEVA.Historian.Client.Tests; public sealed class WcfHistoricalWriteProtocolTests { // Exact HistoryService.AddStreamValues "values" buffers captured live from the native 2023 R2 // client writing one historical sample to a sandbox tag of each analog type — see // docs/plans/revision-write-path.md §"R3.1 CAPTURED". The value descriptor (C0 10 01 00) is // constant across types; only the value width differs. [Theory] [InlineData(HistorianDataType.Float, 124.5, "4f4e0100380000002e00d51d3107e9f8664793b155d3d1aef54470df38b1d101dd01c000c0100100e64bcb74e201dd01000000000000f942")] [InlineData(HistorianDataType.Double, 124.5, "4f4e01003c0000003200" + "7f970bb0b5a7344bb7bf6899ff06d027" + "c07d5f23d701dd01" + "c000" + "c0100100" + "08eff1e6e701dd01" + "000000000000000000205f40")] [InlineData(HistorianDataType.Int2, 1234d, "4f4e0100360000002c00" + "35bb0ca5865a7f4498f06bf4e4d9ab23" + "f012f356d701dd01" + "c000" + "c0100100" + "9546841ae801dd01" + "00000000d204")] [InlineData(HistorianDataType.Int4, 12345d, "4f4e0100380000002e00" + "ca9735f7f841b244b56f9c14ccfeac32" + "b09bc72fd701dd01" + "c000" + "c0100100" + "104b59f3e701dd01" + "0000000039300000")] [InlineData(HistorianDataType.UInt4, 305419896d, "4f4e0100380000002e00" + "e7ae22d8e4cc65439ebd8bcb09402974" + "602d6663d701dd01" + "c000" + "c0100100" + "498af726e801dd01" + "0000000078563412")] public void SerializeAddStreamValuesBuffer_MatchesCapturedNativeBuffer(HistorianDataType dataType, double value, string capturedHex) { byte[] captured = Convert.FromHexString(capturedHex); // The GUID + both FILETIMEs sit at fixed offsets (the value width varies after them). var tagGuid = new Guid(captured.AsSpan(10, 16).ToArray()); long sampleFt = BinaryPrimitives.ReadInt64LittleEndian(captured.AsSpan(26, 8)); long receivedFt = BinaryPrimitives.ReadInt64LittleEndian(captured.AsSpan(40, 8)); byte[] actual = HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer( tagGuid, dataType, DateTime.FromFileTimeUtc(sampleFt), value, DateTime.FromFileTimeUtc(receivedFt), quality: 192); Assert.Equal(captured, actual); } [Fact] public void SerializeAddStreamValuesBuffer_UnsupportedType_Throws() { Assert.Throws(() => HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer( Guid.NewGuid(), HistorianDataType.SingleByteString, DateTime.UtcNow, 1.0, DateTime.UtcNow)); } [Fact] public void SerializeAddStreamValuesBuffer_HeaderDeclaresLengths() { byte[] buffer = HistorianHistoricalWriteProtocol.SerializeAddStreamValuesBuffer( Guid.NewGuid(), HistorianDataType.Float, DateTime.UtcNow, value: 1.0, DateTime.UtcNow); Assert.Equal(0x4E4F, BitConverter.ToUInt16(buffer, 0)); // "ON" Assert.Equal(1, BitConverter.ToUInt16(buffer, 2)); // sampleCount Assert.Equal((uint)buffer.Length, BitConverter.ToUInt32(buffer, 4)); Assert.Equal(buffer.Length - 10, BitConverter.ToUInt16(buffer, 8)); } }