/* ======================================================================== * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; namespace Opc.Ua.PubSub.Encoding { /// /// UADP Network Message /// public class UadpNetworkMessage : UaNetworkMessage { /// /// The UADPVersion for this specification version is 1. /// private const byte kUadpVersion = 1; private const byte kPublishedIdTypeUsedBits = 0x07; private const byte kUADPVersionBitMask = 0x0F; private const byte kPublishedIdResetMask = 0xFC; private byte m_uadpVersion; private Variant m_publisherId; /// /// Create new instance of UadpNetworkMessage /// internal UadpNetworkMessage(ILogger logger) : this(null!, [], logger) { } /// /// Create new instance of UadpNetworkMessage /// /// The conflagration object that produced this message. /// list as input /// A contextual logger to log to public UadpNetworkMessage( WriterGroupDataType writerGroupConfiguration, List uadpDataSetMessages, ILogger? logger = null) : base( writerGroupConfiguration, uadpDataSetMessages?.ConvertAll(x => x) ?? [], logger) { UADPVersion = kUadpVersion; DataSetClassId = Uuid.Empty; Timestamp = DateTime.UtcNow; UADPNetworkMessageType = UADPNetworkMessageType.DataSetMessage; } /// /// Create new instance of as a DiscoveryResponse DataSetMetaData message /// public UadpNetworkMessage( WriterGroupDataType writerGroupConfiguration, DataSetMetaDataType metadata, ILogger? logger = null) : base(writerGroupConfiguration, metadata, logger) { UADPVersion = kUadpVersion; DataSetClassId = Uuid.Empty; Timestamp = DateTime.UtcNow; UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; UADPDiscoveryType = UADPNetworkMessageDiscoveryType.DataSetMetaData; SetFlagsDiscoveryResponse(); } /// /// Create new instance of as a DiscoveryRequest of specified type /// public UadpNetworkMessage( UADPNetworkMessageDiscoveryType discoveryType, ILogger? logger = null) : base(null!, [], logger) { UADPVersion = kUadpVersion; DataSetClassId = Uuid.Empty; Timestamp = DateTime.UtcNow; UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryRequest; UADPDiscoveryType = discoveryType; SetFlagsDiscoveryRequest(); } /// /// Create new instance of as a DiscoveryResponse of PublisherEndpoints type /// public UadpNetworkMessage( EndpointDescription[] publisherEndpoints, StatusCode publisherProvidesEndpoints, ILogger? logger = null) : base(null!, [], logger) { UADPVersion = kUadpVersion; DataSetClassId = Uuid.Empty; Timestamp = DateTime.UtcNow; PublisherEndpoints = publisherEndpoints; PublisherProvideEndpoints = publisherProvidesEndpoints; UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; UADPDiscoveryType = UADPNetworkMessageDiscoveryType.PublisherEndpoint; SetFlagsDiscoveryResponse(); } /// /// Create new instance of as a DiscoveryResponse of DataSetWriterConfiguration message /// public UadpNetworkMessage( ushort[] writerIds, WriterGroupDataType writerConfig, StatusCode[] streamStatusCodes, ILogger? logger = null) : base(null!, [], logger) { UADPVersion = kUadpVersion; DataSetClassId = Uuid.Empty; Timestamp = DateTime.UtcNow; DataSetWriterIds = writerIds; UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; UADPDiscoveryType = UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration; DataSetWriterConfiguration = writerConfig; MessageStatusCodes = streamStatusCodes; SetFlagsDiscoveryResponse(); } /// /// NetworkMessageContentMask contains the mask that will be used to check NetworkMessage options selected for usage /// public UadpNetworkMessageContentMask NetworkMessageContentMask { get; private set; } /// /// Get the UADP network message type /// public UADPNetworkMessageType UADPNetworkMessageType { get; private set; } /// /// Get the UADP network message discovery type /// public UADPNetworkMessageDiscoveryType UADPDiscoveryType { get; private set; } /// /// Get/Set the StatusCodes /// public StatusCode[]? MessageStatusCodes { get; set; } /// /// Get the DataSetWriterConfig /// public WriterGroupDataType? DataSetWriterConfiguration { get; set; } /// /// Discovery DataSetWriter Identifiers /// public ushort[]? DataSetWriterIds { get; set; } /// /// Get and Set Uadp version /// public byte UADPVersion { get => m_uadpVersion; set => m_uadpVersion = Convert.ToByte(value & kUADPVersionBitMask); } /// /// Get Uadp Flags /// public UADPFlagsEncodingMask UADPFlags { get; private set; } /// /// Get ExtendedFlags1 /// public ExtendedFlags1EncodingMask ExtendedFlags1 { get; private set; } /// /// Get ExtendedFlags2 /// public ExtendedFlags2EncodingMask ExtendedFlags2 { get; private set; } /// /// Get and Set PublisherId type /// /// public Variant PublisherId { get => m_publisherId; set { // ExtendedFlags1: Bit range 0-2: PublisherId Type PublisherIdTypeEncodingMask publishedIdTypeType = PublisherIdTypeEncodingMask.Reserved; if (value.TryGetValue(out byte _)) { publishedIdTypeType = PublisherIdTypeEncodingMask.Byte; } else if (value.TryGetValue(out sbyte i8Value)) { value = Variant.From((byte)i8Value); publishedIdTypeType = PublisherIdTypeEncodingMask.Byte; } else if (value.TryGetValue(out ushort _)) { publishedIdTypeType = PublisherIdTypeEncodingMask.UInt16; } else if (value.TryGetValue(out short i16Value)) { value = Variant.From((ushort)i16Value); publishedIdTypeType = PublisherIdTypeEncodingMask.UInt16; } else if (value.TryGetValue(out uint _)) { publishedIdTypeType = PublisherIdTypeEncodingMask.UInt32; } else if (value.TryGetValue(out int i32Value)) { value = Variant.From((uint)i32Value); publishedIdTypeType = PublisherIdTypeEncodingMask.UInt32; } else if (value.TryGetValue(out ulong _)) { publishedIdTypeType = PublisherIdTypeEncodingMask.UInt64; } else if (value.TryGetValue(out long i64Value)) { value = Variant.From((ulong)i64Value); publishedIdTypeType = PublisherIdTypeEncodingMask.UInt64; } else if (value.TryGetValue(out string _)) { publishedIdTypeType = PublisherIdTypeEncodingMask.String; } m_publisherId = value; // Remove previous PublisherId data type ExtendedFlags1 &= (ExtendedFlags1EncodingMask)kPublishedIdResetMask; ExtendedFlags1 |= (ExtendedFlags1EncodingMask)publishedIdTypeType; } } /// /// Get and Set DataSetClassId /// public Uuid DataSetClassId { get; set; } /// /// Get and Set GroupFlags /// public GroupFlagsEncodingMask GroupFlags { get; private set; } /// /// Get and Set VersionTime type: it represents the time in seconds since the year 2000 /// public uint GroupVersion { get; set; } /// /// Get and Set NetworkMessageNumber /// public ushort NetworkMessageNumber { get; set; } /// /// Get and Set SequenceNumber /// public ushort SequenceNumber { get; set; } /// /// Get and Set Timestamp /// public DateTimeUtc Timestamp { get; set; } /// /// PicoSeconds /// public ushort PicoSeconds { get; set; } /// /// Get and Set SecurityFlags /// public SecurityFlagsEncodingMask SecurityFlags { get; set; } /// /// Get and Set SecurityTokenId has IntegerId type /// public uint SecurityTokenId { get; set; } /// /// Get and Set NonceLength /// public byte NonceLength { get; set; } /// /// Get and Set MessageNonce contains [NonceLength] /// public byte[]? MessageNonce { get; set; } /// /// Get and Set SecurityFooterSize /// public ushort SecurityFooterSize { get; set; } /// /// Get and Set SecurityFooter /// public byte[]? SecurityFooter { get; set; } /// /// Get and Set Signature /// public byte[]? Signature { get; set; } /// /// Discovery Publisher Endpoints message /// internal ArrayOf PublisherEndpoints { get; set; } /// /// StatusCode that specifies if a Discovery message provides PublisherEndpoints /// internal StatusCode PublisherProvideEndpoints { get; set; } /// /// Set network message content mask /// public void SetNetworkMessageContentMask( UadpNetworkMessageContentMask networkMessageContentMask) { NetworkMessageContentMask = networkMessageContentMask; SetFlagsDataSetNetworkMessageType(); } /// /// Encodes the object and returns the resulting byte array. /// /// The context. public override byte[] Encode(IServiceMessageContext messageContext) { using var stream = new MemoryStream(); Encode(messageContext, stream); return stream.ToArray(); } /// /// Encodes the object in the specified stream. /// /// The system context. /// The stream to use. public override void Encode(IServiceMessageContext messageContext, Stream stream) { using var binaryEncoder = new BinaryEncoder(stream, messageContext, true); if (UADPNetworkMessageType == UADPNetworkMessageType.DataSetMessage) { EncodeDataSetNetworkMessageType(binaryEncoder); } else { EncodeNetworkMessageHeader(binaryEncoder); if (UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { EncodeDiscoveryResponse(binaryEncoder); } else if (UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { EncodeDiscoveryRequest(binaryEncoder); } } } /// /// Decodes the message /// public override void Decode( IServiceMessageContext messageContext, byte[] message, IList dataSetReaders) { using var binaryDecoder = new BinaryDecoder(message, messageContext); // 1. decode network message header (PublisherId & DataSetClassId) DecodeNetworkMessageHeader(binaryDecoder); //decode network messages according to their type if (UADPNetworkMessageType == UADPNetworkMessageType.DataSetMessage) { if (dataSetReaders == null || dataSetReaders.Count == 0) { return; } //decode bytes using dataset reader information DecodeSubscribedDataSets(binaryDecoder, dataSetReaders); } else if (UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { DecodeDiscoveryResponse(binaryDecoder); } else if (UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { DecodeDiscoveryRequest(binaryDecoder); } } /// /// Encodes the DataSet Network message in a binary stream. /// /// private void EncodeDataSetNetworkMessageType(BinaryEncoder binaryEncoder) { if (binaryEncoder == null) { throw new ArgumentException(null, nameof(binaryEncoder)); } EncodeNetworkMessageHeader(binaryEncoder); EncodeGroupMessageHeader(binaryEncoder); EncodePayloadHeader(binaryEncoder); EncodeExtendedNetworkMessageHeader(binaryEncoder); EncodeSecurityHeader(binaryEncoder); EncodePayload(binaryEncoder); EncodeSecurityFooter(binaryEncoder); //EncodeSignature(encoder); } /// /// Encodes the NetworkMessage as a DiscoveryResponse of DataSetMetaData Type /// private void EncodeDataSetMetaData(BinaryEncoder binaryEncoder) { if (DataSetWriterId != null) { binaryEncoder.WriteUInt16("DataSetWriterId", DataSetWriterId.Value); } else { m_logger.LogInformation( "The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); binaryEncoder.WriteUInt16("DataSetWriterId", 0); } if (m_metadata == null) { m_logger.LogInformation( "The UADP DiscoveryResponse DataSetMetaData message cannot be encoded: The MetaData property is missing. Value null will be used."); } binaryEncoder.WriteEncodeable("MetaData", m_metadata!); binaryEncoder.WriteStatusCode("StatusCode", StatusCodes.Good); } /// /// Encodes the NetworkMessage as a DiscoveryResponse of DataSetWriterConfiguration Type /// private void EncodeDataSetWriterConfiguration(BinaryEncoder binaryEncoder) { if (DataSetWriterIds != null) { binaryEncoder.WriteUInt16Array("DataSetWriterId", DataSetWriterIds); } else { m_logger.LogInformation( "The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); binaryEncoder.WriteUInt16Array("DataSetWriterIds", []); } if (DataSetWriterIds == null) { m_logger.LogInformation( "The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterConfiguration property is missing. Value null will be used."); } else { binaryEncoder.WriteEncodeable( "DataSetWriterConfiguration", DataSetWriterConfiguration!); } binaryEncoder.WriteStatusCodeArray("StatusCodes", MessageStatusCodes!); } /// /// Encodes the NetworkMessage as a DiscoveryResponse of EndpointDescription[] Type /// private void EncodePublisherEndpoints(BinaryEncoder binaryEncoder) { binaryEncoder.WriteEncodeableArray( "Endpoints", PublisherEndpoints); binaryEncoder.WriteStatusCode("statusCode", PublisherProvideEndpoints); } /// /// Set All flags before encode/decode for a NetworkMessage that contains DataSet messages /// private void SetFlagsDataSetNetworkMessageType() { UADPFlags = 0; ExtendedFlags1 &= (ExtendedFlags1EncodingMask)kPublishedIdTypeUsedBits; ExtendedFlags2 = 0; GroupFlags = 0; if (((int)NetworkMessageContentMask & ((int)UadpNetworkMessageContentMask.PublisherId | (int)UadpNetworkMessageContentMask.DataSetClassId)) != 0) { // UADPFlags: The ExtendedFlags1 shall be omitted if bit 7 of the UADPFlags is false. // Enable ExtendedFlags1 usage UADPFlags |= UADPFlagsEncodingMask.ExtendedFlags1; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.PublisherId) != 0) { // UADPFlags: Bit 4: PublisherId enabled UADPFlags |= UADPFlagsEncodingMask.PublisherId; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.DataSetClassId) != 0) { // ExtendedFlags1 Bit 3: DataSetClassId enabled ExtendedFlags1 |= ExtendedFlags1EncodingMask.DataSetClassId; } if (((int)NetworkMessageContentMask & ((int)UadpNetworkMessageContentMask.GroupHeader | (int)UadpNetworkMessageContentMask.WriterGroupId | (int)UadpNetworkMessageContentMask.GroupVersion | (int)UadpNetworkMessageContentMask.NetworkMessageNumber | (int)UadpNetworkMessageContentMask.SequenceNumber)) != 0) { // UADPFlags: Bit 5: GroupHeader enabled UADPFlags |= UADPFlagsEncodingMask.GroupHeader; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.WriterGroupId) != 0) { // GroupFlags: Bit 0: WriterGroupId enabled GroupFlags |= GroupFlagsEncodingMask.WriterGroupId; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.GroupVersion) != 0) { // GroupFlags: Bit 1: GroupVersion enabled GroupFlags |= GroupFlagsEncodingMask.GroupVersion; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.NetworkMessageNumber) != 0) { // GroupFlags: Bit 2: NetworkMessageNumber enabled GroupFlags |= GroupFlagsEncodingMask.NetworkMessageNumber; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.SequenceNumber) != 0) { // GroupFlags: Bit 3: SequenceNumber enabled GroupFlags |= GroupFlagsEncodingMask.SequenceNumber; } if (((int)NetworkMessageContentMask & ((int)UadpNetworkMessageContentMask.Timestamp | (int)UadpNetworkMessageContentMask.PicoSeconds | (int)UadpNetworkMessageContentMask.PromotedFields)) != 0) { // Enable ExtendedFlags1 usage UADPFlags |= UADPFlagsEncodingMask.ExtendedFlags1; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.Timestamp) != 0) { // ExtendedFlags1: Bit 5: Timestamp enabled ExtendedFlags1 |= ExtendedFlags1EncodingMask.Timestamp; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.PicoSeconds) != 0) { // ExtendedFlags1: Bit 6: PicoSeconds enabled ExtendedFlags1 |= ExtendedFlags1EncodingMask.PicoSeconds; } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.PromotedFields) != 0) { // ExtendedFlags1: Bit 7: ExtendedFlags2 enabled ExtendedFlags1 |= ExtendedFlags1EncodingMask.ExtendedFlags2; // The PromotedFields shall be omitted if bit 4 of the ExtendedFlags2 is false. // ExtendedFlags2: Bit 1: PromotedFields enabled // Wireshark: PromotedFields; omitted if bit 1 of ExtendedFlags2 is false ExtendedFlags2 |= ExtendedFlags2EncodingMask.PromotedFields; // Bit range 2-4: UADP NetworkMessage type // 000 NetworkMessage with DataSetMessage payload for now } if (((int)NetworkMessageContentMask & (int)UadpNetworkMessageContentMask.PayloadHeader) != 0) { // UADPFlag: Bit 6: PayloadHeader enabled UADPFlags |= UADPFlagsEncodingMask.PayloadHeader; } // ExtendedFlags1: Bit 4: Security enabled // Disable security for now ExtendedFlags1 &= ~ExtendedFlags1EncodingMask.Security; // The security footer size shall be omitted if bit 2 of the SecurityFlags is false. SecurityFlags &= ~SecurityFlagsEncodingMask.SecurityFooter; } /// /// Set All flags before encode/decode for a NetworkMessage that contains a DiscoveryResponse containing data set metadata /// private void SetFlagsDiscoveryResponse() { /* DiscoveryResponse: * UADPFlags bits 5 and 6 shall be false, bits 4 and 7 shall be true * ExtendedFlags1 bits 3, 5 and 6 shall be false, bit 7 shall be true (erata 9):Bit 4 of ExtendedFlags1 shall be true * ExtendedFlags2 bit 1 shall be false and the NetworkMessage type shall be discovery response * */ UADPFlags = UADPFlagsEncodingMask.PublisherId | UADPFlagsEncodingMask.ExtendedFlags1; ExtendedFlags1 = ExtendedFlags1EncodingMask.Security | ExtendedFlags1EncodingMask.ExtendedFlags2; ExtendedFlags2 = ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryResponse; // enable encoding of PublisherId in message header NetworkMessageContentMask = UadpNetworkMessageContentMask.PublisherId; } /// /// Set All flags before encode/decode for a NetworkMessage that contains A DiscoveryRequest /// private void SetFlagsDiscoveryRequest() { /* The NetworkMessage flags used with the discovery request messages shall use the following * bit values. * UADPFlags bits 5 and 6 shall be false, bits 4 and 7 shall be true * ExtendedFlags1 bits 3, 5 and 6 shall be false, bits 4 and 7 shall be true * ExtendedFlags2 bit 2 shall be true, all other bits shall be false */ UADPFlags = UADPFlagsEncodingMask.PublisherId | UADPFlagsEncodingMask.ExtendedFlags1; ExtendedFlags1 = ExtendedFlags1EncodingMask.Security | ExtendedFlags1EncodingMask.ExtendedFlags2; ExtendedFlags2 = ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryRequest; } /// /// Decode the stream from decoder parameter and produce a Dataset /// public void DecodeSubscribedDataSets( BinaryDecoder binaryDecoder, IList dataSetReaders) { if (dataSetReaders == null || dataSetReaders.Count == 0) { return; } try { var dataSetReadersFiltered = new List(); /* 6.2.8.1 PublisherId The parameter PublisherId defines the Publisher to receive NetworkMessages from. If the value is null, the parameter shall be ignored and all received NetworkMessages pass the PublisherId filter. */ foreach (DataSetReaderDataType dataSetReader in dataSetReaders) { //check Enabled & publisher id if (dataSetReader.PublisherId.IsNull || (!PublisherId.IsNull && PublisherId.Equals(dataSetReader.PublisherId))) { dataSetReadersFiltered.Add(dataSetReader); } } if (dataSetReadersFiltered.Count == 0) { return; } dataSetReaders = dataSetReadersFiltered; //continue filtering dataSetReadersFiltered = []; // 2. decode WriterGroupId DecodeGroupMessageHeader(binaryDecoder); /* 6.2.8.2 WriterGroupId The parameter WriterGroupId with DataType UInt16 defines the identifier of the corresponding WriterGroup. The default value 0 is defined as null value, and means this parameter shall be ignored.*/ foreach (DataSetReaderDataType dataSetReader in dataSetReaders) { //check WriterGroupId id if (dataSetReader.WriterGroupId == 0 || dataSetReader.WriterGroupId == WriterGroupId) { dataSetReadersFiltered.Add(dataSetReader); } } if (dataSetReadersFiltered.Count == 0) { return; } dataSetReaders = dataSetReadersFiltered; // 3. decode payload header DecodePayloadHeader(binaryDecoder); // 4. DecodeExtendedNetworkMessageHeader(binaryDecoder); // 5. DecodeSecurityHeader(binaryDecoder); //6.1 DecodePayloadSize(binaryDecoder); // the list of decode dataset messages for this network message var dataSetMessages = new List(); /* 6.2.8.3 DataSetWriterId The parameter DataSetWriterId with DataType UInt16 defines the DataSet selected in the Publisher for the DataSetReader. If the value is 0 (null), the parameter shall be ignored and all received DataSetMessages pass the DataSetWriterId filter.*/ foreach (DataSetReaderDataType dataSetReader in dataSetReaders) { var uadpDataSetMessages = new List(DataSetMessages); //if there is no information regarding dataSet in network message, add dummy datasetMessage to try decoding if (uadpDataSetMessages.Count == 0) { uadpDataSetMessages.Add(new UadpDataSetMessage(m_logger)); } // 6.2 Decode payload into DataSets // Restore the encoded fields (into dataset for now) for each possible dataset reader foreach (UadpDataSetMessage uadpDataSetMessage in uadpDataSetMessages .OfType()) { if (uadpDataSetMessage.DataSet != null) { continue; // this dataset message was already decoded } if (dataSetReader.DataSetWriterId == 0 || uadpDataSetMessage.DataSetWriterId == dataSetReader.DataSetWriterId) { //attempt to decode dataset message using the reader uadpDataSetMessage.DecodePossibleDataSetReader( binaryDecoder, dataSetReader); if (uadpDataSetMessage.DataSet != null) { dataSetMessages.Add(uadpDataSetMessage); } else if (uadpDataSetMessage.IsMetadataMajorVersionChange) { OnDataSetDecodeErrorOccurred( new DataSetDecodeErrorEventArgs( DataSetDecodeErrorReason.MetadataMajorVersion, this, dataSetReader)); } } } } if (m_uaDataSetMessages.Count == 0) { // set the list of dataset messages to the network message m_uaDataSetMessages.AddRange(dataSetMessages); } else { dataSetMessages = []; // check if DataSets are decoded into the existing dataSetMessages foreach (UaDataSetMessage dataSetMessage in m_uaDataSetMessages) { if (dataSetMessage.DataSet != null) { dataSetMessages.Add(dataSetMessage); } } m_uaDataSetMessages.Clear(); m_uaDataSetMessages.AddRange(dataSetMessages); } } catch (Exception ex) { // Unexpected exception in DecodeSubscribedDataSets m_logger.LogError(ex, "UadpNetworkMessage.DecodeSubscribedDataSets"); } } /// /// Decode the binaryDecoder content as a MetaData message /// private void DecodeMetaDataMessage(BinaryDecoder binaryDecoder) { DataSetWriterId = binaryDecoder.ReadUInt16("DataSetWriterId"); m_metadata = binaryDecoder.ReadEncodeable("MetaData"); // temporary write StatusCode.Good StatusCode statusCode = binaryDecoder.ReadStatusCode("StatusCode"); m_logger.LogInformation("DecodeMetaDataMessage returned: {StatusCode}", statusCode); } /// /// Decode the binaryDecoder content as Endpoints message /// private void DecodePublisherEndpoints(BinaryDecoder binaryDecoder) { PublisherEndpoints = binaryDecoder.ReadEncodeableArray("Endpoints"); PublisherProvideEndpoints = binaryDecoder.ReadStatusCode("statusCode"); m_logger.LogInformation( "DecodePublisherEndpointsMessage returned: {PublisherProvideEndpoints}", PublisherProvideEndpoints); } /// /// Decode the binaryDecoder content as a DataSetWriterConfiguration message /// /// the decoder private void DecodeDataSetWriterConfigurationMessage(BinaryDecoder binaryDecoder) { DataSetWriterIds = [.. binaryDecoder.ReadUInt16Array("DataSetWriterIds")]; WriterGroupDataType dataSetWriterConfigurationDecoded = binaryDecoder.ReadEncodeable("DataSetWriterConfiguration")!; DataSetWriterConfiguration = dataSetWriterConfigurationDecoded.MaxNetworkMessageSize != 0 ? dataSetWriterConfigurationDecoded : null; // temporary write StatusCode.Good MessageStatusCodes = [.. binaryDecoder.ReadStatusCodeArray("StatusCodes")]; m_logger.LogInformation("DecodeDataSetWriterConfigurationMessage returned: {MessageStatusCodes}", MessageStatusCodes); } /// /// Encode Network Message Header /// /// private void EncodeNetworkMessageHeader(BinaryEncoder encoder) { // byte[0..3] UADPVersion value 1 (for now) // byte[4..7] UADPFlags encoder.WriteByte("VersionFlags", (byte)(UADPVersion | (byte)UADPFlags)); if ((UADPFlags & UADPFlagsEncodingMask.ExtendedFlags1) != 0) { encoder.WriteByte("ExtendedFlags1", (byte)ExtendedFlags1); } if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.ExtendedFlags2) != 0) { encoder.WriteByte("ExtendedFlags2", (byte)ExtendedFlags2); } if ((UADPFlags & UADPFlagsEncodingMask.PublisherId) != 0) { if (PublisherId.IsNull) { m_logger.LogError( Utils.TraceMasks.Error, "NetworkMessageHeader cannot be encoded. PublisherId is null but it is expected to be encoded."); } else { var publisherIdEncoding = (PublisherIdTypeEncodingMask) ((byte)ExtendedFlags1 & kPublishedIdTypeUsedBits); switch (publisherIdEncoding) { case PublisherIdTypeEncodingMask.Byte: encoder.WriteByte( "PublisherId", PublisherId.GetByte()); break; case PublisherIdTypeEncodingMask.UInt16: encoder.WriteUInt16( "PublisherId", PublisherId.GetUInt16()); break; case PublisherIdTypeEncodingMask.UInt32: encoder.WriteUInt32( "PublisherId", PublisherId.GetUInt32()); break; case PublisherIdTypeEncodingMask.UInt64: encoder.WriteUInt64( "PublisherId", PublisherId.GetUInt64()); break; case PublisherIdTypeEncodingMask.String: encoder.WriteString( "PublisherId", PublisherId.GetString()); break; case PublisherIdTypeEncodingMask.Reserved: break; default: throw ServiceResultException.Unexpected( $"Unexpected PublisherIdTypeEncodingMask {publisherIdEncoding}"); } } } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.DataSetClassId) != 0) { encoder.WriteGuid("DataSetClassId", DataSetClassId); } } /// /// Encode Group Message Header /// private void EncodeGroupMessageHeader(BinaryEncoder encoder) { if (( NetworkMessageContentMask & ( UadpNetworkMessageContentMask.GroupHeader | UadpNetworkMessageContentMask.WriterGroupId | UadpNetworkMessageContentMask.GroupVersion | UadpNetworkMessageContentMask.NetworkMessageNumber | UadpNetworkMessageContentMask.SequenceNumber) ) != UadpNetworkMessageContentMask.None) { encoder.WriteByte("GroupFlags", (byte)GroupFlags); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.WriterGroupId) != 0) { encoder.WriteUInt16("WriterGroupId", WriterGroupId); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.GroupVersion) != 0) { encoder.WriteUInt32("GroupVersion", GroupVersion); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.NetworkMessageNumber) != 0) { encoder.WriteUInt16("NetworkMessageNumber", NetworkMessageNumber); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.SequenceNumber) != 0) { encoder.WriteUInt16("SequenceNumber", SequenceNumber); } } /// /// Encode Payload Header /// private void EncodePayloadHeader(BinaryEncoder encoder) { if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.PayloadHeader) != 0) { encoder.WriteByte("Count", (byte)DataSetMessages.Count); // Collect DataSetSetMessages headers for (int index = 0; index < DataSetMessages.Count; index++) { if (DataSetMessages[index] is UadpDataSetMessage uadpDataSetMessage && uadpDataSetMessage.DataSet != null) { encoder.WriteUInt16("DataSetWriterId", uadpDataSetMessage.DataSetWriterId); } } } } /// /// Encode Extended network message header /// private void EncodeExtendedNetworkMessageHeader(BinaryEncoder encoder) { if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.Timestamp) != 0) { encoder.WriteDateTime("Timestamp", Timestamp); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.PicoSeconds) != 0) { encoder.WriteUInt16("PicoSeconds", PicoSeconds); } if ((NetworkMessageContentMask & UadpNetworkMessageContentMask.PromotedFields) != 0) { EncodePromotedFields(encoder); } } /// /// Encode promoted fields /// private static void EncodePromotedFields(BinaryEncoder encoder) { // todo: Promoted fields not supported } /// /// Encode security header /// private void EncodeSecurityHeader(BinaryEncoder encoder) { if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.Security) != 0) { encoder.WriteByte("SecurityFlags", (byte)SecurityFlags); encoder.WriteUInt32("SecurityTokenId", SecurityTokenId); encoder.WriteByte("NonceLength", NonceLength); MessageNonce = new byte[NonceLength]; encoder.WriteByteArray("MessageNonce", MessageNonce); if ((SecurityFlags & SecurityFlagsEncodingMask.SecurityFooter) != 0) { encoder.WriteUInt16("SecurityFooterSize", SecurityFooterSize); } } } /// /// Encode payload /// private void EncodePayload(BinaryEncoder encoder) { int payloadStartPositionInStream = encoder.Position; if (DataSetMessages.Count > 1 && (NetworkMessageContentMask & UadpNetworkMessageContentMask.PayloadHeader) != 0) { //skip 2 * dataset count for each dataset payload size encoder.Position += 2 * DataSetMessages.Count; } //encode dataset message payload foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages .OfType()) { uadpDataSetMessage.Encode(encoder); } if (DataSetMessages.Count > 1 && (NetworkMessageContentMask & UadpNetworkMessageContentMask.PayloadHeader) != 0) { int payloadEndPositionInStream = encoder.Position; encoder.Position = payloadStartPositionInStream; foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages .OfType()) { encoder.WriteUInt16("Size", uadpDataSetMessage.PayloadSizeInStream); } encoder.Position = payloadEndPositionInStream; } } /// /// Encode security footer /// private void EncodeSecurityFooter(BinaryEncoder encoder) { if ((SecurityFlags & SecurityFlagsEncodingMask.SecurityFooter) != 0) { encoder.WriteByteArray("SecurityFooter", SecurityFooter!); } } private void EncodeDiscoveryResponse(BinaryEncoder binaryEncoder) { binaryEncoder.WriteByte("ResponseType", (byte)UADPDiscoveryType); // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. binaryEncoder.WriteUInt16("SequenceNumber", SequenceNumber); switch (UADPDiscoveryType) { case UADPNetworkMessageDiscoveryType.DataSetMetaData: EncodeDataSetMetaData(binaryEncoder); break; case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: EncodeDataSetWriterConfiguration(binaryEncoder); break; case UADPNetworkMessageDiscoveryType.PublisherEndpoint: EncodePublisherEndpoints(binaryEncoder); break; case UADPNetworkMessageDiscoveryType.None: break; default: throw ServiceResultException.Unexpected( $"Unexpected UADPNetworkMessageDiscoveryType {UADPDiscoveryType}"); } } private void EncodeDiscoveryRequest(BinaryEncoder binaryEncoder) { // RequestType => InformationType binaryEncoder.WriteByte("RequestType", (byte)UADPDiscoveryType); binaryEncoder.WriteUInt16Array("DataSetWriterIds", DataSetWriterIds!); } /// /// Encode Network Message Header /// /// private void DecodeNetworkMessageHeader(BinaryDecoder decoder) { // byte[0..3] UADPVersion value 1 (for now) // byte[4..7] UADPFlags byte versionFlags = decoder.ReadByte("VersionFlags"); UADPVersion = (byte)(versionFlags & kUADPVersionBitMask); // Decode UADPFlags UADPFlags = (UADPFlagsEncodingMask)(versionFlags & 0xF0); // Decode the ExtendedFlags1 if ((UADPFlags & UADPFlagsEncodingMask.ExtendedFlags1) != 0) { ExtendedFlags1 = (ExtendedFlags1EncodingMask)decoder.ReadByte("ExtendedFlags1"); } // Decode the ExtendedFlags2 if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.ExtendedFlags2) != 0) { ExtendedFlags2 = (ExtendedFlags2EncodingMask)decoder.ReadByte("ExtendedFlags2"); } // calculate UADPNetworkMessageType if ((ExtendedFlags2 & ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryRequest) != 0) { UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryRequest; } else if ((ExtendedFlags2 & ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryResponse) != 0) { UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; } else { UADPNetworkMessageType = UADPNetworkMessageType.DataSetMessage; } // Decode PublisherId if ((UADPFlags & UADPFlagsEncodingMask.PublisherId) != 0) { var publisherIdEncoding = (PublisherIdTypeEncodingMask) ((byte)ExtendedFlags1 & kPublishedIdTypeUsedBits); switch (publisherIdEncoding) { case PublisherIdTypeEncodingMask.UInt16: m_publisherId = decoder.ReadUInt16("PublisherId"); break; case PublisherIdTypeEncodingMask.UInt32: m_publisherId = decoder.ReadUInt32("PublisherId"); break; case PublisherIdTypeEncodingMask.UInt64: m_publisherId = decoder.ReadUInt64("PublisherId"); break; case PublisherIdTypeEncodingMask.String: m_publisherId = decoder.ReadString("PublisherId")!; break; case PublisherIdTypeEncodingMask.Byte: m_publisherId = decoder.ReadByte("PublisherId"); break; case PublisherIdTypeEncodingMask.Reserved: break; default: throw ServiceResultException.Unexpected( $"Unexpected PublisherIdTypeEncodingMask {publisherIdEncoding}"); } } // Decode DataSetClassId if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.DataSetClassId) != 0) { DataSetClassId = decoder.ReadGuid("DataSetClassId"); } } /// /// Decode Group Message Header /// private void DecodeGroupMessageHeader(BinaryDecoder decoder) { // Decode GroupHeader (that holds GroupFlags) if ((UADPFlags & UADPFlagsEncodingMask.GroupHeader) != 0) { GroupFlags = (GroupFlagsEncodingMask)decoder.ReadByte("GroupFlags"); } // Decode WriterGroupId if ((GroupFlags & GroupFlagsEncodingMask.WriterGroupId) != 0) { WriterGroupId = decoder.ReadUInt16("WriterGroupId"); } // Decode GroupVersion if ((GroupFlags & GroupFlagsEncodingMask.GroupVersion) != 0) { GroupVersion = decoder.ReadUInt32("GroupVersion"); } // Decode NetworkMessageNumber if ((GroupFlags & GroupFlagsEncodingMask.NetworkMessageNumber) != 0) { NetworkMessageNumber = decoder.ReadUInt16("NetworkMessageNumber"); } // Decode SequenceNumber if ((GroupFlags & GroupFlagsEncodingMask.SequenceNumber) != 0) { SequenceNumber = decoder.ReadUInt16("SequenceNumber"); } } /// /// Decode Payload Header /// private void DecodePayloadHeader(BinaryDecoder decoder) { // Decode PayloadHeader if ((UADPFlags & UADPFlagsEncodingMask.PayloadHeader) != 0) { byte count = decoder.ReadByte("Count"); for (int idx = 0; idx < count; idx++) { m_uaDataSetMessages.Add(new UadpDataSetMessage(m_logger)); } // collect DataSetSetMessages headers foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages .OfType()) { uadpDataSetMessage.DataSetWriterId = decoder.ReadUInt16("DataSetWriterId"); } } } /// /// Decode extended network message header /// private void DecodeExtendedNetworkMessageHeader(BinaryDecoder decoder) { // Decode Timestamp if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.Timestamp) != 0) { Timestamp = decoder.ReadDateTime("Timestamp"); } // Decode PicoSeconds if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.PicoSeconds) != 0) { PicoSeconds = decoder.ReadUInt16("PicoSeconds"); } // Decode Promoted Fields if ((ExtendedFlags2 & ExtendedFlags2EncodingMask.PromotedFields) != 0) { DecodePromotedFields(decoder); } } /// /// Decode promoted fields /// private static void DecodePromotedFields(BinaryDecoder decoder) { // todo: Promoted fields not supported } /// /// Decode payload size and prepare for decoding payload /// private void DecodePayloadSize(BinaryDecoder decoder) { if (DataSetMessages.Count > 1) { // Decode PayloadHeader Size if ((UADPFlags & UADPFlagsEncodingMask.PayloadHeader) != 0) { foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages .OfType()) { // Save the size uadpDataSetMessage.PayloadSizeInStream = decoder.ReadUInt16("Size"); } } } BinaryDecoder binaryDecoder = decoder; if (binaryDecoder != null) { int offset = 0; // set start position of dataset message in binary stream foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages .OfType()) { uadpDataSetMessage.StartPositionInStream = binaryDecoder.Position + offset; offset += uadpDataSetMessage.PayloadSizeInStream; } } } /// /// Decode security header /// private void DecodeSecurityHeader(BinaryDecoder decoder) { if ((ExtendedFlags1 & ExtendedFlags1EncodingMask.Security) != 0) { SecurityFlags = (SecurityFlagsEncodingMask)decoder.ReadByte("SecurityFlags"); SecurityTokenId = decoder.ReadUInt32("SecurityTokenId"); NonceLength = decoder.ReadByte("NonceLength"); MessageNonce = [.. decoder.ReadByteArray("MessageNonce")]; if ((SecurityFlags & SecurityFlagsEncodingMask.SecurityFooter) != 0) { SecurityFooterSize = decoder.ReadUInt16("SecurityFooterSize"); } } } /// /// Decode the Discovery Request Header /// private void DecodeDiscoveryRequest(BinaryDecoder binaryDecoder) { UADPDiscoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte( "RequestType"); DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")!.ToArray(); } /// /// Decode the Discovery Response Header /// /// private void DecodeDiscoveryResponse(BinaryDecoder binaryDecoder) { UADPDiscoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte( "ResponseType"); // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. SequenceNumber = binaryDecoder.ReadUInt16("SequenceNumber"); switch (UADPDiscoveryType) { case UADPNetworkMessageDiscoveryType.DataSetMetaData: DecodeMetaDataMessage(binaryDecoder); break; case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: DecodeDataSetWriterConfigurationMessage(binaryDecoder); break; case UADPNetworkMessageDiscoveryType.PublisherEndpoint: DecodePublisherEndpoints(binaryDecoder); break; case UADPNetworkMessageDiscoveryType.None: break; default: throw ServiceResultException.Unexpected( $"Unexpected UADPNetworkMessageDiscoveryType {UADPDiscoveryType}"); } } } }