diff --git a/docs/Historian.md b/docs/Historian.md index 18e70b426..4f8f36d11 100644 --- a/docs/Historian.md +++ b/docs/Historian.md @@ -1848,9 +1848,9 @@ value at **10:00**, not 09:00, and no point ever falls before the window. The in `(start − (k+1)·interval, start − k·interval]`, the earliest one truncated, each stamped with its later edge. **SDK parity (#677).** `SdkAggregateParityTests` (unit, `Sql.Common.Tests`) and -`ProcessedConformance.Processed_reads_match_the_SDK_aggregate_calculators` (both SQL backends, through the real SQL) run the pinned SDK's -own calculators (`AverageAggregateCalculator`, `MinMaxAggregateCalculator`, `CountAggregateCalculator`, the base `AggregateCalculator` -for Interpolative, `StatusAggregateCalculator` for PercentGood/Bad) over the same samples, forward and reverse, and compare every +`ProcessedConformance.Processed_reads_match_the_SDK_aggregate_calculators` (both SQL backends, through the real SQL) use the preserved +1.5.378.182 calculator source through `CompatibilityAggregateFactory`, not the new SDK 2 algorithms, over the same samples, +forward and reverse, and compare every interval's timestamp, value, status code and Partial bit. The per-sample comparison covers Average, Minimum, Maximum, Count, Range, Start, End, Delta and Minimum/MaximumActualTime; the time-weighted one TimeAverage, TimeAverage2, Total, Total2, Interpolative and PercentGood/Bad (#680), including datasets whose data ends inside the window and with an interval wholly inside an outage; reverse diff --git a/docs/security.md b/docs/security.md index 41eb370d9..c68a09a83 100644 --- a/docs/security.md +++ b/docs/security.md @@ -301,12 +301,13 @@ guards in front of the store, bound from the same `Security:ApiKeys` section **Per-client accounting and its limits (#751).** `IOpcUaUserAuthenticator` has an endpoint-carrying `AuthenticateUserNameAsync(username, password, remoteEndpoint, ct)` overload (default: ignore the endpoint and delegate), which the server calls and `ApiKeyOpcUaUserAuthenticator` forwards to -`IApiKeyStore.FindAsync(..., remoteAddress, ...)`. The OPC UA SDK (1.5.378) exposes no client address on any -public surface, so `SecureChannelRemoteEndpoint` reads it from the TCP listener's private channel map by -reflection, keyed by the session's secure channel id. It is fail-soft: an unresolvable address (an SDK upgrade -that renamed the members, or a client re-activating over a new channel) is charged to the process-wide bucket -only, which therefore stays as the backstop. `ApiKeyMissBudgetPerEndpointTests` (OpcUaServer.IntegrationTests) -pins the resolution over the wire so such an upgrade fails a test. At most `BoundedApiKeyStore.MaxTrackedEndpoints` +`IApiKeyStore.FindAsync(..., remoteAddress, ...)`. SDK 2.0.1 supplies the public transport-observed +`SecureChannelContext.PeerAddress` on session activation. `OtOpcUaSdkServer` scopes it to the current +asynchronous activation and restores the prior context in `finally`; `OpcUaApplicationHost` adapts it to +the existing IP-budget seam with port zero (no peer port is exposed). The former private-channel-map +reflection helper is removed. An unknown address is charged to the process-wide bucket only, which +therefore stays as the backstop. `ApiKeyMissBudgetPerEndpointTests` (OpcUaServer.IntegrationTests) +pins per-client accounting over the wire; concurrent IPv4/IPv6 activation probes check peer isolation. At most `BoundedApiKeyStore.MaxTrackedEndpoints` (4096) addresses are tracked; when full, idle (refilled) buckets are pruned and a new address beyond that is charged process-wide only — never handed a fresh budget. Clients behind one NAT address share a bucket. Neither guard applies to a driver-only node: its `ArtifactApiKeyStore` is two in-memory dictionary probes and is never diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs index da89da042..411ed295a 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs @@ -1086,9 +1086,8 @@ public sealed class OpcUaClientDriver : IDriver, ITagDiscovery, IReadable, IWrit { // GetEndpoints returns everything the server advertises; policy + mode filter is // applied client-side so the selection is explicit and fails loudly if the operator - // asks for a combination the server doesn't publish. DiscoveryClient.CreateAsync - // is the non-obsolete path in SDK 1.5.378; the synchronous Create(..) variants are - // all deprecated. + // asks for a combination the server doesn't publish. Use the SDK's asynchronous + // discovery factory and pass the caller's cancellation token. using var client = await DiscoveryClient.CreateAsync( appConfig, new Uri(endpointUrl), Opc.Ua.DiagnosticsMasks.None, ct).ConfigureAwait(false); var all = await client.GetEndpointsAsync(default, ct).ConfigureAwait(false); diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs index b383df192..6cbec3f04 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OpcUaApplicationHost.cs @@ -205,8 +205,8 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable if (server is not OtOpcUaSdkServer sdkServer) throw new ArgumentException("The server must support application authentication before its listeners open.", nameof(server)); _server = server; - // 1.5.378 requires an ITelemetryContext on the ApplicationInstance ctor (the parameterless ctor - // is obsolete). DefaultTelemetry.Create wires the SDK's internal logging; an empty builder keeps + // ApplicationInstance receives the host's ITelemetryContext. DefaultTelemetry.Create wires + // the SDK's internal logging; an empty builder keeps // the SDK's trace off our ILogger (the host keeps its own _logger) — sufficient for the bootstrap. _application = new ApplicationInstance(_telemetry) { @@ -363,8 +363,7 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable string password; try { - // 1.5.378 exposes DecryptedPassword as raw bytes (was string); UserName token passwords - // are UTF-8 on the wire. + // The SDK UserName token handler exposes decrypted password bytes; decode the wire UTF-8. var decryptedBytes = (args.UserIdentityTokenHandler as UserNameIdentityTokenHandler)?.DecryptedPassword; password = decryptedBytes is null ? string.Empty : Encoding.UTF8.GetString(decryptedBytes); } diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs index 7f053d6be..2fa14de03 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/IOpcUaUserAuthenticator.cs @@ -32,7 +32,7 @@ public interface IOpcUaUserAuthenticator /// endpoint and delegates, so an implementation that does not need it is unchanged. /// /// - /// The endpoint is best effort () and null when unknown — an + /// The endpoint uses the SDK's public transport peer address during activation and is null when unknown — an /// implementation must treat null as "no per-client information", never as a distinct client. Same /// must-not-throw contract as the three-argument overload. ///