mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-19 07:02:16 +08:00
fuzzing
20 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5530a12d4a |
McpServerOptions: DI-driven NodeSetExportRoot + PcapBaseFolder
Replaces the static-only ExportRoot / env-var-only configuration in NodeSetExportTools with an instance-aware ResolveExportRoot(IServiceProvider) that consults McpServerOptions from DI first. Same pattern applied to PacketDecodeTools.GetDecodeAllowedRoot for symmetric naming. Precedence (both options): services.GetService<McpServerOptions>().Property > OPCUA_MCP_EXPORT_ROOT env var (NodeSet) or PcapOptions.BaseFolder (Decode) > per-tool default. Backward-compat: ExportRoot static property + OPCUA_MCP_EXPORT_ROOT env var both still work. Program.cs now seeds McpServerOptions from OPCUA_MCP_NODESET_EXPORT_ROOT and OPCUA_MCP_PCAP_BASE_FOLDER environment variables so hosts can override without code changes. Tests: 7 NUnit tests in McpServerOptionsTests covering DI > env var > default precedence for both options. Reflection-based to reach internal/private helpers — same pattern as the existing PacketDecodePathValidationTests. Build: 0 warnings / 0 errors on Applications/McpServer for net10.0. Pcap.Tests build blocked locally by the unrelated SDK 10 workload manifest issue (see prior commit for details). |
||
|
|
e3e7134d56 |
Merge branch 'channelrefinements' into channelrefinements2
Resolved NodeSetExportTools.cs conflict by adopting base branch's ResolveExportPath / ExportRoot abstraction (configurable via OPCUA_MCP_EXPORT_ROOT env var) and removing my redundant ResolveAndValidateExportPath helper + NodeSetExportPathValidationTests.cs. Base implementation is functionally a superset of F19's hard-coded LocalAppData approach. |
||
|
|
e8afa4237b |
Security hardening: 25-finding pass against SFI / SDL / OPC UA Part 2 baseline
Addresses 25 of 39 findings from a branch security review (assessment 5). CRITICAL: F1 gate MCP key-disclosure tools, F2 0600 keylog/pcap files, F3 per-user BaseFolder, F16 ChannelKeyMaterial IDisposable + ZeroMemory, F17 PcapFileReader MaxPacketBytes (64MB) bound, F18 FlowBuffer MaxBufferBytes (256MB) cap, F19 NodeSet export path validation, F20 ReplaySession listenPort range. HIGH: F4 IPcapAuditSink + LoggerPcapAuditSink + rate-limited frame-capture audit, F5 AES-256-GCM EncryptedKeyLogStream + SessionKeyManager (sibling .key file 0600), F14 IKeyEscrowProvider abstraction + DiskKeyEscrowProvider default (DI TryAddSingleton so consumers can route to KMS), F21 ChannelManagerOptions.MaxChannels (256) cap, F22 CaptureSessionManager.SessionFolder path-traversal guard, F23 PacketDecodeTools pcapPath/keyLogPath validation, F24 replay_pcap speed validation (NaN/Inf/<=0), F25 explicit permissions blocks on .github/workflows. MEDIUM: F6 OnTokenActivated [EditorBrowsable(Never)] + key-disclosure doc, F7 IDiagnosticsChannelMutation accessor + stack-walk caller guard (replaces internal OfflineLoadTokens), F8 PcapOptions.AllowMockClientReplay + AllowedReplayEndpoints, F9 PcapFileWriter / UaKeyLog*Writer rotation (.NNN suffix) with MaxBytes / MaxArtifactsPerSession, F13 keylog-isolation gap acknowledgment doc, F15 HashChainedAuditFileSink (HMAC-chained JSONL ledger, VerifyChain), F11 replay URL scheme allow-list (opc.tcp/opc.tcps/opc.https). LOW/INFO: F10 SBOM/CVE governance comments on PacketDotNet/SharpPcap + Docs/PacketCapture.md, F12 Security model section at top of PacketCapture.md. Tests: 100+ new NUnit tests across Audit/, Capture/, Frame/, KeyLog/, McpServerTools/, Replay/, Stack/Tcp/, DependencyInjection/. All file-mode/HMAC-chain/encryption tests are [Platform(Linux,MacOSX)]-gated where Unix-specific. Build: 0 warnings / 0 errors on Stack/Opc.Ua.Bindings.Pcap for net8.0 and net9.0. net10.0 build is environmentally blocked by a corrupted local SDK 10.0.300 workload manifest (microsoft.net.workload.mono.toolchain.net6 10.0.108 requires Emscripten.net6 >=10.0.108, only 9.0.17 installed); requires 'dotnet workload repair' from an elevated shell to validate the third TFM. No tests were run pending the SDK fix. 5 CA1508/CA2000 false positives suppressed with documented #pragma blocks (ownership transfer in escrow factory / IAsyncEnumerable iterator / unreachable catch path). Deferred to follow-up GitHub issues: 9 MEDIUM (M-SEC-07/09/10/11/12/13/16/22/31 — exception sanitization, reconnect defaults, cert-rotation races, nonce-suppress deprecation, parser hardening) and 10 LOW/INFO (L-SEC-14/15/17/23/24/33/34/35/36/37 — handler cleanup, EventSource endpoint masking, key-log schema validation, EOL deps, SAST). Full reasoning in session plan.md Round 10. |
||
|
|
5353c69287 | Merge branch 'master' into channelrefinements | ||
|
|
38d4a29070 |
[security] McpServer NodeSetExportTools: constrain MCP-tool file paths to an export root
ExportNodeSetAsync.filePath and ExportNodeSetPerNamespaceAsync.
outputDirectory are LLM-supplied MCP tool arguments. The old code
passed them directly to Directory.CreateDirectory and to
new FileStream(path, FileMode.Create) with no canonicalization or
allowlist. A prompt-injected LLM call could overwrite arbitrary files
the MCP-server process can write to.
Add a ResolveExportPath helper that:
- rejects null/whitespace
- resolves the request via Path.GetFullPath (so .. segments cannot
escape after canonicalization)
- rejects any candidate that would resolve outside the export root
(relative or already-absolute paths must both end up inside)
- returns the canonicalized absolute path
ExportRoot:
- defaults to {Path.GetTempPath}/Opc.Ua.Mcp/exports
- overridable via the OPCUA_MCP_EXPORT_ROOT environment variable
set before the MCP server starts
- cached via Lazy<string> with ExecutionAndPublication semantics
- exposed publicly as NodeSetExportTools.ExportRoot for callers
and tests
Both ExportNodeSetAsync and ExportNodeSetPerNamespaceAsync now call
ResolveExportPath first; if the LLM supplied an out-of-root path,
the call throws ArgumentException before any filesystem write
occurs.
Activity-level success records continue to include the (resolved)
filePath so the MCP caller still sees where the file landed.
Verified Applications/McpServer/Opc.Ua.Mcp.csproj builds clean
(net10.0): 0 W, 0 E.
|
||
|
|
4d613d11dc |
CI: migrate to netstandard Managed DevOps Pool + per-TFM build matrix (#3866)
# Description End-to-end migration of the Azure DevOps CI pipelines to the `netstandard` Managed DevOps Pool with the build matrix re-shaped to fit the new agents and a handful of test/project fixes that fell out of the migration. **Pipeline routing & defaults** - `azure-pipelines.yml` and `azure-pipelines-preview.yml` default `managedPool=true`, routing Windows/Linux jobs to the `netstandard` Managed DevOps Pool with `ImageOverride` demands; flipping the queue-time boolean to `false` falls back to Microsoft-hosted agents. - Per-platform images come from the root yml (single source of truth): - Managed pool: `windows-2022-g2` / `ubuntu-22.04-g2` (Mac stays hosted: `macOS-15`). - Hosted: `windows-2025-vs2026` / `ubuntu-24.04` / `macOS-15`. - `.azurepipelines/test.yml` and `test-aot.yml` declare `hosted` as a typed `boolean` parameter so `not(parameters.hosted)` actually evaluates correctly (an untyped value left it as the string `'false'`, which is truthy and silently routed managed-pool jobs to Microsoft-hosted `vmImage` demands). - macOS test jobs always run on the Microsoft-hosted `macOS-15` image regardless of `managedPool`; the four per-framework test stages queue a dedicated `hosted: true` Mac invocation unconditionally and the main matrix carries only Windows+Linux. - Test-template prep jobs (which only run a PowerShell matrix script) run on the managed pool whenever a `poolName` is set, even when the matrix `hosted: true`, so the Microsoft-hosted parallel-job allotment isn't blocked. **Build stage — per-(TFM × configuration) matrix** - `.azurepipelines/get-matrix.ps1` gained a `-Tfms` parameter that fans the matrix across target frameworks alongside the existing agent / configuration dimensions and emits a per-entry `targetTfm`. - `.azurepipelines/sln.yml` accepts a `tfms` parameter and threads `$(targetTfm)` into both `dotnet restore` and `dotnet build` as `/p:CustomTestTarget=$(targetTfm)` so each matrix job builds a single TFM and fits on the Managed DevOps Pool's small (~16 GB) temp disk. - `azure-pipelines.yml` `build` stage passes `tfms: 'net10.0,net48'` for PR builds and the full `net472,net48,netstandard2.0,netstandard2.1,net8.0,net9.0,net10.0` set for master / scheduled / manual / fork-PR runs. - `sln.yml`'s restore step uses `command: custom` (`custom: restore`) because `command: restore` silently drops the `arguments` input — without it `/p:CustomTestTarget` never reached restore and `obj/project.assets.json` was populated with the wrong TFM set. - `sln.yml` no longer uses the `MSBuild@1` task; it now uses `DotNetCoreCLI@2` with `dotnet build` so the .NET 10 SDK's bundled MSBuild (which understands `.slnx`) is used instead of Visual Studio 2022's MSBuild on the managed-pool image. **Project-level fixes that fell out of the matrix split** - `Applications/McpServer`, `Applications/MinimalBoilerServer`, `Applications/MinimalCalcServer`, and `Tests/Opc.Ua.Aot.Tests` each get a `Directory.Build.targets` that turns the project into a no-op (cleared `Compile` / `ProjectReference` / `PackageReference` / etc., `OutputType=Library`) when `CustomTestTarget` restricts the matrix to a legacy TFM (`net4*` / `netstandard2.*`) the project can't target. Target overrides are deliberately avoided — same-named targets in `Directory.Build.targets` ALWAYS replace the SDK definitions, which would silently break `dotnet publish` / `dotnet test` in the unrestricted GitHub Actions flows. - `Tools/Opc.Ua.MigrationAnalyzer.csproj`'s `ProjectReference` to `Opc.Ua.MigrationAnalyzer.Core` no longer hard-pins `SetTargetFramework=net10.0`; the pin follows `CustomTestTarget` (and falls back to `net10.0` when unset). **Test reliability** - `Tests/Opc.Ua.PubSub.Tests/IntervalRunnerTests.FakeTimeProviderDrivesDeterministicSchedulingAsync` no longer calls a single `fake.Advance(300)` expecting three timers to fire — it advances one 100 ms interval at a time and waits for each fire (the runner only registers the next `Delay` after the previous callback resumes on the thread pool; on slower CI agents the race surfaces consistently). - `Tests/Opc.Ua.Server.Tests/Fluent/SimulationBuilderExtensionsTests` (four "expect ≥ N ticks in 150 ms" tests) replace fixed `Task.Delay`/`Thread.Sleep` waits with a `WaitForAsync` polling helper, eliminating thread-pool cold-start flakiness on slow agents (observed on the hosted `macOS-15` runner). ## Related Issues _No tracking issue — pipeline tuning to standardize on the netstandard Managed DevOps Pool, plus the test/project follow-ups required for the matrix to be green on that pool._ ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. _(N/A — pipeline + test-reliability fixes; the two test fixes themselves were verified to pass 5× in stress runs locally.)_ - [x] I have added all necessary documentation. _(Comments inline in the new `Directory.Build.targets`, `sln.yml`, and `azure-pipelines.yml` explain the unusual MSBuild/`DotNetCoreCLI@2` interactions for future maintainers.)_ - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. _(Verified `dotnet build UA.slnx -p:CustomTestTarget=net10.0 -c Release` and `-p:CustomTestTarget=net48 -c Debug` both complete with 0 errors; targeted test runs for the two modified test files pass 5/5.)_ - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
a4a7d487d9 | McpServer/readme.md: sync tool inventory with Docs (add PKI/Config/NodeSet/Packet Capture, 64 tools) | ||
|
|
a3a4dc2703 |
Merge origin/capture into channelrefinements2 (round 2)
Brings in three new commits on capture: - |
||
|
|
87dcfc10cf |
Merge origin/capture into channelrefinements2
Brings the packet-capture / dissection / replay engine (Opc.Ua.Bindings.Pcap) onto the channel-refinements branch so it composes cleanly with the central IClientChannelManager work landed in PR #3852 (channelrefinements). Conflicts resolved: - Applications/McpServer/Program.cs: union of both ConfigureServices changes — registers AddOpcUa().AddClient() (channel manager) AND AddOpcUaBindingsPcap() / Formatters / Replay (capture). Channel manager picks up the capture binding automatically via the shared TransportBindings.Channels registry. - Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs: keep both m_sessionCount (channelrefinements2) and m_tokenActivated event (origin/capture). Server-side capture relies on TcpListenerChannel.OnTokenActivated to snapshot per-channel ChannelToken keys for offline decoding. - UA.slnx: keep both Opc.Ua.Di.Tests and Opc.Ua.Bindings.Pcap.Tests project entries. Pre-existing fix folded in (build-blocker introduced earlier on channelrefinements2, not by the capture merge): - Applications/Quickstarts.Servers/ReferenceServer/ReferenceServerConfigurationNodeManager.cs s_optionalPubSubFolders previously referenced ObjectIds.SecurityGroups and ObjectIds.KeyPushTargets, neither of which exists on the curated Stack/Opc.Ua.Types/Internal/ObjectIds.cs surface. Inlined the NodeId literals (i=15443 / i=25440) per the docstring above the array so the merged tree builds end-to-end across all TFMs and every transitive test project (Tests/Opc.Ua.Client.Tests, Tests/Opc.Ua.Sessions.Tests, etc.) compiles. Architectural fit ("take advantage of all the changes you made here"): 1. Pcap integrates with the central channel manager via the global TransportBindings.Channels registry. ClientChannelManager.ctor accepts an injected ITransportChannelBindings? that defaults to the global registry, so the Pcap binding installed by AddOpcUaBindingsPcap is automatically picked up by every channel created through the manager. No code wiring needed — the composition is pure layering. 2. Channel sharing transparently includes capture. When multiple participants share a single IManagedTransportChannel (the F1 refcount + key-equivalence work), the underlying CapturingMessageSocket captures all participant traffic on the same wire, automatically. 3. Transparent reconnect preserves the capture session. The F8 diagnostics events on ClientChannelManager (state-change / close notifications) and the Phase E SwapFaultedEntryAsync swap path produce fresh ChannelEntry instances under the same ManagedChannelKey — IChannelCaptureRegistry is keyed by channel identity, so a swap rolls the active observer into the new entry and the capture session continues recording across the reconnect / faulted-swap cycle. 4. MCP server composes both: services.AddOpcUa().AddClient() (channel manager + DI), services.AddSingleton<OpcUaSessionManager>(), services.AddOpcUaBindingsPcap() (capture binding). OpcUaSessionManager resolves IClientChannelManager from IServiceProvider; the Pcap tools (Tools/PacketCapture/Decode/ReplayTools.cs) resolve CaptureSessionManager and IChannelCaptureRegistry from the same provider. Verified locally on net10.0: - Stack/Opc.Ua.Bindings.Pcap build: 0 warnings, 0 errors. - Libraries/Opc.Ua.Client build (channel manager): 0 warnings, 0 errors. - Tests/Opc.Ua.Bindings.Pcap.Tests: 53/53 passed. - Tests/Opc.Ua.Client.Tests ClientChannelManager filter: 29/29 passed. - Tests/Opc.Ua.Sessions.Tests ChannelManager filter: 7/7 passed, 1 skipped (RequiresSessionRecreate carry-forward). - Applications/McpServer build: 0 warnings, 0 errors. |
||
|
|
c781514ad5 |
Refactor pcap diagnostics: decorator binding + rename to Opc.Ua.Bindings.Pcap
Three changes addressing the design feedback on PR #3855: 1. Renamed Stack/Opc.Ua.Diagnostics.Pcap -> Stack/Opc.Ua.Bindings.Pcap (and the corresponding test project). PackageId, AssemblyName and RootNamespace updated to Opc.Ua.Bindings.Pcap; references in McpServer, signlists, slnx files, docs, and the interop-tester agent updated; Opc.Ua.Core's InternalsVisibleTo entries updated. 2. Replaced UaSCUaBinaryChannel.FrameCaptureSink + per-call NotifyFrameCaptureSink null check with a transport-binding decorator. New files in Opc.Ua.Bindings.Pcap/Bindings: - IChannelCaptureRegistry / ChannelCaptureRegistry: singleton with a volatile IFrameCaptureSink slot (1 atomic write to switch recording on/off). - CapturingMessageSocket / CapturingMessageSocketFactory: IMessageSocket decorator that taps Send/Receive bytes via a single volatile read of CurrentObserver. - PcapTransportChannelBinding: ITransportChannelFactory decorator that injects the capturing socket factory into every TcpTransportChannel and wires ISecureChannel.OnTokenActivated through to the active observer. - PcapBindings.Install(): non-DI installer for direct TransportBindings.Channels.SetBinding wiring. IFrameCaptureSink moved to Opc.Ua.Core/Stack/Bindings and gained an OnTokenActivated method so the binding can publish key material alongside chunks. UaSCUaBinaryChannel.FrameCaptureSink property and all three NotifyFrameCaptureSink call sites removed - hot path returns to original shape with zero indirection when the binding is not installed. IMessageSocketAsyncEventArgs gains Offset + Count getters (additive). AddOpcUaDiagnosticsPcap/Formatters/Replay extension methods renamed to AddOpcUaBindingsPcap[Formatters|Replay]; the new AddOpcUaBindingsPcap also installs the binding into TransportBindings.Channels. InProcessClientCaptureSource / InProcessServerCaptureSource: dropped reflection-based AttachClientChannel/AttachServerChannel. The source itself implements IFrameCaptureSink and on Start publishes itself as the registry observer; on Stop clears it. McpServer's PacketCaptureTools simplified accordingly (no more per-channel attach loop). 3. Discarded the source-generator suggestion. Confirmed that the dissection path already 100% reuses the source-generated decoders: UaSCUaBinaryChannel.ReadSymmetricMessage for chunk decryption and BinaryDecoder.ReadEncodeable + EncodeableFactory.GlobalFactory (populated by Tools/Opc.Ua.SourceGeneration's DataTypeGenerator) for service-type decoding. Emitting additional dissection code would duplicate what's already there. Validation: - Full UA.slnx builds clean: 0 errors, 2 pre-existing unrelated warnings. - All 53 tests pass on net10.0 (49 existing + 4 new CapturingMessageSocketTests covering hot-path: not-installed/installed-off/installed-on costs, byte equality, exception isolation). - CI command line (dotnet build/test --framework net10.0 --configuration Release /p:CustomTestTarget=net10.0) reproduces clean locally. |
||
|
|
cbd84388d9 |
Add OPC UA packet capture, offline decoding and replay (Opc.Ua.Diagnostics.Pcap)
Adds a new diagnostics library (Stack/Opc.Ua.Diagnostics.Pcap) plus 13 MCP tools that capture OPC UA traffic, decode it offline by reusing the stack's own UaSCUaBinaryChannel, and replay it. Includes additive stack hooks (IFrameCaptureSink, TcpListenerChannel.OnTokenActivated), four capture sources (nic / inproc-client / inproc-server / replay), two keylog formats (.uakeys.json, .uakeys.txt), six output formats (pcap, pcapng, json, csv, text, service-timeline), and mock-server / mock-client replay engines. 49 new tests pass; no regressions in the 163 existing transport tests; full UA.slnx builds clean. |
||
|
|
82347dc1da |
Address PR #3852 review feedback
- Move all channel-related types into `Stack/Opc.Ua.Core/Stack/Client/Channels/` subfolder (#13). - Un-obsolete `SessionReconnectHandler` and the SessionExtensions `ReconnectAsync(connection, ct)` / `(channel, ct)` extensions; remove all SRH-related `#pragma warning disable CS0618` suppressions from applications and tests. AttachChannel/DetachChannel remain obsolete (#1, #4). - Remove the shared-budget section from MigrationGuide.md (#3). - Mark `ManagedSession.AttachChannel`/`DetachChannel` `[Obsolete]` so the warning surfaces all the way up the supported API surface (#9). - Break the `<see cref=…>` line in `Session.ChannelManager.cs` to stay under 140 chars (#10). - Move `ChannelManagerSessionFactory` from `Opc.Ua.Gds.Client.Common` into `Opc.Ua.Client` as a public, documented session-factory option (#11). - Audit other client classes for `IClientChannelManager`-aware overloads; add `ISessionFactory`-accepting overloads on `GlobalDiscoveryServerClient`, `LocalDiscoveryServerClient`, `ServerPushConfigurationClient` so any session factory (including `ChannelManagerSessionFactory`) works (#12). - Refactor MCP server (`OpcUaSessionManager`, `Program`) to use `ManagedSession` + DI-resolved `IClientChannelManager`; remove `SessionReconnectHandler` and manual keep-alive reconnect (#2). - ConnectionStateMachine code-style fixes: use named delegate types, collapse multi-line callback properties to single lines, swap `timeProvider`/`maxTotalReconnectTime` constructor param order, fix multi-line declaration (#6, #7, #8). - HTTPS resilience: confirmed `Microsoft.Extensions.Http` and `Microsoft.Extensions.Http.Resilience` 10.6.0 support `net472`/`net48`/`netstandard2.1`; removed `#if NET8_0_OR_GREATER` gating from `ManagedSessionBuilder` and `OpcUaClientBuilderExtensions` and HTTPS csproj package refs (#5). Verification: - `Opc.Ua.Client` + `Opc.Ua.Core` + `Opc.Ua.Gds.Client.Common` build clean (0 warnings, 0 errors). - 56/56 core channel-manager / retry-budget / HTTPS factory tests pass. - 129/129 client (ManagedSession + legacy SRH back-compat + SessionExtensions) tests pass. |
||
|
|
36d67b3d3a |
[Client] Central channel manager with shared, ref-counted transport channels (#3288)
Introduces `IClientChannelManager` as the central registry for client-side transport channels. Sessions and discovery clients now share ref-counted managed channels per (endpoint, reverse-connect identity); reconnect is coalesced and notified to attached participants via a new `IReconnectParticipant` callback interface; service calls are gated by a three-state lifecycle (`TransportReconnecting` -> `TransportConnectedSessionReactivating` -> `Ready`) until reactivation completes. New types in `Stack/Opc.Ua.Core/Stack/Client/`: - `ManagedChannelKey`, `ChannelState`, `ChannelStateChange`, `ParticipantReconnectResult` - `IReconnectParticipant`, `IManagedTransportChannel`, `IClientChannelManager` - `IChannelReconnectPolicy` + `ExponentialBackoffChannelReconnectPolicy` - `IRetryBudget` + `RetryBudget` (shared retry deadline across the two layers) - `ClientChannelManager.Managed/Entry/Lease/Metrics/Diagnostics/CertRotation.cs` partials with refcount, coalesced reconnect, IMeter instruments, ActivitySource + EventSource, and automatic ReconnectAllAsync on certificate rotation. Session integration (Libraries/Opc.Ua.Client): - `Session.CreateAsync(IClientChannelManager, ...)` overload constructs a Session that shares its channel with co-located participants. - `Session` implements `IReconnectParticipant`; reactivation handler distinguishes Reactivated / RequiresSessionRecreate / TransientFailure / FatalForParticipant / FatalForChannel. - `Session.ReconnectAsync(ct)` automatically delegates to the channel manager when wired. - `Session.RecreateInPlaceAsync` swaps managed-channel leases for failover-to-different-endpoint; same-key recreates delegate to manager reconnect; explicit channel/connection callers retain legacy behavior. - KA failure routes through `IClientChannelManager.ReconnectAsync(channel)`. ManagedSession integration: - `WithChannelManager(...)` builder + ctor param. - Two-level retry: channel-mgr handles transparent reconnect; outer `ConnectionStateMachine` + `IReconnectPolicy` only triggers on terminal channel `Faulted`. Outer-state churn suppressed during channel-mgr cycles via `_channelReconnectInProgress` flag wired through `IManagedTransportChannel.StateChanged`. - New `ManagedSession.ChannelStateChanged` event surfaces transparent reconnects to UI / health dashboards. - `ConnectionStateChangedEventArgs.UnderlyingChannelState` populated when outer reconnect was triggered by a channel fault. - Shared `IRetryBudget` propagated to channel-mgr so the two layers enforce a single max-total-time instead of compounding multiplicatively. Discovery/Registration/GDS: - Channel-manager-aware `CreateAsync` overloads on `DiscoveryClient`, `RegistrationClient`, `ServerPushConfigurationClient`, `LocalDiscoveryServerClient`, `GlobalDiscoveryServerClient`. Each registers a minimal `IReconnectParticipant`; co-located sessions and discovery probes targeting the same endpoint share a channel. HTTPS transport (`Stack/Opc.Ua.Bindings.Https/`): - New `IOpcUaHttpClientFactory` abstraction. - HTTPS / OPC-HTTPS channels migrated to `IHttpClientFactory` + `Microsoft.Extensions.Http.Resilience` (10.6.0, AOT-compatible on net10 via source generators; legacy direct-HTTP path retained for older TFMs / no-DI consumers). DI: - `AddOpcUa().AddClient(...)` registers `IClientChannelManager` and the named HTTPS HttpClient + standard resilience handler automatically. - `ManagedSessionBuilder` resolves the manager from DI when present. Obsoletions (functional, `[Obsolete]` with guidance): - `IClientBase.AttachChannel`/`DetachChannel` - `SessionReconnectHandler` - `SessionExtensions.ReconnectAsync(connection, ct)` and `(channel, ct)` legacy overloads Tests (~70 new unit + 4 live-server integration fixtures): - Core: `ClientChannelManagerManagedTests`, `ClientChannelManagerCertRotationTests`, `RetryBudgetTests`, `OpcUaHttpClientFactoryTests` (56/56 pass). - Client: `ManagedSession*Tests` updated (129/129 pass) + back-compat suites pass with `#pragma warning disable CS0618`. - Sessions integration: `ChannelManagerSharing/TransparentReconnect/SessionLifecycle/CertRotation` (6 pass, 2 skipped with documented blockers for faulted-entry reset and RequiresSessionRecreate end-to-end plumbing). Builds clean (0 warnings / 0 errors) across all six TFMs (net472, net48, netstandard2.1, net8.0, net9.0, net10.0). Docs: - `Docs/Sessions.md` new section 4 documents the channel manager, three-state gating, two-level retry with shared `IRetryBudget`, and HTTPS resilience layering. - `Docs/MigrationGuide.md` migration recipes for the four obsoleted API groups. - `Docs/DependencyInjection.md` channel-manager and HTTPS factory registration. |
||
|
|
0000513cde |
Pluggable user-identity provider model (#3807)
# Description Introduces a pluggable user-identity provider model covering all OPC UA Part 6 §6.5 mechanisms (Anonymous, UserName, X509, IssuedToken/JWT) on both client and server, with full `Microsoft.Extensions.DependencyInjection` integration, plus the Part 12 §9 `AuthorizationService` two-phase token-request flow **including refresh-token rotation**, the Part 12 §8 KeyCredential Push model, and a clean provider model for OPC 10000-12 §7.2 `ApplicationSelfAdmin`. The work is split across phase-by-phase commits so it can be reviewed independently; nothing here changes wire-level behaviour or forces existing applications to migrate. ### Documentation All under `Docs/`: - `IdentityProviders.md` — full developer guide: three-layer model, recipes, configuration reference, "what gets auto-wired" matrix, JWS algorithm table, identity-augmenters section, **"Implementing your own provider"** how-to subsections for Entra / OIDC / Windows / AspNetCore (sibling files merged in per review feedback). - `DependencyInjection.md` — `Identity` sections with `appsettings.json` examples, identity-extension matrix, `WithAuthorizationService` + `WithKeyCredentialPush` + `AddIdentityAugmenter` extensions documented. - `RoleBasedUserManagement.md` — claim-criteria callout describing `IIdentityClaims` probe surface. - `AuthorizationService.md` — Architecture diagram + `IAccessTokenProvider` conceptual narrative restored from master; modern `StartRequestToken` / `FinishRequestToken` flow leads; refresh-token recipe added. - `KeyCredentialService.md` — Push model + Pull vs Push vs Bridge decision matrix + Hybrid (Push + Pull) section; explicit experimental warning for the bridge. - `GDS.md` — KeyCredential Push, `StartRequestToken`, `GdsApplicationSelfAdminProvider`, **shipped Refresh-token support with rotation policy and audit semantics** all documented. - `MigrationGuide.md` — User Identity Providers section listing `[Obsolete]` markers + mechanical recipes; SelfAdmin migration via `IIdentityAugmenter`; `EnableRefreshTokens` default note. - `README.md` index entries. ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [x] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. |
||
|
|
889572472f |
Convert DataValue from class to readonly struct for further GC relief (#3796)
Convert `DataValue` from a mutable reference type (`class`) to a `readonly struct` to eliminate per-allocation GC pressure on every attribute read, subscription publish, and history sample. ### Key API changes (user-visible) | Before | After | Pattern | |---|---|---| | `class DataValue` | `readonly struct DataValue : INullable` | Foundation | | `Equals(DataValue?)` / `operator ==(DataValue?, DataValue?)` | `Equals(DataValue)` / `operator ==(DataValue, DataValue)` | Value semantics | | `IEncoder.WriteDataValue(string?, DataValue?)` | `IEncoder.WriteDataValue(string?, DataValue)` | Non-nullable wire API | | `IDecoder.ReadDataValue(...) -> DataValue?` | `IDecoder.ReadDataValue(...) -> DataValue` | Same | | `IDataChangeMonitoredItemQueue.PeekLast/Oldest()` | `TryPeekLastValue/Oldest(out DataValue)` | TryGet pattern | | `IAggregateCalculator.GetProcessedValue(bool)` | `TryGetProcessedValue(bool, out DataValue)` | Same | | `IUaPubSubDataStore.ReadPublishedDataItem(...)` | `TryReadPublishedDataItem(NodeId, uint, out DataValue)` | Same | | `Dictionary<uint, DataValue?>` (NodeCacheContext) | `Dictionary<uint, DataValue>` | Default + IsNull | | `IHistoryDataSource.FirstRaw/NextRaw` | `TryFirstRaw/TryNextRaw(out DataValue)` | TryGet pattern | | `NodeState.ReadAttribute(... DataValue)` | `NodeState.ReadAttribute(... ref DataValue)` | Sync mutation via ref | | `NodeState.ReadAttributeAsync(... DataValue)` | `NodeState.ReadAttributeAsync(...) -> ValueTask<(ServiceResult, DataValue)>` | Async return tuple | | `obj as DataValue` (heterogeneous Dictionary<string, object>) | `obj is DataValue dv ? dv : default(DataValue)` | Safe struct cast | | `Assert.That(dv, Is.Not.Null)` (tests) | `Assert.That(dv.IsNull, Is.False)` | Struct test pattern | | `[Obsolete] Value` setter, all property setters | Removed; use ctor or `With*()` | Immutability | | `[DataContract]` + `[DataMember]` | Removed; surrogate via `SerializableDataValue` + `DataValueSurrogateProvider` | Serialization | ### Wire format Wire format is **byte-identical** to the previous class-based implementation across binary / JSON / XML encoders. Default DataValue (`.IsNull == true`) is treated as the equivalent of the old null reference (writes JSON `null`, binary `byte 0`, XML omitted element). ### Why TryGet pattern (instead of leaving `DataValue?`) For methods that previously returned `DataValue?` to signal "no value available" (queue peek, aggregate result, data store lookup, history navigation), the conversion to `bool TryGet(out DataValue)` matches BCL conventions (`Dictionary.TryGetValue`, `Queue.TryPeek`, `Int32.TryParse`) and gives the caller an explicit "may be absent" affordance without `Nullable<DataValue>` overhead. ### Breaking changes summary - `DataValue` is now a `readonly struct` - boxing/unboxing semantics change for callers using `object`-typed APIs. - `IEncoder.WriteDataValue` / `IDecoder.ReadDataValue` / `ReadDataValueArray` interface signatures lose the `Nullable` wrapper. - `IDataChangeMonitoredItemQueue.PeekLastValue` / `PeekOldestValue` -> `TryPeekLastValue` / `TryPeekOldestValue` (out parameter). - `IAggregateCalculator.GetProcessedValue` -> `TryGetProcessedValue` (out parameter). - `IUaPubSubDataStore.ReadPublishedDataItem` -> `TryReadPublishedDataItem` (out parameter). - `NodeState.ReadAttribute` takes `ref DataValue`; `ReadAttributeAsync` returns `ValueTask<(ServiceResult, DataValue)>`. - `DataValue` property setters removed (use constructor + `With*` mutators). |
||
|
|
3e5f129c6f | Enable nullable in Stack, Libraries (9), and Applications projects (#3732) | ||
|
|
70b4498db8 |
Certificate manager and ref counted certificate type (#3688)
* New certificate and secrete abstraction with certificate infrastructure and certificate/secret stores. * leak testing for certificate resources to validate new ref counting in every test assembly * Migration guide updated and documentation added for all * Remove IDisposable from Nodestate and UserIdentity. * UserCertificateTokenHandler now just points into certificate registry/stores, no dispose required * Fix CA2000 warnings and cleanup of other diagnostics, increase warning levels in .editorconfig |
||
|
|
bd4543e961 |
ManagedSession, Pluggable Subscription engine, and fluent builder + DI (#3730)
- Add new managed session, wrapping session and providing connection management and failover handling - Add new Subscription engine that can act as alternative to current engine, but is based on channels with worker tasks - Add a fluent builder for managed session clients - Replace NodeCache with LruNodeCache, rename ILruNodeCache and LruNodeCache to INodeCache and NodeCache - Update migration guide and document new functionality. |
||
|
|
54cc3d88b1 | Prepare for C# 15 union types: UnionAttribute polyfill, TryGetValue rename, HasValue (#3727) | ||
|
|
94653f1aee |
Add a MCP server that enables LLM/Copilot to be a OPC UA client (#3701)
* Opc ua client Mcp Server * fix for #3699 |