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;
}
}
}