using AVEVA.Historian.Client.Models; using AVEVA.Historian.Client.Wcf; namespace AVEVA.Historian.Client.Tests; public sealed class WcfEventWriteProtocolTests { // Golden bytes captured from a native AddStreamedValue(HistorianEvent) over WCF (AddS2.pBuf), // instrument-wcf-writemessage, event-send capture A (Type="User.Write"). private const string CaptureAddS2PBufBase64 = "T1MBANMAAADJAEWBOzXwXUZNolOHGu9JsyHwZ9L7+QDdAcAAwACNEbJ2OdoXacNJkFutCzR+krSAst37" + "+QDdAQkSAFJldGVzdFNka0V2ZW50U2VuZAkKAFVzZXIuV3JpdGUFAAIACQYAU291cmNlQyYAEgBSAGUA" + "dABlAHMAdABTAGQAawBFAHYAZQBuAHQAUwBlAG4AZAAJCgBUZXN0TWFya2VyQyoAFABoAGkAcwB0AHMA" + "ZABrAC0AUgAyAC4AMQAtAGMAYQBwAHQAdQByAGUA"; [Fact] public void SerializeAddStreamValuesBufferMatchesInstrumentedNativeEventSend() { // Exact field values recovered from capture A's pBuf. Guid eventId = new("da3976b2-6917-49c3-905b-ad0b347e92b4"); DateTime eventTimeUtc = DateTime.FromFileTimeUtc(134264637562710000L); DateTime receivedTimeUtc = DateTime.FromFileTimeUtc(134264637563449984L); HistorianEvent evt = new( Id: eventId, EventTimeUtc: eventTimeUtc, ReceivedTimeUtc: receivedTimeUtc, Type: "User.Write", SourceName: string.Empty, Namespace: "RetestSdkEventSend", RevisionVersion: 0, Properties: new Dictionary { ["Source"] = "RetestSdkEventSend", ["TestMarker"] = "histsdk-R2.1-capture", }); // The capture's MDAS length marker excluded the final trailing pad byte, so the golden // bytes are the 210-byte deterministic content; the serializer appends one pad byte // (total 211) to satisfy the server's packet-length == buffer-size check. byte[] expectedContent = Convert.FromBase64String(CaptureAddS2PBufBase64); byte[] actual = HistorianEventWriteProtocol.SerializeAddStreamValuesBuffer(evt, receivedTimeUtc); Assert.Equal(expectedContent.Length + 1, actual.Length); Assert.Equal(expectedContent, actual[..expectedContent.Length]); Assert.Equal(0, actual[^1]); // The declared packet length (UInt32 @0x04) equals the delivered buffer length. Assert.Equal((uint)actual.Length, BitConverter.ToUInt32(actual, 4)); } [Fact] public void BufferFramingFieldsAreDerivedFromValueBlobLength() { HistorianEvent evt = new( Id: new("da3976b2-6917-49c3-905b-ad0b347e92b4"), EventTimeUtc: DateTime.FromFileTimeUtc(134264637562710000L), ReceivedTimeUtc: DateTime.FromFileTimeUtc(134264637563449984L), Type: "User.Write", SourceName: string.Empty, Namespace: "RetestSdkEventSend", RevisionVersion: 0, Properties: new Dictionary { ["Source"] = "RetestSdkEventSend", ["TestMarker"] = "histsdk-R2.1-capture" }); byte[] buf = HistorianEventWriteProtocol.SerializeAddStreamValuesBuffer(evt, evt.ReceivedTimeUtc); Assert.Equal(0x534F, BitConverter.ToUInt16(buf, 0)); // "OS" Assert.Equal(1, BitConverter.ToUInt16(buf, 2)); // sampleCount // Declared packet length (UInt32 @0x04) equals the delivered buffer length (incl. pad). Assert.Equal((uint)buf.Length, BitConverter.ToUInt32(buf, 4)); // Inner length (UInt16 @0x08) = buffer length - 10. Assert.Equal((ushort)(buf.Length - 10), BitConverter.ToUInt16(buf, 8)); // CM_EVENT tag id at the head of the value blob. Assert.Equal(HistorianEventWriteProtocol.CmEventTagId, new Guid(buf.AsSpan(10, 16).ToArray())); } [Fact] public void NonStringPropertyValueThrowsProtocolEvidenceMissing() { HistorianEvent evt = new( Id: Guid.NewGuid(), EventTimeUtc: new DateTime(2026, 6, 20, 12, 0, 0, DateTimeKind.Utc), ReceivedTimeUtc: new DateTime(2026, 6, 20, 12, 0, 0, DateTimeKind.Utc), Type: "User.Write", SourceName: string.Empty, Namespace: "ns", RevisionVersion: 0, Properties: new Dictionary { ["Count"] = 5 }); Assert.Throws( () => HistorianEventWriteProtocol.SerializeAddStreamValuesBuffer(evt, evt.ReceivedTimeUtc)); } }