Commit Graph
2380 Commits
Author SHA1 Message Date
MrAlaskan b105eaae02 fix(subscription): do not acknowledge keep-alive future sequence numbers (#4030)
### Summary

This PR stops the V2 subscription message processor from acknowledging
keep-alive sequence numbers and adds a regression test that verifies
acknowledgements are only queued for notification messages that actually
contain data.

### Problem

According to OPC UA Part 4 publish semantics, a keep-alive
`NotificationMessage` does not acknowledge delivery of a real
notification. Its `SequenceNumber` identifies the next notification
message that will be sent, not a notification payload that the client
has already received.

Previously, `MessageProcessor.ProcessMessageAsync()` correctly treated
`NotificationData.Count == 0` as a keep-alive and intentionally avoided
advancing the data-message deduplication gate. However,
`OnNotificationReceivedAsync()` still unconditionally queued a
`SubscriptionAcknowledgement` for every message, including empty
keep-alives. As a result, the V2 client could acknowledge a future
sequence number before the corresponding data/event notification had
actually been received.

This could produce spurious acknowledgement noise against strict servers
and made the V2 path inconsistent with the existing classic subscription
engine, which only acknowledges messages that contain notification data.

### Changes

- Stop queueing acknowledgements for keep-alive messages in
`MessageProcessor.OnNotificationReceivedAsync()`.
- Preserve the existing keep-alive dispatch path and continue
acknowledging notification messages that carry real data.
- Extend the message processor tests to verify that keep-alives do not
enqueue acknowledgements and that the subsequent real notification still
does.
2026-07-20 02:47:24 +02:00
MrAlaskan 12f770b3b6 Fix managed session failover target selection (#4025)
### Summary

This PR makes `ManagedSession` select failover targets relative to the
currently active endpoint after a successful failover and adds a
regression test that verifies subsequent failover decisions no longer
use the original connection target as the current server.

### Problem

According to OPC UA Part 4 redundancy semantics, once a client has
failed over to a different redundant server, subsequent target selection
must be based on the server that is currently hosting the active
session. The current server is not permanently tied to the endpoint that
was used for the initial connection.

Previously, `ManagedSession` stored the constructor-supplied
`ConfiguredEndpoint` in a read-only property and always passed that
outer value into `IServerRedundancyHandler.SelectFailoverTarget()`.
However, the inner `Session` updates its own `ConfiguredEndpoint` during
`RecreateInPlaceAsync()` when failover succeeds. After the first
failover, the outer `ManagedSession` and inner `Session` could therefore
disagree about which redundant member was currently active, causing
later failover decisions to be evaluated against the original endpoint
instead of the active one.

### Changes

- Make `ManagedSession.HandleFailoverAsync()` prefer the inner session's
current `ConfiguredEndpoint` when selecting a failover target, while
keeping the outer endpoint as a fallback before the session is
available.
- Preserve the existing failover flow and scope the fix to target
selection only.
- Add a regression test that verifies `HandleFailoverAsync()` passes the
inner session's current endpoint to
`IServerRedundancyHandler.SelectFailoverTarget()`.
2026-07-19 07:43:19 +02:00
MrAlaskan 70c74e7cb8 fix: scope Cancel request matching to the current session (#4023)
### Summary

This PR scopes `Cancel` request matching to the caller's session and
adds regression tests that verify a request handle can no longer cancel
work that belongs to another session.

### Problem

According to OPC UA Part 4, `Cancel` applies to a request handle from
the same session that issued the original request. The server should not
use a client-supplied `requestHandle` to match outstanding requests
across all sessions.

Previously, `StandardServer.CancelAsync()` validated the caller's
session but then forwarded only the raw `requestHandle` to
`RequestManager.CancelRequests()`. `RequestManager` iterated the global
outstanding-request table and canceled every request whose
`ClientHandle` matched, without checking the owning session. Since
request handles are only session-scoped and different sessions can
legitimately reuse the same value, one session could cancel another
session's in-flight request.

### Changes

- Pass the validated caller session id from
`StandardServer.CancelAsync()` into `RequestManager.CancelRequests()`.
- Restrict `RequestManager.CancelRequests()` to requests whose
`SessionId` and `ClientHandle` both match the `Cancel` request.
- Update existing request-manager and publish-queue tests to pass the
session id and add a regression test that proves matching request
handles in different sessions no longer interfere with each other.
2026-07-19 06:23:52 +02:00
MrAlaskan 2d55126cb8 fix: return BadInvalidState for unlatched alarm reset (#4020)
### Summary

This PR fixes the reset status code returned for unlatched alarms and
adds a regression test that verifies `Reset` and `Reset2` both report
`BadInvalidState` when the alarm is not latched.

### Problem

According to the alarm reset semantics described in OPC UA Part 9,
`Reset` is only valid for a latched alarm that is no longer active and
has already been acknowledged and confirmed when those states are
present. For any other invalid state, the server should return
`Bad_InvalidState`.

Previously, `AlarmConditionState.HandleResetCore()` returned
`BadConditionNotShelved` when `LatchedState.Id == false`. That status
code belongs to the shelving state machine, not the latched alarm reset
flow. As a result, clients calling `AlarmClient.ResetAsync()` could
receive a shelving-related error for a reset precondition failure, which
misrepresents the actual alarm state and breaks the expected Part 9
semantics.

### Changes

- Change the unlatched branch in `AlarmConditionState.HandleResetCore()`
to return `BadInvalidState` instead of `BadConditionNotShelved`.
- Add a regression test in `AlarmConditionStateMethodTests` that covers
both `Reset` and `Reset2` when `LatchedState` exists but the alarm is
not latched.
2026-07-18 21:45:32 +02:00
Marc Schier 20d9d65a64 Fix generated configuration namespace resolution (#4019)
# Description

Fix source-generated configuration types whose explicit namespace
references constants emitted by the same generator pass. Roslyn cannot
resolve those constants while `[DataType]` attributes are analyzed, so
the generated XML codec silently fell back to `urn:memorybuffer` /
`urn:testdata` while the shipped configuration uses the model namespace
URIs.

This change:

- adds `MODELGEN021` to fail when an explicitly supplied
`[DataType.Namespace]` expression cannot be resolved;
- uses compile-time-available model namespace URIs for the MemoryBuffer
and TestData configuration types;
- restores production-shaped XML fixtures and exercises the
`UpdateExtension<T>()` / `ParseExtension<T>()` path;
- documents the source-generator namespace requirement and migration
guidance.

Targeted validation passed:

- `TypeGeneratorTests`: 17 tests on `net10.0` and `net48`;
- `ConfigurationRoundTripTests`: 4 tests on `net10.0` and `net48`.

## Related Issues

- Fixes #4014

## 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.
- [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.
- [x] I have addressed **all** PR feedback received.
2026-07-18 20:15:49 +02:00
84fa8e8906 CTT follow-up: monitored items, alarms, codecs, history, and model validation (#4002)
# Description

Follow-up to #3978 based on the latest OPC UA CTT aggregate, matrix,
Historical Access, Attribute, and Node Management runs.

## Aggregate run 16

- Ignore synthetic `BadBoundNotFound` raw-bound markers when feeding
aggregate calculators.
- Add direct and live historian regression coverage.

## Multi-dimensional Variant matrices

- Fix `Variant.Copy()` so rank-2 Variant matrices preserve their payload
and dimensions.
- Enforce valid Variant matrix dimensions consistently across Binary,
JSON, XML stream, and XML parser codecs.
- Encoders return `BadEncodingError`; decoders return `BadDecodingError`
for malformed matrix dimensions.
- Add cross-codec valid/null/empty/malformed tests and a batched
ReferenceServer Read regression.
- Correct the previous CTT issue attribution: the invalid
`Scalar_Static_Arrays2D_Variant` server encoding poisoned the batched
CTT Read.

## Node Management AddNodes

- Return `BadNodeIdRejected` for unsupported requested-NodeId
namespaces.
- Validate hierarchical ReferenceType source/target NodeClass
constraints.
- Document run-18 confirmation that all remaining 15 AddNodes errors are
CTT project/script configuration issues.

## Historical Access and Attribute Services run 18

- Reject whitespace in `NumericRange` syntax with
`BadIndexRangeInvalid`.
- Add HistoryRead tests for operation-level invalid syntax and
per-DataValue `BadIndexRangeNoData`.
- Document CTT defects:
- `012.js` expects `BadIndexRangeNoData` at the operation level instead
of on returned DataValues;
  - `Err-012.js` uses a non-historizing node for an access-denied test;
- generic Attribute array helpers omit NodeId[] and StatusCode[]
conversion paths.

## Base Information model validation run 19

- Run the CTT main session as `sysadmin` over Basic256Sha256
SignAndEncrypt so SecurityAdmin RolePermissions and EncryptionRequired
restrictions are both satisfied without weakening the server model.
- Preserve predefined well-known NodeIds while making generated dynamic
type and optional-child factories allocate compatible per-instance
NodeIds, remap internal references, and update references from the
owning subtree.
- Materialize dynamic RoleType properties through generated helpers and
keep role-child string NodeIds disjoint from numeric RoleManager root
IDs.
- Add net48/net10 regressions for generator output, NodeId
allocation/reference remapping, DI mandatory children, dynamic roles,
and low-privilege versus SecurityAdmin Browse visibility.
- Document CTT defects for permission-unaware mandatory-child
validation, the UA 1.04 reference model used against a UA 1.05 server,
stale ServerDiagnostics cache, and the `ConformanceUnits` scalar/array
mismatch.
## Related Issues

- Follow-up to #3960 and #3978.

## Checklist

- [x] I have signed the CLA and read the CONTRIBUTING document.
- [x] Tests prove the fixes and improve coverage.
- [x] Required CTT documentation is updated.
- [x] Focused builds/tests introduce no warnings.
- [x] Affected tests pass on .NET Framework and .NET 10.
- [ ] CI and CodeQL are green.
- [ ] All PR feedback is addressed.



## Latest CTT full run and A&C run 20

- Fix Monitor Value Change V2 `036.js`: an ambiguous
`DataValue(StatusCode)` call encoded `BadIndexRangeNoData` as a Good
value.
- Prove all 19 configured matrix monitored items notify when the
selected value actually changes; document the independent `042.js`
script defects.
- Rebase dynamic `CertificateExpired` / `TrustListOutOfDate` alarm roots
and descendants, restore standard type declarations and ModellingRules,
and expose the cross-node-manager references.
- Deliver method-triggered events to every authorized Session, fixing
the two-Session Confirm scenario.
- Preserve locale-only `LocalizedText` values consistently across
Binary, JSON, and XML and align Acknowledge/Confirm comment behavior and
tests.
- Document the remaining proven CTT defects for TransactionDiagnostics,
SemanticChange, History `013`, PolicyId scope, A&C, durable
subscriptions, and Subscription Minimum events.

## Base synchronization

Merged `master` through `007cfaf90` (`#4010`, repository layout
consolidation) and retained the matrix regression tests at their new
`tests/` locations.

## Latest validation

- Focused merged-tree tests pass on net10 and net48.
- `Opc.Ua.Server` and `Quickstarts.Servers` Release builds complete with
0 warnings and 0 errors on net10 and net48.
- The existing
`DisableEnableViaTypeAndInstanceMethodNoEventsDuringDisabledAsync`
timing test took its documented inconclusive timeout path after the
layout merge; no test failure was reported.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <romanett98@gmail.com>
2026-07-18 05:01:03 +02:00
b76022d13d Add MinimalClient - lightweight OPC UA console client with fluent API (#4006)
# Description

Adds `MinimalClient`, a compact console OPC UA application demonstrating
the unified dependency-injection surface, secure endpoint discovery,
managed sessions, fluent V2 subscriptions, and Alarms & Conditions
support.

## Changes

- Adds `ConfigureApplication(options => ...)` to build and validate one
shared `ApplicationConfiguration` for combined client/server hosts.
- Extends `OpcUaClientOptions` so the existing `AddClient(options =>
...)` callback accepts application identity, PKI, and
certificate-validation settings like `AddServer(...)`.
- Composes root `ConfigureApplication(...)` values with client defaults
in either registration order; shared values take precedence and client
values fill gaps.
- Keeps server samples on their established `AddServer(...)`
configuration pattern.
- Adds `MinimalClient` as a .NET 10 Native AOT console application using
top-level statements and `HostApplicationBuilder`.
- Discovers a `SignAndEncrypt` / `Basic256Sha256` endpoint by default;
`--insecure` and `--auto-accept` are explicit opt-ins with warnings.
- Demonstrates fluent `AddSubscription(...)`,
`TryAddMonitoredItem(...)`, and `AlarmClientFactory` usage.
- Adds targeted configuration, client, and combined client/server tests.

## Example

```csharp
builder.Services
    .AddOpcUa()
    .AddClient(options =>
    {
        options.ApplicationName = "MinimalClient";
        options.ApplicationUri = "urn:localhost:OPCFoundation:MinimalClient";
        options.ProductUri = "uri:opcfoundation.org:MinimalClient";
        options.Session = new ManagedSessionOptions
        {
            SessionName = "MinimalClient",
            SessionTimeout = TimeSpan.FromSeconds(60)
        };
    })
    .AddDiscoveryAndConnect(options =>
    {
        options.DiscoveryUrl = discoveryUrl;
        options.SecurityMode = MessageSecurityMode.SignAndEncrypt;
        options.SecurityPolicyUri = SecurityPolicies.Basic256Sha256;
    })
    .AddSubscriptions()
    .AddAlarms();
```

## Related Issues

_If there is no issue yet, open one and link it here._

## 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.
- [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.
- [ ] I fixed all failing and flaky tests in the CI pipelines and all
CodeQL warnings.
- [x] I have addressed all PR feedback received.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <romanett98@gmail.com>
Co-authored-by: Marc Schier <marcschier@hotmail.com>
2026-07-17 19:36:53 +02:00
Marc Schier 41a648c9db Fail fast on reverse connect listener startup (#4009)
# Description

Fail reverse-connect listener startup immediately instead of logging
bind failures and surfacing a later connection timeout.

- Validate and stage configured and programmatic listener endpoints
before replacing active configuration.
- Open all listeners atomically and report endpoint-specific failures
with `BadTcpEndpointUrlInvalid`, the transport-specific status, or
`BadNoCommunication`.
- Roll back listeners opened by a failed attempt without reacquiring the
manager lock, including forced listener disposal when normal close
fails.
- Preserve meaningful `ServiceResultException` status codes through both
`StartService` overloads.
- Dispose replaced configuration watchers and reject callbacks from
stale watcher generations.
- Keep the intended shared-listener behavior: one long-lived
`ReverseConnectManager` can accept connections from multiple Servers on
the same Client URL.
- Expand `Docs/ReverseConnect.md` with listener lifetime, shared-manager
usage, diagnostics, and specification references.

## Validation

- `ReverseConnectManagerTests`: net10.0 and net48.
- `ReverseConnectHostTests`: net10.0 and net48.
- Ordered reverse-connect listener reuse integration tests: net10.0.

## Related Issues

- Relates to #3985.

## 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.
- [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.
2026-07-17 13:20:24 +02:00
Marc Schier 98984af7c3 CI: share managed pool and hosted agents across the pipeline (#3969)
Replace the all-or-nothing `managedPool` boolean with a `poolMode` enum
(mixed | managed | hosted) and route stages across two pool profiles so
the netstandard Managed DevOps Pool and the Microsoft-hosted agents share
the workload of a single run.

- mixed (default): each stage uses its assigned profile, so both pools run
  in parallel. The two always-concurrent PR stages are split across pools:
  build -> hosted (bigger temp disk for multi-TFM bin trees), testfastpr ->
  managed (fast, image-pinned gate). Full-run stages are balanced ~5/5.
- managed / hosted: global overrides that collapse both profiles onto a
  single pool (all-managed or all-hosted) for emergencies.

The pool switch lives only in the variables block (mgd*/host* profiles),
so stages carry no per-stage conditionals. macOS is unchanged (always
hosted; the managed pool has no macOS images).
2026-07-17 13:19:06 +02:00
Marc Schier 12ebfba603 Add ISA95 JobControl and ManagedSession regression coverage (#3998)
## Summary

- Adds the official ISA95 JobControl 2.0.0 NodeSet2 as a complete
source-generator regression fixture.
- Extends the lightweight generated-stack test stub with
`NamespaceMetadataState`, required by the complete model.
- Adds a deterministic ManagedSession cancellation regression proving
that a started connect attempt is stopped.

The ManagedSession cleanup implementation and Quickstarts utility
coverage now come from `master`; this PR retains the additional
deterministic cleanup regression.

## ISA95 fixture provenance and integrity

The fixture is byte-for-byte the official file:

- Source:
`OPCFoundation/UA-Nodeset@e0b5d80cfff698f0276393e28df138a7fbc4b541`
- Path: `ISA95-JOBCONTROL/opc.ua.isa95-jobcontrol.nodeset2.xml`
- Git blob: `82f008cd126ade54e5f2c737bcc116621a8c3087`
- SHA-256:
`52327e736beb604c7253a5a5393d1ad525b9ecf35b71fbacb4f429cacb334064`

The fixture test retains the OPC Foundation MIT header and verifies the
2.0.0 model/date, 258 nodes (including 134 variables), representative
late variables, and that every `ns=1` reference target resolves locally.

## Validation

- ManagedSession tests: 17 passing on net10.0; 15 passing and 2
framework-gated skips on net48.
- Quickstarts server utility tests: 3 passing on net10.0 and net48.
- ISA95 generator and fixture integrity regression: 5 passing on
net10.0.
2026-07-17 13:19:00 +02:00
Marc Schier 007cfaf902 DO NOT SQUASH: Consolidate repository layout under src, samples, tests, tools, docs, and fuzzing (#4010)
# Description

Consolidates the repository into conventional lowercase source roots:

- flattens `Stack/` and `Libraries/` into `src/`;
- moves `Applications/` to `samples/`;
- moves the MCP tool to `tools/Opc.Ua.Mcp`;
- renames `Tests/`, `Tools/`, `Docs/`, and `Fuzzing/` to lowercase;
- updates all project references, solutions, CI pipelines, scripts,
Dockerfiles, package documentation, and repository links.

The final tree contains the same projects, package identities,
assemblies, and runtime behavior as before.

> [!IMPORTANT]
> Merge this PR with a merge commit; do not squash it. Git stores trees
rather than rename metadata, and 115 files share duplicate blobs. The
four commits temporarily disambiguate those files, preserve all 4,580
exact old-to-new path mappings, and then restore the final content.

## Related Issues

- Fixes #3907

## Validation

- Windows `net10.0`: 45 test projects, 42,485 passed and 683 skipped.
One timing-sensitive History project was rerun in isolation and passed.
- Windows `net48`: 45 test projects, 40,651 passed and 706 skipped.
- Linux case-sensitive `net10.0` build and path-sensitive integration
tests passed.
- NativeAOT publish and all 115 published AOT tests passed.
- Full preview solution restore/pack completed across all target
frameworks and produced 81 packages.
- The packaged MCP tool installed and returned `--help` successfully.
- All 10 Dockerfile paths and all 6 compose files validated. Full image
builds were attempted but Docker Desktop's Linux daemon was unavailable
locally.

## 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.
- [ ] 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.

This is a repository-layout-only change, so no new behavioral tests were
added.
2026-07-16 14:17:26 +02:00
Marc Schier 437a0dadc7 Merge origin/master into marcschier/3907-repository-layout 2026-07-16 07:11:36 +02:00
Marc Schier 8538f531e7 Update paths for the consolidated repository layout 2026-07-16 06:26:05 +02:00
Marc Schier a21b690d08 Move the remaining duplicate binary assets 2026-07-16 06:26:05 +02:00
Marc Schier 7cec2d614d Move repository files to the consolidated layout 2026-07-16 06:25:54 +02:00
Marc Schier 9335c15bdd Temporarily disambiguate duplicate files for the repository move 2026-07-16 06:24:49 +02:00
Alexander KöpkeandCopilot Autofix powered by AI 80889b0971 PumpDeviceIntegrationServer - Register DeviceHealth node, use HasAddIn for SoftwareUpdate (#4008)
This pull request makes important improvements to how the
`SoftwareUpdate` object is modeled and registered in the OPC UA Device
Integration (DI) server, ensuring better compliance with the OPC
10000-100 specification and improved compatibility with companion-spec
checkers. The main changes involve updating the reference type for
`SoftwareUpdate` to use `HasAddIn`, explicitly registering the
`DeviceHealth` node, and enhancing related tests.

**Device Model Compliance and Registration:**

* The `SoftwareUpdate` object is now attached to its host device using
the `HasAddIn` reference type instead of `HasComponent`, aligning with
the OPC 10000-100 specification and reducing validation noise from
companion-spec checkers.
* The `DeviceHealth` child node is now explicitly registered with the
node manager after device registration to ensure its attributes resolve
correctly and it can be mapped by companion-spec checkers.

**Testing Improvements:**

* Added a test assertion to verify that the `SoftwareUpdate` child is
attached via the `HasAddIn` reference, ensuring the implementation
matches the OPC UA specification.Explicitly register DeviceHealth child
node after creation to ensure attributes resolve and DI checker
compatibility. Reference SoftwareUpdate facet via HasAddIn per OPC
10000-100 spec, not HasComponent, to avoid validation issues. Update
tests to assert correct reference type.

# Description

_Describe the changes here to communicate to the maintainers why they
should accept this pull request. By default - this will become the
Commit message after merging and thus define history._

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

- Fixes #github-issue-number, ...

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] I have added all necessary documentation.
- [ ] 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.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 06:00:22 +02:00
Marc Schier 7d0e5870cb Migrate to source-generated logging across Stack, Libraries and Applications (#3908) (#4003)
# Description

Migrates the entire code base (Stack, Libraries and Applications) from
direct

`ILogger.LogInformation/LogError/LogWarning/LogDebug/LogTrace/LogCritical(...)`
calls to **source-generated `[LoggerMessage]` logging**, as tracked in
#3908.
Source-generated logging avoids boxing of value-type arguments, caches
the
message formatter, and emits an efficient `IsEnabled` check so a
disabled log
level costs nothing.

This is a behavior-preserving refactor: message text, levels and
structured
placeholders are unchanged; only the call shape changes.

## What changed

- **36 projects migrated.** Every static log call now goes through a
per-file
  `internal static partial class <Class>Log` holding `[LoggerMessage]`
  extension methods on `ILogger`, so call sites keep the natural
`logger.SomeEvent(...)` shape. Closely-related files that emit the same
messages share one log class (e.g. the encoders/decoders in
`Opc.Ua.Types`
  share `EncodingLog`) to avoid duplication and CS0121 ambiguity.
- **Per-assembly event-id classes.** Each project has one
  `internal static class <AssemblyToken>EventIds` (e.g. `CoreEventIds`,
  `ClientEventIds`, `PubSubEventIds`) with a reserved `public const int`
  offset block per log class. The assembly-token prefix avoids CS0436
  collisions across `InternalsVisibleTo` boundaries.
- **`CA1873` enabled solution-wide** in `.editorconfig` now that the
whole
code base (including test, sample and tooling projects) is migrated, so
any
regression back to a direct `ILogger.Log*` call with an expensive
argument is
flagged. Every occurrence has been guarded with an `IsEnabled` check (or
the
check merged into an existing condition where the log was the sole body
of an
`if`/`else`, preserving side effects); the only suppressions are two Moq
  `Verify` expression-tree sites, which are not executed log calls.
- **Dynamic log levels stay hand-written.** A handful of calls whose
level is
only known at runtime keep the structured `logger.Log(logLevel, ...)`
form
under an `if (logger.IsEnabled(logLevel))` guard — `[LoggerMessage]`
requires
  a compile-time level.
- **netstandard duplicate-generator fix.** Projects that also pull in an
R9
package (`Microsoft.Extensions.Http.Resilience`, `.Compliance`,
`.Telemetry`)
get the `Microsoft.Gen.Logging` generator in addition to the in-box one;
on
  `netstandard` both implement every partial method (CS0757). A single
`Directory.Build.targets` target removes the R9 analyzer on
`netstandard`
  only (no-op elsewhere and where it is not referenced).
- **Shared/linked source files** that are compiled into more than one
project
(e.g. sample files linked into test projects) use self-contained literal
event ids instead of a per-assembly class the consumer does not compile.
- **Docs.** The authoring recipe (event-id convention, log-class
convention,
  parameter rules, the `IsEnabled` guard, shared/linked files, and the
  netstandard generator interaction) lives in the new

[`Docs/DeveloperGuide.md`](Docs/DeveloperGuide.md#add-a-log-message-source-generated);
`Docs/Diagnostics.md` keeps the runtime/observability side and links to
it.
- **Redaction preserved.** Log calls that previously wrapped sensitive
values
with `Redact.Create(...)` keep passing the `RedactionWrapper<T>`
(instead of
an eager `.ToString()`), so format-time redaction still applies — this
also
  clears the CodeQL clear-text alerts introduced during the migration.

## Validation

- Full `UA.slnx` build passes with **0 errors / 0 warnings**; the
multi-target
  test projects force every referenced library to build on
  net472/net48/netstandard2.1/net8.0/net9.0/net10.0.
- No unguarded static `ILogger.Log*` calls remain anywhere (only the
guarded
dynamic-level calls); `CA1873` is clean across the full solution on
net10.0.

## Related Issues

- Fixes #3908

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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.
- [ ] 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.
2026-07-15 15:07:25 +02:00
Marc Schier ce2ccc72b0 Fix transport buffer pooling, limiting, and in-process capture (#3994)
# Description

Fixes transport-buffer pool amplification when
`TransportQuotas.MaxBufferSize` is an exact power of two, adds
injectable buffer-manager policies and a process-wide memory limiter,
and fixes in-process pcap capture of maximum-size UASC chunks.

`BufferManager` previously reserved an ownership cookie after the usable
data in every build. A 65,536-byte limit therefore requested 65,537
bytes from `ArrayPool<byte>.Shared`, selecting the 131,072-byte bucket.
The compatibility `BufferManager` is now a facade over `IBufferManager`;
Release defaults to `FastBufferManager`, Debug defaults to
`CookieBufferManager`, and explicit `TRACK_MEMORY` builds use
`TracingBufferManager`.

`IBufferManagerFactory` and `BufferManagerFactoryOptions` are registered
through dependency injection and wired through raw TCP, HTTPS/WSS, and
Kestrel channel/listener creation while preserving existing direct
constructors. Applications can replace the factory or explicitly select
fast, cookie, or tracing behavior.

`LimitingBufferManager` wraps any implementation and uses a
factory-shared `BufferManagerMemoryLimiter` to cap actual outstanding
bytes across all managers created by that factory. It blocks synchronous
rents without holding manager locks, supports cancellation on transport
I/O paths, accounts for actual pool bucket sizes, rejects impossible or
synchronous re-entrant rents safely, and protects accounting from
failed, foreign, and duplicate returns.

The client and accepted-server TCP transports also retained raw quota
values. The fix carries bucket-safe limits into transport creation and
reconnects, while preserving the active connection’s negotiated receive
limit. Configurable transports receive updated limits during server
reconnect, and every channel independently rejects and faults/reconnects
on chunks above the negotiated limit.

Maximum-size UASC chunks exceeded the synthetic IPv4 total-length field
once TCP/IP headers were added. The capture path now segments those
chunks into sequence-aware synthetic TCP packets sized for both IPv4 and
the pcap snap length. Every capturing transport receives a stable
process-unique synthetic flow identity that is preserved in the full TCP
tuple and independent sequence state. Capture queuing remains
non-blocking and queues or drops complete UASC chunks atomically;
sequence space is reserved before bounded frame admission so dropped
chunks remain visible as TCP gaps, while packetization stays in the
single writer. Key-material snapshots use a separate prioritized
unbounded queue and are drained and disposed reliably during shutdown.

This change includes:

- `IBufferManager`, `IBufferManagerFactory`, factory options, and
compatibility facade;
- fast, cookie, allocation-tracing, and memory-limiting implementations;
- shared DI factory wiring for raw TCP, HTTPS/WSS, and Kestrel
transports;
- Debug/Release/TRACK_MEMORY implementation selection;
- cancellation-aware transport rents and no-loss in-process receive
behavior;
- deterministic ownership, limiter, bucket-size, reconnect,
oversized-chunk, cancellation, and DI tests;
- explicit subscription diagnostics using `ArrayPoolEventSource`;
- pcap framing, sequence-space, atomic-queue, and reassembly regression
tests;
- updated benchmark and dependency-injection documentation.

Measured with 255 and 257 Publish queues plus a slow-consumer scenario,
configured values of both 65,535 and 65,536 used only 65,536-byte
arrays. Every observed rent had a matching return, outstanding buffers
returned to zero, and no Gen 2 collections occurred. The 500-session
baseline also passed for `opc.tcp`, `https`, and `opc.https`, with all
sessions receiving notifications.

## Related Issues

- Fixes #3988

## 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.
- [x] I have addressed **all** PR feedback received.
2026-07-15 08:57:42 +02:00
Marc Schieranddependabot[bot] c250db682b Complete Part 12 ServerConfiguration and transactional PushManagement (#3989)
# Description

Completes the OPC UA Part 12 ServerConfiguration and transactional
PushManagement implementation, including opt-in distributed/HA
coordination, while retaining the original review-feedback fixes from
#3952.

## Transactional certificate and TrustList management

- Adds a Session-owned transaction coordinator shared by certificate
groups and TrustLists.
- Stages certificate and TrustList changes until `ApplyChanges`, with
ordered prepare/commit, rollback, intra-operation compensation,
cancellation, Session cleanup, and diagnostics.
- Implements `CreateSelfSignedCertificate`, `DeleteCertificate`,
`GetCertificates`, `CancelChanges`, `SupportsTransactions`, and
`TransactionDiagnostics`.
- Enforces method-specific SecureChannel requirements and standard Part
12 status codes.
- Persists regenerated signing-request keys and incorporates caller
nonce entropy where supported.
- Protects certificates currently presented by live endpoint security
policies.

## Distributed and high-availability PushManagement

- Adds opt-in `UseDistributedPushConfiguration` for redundant server
deployments.
- Uses the existing `ISharedKeyValueStore` CAS primitives for a
renewable cluster-wide transaction lease.
- Reuses the configured `ILeaderElection`; Kubernetes deployments use
the existing native Kubernetes Lease implementation, while
Raft/shared-store deployments use their existing providers.
- Persists pending regenerated keys in the shared store with
`IRecordProtector` authenticated encryption and atomic cross-replica
consumption.
- Fails closed when an external shared store lacks record protection.
- Keeps non-distributed server behavior unchanged.

## ApplyChanges effects

- Forces affected classic and Kestrel opc.tcp peer channels to
renegotiate after TrustList changes.
- Routes trust effects by listener trust-list scope.
- Revalidates X.509 user identities and closes invalid Sessions and
Subscriptions.
- Preserves response-before-channel-close sequencing and drains deferred
effects during shutdown.

## Diagnostics, alarms, and optional configuration

- Implements Part 12 TransactionDiagnostics DataValue status semantics
with client-safe error messages.
- Activates CertificateExpiration and TrustListOutOfDate alarms.
- Exposes identity properties and provider-backed optional
ServerConfiguration functionality.
- Adds secure, versioned configuration-file updates with rollback and
confirm/revert lifecycle.
- Aligns advertised and enforced MaxTrustListSize.

## Security and reliability feedback

- Clears certificate-store password buffers after use.
- Prevents internal exception details and paths from leaking through
TransactionDiagnostics.
- Hardens shared-fixture transaction cleanup.
- Preserves `IRecordProtector` compatibility through an additive
owned-buffer extension.

## Related Issues

- Follow-up to #3952

## Testing

- Full server and GDS suites pass on net10.0.
- Focused server and distributed PushManagement suites pass on net10.0
and net48.
- Distributed HA tests cover cross-replica contention, lease
expiry/renewal, leadership gating, protected pending-key transfer,
tamper rejection, and fail-closed configuration.
- Analyzer-enabled affected-project builds pass without new warnings or
errors.
- CTT/UACTT was not run because the licensed GUI tooling is unavailable
in this environment.

## 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.
- [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.
- [x] I have addressed **all** PR feedback received.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 14:40:47 +02:00
Marc SchierandCopilot b8765ee9b4 Dispose canceled managed session creation and expand server utility coverage (#3995)
# Description

Dispose a `ManagedSession` when its initial connection wait fails or is
canceled so the background state machine and owned resources do not
outlive a failed `CreateAsync` call. Log cleanup failures while
preserving the original connection failure or cancellation.

Add regression coverage for canceled managed-session creation and
cleanup logging, plus coverage for Quickstarts default node manager
registration and the reusable Reference Server helper flags.

## Related Issues

None.

## 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.
- [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.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 14:29:27 +02:00
Alexander KöpkeandCopilot 2e24fd0b74 Fix companion spec compliance bugs in pump sample application (#3999)
This pull request introduces several important improvements to the
Device Integration (DI) server implementation and its documentation,
focusing on compliance with the DI companion specification, robust node
instantiation, and clearer documentation for users. The key changes
ensure that device instances are created with all mandatory children and
correct namespace assignments, resolve NodeId collisions for multiple
instances, and clarify the behavior of helper methods in the
documentation.

**Device instantiation and compliance improvements:**

* Device instances are now materialized through the source-generated
`CreateInstanceOfDeviceType` factory, ensuring that all mandatory DI
children (such as the eight nameplate variables) and `HasInterface`
references are present with the correct DI-namespace BrowseNames. This
change brings the implementation in line with the DI companion-spec
requirements and prevents compliance failures.
(`Libraries/Opc.Ua.Di.Server/DiNodeManager.cs`,
`Docs/DeviceIntegration.md`)
[[1]](diffhunk://#diff-8786a28f8727cc3c9b7d14ca549dd08dfbe257842d287b4ea6bd614ab0ad2787L340-R355)
[[2]](diffhunk://#diff-f8292a4fd8ad9f3cdb4955707f4e63d6b0ca7472045cce21e46e835e69bc7bf5L168-R179)
[[3]](diffhunk://#diff-f8292a4fd8ad9f3cdb4955707f4e63d6b0ca7472045cce21e46e835e69bc7bf5L210-R242)
* When registering devices and software update subtrees, per-instance
NodeIds are recursively assigned to all children, preventing NodeId
collisions across multiple instances of the same type.
(`Libraries/Opc.Ua.Di.Server/DiNodeManager.cs`,
`Libraries/Opc.Ua.Di.Server/Builders/SoftwareUpdateFacetWiring.cs`)
[[1]](diffhunk://#diff-8786a28f8727cc3c9b7d14ca549dd08dfbe257842d287b4ea6bd614ab0ad2787R438-R490)
[[2]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR126-R134)
[[3]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR144-R161)

**Property and namespace handling:**

* The `WithProperty` helper now creates properties when missing, with
correct DI-namespace qualification, and documentation has been updated
to reflect this behavior. This ensures that both mandatory and optional
properties are handled correctly and that custom functional groups can
add properties dynamically. (`Docs/SourceGeneratedNodeManagers.md`,
`Applications/PumpDeviceIntegrationServer/Program.cs`,
`Libraries/Opc.Ua.Di.Server/Builders/DeviceBuilder.cs`)
[[1]](diffhunk://#diff-24f0ac53195633e085935831e9bd8de7d918de1ccf59699730b8f7770d9c7899L621-R626)
[[2]](diffhunk://#diff-24f0ac53195633e085935831e9bd8de7d918de1ccf59699730b8f7770d9c7899L639-R668)
[[3]](diffhunk://#diff-82e3eaf8442a044f3bfad256e188e4eb6fdb62a2146796aebae0323c7f2b7dc8R458-R465)
[[4]](diffhunk://#diff-0b763658716a4959f371881988e7ddb399ec62eb92345dab229ca8441ac452b6L105-R108)

**Software update facet and method declaration fixes:**

* The software update facet builder now correctly assigns
`MethodDeclarationId`s for methods such as `Resume`,
`InstallSoftwarePackage`, `InstallFiles`, and `Uninstall`, ensuring that
these methods resolve to their in-type declarations and pass compliance
checks.
(`Libraries/Opc.Ua.Di.Server/Builders/SoftwareUpdateFacetWiring.cs`)
[[1]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR258-R264)
[[2]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR309-R314)
[[3]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR326-R327)
[[4]](diffhunk://#diff-642220ec038696416cb3a615a37ee47e2e7e38c81bea6ff8cf652a2fac5a178bR336-R339)

**Documentation updates:**

* The documentation has been updated to clarify the device instantiation
process, DI companion-spec compliance, and the behavior of helper
methods for property creation and bulk initialization.
(`Docs/DeviceIntegration.md`, `Docs/SourceGeneratedNodeManagers.md`)
[[1]](diffhunk://#diff-f8292a4fd8ad9f3cdb4955707f4e63d6b0ca7472045cce21e46e835e69bc7bf5L168-R179)
[[2]](diffhunk://#diff-f8292a4fd8ad9f3cdb4955707f4e63d6b0ca7472045cce21e46e835e69bc7bf5L210-R242)
[[3]](diffhunk://#diff-24f0ac53195633e085935831e9bd8de7d918de1ccf59699730b8f7770d9c7899L621-R626)
[[4]](diffhunk://#diff-24f0ac53195633e085935831e9bd8de7d918de1ccf59699730b8f7770d9c7899L639-R668)

These changes collectively improve DI spec compliance, prevent subtle
bugs with NodeId collisions, and make the codebase and documentation
more robust and user-friendly.# Description

_Describe the changes here to communicate to the maintainers why they
should accept this pull request. By default - this will become the
Commit message after merging and thus define history._

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

- Fixes #github-issue-number, ...

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [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.
- [ ] 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.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 11:38:50 +02:00
0da85b9d89 CTT conformance: access rights, X509, aggregates, and matrix diagnostics (#3978)
# Description

Investigates successive OPC UA CTT runs and fixes the proven server
conformance defects while documenting proven CTT script/codec defects in
`plans/ctt-issues.md`.

## Server fixes

- Grant `WriteAttribute` on `Scalar_Static_Int32` so attributes
advertised by `WriteMask` are writable.
- Disable the client-certificate-keyed authentication lockout in the
dedicated CTT configuration so negative token tests do not mask later
results with `BadUserAccessDenied`.
- Map an X509 user-token signature algorithm mismatch to token-level
`BadIdentityTokenInvalid`.
- Persist rejected X509 user certificates to `pki/rejectedUser` to
simplify trusted-user provisioning.
- Populate `HistoricalDataConfiguration.AggregateConfiguration` with the
server's actual defaults.
- Return per-node `BadInvalidArgument` for processed reads with equal
StartTime/EndTime.
- Feed reverse processed reads in request order and preserve exact
non-Bad interpolation boundaries.
- Correct PercentGood/PercentBad uncertain classification.
- Correct WorstQuality/WorstQuality2 eligible bounds, StatusCode
preservation, and `MultipleValues`.
- Correct reverse Min/Max Raw-vs-Calculated semantics.
- Fix `HistoryClient.GetConfigurationAsync` traversal and
aggregate-configuration reads.

## Tests and CTT evidence

- Added direct calculator and live historian Part 11/13 oracle tests
covering forward/reverse intervals, equal-time validation, bounds,
status aggregates, DurationInState, and Min/Max.
- Added exact Part 6 Table 26 binary golden-vector coverage for a 2×3
Int32 Matrix Variant.
- Added in-process whole-matrix read/write and multidimensional
`NumericRange` coverage.
- Updated `plans/ctt-issues.md` with exact evidence and recommended
fixes for CTT aggregate, X509, multidimensional-array, A&C, and
AliasName script defects.

No CTT failure is suppressed or accepted without a spec-based
classification.

## Related Issues

- Relates to #3960.

## Checklist

- [x] Tests proving the server fixes are included.
- [x] CTT-side defects are documented with script locations and
specification references.
- [x] Targeted aggregate/routing/matrix suites pass on net48 and net10.
- [x] History aggregate/client suites pass on net48 and net10.
- [x] Modified lines are analyzer-clean.
- [ ] CI and CodeQL complete.
- [ ] Maintainer review complete.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Roman Ettlinger <romanett98@gmail.com>
2026-07-13 19:14:19 +02:00
Marc Schier 2a1db65aec Fix legacy password verification and JSON QualifiedName decoding (#3991)
# Description

Fix two compatibility and decoding regressions:

- Verify persisted PBKDF2 password hashes using the iteration count
stored with each hash. This preserves authentication for user databases
created with the previous 10,000-iteration policy when running on .NET
10.
- Normalize malformed string-form JSON `QualifiedName` values through
the decoder's existing strict/lenient contract. Lenient parsing now
returns `QualifiedName.Null`, while strict parsing reports
`BadDecodingError` instead of leaking `BadNodeIdInvalid`.

The change adds deterministic legacy/current password-hash vectors and
scalar, array, and Variant JSON decoder coverage.

## Related Issues

- No related issue.

## 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.
- [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.

## Validation

- `LinqUserDatabaseTests`: 10/10 passed on `net10.0` and `net48`.
- `JsonDecoderTests`: 262/262 passed on `net10.0` and `net48`.
- Deterministic JSON fuzz regression tests: 1368/1368 passed on
`net10.0` and `net48`.
- Affected test projects build with zero warnings and errors on
`net10.0` and `net48`.
2026-07-12 15:14:00 +02:00
Marc SchierandCopilot aadba8b618 Add runtime NodeSet-backed node managers (#3990)
# Description

Add a dedicated startup-time runtime NodeSet2 loading path for servers
that need to expose information models without source generation.

This introduces:

- `AddRuntimeNodeSet` DI extensions for single files, grouped sources,
and lazy stream providers.
- A dedicated `RuntimeNodeSetNodeManagerFactory` and
`FluentNodeManagerBase` implementation that remain separate from
source-generated NodeManagers and typed fluent builders.
- Model ownership validation, `RequiredModel` dependency ordering,
parent-child linking, external reference handling, and fail-fast
diagnostics.
- Untyped `INodeManagerBuilder` code-behind wiring for imported
variables, methods, events, alarms, and simulations.
- Reuse of the existing default server-side complex-type loading path
after the runtime address space is created.
- Runtime NodeSet documentation plus NUnit and NativeAOT coverage.

The feature intentionally loads NodeSets only during server startup.
Live add, reload, or removal after clients connect remains out of scope
because it requires separate MasterNodeManager routing, NamespaceArray,
model-change, subscription, and complex-type lifecycle design.

## Related Issues

- Fixes #3974

## Validation

- Runtime NodeSet tests: 38 passed on `net10.0`.
- Runtime NodeSet tests: 38 passed on `net48`.
- NativeAOT publish succeeded and the full 107-test executable passed.
- Changed C# files pass whitespace verification and the server library
builds without new warnings.

## 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.
- [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.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-12 13:49:34 +02:00
Marc Schier c8bafd2bdf Add test coverage for JSON user database and state machine availability (#3992)
# Description

Adds focused tests for production paths introduced in #3980 that
remained below the repository's 80% Codecov patch-coverage threshold.

The existing fixtures now cover:

- Current and legacy `ExpandedNodeId` JSON representations used by
`JsonUserDatabase`, including numeric, string, GUID, opaque, null,
absolute, and malformed role identifiers.
- Optional or null `AvailableStates` and `AvailableTransitions` values
in `FiniteStateMachineTypeClientExtensions`.

The tests assert the exact loaded users, roles, identifiers, fallback
behavior, and state-machine results rather than only executing converter
branches.

Local changed-line measurement reports:

- `JsonUserDatabase.cs`: 100% line coverage.
- Changed executable lines in
`FiniteStateMachineTypeClientExtensions.cs`: 100% coverage.

Focused validation passes on .NET 10 and .NET Framework 4.8: 58 tests
passed.

## Related Issues

Follow-up to #3980.

## 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.
- [x] I have addressed **all** PR feedback received.
2026-07-12 12:42:42 +02:00
Marc Schier c8bc1bbee6 Fix nightly Stress Tests (Chaos TCP): transparent reconnect + robust chaos assertions (#3986)
# Description

Fixes the nightly **Stress Tests / Chaos TCP** job (e.g. run
[29068846476](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/29068846476)),
which had failed on **every run since it was introduced** — the workflow
runs on a schedule and never gated PRs, so the failures went unnoticed.

## Root cause

The Chaos TCP tests point a `ConfiguredEndpoint` at a local
`TcpChaosProxy` and set `UpdateBeforeConnect = false` so the session
connects *through* the proxy. Two problems prevented this from working:

1. `ManagedSession.HandleConnectAsync` **ignored**
`ConfiguredEndpoint.UpdateBeforeConnect` and hard-coded a pre-connect
re-discovery for all non-OpenAPI endpoints. Re-discovery adopted the
server's advertised `EndpointUrl`, so the session connected **directly
to the server, bypassing the proxy** — no chaos ever reached the
connection. Two tests "passed" vacuously.
2. Once the proxy was genuinely engaged, the transparent-reconnect
feature had a gap: a request that hit the just-dropped channel (before
the manager detected the drop) failed with `BadConnectionClosed` instead
of transparently recovering.

## Changes

**Product**

- **`ManagedSession`** now honours
`ConfiguredEndpoint.UpdateBeforeConnect` (default `true`). Setting it
`false` opens the channel against exactly the supplied URL (proxy /
gateway / NAT / pinned endpoint) without re-discovering.
- **`ManagedTransportChannelLease.SendRequestAsync`** transparently
resends **idempotent** requests (`Read` / `Browse` /
`TranslateBrowsePaths` / `GetEndpoints` / `FindServers`) across a
channel-manager reconnect. On a transient transport-drop error it forces
a *coalesced* reconnect and resends once the shared channel recovers.
- A new **`ChannelEntry.ReconnectGeneration`** counter guards this so a
reconnect is only forced for a genuinely undetected drop (same
generation, still `Ready`), never for a stale in-flight failure that
arrives after recovery — which previously spawned spurious extra
reconnect cycles that tore down healthy channels.
- **Non-idempotent** requests (`Create*`, `Write`, `Call`, `Publish`, …)
surface the error so higher-level recovery (e.g. the subscription
engine's own re-create loop) stays in control; requests are never
double-applied.

**Tests / harness**

- **`TcpChaosProxy`** no longer rethrows the expected `OperationAborted`
raised by an in-flight upstream `ConnectAsync` during
`DropAllConnections` (it was faulting the awaited connection task and
failing the test).
- Chaos reconnect assertions (L3-A1/A2/A5) are made robust to the
inherent non-determinism of reconnect **counts** under concurrent chaos
(coalescing merges drops landing in one recovery window; a stale
keep-alive `Bad` can add one cycle). Survival is asserted via
`FailureRate`, `ReconnectFailed == 0`, and the coalescing fan-out
relationship rather than exact `ReconnectStarted/Completed ==
dropCount`.
- Subscription post-drop recovery window widened (`2s → 8s`) to allow
re-creating all sessions' subscriptions and monitored items on a loaded
CI agent.
- **New unit tests** in `ClientChannelManagerManagedTests` cover the new
product paths (fast-PR / codecov, since the nightly ChaosTCP job does
not run in the fast pipeline): idempotent transient-drop retry,
non-idempotent no-retry, non-transient no-retry, and
`ChannelEntry.ReconnectGeneration` increment.

## Validation (local, net10.0)

- **Chaos TCP**: 6/6 pass across many random seeds (incl. post-merge
re-runs of `338475986` and `1999999999`).
- `Opc.Ua.Client.Tests` ChannelManager/ManagedSession: **256/256**
(incl. the 4 new tests).
- `Opc.Ua.Sessions.Tests` ChannelManager/Reconnect: **139/139**.
- Core / Client / Stress build with **0 warnings**.

## Notes

- This branch has been merged up to date with `master` (redundancy PR
#3918).
- The two failing `Opc.Ua.PubSub.Tests` in CI —
`UdpLoopbackActionResponderAnswersRequesterAsync` and
`UdpLoopbackDiscoveryPublisherAnswersSubscriberRequests` — are
**unrelated** to this PR (UDP-loopback request/response tests in the
PubSub library, associated with #3918) and are timing/environment-flaky.
They are out of scope for this change.

## Related Issues

- Fixes the recurring nightly Chaos TCP failure (workflow:
`.github/workflows/stress-test.yml`).

## 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.
- [ ] 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.
2026-07-12 10:20:02 +02:00
28a1779dc9 Stabilize Core.Security X509 user tests on macOS by preferring RSA endpoints (#3983)
# Description

The `test-macOS-latest-Core.Security` job was failing in X509 user-token
tests due to endpoint/certificate algorithm mismatch.
`SecurityX509UserTests` generated RSA user certs but could bind to
ECC-first endpoints on macOS, leading to signing failures (`Missing
private key needed for create a signature`).

- **Root cause**
- Endpoint selection matched only `MessageSecurityMode` and could choose
ECC policies first depending on endpoint ordering.
- X509 user-token signing then entered the ECDSA path with an RSA user
cert.

- **Change**
- Updated `SecurityX509UserTests.FindEndpoint(...)` to prefer
RSA-compatible endpoints first, then fall back to any endpoint in the
same mode.
- Added focused helper methods in the test fixture for policy filtering:
    - `FindMatchingEndpoint(...)`
    - `IsEccPolicy(...)`

- **Why this is minimal**
  - Test-only change, localized to one fixture.
  - No production runtime behavior changed.

```csharp
private EndpointDescription FindEndpoint(ArrayOf<EndpointDescription> endpoints, MessageSecurityMode mode)
{
    EndpointDescription rsaEndpoint = FindMatchingEndpoint(endpoints, mode, requireRsa: true);
    if (rsaEndpoint != null)
    {
        return rsaEndpoint;
    }

    return FindMatchingEndpoint(endpoints, mode, requireRsa: false);
}
```

## Related Issues

Issue linkage is handled automatically by the system.

## 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.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] 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.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <romanett98@gmail.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-12 08:05:16 +02:00
Alexander KöpkeandMarc Schier 26771349af Add Docker support for Pump Device Integration Server (#3987)
- Introduced a GitHub Actions workflow for building and pushing the
Docker image.
- Created a Dockerfile for multi-stage build of the
PumpDeviceIntegrationServer.
- Updated Program.cs to allow configurable host and port for the OPC UA
endpoint.
- Enhanced README with Docker usage instructions.
- Added fluent builder extensions for property access levels.
- Implemented unit tests for new fluent builder functionalities.

# Description

_Describe the changes here to communicate to the maintainers why they
should accept this pull request. By default - this will become the
Commit message after merging and thus define history._

## Related Issues

_Reference all GitHub issues this PR addresses. If there is no issue
yet, open one and link it here._

_If this is a relatively large or complex change, a design must have
been discussed in the related tracking issue and signed off (which
becomes the Architectural Decision Record (ADR))._

- Fixes #github-issue-number, ...

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [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.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] I have added all necessary documentation.
- [ ] 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.

---------

Co-authored-by: Marc Schier <marcschier@hotmail.com>
2026-07-11 21:25:45 +02:00
70f2e0557a Increase Core, Client and Server coverage to 80-85% and fix test-discovered defects (#3980)
# Description

Raises **line coverage to the 80–85% target** for the core stack
libraries by extending existing NUnit fixtures wherever possible and
preferring unit tests over integration tests.

Each library was measured for a per-file coverage baseline, gaps were
ranked by absolute uncovered lines, and tests were added around concrete
behavior rather than type-only or non-null assertions.

| Library | Before | After |
|---------|-------:|------:|
| Opc.Ua.Server | 78.3% | **80.0%** |
| Opc.Ua.Core | 69.5% | **81.1%** (combined with integration suites) |
| Opc.Ua.Types | 79.0% | **80.3%** |
| Opc.Ua.Core.Types | 0% | **82.9%** |
| Opc.Ua.Client | 57.9% | **81.4%** |
| Opc.Ua.Core.Diagnostics | 68.9% | **80.3%** |
| Opc.Ua.Core.Schema | 43.7% | **80.2%** |

Reviewing the added tests exposed several production defects, which are
fixed in this PR:

- Correct registered and unknown view handling in `ViewTable`.
- Set `FolderTypeState` to derive from `BaseObjectType` rather than
itself.
- Preserve `JsonUserDatabase` users, credentials, and roles across
current and legacy JSON round trips.
- Produce semantic, allocation-free diagnostics summaries for generated
request collections and Browse views.
- Discover finite-state-machine availability through the standard
`AvailableStates` and `AvailableTransitions` properties.

The tests were strengthened to validate exact request IDs, index ranges,
signature inputs, typed ECDH payloads, subscription lifecycle behavior,
delegation calls, cached-secret disposal, and deterministic fake-time
reconnect behavior.

### Notes

- Tests use NUnit `Assert.That` and Moq and follow `.editorconfig`.
- The affected projects pass on .NET Framework 4.8 and .NET 10.
`Opc.Ua.Core.Diagnostics.Tests` remains modern-.NET-only by design.
- The three pre-existing `OfflineSecureChannelSmokeTests` token-expiry
failures are unrelated and reproduce without these changes.

## Related Issues

No tracking issue.

## 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.
- [x] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-11 20:43:06 +02:00
Marc SchierandCopilot 7f409657ad OPC UA Part 4 6.6 Redundancy (server + client) with opt-in distributed high-availability (#3918)
## Summary

This branch implements **OPC 10000-4 §6.6 Redundancy** end to end —
server and client, **transparent and non-transparent**, across all
failover modes (`None`, `Cold`, `Warm`, `Hot`, `HotAndMirrored`,
`Transparent`) — and layers an **opt-in distributed high-availability
(HA)** capability on top so replicas can share address-space, session,
and subscription state and expose redundancy to clients through the
documented OPC UA mechanisms (for example, running a server replica set
across nodes in Kubernetes).

The single-instance, in-memory path stays **zero-overhead**: every
distributed feature is opt-in through dependency injection, with a
direct-construction fallback, and replaced APIs are kept and marked
`[Obsolete]`.

## New packages

| Package | Purpose |
| --- | --- |
| `OPCFoundation.NetStandard.Opc.Ua.Server.Redundancy` | §6.6 redundancy
nodes plus opt-in distributed state: shared address space, secure
session sharing, subscription/continuation-point mirroring, and leader
election. |
| `OPCFoundation.NetStandard.Opc.Ua.Server.Redundancy.Crdt` |
Active/active, leaderless multi-writer replication via CRDTs (an
extension beyond §6.6). |
| `OPCFoundation.NetStandard.Opc.Ua.Server.Redundancy.K8s` | Kubernetes
integration: Lease leader election, EndpointSlice peer discovery, and
ServiceLevel-driven readiness. |

Also adds the worked samples `Applications/RedundantServer` and
`Applications/RedundantClient`, and the guides
`Docs/HighAvailability.md` and `Docs/HighAvailabilityKubernetes.md`.

## Server side (§6.6)

- `AddServerRedundancy(...)` populates the live
`Server.ServerRedundancy` nodes (`RedundancySupport` plus
`RedundantServerArray` / `ServerUriArray` / `CurrentServerId`) and
drives `Server.ServiceLevel` from an `IServiceLevelProvider` (leader
high, standby low). `Server.ServerRedundancy` is typed to the
NonTransparent or Transparent subtype according to the configured mode.
- `AddRequestServerStateChange(...)` implements the §6.6.5
manual-failover method, with `EstimatedReturnTime` and the Maintenance /
NoData `ServiceLevel` sub-ranges.
- Non-transparent peers resolve through `FindServers`
(`ConfiguredRedundantServerSetProvider`) and advertise the `NTRS`
discovery capability for GDS/NTRS registration.
- Deterministic, replica-stable `EventId` synchronization for
Transparent and HotAndMirrored sets (§6.6.2.2), excluding per-replica
fields so de-duplicating clients do not double-process events.

## Client side (§6.6)

- `DefaultServerRedundancyHandler` reads `Server.ServerRedundancy` and
`Server.ServiceLevel` and fails over to the highest-ServiceLevel peer.
It honors Maintenance (Part 4 Table 105 — disconnect to an available
peer), keeps Warm backups disabled until failover (Table 107), and
treats peers known only through `ServerUriArray` as failover candidates.
- `RedundantManagedClient` implements the Cold, Warm, Hot (a), Hot (b),
and HotAndMirrored client patterns: one active session, optional
lightweight ServiceLevel status-check sessions to backups, and — for Hot
(b) — merging of concurrent reporting streams with exact value-identity
de-duplication. HotAndMirrored failover re-activates the mirrored
session with the existing `AuthenticationToken` instead of recreating
it.
- Network redundancy: alternate communication paths via endpoint
selection.

## Distributed state (opt-in; an extension beyond §6.6)

- `ISharedKeyValueStore` (in-memory default) and `INodeStateStore`, with
an `AddressSpaceSynchronizer` that bridges a `CustomNodeManager2`'s
predefined nodes to the shared store, so node and reference
additions/removals and value changes propagate to other replicas.
`IDistributedValueCache` lets read/write callbacks cache the last value
with a freshness bound; monitored items use the normal read pipeline and
therefore participate only when the read path does.
- **Secure session sharing (active/passive fast reconnect), server and
client.** `DistributedSessionManager` (wired through a new additive
`ISessionManagerFactory` seam on `StandardServer`) mirrors **encrypted**
session state across replicas. On a failover reconnect the standby
restores the session and performs the **full `ActivateSession`
client-certificate signature validation** against a **single-use**
`serverNonce` consumed via compare-and-swap across the replica set,
enforcing the same SecurityPolicy and SecurityMode. The
`AuthenticationToken` is a **lookup key only**, never an authenticator:
entries are keyed by the SHA-256 digest of the token, and a
cross-replica restore emits a distinct audit event.
- Subscription, retransmission-queue, and continuation-point mirroring
for subscription transfer and failover.
- Leader election: a static single-leader provider and a shared-store
**lease via compare-and-swap**, supporting active/passive and
active/active (shared-read / master-write).
- Fail-closed security defaults: shared records are protected at rest
through `IRecordProtector`; secret-bearing mirrors require a registered
protector or an explicit opt-out.

## Active/active (CRDT)

`Opc.Ua.Server.Redundancy.Crdt` adds leaderless multi-writer
address-space replication with CRDTs and gossip
(`UseReplicatedAddressSpace`) plus CRDT-backed session metadata
(`UseReplicatedSessions`). Networked gossip is **fail-closed without
authenticated transport** (mutual TLS for TCP; an explicit opt-out for
isolated dev/test). CRDT state is eventually consistent, so exactly-once
decisions (such as the single-use nonce registry) stay on a strongly
consistent store. The package is available on all stack target
frameworks.

## Kubernetes

`Opc.Ua.Server.Redundancy.K8s` provides Kubernetes Lease leader
election, EndpointSlice-based peer discovery, and ServiceLevel-driven
readiness for running an OPC UA server replica set.
`Docs/HighAvailabilityKubernetes.md` covers StatefulSet/Deployment and
Service manifests, RBAC, probes, time synchronization, secrets,
gossip-port NetworkPolicy, and GDS/NTRS registration.

## Compatibility and validation

- **Zero-overhead** single-instance default; additive,
backward-compatible seams (for example `ISessionManagerFactory` and
`IServerStartupTask`); replaced APIs marked `[Obsolete]` for migration.
- Builds across the full stack TFM set (`net472`, `net48`,
`netstandard2.0`, `netstandard2.1`, `net8.0`, `net9.0`, `net10.0`);
NativeAOT-compatible where the runtime supports it.
- Unit and integration tests for server, client, subscriptions, session
mirroring, CRDT, and Kubernetes; the conformance suite stays green.

See `Docs/HighAvailability.md` for the full OPC 10000-4 §6.6 mapping,
the `Add*` (standard nodes) versus `Use*` (extension) builder
convention, and the security/rotation guidance.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-11 04:47:25 +02:00
Marc Schier cff6e47596 Fix Fluent NodeManager Configure wiring: typed OnCall for imported methods + runtime value updates (#3973) (#3977)
# Description

Fixes two gaps in the fluent, source-generated NodeManager `Configure`
surface reported in #3973.

## 1. Typed `OnCall` overloads for imported / void-returning methods

The documented form `builder.X.SetOutputVal.OnCall((float v) => …)`
failed to compile with **CS1593 "Delegate 'Action' does not take 1
arguments"**. The Fluent and ObjectType-proxy generators read
`MethodDesign.InputArguments` / `OutputArguments` directly, with no
fallback. For **NodeSet2-imported instance methods**, the arguments live
only on the referenced method declaration (`MethodDeclarationNode` /
`MethodType`), so the generator saw zero inputs and emitted only
`OnCall(System.Action)`.

- Added
`Tools/Opc.Ua.SourceGeneration.Core/Generators/MethodDesignArgumentResolver.cs`
— resolves the effective input/output `Parameter[]` with a fallback to
`MethodDeclarationNode` → `MethodType`.
- Wired it into `FluentBuilderGenerator` and `ObjectTypeProxyGenerator`.
- A method with inputs but no output now binds to `OnCall(Action<TIn…>)`
(and the async `Func<…, CancellationToken, ValueTask>`), with
`Variant.TryGetValue` unpacking.

## 2. Runtime value changes now reach subscribed MonitoredItems

An `OnRead`-backed variable (or a backing field mutated inside an
`OnCall` handler) updated on the Attribute (Read) service but **not**
for subscriptions: the getter isn't sampled, and the old
`Node.ClearChangeMasks(...)` recipe is unreachable because the fluent
builder is sealed after `Configure`.

Added a supported, AOT-safe, DI-friendly runtime surface in
`Libraries/Opc.Ua.Server/Fluent/`:

- **`IValueUpdater<TValue>`** (+ `ValueUpdater<TValue>`) — a runtime
handle captured during `Configure` that survives sealing. `SetValue`
sets Value + StatusCode + Timestamp and flushes `ClearChangeMasks` in
one serialized call; overloads for status/timestamp plus
`NotifyChange()` for in-place mutations.
- **`RuntimeValueBuilderExtensions`** —
- `Bind(out IValueUpdater<TValue>)` for explicit pushes (e.g. from an
`OnCall` handler);
- `PollEvery(interval, getter)` opt-in auto-sampling that pushes only on
change, reusing the manager-owned `SimulationRegistry` loop
infrastructure.
- `FluentVariant` centralizes the single Variant boxing /
AOT-suppression site (shared with `VariableBuilder`).

Usage:

```csharp
private IValueUpdater<float> m_ao01 = null!;

partial void Configure(IMyNodeManagerBuilder builder)
{
    builder.MyEquipment03.AO01.Builder.AsVariable<float>().Bind(out m_ao01);
    builder.MyEquipment03.SetOutputVal.OnCall((float value) => m_ao01.SetValue(value));
}
```

## Documentation

- `Docs/SourceGeneratedNodeManagers.md`: corrected the typed-`OnCall`
overload description (input-only / imported methods) and added a
**"Pushing runtime value changes to subscribers"** section with `Bind` /
`PollEvery` examples.

## Related Issues

- Fixes #3973

## 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.
- [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.

---

### Validation

- `Opc.Ua.Server.Tests` Fluent suite: **232/232** pass on **net10.0**
and **net48** (new `RuntimeValueBuilderExtensionsTests`, 8 cases).
- `Opc.Ua.SourceGeneration.Core.Tests`: **3712** pass on **net10.0** (3
new generator tests + declaration-backed NodeSet2 fixture; new tests
also verified on net48).
- Builds are warning-clean under `TreatWarningsAsErrors`.
2026-07-10 06:49:37 +02:00
13c16a0376 Fix unreachable owner-identity fallback in SamplingGroup.MeetsGroupCriteria (#3971)
# Description

`SamplingGroup.MeetsGroupCriteria` gated its owner-identity fallback on
`context?.SessionId == null`. Because `NodeId` is now a readonly struct,
`OperationContext.SessionId` returns `default(NodeId)` (not `null`) for
a sessionless context, so that condition was always `false`. As a result
the owner-identity comparison was dead code, sessionless groups fell
into the session branch and compared `default(NodeId)` against `null`
(always mismatching), and a null `context` would throw on the final
`context!.DiagnosticsMask` dereference.

Changes:
- **Gate on group state, not context** — the fallback now branches on
`m_session == null` (the group's own sessionless state) and compares
`m_effectiveIdentity` against `savedOwnerIdentity`; otherwise it
compares `context?.SessionId` to `m_session.Id`.
- **Null-context hardening** — the final diagnostics-mask check now
returns `false` for a null `context` instead of throwing.
- **Tests** — added offline unit tests for the sessionless
accept-by-owner-identity path and the mismatched-owner-identity reject
path.

```csharp
if (m_session == null)
{
    // fallback to compare user Identity if session is not set.
    if (!m_effectiveIdentity!.Equals(savedOwnerIdentity))
    {
        return false;
    }
}
// compare session
else if (context?.SessionId != m_session.Id)
{
    return false;
}

// check the diagnostics marks.
return context != null &&
    m_diagnosticsMask == (context.DiagnosticsMask & DiagnosticsMasks.OperationAll);
```

Session-based matching is unaffected.

## Related Issues

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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.
- [ ] 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.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@hotmail.com>
2026-07-09 15:24:11 +02:00
Marc SchierandCopilot Autofix powered by AI 61ba003116 Add integration test for server side complex types (#3961) (#3970)
# Description

Adds a client↔server **integration test** for the server side complex
type system
(`ServerComplexTypeSystem.LoadComplexTypesAsync` / the
`AddComplexTypeSystem` builder extension). Until now this
feature was only covered by mock-based unit tests
(`ServerComplexTypeSystemTests`); there was no end-to-end test
proving a real server that hosts runtime-loaded custom DataTypes (no
compiled .NET backing) can encode/decode them
over the wire.

The new test (`Tests/Opc.Ua.Server.Tests/ComplexTypes/`):

- A `ReferenceServer` subclass hosts a small hand-authored **NodeSet2**
(embedded resource) loaded at runtime by a
  test node manager. It declares custom types with no compiled backing:
- `TestPoint` — a `Structure` (`X:Int32`, `Y:Int32`, `Name:String`) with
a Default Binary encoding node.
  - `TestColor` — an `Enumeration` (Red/Green/Blue).
- The server builds dynamic stand-in encodeables via the default
`OnNodeManagerStartedAsync` path and then
populates the sample variables (the runtime struct value is built
through
  `server.Factory.TryGetEncodeableType(...).CreateInstance()`).
- A real client connects, loads its complex type system, and exercises
the round-trip.

**Assertions:**

1. The server `IEncodeableFactory` registers the runtime `TestPoint`
stand-in (+ its binary encoding id) and the
   `TestColor` enumeration.
2. Server→client read: the client decodes the runtime structure
(`X`/`Y`/`Name`).
3. Enum read round-trips.
4. Client→server write: the server decodes the runtime structure via its
stand-in and returns the updated value.

This is a **test-only** change — no production code is modified.

## Related Issues

- Fixes #3961

## 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.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran the affected tests locally against .net **framework
(net48)** and .net **10**, and all passed (4/4 on each TFM).
- [ ] 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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-09 10:23:43 +02:00
Marc Schier d7cb170b86 CTT compliance (#3960) + HA/Aggregate facets (#3875) + server-side pcap capture (#3962)
## Summary

This PR bundles two independent bodies of work on the branch:

1. **CTT compliance & Historical Access** — improves OPC UA Compliance
Test Tool (CTT) conformance of the Console Reference Server (issue
#3960) and advertises the correct Historical Raw Data + Aggregate server
facets (issue #3875).
2. **Server-side in-process packet capture (pcap)** — lets a hosted
server capture its inbound client→server traffic in-process, with a
documented non-DI wiring path.

The CTT work was driven by an actual CTT run (`NewCTT2`, script
v1.05.513) whose 4757 error results were parsed and clustered by
conformance unit. Fixes are verified live via MCP against the running
`--ctt` server and covered by unit/integration tests that are green on
**net48 + net10**.

## Server-side in-process packet capture (pcap)

Adds the ability to capture a hosted server's **inbound**
(client→server) `opc.tcp` traffic in-process, symmetric to the existing
client-channel capture in `Opc.Ua.Core.Diagnostics`. Previously
`PcapBindings.Install(server.Server.TransportBindings)` only installed a
client *channel* decorator — a server accepts connections through
*listeners*, which never consult channel factories — so nothing was
captured, and `InProcessServerCaptureSource` was a placeholder.
Installing a binding also records nothing on its own until a
`CaptureSessionManager` session is started (which is now documented).

- **Core seam** (`Opc.Ua.Core`, opt-in, zero cost when unused):
`TransportListenerSettings.AcceptedTransportDecorator` +
`OnAcceptedChannel`, applied in `TcpListenerChannel.Attach(socket)` and
the reverse-connect path; `TcpTransportListener` wires them per accepted
channel. Default `null` → no behavior change for non-capturing servers.
- **`PcapTransportListenerBinding`** — an `ITransportListenerFactory`
**listener decorator** (the server-side mirror of the client
`PcapTransportChannelBinding`) that injects the capture seam via
`OpenAsync`, wraps each accepted channel's byte transport in
`CapturingByteTransport`, and forwards channel-token key material to the
active observer.
- **`PcapBindings`** — `Install` now wires **both** client and server;
new `InstallClient` / `InstallServer` helpers. `AddPcap` covers both
sides too. `InProcessServerCaptureSource` is now functional.
- **Non-DI usability** — new public
`ClientChannelManager.DefaultChannelBindings` accessor so non-DI clients
can install the binding into the registry the client actually uses, plus
a new "Enable pcap capture without dependency injection" guide in
`Docs/Diagnostics.md` (server / client / both recipes,
install-before-start timing, the two-registries nuance) and
`NugetREADME` updates.
- **Tests** — Core settings-seam unit tests (net48 + net10),
`PcapTransportListenerBinding` / `PcapBindings` unit tests, and a real
server↔client integration test asserting a non-empty pcap of
server-accepted frames.

## Fixes (CTT compliance / Historical Access)

### Historical Access — Read Raw (~1996 CTT errors)
The new historian returned `Good_MoreData` when handing back a
`ContinuationPoint`. Per OPC UA Part 11 §6.5.3.2 (and the CTT HA Read
Raw tests 008/009, which assert `Good` **and** a `ContinuationPoint`
together) the operation status must be **`Good`** — the non-empty
ContinuationPoint alone signals more data. Fixed all four continuation
sites (raw / modified / annotation / processed-event) in
`HistorianDispatcher`, and additionally report **`Good_NoData`** for a
raw/modified read of an interval that contains no data and requests no
bounds (CTT HA Read Raw 007). Adds regression tests.

### Historical Access — HA Configuration (~282 CTT errors)
Historizing reference-server nodes had no `HasHistoricalConfiguration`
reference, so History Access aggregate clients (and the CTT) could not
discover the archive/aggregate configuration. The reference server now
attaches a populated `HistoricalDataConfigurationType` companion object
("HA Configuration") to every historizing node. This also fixes a latent
bug in `HistoricalDataConfigurationInstaller`: a freshly created
configuration instance (and its mandatory `Stepped` /
`AggregateConfiguration` children) retained the type's shared NodeIds,
so the reference targeted the type node and multiple nodes collided —
the installer now assigns instance NodeIds to the whole subtree via the
node manager's `INodeIdFactory`. Adds a regression test.

### Historical Access — Read Raw status codes
Additional CTT HA Read Raw conformance fixes:
- Insufficient details (<2 of startTime/endTime/numValuesPerNode) →
`Bad_HistoryOperationInvalid` (Err-027).
- IndexRange on scalar history → `Bad_IndexRangeNoData` at the operation
level (Err-009).
- Unknown/foreign ContinuationPoint (e.g. a Browse CP) →
`Bad_ContinuationPointInvalid` (Err-014/Err-024).
- No spurious ContinuationPoint on the final page of a
one-value-at-a-time read (008/009).
- `TimestampsToReturn.Neither` → `Bad_TimestampsToReturnInvalid`
(Err-002).

All validated against the actual CTT run (`NewCTT2` Run2, an HA-only
re-run built with the base fixes) which confirms the 1999 "Good_MoreData
vs Good" failures are gone. Covered by tests.

### Earlier commits in this PR
Variant/BaseDataType array write `BadTypeMismatch`, DiagnosticInfos
gating on `DiagnosticsMask`, multi-dimensional array values;
strengthened Attribute Write conformance tests; Condition comment
retention on null Acknowledge/Confirm; historized the full scalar type
set + paired aggregate nodes; populated
`ServerCapabilities.ConformanceUnits`; advertised
`HistoricalRawData2022` + `AggregateHistorical2022` facets (#3875) with
`Docs/Profiles.md` updates.

## Diagnosed (not server bugs — CTT connection configuration)

### Auditing Connections (~1684 CTT errors)
All 1684 failures are identical ("Unable to Find Entry for
ClientAuditEntryId"). Audit events are, per spec, delivered only to
sessions holding the `SecurityAdmin` role: the server validates
`PermissionType.ReceiveEvents` per event-monitored-item, and the
integration test `AuditingOperationTests` connects as `sysadmin`/`demo`
for exactly this reason. The CTT run collected zero audit events because
it connected **anonymously**. Documented in `Docs/Diagnostics.md`:
configure the CTT to authenticate as `sysadmin`/`demo` (mapped to
`[SecurityAdmin, AuthenticatedUser]` in `ReferenceServer.cs`).

### Address Space WriteMask (~126 CTT errors)
`revertToOriginalNodeAttributes` writes back all node attributes and
gets `BadUserAccessDenied` (a permission error) rather than
Good/BadNotWritable/BadAttributeIdInvalid — the same
anonymous-connection cause; expected to clear when the CTT connects as
`sysadmin`/`demo`.

## Still open
Alarms & Conditions Retain/ConditionDisabled/InputNode (logic is
spec-aligned on inspection; needs a CTT-faithful reproduction to
separate a genuine bug from alarm-simulation timing), Aggregate function
values (now unblocked by the HA Configuration fix), GDS
self-registration, residual Attribute IndexRange/type-mismatch.
CTT-script errors (`ArrayItems` / `ItemsHistoryCountExceeds` undefined)
are not server-fixable.

## Testing
`Opc.Ua.Server.Tests` (Historian) and `Opc.Ua.History.Tests` green on
net48 + net10; historian and HA-configuration behaviour verified live
against the running `--ctt` server. Server-side pcap capture: Core
builds clean on net48 + net10, and the new unit + server↔client
integration tests pass (the only failing `Opc.Ua.Core.Diagnostics.Tests`
are 3 pre-existing, unrelated `OfflineSecureChannelSmokeTests`). The
single most decisive remaining CTT step is a re-run authenticated as
`sysadmin`/`demo`, which clears the permission-based clusters and
isolates the true remaining server bugs.
2026-07-09 09:08:27 +02:00
b26be47a48 Increase Opc.Ua.Server library unit-test coverage (#3967)
## Summary

Adds focused, deterministic, offline unit tests that raise coverage
across the **Opc.Ua.Server** library. All changes are test-only — no
production code is modified. The PR diff touches only files under
`Tests/`.

## Areas covered

**NodeManager**
- `ContinuationPoint`, `EventManager`
- `CustomNodeManager2` / `AsyncCustomNodeManager` (incl. `CallAsync`)
- `SamplingGroup`, `SamplingGroupManager`,
`SamplingGroupMonitoredItemManager`
- Shared `DeterministicServerMock` helper for constructing node managers
offline

**Configuration / server**
- `TrustList`
- `ResourceManager`, `ModellingRulesManager`,
`DataTypeSchemaRegistrationExtensions`, `OpcUaServerEventSource`
- `ReverseConnectServer`, `StandardServer`

**Sessions / identity**
- `Session` and `SessionManager` (targeted at the previously-uncovered
lines, ~90%)
- `UserManagementBinding`, `ServerIdentityRegistryExtensions`

**FileSystem** (all files now ≥80% line coverage)
- `PhysicalFileSystemProvider`, `FileSystemNodeId`, `FileHandle`,
`FileSystemNodeManager` / `FileSystemNodeManagerFactory`
- `FileObjectState` (92.6%) and `DirectoryObjectState` (85.7%) — drive
the FileType / FileDirectoryType method handlers, metadata property read
hooks, browser population and exception branches by invoking the state
`OnCall` / `OnCallAsync` delegates against a physical provider rooted at
a temp directory

**Historian** (all files ≥80%)
- Installer, ProviderBase, EventFilterTarget, Dispatcher, Registry,
InMemoryProvider, CaptureSink

## Notes
- Tests use NUnit constraint-model asserts (`Assert.That`), minimal Moq,
no reflection.
- Built and run against `net8.0`; `dotnet format --verify-no-changes` is
clean on the new files.
- A separate issue was filed for the SamplingGroup behaviour discovered
while writing these tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-08 16:24:19 +02:00
Marc Schier 8899af69b5 GH Actions pipelines update, nightly + mac gating, diagnostic dumps (#3881)
# Description

CI-only PR (no shipped-code change). Modernises the AzDO + GitHub
Actions pipelines, adds a preview-package workflow that publishes to
GitHub Packages, adds a nightly full-scope run, tightens macOS gating,
and improves testhost diagnostics.

## Summary

### Azure Pipelines

1. **`managedPool` parameter** — Optional routing of Windows/Linux jobs
to the `netstandard` Managed DevOps Pool (`ImageOverride` demands).
Default `false`; operators can flip per queue.
2. **Per-TFM build matrix** — `sln.yml` fans out into one job per (TFM ×
configuration) for the full-scope build so each matrix leaf fits on
small-disk pool agents.
3. **Cancel-aware `condition:` forwarding in `test.yml`** — parameters
wrapped in `and(succeeded(), <forwarded>)` so user-cancel propagates to
jobs.
4. **Decoupled test / result-publish steps** — `DotNetCoreCLI@2 command:
test` runs with `publishTestResults: false` + explicit `--logger trx`; a
separate `PublishTestResults@2` runs `succeededOrFailed() +
continueOnError: true`. Prevents transient DNS blips against
`opcfoundation.visualstudio.com` from flipping green runs to failed.
5. **Blame + dump collection** — `dotnet test` uses
`--blame-hang-timeout 10m --blame-hang-dump-type mini` so silent hangs
become named test failures with a mini-dump attachment. Fuzz-artifact
download/extract/upload steps gated to `Fuzz.Tests` jobs only.
6. **Diagnostic dumps published as build artifacts** — new
`Stage/Publish diagnostic dumps` steps collect `*.dmp`, `*.mdmp`,
`*.dump`, Linux `core`/`core.*` from `$(Agent.TempDirectory)` on every
finished job (`succeededOrFailed()`), keyed by `System.JobId` so
parallel matrix entries never collide. Root-caused from a run where the
previous `failed()` gate silently skipped because the test task itself
was reported succeeded despite testhost issues.
7. **Weekly full-scope run** — new `schedules:` block firing Sundays
02:00 UTC (`always: true`, master). All stages already gated on
`ScheduledBuild` (`Debug`, `.NET 9/8/472/netstandard`, `Coverage`,
`AOT`, macOS matrix) participate automatically.
8. **macOS gating** — mac net10 job added back to `testfastpr` gated on
`ne(ScheduledBuild, 'False')` so mac runs nightly/manual only (not on
PR/master push). Stripped 4 stale `IsDraft` per-job conditions in
already-`ScheduledBuild`-gated stages (`testdebug`, `testnet90`,
`testnet80`, `testnetstandard21`).
9. **Preview solution generation** — deleted the hand-maintained `UA
Core Library.slnx` (and dead `MonoReferenceServer.csproj`); added
`.azurepipelines/generate-slnx.ps1` that derives `preview-pack.slnx`
from `UA.slnx` by stripping `/Tests/`, `/Fuzzing/`, `/Solution Items/`.
`preview.yml` uses the generated solution.

### GitHub Actions

10. **New `.github/workflows/preview-publish.yml`** — mirrors
`.azurepipelines/preview.yml` end-to-end (versioning via existing
`set-version.ps1`, strong-name + Authenticode + nupkg signing, modern +
legacy nuspec packaging) and publishes to **GitHub Packages**
(`https://nuget.pkg.github.com/OPCFoundation/index.json`) with
`--skip-duplicate`. Triggers: `workflow_dispatch`, push to `master`
(Preview version), push to `v*` tags (Public version). Every signing
step gracefully skips when the corresponding secret is missing
(fork-friendly). Requires `permissions: packages: write`.
11. **Reshuffled `buildandtest.yml` matrix** — GH Actions covers Linux +
macOS (net10 only); Windows is fully covered by AzDO. AoT job expanded
to `ubuntu-latest` (`linux-x64`), `macos-13` (`osx-x64`), `macos-latest`
(`osx-arm64`) with a single RID-aware run step. Added `schedule` (Sun
02:00 UTC) + `workflow_dispatch(include_macos)` triggers. The `discover`
job computes `test_os` + `aot_matrix` dynamically based on event type:
macOS is **off** by default on `pull_request` and **on** by default for
push/schedule/workflow_dispatch (respecting the input).

### Tests

12. **`ReferenceNodeManager.DoSimulation`** — added `catch
(ObjectDisposedException) when (m_simulationTimer is null)` to swallow
the expected teardown race between the simulation timer callback and
`Dispose`.
13. **`AlarmsAndConditions*` test fixture** — added
`DefaultEventWaitTimeout = TimeSpan.FromSeconds(30)` and migrated 22
call sites off the previous 5 s poll; `AcknowledgeBranchAsync` +
`ConfirmBranchAsync` marked `[Retry(2)]` to absorb a rare macOS
`BadSecureChannelClosed` race.

## Related issues

Follow-up to #3872.
2026-07-08 08:31:43 +02:00
Marc Schier 00377a34ab Expand DI / Fluent API: injectability + one-shot ergonomics (client, server, PubSub, discovery/bindings) (#3957)
# Description

Completes a dependency-injection / fluent-API surface review follow-up
so that **every feature is reachable and configurable from the
`services.AddOpcUa()` chain**, while keeping all existing granular
registrations and direct-construct (`new`) fallbacks. The change is
backward compatible — the single rename (`WithAuthorizationService` →
`AddAuthorizationService`) keeps an `[Obsolete]` alias.

**Client** — chainable `IOpcUaClientBuilder` overloads of
`AddComplexTypes` / `AddAlarms` / `AddWebApiTransportChannel`;
`ManagedSessionOptions.LoadComplexTypes` + `AddManagedClient()`
one-shot; injectable multi-endpoint `IManagedSessionFactory`; DI
factories `AddHistorian` / `AddRoleManagement` / `AddFileTransfer` /
`AddAliasNames`; fail-fast options validation + `AddDiscovery()`.

**Server** — `IOpcUaServerFactory` / `AddServer<TServer>()` seam (the
hosted service no longer hardcodes `new StandardServer`) unlocking
injectable durable subscriptions and session/subscription managers; plus
`AddHistorian`, `AddFileSystem` (Part 20), `AddSecretStore` /
`AddCertificateManager` / `AddAliasNameStore`; secure-by-default
anonymous authenticator (marker-guarded, no double-registration).

**PubSub** — `AddUdpPubSub()` / `AddMqttPubSub()` one-shots;
`IPubSubBuilder` anchor unification
(`AddSecurityKeyServiceClient/Server/PushTarget`, `AddSchema`,
`AddPcapCapture`); `ConfigureConfiguration()` config-authoring bridge;
`IMqttTransportBuilder` parity with UDP/Eth; order-independent pcap
decoration.

**Discovery / Bindings** — LDS/GDS non-TCP endpoints fail loudly instead
of silently; GDS `AddInMemoryStores()` + fast-fail when stores are
missing; order-independent one-shot `AddHttpsTransport()` (Kestrel +
HTTPS/WSS + optional WebApi/auth); `AddAuthorizationService`
(`[Obsolete]` alias for `WithAuthorizationService`); root-level
`IOpcUaBuilder.AddPcap()`; WotCon-server-without-`AddServer` startup
guard.

Docs updated: `Docs/DependencyInjection.md`, `Docs/PubSub.md`,
`Docs/Transports.md`, `Docs/WebApi.md`.

Validation performed locally: the 12 changed libraries and 9 affected
test projects build clean (0 warnings / 0 errors) on **net10.0 and
net48**; the full net10.0 sweep of the affected suites passes (~7,700
tests — Server 1920, GDS 904, LDS 149, Client 1577, ComplexTypes 2733,
PubSub 1141 + Diagnostics 53, WotCon 344, HTTPS.WebApi 319); the net48
new-feature suites pass (Client 123, Server 43, PubSub 49, GDS 21, LDS
10, WotCon 8, ComplexTypes 7).

## Related Issues

Follow-up to an internal DI / Fluent-API surface review (no single
tracking issue). Happy to open a tracking issue / ADR if maintainers
prefer before merge.

- Fixes # _(n/a — review follow-up)_

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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. _(Ran the
affected areas comprehensively on net10.0 + net48; a full UA.slnx run
across both TFMs is still pending.)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
2026-07-08 07:03:18 +02:00
Copilot b15d6e7dbc Fix false-positive MODELGEN010 for mixed NodeSet2 + ModelDesign [NodeManager] binding (#3968)
Fixes the false-positive MODELGEN010 ("did not match any model") that blocked builds
when a project combined a NodeSet2 types file (with a [NodeManager] bound to its URI)
and a ModelDesign instances file.

[NodeManager] binding resolution now aggregates a shared "used" set and a global model
count across the NodeSet2 and ModelDesign generation passes, reporting unmatched or
ambiguous bindings once (via Generators.ReportUnmatchedNodeManagerBindings) instead of
per pass. Also corrects the single-model fallback / ambiguity detection to use the
global model count.

Includes end-to-end and core regression tests plus docs fixes (the MODELGEN010
diagnostics entry, and mixed-project [NodeManager] / Prefix-Name clarifications).

Addresses #3937.
2026-07-08 03:02:44 +02:00
Marc Schier 533ed1c358 Fix source generator dropping BaseAnalogType EURange/EngineeringUnits on instances (#3964) (#3966)
# Description

The model source generator (`ModelSourceGeneratorGenerateNodeManager`)
split an instance's child nodes into an **always-emitted** list and an
`if (!forInstance)` **type-template** list. Explicitly-declared
descendants of a *concrete predefined instance* whose modelling rule
only resolved to *Optional* — for example the `EURange` /
`EngineeringUnits` properties of a `BaseAnalogType` / `AnalogUnitType`
variable nested under an instance — were placed in the gated list.
Because the per-instance factory is always invoked with `forInstance:
true`, those property children were **never materialized** on the actual
instance. The pre-existing "top-level instance" exception in
`NodeStateGenerator.LoadTemplate_ReplaceChild` only covered an
instance's *direct* children, not deeper descendants (e.g. `EURange`
under `AI01` under `MyEquipment03`).

Both symptoms reported in the issue stem from this single bug:

1. **Instances were missing `Range` / `EU`** — the property nodes were
never created.
2. **`WithEURange` / `WithEngineeringUnits` (in `Configure`) appeared to
do nothing** — with the property nodes missing, the fluent helpers
created *unregistered* replacement nodes whose values were never served
to clients.

## Fix

`NodeStateGenerator.LoadTemplate_ReplaceChild` now also emits
**explicitly-declared descendants of any concrete predefined instance**
(`IsUnderSingletonInstance`, not a type-definition/type-instance
context, explicitly defined) into the always-emitted list. The new
condition is gated on `!UseTypeDefinitionModellingRules`, so SDK-managed
singletons (`Server`, `ServerConfiguration`, …) — generated with that
option **on** — keep the deliberate `#3768` optional-child suppression.
The core stack (generated with the option on) is therefore unaffected;
the change only corrects user models generated with the option off.

## Verification

- **End-to-end**: built a server from the exact NodeSet2 attached to the
issue and browsed/read it with a real OPC UA client.
`MyEquipment03.AI01` / `AO01` now expose `EURange` (`-20..200`) and
`EngineeringUnits`. With the reporter's `Configure` present the values
become `-20..200` / `Kelvin` (`AO01` reads `10`); with no `Configure`
the nodeset-declared values (`°C`, `-20..200`) materialize — confirming
both reported problems are resolved.
- **Regression test** added
(`ConcreteInstanceAnalogChildren_AreMaterializedUnconditionally`) with a
self-contained `AnalogInstance.NodeSet2.xml` fixture. It passes with the
fix and fails without it.
- **No regressions**: full `Opc.Ua.SourceGeneration.Core.Tests` suite
(3702 passed / 8 analyzer-gated skips, including the `#3768`
singleton-dispatch and stack-generation tests), `Opc.Ua.WotCon.Tests`
(342 passed), and clean builds of the DI / GDS / WotCon / Pumps / Boiler
/ Quickstarts model-consuming projects.

## Related Issues

- Fixes #3964
2026-07-07 16:03:42 +02:00
Marc Schier 44560fd3aa Implement Apache Kafka transport for PubSub (Part 14 Annex B.2) with HA (#3942) (#3949)
Implements **#3942** (Apache Kafka transport for PubSub, OPC UA Part 14
Annex B.2) with HA publishers & subscribers (**#3924**, Part 14 §9.1.6).

## Kafka transport (Part 14 Annex B.2)
New `Opc.Ua.PubSub.Kafka` library mirroring the MQTT broker transport
and the existing `IPubSubTransport`/`IPubSubTransportFactory` +
injectable-adapter pattern:
- Profiles `pubsub-kafka-uadp` / `pubsub-kafka-json` (library-local
`KafkaProfiles`); `kafka://` / `kafkas://` addresses.
- Reuses the existing `Broker*TransportDataType` types (`QueueName` =
topic, `MetaDataQueueName` = metadata topic,
`ResourceUri`/`AuthenticationProfileUri` = auth,
`RequestedDeliveryGuarantee` = QoS) — no new DataTypes, matching the
spec.
- Normative `content-type` record header (`application/opcua+uadp` /
`application/json`); QoS → producer acks/idempotence; SASL/TLS auth with
passwords resolved via `ISecretRegistry`.
- `AddKafkaTransport()` DI (JSON + UADP factories),
`KafkaConnectionOptions` (bootstrap servers, GroupId, security, delivery
guarantee, offset policy).

### Dual-source client by TFM (AOT + broad TFM support)
Via the `IKafkaClientAdapter` seam:
- **net10.0 → Dekaf** (pure-managed, no native dependency) ⇒ the library
is `IsAotCompatible` and Kafka runs in the **NativeAOT-published**
sample directly.
- **net472 / net48 / netstandard2.1 / net8.0 / net9.0 →
Confluent.Kafka** (mature, native librdkafka).

**NativeAOT publish of `ConsoleReferencePubSubClient` (win-x64) was
validated end-to-end**: a 26.9 MB native binary with the Kafka/Dekaf
transport and **zero IL2xxx/IL3xxx trim/AOT warnings**.

## Field promotion to broker headers (builds on #3956)

Now that the PubSub transcoders (#3956) are on `master`,
`KafkaBrokerTransport` implements the `IPubSubHeaderTransport`
capability: transcoded DataSet fields selected via `PromoteFields(...)`
(fluent) or the `PromoteFields` / `PromotedFieldPrefix` route options
are surfaced as **Kafka record headers** — the broker-layer analogue of
the Part 14 PromotedFields concept, with *copy* semantics (the fields
remain in the encoded payload). The normative `content-type` header is
unaffected and values are invariant-formatted strings. This reuses the
existing `KafkaMessage.Headers` channel (already emitted by both the
Dekaf and Confluent adapters), so only the transport gained the
capability. Covered by `KafkaHeaderTransportTests` (net10.0 Dekaf +
net48 Confluent) and documented in `Docs/PubSub.md` under "Promoting
fields to broker headers".

## High availability (#3924, §9.1.6)
Transport-agnostic, additive, and **behaviour-neutral by default**:
- `IPubSubActivationCoordinator` (per-component Active/Standby +
`RoleChanged`) with the default no-op `AlwaysActiveCoordinator`.
- `IPubSubLeaseStore` + `InMemoryPubSubLeaseStore` (monotonic fencing
tokens) and `LeaseActivationCoordinator` (leader election with
per-component renew/acquire loops).
- Runtime wiring: `PubSubApplication` starts/stops the coordinator with
the app; `WriterGroup`/`ReaderGroup` gate at the enable/pause boundary —
Standby pauses publishing/dispatch, Active resumes. No-op under the
default coordinator, so existing behaviour is unchanged.
- Kafka HA: consumer `GroupId` drives Kafka-native subscriber failover;
`AtLeastOnce`/`ExactlyOnce` enable idempotent producing for
active-publisher handover.
- DI: `WithActivationCoordinator(...)` / `WithLeaseStore(...)`.

## Samples & docs
Kafka profiles (`KafkaUadp`/`KafkaJson`) added to the publisher &
subscriber samples; new `Docs/PubSubKafka.md` (config, DI, auth, QoS,
AOT note) linked from `Docs/README.md` + the `Docs/PubSub.md` transport
table.

## Tests & validation
- `Opc.Ua.PubSub.Kafka.Tests`: 82 tests, **80.28% line coverage** on the
Kafka source, using a `FakeKafkaClientAdapter` over an in-memory topic
bus. A **Testcontainers.Kafka** integration test runs a real
produce→consume round-trip and `Assert.Ignore`-skips when Docker is
unavailable.
- HA failover tests (`PubSubActivationFailoverTests`): leader election +
failover, fencing-token monotonicity, writer/reader standby gating
(deterministic, `FakeTimeProvider`).
- `Opc.Ua.PubSub.Tests`: **1134 passed / 0 failed** (1130 existing + 4
new).
- Full `UA.slnx` net10 build: 0 errors; the only warnings are
pre-existing CA warnings in unrelated test projects (new code is
0-warning). net48 (Confluent path) validated for the transport + test
project.

### Dependencies (approved)
- `Dekaf` (MIT, pure-managed, net10-only) and `Confluent.Kafka` 2.15.0
(Apache-2.0) + `librdkafka.redist` (BSD-2).
- Test-only `Testcontainers.Kafka` 4.13.0 (MIT).
- Dekaf's net10 package required bumping the `Microsoft.Extensions.*` /
`System.*` 10.0.8 servicing family to 10.0.9.

Closes #3942
2026-07-07 15:41:55 +02:00
JonasandMarc Schier 76aa8377b4 MCP Server Stdio transport: configure all logs to go to stderr (#3959)
# Description

In the Stdio transport mode of the MCP server, logging is redirected to
stderr in order to avoid deserialization exceptions when calling tools

## Related Issues

- Fixes #3958

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [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.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] 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.

---------

Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
2026-07-07 11:14:13 +02:00
Marc Schier ec6a5e432f Server session scalability: admission control, retry-after signaling, and held-Publish decoupling (#3946)
# Description

Server session scalability work: the reference server degrades
gracefully under connect storms, holds many thousands of steady-state
long-polls on a small worker pool, and gives clients
diagnostics-independent backpressure signals so they ramp instead of
hammering. This combines three related efforts — and merges in #3950 —
all follow-ups to the scalability analysis in #3941
(`Docs/ServerScalability.md`).

Server admission control / rate limiting is **enabled by default** with
conservative limits (it sheds excess load with a fast `BadServerTooBusy`
rather than altering steady-state behavior), and the held-Publish
decoupling is on by default; the HTTPS rate limiter and the client-side
connect gate are opt-in. Nothing here changes the shipped
`MaxSessionCount` (100).

## 1. Admission control / rate limiting

- **TCP listener admission** — a configurable token-bucket connection
gate (socket backlog default 512, raised from a hard-coded 10) sheds
over-limit connections cheaply instead of accepting and starving them.
- **Session-establishment admission** — `CreateSession` /
`ActivateSession` acquire from a concurrency limiter; over-limit
requests fault fast with **`BadServerTooBusy`** before the CPU-bound
certificate validation / signing, instead of queueing behind the
address-space lock.
- **CreateSession RSA out of the lock** — the server-nonce signature
runs outside the session semaphore, so a slow signature no longer
serializes unrelated session establishment.
- **HTTPS/Kestrel** — `AddHttpsRateLimiter(...)` attaches an ASP.NET
Core rate limiter to the HTTPS binding via dependency injection.
- Built on `System.Threading.RateLimiting`, DI-injectable
(`IServerRateLimiterProvider`, `ConfigureRateLimits(...)`) with a
direct-construct fallback.

## 2. Retry-after signaling (diagnostics-independent)

A `BadServerTooBusy` fault's diagnostic `AdditionalInfo` only reaches
the client when it requests diagnostics, and is dropped on client
exception re-wrapping. This delivers the retry-after through carriers
that do **not** depend on diagnostics, all feeding the client's adaptive
reconnect policy:

- **`ResponseHeader.additionalHeader`** (all UA transports) — a
structured `RetryAfterMs` (`Int64`) in the standard
`AdditionalParametersType`, attached to the fault by
`ServerBusyException` + `EndpointBase.CreateFault` (`RetryAfterHeader`)
and read by `ClientBase.ValidateResponse`.
- **HTTP `Retry-After`** (HTTPS) — the rate limiter sets the header on
its 429; the client maps a 429/503 with `Retry-After` to
`BadServerTooBusy`.
- **UA-TCP `Error` reason** — the client honors a `RetryAfterMs=N` token
in a transient server-busy ERR message as a lower bound on
channel-reconnect backoff (`RetryAfterHint`).
- **Client foundation** — the hint survives the diagnostics gate and
exception re-wrapping and reaches `IReconnectPolicy.TryGetNextDelay`
(server-signal-aware adaptive backoff); a client-wide connect admission
gate (`WithConnectRateLimiter`) ramps bulk connects.
- **Load-based `Server.ServiceLevel`** — computed from
session-establishment headroom (255 at low load, scaling toward a floor
as sessions approach `MaxSessionCount`, with hysteresis) as a proactive
capacity signal clients can read/subscribe.
- Implementation summary folded into `Docs/Sessions.md` (Server
retry-after backpressure section); the OPC UA spec-change proposal is
filed as an OPC Foundation Mantis issue.

## 3. Held long-poll Publish decoupling 

A parked `Publish` (waiting for the next subscription notification) no
longer occupies a `MaxRequestThreadCount` worker slot for the whole
wait, so a small fixed worker pool can hold many thousands of
outstanding Publishes and `MaxRequestThreadCount` no longer has to scale
with session count.

- **`IRequestParkSink`** + a one-shot `RequestLifetime.ParkSink`;
`ServerBase.RequestQueue` awaits `Task.WhenAny(processing, ParkedTask)`
and releases the active-worker slot at the **park point** rather than at
completion. Only `Publish` carries a sink — every other request keeps
the byte-for-byte legacy fast path (no extra allocation, no `WhenAny`).
- **`DecoupleHeldPublishRequests`** (default `true`; set `false` to
restore the legacy inline-await worker path).

## Docs

`Docs/ServerScalability.md` (rewritten as user-facing scalability
documentation), `Docs/RateLimiting.md`, `Docs/Sessions.md`.

## Tests

Server admission (`SessionAdmissionRateLimitTests`); retry-after
carriers (`RetryAfterHeaderTests`, `RetryAfterHintTests`,
`EndpointBaseTests`, `ClientBaseTests`, `HttpsTransportChannelTests`,
`AdaptiveReconnectPolicyTests`, `ConnectionStateMachineTests`);
load-based ServiceLevel (`ServerServiceLevelCalculatorTests`);
held-Publish decoupling (`RequestQueueTests`,
`SessionPublishQueueTests`); AOT coverage (`RateLimitingAotTests`).
Changed libraries build clean on **net48** and **net10.0**.
2026-07-07 08:26:42 +02:00
Marc Schier 5204906316 Add high-performance PubSub transcoders (subscriber-to-publisher) (#3956)
# Description

Adds **high-performance PubSub transcoders** to the `Opc.Ua.PubSub`
library (new `Opc.Ua.PubSub.Transcoding` namespace) that take
**subscriber-received NetworkMessages** and transcode them for
**re-publishing on a publisher connection**, supporting **all
transformations**.

The transcoder operates on the decoded `PubSubNetworkMessage`
intermediate representation — the seam between the pluggable
`INetworkMessageDecoder`/`INetworkMessageEncoder` — so cross-encoding
and structural transforms happen without a full dataset re-sampling
round-trip. Pipeline per message: `decode(+unwrap) → transforms →
project-to-target-profile → encode(+wrap)`, with a **raw-frame zero-copy
passthrough fast path** for identity same-encoding routes.

**What's included**

- **Engine:** `PubSubTranscoder` (frame-level `ITranscoder`) +
`NetworkMessageTranscoder` (structured primitive),
`TranscodeSpec`/`TranscodeInput`/`TranscodeResult`/`TranscodeContext`.
- **Cross-encoding:** `NetworkMessageProfileProjector` maps UADP↔JSON
(all four source/target combinations), translating content masks,
DataSetMessage subtypes, identifiers, timestamps, and metadata.
- **8 built-in transforms:** identifier remap
(PublisherId/WriterGroupId/DataSetWriterId/DataSetClassId),
field-encoding conversion (Variant/RawData/DataValue), field projection
(select/drop/reorder), field rename, per-field value transform,
message-type filtering (KeyFrame/DeltaFrame/Event/KeepAlive), metadata
rewrite, and a delegate escape hatch.
- **Transcoder-managed security:** `TranscodeSecurity` re-secures via
the existing UADP `UadpSecurityWrapper`; an `AllowInsecureCrossEncoding`
policy (default off) refuses silent security downgrades, since
message-layer security in Part 14 is UADP-only (secured→JSON relies on
transport-layer security).
- **In-process bridge:** an opt-in `IReceivedNetworkMessageSink` receive
hook on `PubSubConnection`, `PubSubTranscodingBridge`, and
`ConnectionTranscodeEgress` (chunk/`MaxNetworkMessageSize`-aware send).
- **Fluent / DI:** `IPubSubBuilder.AddTranscodingBridge(...)` +
`PubSubTranscoderBuilder`; the bridge is started as a hosted service
after the PubSub application and resolves source/target connections by
name.

**Design notes**

- Additive only — no public API is obsoleted and no behavior changes
when the feature is unused (the receive-loop observer is null-guarded).
One **private** method (`PubSubConnection.SendChunkedAsync`) was
refactored to be identifier-based so the egress can reuse chunking; its
reflection-based unit test was updated accordingly.
- Reuses `UadpEncoder.EncodeWithSecurityBoundary` and
`UadpDecoder.TryReadOuterPrefix` rather than duplicating the fragile
UADP security-splitting logic.
- New public API follows repo conventions (`ArrayOf<T>`, `Variant`,
spans/`ByteString`, sealed-by-default, async-only, AOT-clean, license
headers, `.editorconfig`).

**Testing & docs**

- 33 unit tests (each transform, projector for all four combos, engine
fast-path/round-trip/drop/no-encoder, security downgrade policy, fluent
builder) + 4 end-to-end integration tests driving a real
`PubSubConnection` receive path through the bridge and egress.
- `TranscodingBenchmarks` (UADP↔JSON, identity fast path, projection).
- New `Docs/PubSubTranscoding.md` with links from `Docs/README.md` and a
Transcoding section in `Docs/PubSub.md`.

Verified locally: the `Opc.Ua.PubSub` library builds **0 warnings/errors
on all target frameworks**
(net472/net48/netstandard2.1/net8.0/net9.0/net10.0); the transcoding
tests pass on **net10.0 and net48**; the full `Opc.Ua.PubSub.Tests`
suite passes (**1167 passed, 1 skipped, 0 failed**) with no regressions.

## Related Issues

_No tracking issue yet — new feature. Happy to open one to host the ADR
discussion if preferred._

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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. _(Ran the
full `Opc.Ua.PubSub.Tests` suite on net10.0 and the transcoding tests on
net48; full-solution run pending.)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.



## Follow-up: review feedback

Two maintainer requests were addressed on top of the original transcoder
work:

- **Reloadable configuration** — transcoding routes can be declared in
configuration and reloaded at runtime, mirroring the
`Opc.Ua.PubSub.Adapter` package.
`IPubSubBuilder.AddTranscoding(IConfiguration)` binds a
`PubSubTranscodingOptions` (a `Routes` array) with AOT-safe manual
binding, and `PubSubTranscodingReloadCoordinator` watches an
`IOptionsMonitor` and reconfigures **only** the added/removed/changed
routes (diffed by name + content signature) while unchanged routes keep
running. The fluent `AddTranscodingBridge` API remains available;
configuration covers the declarative transforms (delegate transforms
stay fluent-only).
- **MQTT field promotion** — selected DataSet fields can be promoted
into MQTT 5.0 User Properties (the broker-layer analogue of Part 14
PromotedFields, copy semantics). New core capability interface
`IPubSubHeaderTransport` + `PubSubMessageProperty`, implemented by
`MqttBrokerTransport`; configure via fluent
`PromoteFields(...)`/`WithPromotedFieldPrefix(...)` or the equivalent
route options. Non-header transports ignore promoted properties, and on
MQTT 3.1.1 they are silently dropped.

Both are covered by unit + integration tests and documented under
`Docs/PubSub.md` → Transcoding.
2026-07-06 14:27:23 +02:00
Marc Schier 7149ab52e4 Server-side complex types via the shared native-AOT path (#1306) (#3955)
# Description

Enable an OPC UA **server** to build dynamic stand-in encodeables for
the custom DataTypes it loaded from a NodeSet at **runtime**, so a
`Variant` / `ExtensionObject` carrying a custom type "just works"
without a compiled .NET type. The server reuses **exactly the same**
NativeAOT-friendly path as the client (`ComplexTypeSystem`) rather than
a parallel implementation, and does not touch the legacy reflection-emit
builder.

## What changed

**Shared machinery moved to a common assembly**

- Moved `ComplexTypeSystem`, the resolver/factory interfaces, the
`DefaultComplexType*` builder and `DataDictionary` from `Opc.Ua.Client`
to **`Opc.Ua.Core.Schema`** (namespace `Opc.Ua.Schema`) so both client
and server use it (git history preserved via renames).
- The client keeps `NodeCacheResolver` and gains
`ComplexTypeSystem.Create(session, …)` helpers; the `ISession`
constructors are replaced by those helpers (the reflection-emit builder
is opt-in via `Create(session, new ComplexTypeBuilderFactory(),
telemetry)`).

**Server-side complex types**

- `AddressSpaceComplexTypeResolver` reads the server's in-memory address
space (DataType nodes + `DataTypeDefinition`).
- Opt-in fluent/DI `AddComplexTypeSystem()`, a ready-made
`ComplexTypeStandardServer`, and
`IServerInternal.LoadComplexTypesAsync(…)`. A new async
`StandardServer.OnNodeManagerStartedAsync` hook runs the pass after the
address space is built and **before endpoints open**. DataTypes already
backed by a compiled/generated type are skipped; only runtime-loaded
ones become stand-ins.

**Encodeable factory as the source of truth for data type definitions**

- The runtime stand-ins (`Structure` / `Enumeration` / `OptionSet`) now
implement `IDataTypeDefinitionSource`, so
`EncodeableFactoryDefinitionSource` resolves generated **and** runtime
types. The server exposes the primed factory as the schema
`IDataTypeDefinitionResolver`; `DataTypeDefinitionRegistry` is kept only
as an optional supplement for schema-only types (composed via
`CompositeDataTypeDefinitionResolver`). No address-space walk or
separate registry population is required.

## Tests

- New `ServerComplexTypeSystemTests` (factory priming by type + encoding
id, skip-known, factory-backed resolver + composite fallthrough, DI
registration) and new stand-in resolution tests in
`EncodeableFactoryDefinitionSourceTests`.
- Verified locally on **.NET Framework 4.8** and **.NET 10**:
`Opc.Ua.Core.Schema.Tests` (67), the server complex-type tests, and 2228
client complex-type tests pass; net48 + net10 builds are 0‑warning.

## Docs

- `Docs/ComplexTypes.md` (new server section),
`Docs/SchemaGeneration.md` (factory-as-resolver), and
`Docs/migrate/2.0.x/encoders.md`.

## Related Issues

- Fixes #1306
2026-07-06 11:17:58 +02:00
Marc Schierandromanett ed09030498 Tests: Up code coverage, improve reliability and accuracy of the existing test suite (#3951)
# Description

Quality and reliability cleanup of the **existing test suite**,
discovered during a full test-quality review (consistent naming, real
assertions vs. warnings, correctness/documentation of skips, and general
review). **No production code is changed** — these are test-only
improvements, so there is no API or behavioural impact on the stack
itself.

### What changed

**Stop masking setup failures**
- `Tests/Opc.Ua.Client.TestFramework/ClientTestFramework.cs`: a
single-session `OneTimeSetUp` connect failure was downgraded to
`Assert.Warn` (the comment even claimed "tests fail", but they did not —
the fixture continued with a `null` `Session` and produced confusing
`NullReferenceException` cascades). Switched to `Assert.Ignore`,
consistent with the sibling `SetUpAsync`. Only the failure path changes;
healthy runs are unaffected.

**Remove fixed-delay flakiness and premature skips (Alarms &
Conditions)**
- `Tests/Opc.Ua.History.Tests/AlarmsAndConditionsTestFixture.cs`: added
a `WaitForEventIdAsync` polling helper that mirrors the existing, proven
`WaitForStateIdAsync`.
- `Tests/Opc.Ua.History.Tests/AlarmsAndConditionsAcknowledgeTests.cs`:
replaced **four** copy-pasted `await Task.Delay(1500)` + one-shot read +
`Assert.Ignore("… no EventId yet")` blocks with the new polling helper,
and made the `AckedState` assertion race-proof via
`WaitForStateIdAsync`. A slow server now waits (up to the existing 30 s
budget) instead of skipping after a blind 1.5 s, and the test skips only
when the alarm is genuinely not in an alarming state.

**Strengthen weak assertions**
- `Tests/Opc.Ua.Sessions.Tests/ClientTest.cs`: `GetEndpointsAsync` /
`FindServersAsync` previously asserted only the close status and then
printed the results; they now also assert the response is non-empty (a
UA server always returns at least one endpoint / at least itself).
- `Tests/Opc.Ua.Server.Tests/MonitoredItemTests.cs`: removed a stale
commented-out assertion — the queued value is already proven downstream
by the publish result.

**Documentation and style accuracy**
- Fixed three incorrect copy-paste XML docs:
`CertificateTestsForECDsa.OneTimeSetUp` claimed to "Set up a Global
Discovery Server…" (it is an empty cert-test setup);
`ApplicationInstanceTests` was documented "Tests for the BuiltIn Types";
and its `[TearDown]` was documented "Test setup".
- Removed an empty `[OneTimeSetUp]` and five comment-only `// ===== …
=====` region dividers (the repo style rules forbid comment-only
dividers).

### Verification

All eight files were re-read after editing and checked against the real
fixture/production APIs and both target frameworks (**net48** and
**net10.0**). The changes were deliberately chosen so they cannot turn a
currently-green run red — they affect only failure/skip paths or add
spec-guaranteed assertions. Tests were **not** executed in the authoring
environment; please rely on CI (or a local `UA.slnx` run on net48 +
net10.0) to confirm before merge.

## Related Issues

No dedicated tracking issue — this is a test-suite quality/reliability
cleanup with no production-code, API, or documentation impact. Happy to
link or open a tracking issue if the maintainers prefer.

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [ ] I have added all necessary documentation.
- [ ] 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.

---------

Co-authored-by: romanett <romanett98@gmail.com>
2026-07-06 08:48:56 +02:00
Marc Schier e3d322cbf5 Fix three high-severity audit findings in new 2.0 pub sub/GDS code (#3954)
# Description

Fixes three high-severity, remotely-relevant security weaknesses
identified by a full-codebase security audit. Each fix is minimal and
surgical, and ships with regression tests.

### 1. PubSub inbound message-security fail-open for non-UADP (JSON)
frames

`PubSubConnection` nested the inbound signature/encryption/replay
enforcement **inside** the `UadpDecoder.TryReadOuterPrefix(...)` branch.
Because that check returns `false` for a JSON frame, a reader configured
for `Sign`/`SignAndEncrypt` over a JSON transport silently accepted
attacker-forged, unauthenticated frames, delivering forged
DataSet/MetaData messages to application sinks. The send path already
fail-*closes* the identical case.

**Fix:** drop any non-UADP frame fail-closed on a secured reader before
decode (`else if (RequiresInboundSecurity)`), mirroring the send-side
refusal.

### 2. Client schema-import SSRF / NetNTLM credential leak

When loading complex types, the client falls back to the server's type
dictionary and resolves each `Import`/`SchemaLocation` against the local
file system with no sanitization. A malicious server could supply a UNC
path (e.g. `\\attacker\share\x`), triggering an outbound SMB/NetNTLM
handshake (hash theft / SSRF) or arbitrary local file read. This
bypassed the existing XXE hardening because imports are followed by
manual file-system code, not the XML resolver.

**Fix:** added a deny-all `NullFileSystem` and wired it into the
in-memory/import-table schema validator constructors, so
network-supplied dictionaries never touch the local file system. The XML
loader now prefers the in-memory import table before any (untrusted)
location, so legitimate in-memory resolution is unchanged.

### 3. GDS AuthorizationService JWT impersonation / privilege escalation

`FinishRequestToken` derived the JWT subject from the
**unauthenticated** client-supplied `userIdentityToken` (never verifying
`userTokenSignature`) and embedded the client's `requestedRoles`
verbatim. Any caller able to open an encrypted channel — even anonymous
— could obtain a GDS-signed token asserting `sub=admin`,
`roles=[SecurityAdmin, ...]`, impersonating any principal to resource
servers that trust GDS-issued JWTs.

**Fix:**
- The token subject is now bound to the identity that was
**authenticated on the session** when the request was started; the
client token is no longer trusted for the subject.
- Requested roles are gated by a new **fail-closed**
`AuthorizationServiceOptions.AuthorizeRoles` hook. With no callback
configured, **no roles** are granted; the callback can only ever narrow
the requested set. This removes privilege escalation via arbitrary role
requests.
- The obsolete single-call `RequestAccessToken` path no longer trusts
the client-supplied token: it issues an unprivileged
**anonymous**-subject token with no roles. Use
`StartRequestToken`/`FinishRequestToken` for authenticated identity
binding.

## Related Issues

No public issue is linked — these findings were identified by an
internal security audit and fixed directly. Maintainers may open
tracking issues if desired.

## Verification

- All affected projects build with **0 warnings / 0 errors** (net10.0).
- Regression tests added and passing:
- PubSub: `SecuredReaderRejectsNonUadpJsonFrameAsync` (+ existing
receive-security suite, 6/6).
- Schema/SSRF: `NullFileSystemTests` (6/6),
`ImportTableValidatorDoesNotResolveServerSuppliedFilesystemLocation`
(BSD, 3/3), source-gen schema validation (6 passed), full ComplexTypes
client-load suite (2214/2214).
- GDS: `FinishDoesNotGrantRequestedRolesWithoutAuthorization`,
`FinishBindsSubjectToAuthenticatedIdentityNotRequestToken`, plus
`LegacyRequestAccessTokenIssuesAnonymousUnprivilegedToken` and
`AuthorizeRoles` intersection/empty edge cases (AuthorizationService
unit 15/15, RefreshToken integration 3/3).

_Review feedback from the automated reviewer has been addressed
(NullFileSystem singleton constructor + header year, PubSub drop logged
at Debug to avoid log amplification, and the obsolete
`RequestAccessToken` path simplified to a uniform anonymous subject);
all review threads resolved._
2026-07-06 08:48:07 +02:00
Marc Schier 2b4b0677d4 Validate issuer/CA certificate KeyUsage (keyCertSign, cRLSign) (#3947)
## Summary

Fixes a certificate-validation compliance gap: the stack did not verify
that **CA/issuer certificates** in a chain assert the KeyUsage required
for a CA.

Per **OPC 10000-6 §6.2.4, Table 52 (Issuer Certificate)**, a CA
certificate must carry a KeyUsage extension asserting `keyCertSign` and
`cRLSign`, and **OPC 10000-4 §6.1.3, Table 100 ("Certificate Usage")**
requires validators to check issuer usage and report
`Bad_CertificateIssuerUseNotAllowed`.

The validator only checked the **leaf** certificate's KeyUsage and
delegated all issuer/CA usage to .NET `X509Chain`. Per RFC 5280
§4.2.1.3, `X509Chain` does **not** reject a CA whose KeyUsage extension
is *absent* (absence implies no usage restriction), so a
root/intermediate CA with no KeyUsage — exactly the scenario reported in
#3944 — was accepted by two .NET peers while strict third-party OPC UA
stacks reject the same chain with `BadCertificateInvalid`.

## Changes

- `CertificateValidationHelpers.HasRequiredIssuerKeyUsage(Certificate)`
— returns whether both `keyCertSign` and `cRLSign` are asserted (absent
KeyUsage → `None` → fails).
- `CertificateValidationCore.InternalValidateAsync` — after the leaf
KeyUsage check, verifies every resolved issuer/CA certificate in the
chain and appends a **suppressible**
`Bad_CertificateIssuerUseNotAllowed` when the required bits are missing.
Existing deployments that must interoperate with a non-compliant CA can
suppress it via the validation callback.
- **Two coupled defects fixed in `CheckChainStatus`** (surfaced while
testing the `NotValidForUsage` path):
1. A `FormatException` — `ServiceResult.Create` was given a
named-placeholder format string (`{Status}: {Information}`) where
positional `{0}: {1}` is required; this crashed validation whenever
`X509Chain` reported `NotValidForUsage`.
2. An inverted `isIssuer` status-code ternary (issuer↔leaf codes were
swapped, contrary to Part 4 Table 100).
- `Docs/Certificates.md` — new "CA (issuer) KeyUsage validation"
subsection.

Every new check/helper carries an in-code citation to the governing spec
sections.

## Testing

- New tests in `CertificateValidatorTest.cs`: a deterministic helper
unit test (absent / partial / compliant KeyUsage) plus integration tests
for reject / suppress / accept.
- Full `CertificateValidator` fixture green on **net10.0** and
**net48**, no regressions.

## Compatibility

- Long-standing behaviour present in both `master` (2.0) and `master378`
(1.5.378) — **not** a 2.0 regression. The 1.5.378 backport is submitted
separately.
- No public API change; the new error is suppressible, preserving
backward compatibility.

Relates to #3944
2026-07-04 11:35:37 +02:00
Marc Schier 26ef554283 Server: fully asynchronous change-notification and event push path (#3941)
# Description

Makes the server data-change **and** event push path fully asynchronous,
so a monitored node whose value is read through an asynchronous read
handler (`OnReadValueAsync` / `OnSimpleReadValueAsync`) is honored
without a thread ever blocking, and the per-node notification channel
applies back-pressure by awaiting rather than blocking a caller. This is
the deeper follow-up to the review discussion on #3922, where the
synchronous change-notification path was silently bypassing asynchronous
value read handlers.

The change is **additive and non-breaking**: every existing synchronous
API and sink keeps working unchanged.

**Asynchronous `NodeState` change-notification API**
(`Stack/Opc.Ua.Types`)
- New `ClearChangeMasksAsync`, `OnStateChangedAsync` /
`StateChangedAsync`, and `ReportEventAsync` / `OnReportEventAsync` (plus
the `BaseInstanceState` parent-recursion override).
- The synchronous `ClearChangeMasks` / `ReportEvent` now also drive the
asynchronous sinks: they complete inline for a synchronously-readable
node with channel capacity, and block the calling thread only when a
genuinely asynchronous read is in flight or the channel is full (so only
synchronous-API callers ever pay that cost).

**Asynchronous `MonitoredNode2` producers** (`Libraries/Opc.Ua.Server`)
- `OnMonitoredNodeChangedAsync` / `OnReportEventAsync` read each
monitored attribute at enqueue time via `ReadAttributeAsync` — honoring
an asynchronous read handler and materializing the value **before** the
change is enqueued (a strict enqueue-time snapshot) — and enqueue by
awaiting the bounded per-node channel. Synchronous wrappers are retained
for compatibility.
- FIFO/LIFO discard and the `Overflow` status bit remain a
per-monitored-item queue concern; the shared per-node channel stays a
lossless conduit.

**Asynchronous event source-report path**
- New `IServerInternal.ReportEventAsync` /
`ServerInternalData.ReportEventAsync`, and an asynchronous root-notifier
`OnReportEventAsync` in `AsyncCustomNodeManager` (event propagation to
the Server object is now awaited). The rare model-change
`Raise*ChangeEvent` helpers remain synchronous — they drive the async
producer via the sync wrapper, and events do not perform an asynchronous
read, so a full async cascade through the model-change/audit subsystems
would be near-zero value.

**Internal flush migration**
- The hot value-write flush (`AsyncCustomNodeManager.WriteAsync`) and
all other asynchronous-context `ClearChangeMasks` call sites now await
`ClearChangeMasksAsync`. Genuinely synchronous contexts (builders,
diagnostics timers, the synchronous `CustomNodeManager`, supervision)
remain on the synchronous wrapper (correct — not hot paths).

**Performance:** synchronous reads are unchanged (they complete inline);
asynchronous reads no longer block a thread (proven by a unit test
asserting the producer's `ValueTask` is not completed while a slow
asynchronous read is in flight); enqueue replaces a blocking wait /
per-event `AsTask` with an awaited write.

**Docs:** a new "Fully asynchronous change notifications" section in
`Docs/AsyncServerSupport.md`.

## Related Issues

Follow-up to the review feedback on #3922 (asynchronous value read
handlers were bypassed on the synchronous change-notification push
path).

- Fixes #

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] 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.
2026-07-03 14:42:24 +02:00
Marc Schier d4714bd8ff Fix #3937: resolve ModelDesign references to NodeSet2-defined types (#3940)
## Problem

Fixes #3937.

The source generator processes `NodeSet2` and `ModelDesign`
`AdditionalFiles` in two independent passes (each with its own
validator). The `ModelDesign` pass ran with **no dependencies**, so a
`ModelDesign` instance whose `TypeDefinition` points at an `ObjectType`
defined in a `NodeSet2` input could not resolve it and failed with:

```
MODELGEN003 'The TypeDefinition reference for node Equipment01 is not the expected type: ObjectTypeDesign.'
```

(The message is misleading — the node was actually *unresolved*, not the
wrong type, because the NodeSet2 namespace was never loaded into the
ModelDesign pass.)

## Fix

Wire every `NodeSet2` input (encoded with the prefix/name computed by
the nodeset pass) and every other `ModelDesign` input into the
`ModelDesign` generation pass as **dependencies**, so cross-model
references resolve — both `ModelDesign → NodeSet2` (e.g. an instance
whose `TypeDefinition` is a NodeSet2-defined `ObjectType`) and
`ModelDesign → ModelDesign` across directories. A single `GenerateCode`
call is kept so `[NodeManager]` attribute binding still matches across
all targets.

- `Tools/Opc.Ua.SourceGeneration.Core/NodesetFile.cs` — add
`NodesetFileCollection.DesignFileEntries` (each nodeset encoded
`file,prefix,name`, excluding OpcUa / ignored).
- `Tools/Opc.Ua.SourceGeneration/ModelCompilation.cs` — supply the
design pass with those dependencies.

## Test

`Tests/Opc.Ua.SourceGeneration.Tests/ModelGeneratorTests.cs` →
`GenerateAndCompileModelDesignReferencingNodeSet2TypesTest`, using a
**synthetic** NodeSet2 (`CrossModelTypes` — a `WidgetType` ObjectType)
plus a ModelDesign (`CrossModelInstances` — a `Widget1` instance whose
`TypeDefinition` is the NodeSet2 type), added as embedded resources. The
synthetic pair is collision-free, so the test runs a full
generate-and-compile and asserts the instance output references the
generated type class (`global::CrossModelTypes.WidgetState`). Verified
it **fails before** the fix (MODELGEN003 for `Widget1`) and **passes
after**, on net10.0 and net48.

## Docs

- `Docs/SourceGeneratedNodeManagers.md` — new "Mixing ModelDesign and
NodeSet2 in one project" section + a reserved-name limitation bullet.
- `Tools/Opc.Ua.SourceGeneration/readme.md` — per-file `AdditionalFiles`
metadata table + prefix/name control guidance.

## Validation

- `Opc.Ua.SourceGeneration.Tests` (44) and
`Opc.Ua.SourceGeneration.Core.Tests` (3654) pass on **net10.0**.
- New test passes and the test project builds clean (0 warnings) on
**net48**.

## Notes (separate from this fix)

After this fix, the reporter's *specific* model still needs one change
of its own: a component named `Description` collides with the built-in
`NodeState.Description` attribute (`CS0029`) and must be renamed. (The
`NamespaceMetadataState` `CS0234` only appears in the test's minimal
stub stack; the type exists in the real `Opc.Ua.Core`.)
2026-07-03 04:47:17 +02:00