mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-28 22:55:49 +08:00
fuzzing
281
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 | ||
|
|
a3ae6db71a |
[test] Accommodate non-blocking lease Dispose with thread-safe listeners and polling helpers
After commit |
||
|
|
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. |
||
|
|
392ad0d3f6 |
Merge origin/master into channelrefinements
Conflict resolved in Libraries/Opc.Ua.Client/Session/ConnectionStateMachine.cs:
adjacent doc-comment edits. Kept channelrefinements' new
<maxTotalReconnectTime> param doc and adopted master's shorter
<see cref="TimeProvider"/> form (System namespace already imported).
Verified post-merge:
- Libraries/Opc.Ua.Client: net10.0 build clean (0 W, 0 E)
- Applications/Quickstarts.Servers: net10.0 build clean (inline NodeId
fix for SecurityGroups/KeyPushTargets in
ReferenceServerConfigurationNodeManager survived auto-merge)
- Tests/Opc.Ua.Client.Tests: net10.0 build clean (only pre-existing
master CA2000 warnings in StaticIssuerKeyResolverTests)
|
||
|
|
53ff029df8 |
Fix MetricsAreEmittedForReconnectAndGateWaitAsync race on fast runners
CI failure (AzDO build 14553 log 874, net48 Windows): the test asserted that opcua.channel.reconnect.duration was emitted but only saw the attempts / gate.wait / open / active measurements. Root cause: race in the TEST (production behavior is correct). ChannelEntry.RunReconnectCycleAsync records RecordReconnectAttempt BEFORE calling tcs.TrySetResult(true), but RecordReconnectDuration is invoked in the finally block AFTER TrySetResult returns control. The test awaits the reconnect task — which completes on TrySetResult — and then immediately asserts both metrics. On a fast net48 runner the test thread reaches the duration assertion before the finally block runs, so the histogram measurement is missing. Add a WaitForMeasurementAsync helper that polls for the duration measurement with a 2-second budget before the existing hard assertion fires. The hard assertion is preserved so the test still fails if the metric is genuinely never emitted. Verified locally: net48 5/5 runs pass (was 0/1 in CI), net10 1/1 (no regression). |
||
|
|
a81d981755 |
dotnet format sweep (whitespace, IDE, RCS) + code-style enforcer agent fix (#3865)
# Description
Pure code-style sweep across the repo — no behavioral changes. Drives
`dotnet format` info-level diagnostics down on a large slice of the
codebase (Stack, Libraries, Tests, Tools).
Six commits, applied bottom-up in safety order so each batch leaves the
build green:
1. **`style: fix whitespace and final-newline formatting (WHITESPACE,
FINALNEWLINE)`**
197 hits across ~30 files; pure mechanical cleanup.
2. **`style: apply dotnet format style fixes (IDE rules)`**
IDE-rule batch via `dotnet format style --severity info` —
`var`/explicit type, simplify names, collection expressions, expression
bodies, unused usings, etc.
3. **`style: apply dotnet format analyzers fixes (RCS blank-line
formatting)`**
Roslynator formatting (RCS0xxx) blank-line and punctuation rules.
4. **`style: convert comments to doc comments and inline local vars
(RCS1181, RCS1124)`**
24 files. RCS1181 promotes `// summary`-style comments above
public/internal members to `/// <summary>` doc comments; RCS1124 inlines
locals used exactly once. Generated `Tools/` files were reverted to keep
the source-generator outputs deterministic.
5. **`docs(agent): correct --diagnostics flag form and verify Roslynator
4.x code fixers`**
Updates `.github/agents/opc-ua-codestyle-enforcer.agent.md` with two
corrections discovered while running this sweep:
- `dotnet format --diagnostics "RULE1 RULE2"` (single quoted,
space-separated) silently fixes nothing on `dotnet format` 10.x — it
parses as one unknown ID and exits 0. The correct form is `--diagnostics
RULE1 --diagnostics RULE2 …`, repeated per rule.
- Roslynator 4.15.0 **does** ship code fixers for
RCS1078/1118/1124/1140/1142/1181/1221/1260 (the previous "no auto-fixer"
list was outdated). RCS1140/1142 default to `Hidden` so need `--severity
info` to surface and fire; RCS1078 requires
`roslynator_empty_string_style = field` (already set in
`.editorconfig`). Only NUnit4002/2046/2010 genuinely lack auto-fixers in
this stack.
6. **`style: format new code from master merge (dotnet format sweep)`**
After merging master into the branch (commit `e3ebc8742`), re-ran the
same three-phase format sweep on the 151 .cs files brought in by the
merge, scoped per project via repeated `--include`. 81 files changed;
same rule families as commits 1–4.
- Hand-fixed `Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs`
for `RCS1174` (redundant `async`/`await` removal) because the auto-fixer
didn't apply for that specific case.
- Reverted files that the formatter would break:
- `Libraries/Opc.Ua.Server/Server/ServerInternalData.cs` — multi-TFM
analysis disagreement injected `<<<<<<<` / `=======` / `>>>>>>>`
markers.
-
`Tools/Opc.Ua.MigrationAnalyzer.Core/{Core/Stack/Client/ChannelBase.cs,
Core/Stack/Types/IUserIdentityTokenHandler.cs,
Client/Session/Session.cs}` — IDE0005 removed `using
Opc.Ua.Security.Certificates` that IS used (the files are 1.5.378
reference fixtures for the analyzer).
- `Tests/Opc.Ua.Gds.Tests/PushTest.cs` — IDE0002 over-simplified
`OpcUa.ObjectTypeIds` to `ObjectTypeIds`, which then resolves to a
conflicting type.
### What this PR does **not** touch
- No behavioral or semantic logic changes.
- No `.editorconfig` rule promotions (rules stay at their current
severities; this is a backlog-burndown pass, not a rule tightening).
- Generated source under `Tools/` is intentionally excluded.
- Higher-risk rule families (`RCS1090`/`CA2007` `ConfigureAwait`,
`RCS1249` null-forgiving cleanup, `IDE0270`/`IDE0059`) are left for
follow-up sweeps because they need per-TFM build verification or
semantic review.
## Related Issues
_No tracking issue — this is a non-functional style sweep and the
agent-doc correction is a follow-up to the same work. Happy to open one
if the maintainers prefer._
## 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 — style-only, no
behavioral change)_
- [x] I have added all necessary documentation. _(agent doc updated with
the two corrections)_
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings. _(verified clean on `net10.0` and `net48`; only
pre-existing `NETSDK1005`/`NETSDK1207` failures on Tools/AOT projects)_
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. _(build
verified on net10 and net48 per batch; full test suite not yet run
locally — relying on CI)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
---------
Co-authored-by: agent <agent@local>
|
||
|
|
e7d9a153b9 |
[Test] Unit tests for ManagedSession revalidation loop (follow-up to #3849) (#3864)
# Description Follow-up unit tests for the debounced server-certificate revalidation loop added in PR #3849 (now merged as `15eb55c46`). Codecov flagged `ManagedSession.CertificateChanges.cs` as the lowest-coverage file in that PR (34% patch coverage) — the loop body that drives the debounce / at-most-one-in-flight / re-arm semantics is the heart of the trust/CRL revalidation behaviour and was previously untested. ## Changes * **`Libraries/Opc.Ua.Client/Session/ManagedSession.CertificateChanges.cs`** — extract `RunRevalidationLoopAsync` into a pure `internal static` overload that takes a `ChannelReader<int>`, `TimeProvider`, debounce window, validate callback, logger, and cancellation token. The instance overload now forwards to it. This gives unit tests a seam without standing up a connected `ManagedSession` (whose private constructor + `CreateAsync` triggers a real connect). * **`Tests/Opc.Ua.Client.Tests/Session/ManagedSessionRevalidationLoopTests.cs` (new)** — 5 unit tests covering the loop body in isolation: - `BurstOfSignalsCollapsesToSingleValidationAsync` — 50 signals → exactly 1 validation (bounded-1 channel + DropWrite). - `SignalDuringInflightValidationReArmsExactlyOnceAsync` — re-arm semantics during in-flight validation. - `NoSignalMeansNoValidationAsync` — idle loop never invokes the validator. - `CancellationDuringDebounceAbortsPendingValidationAsync` — cancellation cleanly aborts a pending validation. - `ValidationExceptionsAreSwallowedAndLoopContinuesAsync` — transient validation failures don't break the loop. Uses real `TimeProvider.System` with a 50 ms debounce + 250 ms settle window so each test runs in well under a second. A `FakeTimeProvider` seam was considered but rejected — the loop's `ReadAsync → Delay → drain` sequence is racy to drive with a fake clock because the timer is not created until after the first signal has been consumed. ## Related Issues - Follow-up to #3562 / #3160 (both fixed in #3849, merged). ## 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. - [x] I have added all necessary documentation. _(None needed — pure test addition + non-public testing seam.)_ - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran focused tests locally using the **UA.slnx** projects against .net **10**, and all 5 new tests passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
788567ad75 | Merge branch 'master' of https://github.com/OPCFoundation/UA-.NETStandard into channelrefinements | ||
|
|
0fbc900919 |
Add opt-in SubscriptionRecoveryPolicy for unsolicited Good_SubscriptionTransferred (#3540) (#3847)
# Description Adds an opt-in `SubscriptionRecoveryPolicy` for both the Classic V1 and V2 subscription engines that lets the client recover from server-emitted `Good_SubscriptionTransferred` notifications which arrive against a freshly created subscription (observed against Kepware after a server re-initialise, very likely a leak from the pre-restart session's pending status notifications onto the new session — abetted by Kepware re-using `SubscriptionId = 1` across generations). Per [OPC UA Part 4 §5.14.7](https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.7), `Good_SubscriptionTransferred` is supposed to be delivered to the *old* Session whose subscription was transferred away via `TransferSubscriptions`. The existing V1 behaviour of stopping the per-subscription publish loop on receipt is therefore **spec-compliant**; the issue is that some servers deliver the notification on a Session that is still actively using the subscription, leaving the per-subscription dispatch silently disabled. The fix is **opt-in** and **non-breaking**: - New `SubscriptionRecoveryPolicy` enum (`ReportOnly` default, `RecreateOnUnsolicitedTransfer`). - V1 Classic `Subscription.RecoveryPolicy` property; V2 `Subscriptions.SubscriptionOptions.RecoveryPolicy` init property. - When the policy is `RecreateOnUnsolicitedTransfer`, the SDK drops queued acks for the dead subscription id (avoiding `BadSubscriptionIdInvalid` ack errors on servers that re-use ids) and recreates the subscription in place on the same Session. ## What changed **Public API additions (non-breaking, defaults preserve current behaviour):** - `SubscriptionRecoveryPolicy` enum (`ReportOnly`, `RecreateOnUnsolicitedTransfer`). - V1 `Subscription.RecoveryPolicy` property. - V2 `Subscriptions.SubscriptionOptions.RecoveryPolicy` init property. - Internal `IMessageAckQueue.DropPendingForSubscription(uint)` and protected `MessageProcessor.AckQueue` accessor. **V1 Classic — `Libraries/Opc.Ua.Client/Subscription/Classic/Subscription.cs`:** - Replaces the inline `Good_SubscriptionTransferred` branch with `HandleGoodSubscriptionTransferred`. `ReportOnly` preserves the original behaviour (stop publish timer/worker); `RecreateOnUnsolicitedTransfer` triggers `TriggerUnsolicitedTransferRecovery`, guarded by `m_recreateAfterTransferInProgress` for idempotency. - Recovery drains pending acks via the new `ClassicSubscriptionEngine.RemoveAcknowledgementsForSubscription` **before** resetting the subscription, then runs `DeleteSubscription()` + `CreateAsync()` on the same session so the publish worker restarts via `StartKeepAliveTimer`. - Discriminator (`Session != null && !Session.Reconnecting && Created`) defers to the reconnect pipeline if it is already running. **V2 — `Libraries/Opc.Ua.Client/Subscription/Subscription.cs`:** - Completes the `OnStatusChangeNotificationAsync` TODO: now always fires `OnPublishStateChanged(mask)` so `PublishState.Transferred` actually reaches handlers (previously the no-op override silently swallowed the flag). - Under `RecreateOnUnsolicitedTransfer`, calls `AckQueue.DropPendingForSubscription(deadId)` then existing `ResetToRecreateAsync` on a worker task — no service calls from the dispatch path. - V2 `SubscriptionManager` implements `DropPendingForSubscription` via drain-and-refill on the prioritised ack channel. **Tests — `Tests/Opc.Ua.Client.Tests/Subscription`:** - V1 in `Classic/SubscriptionUnitTests.cs`: `ReportOnlyPolicyDoesNotRecreateOnUnsolicitedTransferAsync`, `RecreatePolicyRecreatesSubscriptionOnUnsolicitedTransferAsync`, `RecreatePolicyCollapsesConcurrentNotificationsAsync`, `RecreatePolicyDoesNotRecreateWhileSessionReconnectingAsync`. - V2 in `SubscriptionTests.cs`: `GoodSubscriptionTransferredRaisesPublishStateTransferredAsync`, `GoodSubscriptionTransferredRecreatesSubscriptionWhenPolicyEnabledAsync`, `GoodSubscriptionTransferredCollapsesConcurrentRecreatesAsync`. - `FakeMessageAckQueue` gained a `DroppedSubscriptions` recorder so the V2 test can assert ack pruning ran. **Docs — `Docs/TransferSubscription.md`:** - New "Recovering from unsolicited `Good_SubscriptionTransferred`" section with spec context, usage examples for both engines, and the caveat that recovery is recreate-based — **not** lossless failover; the server-side retransmission queue and triggering relationships tied to the invalidated subscription id are not preserved. ## Validation | Scope | Result | | --- | --- | | `Libraries/Opc.Ua.Client` build (`net10.0`, `net48`) | 0 warnings, 0 errors | | `Tests/Opc.Ua.Client.Tests` build (`net10.0`, `net472`) | 0 warnings, 0 errors | | New tests on `net10.0` and `net472` | 7/7 pass on each | | Full subscription unit-test suite (`SubscriptionUnitTests` + `SubscriptionTests` + `MessageProcessorTests` + `SubscriptionManagerTests`) | 110/110 pass on each TFM | | Entire `Opc.Ua.Client.Tests` suite (`net10.0`) | 1268/1268 pass | | Classic transfer integration tests (`Tests/Opc.Ua.Subscriptions.Classic.Tests`, `net10.0`) | 20/20 pass | | V2 transfer compliance tests (`Tests/Opc.Ua.Subscriptions.Tests`, `net10.0`) | 9/9 pass | > Marked as **draft** because the maintainer wanted to first confirm whether the stack is spec-compliant before deciding which knobs to ship; this PR documents the investigation (stack is compliant), and surfaces an opt-in recovery policy that addresses the field symptom without changing the default behaviour. Convert to ready-for-review once the design intent is acknowledged. ## Related Issues - Fixes #3540 ## 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. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: marcschier <marc.schier@github.com> |
||
|
|
ff33cdd18a |
Phase E: auto-reset faulted ChannelEntry on ReconnectAsync(lease, ...)
Per review feedback (ChannelManagerTransparentReconnectIntegrationTests.cs:121 "Implement!"). Adds transparent recovery: when ReconnectAsync is called on a lease whose Entry has transitioned to Faulted or Closed, the manager swaps the lease's Entry reference to a freshly-created one for the same key, preserves the participant, and proceeds with the reconnect cycle. Back-off scales via the existing reconnect-policy GetDelay(SwapCount). - Production: ClientChannelManager.SwapFaultedEntryAsync(lease, ct); ManagedTransportChannelLease.SwapEntry(...) + SwapCount tracking; ChannelEntry.ReattachParticipant(lease, factory) for refcount preservation. - Unit test: ClientChannelManagerManagedTests covers the swap flow with a fake transport. - Integration: ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns un-Ignored and implemented — verifies the full transparent exhaustion + recovery cycle against a real ManagedSession. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
2efd335c63 |
Phase C: move ClientChannelManagerManagedTests out of Core.Tests
Per review feedback (Opc.Ua.Core.Tests.csproj:52) — Core.Tests should not have a ProjectReference to Opc.Ua.Client. Audit confirmed only ClientChannelManagerManagedTests.cs uses Opc.Ua.Client types; moved it to Opc.Ua.Client.Tests/Stack/Client/ with namespace update, then dropped the Opc.Ua.Client ProjectReference from Opc.Ua.Core.Tests.csproj. Verified: Core.Tests + Client.Tests build clean; moved tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
cf6bd37360 |
Address PR feedback: small-scope cleanup
Applies the safe / minor review feedback on PR #3852. Larger refactors
are scoped into a separate plan posted on the PR for explicit approval
before starting (merge ClientChannelManager partials, merge Channels.Stress
into Opc.Ua.Stress.Tests, move ClientChannelManagerManagedTests out of
Core.Tests, implement the ignored exhaustion-recovery integration test,
fix remaining CA2007 / CA1861 / CA5394 / CA2025 warnings without NoWarn).
Changes in this commit:
- .github/workflows/buildandtest.yml: drop "legacy" wording from the
EXCLUDE comment (Stress suite is opt-in, not legacy).
- Applications/ConsoleReferenceClient/UAClient.cs +
Applications/ConsoleReferenceClient/ClientSamples.cs: replace
Action<IList, IList>? validateResponse with Action<Array, Array>? to
(1) remove the System.Collections import that
|
||
|
|
48b24bec40 |
Fix CodeQL whole-solution build failures across legacy TFMs
The buildandtest workflow only builds each test project for net10.0 via
CustomTestTarget, so three TFM-compatibility issues were masked. CodeQL
builds the whole solution for all TFMs and surfaced them:
1. Applications/ConsoleReferenceClient/UAClient.cs: regression from
|
||
|
|
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. |
||
|
|
dbc09b8e22 |
Adopt System.TimeProvider across the stack (fixes #2458) (#3829)
# Description Adopt [`System.TimeProvider`](https://learn.microsoft.com/dotnet/api/system.timeprovider) across the stack so that every place that creates timers, runs duration math, or schedules periodic work is mockable, deterministic in tests, and immune to wall-clock changes. The migration touches Core, Core.Types, Server, Client, PubSub, Configuration, Gds (server + client), Lds, and the Quickstarts/Console reference apps. Per-component changes follow the same pattern: 1. Add a `private readonly TimeProvider m_timeProvider;` field. 2. Accept a nullable `TimeProvider? timeProvider = null` as the **last** constructor parameter (new overload; legacy constructor stays for binary compatibility and delegates with `timeProvider: null`). 3. Initialise the field with `m_timeProvider = timeProvider ??= TimeProvider.System;`. 4. Replace `new Timer(...)`, `Stopwatch`, `HiResClock.*`, `DateTime.UtcNow`-based duration math, and `Task.Delay(delay, ct)` in timing loops with the matching `m_timeProvider` API (`CreateTimer`, `GetTimestamp` / `GetElapsedTime`, `GetUtcNow().UtcDateTime`, `Task.Delay(delay, m_timeProvider, ct)`). 5. Wire the provider through DI: `AddOpcUaServerBuilder` / `AddOpcUaClientBuilder` register `TimeProvider.System` via `TryAddSingleton<TimeProvider>()` so tests can swap in `Microsoft.Extensions.Time.Testing.FakeTimeProvider`. `HiResClock` itself is kept (so the `ApplicationConfiguration.DisableHiResClock` schema flag still round-trips), but **every public member** is now `[Obsolete]` with a single shared message pointing to the new migration-guide section. The few internal call sites that still need to set/read `HiResClock` are wrapped with `#pragma warning disable CS0618`. ### Highlights per component - **Core / Stack** — `Opc.Ua.Core`, `Opc.Ua.Core.Types`, channel pipeline (`UaSCBinaryClientChannel`, `ChannelToken`, `UaSCBinaryChannel`), `ApplicationConfiguration`, certificate validators, `JwksIssuerKeyResolver`, `TimeProviderExtensions` polyfills (`GetTimestampMilliseconds`, `GetTickCount`, `CancellationTokenSource(timeout, timeProvider)`, `Task.Delay(delay, provider, ct)` polyfill for older TFMs). - **Server** — session lockout, subscription pipeline (publish/sample/keep-alive/lifetime), `MonitoredItemQueue`, alarm sampling rate, `IntervalRunner`, server diagnostics, configuration state machines, hosted services. - **Client** — `Session`, managed/V2 subscription engines, reconnect handlers, channel keep-alive, periodic publishing, complex-type discovery polling. - **PubSub** — `IntervalRunner` pipeline, message-receive timers, MQTT/UDP keep-alive, publishers/readers. - **Gds / Lds** — `LdsServer`, `LinqApplicationsDatabase` expiry housekeeping, GDS clients (push, pull), `DirectoryCertificateStore`, `ConnectionStateMachine`, classic subscription engine. - **Quickstarts / Console samples** — `SampleNodeManager`, `ReferenceNodeManager`, `AlarmNodeManager`, `BoilerState`, `MemoryBufferState`, `PublishedValuesWrites`. Samples resolve via `(Server as ITimeProviderProvider)?.TimeProvider ?? TimeProvider.System` so existing ctor signatures stay binary-compatible. - **Tests** — `Opc.Ua.PubSub.Tests` adds `IntervalRunnerTests`; existing `SubscriptionTests`, `UaPublisherTests`, and `TestDataSystem` move off `HiResClock`. Added `Microsoft.Extensions.TimeProvider.Testing` package for `FakeTimeProvider` (project-load warning surfaces on net48/net472 only — runtime is .NET 8+). ### Migration guide New section [**Time and Timer abstraction (`TimeProvider`)**](Docs/MigrationGuide.md#time-and-timer-abstraction-timeprovider) documents: - The canonical replacement table (`HiResClock.UtcNow` → `timeProvider.GetUtcNow().UtcDateTime`, `HiResClock.TickCount64` → `timeProvider.GetTimestamp()`, `new Timer` → `timeProvider.CreateTimer`, `Task.Delay(delay, ct)` → `Task.Delay(delay, timeProvider, ct)`, …). - The `TimeProvider? timeProvider = null` + `??=` constructor pattern. - DI wiring and how to swap in `FakeTimeProvider` in tests. - Before/after snippets for `HiResClock.TickCount64`, `HiResClock.UtcNow`, and `new Timer(...)`. ## Related Issues - Fixes #2458 ## 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. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
49305d9085 |
V2 subscription engine close gap for test and feature parity with classic engine (#3824)
# Description V2 subscription engine feature parity round to make the classic engine eligible for deprecation. Splits the test surface so the classic and V2 engines can evolve independently, ports the gaps the classic-engine integration tests had been exercising, and lands the public V2 API additions needed to close them. - Splits `Tests/Opc.Ua.Subscriptions.Tests` into a classic (`Opc.Ua.Subscriptions.Classic.Tests`) and a V2 (`Opc.Ua.Subscriptions.Tests`) project to shrink CI time and let the two engines diverge cleanly. - Adds the V2 public-surface gaps required by the test ports (`Subscription.ServerId` on the class, `SetSubscriptionDurableAsync`, `SendInitialValuesOnTransfer`, unified `OnSubscriptionStateChangedAsync` handler callback, per-item `ConditionRefreshAsync`, fluent `LoadSubscriptionsAsync(Stream, …)` and `Subscription.Snapshot()` / `RestoreAsync()` for save/load + transfer). - Lands handler-centric design (no `PublishingStopped` property handlers maintain derived state); always-sequential per-subscription delivery documented + tested. - **Adds short and long haul stability tests for the V2 `ManagedSession` + subscription engine. Closes #3744** (see "Issue #3744 Haul tests" below). - **Migrates SubscriptionStateSnapshot / MonitoredItemStateSnapshot to `[DataType]` source-generator codecs**. - `SubscriptionDurableV2Tests.cs` 5 V2 ports of the classic durable test patterns. - `V2FollowUpCoverageTests.cs` handler state-change callback, fluent stream-based `LoadSubscriptionsAsync`, `SendInitialValuesOnTransfer`, snapshot edge cases (empty / `DataChangeFilter` round-trip / concurrent mutation). - `SubscriptionFailoverV2Tests.cs` forced channel-break with and without `WithTransferSubscriptionsOnRecreate`. - `MonitoredItemConditionRefreshLiveV2Tests.cs` — live `ConditionRefresh` against the reference server's event source; asserts `RefreshStart` / `RefreshEnd` events flow through the handler. ## Fixes - #3744 Haul tests (V2 ManagedSession + subscription engine) `Tests/Opc.Ua.Sessions.Tests/ManagedSessionStabilityTest.cs` adds the short/long haul coverage requested by [#3744](https://github.com/OPCFoundation/UA-.NETStandard/issues/3744) plus the bonus fault-injection variant: | Test | Duration | Category | Purpose | |---|---|---|---| | `ShortHaulManagedSessionV2Async` | 2 minutes | `ManagedSessionHaul` (`[Explicit]`) | Quick local sanity check of the V2 monotonicity contract. | | `LongHaulManagedSessionV2Async` | 90 minutes (configurable via `TEST_DURATION_MINUTES`) | `ManagedSessionHaul` (`[Explicit]`) | Long-running V2 stability + monotonicity. | | `LongHaulManagedSessionWithFaultInjectionV2Async` | 90 minutes (configurable) + fault every `FAULT_INJECTION_INTERVAL_SECONDS` (default 60s) | `ManagedSessionHaul` (`[Explicit]`) | **Bonus** — force-closes the subscriber transport channel periodically; verifies V2 manager transfers/recreates the subscription and continues monotonic delivery across reconnects. | **Pattern.** A writer `ManagedSession` increments a `uint32` counter and writes it into `Scalar_Static_Mass_UInt32_UInt32_00` every 250ms. A subscriber `ManagedSession` runs a V2 subscription with a single monitored item on the same node. `MonotonicCounterHandler` asserts each received sample is *strictly greater than* the previous one. V2 `ISubscriptionNotificationHandler` per-subscription ordering guarantee. Skipped values (sampling rate < write rate) are tolerated; duplicates and reorderings are violations. |
||
|
|
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. |
||
|
|
01d7162313 |
Part 9 Alarms & Conditions: complete server + client implementation (#3805)
Complete implementation of **OPC UA Part 9 ΓÇö Alarms & Conditions** for
both server and client, **OPC UA Part 16 ΓÇö State Machines** (generic
extensible client + server API), and the supporting infrastructure
(source-generated event records, source-generated ObjectType proxies,
`IAsyncEnumerable` streaming subscriptions, address-space model change
tracking, per-node `INodeCache` invalidation, unified DI integration).
### Server-side (Part 9 complete)
- **`AlarmConditionState`** ΓÇö every Part 9 method wired automatically
in `OnAfterCreate`: `Silence`, `Suppress`/`Suppress2`,
`Unsuppress`/`Unsuppress2`, `RemoveFromService`/`RemoveFromService2`,
`PlaceInService`/`PlaceInService2`, `Reset`/`Reset2`,
`GetGroupMemberships`. Each method generates the spec-mandated audit
event and supports an optional veto-delegate hook.
- **Latched alarms** ΓÇö `ActiveState` reflects real process state;
`LatchedState` persists until explicit `Reset` (with full precondition
validation).
- **Re-alarming** ΓÇö
`ProcessReAlarm`/`ResetReAlarmRepeatCount`/`IsReAlarmEnabled` helpers
(opt-in, no hidden timer).
- **Audible alarms** ΓÇö `UpdateAudibleState` for
`AudibleEnabled`/`AudibleSound`/`SilenceState` lifecycle.
- **Suppression + groups** ΓÇö `AlarmGroup`, `AlarmSuppressionEngine`
(with first-in-group), `AlarmRateTracker` (for `AlarmMetricsType`).
- **Spec-compliance fixes** ΓÇö `SuppressedOrShelved` now includes
`OutOfServiceState`; `GetRetainState` includes `LatchedState`;
`EffectiveDisplayName` composition includes `OutOfServiceState` +
`LatchedState`.
### Client-side Part 9 API
- **`AlarmClient`** ΓÇö typed Part 9 operations. Each method delegates
to the matching source-generated `*TypeClient` proxy
(`ConditionTypeClient`, `AcknowledgeableConditionTypeClient`,
`AlarmConditionTypeClient`, `DialogConditionTypeClient`,
`ShelvedStateMachineTypeClient`) ΓÇö one source of truth for every
method NodeId.
- **Interface hierarchy** — `IConditionOperations` →
`IAcknowledgeableConditionOperations` → `IAlarmOperations` +
`IDialogConditionOperations`.
- **Source-generated event records** ΓÇö new `EventRecordGenerator`
(alongside `ObjectTypeProxyGenerator`) emits `{Type}TypeRecord` per OPC
UA event type: `ConditionTypeRecord` →
`AcknowledgeableConditionTypeRecord` → `AlarmConditionTypeRecord` →
`LimitAlarmTypeRecord`/`ExclusiveLimitAlarmTypeRecord`/… all the way
to vendor subtypes. **Vendor alarm types get records automatically.**
- **`AlarmEventDecoder`** ΓÇö decodes `EventNotification.Fields` into
the most-specific generated record.
- **`AlarmEventFilterBuilder`** ΓÇö fluent builder for Part 9
`EventFilter`s.
- **`ISession.GetAlarmClient(telemetry)`** convenience extension.
### Part 16 state machines (generic, extensible)
- **Client**: extension methods on the source-generated
`StateMachineTypeClient` / `FiniteStateMachineTypeClient` ΓÇö
`GetCurrentStateAsync`, `ObserveFiniteTransitionsAsync`
(`IAsyncEnumerable<FiniteStateSnapshot>`), `WaitForStateAsync`,
`GetAvailableStatesAsync` / `GetAvailableTransitionsAsync`. **Vendor
proxies inherit the API automatically** through the generated
inheritance chain.
- **Server**: `StateMachineBuilder` (fluent: `AddState` /
`AddTransition` / `OnCause` / validation) +
`FluentFiniteStateMachineState` ΓÇö declare a Part 16 state machine
without subclassing the abstract base.
- **Alarms alignment**: `AlarmClient.GetShelvingStateAsync` /
`ObserveShelvingTransitionsAsync` surface the API for every alarm's
`ShelvingState` child.
### IAsyncEnumerable streaming subscription
- **`IStreamingSubscription`** with
`SubscribeDataChangesAsync`/`SubscribeEventsAsync` returning
`IAsyncEnumerable<T>` (Channel-backed).
- **`ManagedSession.DefaultStreaming`** ΓÇö lazy shared subscription;
disposed with the session.
- **Helpers** ΓÇö `TakeUntilAsync`, `WithTimeoutAsync`, `TakeAsync`,
`BufferedAsync` for state-machine waits and bounded observation windows.
- **`AlarmStreamExtensions`** ΓÇö
`SubscribeAlarmsAsync`/`SubscribeConditionsAsync`/`SubscribeDialogsAsync`
returning the source-generated record types.
### Address-space model change tracking
- **Server**: `RaiseGeneralModelChangeEvent` on both `CustomNodeManager`
and `AsyncCustomNodeManager`; `ModelChangeAggregator` (in
`Opc.Ua.Server.NodeManager`, used by every node manager) batches changes
per service call; **auto-emit** from `CreateNode`/`DeleteNode` (opt-out
via `ModelChangeEmissionEnabled = false`).
- **Client**: `IModelChangeTracker` subscribes to
`BaseModelChangeEventType` via the streaming subscription, decodes
`ModelChangeStructureDataType` payloads, invalidates the `INodeCache`
per-node.
- **`INodeCache.InvalidateNode(NodeId)`** ΓÇö new interface member (true
per-node eviction in `NodeCache`).
- **`ManagedSession.EnableModelChangeTrackingAsync()`** +
`ManagedSessionOptions.ModelChangeTracking` +
`ManagedSessionBuilder.WithModelChangeTracking()` fluent method.
### Dependency Injection integration
- **`services.AddOpcUa().AddClient(...).AddAlarms()`** ΓÇö registers
`AlarmClientFactory` so DI-hosted apps can resolve a Part 9 client per
session without `new`-ing one manually.
- Cross-link added to the unified DI surface (`AddServer` / `AddClient`
/ `AddComplexTypes` / `AddGdsServer` / `AddLdsServer` /
`AddWotConServer`).
### Quickstart server modernization
- `Quickstarts.Servers/Alarms/AlarmNodeManager.cs` ported to
`AsyncCustomNodeManager` and demonstrates the new `AlarmGroup` +
`AlarmSuppressionEngine` end-to-end via a writable `MaintenanceMode`
boolean that suppresses every member alarm.
### Documentation
- New: `Docs/AlarmsAndConditions.md`, `Docs/StreamingSubscription.md`,
`Docs/ModelChangeTracking.md`, `Docs/StateMachines.md` ΓÇö full
developer guides.
- Updated: `Docs/DependencyInjection.md` (`AddAlarms()` row + section),
`Docs/README.md` index, `Docs/MigrationGuide.md` (Part 9 / Part 16 /
model change tracking sections covering behavior changes + breaking API
surface).
- Sample: `Applications/ConsoleReferenceClient/AlarmClientSample.cs`.
|
||
|
|
5ff968fcfb |
Test project reorganization follow up (#3810)
### Test project reorganization
Splits the integration- vs unit-test boundary along **project
boundaries**
and introduces a runtime-tier split for subscription tests so the
mainline
CI no longer drags in stress / durable suites.
**New projects:**
- `Opc.Ua.Stress.Tests` ΓÇö cross-area stress tier (designed to
absorb
session / channel / pub-sub stress in future). Seeded with the 6
subscription stress classes (`SubscriptionDepthTests`,
`SubscriptionBasicDepthTests`, `MonitoredItemDepthTests`,
`SubscriptionTransferDepthTests`, `SubscriptionPublishTooManyTests`,
`SubscriptionMultipleTests`). **Excluded from mainline CI; runs
nightly.**
- `Opc.Ua.Subscriptions.Durable.Tests` ΓÇö durable-subscription tier
(needs `DurableSubscriptionsEnabled` + storage-backed lifetime).
Receives `SubscriptionDurableTests` plus the migrated
`DurableSubscriptionTest` and `DurableSubscriptionTestDebug`.
**Excluded from mainline CI; runs nightly.**
- `Opc.Ua.Features.Tests` ΓÇö cross-feature integration tier.
Receives the
`[OneTimeSetUp]`-bootstraps-its-own-server integrations for AliasNames
(Part 17), Roles (Part 18), FileSystem (Part 5 §C / Part 20), and
NodeCache. Also absorbs the misplaced
`Tests/Opc.Ua.History.Tests/FileSystemTests.cs`.
**Client.Tests cleanup (~28 files moved):**
- `Opc.Ua.Sessions.Tests` (existing) gains 10 session integration files
plus the 4 session benchmark files inline (`LoadTest`,
`RequestHeaderTest`, `TraceableRequestHeaderTest`,
`SecurityPolicyBenchmarks`).
- `Opc.Ua.Subscriptions.Tests` (existing) gains 4 subscription
integration files plus `PooledNotificationBenchmarks` inline.
- `Opc.Ua.Client.ComplexTypes.Tests` (existing) gains all
`ComplexTypes/*` integration files. The Client.Tests copy of
`TypeSystemClientTest.cs` (a stale near-duplicate) is removed.
- Benchmarks **stay inline** as `[Test]` + BenchmarkDotNet dual-use; no
new benchmark project (per the approved plan).
**CI pipeline:**
- `.azurepipelines/test.yml` now filters
`TestCategory!=LongRunning&TestCategory!=Stress` on the mainline run.
- `-ExcludeFileName` now accepts a comma-separated list. The mainline
matrix excludes the 3 nightly-tier project files alongside the
existing `Opc.Ua.Aot.Tests.csproj`.
- `get-matrix.ps1` enhanced to support multi-pattern excludes.
`InternalsVisibleTo` extended on `Opc.Ua.Client` and `Opc.Ua.Server` to
grant the new test projects access. All affected projects build clean
(`0 Error`) for `net10.0`.
### Fix 8 skipped tests flagged as suspected bugs
Triage of every `[Ignore]` / `[Explicit]` test in `Tests/**` surfaced 8
entries labelled as suspected production bugs. After inspection:
- **1 real production bug** ΓÇö the
`CancellationTokenSource.CancelAsync`
polyfill for netstandard2.0 / netfx was running cancellation callbacks
inline on the caller's thread, deadlocking dispose paths
(`SubscriptionManager.DisposeAsync`, etc.) when a callback awaits on
the caller's `SynchronizationContext`. Fixed at the polyfill root by
hoisting `Cancel()` onto the ThreadPool via `Task.Run`, matching real
.NET 8+ semantics. Caveat (deferred `IsCancellationRequested`
transition) documented inline; all in-repo callers use
`await cts.CancelAsync()` so the deviation is invisible. Test
`DrainAsyncWaitsForInFlightPublishToCompleteAsync` un-ignored ΓÇö
verified passes on net48 (943 ms) and net10.0 (1 s).
- **2 flaky tests stabilized** ΓÇö both
`ValidateMetaDataUpdateTimeNonZeroIsSentPeriodically` (MQTT JSON &
UADP) now use `Stopwatch.GetTimestamp()` instead of `DateTime.Now`,
compute deviation against the **median** interval (not the configured
cadence, so a startup hiccup doesn't skew the run), and use a
proportional `max(interval / 4, 50 ms)` tolerance. `[Ignore]` removed,
`[Category("LongRunning")]` retained.
- **1 mis-labelled "bug"** ΓÇö
`UnorderedMessagesWouldBeLostForSequentialPublishingAsync` asserts the
*documented* lossy trade-off of strict sequential publishing.
`[Explicit]`
removed and renamed to
`OldMessagesAbandonedAfterRepublishTimeoutInSequentialModeAsync`; it's
now a CI-verified regression test for the WAI behavior.
- **4 false positives** ΓÇö `JsonEncoderTestsOld.cs` (16 kLOC) was
excluded from compilation via `<Compile Remove>` and never reached the
runner. The 4 `[Ignore("ProductionBugSuspected")]` markers inside were
orphaned source from a previous JsonEncoder API. Active coverage lives
in `JsonEncoderTests.cs` and `JsonValidationData` fixtures. Deleted
the orphan file and the now-redundant `<Compile Remove>` line.
|
||
|
|
e49d17d706 |
[Server] Make MonitoredNode aware of IAsyncNodeManager and introduce channel-based queueing under load (#3804)
## Proposed changes This PR updates the server monitored-item pipeline to be `IAsyncNodeManager`-aware and introduces channel-based FIFO queueing in the monitored-node path to improve behavior under load. It also includes follow-up fixes from review/CI feedback: - Adjusted `AsyncCustomNodeManagerTests.WriteAsync_PublishesValueToMonitoredItemQueueAsync` to account for channel-based delivery timing/coalescing while still asserting the written value is observed. - Merged latest `master` into this branch and resolved merge conflicts (notably in `ConfigurationNodeManager`), keeping async address-space lookup behavior intact. - Applied small compile-safety corrections needed for successful validation. |
||
|
|
e1b9cea3ea |
Reduce sync-over-async in non-obsolete server APIs + post-merge code style cleanup (#3806)
## Proposed changes Reduces sync-over-async in non-obsolete server APIs, removes stale `#region/#endregion` noise from the test suite, and fixes analyzer/test fallout from merging the Part 11 Historical Access framework (#3797). Follow-up review feedback is included: `FileSystemNodeManager` is now based on `AsyncCustomNodeManager`. ### Sync-over-async reduction (~24 `GetAwaiter().GetResult()` sites) **Server-side:** - `TrustList`: async `OnCallAsync` handlers added; sync `OnCall` handlers retained as compat shims so legacy `CustomNodeManager2` subclasses that host the TrustList but do not implement `ICallAsyncNodeManager` keep working through `MethodState.Call`. - `GDS ApplicationsNodeManager`: `RequestAccessToken`, `KeyCredentialStart/FinishRequest`, and `Revoke` moved to `OnCallAsync`. - `IKeyCredentialRequestStore`: interface + in-memory implementation converted to async; `FinishKeyCredentialRequestResult` record carries the previous out-set. Tests in `Tests/Opc.Ua.Gds.Tests/` updated to the new async API. - `FileSystem`: - `DirectoryObjectState` methods (`CreateDirectory` / `CreateFile` / `Delete` / `MoveOrCopy`) use `OnCallAsync`. - `FileSystemNodeManager` now derives from `AsyncCustomNodeManager` and overrides async address-space/node-resolution paths (`CreateAddressSpaceAsync`, `DeleteAddressSpaceAsync`, `GetManagerHandleAsync`, `ValidateNodeAsync`) to avoid sync-over-async in dynamic node validation. - `FileSystemNodeManagerFactory` now supports async creation (`IAsyncNodeManagerFactory`) while retaining sync factory compatibility. - Reference server registration of the FileSystem node manager is aligned to the async node manager list. - `ConfigurationNodeManager` namespace lookup uses sync `GetManagerHandle` to keep the remaining `GetResult` inside the documented `SyncNodeManagerAdapter` bridge. - `ServerBase`: new `OnServerStartingAsync`; `StartAsync` now awaits it. - `StandardServer.CreateMasterNodeManagerAsync` added; sync wrapper kept for backward compatibility. - `IServerInternal.CloseSession` marked `[Obsolete]` (`CloseSessionAsync` exists). - `IMasterNodeManager.RemoveReferences` marked `[Obsolete]`; in-tree sync callers remain with scoped `CS0618` pragmas + TODO. **Stack:** - `EncryptedSecret`: added `VerifyHeaderForEccAsync` / `DecryptAsync` / `TryDecryptAsync`; `UserNameIdentityTokenHandler.DecryptAsync` awaits ECC validation. - `CertificatePasswordProvider` ctor: `GetResult` fallback replaced with explicit `InvalidOperationException`. ### Code-style and post-merge cleanup - Removed unused `#region`/`#endregion` directives across test files. - Restored green build state after Part 11 merge fallout fixes (production + analyzer + test cleanup from earlier commits in this PR). ### CI / validation notes - This PR branch surfaced runtime regressions introduced by Part 11 after the build-breaker was fixed (outside scope of this async/code-style cleanup). - Follow-up feedback change validation for `FileSystemNodeManager`: - `dotnet build Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj -f net10.0 --no-restore` ✅ - `dotnet test Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj -f net10.0 --filter FileSystemNodeIdTests` ✅ - `dotnet test Tests/Opc.Ua.History.Tests/Opc.Ua.History.Tests.csproj -f net10.0 --filter FileSystemSmokeTests` ✅ (test skipped in environment) ## Related Issues - None. ## Types of changes - [ ] Bugfix (non-breaking change which fixes an issue) - [x] Enhancement (non-breaking change which adds functionality) - [x] Test enhancement (non-breaking change to increase test coverage) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected, requires version increase of Nuget packages) - [ ] Documentation Update (if none of the other choices apply) ## Checklist - [x] I have read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf). - [x] I ran tests locally with my changes, all passed. - [ ] I fixed all failing tests in the CI pipelines. - [ ] I fixed all introduced issues with CodeQL and LGTM. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added necessary documentation (if appropriate). - [x] Any dependent changes have been merged and published in downstream modules. |
||
|
|
c89bc92e7c |
Part 11 Historical Access client/server framework (#3797)
## Proposed changes
Implements **OPC UA Part 11 (Historical Access)** end-to-end via a clean
**provider model** that lets servers back historizing variables with any
time-series storage engine. A reference-quality **in-memory engine**
ships in `Opc.Ua.Server` so getting started requires no extra packages.
The implementation replaces the legacy `HistoryArchive` /
`HistoryDataReader` / `HistoryFile` / `IHistoryDataSource` files
(removed from `Quickstarts.Servers/TestData/`) with a stack-level Part
11 surface.
### What's new
- **Server provider model** in `Libraries/Opc.Ua.Server/Historian/`:
- Umbrella `IHistorianProvider` + narrow capability interfaces
(`IHistorianDataProvider`, `IHistorianModifiedProvider`,
`IHistorianAtTimeProvider`, `IHistorianProcessedProvider`,
`IHistorianAnnotationProvider`, `IHistorianEventProvider`,
`IHistorianTransactionalProvider`).
- `HistorianDispatcher` — routes HistoryRead / HistoryUpdate (raw,
modified, at-time, processed, events, annotations) including
event-`WhereClause` evaluation via the framework's `FilterEvaluator`,
audit-event reporting, and continuation-point lifecycle.
- `HistorianProviderRegistry` with Node → Namespace → Default
precedence; surfaced via `IHistorianRegistryProvider` on
`ServerInternalData`.
- Fluent `HistorianBuilder`
(`server.UseHistorian().UseInMemory().Historize(...).RegisterAsDefault()`).
- Lazy `HistoricalDataConfigurationType` companion installer
(`OnPopulateBrowser`-attached).
- `ServerSystemContext` now exposes `IServerInternal Server { get; }` so
infrastructure code stops relying on the per-NM `SystemHandle`.
- **In-memory reference engine** (`InMemoryHistorianProvider`) — raw,
modified, processed via framework fallback, at-time via framework
fallback, annotations, events, transactional batches.
- **Client surface** in `Libraries/Opc.Ua.Client/Historian/`:
- `HistoryClient` with `IAsyncEnumerable<DataValue>`
raw/modified/at-time/processed/annotation reads (transparent
continuation-point handling and `try/finally`-based release on iterator
abandonment).
- `session.Historian()` extension.
- Capability discovery (`GetServerCapabilitiesAsync`,
`GetConfigurationAsync`).
- **Node-manager wiring** through both `AsyncCustomNodeManager` and
`CustomNodeManager2` (sync-via-dispatcher).
- **Capability rollup** — `DiagnosticsNodeManager` populates the
server-wide `HistoryServerCapabilities` as the union of registered
providers' `GetCapabilitiesAsync(NodeId.Null, ...)` (runs whether the
node was loaded from the predefined nodeset or freshly created; fully
async).
- **Audit-event reporting** for HistoryUpdate (gated on
at-least-one-success):
`AuditHistoryValueUpdateEventType`,
`AuditHistoryAnnotationUpdateEventType`,
`AuditHistoryRawModifyDeleteEventType`,
`AuditHistoryAtTimeDeleteEventType`, `AuditHistoryEventUpdateEventType`,
`AuditHistoryEventDeleteEventType`.
- **ReferenceServer + TestData** historize sample variables through the
new pipeline (replaces the deleted legacy `HistoryArchive`).
- **Comprehensive developer documentation** in
`Docs/HistoricalAccess.md` (~657 lines): server-developer guide,
provider-author guide, client-developer guide, capability discovery,
auditing, limitations/roadmap.
### Tests
| Suite | Files | Tests |
| --- | --- | --- |
| Server unit (Opc.Ua.Server.Tests/Historian) | dispatcher,
event-dispatcher, registry, in-memory provider, transactional provider,
concurrency, coverage, event-filter target | **34** passing |
| Client integration (Opc.Ua.Client.Tests/Historian) |
`HistoryClientIntegrationTests` over the live `ReferenceServer` via
`ServerFixture` | **4** passing |
| AOT (Opc.Ua.Aot.Tests/HistoryAotTests) | positive assertions against
historized `Scalar_Static_Int32` (read raw, read processed/Average) |
**2** passing |
### Local validation
- `dotnet build UA.slnx -c Release` → **0 errors**.
- All historian / client integration / AOT history tests **green**.
### Hardening applied post-internal-review
- `DiagnosticsNodeManager.RollUpHistorianCapabilitiesAsync` — true async
(no more `.GetAwaiter().GetResult()` on the startup path).
- `HistoryClient` raw/modified/details readers — `try/finally` releases
the in-flight continuation point on `break`/exception/cancellation; also
detects malformed-server empty-page loops.
- `ReportAudit*` helpers gated on `HasAnyGood(...)` — audit events do
not fire when every per-value op failed.
- `ServerSystemContext` ctors throw
`ArgumentNullException(nameof(server))` at the boundary.
- `HistorianEventFilterTarget.IsTypeOf` logs a one-shot warning when
`IFilterContext.TypeTree` is null (degrades safely instead of silently).
- `InMemoryHistorianProvider.ReadEventsAsync` snapshots under lock,
filters outside (cuts lock-hold time).
- `TryRestoreContinuation` rejects cross-wired continuation points
(different `NodeId`).
- Processed-read framework fallback capped at 100 000 buffered outputs
(`BadTooManyOperations` when exceeded).
- ReferenceServer / TestData seeding is async-awaited /
`Task.Run`-bridged to escape any captured `SynchronizationContext`.
## Related Issues
- Fixes #
## Types of changes
- [x] Bugfix (non-breaking change which fixes an issue)
- [x] Enhancement (non-breaking change which adds functionality)
- [x] Test enhancement (non-breaking change to increase test coverage)
- [ ] Breaking change
- [x] Documentation Update
> The deletion of legacy
`Applications/Quickstarts.Servers/TestData/HistoryArchive*` is a
refactor of sample code only; the public stack surface is purely
additive.
## Checklist
- [x] I have read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf).
- [x] I ran tests locally with my changes, all passed.
- [ ] I fixed all failing tests in the CI pipelines.
- [ ] I fixed all introduced issues with CodeQL and LGTM.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added necessary documentation (`Docs/HistoricalAccess.md`).
- [x] Any dependent changes have been merged and published in downstream
modules.
## Further comments
The provider model is intentionally minimal and orthogonal — providers
opt into specific Part 11 features by implementing one or more of the
narrow capability interfaces, and the dispatcher type-tests the concrete
provider at runtime. This keeps the bundled `InMemoryHistorianProvider`
simple while making it trivial for vendors to plug in their own
time-series database.
A few items remain explicitly deferred (documented in
`Docs/HistoricalAccess.md` *Limitations and roadmap*):
`HistoricalEventConfigurationType` auto-install for event notifiers,
audit-event `OldValues` fidelity (would require read-before-write across
update sites), true streaming processed-read continuation (the framework
fallback is bounded by the 100 k cap; native
`IHistorianProcessedProvider` providers already get arbitrary paging).
These are on-demand items, not gaps in the mandatory Part 11 surface.
|
||
|
|
eb40a59566 |
More format and diagnostic fixes (#3780)
Drive the solution to zero warnings / zero errors with analyzers on, promote the consistently-fixed rules to error, and lock in coverage on the only line of new logic. |
||
|
|
f3928db05b |
Add unified Microsoft.Extensions DI surface across OPC UA libraries (#3803)
Introduces a consistent `Microsoft.Extensions.DependencyInjection`
surface across the OPC UA libraries. Every consumer starts from a single
root entry point and chains feature-specific extension methods on a
unified builder. PubSub is intentionally excluded per the original
scope.
```csharp
services
.AddOpcUa() // Stack/Opc.Ua.Core
.AddServer(o => { /* … */ }) // Libraries/Opc.Ua.Server
.AddNodeManager<MyNodeManagerFactory>()
.Services
.AddOpcUa()
.AddClient(o => { /* … */ }); // Libraries/Opc.Ua.Client
```
Manual non-DI constructors (`new ApplicationInstance(...)`, `new
StandardServer(...)`, `new LdsServer(...)`, `new ManagedSession(...)`,
etc.) remain unchanged — DI is purely additive for users who want the
.NET Generic Host to own application lifetime, logging, and
configuration.
### What ships
| Feature library | Method on `IOpcUaBuilder` | Returns |
|----------------------------------------------|-------------------------------------------|--------------------------|
| `Opc.Ua.Core` (root) | `services.AddOpcUa()` | `IOpcUaBuilder` |
| `Opc.Ua.Configuration` | `builder.AddApplicationInstance()` |
`IOpcUaBuilder` |
| `Opc.Ua.Client` | `builder.AddClient(opt => …)` |
`IOpcUaClientBuilder` |
| `Opc.Ua.Client.ComplexTypes` | `builder.AddComplexTypes()` |
`IOpcUaBuilder` |
| `Opc.Ua.Server` | `builder.AddServer(opt => …)` |
`IOpcUaServerBuilder` |
| `Opc.Ua.Gds.Client.Common` | `builder.AddGdsClient(opt => …)` |
`IGdsClientBuilder` |
| `Opc.Ua.Gds.Server.Common` | `builder.AddGdsServer(opt => …)` |
`IGdsServerBuilder` |
| `Opc.Ua.Lds.Server` | `builder.AddLdsServer(opt => …)` |
`ILdsServerBuilder` |
| `Opc.Ua.WotCon.Server` | `builder.AddWotConServer(opt => …)` |
`IWotConServerBuilder` |
| `Opc.Ua.WotCon.Client` | `builder.AddWotConClient(opt => …)` |
`IOpcUaBuilder` |
### Design highlights
- **Binary-safe inheritance** — `IOpcUaBuilder :
IDependencyInjectionBuilder` so any existing `AddLogging(this
IDependencyInjectionBuilder)` callers keep compiling unchanged.
- **Three options overloads** per feature: `Action<TOptions>`
(AOT-safe), `IConfiguration`, `IConfigurationSection`. The two binding
overloads are annotated `[RequiresUnreferencedCode]` /
`[RequiresDynamicCode]`. Section names follow the `OpcUa:<Feature>`
convention (`OpcUa:Server`, `OpcUa:Client`, `OpcUa:Gds:Server`,
`OpcUa:Lds`, …).
- **Generic Host integration** — each server feature (regular, GDS, LDS)
registers its own `BackgroundService`. Marker singletons throw
`InvalidOperationException` on duplicate `.AddServer()` /
`.AddGdsServer()` / `.AddLdsServer()`. A regular server + GDS + LDS can
coexist in one host; each owns its own `IApplicationInstance` produced
by a shared `IApplicationInstanceFactory` so PKI roots, endpoints, and
certificates don't collide.
- **Registration isolation** — node managers registered under the
regular server via `.AddNodeManager<T>()` are wrapped in
`OpcUaServerNodeManagerRegistration` so they don't bleed into the GDS /
LDS hosted services.
- **WoT Connectivity server** composes on top of the regular server
feature: `.AddServer(...).AddWotConServer(...)` registers
`WotConnectivityNodeManagerFactory` as a server node-manager
registration.
- **Unified telemetry** — `services.AddOpcUa()` registers
`ITelemetryContext` via `TryAddSingleton` using a new
`ServiceProviderTelemetryContext` that resolves the host's
`ILoggerFactory` on first use. User-supplied registrations win.
### Removed pre-release DI surface
The pre-release `services.AddOpcUaServer(...)`,
`services.AddOpcUaClient(...)`, `services.AddOpcUaGdsClient(...)`
extensions are deleted. `IClientBuilder` was renamed to
`IOpcUaClientBuilder`. In-repo callers migrated:
- `Applications/MinimalCalcServer/Program.cs`
- `Applications/MinimalBoilerServer/Program.cs`
- `Tests/Opc.Ua.Client.Tests/ClientBuilder/OpcUaClientBuilderTests.cs`
(renamed from `OpcUaClientServiceCollectionExtensionsTests.cs`)
- Docs (`MigrationGuide.md`, `Sessions.md`,
`SourceGeneratedNodeManagers.md`, README, new
`Docs/DependencyInjection.md`)
### csproj audit
Pruned every redundant `Microsoft.Extensions.*` package reference.
`Opc.Ua.Core` already pulls in `Microsoft.Extensions.Diagnostics` and
`Microsoft.Extensions.Logging`, which transitively expose DI / Options /
Configuration.Abstractions / Options.ConfigurationExtensions to every
downstream library. Only `Microsoft.Extensions.Hosting.Abstractions`
stays explicit on the server libraries (it isn't transitively
available). Also pruned redundant `System.Formats.Asn1` (Bindings.Https)
and `System.Collections.Immutable` (Security.Certificates).
### Tests
50 new tests in eight projects:
| Project | Tests |
|--------------------------------------|-------|
| `Opc.Ua.Core.Tests` | 12 |
| `Opc.Ua.Client.Tests` | 4 |
| `Opc.Ua.Client.ComplexTypes.Tests` | 3 |
| `Opc.Ua.WotCon.Tests` | 6 |
| `Opc.Ua.Lds.Tests` | 5 |
| `Opc.Ua.Gds.Tests` | 6 |
| `Opc.Ua.Server.Tests` (integration) | 10 |
| `Opc.Ua.Aot.Tests` (smoke) | 4 |
| **Total** | **50** |
Combined-host integration tests cover the rubber-duck-driven scenarios:
telemetry-context override across legacy and new entry paths, server +
GDS + LDS coexistence with distinct ports and PKI roots,
duplicate-registration rejection, and node-manager isolation across
features.
### AOT
`dotnet publish Tests/Opc.Ua.Aot.Tests -c Release -r win-x64
--self-contained` reports zero `IL2026` / `IL3050` / `IL2104` warnings
from the new DI surface. All generic registrations carry
`[DynamicallyAccessedMembers(PublicConstructors)]`. AOT consumers stick
to the `Action<TOptions>` overload exclusively; the new
`Tests/Opc.Ua.Aot.Tests/HostingAotTests.cs` smoke fixture exercises this
end-to-end.
|
||
|
|
14e995c1d0 |
Restructure test projects for faster CI (#3767)
Rebalance the heavy test projects of the OPC UA .NET Standard stack so CI test execution can run cleanly in parallel.
### New test projects (Tests/Opc.Ua.<X>.Tests/)
| Project | Files | Sources |
|---|---:|---|
| Opc.Ua.Sessions.Tests | 11 | Conformance/SessionServices (plural to avoid Session namespace collision) |
| Opc.Ua.Subscriptions.Tests | 25 | SubscriptionServices + MonitoredItemServices |
| Opc.Ua.History.Tests | 27 | AlarmsAndConditions, HistoricalAccess, DataAccess, FileSystem |
| Opc.Ua.Lds.Tests | 15 | Discovery, DiscoveryServices, Miscellaneous |
| Opc.Ua.Core.Security.Tests | 39 | Security, Auditing |
| Opc.Ua.InformationModel.Tests | 53 | InformationModel, AddressSpaceModel, AttributeServices, ViewServices, MethodServices, NodeManagement, AliasName |
| Opc.Ua.Core.Encoders.Tests | 11 | Encoder tests extracted from Core.Tests |
| Opc.Ua.Gds.Tests (existing) | +5 | new GDS tests |
### New helper class libraries (Tests/Opc.Ua.<X>.TestFramework/ + Tests/Opc.Ua.Test.Common/)
* Opc.Ua.Client.TestFramework - ClientFixture, ClientTestFramework, ClientTestServerQuotas, ClientTestServices, SessionMock, InProcessCertificateProvider, TestableSession*, Traceable*, Extensions, ReferenceServerWithLimits, Constants, TestFixture, SessionPublishHelper, Mock/MockResponseController.
* Opc.Ua.Server.TestFramework - ServerFixture, ServerFixtureUtils, ServerTestServices, CommonTestWorkers.
* Opc.Ua.Core.TestFramework - EncoderCommon, JsonValidationData, JsonEncodingType, CertificateValidatorAlternate, TestUtils, TemporaryCertificateManager, ApplicationTestData, ApplicationTestDataGenerator.
* Opc.Ua.Test.Common - Logging.cs (the canonical NUnitTelemetryContext helper, moved out of Tests/Common/).
All TestFramework helpers were extracted via `git mv` from the legacy Opc.Ua.Client.Tests / Opc.Ua.Server.Tests / Opc.Ua.Core.Tests projects (no copy-paste).
### Server-side production-code fixes
1. `MasterNodeManager.ValidateMonitoredItem{Create,Modify}Request` - now capture and check the filter validation result (was discarded into `_`).
2. `MasterNodeManager.ReadAsync` - assign `value = values[ii] = value.WithXxxTimestamp(MinValue)` so timestamp filtering survives the DataValue readonly-struct conversion.
3. `ApplicationsDatabaseBase.{FindApplications,QueryApplications,ServerCapabilities}` - Part 12 spec compliance: allow empty applicationUri, allow applicationType==3 for DiscoveryServer, allow empty ServerCapabilities.
4. `LinqApplicationsDatabase.{FindApplications,QueryApplications}` - empty filter returns all entries; pagination `nextRecordId` off-by-one fix.
### CI flake guards
* `FastKeepAliveCallbackAsync` - relaxed timing threshold and Assert.Ignore on slow CI runners.
* `SecurityCertValidationTests` - Assert.Ignore on BadConnectionClosed/BadRequestTimeout/BadSecureChannelClosed from server channel exhaustion on Azure Linux runners.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
||
|
|
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). |
||
|
|
239a40b183 |
GC reduction via IPooledEncodeable activator pooling (#3793)
* GC reduction via IPooledEncodeable activator pooling
Add opt-in activator-level pooling for IEncodeable instances decoded
during V2 subscription publish dispatch. The design replaces the
earlier IAllocator-on-IServiceMessageContext approach (which had a
cross-contamination hazard for non-publish decodes) with a clean
factory-based model where pooling lives on the encodeable activator.
New public API:
- IPooledEncodeable : IEncodeable - opt-in marker with Reuse() method
- PooledEncodeableType<T> : EncodeableType<T> - activator base with
BoundedObjectPool<T> (lock-free, no new package dependency)
- ISubscriptionManager.PoolNotifications { get; set; }
- ManagedSessionOptions.PoolNotifications { get; init; }
- ManagedSessionBuilder.WithPoolNotifications(bool)
Source-gen:
- All concrete non-abstract structure types (including service
request/response types) are generated with PooledEncodeableType<T>
activators and IPooledEncodeable.Reuse() bodies by default
- Derived types use 'new' to hide inherited Reuse/ClearPooledSentinel
so each concrete type routes through its own activator's pool
- No changes to external design XML files (UA Core Services.xml etc.)
- poolable selection is hardcoded in the generator
Benchmarks (MonitoredItemNotification, 1000 ops):
- Allocations: 128 KB -> 408 B (~315x reduction)
- Gen0 collections: 29.75 -> 0.09 per 1000 ops
No interface changes to IEncodeable, IDecoder, IServiceMessageContext,
or ISubscriptionNotificationHandler. Handlers retain by copy when
pooling is enabled.
Call IPooledEncodeable.Reuse() on decoded IServiceRequest and
IServiceResponse after the server is done with them:
- TcpTransportListener.OnRequestReceivedAsync: reuse both request
and response in a finally block after ProcessRequestAsync +
SendResponse complete
- EndpointBase.InvokeServiceAsync (HTTPS path): same pattern after
ProcessRequestAsync + BinaryEncoder.EncodeMessage
Walk acknowledged, overflow-evicted, and disposed NotificationMessage
objects and call IPooledEncodeable.Reuse() on their notification
payloads (MonitoredItemNotification, DataChangeNotification,
EventFieldList, EventNotificationList).
Insertion points in Subscription.cs:
- Acknowledge(): after RemoveAt from m_sentMessages
- InnerPublish() overflow: before RemoveRange/Clear on m_sentMessages
- Dispose(): before m_sentMessages.Clear()
- InnerPublish() unsent-message overflow: before RemoveRange on
the messages list
Replace new T() with XxxActivator.Instance.CreateInstance() for all
server-side notification payload types so instances are rented from
the activator pool instead of heap-allocated:
New static helper ReuseNotificationPayloads() walks the
NotificationMessage.NotificationData, extracts DataChangeNotification
and EventNotificationList via TryGetValue, iterates their items, and
calls Reuse() on each pooled payload.
|
||
|
|
df2425540d |
Fix/extension object preserve encoding typeid (#3761)
* fix: preserve wire encoding TypeId in ExtensionObject on decode BinaryDecoder, JsonDecoder, and XmlDecoder were overwriting ExtensionObject.TypeId with the DataType NodeId (e.g., i=887) instead of preserving the encoding NodeId from the wire (e.g., i=889). Use ExtensionObject(typeId, encodeable) to retain the original encoding NodeId per OPC 10000-6 §5.2.2.15. * fix: preserve encoding TypeId for XML-in-binary body path * Preserve TypeId in ExtensionObject decoding tests. Exisitng tet used in XmlDecoderTests. Introduced a test encodeable with distinct TypeId and BinaryEncodingId for validation. * ExpandedNodeId used in CreateExtensionObject for tests * copy parameter comment added * fix: JsonEncoder must write DataType NodeId per OPC UA Part 6 §5.4.2.16 Per §5.4.2.16, the UaTypeId field in JSON encoding must be the NodeId of a DataType Node, not a DataTypeEncoding Node. Previously, the JsonEncoder used ExtensionObject.TypeId directly, which after the decoder fix to preserve encoding TypeIds could contain a Binary or XML encoding NodeId when re-encoding from another format. Changed WriteExtensionObject to use encodeable.TypeId (the DataType NodeId) when the body is IEncodeable, falling back to value.TypeId only for raw bodies (ByteString, string) where no IEncodeable is available. This ensures correct cross-encoding behavior (e.g., XML→JSON or Binary→JSON) where the ExtensionObject carries an encoding-specific TypeId from the source format but must emit the DataType NodeId in JSON. * Fix nullable encodeable handling in JsonEncoder --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: romanett <romanett98@gmail.com> |
||
|
|
296a904f34 |
Add full OPC UA Part 18 (Role management) support (#3778)
* role management compliant with OPC UA Part 18 Server-side: - New IRoleManager / RoleManager with spec-correct status codes (Bad_AlreadyExists, Bad_NodeIdUnknown, Bad_RequestNotAllowed, Bad_NotFound) for every mutator per Part 18 4.2 / 4.4. - Pre-populate default identities for the three reserved well-known roles per Part 18 4.3 (Anonymous, AuthenticatedUser, TrustedApplication). - Reject mutations on reserved roles with Bad_RequestNotAllowed. - Validate IdentityMappingRuleType per Part 18 4.4.3 (empty criteria for Anonymous/AuthenticatedUser/TrustedApplication; upper-case hex for Thumbprint; Name=Value grammar for X509Subject). - Honour Part 18 4.4.2 default-field-as-wildcard EndpointType comparisons; tighten TrustedApplication/Application rules to require trusted certificate + signed channel. - Introduce CustomConfiguration flag per Part 18 4.4.1. - Add IRoleManager.RoleConfigurationChanged event raised after every successful mutation per Part 18 4.4.1. - Make RoleEntry an immutable snapshot. Address-space binding (typed proxies): - RoleStateBinding switched from stringly-typed OnCallMethod2 plumbing to the source-generated typed proxies (RoleSetState, RoleState, AddRoleMethodState, AddIdentityMethodState, ...). - DiagnosticsNodeManager.AddBehaviourToPredefinedNodeAsync upgrades passive BaseObjectState nodes of RoleSetType / RoleType to the typed RoleSetState / RoleState proxies at predefined-node load time. - New RoleAuthorizationGate.CheckAdmin centralizes SecurityAdmin role plus SignAndEncrypt enforcement per Part 18 4.2 / 4.4 / 5.2. - Bound writable ApplicationsExclude / EndpointsExclude / CustomConfiguration with the same auth gate. - Raise RoleMappingRuleChangedAuditEventType from every successful mutator (Part 18 4.5) via the existing AuditEvents helper. Client library: - New Opc.Ua.Client.Roles.RoleManagementClient + IRoleManagementClient + RoleInfo + SessionRoleExtensions.OpenRoleManagementClient. - Covers list / read / add / remove role, identity, application, endpoint operations plus Set*Exclude via Write Service. Tests: - New RoleManagerTests.cs - 37 NUnit tests for pre-populated roles, reserved-role immutability, spec-correct status codes, criteria validation, ResolveGrantedRoles, exclude semantics, CustomConfiguration, AddRole well-known NodeId reuse, and RoleConfigurationChanged event. Documentation: - Rewrite Docs/RoleBasedUserManagement.md as a Part 18 compliance reference (status-code matrix, client examples, known limitations). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Part 18 5 UserManagement implementation Server-side: - New IUserManagement interface (Opc.Ua.Server.UserManagement) modelling Part 18 5.2.1 surface: PasswordLength, PasswordOptions, PasswordRestrictions, SnapshotUsers, AddUser, ModifyUser, RemoveUser, ChangePassword, MustChangePassword, IsUserActive, UserDeactivated event. - Default UserManagement implementation wraps the existing IUserDatabase and stores per-user UserConfigurationMask + description in memory. Validates passwords against PasswordLength + character-class flags from PasswordOptions; honours Part 18 5.2 result codes (Bad_AlreadyExists, Bad_NotFound, Bad_OutOfRange, Bad_NotSupported, Bad_ConfigurationError, Bad_InvalidSelfReference, Bad_IdentityTokenInvalid). - ChangePassword clears the MustChangePassword bit on success. - New UserManagementBinding wires the typed source-generated proxies (UserManagementState, AddUserMethodState, ModifyUserMethodState, RemoveUserMethodState, ChangePasswordMethodState) - typed OnCallAsync delegates everywhere; admin gate via RoleAuthorizationGate.CheckAdmin; ChangePassword gated via RoleAuthorizationGate.CheckSelfUserName; closes active sessions for deactivated users via ISessionManager. - ConfigurationNodeManager.AddBehaviourToPredefinedNodeAsync upgrades the passive UserManagement object to the typed UserManagementState proxy at predefined-node load time; CreateServerConfiguration invokes UserManagementBinding.Bind when an IUserManagement was injected. - IServerInternal gains UserManagement getter + SetUserManagement injector. Tests: - New Tests/Opc.Ua.Server.Tests/UserManagementTests.cs - 18 NUnit tests covering AddUser happy path, duplicate / password-length / config-flag validation, RemoveUser non-existent / self-reference / NoDelete, ModifyUser self-disable / disable-other UserDeactivated event, ChangePassword happy path / same-as-old / wrong-old / MustChangePassword bit clearing / NoChangeByUser rejection, IsUserActive on disabled vs active users. Documentation: - Update Docs/RoleBasedUserManagement.md with a User Management section including the spec result-code matrix, example wiring code, and reduce known limitations - UserManagement is no longer a limitation. |
||
|
|
535d017840 |
Add OPC UA Part 17 (Alias Names) support (#3779)
* Add OPC UA Part 17 (Alias Names) support Server-side: pluggable IAliasNameStore + thread-safe InMemoryAliasNameStore, AliasNameStoreRegistry (live dispatcher, surfaces through optional IAliasNameStoreRegistryProvider on ServerInternalData), AliasNameNodeManager (standalone CustomNodeManager2; defaults to SecurityAdmin-only mutations on SignAndEncrypt channels), AliasNameMethodDispatcher bridging generated typed FindAlias/FindAliasVerbose/Add/Delete *MethodState.OnCallAsync handlers, AliasNameWildcardMatcher (Part 4 §7.40 Like operator with proper escape handling), and a DiagnosticsNodeManager partial that late-binds the standard well-known Aliases (i=23470), TagVariables (i=23479) and Topics (i=23488) FindAlias methods plus Aliases.LastChange through the registry. Client-side: AliasNameClient (OpenStandardAliases/TagVariables/Topics + arbitrary category root) covering all four Part 17 methods, sub-category enumeration and ReadLastChangeAsync. Hardcoded method-id fast-path for standard categories; cached TranslateBrowsePaths resolution for custom ones. Service-level errors map to typed exceptions (UnauthorizedAccessException / NotSupportedException). AliasNameResolver (IAsyncDisposable) gives a name->NodeId cache with forward and reverse lookup; default refresh is Manual with opt-in AutoOnLastChange polling that compares VersionTime on inequality (wrap-safe). Reference server: registers an InMemoryAliasNameStore for the standard TagVariables + Topics categories with sample seeds (TIC101_Setpoint, TIC101_PV, FIC202_Flow, Pump1_Status, Heater_Power, MultiRefAlias, ServerEvents, AuditEvents) so the standard well-known FindAlias methods return data through the DiagnosticsNodeManager late binder. Tests: 45 server-side cases (wildcard matcher, in-memory store, registry, node manager) plus 11 mocked-session client cases and 4 end-to-end integration cases against a live ReferenceServer over a real OPC UA channel. All 60 tests pass; full solution builds clean across net472/net48/netstandard2.1/net8.0/net9.0/net10.0. Docs: Docs/Part17AliasNames.md plus a link from Docs/README.md. |
||
|
|
a193a0a278 |
Add OPC UA Server FileSystem (Part 20) and Local Discovery Server libraries (#3776)
This change introduces two independent additions to the .NET Standard
stack:
## `Libraries/Opc.Ua.Server.FileSystem` (Part 5 §C / Part 20 §4)
Server-side implementation of the OPC UA `FileType` /
`FileDirectoryType` companion model, mirroring the existing client
namespace `Libraries/Opc.Ua.Client.FileSystem`.
### Provider abstraction
- `IFileSystemProvider` — async storage abstraction
(`GetEntryAsync` / `EnumerateAsync` / `OpenRead/WriteAsync` /
`Create{Directory,File}Async` / `DeleteAsync` / `MoveAsync` /
`CopyAsync`). All paths are forward-slash provider-relative; the
empty string is the root.
- `FileSystemEntry` record + `FileWriteMode` enum.
### Default `PhysicalFileSystemProvider`
`System.IO`-based implementation that mounts a **single physical
directory** as the FileSystem root. Rejects path-traversal attempts
(`..` escape) via `Path.GetFullPath` + root-prefix check. Supports
an optional `isWritable: false` mode for read-only mounts.
### Node manager
`FileSystemNodeManager` drives every Browse / Read / Method call
through the configured provider. Each mounted provider is anchored
as a `FileDirectoryType` instance under the standard
`Server.FileSystem` object (`ObjectIds.FileSystem`, `i=16314`)
via `HasComponent`, which is what
`FileSystemClient.OpenServerFileSystem` expects to find. Mount
multiple providers by registering multiple
`FileSystemNodeManager` instances. Companion files
(`DirectoryBrowser`, `DirectoryObjectState`, `FileObjectState`,
`FileHandle`, `FileSystemNodeId`, `FileSystemNodeManagerFactory`)
are all provider-driven.
### Client-side fix
`Libraries/Opc.Ua.Client.FileSystem.FileSystemClient.EnumerateChildrenAsync`
now stops the BrowseNext loop on `continuation.Length == 0` in
addition to `continuation.IsNull`. Without this fix, an
empty-but-non-null ByteString continuation returned by the SDK after
wire round-trip would cause a spurious BrowseNext and fail with
`BadContinuationPointInvalid`.
### Reference-server hook
`Quickstarts.ReferenceServer.ReferenceServer` gains two opt-in
properties (default off):
- `EnableFileSystemNodeManager` — toggles whether the FS node
manager is registered.
- `FileSystemProvider` — caller-supplied
`IFileSystemProvider`. When `null` and the flag is on, falls
back to a `PhysicalFileSystemProvider` rooted at
`%TEMP%/OpcUaReferenceServerFs` (mount name `Temp`).
### Tests
`Tests/Opc.Ua.Client.Tests/FileSystem/FileSystemClientIntegrationTests.cs`
— 12 end-to-end tests over a real
`ServerFixture<ReferenceServer>` + `ClientFixture` (opc.tcp,
SecurityNone, temp-dir provider): round-trip write/read,
enumeration, nested `CreateDirectory` / `CreateSubdirectory`,
delete, move, copy, large-file chunked read, mount BrowseName,
missing path returns null, multi-level enumeration, text round-trip,
read-only provider rejects write.
## `Libraries/Opc.Ua.Lds.Server`
A new Local Discovery Server (LDS) library implementing OPC UA Part
12 §7:
- `LdsServer` — main server class.
- `MulticastDiscovery` — DNS-SD / mDNS bootstrap via
`Makaretu.Dns.Multicast` (version 0.27.0 added to
`Directory.Packages.props`).
- `RegisteredServerStore` — in-memory store with TTL.
- `RegistrationEntry` + `ServerOnNetworkRecord` — value types.
Comes with a sample host `Applications/ConsoleLdsServer`.
## Build / test
- `dotnet build UA.slnx -c Release` -> 0 errors on all target
frameworks (net472, net48, netstandard2.1, net8.0, net9.0, net10.0).
- `Opc.Ua.Server.Tests` -> 964 pass, 5 skip, 0 fail.
- `FileSystemClientIntegrationTests` -> 12/12 pass.
|
||
|
|
3e5f129c6f | Enable nullable in Stack, Libraries (9), and Applications projects (#3732) | ||
|
|
3836cbfdfe |
Add FileSystemClient: System.IO-style async client over OPC UA file APIs (#3760)
Add FileSystemClient: System.IO-style async client over OPC UA file APIs |
||
|
|
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 |
||
|
|
9cbed0ef03 |
Fix PrioritizedChannel polyfill missing Count/CanCount overrides (#3748)
The SubscriptionManager publish worker calls m_acks.Reader.Count outside its inner try block. On runtimes that fall back to the PrioritizedChannel polyfill (net48), the base ChannelReader<T>.Count throws NotSupportedException because PrioritizedReader did not override CanCount/Count. Result: every publish worker iteration crashed immediately at GetAcksReadyToSend, the worker task completed, the controller recreated the worker, and the cycle repeated as a tight loop. This prevented any actual publish from happening and caused integration tests that wait for subscription data to time out: - SubscriptionRecoversAfterAutomaticReconnect - SubscriptionRecoversAfterFailover - TransferSubscriptionsKeepsV2SubscriptionWorkingAfterReconnect - BuilderTransferSubscriptionsOnRecreateOptInAppliesToV2Manager The framework's UnboundedPrioritizedChannel<T> reader (used on .NET 9+) overrides Count to expose the underlying queue count; the polyfill must do the same. Added a regression test ReaderSupportsCount. |
||
|
|
13612c03b9 | Fix CreateInstanceOf<X> access restrictions emission and PrioritizedChannel polyfill TryRead bug (#3747) | ||
|
|
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) | ||
|
|
b2cbf6b03b |
Fix DataValue(int) overload resolution bug (#3721)
new DataValue(42) silently resolved to DataValue(StatusCode) instead of DataValue(Variant) due to C# 'better conversion target' rule — StatusCode wins because it implicitly converts to Variant, making it 'more specific'. This caused m_value = Variant.Null and downstream BadTypeMismatch failures. Changes: - Add [OverloadResolutionPriority(1)] on DataValue(Variant) constructor - Obsolete DataValue(StatusCode) and DataValue(StatusCode, DateTimeUtc) - Add DataValue.FromStatusCode() static factory methods (2 overloads) - Delete DataValue(DataValue) copy constructor - Add DataValue.Copy() instance method using base.MemberwiseClone() - Rewrite Clone()/MemberwiseClone() to delegate to Copy() - Migrate all call sites across 14 files - Add tests verifying new DataValue(42) wraps value correctly - Update MigrationGuide.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
70abb05841 |
Remove Default JSON encoding infrastructure (#3713)
* Remove Default JSON encoding infrastructure Remove IJsonEncodeable interface, JsonEncodingId properties, DefaultJson constants, and all source generator JSON encoding output. The actual JsonEncoder/JsonDecoder and PubSub JSON encoding are not affected. - Delete IJsonEncodeable interface from IEncodable.cs - Remove DataTypeAttribute.JsonEncodingId and simplify TryGetTypeIdsFromType - Remove JSON encoding from 9 source generator files - Remove IJsonEncodeable from 13 built-in types and 12 node types - Remove JSON registration from EncodeableFactory and unboundTypeIds infra - Remove jsonEncodingId from OptionSet, ComplexTypeSystem, IComplexTypeFactory - Remove BrowseNames.DefaultJson and 25 *_Encoding_DefaultJson constants - Update ~14 test files and 2 documentation files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add IJsonEncodeable removal to migration guide Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * minor --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
8e01f836c9 |
Fix SessionReconnectHandler infinite loop when server restarts with changed endpoint configuration (#3708)
* Initial plan * Fix reconnect handler: fall back to best available endpoint when original endpoint is removed from server Agent-Logs-Url: https://github.com/OPCFoundation/UA-.NETStandard/sessions/926282f4-5820-4713-8cb1-53979807fc2f Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> * Fix warning message string concatenation in UpdateEndpointFromServerAsync Agent-Logs-Url: https://github.com/OPCFoundation/UA-.NETStandard/sessions/926282f4-5820-4713-8cb1-53979807fc2f Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> |
||
|
|
84161c120c |
[Client] Fixed excessive tasks spawning during session connection loss (#3707)
Added checks for Session.KeepAliveStopped before trying to execute Publish for subscriptions. |
||
|
|
939764b5a7 | Tests for cp null vs empty (#3703) | ||
|
|
d14cd10a86 |
Model dependency annotations using source generator + flaky test fixes (#3687)
* Fix publish/dispose race that hangs Client tests
Subscription.Dispose pre-disposed m_publishTimer and m_messageWorkerCts
before calling ResetPublishTimerAndWorkerState, causing Cancel() to throw
ObjectDisposedException and leaving the publish worker stuck. Removed the
duplicate disposal so the reset method is the single owner of those resources,
and guarded Cancel/Dispose with try/catch ObjectDisposedException for defense
in depth.
Session.OnPublishComplete could run after Session.Dispose disposed
m_reconnectLock, throwing ObjectDisposedException from SemaphoreSlim.Wait()
and re-arming via the throttle path indefinitely. Added an early Disposed
guard, wrapped Wait/Release in try/catch ObjectDisposedException, and added
a Disposed guard in QueueBeginPublish so the throttle cannot reschedule
publishes after disposal.
Fixes the macOS net10.0 hang seen in Azure pipeline build 13257
(TransferSubscriptionAsync, ReconnectWithSavedSessionSecretsAsync).
* fix lockout test and use UserName token for SuccessfulAuthenticationClearsFailedAttempts test
After
|
||
|
|
26a0b21881 |
Replace session state encoder with IEncodable generation using source generators (#3673)
* Replace SessionStateEncoder with IEncodeable on session/subscription models - Source-gen [DataType]/[DataTypeField] on: MonitoredItemOptions, MonitoredItemState, SubscriptionOptions, SubscriptionState (auto-generated Encode/Decode/IsEqual/Clone) - Manual IEncodeable on: SessionOptions, SessionState, SessionConfiguration (ConfiguredEndpoint and IUserIdentity require manual encoding) - Delete SessionStateEncoder.cs (488 lines removed) - Remove [DataContract]/[DataMember]/[KnownType]/[CollectionDataContract] attributes - Remove JsonSerializerContext classes - Change init -> set, byte[] -> ByteString, DateTime -> DateTimeUtc - MonitoredItems: ArrayOf<MonitoredItemState> with StructureHandling.Inline - Subscriptions: ArrayOf<SubscriptionState> * Source generator: support partial init properties with backing fields Add IsInitOnly detection to the [DataType] source generator so that partial properties with init-only setters can be used in IEncodeable types. For each init-only partial property, the generator: - Emits a private backing field (__PropertyName) - Emits the partial property implementation (get => __field; init => __field = value) - Uses the backing field in Decode() instead of the property - Skips simple init-only fields in Clone() (record 'with' already copies them) Detection uses both IPropertySymbol.SetMethod.IsInitOnly and syntax-tree fallback (SyntaxKind.InitAccessorDeclaration) for partial definitions. * docs: add partial init properties and session state migration sections - SourceGeneratedDataTypes.md: new 'Partial Init Properties' section documenting backing field generation for init-only partial properties - MigrationGuide.md: new 'Session State Persistence' section covering |
||
|
|
34ed5716c0 |
Add structure-backed OptionSet DataType support in ComplexTypeSystem (#3672)
Concrete sub-types of the abstract OptionSet DataType (i=12755, e.g. AccessRights, CarExtras, OptionSetBase) were silently skipped by ComplexTypeSystem.LoadBaseStructureDataTypesAsync when only an EnumDefinition or OptionSetValues property was available. This change registers them with a new runtime class Opc.Ua.Encoders.OptionSet that reuses the generated Opc.Ua.OptionSet wire format (Value/ValidBits ByteStrings) and self-registers via IEncodeableType. Key changes: - New Stack/Opc.Ua/Types/OptionSet.cs with EnumDefinition-driven bit accessors (by name and index), GetSetFieldNames helper, and a fixed ByteLength derived from the highest declared bit per OPC UA Part 3 paragraph 8.40 / 3.2.8. SetBit throws ArgumentOutOfRangeException outside that range and always materializes Value/ValidBits to the fixed length. - New AddOptionSetType entry point on IComplexTypeBuilder (source-breaking addition for custom implementations); DefaultComplexTypeBuilder implements it. The Reflection.Emit ComplexTypeBuilder throws NotSupportedException. - ComplexTypeSystem detects OptionSet descendants via a HasSubtype walk and resolves the EnumDefinition from DataTypeDefinition or the OptionSetValues property fallback before the old skip path. - Removed stale V1.04 OptionSet limitation remark from ComplexTypeSystem. - 10 unit tests in Tests/Opc.Ua.Client.Tests/ComplexTypes/DefaultOptionSetTests.cs covering factory registration, bit accessors, binary round-trip via ExtensionObject, hand-crafted Part 6 wire format, Clone/CreateInstance semantics, and the ByteLength invariant. - Live-verified against opc.tcp://DESKTOP-BGFJS91:48010 (UA C++ SDK demo): AccessRights/CarExtras/OptionSetBase now appear in the registered custom-type list; no skip-log entries; LoadAsync(throwOnError:true) returns true. - Docs/ComplexTypes.md Known Limitations reworded; Docs/MigrationGuide.md gains an OptionSet DataType support subsection. UInteger-backed OptionSet DataTypes remain intentionally opaque (carried as their unsigned integer in a Variant). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
6c8c1a6d29 | Enable dispose analyzers CA2000 and CA2213 and fix all issues (#3670) |