From 9a61374f20cfcff4dff17d8c2b30157effd65ee9 Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Tue, 2 Jul 2024 18:53:38 +0200 Subject: [PATCH 1/4] Connection diagnostics and key set logging capabilities --- docs/opc-publisher/api.md | 20 +- docs/opc-publisher/definitions.md | 5 + docs/opc-publisher/media/keyset.png | Bin 0 -> 18471 bytes docs/opc-publisher/media/keyset2.png | Bin 0 -> 26615 bytes docs/opc-publisher/media/keyset3.png | Bin 0 -> 125205 bytes docs/opc-publisher/openapi.json | 19 +- docs/opc-publisher/troubleshooting.md | 21 ++ .../src/ChannelDiagnosticModel.cs | 58 +++++ .../src/ChannelKeyModel.cs | 35 +++ .../src/ConnectionDiagnosticModel.cs | 68 ++++++ .../src/Controllers/DiagnosticsController.cs | 20 +- .../ControllerExceptionFilterAttribute.cs | 1 - .../src/Runtime/CommandLine.cs | 4 + .../src/IClientDiagnostics.cs | 9 +- .../Stack/Extensions/ContainerBuilderEx.cs | 2 + .../src/Stack/Runtime/OpcUaClientConfig.cs | 2 + .../src/Stack/Runtime/OpcUaClientOptions.cs | 6 + .../src/Stack/Services/OpcUaClient.cs | 216 ++++++++++++------ .../src/Stack/Services/OpcUaClientManager.cs | 51 ++++- .../Stack/Services/OpcUaStackKeySetLogger.cs | 154 +++++++++++++ 20 files changed, 589 insertions(+), 102 deletions(-) create mode 100644 docs/opc-publisher/media/keyset.png create mode 100644 docs/opc-publisher/media/keyset2.png create mode 100644 docs/opc-publisher/media/keyset3.png create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelDiagnosticModel.cs create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelKeyModel.cs create mode 100644 src/Azure.IIoT.OpcUa.Publisher.Models/src/ConnectionDiagnosticModel.cs create mode 100644 src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs diff --git a/docs/opc-publisher/api.md b/docs/opc-publisher/api.md index 7bb6d67ba0..371d70888f 100644 --- a/docs/opc-publisher/api.md +++ b/docs/opc-publisher/api.md @@ -862,23 +862,22 @@ This section lists the diagnostics APi provided by OPC Publisher providing name. - -#### ResetAllClients + +#### GetConnectionDiagnostic ``` -GET /v2/reset +GET /v2/diagnostics/connections ``` ##### Description -Can be used to reset all established connections causing a full reconnect and recreate of all subscriptions. +Get connection diagnostic information for all connections. The first set of diagnostics are the diagnostics active for all connections, continue reading to get updates. ##### Responses |HTTP Code|Description|Schema| |---|---|---| -|**200**|The operation was successful.|No Content| -|**408**|The operation timed out.|[ProblemDetails](definitions.md#problemdetails)| +|**200**|The operation was successful.|[ConnectionDiagnosticModelIAsyncEnumerable](definitions.md#connectiondiagnosticmodeliasyncenumerable)| |**500**|An unexpected error occurred|[ProblemDetails](definitions.md#problemdetails)| @@ -888,15 +887,15 @@ Can be used to reset all established connections causing a full reconnect and re * `application/x-msgpack` - -#### SetTraceMode + +#### ResetAllClients ``` -GET /v2/tracemode +GET /v2/reset ``` ##### Description -Can be used to set trace mode for all established connections. Call within a minute to keep trace mode up or else trace mode will be disabled again after 1 minute. Enabling and resetting tracemode will cause a reconnect of the client. +Can be used to reset all established connections causing a full reconnect and recreate of all subscriptions. ##### Responses @@ -904,6 +903,7 @@ Can be used to set trace mode for all established connections. Call within a min |HTTP Code|Description|Schema| |---|---|---| |**200**|The operation was successful.|No Content| +|**408**|The operation timed out.|[ProblemDetails](definitions.md#problemdetails)| |**500**|An unexpected error occurred|[ProblemDetails](definitions.md#problemdetails)| diff --git a/docs/opc-publisher/definitions.md b/docs/opc-publisher/definitions.md index 3f82726180..272ee3b786 100644 --- a/docs/opc-publisher/definitions.md +++ b/docs/opc-publisher/definitions.md @@ -314,6 +314,11 @@ Condition handling options model |**updateInterval**
*optional*|Time interval for sending pending interval updates in seconds.|integer (int32)| + +### ConnectionDiagnosticModelIAsyncEnumerable +*Type* : object + + ### ConnectionModel Connection model diff --git a/docs/opc-publisher/media/keyset.png b/docs/opc-publisher/media/keyset.png new file mode 100644 index 0000000000000000000000000000000000000000..9471e9e5eb5e5b9302e8c583078342810d45d1ff GIT binary patch literal 18471 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 literal 0 HcmV?d00001 diff --git a/docs/opc-publisher/media/keyset2.png b/docs/opc-publisher/media/keyset2.png new file mode 100644 index 0000000000000000000000000000000000000000..2cc6eb10b5f269cae2a8c3a600330e1871402d1b GIT binary patch literal 26615 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 literal 0 HcmV?d00001 diff --git a/docs/opc-publisher/media/keyset3.png b/docs/opc-publisher/media/keyset3.png new file mode 100644 index 0000000000000000000000000000000000000000..650242b955b70076a12f083a1baf1bf0c2ec91ed GIT binary patch literal 125205 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 literal 0 HcmV?d00001 diff --git a/docs/opc-publisher/openapi.json b/docs/opc-publisher/openapi.json index d03354ef5d..fcbca4d922 100644 --- a/docs/opc-publisher/openapi.json +++ b/docs/opc-publisher/openapi.json @@ -1166,21 +1166,24 @@ } } }, - "/v2/tracemode": { + "/v2/diagnostics/connections": { "get": { "tags": [ "Diagnostics" ], - "summary": "SetTraceMode", - "description": "Can be used to set trace mode for all established connections. Call within a minute to keep trace mode up or else trace mode will be disabled again after 1 minute. Enabling and resetting tracemode will cause a reconnect of the client.", - "operationId": "SetTraceMode", + "summary": "GetConnectionDiagnostic", + "description": "Get connection diagnostic information for all connections. The first set of diagnostics are the diagnostics active for all connections, continue reading to get updates.", + "operationId": "GetConnectionDiagnostic", "produces": [ "application/json", "application/x-msgpack" ], "responses": { "200": { - "description": "The operation was successful." + "description": "The operation was successful.", + "schema": { + "$ref": "#/definitions/ConnectionDiagnosticModelIAsyncEnumerable" + } }, "500": { "description": "An unexpected error occurred", @@ -4918,6 +4921,10 @@ }, "additionalProperties": false }, + "ConnectionDiagnosticModelIAsyncEnumerable": { + "type": "object", + "additionalProperties": false + }, "ConnectionModel": { "description": "Connection model", "type": "object", @@ -9314,4 +9321,4 @@ "description": "\r\n\r\n This section contains the API to configure data set writers and writer\r\n groups inside OPC Publisher. It supersedes the configuration API.\r\n Applications should use one or the other, but not both at the same\r\n time.\r\n \r\n\r\n\r\n The method name for all transports other than HTTP (which uses the shown\r\n HTTP methods and resource uris) is the name of the subsection header.\r\n To use the version specific method append \"_V1\" or \"_V2\" to the method\r\n name.\r\n " } ] -} \ No newline at end of file +} diff --git a/docs/opc-publisher/troubleshooting.md b/docs/opc-publisher/troubleshooting.md index c12fb56327..49ce757ea8 100644 --- a/docs/opc-publisher/troubleshooting.md +++ b/docs/opc-publisher/troubleshooting.md @@ -16,6 +16,7 @@ In this document you find information about - [IoT Hub Metrics](#iot-hub-metrics) - [Use Azure IoT Explorer](#use-azure-iot-explorer) - [Restart the module](#restart-the-module) +- [Analyzing network capture files](#analyzing-network-capture-files) - [Limits and contributing factors](#limits-and-contributing-factors) - [Debugging Discovery](#debugging-discovery) @@ -166,6 +167,26 @@ iotedge list You can also troubleshoot the OPC Publisher module remotely through the Azure Portal. Select the module inside the respective IoT Edge device in the IoT Edge blade and click on the Troubleshooting button to see logs and restart the module remotely. +## Analyzing network capture files + +The issue might be between the OPC Publisher OPC UA client and the OPC UA server you are using. A network capture provides the definitive answer as to where the issue lies. To capture network traffic you can use [Wireshark](https://www.wireshark.org/) or [tshark](https://tshark.dev/setup/install/) (aka. command line wireshark) and capture a .pcap file for analysis. An example of how to capture network traces in a docker environment can be found [here](../../deploy/docker/with-pcap-capture.yaml). To analyze OPC UA traffic, you must load the .pcap or .pcapng file you captured with Wireshark. + +Follow [these instructions](https://opcconnect.opcfoundation.org/2017/02/analyzing-opc-ua-communications-with-wireshark/#:~:text=Wireshark%20has%20a%20built-in%20filter%20for%20OPC%20UA%2C,fairly%20easy%20to%20capture%20and%20analyze%20the%20conversation.) to visualize the OPC UA traffic between OPC Publisher and your OPC UA server. + +If your connection to the OPC UA server is encrypted (using security) you must use Wireshark 4.3 (not the stable version!). You will also need to capture the client and server keys. You can start OPC Publisher with the `-ksf` [command line argument](./commandline.md), providing an optional folder path that can be accessed after running OPC Publisher (volume mount). The folder is structured like this: + +![Key Set Log Folder](./media/keyset.png) + +The folder path starts with the port number, because the port number needs to be configured in the OPC UA protocol page in Wireshark. + +![Wireshark configuration](./media/keyset2.png) + +Find the connection you want to trace. You can open the `opcua_debug.log` file in one of the sub folders to identify the connection. The log file shows the remote and local endpoints as well as a summary of the connection configuration that was used to connect OPC Publisher. Once you have found the right folder, load the `opcua_debug.txt` file using the protocol page, then save and Wireshark will be able to decrypt traffic. + +![Wireshark Analysis](./media/keyset3.png) + +> IMPORTANT: While the keys that are logged are scoped to the connection and cannot be re-used, it still presents a security risk, therefore, ensure to clean up the logs when you are done, and do not use the feature in production. + ## Limits and contributing factors IoT Edge runs on various distributions of Linux and leverages Docker containers for all workloads, including the built-in IoT Edge modules `edgeAgent` and `edgeHub`. Docker has settings for the maximum host resources that each container can consume. These can be displayed by the Docker command "stats" (run "docker stats" from the terminal on the gateway host): diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelDiagnosticModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelDiagnosticModel.cs new file mode 100644 index 0000000000..0d2719c71e --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelDiagnosticModel.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Azure.IIoT.OpcUa.Publisher.Models +{ + using System; + using System.Runtime.Serialization; + + /// + /// Channel token. Can be used to decrypt encrypted + /// capture files. + /// + [DataContract] + public record class ChannelDiagnosticModel + { + /// + /// The id assigned to the channel that the token + /// belongs to. + /// + [DataMember(Name = "channelId", Order = 0)] + public required uint ChannelId { get; init; } + + /// + /// The id assigned to the token. + /// + [DataMember(Name = "tokenId", Order = 1)] + public required uint TokenId { get; init; } + + /// + /// When the token was created by the server + /// (refers to the server's clock). + /// + [DataMember(Name = "createdAt", Order = 2)] + public required DateTime CreatedAt { get; init; } + + /// + /// The lifetime of the token + /// + [DataMember(Name = "lifetime", Order = 3)] + public required TimeSpan Lifetime { get; init; } + + /// + /// Client keys + /// + [DataMember(Name = "client", Order = 4, + EmitDefaultValue = false)] + public ChannelKeyModel? Client { get; init; } + + /// + /// Server keys + /// + [DataMember(Name = "aerver", Order = 5, + EmitDefaultValue = false)] + public ChannelKeyModel? Server { get; init; } + } +} diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelKeyModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelKeyModel.cs new file mode 100644 index 0000000000..cdb1d9580c --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ChannelKeyModel.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Azure.IIoT.OpcUa.Publisher.Models +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// Channel token key model. + /// + [DataContract] + public record class ChannelKeyModel + { + /// + /// Iv + /// + [DataMember(Name = "iv", Order = 0)] + public required IReadOnlyList Iv { get; init; } + + /// + /// Key + /// + [DataMember(Name = "key", Order = 1)] + public required IReadOnlyList Key { get; init; } + + /// + /// Signature length + /// + [DataMember(Name = "sigLen", Order = 2)] + public required int SigLen { get; init; } + } +} diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/ConnectionDiagnosticModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ConnectionDiagnosticModel.cs new file mode 100644 index 0000000000..b80f93d48b --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/ConnectionDiagnosticModel.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Azure.IIoT.OpcUa.Publisher.Models +{ + using System; + using System.Runtime.Serialization; + + /// + /// Connection / session diagnostics model + /// + [DataContract] + public record class ConnectionDiagnosticModel + { + /// + /// Timestamp of the diagnostic information + /// + [DataMember(Name = "timeStamp", Order = 0)] + public required DateTimeOffset TimeStamp { get; init; } + + /// + /// The connection information specified by user. + /// + [DataMember(Name = "connection", Order = 1)] + public required ConnectionModel Connection { get; init; } + + /// + /// Effective remote ip address used for the + /// connection if connected. Empty if disconnected. + /// + [DataMember(Name = "remoteIpAddress", Order = 2, + EmitDefaultValue = false)] + public string? RemoteIpAddress { get; init; } + + /// + /// The effective remote port used when connected, + /// null if disconnected. + /// + [DataMember(Name = "remotePort", Order = 3, + EmitDefaultValue = false)] + public int? RemotePort { get; init; } + + /// + /// Effective local ip address used for the connection + /// if connected. Empty if disconnected. + /// + [DataMember(Name = "localIpAddress", Order = 4, + EmitDefaultValue = false)] + public string? LocalIpAddress { get; init; } + + /// + /// The effective local port used when connected, + /// null if disconnected. + /// + [DataMember(Name = "localPort", Order = 5, + EmitDefaultValue = false)] + public int? LocalPort { get; init; } + + /// + /// Channel diagnostics + /// + [DataMember(Name = "channelDiagnostics", Order = 6, + EmitDefaultValue = false)] + public ChannelDiagnosticModel? ChannelDiagnostics { get; init; } + } +} diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs index dc898d7cc7..7ffd28b5b5 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs @@ -15,6 +15,8 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Controllers using System; using System.Threading; using System.Threading.Tasks; + using Azure.IIoT.OpcUa.Publisher.Models; + using System.Collections.Generic; /// /// @@ -68,27 +70,27 @@ public DiagnosticsController(IClientDiagnostics diagnostics) [HttpGet("reset")] public async Task ResetAllClientsAsync(CancellationToken ct = default) { - await _diagnostics.ResetAllClients(ct).ConfigureAwait(false); + await _diagnostics.ResetAllClientsAsync(ct).ConfigureAwait(false); } /// - /// SetTraceMode + /// GetConnectionDiagnostic /// /// - /// Can be used to set trace mode for all established connections. - /// Call within a minute to keep trace mode up or else trace mode - /// will be disabled again after 1 minute. Enabling and resetting - /// tracemode will cause a reconnect of the client. + /// Get connection diagnostic information for all connections. + /// The first set of diagnostics are the diagnostics active for + /// all connections, continue reading to get updates. /// /// /// The operation was successful. /// An unexpected error occurred [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] - [HttpGet("tracemode")] - public async Task SetTraceModeAsync(CancellationToken ct = default) + [HttpGet("diagnostics/connections")] + public IAsyncEnumerable GetConnectionDiagnosticAsync( + CancellationToken ct = default) { - await _diagnostics.SetTraceModeAsync(ct).ConfigureAwait(false); + return _diagnostics.GetConnectionDiagnosticAsync(ct); } private readonly IClientDiagnostics _diagnostics; diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Filters/ControllerExceptionFilterAttribute.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Filters/ControllerExceptionFilterAttribute.cs index d369a40418..4ad0c78982 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Filters/ControllerExceptionFilterAttribute.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Filters/ControllerExceptionFilterAttribute.cs @@ -41,7 +41,6 @@ public override void OnException(ExceptionContext context) return; } - if (context.Exception is AggregateException ae) { var root = ae.GetBaseException(); diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs index e685e2b210..88e937e80c 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs @@ -16,6 +16,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Runtime using Opc.Ua; using System; using System.Collections.Generic; + using System.Diagnostics; using System.Globalization; using System.IO; @@ -554,6 +555,9 @@ public CommandLine(string[] args, CommandLineLogger? logger = null) { $"sl|opcstacklogging:|{OpcUaClientConfig.EnableOpcUaStackLoggingKey}:", "Enable opc ua stack logging beyond logging at error level.\nDefault: `disabled`.\n", (bool? b) => this[OpcUaClientConfig.EnableOpcUaStackLoggingKey] = b?.ToString() ?? "True" }, + { $"ksf|keysetlogfolder:|{OpcUaClientConfig.OpcUaKeySetLogFolderNameKey}:", + "Writes negotiated symmetric keys for all running client connection to this file.\nThe file can be loaded by Wireshark 4.3 and used to decrypt encrypted channels when analyzing network traffic captures.\nNote that enabling this feature presents a security risk!\nDefault: `disabled`.\n", + (string? f) => this[OpcUaClientConfig.OpcUaKeySetLogFolderNameKey] = f ?? Directory.GetCurrentDirectory() }, { $"ecw|enableconsolewriter:|{Configuration.ConsoleWriter.EnableKey}:", "Enable writing encoded messages to standard error log through the filesystem transport (must enable via `-t FileSystem` and `-o` must be set to either `stderr` or `stdout`).\nDefault: `false`.\n", (bool? b) => this[Configuration.ConsoleWriter.EnableKey] = b?.ToString() ?? "True" }, diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/IClientDiagnostics.cs b/src/Azure.IIoT.OpcUa.Publisher/src/IClientDiagnostics.cs index b62a178aa1..d06adeb945 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/IClientDiagnostics.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/IClientDiagnostics.cs @@ -5,6 +5,8 @@ namespace Azure.IIoT.OpcUa.Publisher { + using Azure.IIoT.OpcUa.Publisher.Models; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -19,13 +21,14 @@ public interface IClientDiagnostics /// /// /// - Task ResetAllClients(CancellationToken ct = default); + Task ResetAllClientsAsync(CancellationToken ct = default); /// - /// Set all connections into trace mode for a minute. + /// Watch diagnostic information of all connections. /// /// /// - Task SetTraceModeAsync(CancellationToken ct = default); + IAsyncEnumerable GetConnectionDiagnosticAsync( + CancellationToken ct = default); } } diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Extensions/ContainerBuilderEx.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Extensions/ContainerBuilderEx.cs index 7d8a6c2851..5014215577 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Extensions/ContainerBuilderEx.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Extensions/ContainerBuilderEx.cs @@ -27,6 +27,8 @@ public static void AddOpcUaStack(this ContainerBuilder builder) builder.RegisterType() .AsImplementedInterfaces().SingleInstance().AutoActivate(); + builder.RegisterType() + .AsImplementedInterfaces().SingleInstance().AutoActivate(); builder.RegisterType() .AsImplementedInterfaces().SingleInstance(); builder.RegisterType() diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Runtime/OpcUaClientConfig.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Runtime/OpcUaClientConfig.cs index 2a7c29b2e4..adadf10469 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Runtime/OpcUaClientConfig.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Runtime/OpcUaClientConfig.cs @@ -54,6 +54,7 @@ public sealed class OpcUaClientConfig : PostConfigureOptionBase public bool? EnableOpcUaStackLogging { get; set; } + /// + /// Folder to write keysets to for later decryption + /// of wireshark traces. + /// + public string? OpcUaKeySetLogFolderName { get; set; } + /// /// Minimum number of publish requests to queue /// at all times. Default is 2. diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs index c9625a03c1..7a9275e0f1 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs @@ -14,6 +14,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using Nito.AsyncEx; using Opc.Ua; using Opc.Ua.Client; + using Opc.Ua.Bindings; using System; using System.Collections.Generic; using System.Diagnostics; @@ -25,6 +26,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; + using System.Net; /// /// OPC UA Client based on official ua client reference sample. @@ -107,6 +109,11 @@ internal sealed partial class OpcUaClient : DefaultSessionFactory, IOpcUaClient, /// internal OperationLimits? LimitOverrides { get; set; } + /// + /// Last diagnostic information on this client + /// + internal ConnectionDiagnosticModel LastDiagnostics => _lastDiagnostics; + /// /// No complex type loading ever /// @@ -170,6 +177,7 @@ public int MinPublishRequestCount /// /// /// + /// /// /// /// @@ -179,6 +187,7 @@ public OpcUaClient(ApplicationConfiguration configuration, Meter meter, IMetricsContext metrics, EventHandler? notifier, ReverseConnectManager? reverseConnectManager, + Action diagnosticsCallback, TimeSpan? maxReconnectPeriod = null, string? sessionName = null) { _timeProvider = timeProvider; @@ -188,6 +197,12 @@ public OpcUaClient(ApplicationConfiguration configuration, } _connection = connection.Connection; + _diagnosticsCb = diagnosticsCallback; + _lastDiagnostics = new ConnectionDiagnosticModel + { + Connection = _connection, + TimeStamp = _timeProvider.GetUtcNow() + }; Debug.Assert(_connection.GetEndpointUrls().Any()); _reverseConnectManager = reverseConnectManager; @@ -219,7 +234,7 @@ public OpcUaClient(ApplicationConfiguration configuration, _cts = new CancellationTokenSource(); _channel = Channel.CreateUnbounded<(ConnectionEvent, object?)>(); _disconnectLock = _lock.WriterLock(_cts.Token); - _traceModeTimer = _timeProvider.CreateTimer(_ => OnTraceModeExpired(), + _channelMonitor = _timeProvider.CreateTimer(_ => OnUpdateConnectionDiagnostics(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); _sessionManager = ManageSessionStateMachineAsync(_cts.Token); } @@ -368,48 +383,6 @@ internal Task ResetAsync(CancellationToken ct) return tcs.Task; } - /// - /// Enable trace mode - /// - /// - /// - internal async Task SetTraceModeAsync(CancellationToken ct) - { - bool reset; - lock (_lock) - { - reset = !_traceMode; - _traceMode = true; - - _traceModeTimer.Change(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); - } - - if (reset) - { - // Reset the client into trace mode - await ResetAsync(ct).ConfigureAwait(false); - } - } - - /// - /// Disable trace mode if necessary when watchdog expires - /// - private void OnTraceModeExpired() - { - bool reset; - lock (_lock) - { - reset = _traceMode; - - _traceMode = false; - _traceModeTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); - } - if (reset) - { - TriggerConnectionEvent(ConnectionEvent.Reset); - } - } - /// /// Close client /// @@ -454,8 +427,6 @@ internal async ValueTask CloseAsync() finally { _cts.Dispose(); - - await _traceModeTimer.DisposeAsync().ConfigureAwait(false); } } @@ -1246,11 +1217,6 @@ private async ValueTask TryConnectAsync(CancellationToken ct) // var securityMode = _connection.Endpoint.SecurityMode ?? SecurityMode.NotNone; var securityProfile = _connection.Endpoint.SecurityPolicy; - if (_traceMode) - { - securityMode = SecurityMode.None; - securityProfile = null; - } var endpointDescription = await SelectEndpointAsync(endpointUrl, connection, securityMode, securityProfile).ConfigureAwait(false); @@ -1377,7 +1343,7 @@ internal void Session_HandlePublishError(ISession session, PublishErrorEventArgs "{Client}: Too many publish request error: Limiting number of requests to {Limit}...", this, limit); return; - default: + default: if (session.Connected) { _logger.LogInformation("{Client}: Publish error: {Error} (Actively handled: {Active})...", @@ -1554,30 +1520,140 @@ private async ValueTask UpdateSessionAsync(ISession session) } var oldTable = _session?.NamespaceUris.ToArray(); - try + Debug.Assert(_reconnectingSession == null); + if (ReferenceEquals(_session, session)) { - Debug.Assert(_reconnectingSession == null); - if (ReferenceEquals(_session, session)) + // Not a new session + NotifyConnectivityStateChange(EndpointConnectivityState.Ready); + UpdateNamespaceTableAndSessionDiagnostics(session, oldTable); + return false; + } + + await CloseSessionAsync().ConfigureAwait(false); + _session = (OpcUaSession)session; + + NotifyConnectivityStateChange(EndpointConnectivityState.Ready); + UpdateNamespaceTableAndSessionDiagnostics(_session, oldTable); + kSessions.Add(1, _metrics.TagList); + return true; + + void UpdateNamespaceTableAndSessionDiagnostics(ISession session, + string[]? oldTable) + { + if (oldTable != null) { - // Not a new session - NotifyConnectivityStateChange(EndpointConnectivityState.Ready); - return false; + var newTable = session.NamespaceUris.ToArray(); + LogNamespaceTableChanges(oldTable, newTable); } - await CloseSessionAsync().ConfigureAwait(false); - _session = (OpcUaSession)session; + lock (_channelLock) + { + UpdateConnectionDiagnosticsFromSession(session); + } + } + } - NotifyConnectivityStateChange(EndpointConnectivityState.Ready); - kSessions.Add(1, _metrics.TagList); - return true; + /// + /// Update diagnostic if the channel has changed + /// + private void OnUpdateConnectionDiagnostics() + { + if (_session != null) + { + lock (_channelLock) + { + UpdateConnectionDiagnosticsFromSession(_session); + } } - finally + } + + /// + /// Update session diagnostics + /// + /// + private void UpdateConnectionDiagnosticsFromSession(ISession session) + { + var channel = session.TransportChannel; + var token = channel?.CurrentToken; + + // Get effective ip address and port + var socket = (channel as UaSCUaBinaryTransportChannel)?.Socket; + var remoteIpAddress = socket?.RemoteEndpoint?.GetIPAddress(); + var remotePort = socket?.RemoteEndpoint?.GetPort(); + var localIpAddress = socket?.LocalEndpoint?.GetIPAddress(); + var localPort = socket?.LocalEndpoint?.GetPort(); + var now = _timeProvider.GetUtcNow(); + + var elapsed = now - _lastDiagnostics.TimeStamp; + var lastChannel = _lastDiagnostics.ChannelDiagnostics; + if (lastChannel != null && + lastChannel.ChannelId == token?.ChannelId && + lastChannel.TokenId == token?.TokenId && + lastChannel.CreatedAt == token?.CreatedAt) { - if (oldTable != null && _session != null) + // + // Token has not yet been updated, let's retry later + // It is also assumed that the port/ip are still the same + // + var lifetime = TimeSpan.FromMilliseconds(token.Lifetime); + if (lifetime > elapsed) { - var newTable = _session.NamespaceUris.ToArray(); - LogNamespaceTableChanges(oldTable, newTable); + _channelMonitor.Change(lifetime - elapsed, + Timeout.InfiniteTimeSpan); } + else + { + _channelMonitor.Change(TimeSpan.FromSeconds(1), + Timeout.InfiniteTimeSpan); + } + return; + } + + _lastDiagnostics = new ConnectionDiagnosticModel + { + Connection = _connection, + TimeStamp = now, + RemoteIpAddress = remoteIpAddress?.ToString(), + RemotePort = remotePort == -1 ? null : remotePort, + LocalIpAddress = localIpAddress?.ToString(), + LocalPort = localPort == -1 ? null : localPort, + + ChannelDiagnostics = token != null ? new ChannelDiagnosticModel + { + ChannelId = token.ChannelId, + TokenId = token.TokenId, + CreatedAt = token.CreatedAt, + Lifetime = TimeSpan.FromMilliseconds(token.Lifetime), + Client = ToChannelKey(token.ClientInitializationVector, + token.ClientEncryptingKey, token.ClientSigningKey), + Server = ToChannelKey(token.ServerInitializationVector, + token.ServerEncryptingKey, token.ServerSigningKey) + } : null + }; + + _diagnosticsCb(_lastDiagnostics); + _logger.LogDebug("{Client}: Diagnostics information updated.", this); + + if (token != null) + { + // Monitor channel's token lifetime and update diagnostics + var lifetime = TimeSpan.FromMilliseconds(token.Lifetime); + _channelMonitor.Change(lifetime, Timeout.InfiniteTimeSpan); + } + + static ChannelKeyModel? ToChannelKey(byte[]? iv, byte[]? key, byte[]? signingKey) + { + if (iv == null || key == null || signingKey == null || + iv.Length == 0 || key.Length == 0 || signingKey.Length == 0) + { + return null; + } + return new ChannelKeyModel + { + Iv = iv, + Key = key, + SigLen = signingKey.Length + }; } } @@ -2032,7 +2108,7 @@ private void InitializeMetrics() private int _publishTimeoutCounter; private int _keepAliveCounter; private int _namespaceTableChanges; - private bool _traceMode; + private ConnectionDiagnosticModel _lastDiagnostics; private readonly ReverseConnectManager? _reverseConnectManager; private readonly AsyncReaderWriterLock _lock = new(); private readonly ApplicationConfiguration _configuration; @@ -2043,14 +2119,16 @@ private void InitializeMetrics() private readonly ConnectionModel _connection; private readonly IMetricsContext _metrics; private readonly ILogger _logger; -#pragma warning disable CA2213 // Disposable fields should be disposed - private readonly ITimer _traceModeTimer; private readonly TimeProvider _timeProvider; + private readonly object _channelLock = new(); +#pragma warning disable CA2213 // Disposable fields should be disposed + private readonly ITimer _channelMonitor; private readonly SessionReconnectHandler _reconnectHandler; private readonly CancellationTokenSource _cts; #pragma warning restore CA2213 // Disposable fields should be disposed private readonly TimeSpan _maxReconnectPeriod; private readonly Channel<(ConnectionEvent, object?)> _channel; + private readonly Action _diagnosticsCb; private readonly EventHandler? _notifier; private readonly Dictionary<(string, TimeSpan), Sampler> _samplers = new(); private readonly Dictionary<(string, TimeSpan), Browser> _browsers = new(); diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs index 857da4c6e7..276aee2d1c 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs @@ -24,6 +24,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; + using Nito.AsyncEx; /// /// Client manager @@ -141,15 +142,43 @@ public async Task TestConnectionAsync( } /// - public Task ResetAllClients(CancellationToken ct) + public Task ResetAllClientsAsync(CancellationToken ct) { return Task.WhenAll(_clients.Values.Select(c => c.ResetAsync(ct)).ToArray()); } /// - public Task SetTraceModeAsync(CancellationToken ct) + public async IAsyncEnumerable GetConnectionDiagnosticAsync( + [EnumeratorCancellation] CancellationToken ct) { - return Task.WhenAll(_clients.Values.Select(c => c.SetTraceModeAsync(ct)).ToArray()); + var queue = new AsyncProducerConsumerQueue(); + _listeners.TryAdd(queue, true); + try + { + // Get all items from buffer + var set = new HashSet( + _clients.Values.Select(c => c.LastDiagnostics)); + foreach (var item in set) + { + yield return item; + } + + // Dequeue items we have not yet sent from current state from queue + // until cancelled + while (!ct.IsCancellationRequested) + { + // Get updates - handle fact that we have already sent the reference + var item = await queue.DequeueAsync(ct).ConfigureAwait(false); + if (!set.Contains(item)) + { + yield return item; + } + } + } + finally + { + _listeners.TryRemove(queue, out _); + } } /// @@ -532,7 +561,7 @@ private OpcUaClient GetOrAddClient(ConnectionModel connection) { var client = new OpcUaClient(_configuration.Value, id, _serializer, _loggerFactory, _timeProvider, _meter, _metrics, OnConnectionStateChange, - reverseConnect ? _reverseConnectManager : null, + reverseConnect ? _reverseConnectManager : null, OnClientConnectionDiagnosticChange, _options.Value.MaxReconnectDelayDuration) { OperationTimeout = _options.Value.Quotas.OperationTimeout == 0 ? null : @@ -595,6 +624,18 @@ private OpcUaClient GetOrAddClient(ConnectionModel connection) } } + /// + /// Called by clients when their connection information changed + /// + /// + private void OnClientConnectionDiagnosticChange(ConnectionDiagnosticModel model) + { + foreach (var listener in _listeners.Keys) + { + listener.Enqueue(model); + } + } + /// /// Create metrics /// @@ -615,6 +656,8 @@ private void InitializeMetrics() private readonly IJsonSerializer _serializer; private readonly ReverseConnectManager _reverseConnectManager; private readonly Lazy _reverseConnectStartException; + private readonly ConcurrentDictionary< + AsyncProducerConsumerQueue, bool> _listeners = new(); private readonly ConcurrentDictionary _clients = new(); private readonly IMetricsContext _metrics; private readonly Meter _meter = Diagnostics.NewMeter(); diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs new file mode 100644 index 0000000000..59744326f4 --- /dev/null +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs @@ -0,0 +1,154 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Azure.IIoT.OpcUa.Publisher.Stack.Services +{ + using Microsoft.Extensions.Options; + using System; + using System.Threading.Tasks; + using System.Threading; + using System.Linq; + using System.Globalization; + using System.IO; + using Azure.IIoT.OpcUa.Publisher.Stack.Models; + using Azure.IIoT.OpcUa.Publisher.Models; + + /// + /// + /// Wireshark 4.3 now allows decryption of the UA binary protocol using a keyset log + /// (opc ua debug file). The file contains records in the following format: + /// + /// + /// client_iv_%channel_id%_%token_id%: %hex-string% + /// client_key_%channel_id%_%token_id%: %hex-string% + /// client_siglen_%channel_id%_%token_id%: 32 + /// server_iv_%channel_id%_%token_id%: %hex-string% + /// server_key_%channel_id%_%token_id%: %hex-string% + /// server_siglen_%channel_id%_%token_id%: 16|24|32 + /// ... + /// + /// + /// This class writes the file to disk if a file was configured + /// See: https://gitlab.com/wireshark/wireshark/-/blob/master/plugins/epan/opcua/opcua.c#L232 + /// + /// + public sealed class OpcUaStackKeySetLogger : IDisposable + { + /// + /// Create logger + /// + /// + /// + public OpcUaStackKeySetLogger(IOptions options, + IClientDiagnostics diagnostics) + { + _diagnostics = diagnostics; + _cts = new CancellationTokenSource(); + if (options.Value.OpcUaKeySetLogFolderName == null) + { + _task = Task.CompletedTask; + return; + } + _task = WriteDebugFileAsync(options.Value.OpcUaKeySetLogFolderName, _cts.Token); + } + + /// + public void Dispose() + { + try + { + _cts.Cancel(); + _task.GetAwaiter().GetResult(); + } + catch + { + _cts.Dispose(); + } + } + + /// + /// Log debug file to disk + /// + /// + /// + /// + public async Task WriteDebugFileAsync(string folderName, CancellationToken ct) + { + await foreach (var change in _diagnostics.GetConnectionDiagnosticAsync( + ct).ConfigureAwait(false)) + { + var entry = change.ChannelDiagnostics; + if (entry?.Client == null || entry?.Server == null || change.RemotePort == null) + { + // Not a valid entry, channel without keys + continue; + } + + var id = change.Connection.CreateConsistentHash(); + var path = Path.Combine(folderName, "port", + change.RemotePort.Value.ToString(CultureInfo.InvariantCulture), + "connection", id.ToString("X", CultureInfo.InvariantCulture)); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + var logFileName = Path.Combine(path, "opcua_debug.log"); + var log = File.AppendText(logFileName); + await using (var _ = log.ConfigureAwait(false)) + { + await log.WriteAsync($"Timestamp={change.TimeStamp};") + .ConfigureAwait(false); + await log.WriteAsync($"Connection={change.Connection};") + .ConfigureAwait(false); + await log.WriteAsync($"LocalEP={change.LocalIpAddress}:{change.LocalPort};") + .ConfigureAwait(false); + await log.WriteAsync($"RemoteEP={change.RemoteIpAddress}:{change.RemotePort};") + .ConfigureAwait(false); + await log.WriteLineAsync($"ChannelId={entry.ChannelId};TokenId={entry.TokenId}") + .ConfigureAwait(false); + + await log.FlushAsync(ct).ConfigureAwait(false); + } + + var keysetsFileName = Path.Combine(path, "opcua_debug.txt"); + var keysets = File.AppendText(keysetsFileName); + await using (var _ = keysets.ConfigureAwait(false)) + { + await keysets.WriteAsync($"client_iv_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(Convert.ToHexString(entry.Client.Iv.ToArray())) + .ConfigureAwait(false); + await keysets.WriteAsync($"client_key_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(Convert.ToHexString(entry.Client.Key.ToArray())) + .ConfigureAwait(false); + await keysets.WriteAsync($"client_siglen_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(entry.Client.SigLen.ToString(CultureInfo.InvariantCulture)) + .ConfigureAwait(false); + + await keysets.WriteAsync($"server_iv_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(Convert.ToHexString(entry.Server.Iv.ToArray())) + .ConfigureAwait(false); + await keysets.WriteAsync($"server_key_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(Convert.ToHexString(entry.Server.Key.ToArray())) + .ConfigureAwait(false); + await keysets.WriteAsync($"server_siglen_{entry.ChannelId}_{entry.TokenId}: ") + .ConfigureAwait(false); + await keysets.WriteLineAsync(entry.Server.SigLen.ToString(CultureInfo.InvariantCulture)) + .ConfigureAwait(false); + + await keysets.FlushAsync(ct).ConfigureAwait(false); + } + } + } + + private readonly Task _task; + private readonly CancellationTokenSource _cts; + private readonly IClientDiagnostics _diagnostics; + } +} From 154a00a3402e9185fb22d2cd7da99a0a6dd5e938 Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Wed, 3 Jul 2024 10:43:43 +0200 Subject: [PATCH 2/4] Add TTL and retention settings for messages and writer groups Introduced new properties for message TTL (Time to Live) and retention settings across various models, configuration files, and command-line options. Updated hash calculations and equality checks to include these new properties. Enhanced test cases to validate the new settings. Imported necessary namespaces and refactored relevant code to support these changes. --- .../src/PublishedNodesEntryModel.cs | 34 ++++++++++++- .../src/PublishingQueueSettingsModel.cs | 20 +++++++- .../src/Runtime/CommandLine.cs | 6 +++ .../src/Models/WriterGroupContext.cs | 12 ++++- .../src/Runtime/PublisherConfig.cs | 11 ++++ .../src/Runtime/PublisherOptions.cs | 10 ++++ .../src/Services/NetworkMessageEncoder.cs | 45 +++++++++-------- .../src/Services/WriterGroupDataSource.cs | 50 ++++++++++++++++--- .../src/Storage/PublishedNodesConverter.cs | 24 +++++++-- .../tests/Services/Encoder/NetworkMessage.cs | 8 ++- .../Encoder/NetworkMessageEncoderJsonTests.cs | 28 +++++++++-- .../tests/Stack/OpcUaApplicationTests.cs | 1 + .../Extensions/PublishedNodesEntryModelEx.cs | 32 ++++++++++++ 13 files changed, 239 insertions(+), 42 deletions(-) diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedNodesEntryModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedNodesEntryModel.cs index 68ae04953e..4ac20b57bf 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedNodesEntryModel.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedNodesEntryModel.cs @@ -400,10 +400,42 @@ public sealed record class PublishedNodesEntryModel EmitDefaultValue = false)] public bool? DataSetFetchDisplayNames { get; set; } + /// + /// Default time to live for messages sent through + /// the writer group if the transport supports it. + /// + [DataMember(Name = "WriterGroupMessageTtlTimepan", Order = 49, + EmitDefaultValue = false)] + public TimeSpan? WriterGroupMessageTtlTimepan { get; set; } + + /// + /// Default message retention setting for messages sent + /// through the writer group if the transport supports it. + /// + [DataMember(Name = "WriterGroupMessageRetention", Order = 50, + EmitDefaultValue = false)] + public bool? WriterGroupMessageRetention { get; set; } + + /// + /// Message time to live for messages sent by the + /// writer if the transport supports it. + /// + [DataMember(Name = "MessageTtlTimespan", Order = 52, + EmitDefaultValue = false)] + public TimeSpan? MessageTtlTimespan { get; set; } + + /// + /// Message retention setting for messages sent by + /// the writer if the transport supports it. + /// + [DataMember(Name = "MessageRetention", Order = 53, + EmitDefaultValue = false)] + public bool? MessageRetention { get; set; } + /// /// The node to monitor in "ns=" syntax. /// - [DataMember(Name = "NodeId", Order = 50, + [DataMember(Name = "NodeId", Order = 99, EmitDefaultValue = false)] public NodeIdModel? NodeId { get; set; } } diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishingQueueSettingsModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishingQueueSettingsModel.cs index 782b194860..cddb4c0b6e 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishingQueueSettingsModel.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishingQueueSettingsModel.cs @@ -6,6 +6,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Models { using Furly.Extensions.Messaging; + using System; using System.Runtime.Serialization; /// @@ -24,10 +25,27 @@ public sealed record class PublishingQueueSettingsModel /// /// Desired Quality of service to use in case of broker - /// transport. + /// transport that supports configuring delivery guarantees. /// [DataMember(Name = "requestedDeliveryGuarantee", Order = 2, EmitDefaultValue = false)] public QoS? RequestedDeliveryGuarantee { get; set; } + + /// + /// Desired Time to live to use in case of using a broker + /// transport that supports ttl. + /// + [DataMember(Name = "ttl", Order = 3, + EmitDefaultValue = false)] + public TimeSpan? Ttl { get; set; } + + /// + /// If the broker transport supports message retention this + /// setting determines if the messages should be retained + /// in the queue. + /// + [DataMember(Name = "retain", Order = 4, + EmitDefaultValue = false)] + public bool? Retain { get; set; } } } diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs index 88e937e80c..c1f4ca9e2c 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs @@ -140,6 +140,12 @@ public CommandLine(string[] args, CommandLineLogger? logger = null) { $"qos|{PublisherConfig.DefaultQualityOfServiceKey}=", $"The default quality of service to use for data set messages.\nThis does not apply to metadata messages which are always sent with `AtLeastOnce` semantics.\nAllowed values:\n `{string.Join("`\n `", Enum.GetNames(typeof(QoS)))}`\nDefault: `{nameof(QoS.AtLeastOnce)}`.\n", (QoS q) => this[PublisherConfig.DefaultQualityOfServiceKey] = q.ToString() }, + { $"ttl|{PublisherConfig.DefaultMessageTimeToLiveKey}=", + "The default time to live for all network message published in milliseconds if the transport supports it.\nThis does not apply to metadata messages which are always sent with a ttl of the metadata update interval or infinite ttl.\nDefault: `not set` (infinite).\n", + (uint k) => this[PublisherConfig.DefaultMessageTimeToLiveKey] = TimeSpan.FromMilliseconds(k).ToString() }, + { $"retain:|{PublisherConfig.DefaultMessageRetentionKey}:", + "Whether by default to send messages with retain flag to a broker if the transport supports it.\nThis does not apply to metadata messages which are always sent as retained messages.\nDefault: `false'.\n", + (bool? b) => this[PublisherConfig.DefaultMessageRetentionKey] = b?.ToString() ?? "True" }, // TODO: Add ConfiguredMessageSize diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Models/WriterGroupContext.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Models/WriterGroupContext.cs index 1563299373..34357758a4 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Models/WriterGroupContext.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Models/WriterGroupContext.cs @@ -14,7 +14,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Models public record class WriterGroupContext { /// - /// Topic for the message if not metadata message + /// Topic for the message /// public required string Topic { get; init; } @@ -23,6 +23,16 @@ public record class WriterGroupContext /// public required QoS? Qos { get; init; } + /// + /// Requested Retain + /// + public bool? Retain { get; init; } + + /// + /// Requested Time to live + /// + public TimeSpan? Ttl { get; init; } + /// /// Publisher id /// diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherConfig.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherConfig.cs index 6810701eed..9bd537e821 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherConfig.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherConfig.cs @@ -70,6 +70,8 @@ public sealed class PublisherConfig : PostConfigureOptionBase public const string RenewTlsCertificateOnStartupKey = "RenewTlsCertificateOnStartup"; public const string DefaultTransportKey = "DefaultTransport"; public const string DefaultQualityOfServiceKey = "DefaultQualityOfService"; + public const string DefaultMessageTimeToLiveKey = "DefaultMessageTimeToLive"; + public const string DefaultMessageRetentionKey = "DefaultMessageRetention"; public const string DefaultDataSetRoutingKey = "DefaultDataSetRouting"; public const string ApiKeyOverrideKey = "ApiKey"; public const string PublishMessageSchemaKey = "PublishMessageSchema"; @@ -338,6 +340,15 @@ public override void PostConfigure(string? name, PublisherOptions options) options.DefaultQualityOfService = qos; } + if (options.DefaultMessageTimeToLive == null) + { + var ttl = GetIntOrNull(DefaultMessageTimeToLiveKey); + options.DefaultMessageTimeToLive = ttl.HasValue ? + TimeSpan.FromMilliseconds(ttl.Value) : GetDurationOrNull( + DefaultMessageTimeToLiveKey); + } + options.DefaultMessageRetention = GetBoolOrNull(DefaultMessageRetentionKey); + if (options.MessageTimestamp == null) { if (!Enum.TryParse(GetStringOrDefault(MessageTimestampKey), diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherOptions.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherOptions.cs index 7c0a230bc8..20ba1a3d18 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherOptions.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Runtime/PublisherOptions.cs @@ -157,6 +157,16 @@ public sealed class PublisherOptions /// public QoS? DefaultQualityOfService { get; set; } + /// + /// Default message time to live + /// + public TimeSpan? DefaultMessageTimeToLive { get; set; } + + /// + /// Default whether to set message retain flag + /// + public bool? DefaultMessageRetention { get; set; } + /// /// Default Max data set messages per published network /// message. diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Services/NetworkMessageEncoder.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Services/NetworkMessageEncoder.cs index 9c0b3d7d87..dc1791fbc0 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Services/NetworkMessageEncoder.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Services/NetworkMessageEncoder.cs @@ -122,16 +122,16 @@ public void Dispose() .SetTimestamp(_timeProvider.GetUtcNow().UtcDateTime) // TODO: move to offsets .SetContentEncoding(m.networkMessage.ContentEncoding) .SetContentType(m.networkMessage.ContentType) - .SetTopic(m.topic) - .SetRetain(m.retain) - .SetQoS(m.qos) + .SetTopic(m.queue.QueueName!) + .SetRetain(m.queue.Retain ?? false) + .SetQoS(m.queue.RequestedDeliveryGuarantee ?? QoS.AtLeastOnce) .AddBuffers(chunks) ; - if (m.ttl != default) + if (m.queue.Ttl.HasValue) { chunkedMessage = chunkedMessage - .SetTtl(m.ttl); + .SetTtl(m.queue.Ttl.Value); } if (m.schema != null) @@ -200,16 +200,13 @@ public void Dispose() /// /// /// - /// - /// - /// - /// + /// /// /// /// private record struct EncodedMessage(int notificationsPerMessage, - PubSubMessage networkMessage, string topic, bool retain, - TimeSpan ttl, QoS qos, Action onSent, IEventSchema? schema, + PubSubMessage networkMessage, PublishingQueueSettingsModel queue, + Action onSent, IEventSchema? schema, IServiceMessageContext? encodingContext = null); /// @@ -224,18 +221,23 @@ private List GetNetworkMessages(IEnumerable(); - static QoS GetQos(WriterGroupContext context, QoS? defaultQos) + static PublishingQueueSettingsModel GetQueue(WriterGroupContext context, PublisherOptions options) { - return context.Qos ?? defaultQos ?? QoS.AtLeastOnce; + return new PublishingQueueSettingsModel + { + RequestedDeliveryGuarantee = context.Qos, + Retain = context.Retain, + Ttl = context.Ttl, + QueueName = context.Topic + }; } // Group messages by topic and qos, then writer group and then by dataset class id foreach (var topics in messages .Select(m => (Notification: m, Context: (m.Context as WriterGroupContext)!)) .Where(m => m.Context != null) - .GroupBy(m => (m.Context!.Topic, - GetQos(m.Context, _options.Value.DefaultQualityOfService)))) + .GroupBy(m => GetQueue(m.Context, _options.Value))) { - var (topic, qos) = topics.Key; + var queue = topics.Key; foreach (var publishers in topics.GroupBy(m => m.Context.PublisherId)) { var publisherId = publishers.Key; @@ -463,7 +465,7 @@ void AddMessage(BaseDataSetMessage dataSetMessage) if (maxMessagesToPublish != null && currentMessage.Messages.Count >= maxMessagesToPublish) { result.Add(new EncodedMessage(currentNotifications.Count, currentMessage, - topic, false, default, qos, () => currentNotifications.ForEach(n => n.Dispose()), + queue, () => currentNotifications.ForEach(n => n.Dispose()), schema, Notification.ServiceMessageContext)); #if DEBUG currentNotifications.ForEach(n => n.MarkProcessed()); @@ -479,7 +481,7 @@ void AddMessage(BaseDataSetMessage dataSetMessage) { // Start a new message but first emit current result.Add(new EncodedMessage(currentNotifications.Count, currentMessage, - topic, false, default, qos, () => currentNotifications.ForEach(n => n.Dispose()), + queue, () => currentNotifications.ForEach(n => n.Dispose()), schema, Notification.ServiceMessageContext)); #if DEBUG currentNotifications.ForEach(n => n.MarkProcessed()); @@ -494,8 +496,7 @@ void AddMessage(BaseDataSetMessage dataSetMessage) Notification.MetaData, namespaceFormat, standardsCompliant, out var metadataMessage)) { - result.Add(new EncodedMessage(0, metadataMessage, topic, true, - Context.Writer.MetaDataUpdateTime ?? default, qos, Notification.Dispose, + result.Add(new EncodedMessage(0, metadataMessage, queue, Notification.Dispose, schema, Notification.ServiceMessageContext)); } #if DEBUG @@ -506,8 +507,8 @@ void AddMessage(BaseDataSetMessage dataSetMessage) } if (currentMessage?.Messages.Count > 0) { - result.Add(new EncodedMessage(currentNotifications.Count, currentMessage, topic, false, - default, qos, () => currentNotifications.ForEach(n => n.Dispose()), + result.Add(new EncodedMessage(currentNotifications.Count, currentMessage, queue, + () => currentNotifications.ForEach(n => n.Dispose()), schema, currentNotifications.LastOrDefault()?.ServiceMessageContext)); #if DEBUG currentNotifications.ForEach(n => n.MarkProcessed()); diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Services/WriterGroupDataSource.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Services/WriterGroupDataSource.cs index 87e5786010..027be367f1 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Services/WriterGroupDataSource.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Services/WriterGroupDataSource.cs @@ -419,8 +419,16 @@ public DataSetWriterSubscription(WriterGroupDataSource outer, }, _variables); _topic = builder.TelemetryTopic; + _qos = _dataSetWriter.Publishing?.RequestedDeliveryGuarantee - ?? _outer._writerGroup.Publishing?.RequestedDeliveryGuarantee; + ?? _outer._writerGroup.Publishing?.RequestedDeliveryGuarantee + ?? _outer._options.Value.DefaultQualityOfService; + _ttl = _dataSetWriter.Publishing?.Ttl + ?? _outer._writerGroup.Publishing?.Ttl + ?? _outer._options.Value.DefaultMessageTimeToLive; + _retain = _dataSetWriter.Publishing?.Retain + ?? _outer._writerGroup.Publishing?.Retain + ?? _outer._options.Value.DefaultMessageRetention; _metadataTopic = builder.DataSetMetaDataTopic; if (string.IsNullOrWhiteSpace(_metadataTopic)) @@ -431,7 +439,8 @@ public DataSetWriterSubscription(WriterGroupDataSource outer, _contextSelector = _routing == DataSetRoutingMode.None ? n => n.Context : n => n.PathFromRoot == null || n.Context != null ? n.Context : new TopicContext( - _topic, n.PathFromRoot, _qos, _routing != DataSetRoutingMode.UseBrowseNames); + _topic, n.PathFromRoot, _qos, _retain, _ttl, + _routing != DataSetRoutingMode.UseBrowseNames); TagList = new TagList(outer._metrics.TagList.ToArray().AsSpan()) { @@ -818,7 +827,8 @@ private void CallMessageReceiverDelegates(IOpcUaSubscriptionNotification subscri #pragma warning disable CA2000 // Dispose objects before losing scope var metadata = new MetadataNotificationModel(notification, _outer._timeProvider) { - Context = CreateMessageContext(_metadataTopic, _qos, + Context = CreateMessageContext(_metadataTopic, QoS.AtLeastOnce, true, + _metadataTimer?.Interval ?? _dataSetWriter.MetaDataUpdateTime, () => Interlocked.Increment(ref _metadataSequenceNumber)) }; #pragma warning restore CA2000 // Dispose objects before losing scope @@ -830,7 +840,7 @@ private void CallMessageReceiverDelegates(IOpcUaSubscriptionNotification subscri if (!metaDataTimer) { Debug.Assert(notification.Notifications != null); - notification.Context = CreateMessageContext(_topic, _qos, + notification.Context = CreateMessageContext(_topic, _qos, _retain, _ttl, () => Interlocked.Increment(ref _dataSetSequenceNumber), itemContext); _outer._logger.LogTrace("Enqueuing notification: {Notification}", notification.ToString()); @@ -844,8 +854,8 @@ private void CallMessageReceiverDelegates(IOpcUaSubscriptionNotification subscri _outer._logger.LogWarning(ex, "Failed to produce message."); } - WriterGroupContext CreateMessageContext(string topic, QoS? qos, Func sequenceNumber, - MonitoredItemContext? item = null) + WriterGroupContext CreateMessageContext(string topic, QoS? qos, bool? retain, TimeSpan? ttl, + Func sequenceNumber, MonitoredItemContext? item = null) { _outer.GetWriterGroup(out var writerGroup, out var networkMessageSchema); return new WriterGroupContext @@ -855,6 +865,8 @@ WriterGroupContext CreateMessageContext(string topic, QoS? qos, Func seque NextWriterSequenceNumber = sequenceNumber, WriterGroup = writerGroup, Schema = networkMessageSchema, + Retain = item?.Retain ?? retain, + Ttl = item?.Ttl ?? ttl, Topic = item?.Topic ?? topic, Qos = item?.Qos ?? qos }; @@ -960,6 +972,16 @@ private abstract class MonitoredItemContext /// Topic for the message if not metadata message /// public abstract QoS? Qos { get; } + + /// + /// Time to live + /// + public abstract TimeSpan? Ttl { get; } + + /// + /// Retain + /// + public abstract bool? Retain { get; } } /// @@ -971,6 +993,10 @@ private sealed class TopicContext : MonitoredItemContext public override string Topic { get; } /// public override QoS? Qos { get; } + /// + public override TimeSpan? Ttl { get; } + /// + public override bool? Retain { get; } /// /// Create @@ -978,9 +1004,11 @@ private sealed class TopicContext : MonitoredItemContext /// /// /// + /// + /// /// public TopicContext(string root, RelativePath subpath, QoS? qos, - bool includeNamespaceIndex) + bool? retain, TimeSpan? ttl, bool includeNamespaceIndex) { var sb = new StringBuilder().Append(root); foreach (var path in subpath.Elements) @@ -993,6 +1021,8 @@ public TopicContext(string root, RelativePath subpath, QoS? qos, sb.Append(TopicFilter.Escape(path.TargetName.Name)); } Topic = sb.ToString(); + Ttl = ttl; + Retain = retain; Qos = qos; } @@ -1019,6 +1049,10 @@ private sealed class LazilyEvaluatedContext : MonitoredItemContext public override string Topic => _topic.Value; /// public override QoS? Qos => _settings.RequestedDeliveryGuarantee; + /// + public override TimeSpan? Ttl => _settings.Ttl; + /// + public override bool? Retain => _settings.Retain; /// /// Create context @@ -1064,6 +1098,8 @@ public override int GetHashCode() private volatile uint _frameCount; private readonly string _topic; private readonly QoS? _qos; + private readonly TimeSpan? _ttl; + private readonly bool? _retain; private readonly string _metadataTopic; private readonly Dictionary _variables; private readonly DataSetRoutingMode _routing; diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs index 8a28c24d70..4edc8187a3 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs @@ -114,6 +114,8 @@ public IEnumerable ToPublishedNodes(uint version, Date MessageEncoding = item.WriterGroup.MessageType, WriterGroupTransport = item.WriterGroup.Transport, WriterGroupQualityOfService = item.WriterGroup.Publishing?.RequestedDeliveryGuarantee, + WriterGroupMessageTtlTimepan = item.WriterGroup.Publishing?.Ttl, + WriterGroupMessageRetention = item.WriterGroup.Publishing?.Retain, WriterGroupPartitions = item.WriterGroup.PublishQueuePartitions, WriterGroupQueueName = item.WriterGroup.Publishing?.QueueName, SendKeepAliveDataSetMessages = item.Writer.DataSet?.SendKeepAlive ?? false, @@ -121,6 +123,8 @@ public IEnumerable ToPublishedNodes(uint version, Date MetaDataUpdateTimeTimespan = item.Writer.MetaDataUpdateTime, QueueName = item.Writer.Publishing?.QueueName, QualityOfService = item.Writer.Publishing?.RequestedDeliveryGuarantee, + MessageTtlTimespan = item.Writer.Publishing?.Ttl, + MessageRetention = item.Writer.Publishing?.Retain, MetaDataQueueName = item.Writer.MetaData?.QueueName, MetaDataUpdateTime = null, BatchTriggerIntervalTimespan = item.WriterGroup.PublishingInterval, @@ -369,7 +373,9 @@ public IEnumerable ToWriterGroups(IEnumerable ToWriterGroups(IEnumerable op ? null : new PublishingQueueSettingsModel { QueueName = node.Topic, - RequestedDeliveryGuarantee = node.QualityOfService + RequestedDeliveryGuarantee = node.QualityOfService, + Retain = null, + Ttl = null }, Triggering = skipTriggering || node.TriggeredNodes == null ? null : new PublishedDataSetTriggerModel @@ -659,7 +671,9 @@ static PublishedEventItemsModel ToPublishedEventItems(IEnumerable ? null : new PublishingQueueSettingsModel { QueueName = node.Topic, - RequestedDeliveryGuarantee = node.QualityOfService + RequestedDeliveryGuarantee = node.QualityOfService, + Retain = null, + Ttl = null }, Triggering = skipTriggering || node.TriggeredNodes == null ? null : new PublishedDataSetTriggerModel diff --git a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessage.cs b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessage.cs index 21e9c843e8..7678fc3e30 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessage.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessage.cs @@ -108,7 +108,7 @@ public static IList GenerateSampleSubscriptionNot uint numOfMessages, bool eventList = false, MessageEncoding encoding = MessageEncoding.Json, NetworkMessageContentFlags extraNetworkMessageMask = 0, - bool isSampleMode = false) + bool isSampleMode = false, bool randomTopic = false) { var messages = new List(); const string publisherId = "Publisher"; @@ -234,13 +234,16 @@ public static IList GenerateSampleSubscriptionNot } } +#pragma warning disable CA5394 // Do not use insecure randomness var message = new SubscriptionNotificationModel(DateTimeOffset.UtcNow, new ServiceMessageContext()) { Context = new WriterGroupContext { NextWriterSequenceNumber = () => i, Qos = null, - Topic = string.Empty, + Topic = randomTopic ? Guid.NewGuid().ToString() : string.Empty, + Retain = false, + Ttl = randomTopic ? TimeSpan.FromSeconds(Random.Shared.Next(60)) : null, PublisherId = publisherId, Schema = null, Writer = writer, @@ -254,6 +257,7 @@ public static IList GenerateSampleSubscriptionNot EndpointUrl = "EndpointUrl" + suffix, ApplicationUri = "ApplicationUri" + suffix }; +#pragma warning restore CA5394 // Do not use insecure randomness messages.Add(message); } diff --git a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessageEncoderJsonTests.cs b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessageEncoderJsonTests.cs index 6439095043..69f08304e8 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessageEncoderJsonTests.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/Encoder/NetworkMessageEncoderJsonTests.cs @@ -108,6 +108,26 @@ public void EncodeJsonTest(bool encodeBatchFlag) Assert.Equal(20, encoder.AvgNotificationsPerMessage); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EncodeJsonWithRandomTopicTest(bool encodeBatchFlag) + { + const int maxMessageSize = 256 * 1024; + var messages = NetworkMessage.GenerateSampleSubscriptionNotifications(20, false, MessageEncoding.Json, randomTopic: true); + + using var encoder = GetEncoder(); + var networkMessages = encoder.Encode(NetworkMessage.Create, messages, maxMessageSize, encodeBatchFlag); + + // Batch or no batch, each notification has its own topic, so every single one generates a message + + Assert.Equal(20, networkMessages.Sum(m => ((NetworkMessage)m.Event).Buffers.Count)); + Assert.Equal(20, encoder.NotificationsProcessedCount); + Assert.Equal(0, encoder.NotificationsDroppedCount); + Assert.Equal(20, encoder.MessagesProcessedCount); + Assert.Equal(1, encoder.AvgNotificationsPerMessage); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -129,12 +149,14 @@ public void EncodeChunkTest(bool encodeBatchFlag) Assert.Equal(1, Math.Round(encoder.AvgNotificationsPerMessage)); } - [Fact] - public void EncodeJsonSingleMessageTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void EncodeJsonSingleMessageTest(bool randomTopic) { const int maxMessageSize = 256 * 1024; var messages = NetworkMessage.GenerateSampleSubscriptionNotifications(20, false, MessageEncoding.Json, - NetworkMessageContentFlags.SingleDataSetMessage); + NetworkMessageContentFlags.SingleDataSetMessage, randomTopic: randomTopic); using var encoder = GetEncoder(); var networkMessages = encoder.Encode(NetworkMessage.Create, messages, maxMessageSize, false); diff --git a/src/Azure.IIoT.OpcUa.Publisher/tests/Stack/OpcUaApplicationTests.cs b/src/Azure.IIoT.OpcUa.Publisher/tests/Stack/OpcUaApplicationTests.cs index e653859e28..db8bc83e06 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/tests/Stack/OpcUaApplicationTests.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/tests/Stack/OpcUaApplicationTests.cs @@ -408,6 +408,7 @@ private static IContainer Build() var containerBuilder = new ContainerBuilder(); containerBuilder.AddLogging(); containerBuilder.AddOpcUaStack(); + containerBuilder.AddNewtonsoftJsonSerializer(); return containerBuilder.Build(); } } diff --git a/src/Azure.IIoT.OpcUa/src/Publisher/Extensions/PublishedNodesEntryModelEx.cs b/src/Azure.IIoT.OpcUa/src/Publisher/Extensions/PublishedNodesEntryModelEx.cs index 552db23152..3d4a1c9322 100644 --- a/src/Azure.IIoT.OpcUa/src/Publisher/Extensions/PublishedNodesEntryModelEx.cs +++ b/src/Azure.IIoT.OpcUa/src/Publisher/Extensions/PublishedNodesEntryModelEx.cs @@ -62,6 +62,14 @@ public static string GetUniqueWriterGroupId(this PublishedNodesEntryModel model) { id.Append(model.WriterGroupPartitions.Value); } + if (model.WriterGroupMessageTtlTimepan != null) + { + id.Append(model.WriterGroupMessageTtlTimepan.Value); + } + if (model.WriterGroupMessageRetention == true) + { + id.AppendLine(); + } return id.ToString().ToSha1Hash(); } @@ -116,6 +124,14 @@ public static bool HasSameWriterGroup(this PublishedNodesEntryModel model, { return false; } + if (model.WriterGroupMessageRetention != that.WriterGroupMessageRetention) + { + return false; + } + if (model.WriterGroupMessageTtlTimepan != that.WriterGroupMessageTtlTimepan) + { + return false; + } return true; } @@ -337,6 +353,14 @@ public static string GetUniqueDataSetWriterId(this PublishedNodesEntryModel mode { id.Append(model.DataSetFetchDisplayNames.Value); } + if (model.MessageTtlTimespan != null) + { + id.Append(model.MessageTtlTimespan.Value); + } + if (model.MessageRetention == true) + { + id.AppendLine(); + } Debug.Assert(id.Length != 0); // Should always have an endpoint mixed in return id.ToString().ToSha1Hash(); } @@ -492,6 +516,14 @@ public static bool HasSameDataSet(this PublishedNodesEntryModel model, { return false; } + if (model.MessageRetention != that.MessageRetention) + { + return false; + } + if (model.MessageTtlTimespan != that.MessageTtlTimespan) + { + return false; + } return true; } From 06ab84f2ac4393f834f39b8716ff0972a41fb770 Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Wed, 3 Jul 2024 10:52:20 +0200 Subject: [PATCH 3/4] Add namespace and async complex type system loading Added a conditional block to load the complex type system asynchronously if it is not already loaded and complex type loading is not disabled. --- .../src/Stack/Services/OpcUaClient.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs index 7a9275e0f1..f2f3855097 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs @@ -1024,6 +1024,12 @@ async ValueTask ApplySubscriptionAsync(IReadOnlyList subscri _logger.LogWarning(sre, "{Client}: Failed to fetch namespace table...", this); } + if (!DisableComplexTypeLoading && !session.IsTypeSystemLoaded) + { + // Ensure type system is loaded + await session.GetComplexTypeSystemAsync(ct).ConfigureAwait(false); + } + await Task.WhenAll(subscriptions.Concat(extra).Select(async subscription => { try From cac4f34cef13d2ea4b4269ecf6e5465c3064c853 Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Wed, 3 Jul 2024 11:16:33 +0200 Subject: [PATCH 4/4] Formatting --- .../src/PublishedDataItemsModel.cs | 1 - .../src/Controllers/DiagnosticsController.cs | 4 ++-- .../src/Runtime/CommandLine.cs | 2 -- .../src/Services/PublisherConfigurationService.cs | 1 - .../src/Stack/Services/OpcUaClient.cs | 4 ++-- .../src/Stack/Services/OpcUaClientManager.cs | 2 +- .../src/Stack/Services/OpcUaStackKeySetLogger.cs | 9 ++++----- .../src/Storage/PublishedNodesConverter.cs | 2 +- .../tests/Services/PublisherConfigServicesTests.cs | 1 - src/Azure.IIoT.OpcUa/src/Encoders/ConsoleWriter.cs | 1 - 10 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedDataItemsModel.cs b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedDataItemsModel.cs index ad3c64849c..52a23ab920 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedDataItemsModel.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Models/src/PublishedDataItemsModel.cs @@ -5,7 +5,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Models { - using System; using System.Collections.Generic; using System.Runtime.Serialization; diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs index 7ffd28b5b5..3013c0771c 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Controllers/DiagnosticsController.cs @@ -6,6 +6,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Controllers { using Azure.IIoT.OpcUa.Publisher.Module.Filters; + using Azure.IIoT.OpcUa.Publisher.Models; using Asp.Versioning; using Furly; using Furly.Tunnel.Router; @@ -13,10 +14,9 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Controllers using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Azure.IIoT.OpcUa.Publisher.Models; - using System.Collections.Generic; /// /// diff --git a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs index c1f4ca9e2c..e930ff08f1 100644 --- a/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs +++ b/src/Azure.IIoT.OpcUa.Publisher.Module/src/Runtime/CommandLine.cs @@ -7,7 +7,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Runtime { using Azure.IIoT.OpcUa.Publisher.Models; using Azure.IIoT.OpcUa.Publisher.Stack.Runtime; - using Azure.IIoT.OpcUa.Publisher.Stack.Services; using Furly.Azure.IoT.Edge; using Furly.Extensions.Messaging; using Microsoft.Extensions.Configuration; @@ -16,7 +15,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Module.Runtime using Opc.Ua; using System; using System.Collections.Generic; - using System.Diagnostics; using System.Globalization; using System.IO; diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Services/PublisherConfigurationService.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Services/PublisherConfigurationService.cs index 0cbc90fdd0..37ec6a11b3 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Services/PublisherConfigurationService.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Services/PublisherConfigurationService.cs @@ -14,7 +14,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Services using Furly.Exceptions; using Furly.Extensions.Serializers; using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs index f2f3855097..5cb5c2bd47 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClient.cs @@ -13,20 +13,20 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using Microsoft.Extensions.Logging; using Nito.AsyncEx; using Opc.Ua; - using Opc.Ua.Client; using Opc.Ua.Bindings; + using Opc.Ua.Client; using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; using System.Globalization; using System.Linq; + using System.Net; using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; - using System.Net; /// /// OPC UA Client based on official ua client reference sample. diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs index 276aee2d1c..16ea782061 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaClientManager.cs @@ -13,6 +13,7 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using Furly.Extensions.Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Nito.AsyncEx; using Opc.Ua; using Opc.Ua.Client; using System; @@ -24,7 +25,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; - using Nito.AsyncEx; /// /// Client manager diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs index 59744326f4..bf9881035a 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaStackKeySetLogger.cs @@ -5,15 +5,14 @@ namespace Azure.IIoT.OpcUa.Publisher.Stack.Services { + using Azure.IIoT.OpcUa.Publisher.Models; using Microsoft.Extensions.Options; using System; - using System.Threading.Tasks; - using System.Threading; - using System.Linq; using System.Globalization; using System.IO; - using Azure.IIoT.OpcUa.Publisher.Stack.Models; - using Azure.IIoT.OpcUa.Publisher.Models; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; /// /// diff --git a/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs b/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs index 4edc8187a3..1ffbbe407a 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/src/Storage/PublishedNodesConverter.cs @@ -390,7 +390,7 @@ public IEnumerable ToWriterGroups(IEnumerable w.OpcNodes!) .Distinct(OpcNodeModelEx.Comparer) .Batch(_maxNodesPerDataSet) - // Future: batch in service so it is centralized + // Future: batch in service so it is centralized )) .SelectMany(b => b.WriterBatches // Do we need to materialize here? .DefaultIfEmpty(kDummyEntry.YieldReturn()) diff --git a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/PublisherConfigServicesTests.cs b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/PublisherConfigServicesTests.cs index 9d9785579c..6ead41f8b1 100644 --- a/src/Azure.IIoT.OpcUa.Publisher/tests/Services/PublisherConfigServicesTests.cs +++ b/src/Azure.IIoT.OpcUa.Publisher/tests/Services/PublisherConfigServicesTests.cs @@ -11,7 +11,6 @@ namespace Azure.IIoT.OpcUa.Publisher.Tests.Services using Azure.IIoT.OpcUa.Publisher.Models; using Azure.IIoT.OpcUa.Publisher.Stack.Runtime; using Azure.IIoT.OpcUa.Publisher.Storage; - using Avro.Generic; using FluentAssertions; using Furly.Exceptions; using Furly.Extensions.Serializers; diff --git a/src/Azure.IIoT.OpcUa/src/Encoders/ConsoleWriter.cs b/src/Azure.IIoT.OpcUa/src/Encoders/ConsoleWriter.cs index fd500c07df..0060d6d08a 100644 --- a/src/Azure.IIoT.OpcUa/src/Encoders/ConsoleWriter.cs +++ b/src/Azure.IIoT.OpcUa/src/Encoders/ConsoleWriter.cs @@ -8,7 +8,6 @@ namespace Azure.IIoT.OpcUa.Encoders using Furly; using Furly.Extensions.Messaging; using Furly.Extensions.Storage; - using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Buffers;