Files

1401 lines
56 KiB
C#

/* ========================================================================
* 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
{
/// <summary>
/// UADP Network Message
/// </summary>
public class UadpNetworkMessage : UaNetworkMessage
{
/// <summary>
/// The UADPVersion for this specification version is 1.
/// </summary>
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;
/// <summary>
/// Create new instance of UadpNetworkMessage
/// </summary>
internal UadpNetworkMessage(ILogger logger)
: this(null!, [], logger)
{
}
/// <summary>
/// Create new instance of UadpNetworkMessage
/// </summary>
/// <param name="writerGroupConfiguration">The <see cref="WriterGroupDataType"/> conflagration object that produced this message.</param>
/// <param name="uadpDataSetMessages"><see cref="UadpDataSetMessage"/> list as input</param>
/// <param name="logger">A contextual logger to log to</param>
public UadpNetworkMessage(
WriterGroupDataType writerGroupConfiguration,
List<UadpDataSetMessage> uadpDataSetMessages,
ILogger? logger = null)
: base(
writerGroupConfiguration,
uadpDataSetMessages?.ConvertAll<UaDataSetMessage>(x => x) ?? [],
logger)
{
UADPVersion = kUadpVersion;
DataSetClassId = Uuid.Empty;
Timestamp = DateTime.UtcNow;
UADPNetworkMessageType = UADPNetworkMessageType.DataSetMessage;
}
/// <summary>
/// Create new instance of <see cref="UadpNetworkMessage"/> as a DiscoveryResponse DataSetMetaData message
/// </summary>
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();
}
/// <summary>
/// Create new instance of <see cref="UadpNetworkMessage"/> as a DiscoveryRequest of specified type
/// </summary>
public UadpNetworkMessage(
UADPNetworkMessageDiscoveryType discoveryType,
ILogger? logger = null)
: base(null!, [], logger)
{
UADPVersion = kUadpVersion;
DataSetClassId = Uuid.Empty;
Timestamp = DateTime.UtcNow;
UADPNetworkMessageType = UADPNetworkMessageType.DiscoveryRequest;
UADPDiscoveryType = discoveryType;
SetFlagsDiscoveryRequest();
}
/// <summary>
/// Create new instance of <see cref="UadpNetworkMessage"/> as a DiscoveryResponse of PublisherEndpoints type
/// </summary>
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();
}
/// <summary>
/// Create new instance of <see cref="UadpNetworkMessage"/> as a DiscoveryResponse of DataSetWriterConfiguration message
/// </summary>
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();
}
/// <summary>
/// NetworkMessageContentMask contains the mask that will be used to check NetworkMessage options selected for usage
/// </summary>
public UadpNetworkMessageContentMask NetworkMessageContentMask { get; private set; }
/// <summary>
/// Get the UADP network message type
/// </summary>
public UADPNetworkMessageType UADPNetworkMessageType { get; private set; }
/// <summary>
/// Get the UADP network message discovery type
/// </summary>
public UADPNetworkMessageDiscoveryType UADPDiscoveryType { get; private set; }
/// <summary>
/// Get/Set the StatusCodes
/// </summary>
public StatusCode[]? MessageStatusCodes { get; set; }
/// <summary>
/// Get the DataSetWriterConfig
/// </summary>
public WriterGroupDataType? DataSetWriterConfiguration { get; set; }
/// <summary>
/// Discovery DataSetWriter Identifiers
/// </summary>
public ushort[]? DataSetWriterIds { get; set; }
/// <summary>
/// Get and Set Uadp version
/// </summary>
public byte UADPVersion
{
get => m_uadpVersion;
set => m_uadpVersion = Convert.ToByte(value & kUADPVersionBitMask);
}
/// <summary>
/// Get Uadp Flags
/// </summary>
public UADPFlagsEncodingMask UADPFlags { get; private set; }
/// <summary>
/// Get ExtendedFlags1
/// </summary>
public ExtendedFlags1EncodingMask ExtendedFlags1 { get; private set; }
/// <summary>
/// Get ExtendedFlags2
/// </summary>
public ExtendedFlags2EncodingMask ExtendedFlags2 { get; private set; }
/// <summary>
/// Get and Set PublisherId type
/// </summary>
/// <exception cref="ArgumentException"></exception>
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;
}
}
/// <summary>
/// Get and Set DataSetClassId
/// </summary>
public Uuid DataSetClassId { get; set; }
/// <summary>
/// Get and Set GroupFlags
/// </summary>
public GroupFlagsEncodingMask GroupFlags { get; private set; }
/// <summary>
/// Get and Set VersionTime type: it represents the time in seconds since the year 2000
/// </summary>
public uint GroupVersion { get; set; }
/// <summary>
/// Get and Set NetworkMessageNumber
/// </summary>
public ushort NetworkMessageNumber { get; set; }
/// <summary>
/// Get and Set SequenceNumber
/// </summary>
public ushort SequenceNumber { get; set; }
/// <summary>
/// Get and Set Timestamp
/// </summary>
public DateTimeUtc Timestamp { get; set; }
/// <summary>
/// PicoSeconds
/// </summary>
public ushort PicoSeconds { get; set; }
/// <summary>
/// Get and Set SecurityFlags
/// </summary>
public SecurityFlagsEncodingMask SecurityFlags { get; set; }
/// <summary>
/// Get and Set SecurityTokenId has IntegerId type
/// </summary>
public uint SecurityTokenId { get; set; }
/// <summary>
/// Get and Set NonceLength
/// </summary>
public byte NonceLength { get; set; }
/// <summary>
/// Get and Set MessageNonce contains [NonceLength]
/// </summary>
public byte[]? MessageNonce { get; set; }
/// <summary>
/// Get and Set SecurityFooterSize
/// </summary>
public ushort SecurityFooterSize { get; set; }
/// <summary>
/// Get and Set SecurityFooter
/// </summary>
public byte[]? SecurityFooter { get; set; }
/// <summary>
/// Get and Set Signature
/// </summary>
public byte[]? Signature { get; set; }
/// <summary>
/// Discovery Publisher Endpoints message
/// </summary>
internal ArrayOf<EndpointDescription> PublisherEndpoints { get; set; }
/// <summary>
/// StatusCode that specifies if a Discovery message provides PublisherEndpoints
/// </summary>
internal StatusCode PublisherProvideEndpoints { get; set; }
/// <summary>
/// Set network message content mask
/// </summary>
public void SetNetworkMessageContentMask(
UadpNetworkMessageContentMask networkMessageContentMask)
{
NetworkMessageContentMask = networkMessageContentMask;
SetFlagsDataSetNetworkMessageType();
}
/// <summary>
/// Encodes the object and returns the resulting byte array.
/// </summary>
/// <param name="messageContext">The context.</param>
public override byte[] Encode(IServiceMessageContext messageContext)
{
using var stream = new MemoryStream();
Encode(messageContext, stream);
return stream.ToArray();
}
/// <summary>
/// Encodes the object in the specified stream.
/// </summary>
/// <param name="messageContext">The system context.</param>
/// <param name="stream">The stream to use.</param>
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);
}
}
}
/// <summary>
/// Decodes the message
/// </summary>
public override void Decode(
IServiceMessageContext messageContext,
byte[] message,
IList<DataSetReaderDataType> 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);
}
}
/// <summary>
/// Encodes the DataSet Network message in a binary stream.
/// </summary>
/// <exception cref="ArgumentException"><paramref name="binaryEncoder"/></exception>
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);
}
/// <summary>
/// Encodes the NetworkMessage as a DiscoveryResponse of DataSetMetaData Type
/// </summary>
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);
}
/// <summary>
/// Encodes the NetworkMessage as a DiscoveryResponse of DataSetWriterConfiguration Type
/// </summary>
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!);
}
/// <summary>
/// Encodes the NetworkMessage as a DiscoveryResponse of EndpointDescription[] Type
/// </summary>
private void EncodePublisherEndpoints(BinaryEncoder binaryEncoder)
{
binaryEncoder.WriteEncodeableArray(
"Endpoints",
PublisherEndpoints);
binaryEncoder.WriteStatusCode("statusCode", PublisherProvideEndpoints);
}
/// <summary>
/// Set All flags before encode/decode for a NetworkMessage that contains DataSet messages
/// </summary>
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;
}
/// <summary>
/// Set All flags before encode/decode for a NetworkMessage that contains a DiscoveryResponse containing data set metadata
/// </summary>
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;
}
/// <summary>
/// Set All flags before encode/decode for a NetworkMessage that contains A DiscoveryRequest
/// </summary>
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;
}
/// <summary>
/// Decode the stream from decoder parameter and produce a Dataset
/// </summary>
public void DecodeSubscribedDataSets(
BinaryDecoder binaryDecoder,
IList<DataSetReaderDataType> dataSetReaders)
{
if (dataSetReaders == null || dataSetReaders.Count == 0)
{
return;
}
try
{
var dataSetReadersFiltered = new List<DataSetReaderDataType>();
/* 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<UaDataSetMessage>();
/* 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<UaDataSetMessage>(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<UadpDataSetMessage>())
{
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");
}
}
/// <summary>
/// Decode the binaryDecoder content as a MetaData message
/// </summary>
private void DecodeMetaDataMessage(BinaryDecoder binaryDecoder)
{
DataSetWriterId = binaryDecoder.ReadUInt16("DataSetWriterId");
m_metadata = binaryDecoder.ReadEncodeable<DataSetMetaDataType>("MetaData");
// temporary write StatusCode.Good
StatusCode statusCode = binaryDecoder.ReadStatusCode("StatusCode");
m_logger.LogInformation("DecodeMetaDataMessage returned: {StatusCode}", statusCode);
}
/// <summary>
/// Decode the binaryDecoder content as Endpoints message
/// </summary>
private void DecodePublisherEndpoints(BinaryDecoder binaryDecoder)
{
PublisherEndpoints =
binaryDecoder.ReadEncodeableArray<EndpointDescription>("Endpoints");
PublisherProvideEndpoints = binaryDecoder.ReadStatusCode("statusCode");
m_logger.LogInformation(
"DecodePublisherEndpointsMessage returned: {PublisherProvideEndpoints}",
PublisherProvideEndpoints);
}
/// <summary>
/// Decode the binaryDecoder content as a DataSetWriterConfiguration message
/// </summary>
/// <param name="binaryDecoder">the decoder</param>
private void DecodeDataSetWriterConfigurationMessage(BinaryDecoder binaryDecoder)
{
DataSetWriterIds = [.. binaryDecoder.ReadUInt16Array("DataSetWriterIds")];
WriterGroupDataType dataSetWriterConfigurationDecoded =
binaryDecoder.ReadEncodeable<WriterGroupDataType>("DataSetWriterConfiguration")!;
DataSetWriterConfiguration =
dataSetWriterConfigurationDecoded.MaxNetworkMessageSize != 0
? dataSetWriterConfigurationDecoded
: null;
// temporary write StatusCode.Good
MessageStatusCodes = [.. binaryDecoder.ReadStatusCodeArray("StatusCodes")];
m_logger.LogInformation("DecodeDataSetWriterConfigurationMessage returned: {MessageStatusCodes}", MessageStatusCodes);
}
/// <summary>
/// Encode Network Message Header
/// </summary>
/// <exception cref="ServiceResultException"></exception>
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);
}
}
/// <summary>
/// Encode Group Message Header
/// </summary>
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);
}
}
/// <summary>
/// Encode Payload Header
/// </summary>
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);
}
}
}
}
/// <summary>
/// Encode Extended network message header
/// </summary>
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);
}
}
/// <summary>
/// Encode promoted fields
/// </summary>
private static void EncodePromotedFields(BinaryEncoder encoder)
{
// todo: Promoted fields not supported
}
/// <summary>
/// Encode security header
/// </summary>
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);
}
}
}
/// <summary>
/// Encode payload
/// </summary>
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>())
{
uadpDataSetMessage.Encode(encoder);
}
if (DataSetMessages.Count > 1 &&
(NetworkMessageContentMask & UadpNetworkMessageContentMask.PayloadHeader) != 0)
{
int payloadEndPositionInStream = encoder.Position;
encoder.Position = payloadStartPositionInStream;
foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages
.OfType<UadpDataSetMessage>())
{
encoder.WriteUInt16("Size", uadpDataSetMessage.PayloadSizeInStream);
}
encoder.Position = payloadEndPositionInStream;
}
}
/// <summary>
/// Encode security footer
/// </summary>
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!);
}
/// <summary>
/// Encode Network Message Header
/// </summary>
/// <exception cref="ServiceResultException"></exception>
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");
}
}
/// <summary>
/// Decode Group Message Header
/// </summary>
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");
}
}
/// <summary>
/// Decode Payload Header
/// </summary>
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>())
{
uadpDataSetMessage.DataSetWriterId = decoder.ReadUInt16("DataSetWriterId");
}
}
}
/// <summary>
/// Decode extended network message header
/// </summary>
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);
}
}
/// <summary>
/// Decode promoted fields
/// </summary>
private static void DecodePromotedFields(BinaryDecoder decoder)
{
// todo: Promoted fields not supported
}
/// <summary>
/// Decode payload size and prepare for decoding payload
/// </summary>
private void DecodePayloadSize(BinaryDecoder decoder)
{
if (DataSetMessages.Count > 1)
{
// Decode PayloadHeader Size
if ((UADPFlags & UADPFlagsEncodingMask.PayloadHeader) != 0)
{
foreach (UadpDataSetMessage uadpDataSetMessage in DataSetMessages
.OfType<UadpDataSetMessage>())
{
// 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>())
{
uadpDataSetMessage.StartPositionInStream = binaryDecoder.Position + offset;
offset += uadpDataSetMessage.PayloadSizeInStream;
}
}
}
/// <summary>
/// Decode security header
/// </summary>
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");
}
}
}
/// <summary>
/// Decode the Discovery Request Header
/// </summary>
private void DecodeDiscoveryRequest(BinaryDecoder binaryDecoder)
{
UADPDiscoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte(
"RequestType");
DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")!.ToArray();
}
/// <summary>
/// Decode the Discovery Response Header
/// </summary>
/// <exception cref="ServiceResultException"></exception>
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}");
}
}
}
}