diff --git a/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs b/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs new file mode 100644 index 000000000..53f401da0 --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/DataSetWriterConfigurationResponse.cs @@ -0,0 +1,54 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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/ + * ======================================================================*/ + +namespace Opc.Ua.PubSub +{ + /// + /// Data Set Writer Configuration message + /// + public class DataSetWriterConfigurationResponse + { + /// + /// DataSetWriterIds contained in the configuration information. + /// + public ushort[] DataSetWriterIds { get; set; } + + /// + /// The field shall contain only the entry for the requested or changed DataSetWriters in the WriterGroup. + /// + public WriterGroupDataType DataSetWriterConfig { get; set; } + + /// + /// Status codes indicating the capability of the Publisher to provide + /// configuration information for the DataSetWriterIds.The size of the array + /// shall match the size of the DataSetWriterIds array. + /// + public StatusCode[] StatusCodes { get; set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs b/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs new file mode 100644 index 000000000..112463565 --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/DatasetWriterConfigurationEventArgs.cs @@ -0,0 +1,64 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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; + +namespace Opc.Ua.PubSub +{ + /// + /// Class that contains data related to DatasetWriterConfigurationReceived event + /// + public class DataSetWriterConfigurationEventArgs : EventArgs + { + /// + /// Get the ids of the DataSetWriters + /// + public ushort[] DataSetWriterIds { get; internal set; } + + /// + /// Get the received configuration. + /// + public WriterGroupDataType DataSetWriterConfiguration { get; internal set; } + + /// + /// Get the source information + /// + public string Source { get; internal set; } + + /// + /// Get the publisher Id + /// + public object PublisherId { get; internal set; } + + /// + /// Get the statuses code of the DataSetWriter + /// + public StatusCode[] StatusCodes { get; internal set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs b/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs index 049532f53..3e0f74503 100644 --- a/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs +++ b/Libraries/Opc.Ua.PubSub/Encoding/UadpNetworkMessage.cs @@ -50,7 +50,10 @@ public class UadpNetworkMessage : UaNetworkMessage private object m_publisherId; private UADPNetworkMessageType m_uadpNetworkMessageType; private UADPNetworkMessageDiscoveryType m_discoveryType; - private UInt16[] m_dataSetWriterIds; + private ushort[] m_dataSetWriterIds; + + private WriterGroupDataType m_dataSetWriterConfiguration; + private StatusCode[] m_statusCodes; #endregion #region Constructor @@ -90,7 +93,7 @@ public UadpNetworkMessage(WriterGroupDataType writerGroupConfiguration, DataSetM m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; m_discoveryType = UADPNetworkMessageDiscoveryType.DataSetMetaData; - SetFlagsDiscoveryResponseMetaData(); + SetFlagsDiscoveryResponse(); } /// @@ -109,6 +112,47 @@ public UadpNetworkMessage(UADPNetworkMessageDiscoveryType discoveryType) SetFlagsDiscoveryRequest(); } + /// + /// Create new instance of as a DiscoveryResponse of PublisherEndpoints type + /// + /// + /// + public UadpNetworkMessage(EndpointDescription[] publisherEndpoints, StatusCode publisherProvidesEndpoints) + : base(null, new List()) + { + UADPVersion = kUadpVersion; + DataSetClassId = Guid.Empty; + Timestamp = DateTime.UtcNow; + + PublisherEndpoints = publisherEndpoints; + PublisherProvideEndpoints = publisherProvidesEndpoints; + + m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; + m_discoveryType = UADPNetworkMessageDiscoveryType.PublisherEndpoint; + + SetFlagsDiscoveryResponse(); + } + + /// + /// Create new instance of as a DiscoveryResponse of DataSetWriterConfiguration message + /// + public UadpNetworkMessage(ushort[] writerIds, WriterGroupDataType writerConfig, StatusCode[] streamStatusCodes) + : base(null, new List()) + { + UADPVersion = kUadpVersion; + DataSetClassId = Guid.Empty; + Timestamp = DateTime.UtcNow; + + DataSetWriterIds = writerIds; + + m_uadpNetworkMessageType = UADPNetworkMessageType.DiscoveryResponse; + m_discoveryType = UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration; + m_dataSetWriterConfiguration = writerConfig; + m_statusCodes = streamStatusCodes; + + SetFlagsDiscoveryResponse(); + } + #endregion #region Properties @@ -138,7 +182,37 @@ public UADPNetworkMessageDiscoveryType UADPDiscoveryType } /// - /// Get/Set the DataSetWriterIds + /// Get/Set the StatusCodes + /// + public StatusCode[] MessageStatusCodes + { + get + { + return m_statusCodes; + } + set + { + m_statusCodes = value; + } + } + + /// + /// Get the DataSetWriterConfig + /// + public WriterGroupDataType DataSetWriterConfiguration + { + get + { + return m_dataSetWriterConfiguration; + } + set + { + m_dataSetWriterConfiguration = value; + } + } + + /// + /// Discovery DataSetWriter Identifiers /// public UInt16[] DataSetWriterIds { @@ -336,6 +410,20 @@ public object PublisherId #endregion + #region Publisher endpoints + /// + /// Discovery Publisher Endpoints message + /// + internal EndpointDescription[] PublisherEndpoints { get; set; } + + + + /// + /// StatusCode that specifies if a Discovery message provides PublisherEndpoints + /// + internal StatusCode PublisherProvideEndpoints { get; set; } + #endregion + #endregion #region Public Methods @@ -371,38 +459,23 @@ public override byte[] Encode(IServiceMessageContext messageContext) /// The stream to use. public override void Encode(IServiceMessageContext messageContext, Stream stream) { - using (BinaryEncoder encoder = new BinaryEncoder(stream, messageContext, true)) + using (BinaryEncoder binaryEncoder = new BinaryEncoder(stream, messageContext, true)) { if (m_uadpNetworkMessageType == UADPNetworkMessageType.DataSetMessage) { - EncodeDataSetNetworkMessageType(encoder); + EncodeDataSetNetworkMessageType(binaryEncoder); } else { - EncodeNetworkMessageHeader(encoder); + EncodeNetworkMessageHeader(binaryEncoder); if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { - encoder.WriteByte("ResponseType", (byte)m_discoveryType); - // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. - encoder.WriteUInt16("SequenceNumber", SequenceNumber); - - switch (m_discoveryType) - { - case UADPNetworkMessageDiscoveryType.DataSetMetaData: - EncodeDataSetMetaData(encoder); - break; - case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: - case UADPNetworkMessageDiscoveryType.PublisherEndpoint: - // not implemented - break; - } + EncodeDiscoveryResponse(binaryEncoder); } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { - encoder.WriteByte("RequestType", (byte)m_discoveryType); - // write DataSetWriterIds - encoder.WriteUInt16Array("DataSetWriterIds", m_dataSetWriterIds); + EncodeDiscoveryRequest(binaryEncoder); } } } @@ -433,30 +506,15 @@ public override void Decode(IServiceMessageContext context, byte[] message, ILis } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) { - // Decode the Discovery Response Header - m_discoveryType = (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 (m_discoveryType) - { - case UADPNetworkMessageDiscoveryType.DataSetMetaData: - DecodeMetaDataMessage(binaryDecoder); - break; - case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: - case UADPNetworkMessageDiscoveryType.PublisherEndpoint: - // not implemented - break; - } + DecodeDiscoveryResponse(binaryDecoder); } else if (m_uadpNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest) { - // Decode the Discovery Response Header - m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("ResponseType"); - m_dataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")?.ToArray(); + DecodeDiscoveryRequest(binaryDecoder); } } } + #endregion #region Private Methods - Encoding @@ -502,10 +560,48 @@ private void EncodeDataSetMetaData(BinaryEncoder binaryEncoder) } binaryEncoder.WriteEncodeable("MetaData", m_metadata, typeof(DataSetMetaDataType)); - // temporary write StatusCode.Good 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 + { + Trace("The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterId property is missing. Value 0 will be used."); + binaryEncoder.WriteUInt16Array("DataSetWriterIds", new List()); + } + + if (DataSetWriterIds == null) + { + Trace("The UADP DiscoveryResponse DataSetWriterConfiguration message cannot be encoded: The DataSetWriterConfiguration property is missing. Value null will be used."); + } + else + { + binaryEncoder.WriteEncodeable("DataSetWriterConfiguration", DataSetWriterConfiguration, typeof(WriterGroupDataType)); + } + + binaryEncoder.WriteStatusCodeArray("StatusCodes", MessageStatusCodes); + } + + /// + /// Encodes the NetworkMessage as a DiscoveryResponse of EndpointDescription[] Type + /// + /// + private void EncodePublisherEndpoints(BinaryEncoder binaryEncoder) + { + binaryEncoder.WriteEncodeableArray("Endpoints", PublisherEndpoints, typeof(EndpointDescription)); + + binaryEncoder.WriteStatusCode("statusCode", PublisherProvideEndpoints); + } + /// /// Set All flags before encode/decode for a NetworkMessage that contains DataSet messages /// @@ -639,11 +735,10 @@ private void SetFlagsDataSetNetworkMessageType() #endregion } - /// - /// Set All flags before encode/decode for a NetworkMessage that contains A DiscoveryResponse containing data set metadata + /// Set All flags before encode/decode for a NetworkMessage that contains a DiscoveryResponse containing data set metadata /// - private void SetFlagsDiscoveryResponseMetaData() + private void SetFlagsDiscoveryResponse() { /* DiscoveryResponse: * UADPFlags bits 5 and 6 shall be false, bits 4 and 7 shall be true @@ -674,9 +769,6 @@ private void SetFlagsDiscoveryRequest() ExtendedFlags2 = ExtendedFlags2EncodingMask.NetworkMessageWithDiscoveryRequest; } - - - /// /// Decode the stream from decoder parameter and produce a Dataset /// @@ -827,6 +919,38 @@ private void DecodeMetaDataMessage(BinaryDecoder binaryDecoder) } + /// + /// Decode the binaryDecoder content as Endpoints message + /// + /// + private void DecodePublisherEndpoints(BinaryDecoder binaryDecoder) + { + PublisherEndpoints = (EndpointDescription[])binaryDecoder.ReadEncodeableArray("Endpoints", typeof(EndpointDescription)); + + PublisherProvideEndpoints = binaryDecoder.ReadStatusCode("statusCode"); + + Trace("DecodePublisherEndpointsMessage returned: ", PublisherProvideEndpoints); + } + + /// + /// Decode the binaryDecoder content as a DataSetWriterConfiguration message + /// + /// the decoder + private void DecodeDataSetWriterConfigurationMessage(BinaryDecoder binaryDecoder) + { + DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds").ToArray(); + + var dataSetWriterConfigurationDecoded = binaryDecoder.ReadEncodeable("DataSetWriterConfiguration", typeof(WriterGroupDataType)) as WriterGroupDataType; + + DataSetWriterConfiguration = dataSetWriterConfigurationDecoded.MaxNetworkMessageSize != 0 + ? dataSetWriterConfigurationDecoded + : null; + + // temporary write StatusCode.Good + MessageStatusCodes = binaryDecoder.ReadStatusCodeArray("StatusCodes").ToArray(); + Trace("DecodeDataSetWriterConfigurationMessage returned: ", MessageStatusCodes); + } + /// /// Encode Network Message Header /// @@ -931,7 +1055,8 @@ private void EncodePayloadHeader(BinaryEncoder encoder) // Collect DataSetSetMessages headers for (int index = 0; index < DataSetMessages.Count; index++) { - if (DataSetMessages[index] is UadpDataSetMessage uadpDataSetMessage && uadpDataSetMessage.DataSet != null) + UadpDataSetMessage uadpDataSetMessage = DataSetMessages[index] as UadpDataSetMessage; + if (uadpDataSetMessage != null && uadpDataSetMessage.DataSet != null) { encoder.WriteUInt16("DataSetWriterId", uadpDataSetMessage.DataSetWriterId); } @@ -965,7 +1090,7 @@ private void EncodeExtendedNetworkMessageHeader(BinaryEncoder encoder) /// Encode promoted fields /// /// - private static void EncodePromotedFields(BinaryEncoder encoder) + private void EncodePromotedFields(BinaryEncoder encoder) { // todo: Promoted fields not supported } @@ -1035,15 +1160,32 @@ private void EncodeSecurityFooter(BinaryEncoder encoder) } } - /// - /// Encode signature - /// - /// - private static void EncodeSignature(BinaryEncoder encoder) + private void EncodeDiscoveryResponse(BinaryEncoder binaryEncoder) { - // encoder.WriteByteArray("Signature", Signature); + binaryEncoder.WriteByte("ResponseType", (byte)m_discoveryType); + // A strictly monotonically increasing sequence number assigned to each discovery response sent in the scope of a PublisherId. + binaryEncoder.WriteUInt16("SequenceNumber", SequenceNumber); + + switch (m_discoveryType) + { + case UADPNetworkMessageDiscoveryType.DataSetMetaData: + EncodeDataSetMetaData(binaryEncoder); + break; + case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: + EncodeDataSetWriterConfiguration(binaryEncoder); + break; + case UADPNetworkMessageDiscoveryType.PublisherEndpoint: + EncodePublisherEndpoints(binaryEncoder); + break; + } } + private void EncodeDiscoveryRequest(BinaryEncoder binaryEncoder) + { + // RequestType => InformationType + binaryEncoder.WriteByte("RequestType", (byte)m_discoveryType); + binaryEncoder.WriteUInt16Array("DataSetWriterIds", DataSetWriterIds); + } #endregion #region Private Methods - Decoding @@ -1206,7 +1348,7 @@ private void DecodeExtendedNetworkMessageHeader(BinaryDecoder decoder) /// Decode promoted fields /// /// - private static void DecodePromotedFields(BinaryDecoder decoder) + private void DecodePromotedFields(BinaryDecoder decoder) { // todo: Promoted fields not supported } @@ -1229,7 +1371,8 @@ private void DecodePayloadSize(BinaryDecoder decoder) } } } - if (decoder is BinaryDecoder binaryDecoder) + BinaryDecoder binaryDecoder = decoder as BinaryDecoder; + if (binaryDecoder != null) { int offset = 0; // set start position of dataset message in binary stream @@ -1263,24 +1406,37 @@ private void DecodeSecurityHeader(BinaryDecoder decoder) } /// - /// Decode security footer + /// Decode the Discovery Request Header /// - /// - private void DecodeSecurityFooter(BinaryDecoder decoder) + /// + private void DecodeDiscoveryRequest(BinaryDecoder binaryDecoder) { - if ((SecurityFlags & SecurityFlagsEncodingMask.SecurityFooter) != 0) - { - SecurityFooter = decoder.ReadByteArray("SecurityFooter").ToArray(); - } + m_discoveryType = (UADPNetworkMessageDiscoveryType)binaryDecoder.ReadByte("RequestType"); + DataSetWriterIds = binaryDecoder.ReadUInt16Array("DataSetWriterIds")?.ToArray(); } /// - /// Decode signature + /// Decode the Discovery Response Header /// - /// - private static void DecodeSignature(BinaryDecoder decoder) + /// + private void DecodeDiscoveryResponse(BinaryDecoder binaryDecoder) { - // Signature = decoder.ReadByteArray("Signature").ToArray(); + m_discoveryType = (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 (m_discoveryType) + { + case UADPNetworkMessageDiscoveryType.DataSetMetaData: + DecodeMetaDataMessage(binaryDecoder); + break; + case UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration: + DecodeDataSetWriterConfigurationMessage(binaryDecoder); + break; + case UADPNetworkMessageDiscoveryType.PublisherEndpoint: + DecodePublisherEndpoints(binaryDecoder); + break; + } } #endregion } diff --git a/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs b/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs new file mode 100644 index 000000000..6d5bf8b7b --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/PublisherEndpointsEventArgs.cs @@ -0,0 +1,59 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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; + +namespace Opc.Ua.PubSub +{ + /// + /// Class that contains data related to PublisherEndpoints event + /// + public class PublisherEndpointsEventArgs : EventArgs + { + /// + /// Get the received Publisher identifier. + /// + public object PublisherId { get; internal set; } + + /// + /// Get the source information + /// + public string Source { get; internal set; } + + /// + /// Get the received Publisher Endpoints. + /// + public EndpointDescription[] PublisherEndpoints { get; internal set; } + + /// + /// Get the status code of the DataSetWriter + /// + public StatusCode StatusCode { get; internal set; } + } +} diff --git a/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs b/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs new file mode 100644 index 000000000..cd6dedd1e --- /dev/null +++ b/Libraries/Opc.Ua.PubSub/Transport/IUadpDiscoveryMessages.cs @@ -0,0 +1,103 @@ +/* ======================================================================== + * Copyright (c) 2005-2021 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; + +namespace Opc.Ua.PubSub +{ + /// + /// UADP Discovery messages interface + /// + public interface IUadpDiscoveryMessages + { + /// + /// Set GetPublisherEndpoints callback used by the subscriber to receive PublisherEndpoints data from publisher + /// + /// + void GetPublisherEndpointsCallback(GetPublisherEndpointsEventHandler eventHandler); + + /// + /// Set GetDataSetWriterIds callback used by the subscriber to receive DataSetWriter ids from publisher + /// + /// + void GetDataSetWriterConfigurationCallback(GetDataSetWriterIdsEventHandler eventHandler); + + /// + /// Create and return the list of EndpointDescription to be used only by UADP Discovery response messages + /// + /// + /// + /// + /// + UaNetworkMessage CreatePublisherEndpointsNetworkMessage(EndpointDescription[] endpoints, + StatusCode publisherProvideEndpointsStatusCode, object publisherId); + + /// + /// Create and return the list of DataSetMetaData response messages + /// + /// + /// + IList CreateDataSetMetaDataNetworkMessages(UInt16[] dataSetWriterIds); + + /// + /// Create and return the list of DataSetWriterConfiguration response message + /// + /// DatasetWriter ids + /// + IList CreateDataSetWriterCofigurationMessage(UInt16[] dataSetWriterIds); + + /// + /// Request UADP Discovery DataSetWriterConfiguration messages + /// + void RequestDataSetWriterConfiguration(); + + /// + /// Request UADP Discovery DataSetMetaData messages + /// + void RequestDataSetMetaData(); + + /// + /// Request UADP Discovery Publisher endpoints only + /// + void RequestPublisherEndpoints(); + } + + /// + /// Get PublisherEndpoints event handler + /// + /// + public delegate IList GetPublisherEndpointsEventHandler(); + + /// + /// Get DataSetWriterConfiguration ids event handler + /// + /// + public delegate IList GetDataSetWriterIdsEventHandler(UaPubSubApplication uaPubSubApplication); +} diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs index 326807f87..706d5340b 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoveryPublisher.cs @@ -33,6 +33,7 @@ using System.Net.Sockets; using System.Threading.Tasks; using Opc.Ua.PubSub.Encoding; +using System.Linq; namespace Opc.Ua.PubSub.Transport { @@ -41,11 +42,13 @@ namespace Opc.Ua.PubSub.Transport /// internal class UdpDiscoveryPublisher : UdpDiscovery { + #region Private fields + // Minimum response interval private const int kMinimumResponseInterval = 500; // The list that will store the WriterIds that shall be set as DataSetMetaData Response message private readonly List m_metadataWriterIdsToSend; - private int m_responseInterval = kMinimumResponseInterval; + #endregion #region Constructor /// @@ -86,7 +89,6 @@ public override async Task StartAsync(IServiceMessageContext messageContext) } } } - #endregion #region Private Methods @@ -97,8 +99,9 @@ public override async Task StartAsync(IServiceMessageContext messageContext) private void OnUadpDiscoveryReceive(IAsyncResult result) { // this is what had been passed into BeginReceive as the second parameter: + UdpClient socket = result.AsyncState as UdpClient; - if (!(result.AsyncState is UdpClient socket)) + if (socket == null) { return; } @@ -174,14 +177,30 @@ private void ProcessReceivedMessageDiscovery(byte[] messageBytes, IPEndPoint sou } } - Task.Run(SendResponseDataSetMetaDataAsync).ConfigureAwait(false); + Task.Run(SendResponseDataSetMetaData).ConfigureAwait(false); + } + + else if (networkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest + && networkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.PublisherEndpoint) + { + Task.Run(SendResponsePublisherEndpoints).ConfigureAwait(false); + } + + else if (networkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryRequest + && networkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration + && networkMessage.DataSetWriterIds != null) + { + Task.Run(SendResponseDataSetWriterConfiguration).ConfigureAwait(false); } } - private async Task SendResponseDataSetMetaDataAsync() - { - await Task.Delay(m_responseInterval).ConfigureAwait(false); + /// + /// Sends a DataSetMetadata discovery response message + /// + private async Task SendResponseDataSetMetaData() + { + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); lock (m_lock) { if (m_metadataWriterIdsToSend.Count > 0) @@ -190,7 +209,7 @@ private async Task SendResponseDataSetMetaDataAsync() foreach (UaNetworkMessage message in responseMessages) { - Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetMetaData Before sending message for DataSetWriterId:{0}", message.DataSetWriterId); + Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetMetaData before sending message for DataSetWriterId:{0}", message.DataSetWriterId); m_udpConnection.PublishNetworkMessage(message); } @@ -199,6 +218,61 @@ private async Task SendResponseDataSetMetaDataAsync() } } + /// + /// Sends a DataSetWriterConfiguration discovery response message + /// + private async Task SendResponseDataSetWriterConfiguration() + { + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); + lock (m_lock) + { + IList dataSetWriterIdsToSend = new List(); + if (GetDataSetWriterIds != null) + { + dataSetWriterIdsToSend = GetDataSetWriterIds.Invoke(m_udpConnection.Application); + } + + if (dataSetWriterIdsToSend.Count > 0) + { + IList responsesMessages = m_udpConnection.CreateDataSetWriterCofigurationMessage( + dataSetWriterIdsToSend.ToArray()); + + foreach (var responsesMessage in responsesMessages) + { + Utils.Trace("UdpDiscoveryPublisher.SendResponseDataSetWriterConfiguration Before sending message for DataSetWriterId:{0}", responsesMessage.DataSetWriterId); + + m_udpConnection.PublishNetworkMessage(responsesMessage); + } + } + } + } + + /// + /// Send response PublisherEndpoints + /// + private async Task SendResponsePublisherEndpoints() + { + await Task.Delay(kMinimumResponseInterval).ConfigureAwait(false); + + lock (m_lock) + { + IList publisherEndpointsToSend = new List(); + if (GetPublisherEndpoints != null) + { + publisherEndpointsToSend = GetPublisherEndpoints.Invoke(); + } + + UaNetworkMessage message = m_udpConnection.CreatePublisherEndpointsNetworkMessage( + publisherEndpointsToSend.ToArray(), + publisherEndpointsToSend.Count > 0 ? StatusCodes.Good : StatusCodes.BadNotFound, + m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value); + + Utils.Trace("UdpDiscoveryPublisher.SendResponsePublisherEndpoints before sending message for PublisherEndpoints."); + + m_udpConnection.PublishNetworkMessage(message); + } + } + /// /// Re initializes the socket /// @@ -230,5 +304,19 @@ private void Renew(UdpClient socket) } } #endregion + + #region Public Properties + + /// + /// The GetPublisherEndpoints event callback reference to store the EndpointDescription[] to be set as PublisherEndpoints Response message + /// + public GetPublisherEndpointsEventHandler GetPublisherEndpoints { get; set; } + + /// + /// The GetDataSetWriterIds event callback reference to store the DataSetWriter ids to be set as PublisherEndpoints Response message + /// + public GetDataSetWriterIdsEventHandler GetDataSetWriterIds { get; set; } + + #endregion } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs index dc5e45f2b..0d7f0f992 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpDiscoverySubscriber.cs @@ -29,6 +29,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; using System.Threading.Tasks; using Opc.Ua.PubSub.Encoding; @@ -39,6 +41,7 @@ namespace Opc.Ua.PubSub.Transport /// internal class UdpDiscoverySubscriber : UdpDiscovery { + #region Private Fields private const int kInitialRequestInterval = 5000; // The list that will store the WriterIds that shall be included in a DataSetMetaData Request message @@ -46,6 +49,7 @@ internal class UdpDiscoverySubscriber : UdpDiscovery // the component that triggers the publish request messages private readonly IntervalRunner m_intervalRunner; + #endregion #region Constructor /// @@ -57,12 +61,12 @@ public UdpDiscoverySubscriber(UdpPubSubConnection udpConnection) : base(udpConne m_metadataWriterIdsToSend = new List(); m_intervalRunner = new IntervalRunner(udpConnection.PubSubConnectionConfiguration.Name, - kInitialRequestInterval, CanPublish, SendDiscoveryRequestDataSetMetaData); + kInitialRequestInterval, CanPublish, RequestDiscoveryMessages); } #endregion - #region Start/Stop Method Overides + #region Start/Stop Method Overrides /// /// Implementation of StartAsync for the subscriber Discovery @@ -105,7 +109,7 @@ public void AddWriterIdForDataSetMetadata(UInt16 writerId) } /// - /// Removes the specfoed DataSetWriterId for DataSetInformation to be requested + /// Removes the specified DataSetWriterId for DataSetInformation to be requested /// /// public void RemoveWriterIdForDataSetMetadata(UInt16 writerId) @@ -118,33 +122,91 @@ public void RemoveWriterIdForDataSetMetadata(UInt16 writerId) } } } - #endregion + /// + /// Send a discovery Request for DataSetWriterConfiguration + /// + public void SendDiscoveryRequestDataSetWriterConfiguration() + { + ushort[] dataSetWriterIds = m_udpConnection.PubSubConnectionConfiguration.ReaderGroups? + .SelectMany(group => group.DataSetReaders)? + .Select(group => group.DataSetWriterId)? + .ToArray(); - #region Private Methods + UadpNetworkMessage discoveryRequestDataSetWriterConfiguration = new UadpNetworkMessage(UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration) { + DataSetWriterIds = dataSetWriterIds, + PublisherId = m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value, + }; + + byte[] bytes = discoveryRequestDataSetWriterConfiguration.Encode(MessageContext); + + // send the Discovery request message to all open UADPClient + foreach (UdpClient udpClient in m_discoveryUdpClients) + { + try + { + Utils.Trace("UdpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration message"); + udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); + } + catch (Exception ex) + { + Utils.Trace(ex, "UdpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration"); + } + } + + // double the time between requests + m_intervalRunner.Interval = m_intervalRunner.Interval * 2; + } /// - /// Decide if there is anything to publish + /// Updates the dataset writer configuration /// - /// - private bool CanPublish() + /// the configuration + public void UpdateDataSetWriterConfiguration(WriterGroupDataType writerConfig) { - lock (m_lock) + WriterGroupDataType writerGroup = m_udpConnection.PubSubConnectionConfiguration.WriterGroups? + .Find(x => x.WriterGroupId == writerConfig.WriterGroupId); + if (writerGroup != null) { - if (m_metadataWriterIdsToSend.Count == 0) + int index = m_udpConnection.PubSubConnectionConfiguration.WriterGroups.IndexOf(writerGroup); + m_udpConnection.PubSubConnectionConfiguration.WriterGroups[index] = writerConfig; + } + } + + /// + /// Send a discovery Request for PublisherEndpoints + /// + public void SendDiscoveryRequestPublisherEndpoints() + { + UadpNetworkMessage discoveryRequestPublisherEndpoints = new UadpNetworkMessage(UADPNetworkMessageDiscoveryType.PublisherEndpoint); + discoveryRequestPublisherEndpoints.PublisherId = m_udpConnection.PubSubConnectionConfiguration.PublisherId.Value; + + byte[] bytes = discoveryRequestPublisherEndpoints.Encode(MessageContext); + + // send the PublisherEndpoints DiscoveryRequest message to all open UdpClients + foreach (var udpClient in m_discoveryUdpClients) + { + try { - // reset the interval for publisher if there is nothing to send - m_intervalRunner.Interval = kInitialRequestInterval; - } + Utils.Trace("UdpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints message for PublisherId: {0}", + discoveryRequestPublisherEndpoints.PublisherId); - return m_metadataWriterIdsToSend.Count > 0; + udpClient.Send(bytes, bytes.Length, DiscoveryNetworkAddressEndPoint); + } + catch (Exception ex) + { + Utils.Trace(ex, "UdpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints"); + } } + + // double the time between requests + m_intervalRunner.Interval *= 2; } /// /// Create and Send the DiscoveryRequest messages for DataSetMetaData /// - private void SendDiscoveryRequestDataSetMetaData() + public void SendDiscoveryRequestDataSetMetaData() { UInt16[] dataSetWriterIds = null; lock (m_lock) @@ -186,5 +248,34 @@ private void SendDiscoveryRequestDataSetMetaData() m_intervalRunner.Interval = m_intervalRunner.Interval * 2; } #endregion + + #region Private Methods + /// + /// Decide if there is anything to publish + /// + /// + private bool CanPublish() + { + lock (m_lock) + { + if (m_metadataWriterIdsToSend.Count == 0) + { + // reset the interval for publisher if there is nothing to send + m_intervalRunner.Interval = kInitialRequestInterval; + } + + return m_metadataWriterIdsToSend.Count > 0; + } + } + + /// + /// Joint task to request discovery messages + /// + private void RequestDiscoveryMessages() + { + SendDiscoveryRequestDataSetMetaData(); + SendDiscoveryRequestDataSetWriterConfiguration(); + } + #endregion } } diff --git a/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs index e8c312886..9b207ba0d 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/UdpPubSubConnection.cs @@ -42,17 +42,15 @@ namespace Opc.Ua.PubSub.Transport /// /// UADP implementation of class. /// - internal class UdpPubSubConnection : UaPubSubConnection + internal class UdpPubSubConnection : UaPubSubConnection, IUadpDiscoveryMessages { #region Private Fields private List m_publisherUdpClients = new List(); private List m_subscriberUdpClients = new List(); private UdpDiscoverySubscriber m_udpDiscoverySubscriber; private UdpDiscoveryPublisher m_udpDiscoveryPublisher; - private static int s_sequenceNumber = 0; private static int s_dataSetSequenceNumber = 0; - #endregion #region Constructor @@ -71,6 +69,12 @@ public UdpPubSubConnection(UaPubSubApplication uaPubSubApplication, PubSubConnec #endregion #region Public Properties + + /// + /// Get or set the event handler + /// + public GetPublisherEndpointsEventHandler GetPublisherEndpoints { get; set; } + /// /// Get the NetworkInterface name from configured .Address. /// @@ -139,7 +143,8 @@ protected override async Task InternalStart() await m_udpDiscoverySubscriber.StartAsync(MessageContext).ConfigureAwait(false); // add handler to metaDataReceived event - this.Application.MetaDataReceived += Application_MetaDataReceived; + this.Application.MetaDataReceived += MetaDataReceived; + this.Application.DataSetWriterConfigurationReceived += DataSetWriterConfigurationReceived; } } @@ -174,7 +179,7 @@ protected override async Task InternalStop() await m_udpDiscoverySubscriber.StopAsync().ConfigureAwait(false); // remove handler to metaDataReceived event - this.Application.MetaDataReceived -= Application_MetaDataReceived; + this.Application.MetaDataReceived -= MetaDataReceived; } } @@ -183,13 +188,17 @@ protected override async Task InternalStop() /// public override IList CreateNetworkMessages(WriterGroupDataType writerGroupConfiguration, WriterGroupPublishState state) { - if (!(ExtensionObject.ToEncodeable(writerGroupConfiguration.MessageSettings) is UadpWriterGroupMessageDataType messageSettings)) + UadpWriterGroupMessageDataType messageSettings = ExtensionObject.ToEncodeable(writerGroupConfiguration.MessageSettings) + as UadpWriterGroupMessageDataType; + if (messageSettings == null) { //Wrong configuration of writer group MessageSettings return null; } + DatagramWriterGroupTransportDataType transportSettings = ExtensionObject.ToEncodeable(writerGroupConfiguration.TransportSettings) + as DatagramWriterGroupTransportDataType; - if (!(ExtensionObject.ToEncodeable(writerGroupConfiguration.TransportSettings) is DatagramWriterGroupTransportDataType transportSettings)) + if (transportSettings == null) { //Wrong configuration of writer group TransportSettings return null; @@ -218,8 +227,10 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp }); } + UadpDataSetWriterMessageDataType dataSetMessageSettings = ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) as + UadpDataSetWriterMessageDataType; // check MessageSettings to see how to encode DataSet - if (ExtensionObject.ToEncodeable(dataSetWriter.MessageSettings) is UadpDataSetWriterMessageDataType dataSetMessageSettings) + if (dataSetMessageSettings != null) { UadpDataSetMessage uadpDataSetMessage = new UadpDataSetMessage(dataSet); uadpDataSetMessage.DataSetWriterId = dataSetWriter.DataSetWriterId; @@ -269,7 +280,7 @@ public override IList CreateNetworkMessages(WriterGroupDataTyp public IList CreateDataSetMetaDataNetworkMessages(UInt16[] dataSetWriterIds) { List networkMessages = new List(); - var writers = GetAllDataSetWriters(); + var writers = GetWriterGroupsDataType(); foreach (UInt16 dataSetWriterId in dataSetWriterIds) { @@ -295,6 +306,31 @@ public IList CreateDataSetMetaDataNetworkMessages(UInt16[] dat return networkMessages; } + /// + /// Create and return the list of DataSetWriterConfiguration response message + /// + /// DatasetWriter ids + /// + public IList CreateDataSetWriterCofigurationMessage(UInt16[] dataSetWriterIds) + { + List networkMessages = new List(); + + IList responses = GetDataSetWriterDiscoveryResponses(dataSetWriterIds); + + foreach (DataSetWriterConfigurationResponse response in responses) + { + UadpNetworkMessage networkMessage = new UadpNetworkMessage(response.DataSetWriterIds, + response.DataSetWriterConfig, + response.StatusCodes); + + networkMessage.PublisherId = PubSubConnectionConfiguration.PublisherId.Value; + networkMessage.MessageStatusCodes.ToList().AddRange(response.StatusCodes); + networkMessages.Add(networkMessage); + } + + return networkMessages; + } + /// /// Publish the network message /// @@ -348,6 +384,94 @@ public override bool AreClientsConnected() { return true; } + + #region IUadpDiscoveryMessages interface methods + + /// + /// Set GetPublisherEndpoints callback used by the subscriber to receive PublisherEndpoints data from publisher + /// + /// + public void GetPublisherEndpointsCallback(GetPublisherEndpointsEventHandler getPubliherEndpoints) + { + if (m_udpDiscoveryPublisher != null) + { + m_udpDiscoveryPublisher.GetPublisherEndpoints = getPubliherEndpoints; + } + } + + /// + /// Set GetDataSetWriterConfiguration callback used by the subscriber to receive DataSetWriter ids from publisher + /// + /// + public void GetDataSetWriterConfigurationCallback(GetDataSetWriterIdsEventHandler getDataSetWriterIds) + { + if (m_udpDiscoveryPublisher != null) + { + m_udpDiscoveryPublisher.GetDataSetWriterIds = getDataSetWriterIds; + } + } + + /// + /// Create and return the list of EndpointDescription response messages + /// To be used only by UADP Discovery response messages + /// + /// + /// + /// + /// + public UaNetworkMessage CreatePublisherEndpointsNetworkMessage(EndpointDescription[] endpoints, + StatusCode publisherProvideEndpointsStatusCode, object publisherId) + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport) + { + UadpNetworkMessage networkMessage = new UadpNetworkMessage(endpoints, publisherProvideEndpointsStatusCode); + networkMessage.PublisherId = publisherId; + + return networkMessage; + } + + return null; + } + + /// + /// Request UADP Discovery Publisher endpoints only + /// + public void RequestPublisherEndpoints() + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport && + m_udpDiscoverySubscriber != null) + { + // send discovery request publisher endpoints here for now + m_udpDiscoverySubscriber.SendDiscoveryRequestPublisherEndpoints(); + } + } + + /// + /// Request UADP Discovery DataSetWriterConfiguration messages + /// + public void RequestDataSetWriterConfiguration() + { + if (PubSubConnectionConfiguration != null && + PubSubConnectionConfiguration.TransportProfileUri == Profiles.PubSubUdpUadpTransport && + m_udpDiscoverySubscriber != null) + { + m_udpDiscoverySubscriber.SendDiscoveryRequestDataSetWriterConfiguration(); + } + } + + /// + /// Request DataSetMetaData + /// + public void RequestDataSetMetaData() + { + if (m_udpDiscoverySubscriber != null) + { + m_udpDiscoverySubscriber.SendDiscoveryRequestDataSetMetaData(); + } + } + #endregion #endregion #region Private methods @@ -356,7 +480,9 @@ public override bool AreClientsConnected() /// private void Initialize() { - if (!(ExtensionObject.ToEncodeable(PubSubConnectionConfiguration.Address) is NetworkAddressUrlDataType networkAddressUrlState)) + NetworkAddressUrlDataType networkAddressUrlState = ExtensionObject.ToEncodeable(PubSubConnectionConfiguration.Address) + as NetworkAddressUrlDataType; + if (networkAddressUrlState == null) { Utils.Trace(Utils.TraceMasks.Error, "The configuration for connection {0} has invalid Address configuration.", PubSubConnectionConfiguration.Name); @@ -427,8 +553,9 @@ private void OnUadpReceive(IAsyncResult result) } // this is what had been passed into BeginReceive as the second parameter: + UdpClient socket = result.AsyncState as UdpClient; - if (!(result.AsyncState is UdpClient socket)) + if (socket == null) { return; } @@ -538,7 +665,7 @@ internal static void ResetSequenceNumber() /// /// /// - private void Application_MetaDataReceived(object sender, SubscribedDataEventArgs e) + private void MetaDataReceived(object sender, SubscribedDataEventArgs e) { if (m_udpDiscoverySubscriber != null && e.NetworkMessage.DataSetWriterId != null) { @@ -546,6 +673,24 @@ private void Application_MetaDataReceived(object sender, SubscribedDataEventArgs } } + + /// + /// Handler for DatasetWriterConfigurationReceived event. + /// + /// + /// + private void DataSetWriterConfigurationReceived(object sender, DataSetWriterConfigurationEventArgs e) + { + lock (Lock) + { + WriterGroupDataType config = e.DataSetWriterConfiguration; + if (e.DataSetWriterConfiguration != null) + { + m_udpDiscoverySubscriber.UpdateDataSetWriterConfiguration(config); + } + } + } + /// /// Handle event. /// diff --git a/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs b/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs index e961e7615..74e8f5514 100644 --- a/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs +++ b/Libraries/Opc.Ua.PubSub/UaPubSubApplication.cs @@ -64,11 +64,22 @@ public class UaPubSubApplication : IDisposable /// public event EventHandler MetaDataReceived; + /// + /// Event that is triggered when the receives and decodes subscribed DataSet PublisherEndpoints + /// + public event EventHandler PublisherEndpointsReceived; + /// /// Event that is triggered before the configuration is updated with a new MetaData /// The configuration will not be updated if flag is set on true. /// public event EventHandler ConfigurationUpdating; + + /// + /// Event that is triggered when the receives and decodes subscribed DataSet MetaData + /// + public event EventHandler DataSetWriterConfigurationReceived; + #endregion #region Event Callbacks @@ -309,6 +320,43 @@ internal void RaiseMetaDataReceivedEvent(SubscribedDataEventArgs e) Utils.Trace(ex, "UaPubSubApplication.RaiseMetaDataReceivedEvent"); } } + /// + /// Raise DatasetWriterConfigurationReceived event + /// + /// + internal void RaiseDatasetWriterConfigurationReceivedEvent(DataSetWriterConfigurationEventArgs e) + { + try + { + if (DataSetWriterConfigurationReceived != null) + { + DataSetWriterConfigurationReceived(this, e); + } + } + catch (Exception ex) + { + Utils.Trace(ex, "UaPubSubApplication.DatasetWriterConfigurationReceivedEvent"); + } + } + + /// + /// Raise PublisherEndpointsReceived event + /// + /// + internal void RaisePublisherEndpointsReceivedEvent(PublisherEndpointsEventArgs e) + { + try + { + if (PublisherEndpointsReceived != null) + { + PublisherEndpointsReceived(this, e); + } + } + catch (Exception ex) + { + Utils.Trace(ex, "UaPubSubApplication.RaisePublisherEndpointsReceivedEvent"); + } + } /// /// Raise event diff --git a/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs index 540955be1..af1969afd 100644 --- a/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Opc.Ua.PubSub.Configuration; using Opc.Ua.PubSub.PublishedData; @@ -294,7 +295,7 @@ public List GetOperationalDataSetReaders() /// /// Processes the decoded and - /// raises the or event. + /// raises the or or or event. /// /// The network message that was received. /// The source of the received event. @@ -375,9 +376,50 @@ protected void ProcessDecodedNetworkMessage(UaNetworkMessage networkMessage, str source, subscribedDataEventArgs.NetworkMessage.DataSetMessages.Count); } - else + else if (networkMessage is Encoding.UadpNetworkMessage) { - Utils.Trace("Connection '{0}' - RaiseNetworkMessageDataReceivedEvent() message from source={0} cannot be decoded.", source); + Encoding.UadpNetworkMessage uadpNetworkMessage = networkMessage as Encoding.UadpNetworkMessage; + + if (uadpNetworkMessage != null) + { + if (uadpNetworkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.DataSetWriterConfiguration && + uadpNetworkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) + { + DataSetWriterConfigurationEventArgs eventArgs = new DataSetWriterConfigurationEventArgs() { + DataSetWriterIds = uadpNetworkMessage.DataSetWriterIds, + Source = source, + DataSetWriterConfiguration = uadpNetworkMessage.DataSetWriterConfiguration, + PublisherId = uadpNetworkMessage.PublisherId, + StatusCodes = uadpNetworkMessage.MessageStatusCodes + }; + + //trigger notification for received configuration + Application.RaiseDatasetWriterConfigurationReceivedEvent(eventArgs); + + Utils.Trace( + "Connection '{0}' - RaiseDataSetWriterConfigurationReceivedEvent() from source={0}, with {1} DataSetWriterConfiguration", + source, + eventArgs.DataSetWriterIds.Count()); + } + else if (uadpNetworkMessage.UADPDiscoveryType == UADPNetworkMessageDiscoveryType.PublisherEndpoint && + uadpNetworkMessage.UADPNetworkMessageType == UADPNetworkMessageType.DiscoveryResponse) + { + PublisherEndpointsEventArgs publisherEndpointsEventArgs = new PublisherEndpointsEventArgs() { + PublisherEndpoints = uadpNetworkMessage.PublisherEndpoints, + Source = source, + PublisherId = uadpNetworkMessage.PublisherId, + StatusCode = uadpNetworkMessage.PublisherProvideEndpoints + }; + + //trigger notification for received publisher endpoints + Application.RaisePublisherEndpointsReceivedEvent(publisherEndpointsEventArgs); + + Utils.Trace( + "Connection '{0}' - RaisePublisherEndpointsReceivedEvent() from source={0}, with {1} PublisherEndpoints", + source, + publisherEndpointsEventArgs.PublisherEndpoints.Length); + } + } } } @@ -400,7 +442,7 @@ protected List GetAllDataSetReaders() /// /// Get all dataset writers defined for this UaPublisher component /// - protected List GetAllDataSetWriters() + protected List GetWriterGroupsDataType() { List writerList = new List(); @@ -414,6 +456,44 @@ protected List GetAllDataSetWriters() return writerList; } + /// + /// Get data set writer discovery responses + /// + protected IList GetDataSetWriterDiscoveryResponses(UInt16[] dataSetWriterIds) + { + List responses = new List(); + + List writerGroupsIds = m_pubSubConnectionDataType.WriterGroups + .SelectMany(group => group.DataSetWriters) + .Select(writer => writer.DataSetWriterId) + .ToList(); + + foreach (var dataSetWriterId in dataSetWriterIds) + { + DataSetWriterConfigurationResponse response = new DataSetWriterConfigurationResponse(); + + if (!writerGroupsIds.Contains(dataSetWriterId)) + { + response.DataSetWriterIds = new ushort[] { dataSetWriterId }; + + response.StatusCodes = new StatusCode[] { StatusCodes.BadNotFound }; + } + else + { + response.DataSetWriterConfig = m_pubSubConnectionDataType.WriterGroups + .First(group => group.DataSetWriters.First(writer => writer.DataSetWriterId == dataSetWriterId) != null); + + response.DataSetWriterIds = new ushort[] { dataSetWriterId }; + + response.StatusCodes = new StatusCode[] { StatusCodes.Good }; + } + + responses.Add(response); + } + + return responses; + } + /// /// Get the maximum KeepAlive value from all present WriterGroups /// @@ -459,7 +539,7 @@ protected DataSet CreateDataSet(DataSetWriterDataType dataSetWriter, WriterGroup return dataSet; } - #endregion + #endregion #region Private Methods /// diff --git a/Libraries/Opc.Ua.PubSub/UaPublisher.cs b/Libraries/Opc.Ua.PubSub/UaPublisher.cs index 1c1558667..659e6fb7e 100644 --- a/Libraries/Opc.Ua.PubSub/UaPublisher.cs +++ b/Libraries/Opc.Ua.PubSub/UaPublisher.cs @@ -39,7 +39,7 @@ internal class UaPublisher : IUaPublisher { #region Fields private readonly object m_lock = new object(); - + private readonly IUaPubSubConnection m_pubSubConnection; private readonly WriterGroupDataType m_writerGroupConfiguration; private readonly WriterGroupPublishState m_writerGroupPublishState; @@ -69,7 +69,7 @@ internal UaPublisher(IUaPubSubConnection pubSubConnection, WriterGroupDataType w m_writerGroupPublishState = new WriterGroupPublishState(); m_intervalRunner = new IntervalRunner(m_writerGroupConfiguration.Name, m_writerGroupConfiguration.PublishingInterval, CanPublish, PublishMessages); - + } #endregion @@ -141,7 +141,7 @@ public virtual void Stop() #endregion #region Private Methods - + /// /// Decide if the connection can publish /// @@ -155,7 +155,7 @@ private bool CanPublish() } /// - /// Generate and publish a messages + /// Generate and publish the messages /// private void PublishMessages() { @@ -174,6 +174,7 @@ private void PublishMessages() } } } + } catch (Exception e) { diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs index 6725ec64b..34f0d448e 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Publisher.cs @@ -28,8 +28,6 @@ * ======================================================================*/ using NUnit.Framework; -using Opc.Ua; -using Opc.Ua.PubSub; using Opc.Ua.PubSub.Configuration; using Opc.Ua.PubSub.Transport; using System; @@ -51,7 +49,7 @@ public partial class UdpPubSubConnectionTests #endif public void ValidateUdpPubSubConnectionNetworkMessagePublishUnicast() { - //Arrange + //Arrange var localhost = GetFirstNic(); Assert.IsNotNull(localhost, "localhost is null"); Assert.IsNotNull(localhost.Address, "localhost.Address is null"); @@ -92,7 +90,7 @@ public void ValidateUdpPubSubConnectionNetworkMessagePublishUnicast() Assert.IsNotNull(networkMessages, "connection.CreateNetworkMessages shall not return null"); //Act - publisherConnection.Start(); + publisherConnection.Start(); if (networkMessages != null) { @@ -271,11 +269,11 @@ public void ValidateUdpPubSubConnectionNetworkMessagePublishMulticast() } } - [Test(Description = "Validate discovery request PublishNetworkMessage"), Order(4)] + [Test(Description = "Validate discovery request PublishNetworkMessage for a DataSetMetaData"), Order(4)] #if !CUSTOM_TESTS [Ignore("A network interface controller is necessary in order to run correctly.")] #endif - public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_DataSetMetadata() { //Arrange var localhost = GetFirstNic(); @@ -289,7 +287,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() Assert.Greater(publisherConfiguration.Connections.Count, 1, "publisherConfiguration.Connection should be > 0"); //discovery IP adress 224.0.2.14 - IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); + IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); IPAddress multicastIPAddress = multicastIPAddresses.First(); Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); @@ -300,7 +298,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() //create publisher UaPubSubApplication with changed configuration settings UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); Assert.IsNotNull(publisherApplication, "publisherApplication is null"); - + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; Assert.IsNotNull(publisherConnection, "publisherConnection is null"); @@ -342,6 +340,172 @@ public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish() noMessageReceived = true; } + publisherConnection.Stop(); + udpMulticastClient.Close(); + udpMulticastClient.Dispose(); + + if (noMessageReceived) + { + Assert.Fail("The UDP message was not received"); + } + } + + + [Test(Description = "Validate discovery DataSetWriterConfigurationMessage response"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_DataSetWriterConfiguration() + { + //Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //create publisher configuration object with modified port + string configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + Assert.Greater(publisherConfiguration.Connections.Count, 1, "publisherConfiguration.Connection should be > 0"); + + //discovery IP adress 224.0.2.14 + IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); + IPAddress multicastIPAddress = multicastIPAddresses.First(); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections[0].Address = new ExtensionObject(publisherAddress); + + //create publisher UaPubSubApplication with changed configuration settings + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + // will signal that the uadp message was received from local ip + m_shutdownEvent = new ManualResetEvent(false); + + //setup uadp client for receiving from multicast (simulate a subscriber multicast) + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, kDiscoveryPortNo); + udpMulticastClient.BeginReceive(new AsyncCallback(OnReceive), udpMulticastClient); + + // prepare a network message + WriterGroupDataType writerGroup0 = publisherConnection.PubSubConnectionConfiguration.WriterGroups.First(); + List dataSetWriterIds = new List(); + foreach (DataSetWriterDataType dataSetWriterDataType in writerGroup0.DataSetWriters) + { + dataSetWriterIds.Add(dataSetWriterDataType.DataSetWriterId); + } + UaNetworkMessage networkMessage = publisherConnection.CreateDataSetWriterCofigurationMessage(dataSetWriterIds.ToArray()).First(); + Assert.IsNotNull(networkMessage, "connection.CreateDataSetWriterCofigurationMessages shall not return null"); + + //Act + publisherConnection.Start(); + + if (networkMessage != null) + { + publisherConnection.PublishNetworkMessage(networkMessage); + } + + //Assert + bool noMessageReceived = false; + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + noMessageReceived = true; + } + + publisherConnection.Stop(); + udpMulticastClient.Close(); + udpMulticastClient.Dispose(); + + if (noMessageReceived) + { + Assert.Fail("The UDP message was not received"); + } + } + + [Test(Description = "Validate discovery request PublishNetworkMessage for PublisherEndpoints"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageDiscoveryPublish_PublisherEndpoints() + { + //Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //create publisher configuration object with modified port + string configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + Assert.Greater(publisherConfiguration.Connections.Count, 1, "publisherConfiguration.Connection should be > 0"); + + //discovery IP adress 224.0.2.14 + IPAddress[] multicastIPAddresses = Dns.GetHostAddresses(kUdpDiscoveryIp); + IPAddress multicastIPAddress = multicastIPAddresses.First(); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections[0].Address = new ExtensionObject(publisherAddress); + + //create publisher UaPubSubApplication with changed configuration settings + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + // will signal that the uadp message was received from local ip + m_shutdownEvent = new ManualResetEvent(false); + + //setup uadp client for receiving from multicast (simulate a subscriber multicast) + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, kDiscoveryPortNo); + udpMulticastClient.BeginReceive(new AsyncCallback(OnReceive), udpMulticastClient); + + List endpointDescriptions = new List() + { + new EndpointDescription() { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.None, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None", + Server = new ApplicationDescription() { ApplicationName = "Test security mode None", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.Sign, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode Sign", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.SignAndEncrypt, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode SignAndEncrypt", ApplicationUri = "urn:localhost:Server" } + } + }; + + UaNetworkMessage uaNetworkMessage = publisherConnection.CreatePublisherEndpointsNetworkMessage(endpointDescriptions.ToArray(), + StatusCodes.Good, publisherConnection.PubSubConnectionConfiguration.PublisherId.Value); + Assert.IsNotNull(uaNetworkMessage, "uaNetworkMessage shall not return null"); + + //Act + publisherConnection.Start(); + + publisherConnection.PublishNetworkMessage(uaNetworkMessage); + + // Assert + bool noMessageReceived = false; + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + noMessageReceived = true; + } + publisherConnection.Stop(); udpMulticastClient.Close(); udpMulticastClient.Dispose(); diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs index a2ba8696c..a9fb154d1 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/UdpPubSubConnectionTests.Subscriber.cs @@ -98,7 +98,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromUnicast() IPEndPoint remoteEndPoint = new IPEndPoint(localhost.Address, kDiscoveryPortNo); Assert.IsNotNull(remoteEndPoint, "remoteEndPoint is null"); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); int sentBytesLen = udpUnicastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); @@ -159,7 +159,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromBroadcast() //Act subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); // first physical network ip is mandatory on UdpClientBroadcast as parameter UdpClient udpBroadcastClient = new UdpClientBroadcast(localhost.Address, kDiscoveryPortNo, UsedInContext.Publisher); @@ -227,7 +227,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromMulticast() //Act subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection); + m_sentBytes = BuildNetworkMessages(publisherConnection); // first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); @@ -254,7 +254,7 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromMulticast() #if !CUSTOM_TESTS [Ignore("A network interface controller is necessary in order to run correctly.")] #endif - public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse() + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_DataSetMetadata() { // Arrange var localhost = GetFirstNic(); @@ -265,10 +265,12 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + //set subscriber configuration string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + //set address and create subscriber NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); @@ -278,12 +280,16 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); - subscriberApplication.RawDataReceived += RawDataReceived; + //subscribe to event handlers + subscriberApplication.RawDataReceived += RawDataReceived_NoRequests; + subscriberApplication.MetaDataReceived += MetaDataReceived; + //set publisher cofiguration configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + //set address and create publisher NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); @@ -293,17 +299,33 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; Assert.IsNotNull(publisherConnection, "publisherConnection is null"); - //Act + //start subcriber and prepare the message subscriberConnection.Start(); m_shutdownEvent = new ManualResetEvent(false); - m_sentBytes = PrepareData(publisherConnection, UdpConnectionType.Discovery); + m_sentBytes = BuildNetworkMessages(publisherConnection, UdpConnectionType.Discovery); + subscriberConnection.RequestDataSetMetaData(); + + //create multicast client // first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + //set endpoint and send message IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + + //manually create dataset metadata message and trigger metadata reveived event for test + DataSetMetaDataType metaData = m_uaPublisherApplication.DataCollector.GetPublishedDataSet(m_uaPublisherApplication.UaPubSubConfigurator.PubSubConfiguration.PublishedDataSets.First().Name)?.DataSetMetaData; + WriterGroupDataType writerConfig = m_uaPublisherApplication.PubSubConnections.First().PubSubConnectionConfiguration.WriterGroups.First(); + UadpNetworkMessage networkMessage = new UadpNetworkMessage(writerConfig, metaData) { PublisherId = m_uaPublisherApplication.ApplicationId, DataSetWriterId = writerConfig.DataSetWriters.First().DataSetWriterId }; + SubscribedDataEventArgs subscribedDataEventArgs = new SubscribedDataEventArgs() + { + NetworkMessage = networkMessage, + }; + subscriberApplication.RaiseMetaDataReceivedEvent(subscribedDataEventArgs); + + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); Thread.Sleep(kEstimatedPublishingTime); @@ -317,6 +339,310 @@ public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryRespons subscriberConnection.Stop(); } + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUadpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_DataSetWriterConfig() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + //set configuration + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + //set address and create subscriber + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + //subscribe the event handlers + subscriberApplication.RawDataReceived += RawDataReceived_NoRequests; + subscriberApplication.DataSetWriterConfigurationReceived += DatasetWriterConfigurationReceived; + + //set publisher configuration an create publisher + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + //start the subscriber and prepare message + subscriberConnection.Start(); + m_shutdownEvent = new ManualResetEvent(false); + m_sentBytes = PrepareDataSetWriterConfigurationMessage(publisherConnection); + + //prepare multicast client + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); + Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + + //set endpoint and send message + IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); + int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.DataSetWriterConfigurationReceived -= DatasetWriterConfigurationReceived; + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher holds a DataSetWriterConfiguration, Subscriber requests the configuration;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_SubscriberRequestDataSetWriterConfiguration() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.DataSetWriterConfigurationReceived += DatasetWriterConfigurationReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + m_shutdownEvent = new ManualResetEvent(false); + + publisherConnection.Start(); + // Add DataSetWriterConfiguration on Publisher + if (publisherConnection is IUadpDiscoveryMessages) + { + // set the DataSetWriterConfiguration callback waiting for a Subscriber request to grab them + ((IUadpDiscoveryMessages)publisherConnection).GetDataSetWriterConfigurationCallback(GetDataSetWriterConfiguration); + } + + //Act + subscriberConnection.Start(); + + subscriberConnection.RequestDataSetWriterConfiguration(); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.DataSetWriterConfigurationReceived -= DatasetWriterConfigurationReceived; + + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher holds a PublisherEndpoints collection, Subscriber request available PublisherEndpoints;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_SubscriberRequestPublisherEndpoints() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.PublisherEndpointsReceived += PublisherEndpointsReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + m_shutdownEvent = new ManualResetEvent(false); + + publisherConnection.Start(); + // Add several PublisherEndpoints on Publisher + if (publisherConnection is IUadpDiscoveryMessages) + { + // set the publisher callback (feed with several demo PublisherEndpoints) waiting for a Subscriber request to grab them + ((IUadpDiscoveryMessages)publisherConnection).GetPublisherEndpointsCallback(GetPublisherEndpoints); + } + + //Act + subscriberConnection.Start(); + + subscriberConnection.RequestPublisherEndpoints(); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.PublisherEndpointsReceived -= PublisherEndpointsReceived; + + subscriberConnection.Stop(); + publisherConnection.Stop(); + } + + [Test(Description = "Validate subscriber data on first nic;" + + "Subscriber multicast ip - Publisher multicast ip;" + + "Publisher send a PublisherEndpoints collection to the Subscriber, Subscriber only listen for PublisherEndpoints;" + + "Setting Subscriber as unicast or broadcast not functional. Just discovery request to multicast and response works fine;"), Order(4)] +#if !CUSTOM_TESTS + [Ignore("A network interface controller is necessary in order to run correctly.")] +#endif + public void ValidateUdpPubSubConnectionNetworkMessageReceiveFromDiscoveryResponse_PublisherTriggerEndpoints() + { + // Arrange + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + //discovery IP address 224.0.2.14 + IPAddress multicastIPAddress = new IPAddress(new byte[4] { 224, 0, 2, 14 }); + Assert.IsNotNull(multicastIPAddress, "multicastIPAddress is null"); + + string configurationFile = Utils.GetAbsoluteFilePath(m_subscriberConfigurationFileName, true, true, false); + PubSubConfigurationDataType subscriberConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(subscriberConfiguration, "subscriberConfiguration is null"); + + NetworkAddressUrlDataType subscriberAddress = new NetworkAddressUrlDataType(); + subscriberAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + subscriberConfiguration.Connections[0].Address = new ExtensionObject(subscriberAddress); + UaPubSubApplication subscriberApplication = UaPubSubApplication.Create(subscriberConfiguration); + Assert.IsNotNull(subscriberApplication, "subscriberApplication is null"); + + UdpPubSubConnection subscriberConnection = subscriberApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(subscriberConnection, "subscriberConnection is null"); + + subscriberApplication.PublisherEndpointsReceived += PublisherEndpointsReceived; + + configurationFile = Utils.GetAbsoluteFilePath(m_publisherConfigurationFileName, true, true, false); + PubSubConfigurationDataType publisherConfiguration = UaPubSubConfigurationHelper.LoadConfiguration(configurationFile); + Assert.IsNotNull(publisherConfiguration, "publisherConfiguration is null"); + + NetworkAddressUrlDataType publisherAddress = new NetworkAddressUrlDataType(); + publisherAddress.Url = string.Format(kUdpUrlFormat, Utils.UriSchemeOpcUdp, multicastIPAddress.ToString()); + publisherConfiguration.Connections.First().Address = new ExtensionObject(publisherAddress); + UaPubSubApplication publisherApplication = UaPubSubApplication.Create(publisherConfiguration); + Assert.IsNotNull(publisherApplication, "publisherApplication is null"); + + UdpPubSubConnection publisherConnection = publisherApplication.PubSubConnections.First() as UdpPubSubConnection; + Assert.IsNotNull(publisherConnection, "publisherConnection is null"); + + //Act + subscriberConnection.Start(); + + m_shutdownEvent = new ManualResetEvent(false); + + // Prepare NetworkMessage with PublisherEndpoints + m_sentBytes = PreparePublisherEndpointsMessage(publisherConnection, UdpConnectionType.Discovery); + + // Publisher: first physical network ip is mandatory on UdpClientMulticast as parameter, for multicast publisher the port must not be 4840 + UdpClient udpMulticastClient = new UdpClientMulticast(localhost.Address, multicastIPAddress, 0); + Assert.IsNotNull(udpMulticastClient, "udpMulticastClient is null"); + + IPEndPoint remoteEndPoint = new IPEndPoint(multicastIPAddress, kDiscoveryPortNo); + // Publisher: trigger PublishNetworkMessage including PublisherEndpoints data + int sentBytesLen = udpMulticastClient.Send(m_sentBytes, m_sentBytes.Length, remoteEndPoint); + Assert.AreEqual(sentBytesLen, m_sentBytes.Length, "Sent bytes size not equal to published bytes size!"); + + Thread.Sleep(kEstimatedPublishingTime); + + // Assert + if (!m_shutdownEvent.WaitOne(kEstimatedPublishingTime)) + { + Assert.Fail("Subscriber multicast error ... published data not received"); + } + + subscriberApplication.PublisherEndpointsReceived -= PublisherEndpointsReceived; + + subscriberConnection.Stop(); + } + /// /// Subscriber callback that listen for Publisher uadp notifications /// @@ -343,6 +669,7 @@ private void RawDataReceived(object sender, RawDataReceivedEventArgs e) string sentBytesStr = BitConverter.ToString(m_sentBytes); string bytesStr = BitConverter.ToString(bytes); + Assert.AreEqual(sentBytesStr, bytesStr, "Sent bytes: {0} and received bytes: {1} content are not equal", sentBytesStr, bytesStr); m_shutdownEvent.Set(); @@ -350,11 +677,113 @@ private void RawDataReceived(object sender, RawDataReceivedEventArgs e) } /// - /// Prepare data + /// Subscriber callback that listen for Publisher uadp notifications but does not test requests /// - /// + /// the sender + /// the event args + private void RawDataReceived_NoRequests(object sender, RawDataReceivedEventArgs e) + { + lock (s_lock) + { + // Assert + var localhost = GetFirstNic(); + Assert.IsNotNull(localhost, "localhost is null"); + Assert.IsNotNull(localhost.Address, "localhost.Address is null"); + + Assert.IsNotNull(e.Source, "Udp address received should not be null"); + if (localhost.Address.ToString() != e.Source.ToString()) + { + // the message comes from the network but was not initiated by test + return; + } + + byte[] bytes = e.Message; + if (bytes.Length > 12) + { + Assert.AreEqual(m_sentBytes.Length, bytes.Length, "Sent bytes size: {0} does not match received bytes size: {1}", m_sentBytes.Length, bytes.Length); + + string sentBytesStr = BitConverter.ToString(m_sentBytes); + string bytesStr = BitConverter.ToString(bytes); + + Assert.AreEqual(sentBytesStr, bytesStr, "Sent bytes: {0} and received bytes: {1} content are not equal", sentBytesStr, bytesStr); + } + m_shutdownEvent.Set(); + } + } + + /// + /// Handler for MetaDataDataReceived event. + /// + /// + /// + private void MetaDataReceived(object sender, SubscribedDataEventArgs e) + { + lock (s_lock) + { + Console.WriteLine("Metadata received:"); + bool isNetworkMessage = e.NetworkMessage is UadpNetworkMessage; + Assert.IsTrue(isNetworkMessage); + if (isNetworkMessage) + { + if (e.NetworkMessage.IsMetaDataMessage) + { + UadpNetworkMessage message = (UadpNetworkMessage)e.NetworkMessage; + + Assert.IsNotNull(message.PublisherId); + Assert.IsNotNull(message.DataSetWriterId); + Assert.IsNotNull(message.DataSetMetaData); + Assert.IsNotNull(message.DataSetMetaData.Fields); + Assert.IsTrue(message.DataSetMetaData.Fields.Count > 0); + + + Assert.IsNotNull(message.DataSetMetaData.Name); + Assert.IsNotNull(message.DataSetMetaData.ConfigurationVersion); + + for (int i = 0; i < message.DataSetMetaData.Fields.Count; i++) + { + FieldMetaData field = message.DataSetMetaData.Fields[i]; + Assert.IsNotNull(field.Name); + Assert.IsNotNull(field.DataType); + Assert.IsNotNull(field.ValueRank); + Assert.IsNotNull(field.TypeId); + Assert.IsNotNull(field.Properties); + } + } + } + m_shutdownEvent.Set(); + } + } + + /// + /// Validate received publisher endpoints + /// + /// + /// + private void PublisherEndpointsReceived(object sender, PublisherEndpointsEventArgs e) + { + lock (s_lock) + { + Assert.AreEqual(3, e.PublisherEndpoints.Length, "Send PublisherEndpoints: {0} and received PublisherEndpoints: {1} are not equal", 3, e.PublisherEndpoints.Length); + + foreach (EndpointDescription ep in e.PublisherEndpoints) + { + Assert.IsNotNull(ep.SecurityMode); + Assert.IsNotEmpty(ep.SecurityPolicyUri); + Assert.IsNotEmpty(ep.EndpointUrl); + Assert.IsNotNull(ep.Server); + } + m_shutdownEvent.Set(); + } + } + + /// + /// Prepare data / metadata for network messages + /// + /// the connection + /// the connection's type + /// the network message index /// - private byte[] PrepareData(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Networking, int networkMessageIndex = 0) + private byte[] BuildNetworkMessages(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Discovery, int networkMessageIndex = 0) { try { @@ -384,10 +813,179 @@ private byte[] PrepareData(UdpPubSubConnection publisherConnection, UdpConnectio return bytes; } - catch + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// Prepare Publisher UADP Discovery request with PublisherEndpoints data + /// + /// + /// + /// + private byte[] PreparePublisherEndpointsMessage(UdpPubSubConnection publisherConnection, UdpConnectionType udpConnectionType = UdpConnectionType.Networking) + { + try + { + UaNetworkMessage networkMessage = null; + if (udpConnectionType == UdpConnectionType.Discovery) + { + List endpointDescriptions = CreatePublisherEndpoints(); + + networkMessage = publisherConnection.CreatePublisherEndpointsNetworkMessage(endpointDescriptions.ToArray(), + StatusCodes.Good, publisherConnection.PubSubConnectionConfiguration.PublisherId.Value); + Assert.IsNotNull(networkMessage, "uaNetworkMessage shall not return null"); + + return networkMessage.Encode(ServiceMessageContext.GlobalContext); + } + + return null; + } + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// UADP Discovery: Provide Publisher demo PublisherEndpoints setting GetPublisherEndpointsCallback method to deliver them during a Subscriber request + /// + /// + private List GetPublisherEndpoints() + { + return CreatePublisherEndpoints(); + } + + /// + /// UADP Discovery: Create demo PublisherEndpoints + /// + /// + private List CreatePublisherEndpoints() + { + return new List() + { + new EndpointDescription() { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.None, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None", + Server = new ApplicationDescription() { ApplicationName = "Test security mode None", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.Sign, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode Sign", ApplicationUri = "urn:localhost:Server" } + }, + new EndpointDescription() + { + EndpointUrl = "opc.tcp://server1:4840/Test", + SecurityMode = MessageSecurityMode.SignAndEncrypt, + SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256", + Server = new ApplicationDescription() { ApplicationName = "Test security mode SignAndEncrypt", ApplicationUri = "urn:localhost:Server" } + } + }; + } + + /// + /// Prepare data for a DataSetWriterConfigurationMessage + /// + /// Publisher connection + /// + private byte[] PrepareDataSetWriterConfigurationMessage(UdpPubSubConnection publisherConnection) + { + try + { + WriterGroupDataType writerGroup0 = publisherConnection.PubSubConnectionConfiguration.WriterGroups.First(); + + UaNetworkMessage networkMessage = null; + + List dataSetWriterIds = new List(); + foreach (DataSetWriterDataType dataSetWriterDataType in writerGroup0.DataSetWriters) + { + dataSetWriterIds.Add(dataSetWriterDataType.DataSetWriterId); + } + networkMessage = publisherConnection.CreateDataSetWriterCofigurationMessage(dataSetWriterIds.ToArray()).First(); + + Assert.IsNotNull(networkMessage, "CreateDataSetWriterCofigurationMessages returned null"); + + byte[] bytes = networkMessage.Encode(ServiceMessageContext.GlobalContext); + + return bytes; + } + catch (Exception ex) + { + Assert.Fail(ex.Message); + throw ex; + } + } + + /// + /// Handler for DatasetWriterConfigurationReceived event. + /// + /// + /// + private void DatasetWriterConfigurationReceived(object sender, DataSetWriterConfigurationEventArgs e) + { + lock (s_lock) + { + Console.WriteLine("DataSetWriterConfig received:"); + + if (e.DataSetWriterConfiguration != null) + { + WriterGroupDataType config = e.DataSetWriterConfiguration; + + Assert.IsNotEmpty(config.Name); + Assert.IsNotNull(config.SecurityKeyServices); + Assert.IsNotNull(config.GroupProperties); + Assert.IsNotNull(config.SecurityMode); + Assert.IsNotNull(config.TransportSettings); + Assert.IsNotNull(config.MessageSettings); + Assert.IsNotEmpty(config.HeaderLayoutUri); + Assert.IsTrue(config.DataSetWriters != null); + + foreach (DataSetWriterDataType writer in config.DataSetWriters) + { + Assert.IsNotEmpty(writer.Name); + Assert.IsNotNull(writer.DataSetWriterProperties); + Assert.IsNotNull(writer.MessageSettings); + Assert.IsNotEmpty(writer.DataSetName); + } + m_shutdownEvent.Set(); + } + } + } + + /// + /// UADP Discovery: Provide DataSetWriterConfiguration setting GetDataSetWriterConfigurationCallback method to deliver them during a Subscriber request + /// + /// + private IList GetDataSetWriterConfiguration(UaPubSubApplication uaPubSubApplication) + { + return CreateDataSetWriterIdsList(uaPubSubApplication); + } + + /// + /// Create data set writer ids list from the PubSubConnectionDataType configuration + /// + /// + /// + private static IList CreateDataSetWriterIdsList(UaPubSubApplication uaPubSubApplication) + { + List ids = new List(); + + foreach (var connection in uaPubSubApplication.UaPubSubConfigurator.PubSubConfiguration.Connections) { - return Array.Empty(); + ids.AddRange(connection.WriterGroups + .Select(group => group.DataSetWriters) + .SelectMany(writer => writer.Select(x => x.DataSetWriterId)) + .ToList()); } + return ids; } } }