mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-16 21:02:51 +08:00
master
107 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
87012a147f |
Machinery: add bounded inspection and result transfers (#4804)
# Description - Add opt-in bounded browsing and strict reads with original source, status, timestamp, and structured-value evidence. Preserve the existing tolerant constructors and convenience projections, and wire options through session extensions, factories, and hosting registration. - Add selected-result streaming downloads with byte/request limits and independent cleanup, using `TemporaryFileTransferClient.CopyToAsync` from the client/server predecessor. Retain the detailed transfer regressions in the Machinery test layer. - Expose genuinely null unpublished result slots, carry the two Machinery sample updates, and include the complete Machinery guide and package README changes. ## Related Issues - Related tracking issue: #4461; this extraction does not close it. - Frozen source PR: #4462; this extraction does not close or modify it. - Immediate predecessor: #4803 (`marcschier-extract-amb-changes`). The earlier dependencies are client/server #4802 and core/types #4801. ## 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. |
||
|
|
8772f37275 |
Fix recycling scenario defaults and MCP interoperability (#4808)
# Description Fix five failures reproduced while exercising the recycling sample through a locally built OPC UA MCP server: - Support Certificate authentication through named, host-approved X.509 identity selectors and the existing certificate/identity providers. Tool arguments cannot supply private keys or arbitrary certificate-store paths. - Register custom enumeration dependencies from the validated, bounded type snapshot before loading requested structure roots. - Preserve structured fields and opaque encoded bodies in generic read/call results using the selected session's UA codecs. - Add bounded, explicitly typed UA JSON method arguments while retaining the existing string argument behavior and C# overloads. - Preserve omitted defaults in sparse recycling scenario JSON; explicit invalid values and unknown members still fail validation. All six shipped scenario files are covered by regressions. Update the MCP and recycling documentation. This PR contains one fix commit on a feature branch in **OPCFoundation/UA-.NETStandard**, based directly on upstream `master` at `95850b7e5`. No local registration, certificates, scenario journals or live-test artifacts are included. ### Validation **Final commit `d199feba0`: 216 affected MCP tests and 16 recycling CLI/scenario tests passed on .NET 10**, with no failures, fixture setup/teardown errors or build/analyzer warnings. The repository's TRX verdict helper accepted both reports. **Draft: the full-solution gate is not green.** The .NET 10 solution run encountered an `Opc.Ua.OneFuzz.Validator.Tests` fixture-publication failure outside the files changed by this PR: `MSB3030`, missing `Opc.Ua.OneFuzz.TestDependency.runtimeconfig.json`. It affected 210 tests; the run was stopped rather than expanding this PR into unrelated OneFuzz work. The full .NET Framework 4.8 solution suite was not run. Before the final rebase, the complete managed recycling suite passed **117**, and the CLI/scenario tests passed **16 each on .NET 8 and .NET 9**, with no failures or fixture teardown errors. The five explicit NativeAOT smoke cases were not selected. Actual live qualification used one coordinator and all ten independent process servers with strict certificate trust. Healthy, combined-fault and zero-utility runs each produced 24 complete packages with exact component conservation. Also exercised native companion discovery, subscriptions, authorization, pause/step/enable/disable, durable request replay, quality holds, reset and coordinator crash recovery. NativeAOT executables, container deployment and parity against a complete external producer contract were not qualified. <details> <summary>Exact validation commands and results</summary> Commands ran from the repository root with an isolated .NET SDK 10.0.401. `$root` denotes the private validation-artifact directory; no machine-specific path or key material is included here. Affected MCP matrix, final commit — **passed: 216; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Tools.Tests\Opc.Ua.Tools.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --filter 'FullyQualifiedName~SchemaRemoteTests|FullyQualifiedName~OpcUaJsonHelperTests|FullyQualifiedName~MethodServiceToolsTests|FullyQualifiedName~ConvenienceToolsTests|FullyQualifiedName~AttributeServiceToolsTests|FullyQualifiedName~ViewServiceToolsTests|FullyQualifiedName~OpcUaSessionManagerTests|FullyQualifiedName~ConnectionToolsTests|FullyQualifiedName~McpHostBuilderTests|FullyQualifiedName~ConfigurationToolsTests' --logger 'trx;LogFileName=mcp-pr.trx' --results-directory "$root\pr-validation\focused" ``` .NET 10 CLI/scenario regressions, final commit — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-pr.trx' --results-directory "$root\pr-validation\focused" ``` Complete managed recycling suite, before rebase — **passed: 117; failed: 0; skipped: 0** (five explicit NativeAOT cases not selected): ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net10.0 -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --no-restore --nologo -v:quiet --logger 'trx;LogFileName=recycling-final.trx' --results-directory (Join-Path $root 'results') ``` .NET 8 CLI/scenario regressions, before rebase — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net8.0 -p:CustomTestTarget=net8.0 -p:CustomTargetFrameworks=net8.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-net8.trx' --results-directory "$root\results" ``` .NET 9 CLI/scenario regressions, before rebase — **passed: 16; failed: 0; skipped: 0**: ```powershell dotnet test tests\Opc.Ua.Recycling.Samples.Tests\Opc.Ua.Recycling.Samples.Tests.csproj -c Release -f net9.0 -p:CustomTestTarget=net9.0 -p:CustomTargetFrameworks=net9.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --filter 'FullyQualifiedName~RecyclingCommandLineTests' --logger 'trx;LogFileName=recycling-cli-net9.trx' --results-directory "$root\results" ``` Full .NET 10 solution — **not passed**: stopped after the OneFuzz fixture failure above; incomplete solution run, not a clean validation result: ```powershell dotnet test UA.slnx -c Release -p:CustomTestTarget=net10.0 -p:CustomTargetFrameworks=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --nologo -v:quiet --logger trx --results-directory "$root\pr-validation\net10" ``` </details> ## Related Issues Follow-up to [#4772](https://github.com/OPCFoundation/UA-.NETStandard/pull/4772), which introduced the recycling sample. No separate tracking issue is currently linked. These fixes preserve the existing architecture and address failures reproduced during live qualification. ## 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. Regression tests were added and passed as recorded above; aggregate coverage was not collected, so the combined coverage checklist item remains unchecked. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> |
||
|
|
b3c1e60f46 |
AMB: add bounded strict reads and configurable sample hosting (#4803)
# Description Extract the AMB layer from #4462 onto the client/server predecessor #4802, then address the four review findings in `6428686ae45724510bd6bd00619f4992f3d36e46`. The initial extraction (`95b0caa1ac30d1fdacaf7a780f19c5f97e6fd02d`) contained exactly 19 paths: 12 AMB client files, four AMB test files, two sample entry points and `docs/AMB.md`. Its code/tests matched the frozen source `5b03e9afa492cdb36049ee52ae844f630f89d8db...390c4a7a51b71da40caec2d22ef74518bbe670ce`; only the two agreed UaLens-only documentation passages were omitted. The review follow-up intentionally changes that extracted implementation and adds the related factory/registration tests and package documentation. No UaLens, Machinery, Pumps, Positioning, native-rendering, build or CI changes are introduced by this layer. The AMB client provides opt-in bounded traversal, strict response/type validation and original `AssetReadValue` evidence while preserving tolerant defaults. The samples expose validated host, port, PKI root, certificate auto-accept and bounded-runtime options, with unchanged standalone defaults. The review corrections are: - Accept header-validated empty BrowseNext release responses required by Part 4 §5.9.3.2, as well as the stack's single-result response. Invalid cleanup results still preserve the original failure. - Keep event observation tolerant of denied/limited maintenance-class discovery without caching incomplete classes. Subsequent strict reads retry discovery and still report errors. - Accept typed-null ExtensionObjects only in the nullable structure-field checks, preserving original evidence without weakening scalar validation. - Pass optional immutable limits through `AddAssetManagementClient`, the registered factory/delegate and directly constructed factories. Preserve existing overloads and tolerant behavior when options are omitted; reject invalid limits before opening a session. ### Review regression evidence | Requirement | Evidence | | --- | --- | | Accept empty cleanup responses without masking cancellation or limits | `OneBeyondReferenceLimitReleasesAcquiredContinuationAsync` and `CancellationWhileAwaitingNextReleasesTheSubmittedContinuationAsync`, each with empty and single-result responses; malformed responses remain covered | | Preserve observation fallback and do not cache incomplete classes | `ObservationFallsBackWithoutCachingIncompleteClassesAsync`: both observation APIs, denied browsing and exhausted budget, correct filter/event evidence, and a subsequent strict rediscovery | | Preserve typed-null structure evidence without weakening scalar checks | `ContextAndEntryReadsKeepSourceAndNullStructureEvidenceAsync`, `MaintenanceTypedNullStructuresKeepOriginalEvidenceAsync`, `ScalarHealthRejectsTypedNullStructuresAsync`, plus wrong-non-null-structure rejection tests | | Apply configured hosted limits while retaining tolerant defaults | `HostedClientOptionsReachClientsAsync`: registered factory, registered delegate and direct factory, singleton and builder registration, actual one-reference limit and read evidence; `InvalidHostedLimitsAreRejectedBeforeOpeningASession` | ### Validation All commands below ran in this layer's worktree with the existing pinned .NET SDK 10.0.401. No SDK installation, global.json change, registry/path-policy change, test suppression or analyzer suppression was made. The short owned NUnit directory `D:\a61r` and its generated certificates/state were removed afterward. **Latest full AMB suite: Passed: 285, Failed: 0, Skipped: 0 on each framework.** Both builds emitted no compiler/analyzer diagnostics. TRX files confirm the regression cases executed on both TFMs. ```powershell & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' test 'tests\Opc.Ua.AMB.Tests\Opc.Ua.AMB.Tests.csproj' --no-restore --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 --nologo --verbosity minimal --logger 'trx;LogFileName=amb-feedback-net10.trx' --results-directory 'C:\Users\mschier\.copilot\session-state\61d7ed73-c89e-406a-a049-1d8ba9681308\files\TestResults\feedback-net10' -- 'NUnit.WorkDirectory=D:\a61r' & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' test 'tests\Opc.Ua.AMB.Tests\Opc.Ua.AMB.Tests.csproj' --configuration Release --framework net48 -p:CustomTestTarget=net48 --nologo --verbosity minimal --logger 'trx;LogFileName=amb-feedback-net48.trx' --results-directory 'C:\Users\mschier\.copilot\session-state\61d7ed73-c89e-406a-a049-1d8ba9681308\files\TestResults\feedback-net48' -- 'NUnit.WorkDirectory=D:\a61r' ``` **Focused regression run: Passed: 127, Failed: 0, Skipped: 0.** Before the production fixes, the focused repro executed 112 cases with 16 failures reproducing all four reported defects. Additional registration/validation cases were included in the final run. ```powershell & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' test 'tests\Opc.Ua.AMB.Tests\Opc.Ua.AMB.Tests.csproj' --no-restore --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 --nologo --verbosity minimal --filter 'FullyQualifiedName~AmbBoundedClientTests|FullyQualifiedName~AmbClientTests|FullyQualifiedName~HostedClientOptionsReachClientsAsync' --logger 'trx;LogFileName=amb-feedback-focused.trx' --results-directory 'C:\Users\mschier\.copilot\session-state\61d7ed73-c89e-406a-a049-1d8ba9681308\files\TestResults\feedback-focused' -- 'NUnit.WorkDirectory=D:\a61r' ``` **Whitespace and newly introduced style findings: passed.** `git diff --check` and the following commands exited 0: ```powershell & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' format whitespace --folder --verify-no-changes --verbosity quiet --include 'src\Opc.Ua.AMB.Client\AmbClient.Bounds.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Health.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Structure.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Values.cs' 'src\Opc.Ua.AMB.Client\AmbClientFactory.cs' 'src\Opc.Ua.AMB.Client\Hosting\OpcUaAmbClientBuilderExtensions.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.Traversal.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.Values.cs' 'tests\Opc.Ua.AMB.Tests\AmbClientServerE2eTests.cs' 'tests\Opc.Ua.AMB.Tests\AmbClientTests.cs' $env:CustomTestTarget = 'net10.0' $env:Configuration = 'Release' & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' format style 'tests\Opc.Ua.AMB.Tests\Opc.Ua.AMB.Tests.csproj' --no-restore --verify-no-changes --severity info --diagnostics IDE0039 --verbosity quiet --include 'tests\Opc.Ua.AMB.Tests\AmbClientServerE2eTests.cs' 'tests\Opc.Ua.AMB.Tests\AmbClientTests.cs' ``` The broader formatter check was **not clean**. It reported pre-existing informational suggestions and design-time `NUnit1003` diagnostics for generated browse-name constants, including pre-existing test cases. Those diagnostics do not occur in the actual builds, which compile and execute those cases successfully. Two new local-function suggestions were corrected and rechecked above; no unrelated baseline cleanup or suppression was included. ```powershell $env:CustomTestTarget = 'net10.0' & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' format 'tests\Opc.Ua.AMB.Tests\Opc.Ua.AMB.Tests.csproj' --no-restore --verify-no-changes --severity info --verbosity quiet --include 'src\Opc.Ua.AMB.Client\AmbClient.Bounds.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Health.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Structure.cs' 'src\Opc.Ua.AMB.Client\AmbClient.Values.cs' 'src\Opc.Ua.AMB.Client\AmbClientFactory.cs' 'src\Opc.Ua.AMB.Client\Hosting\OpcUaAmbClientBuilderExtensions.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.Traversal.cs' 'tests\Opc.Ua.AMB.Tests\AmbBoundedClientTests.Values.cs' 'tests\Opc.Ua.AMB.Tests\AmbClientServerE2eTests.cs' 'tests\Opc.Ua.AMB.Tests\AmbClientTests.cs' ``` <details> <summary>Initial extraction validation and environment history</summary> The original extraction passed all 263 AMB tests on both TFMs. Its initial net48 run had one unchanged maintenance-test server-start timeout; shortening only NUnit.WorkDirectory resolved it, consistent with certificate-store path lengths. This follow-up used a short directory from the start and all 285 cases passed on both frameworks. A no-restore test invocation during the follow-up initially executed no tests; it was not counted as passing evidence. A no-restore build identified `NETSDK1005` (missing net10.0 target assets after switching TFMs). The normal test invocation restored the target assets. Initial regression-test compilation issues were corrected before capturing the 16 failing behavioral cases. Both samples were built during extraction on both TFMs; each command below passed with 0 warnings and 0 errors. They were not rebuilt during this review follow-up, which does not modify the samples. ```powershell & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' build 'samples\AMB\AmbClient\AmbClient.csproj' --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 --nologo --verbosity quiet & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' build 'samples\AMB\AmbServer\AmbServer.csproj' --configuration Release --framework net10.0 -p:CustomTestTarget=net10.0 --nologo --verbosity quiet & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' build 'samples\AMB\AmbClient\AmbClient.csproj' --configuration Release --framework net48 -p:CustomTestTarget=net48 --nologo --verbosity quiet & 'D:\git\UA-.NETStandard_master\TestResults\ualens-528-deep-repro\sdk\dotnet.exe' build 'samples\AMB\AmbServer\AmbServer.csproj' --configuration Release --framework net48 -p:CustomTestTarget=net48 --nologo --verbosity quiet ``` </details> This remains a draft: the complete `UA.slnx` net48/net10.0 matrix, coverage, CI/CodeQL and CLA status are not established by this focused validation. No coverage increase or complete-solution pass is claimed. The feedback commit is based on the original AMB extraction; integration of later lower-layer updates is coordinated separately, without rewriting this branch or native stack 4807. ## Related Issues - References #4461, the existing tracking issue; this PR does not close it. - Extracted from #4462 at frozen head `390c4a7a51b71da40caec2d22ef74518bbe670ce`; the source PR is not closed by this PR. - Immediate predecessor: #4802 (`marcschier-extract-client-and-server`), above core/types #4801. The original extraction parent was `86ff10d3994f132f7343a929abdae209bc2e69b7`. This is an isolated extraction and review correction of the existing tracked implementation, not a claim of new architectural sign-off. ## 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. - [x] I have addressed **all** PR feedback received. |
||
|
|
0374e19121 |
Add MachineTool, WMTP, GMS and CranesHoists companions (#4765)
# 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._ Add provider-backed MachineTool, bundled WMTP, GMS and CranesHoists companions using the existing Machinery/MachineVision fluent, DI and typed-client approach. **The PR is open/non-draft by user choice. Current head `ecc4a7d6ca3e8004cf28ab54da605da302d026bf` is mergeable and has 108 successful checks, six intentionally skipped checks, zero failures and zero pending checks. All five primary workflows completed successfully at that exact head.** | Family | Released specification/model | Packaging | | --- | --- | --- | | MachineTool | OPC 40501-1, 1.02.0 | Model, Server, Client and MCP; normal root versions | | WMTP | OPC 40505, 1.0.0 | Bundled in MachineTool; distinct CLR/wire namespace and independent WMTP-only hosting | | GMS | OPC 40210 reference 1.0.1, normative model 1.0.0 | Model, Server, Client and MCP; normal root versions | | CranesHoists | OPC 40020-1, 1.00 | Model, Server, Client and MCP; all four plus Debug variants forced preview | There are **12 new packable projects**. No separate WMTP package/profile/guide, root version bump, tag, release or package publication is included. Mandatory instance structure is the default; selected optional features require explicit providers. The implementation reuses Machinery results/jobs/files/ProcessValues and Robotics topology, including retained MachineTool Production, defined GMS jobs/parts, modern ISA95/Machinery jobs, shared-owner hosting and isolated instances alongside MachineVision. Runtime and integration fixes preserve raw quality/timestamps, coherent acquisition, namespace ownership, accepted-configuration state, borrowed-resource lifetime, session/node/file cleanup, terminal-value publication, event propagation and endpoint-aware capability publication. Firmware/licence transfer means provider acceptance, not installation/activation. Cancellation does not silently abort another caller's operation. The later upstream MCP expansion is integrated without losing either family set or its default-off privileged/live execution policy. ### Conformance and scope Canonical model inputs are pinned to UA-Nodeset revision `96a08945425d0295edb3607d2a38f6841ef44046`. **GMS upstream exception:** [Annex A.1](https://reference.opcfoundation.org/specs/OPC-40210/annex-a) makes the released NodeSet normative, but §12.3.2.7 requires undefined `GMS_ProductionStatisticsType`. Inherited statistics and defined jobs/parts are implemented without inventing a standard type/NodeId; the complete GMS Production facet is withheld. The requirement ledger records **102 positively evidenced CU rows, 96 directly negative-tested rows and six explicit negative-evidence limitations**. All 648 test-class/name/TRX links were independently resolved to passing cases. The six limitations concern Stacklight, retained legacy PowerOnDuration, Machinery PowerOnDuration, WorkingUnit, Obligation and MaintenanceMode. They are not claims that every invalid-input or hardware-control behavior was tested. The undefined GMS requirement is outside the 102 defined CUs. No external certification claim is made. Retained baseline complete-assembly line coverage, with generated code retained and no source exclusions: | Family | Model | Server | Client | | --- | ---: | ---: | ---: | | MachineTool/WMTP | 83.80% | 91.98% | 84.74% | | GMS | 95.51% | 90.63% | 88.38% | | CranesHoists | 92.76% | 94.45% | 92.24% | These nine measurements predate the later focused review fixes and merges; they are not relabelled as new `ecc4a7d6c` percentages. The current head's separate CI code-coverage check succeeded. No new exclusions or threshold relaxations were introduced. ## Related Issues - Fixes [#4764](https://github.com/OPCFoundation/UA-.NETStandard/issues/4764), the implementation/design and acceptance tracker. ## 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. - [ ] 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. |
||
|
|
724757122b |
Add recycling simulator and plastics/rubber companion packages (#4772)
# Description Add a generic, company-neutral recycling simulation sample and reusable plastics/rubber companion-model packages. One durable causal coordinator and ten separately launched OPC UA process views provide deterministic material balances, finite buffers, genealogy, utilities, quality holds/disposition, authenticated controls and crash recovery. Process views consume committed state instead of running independent simulations. Real companion-model equipment instances use DI/Machinery/AMB discovery, Machine Vision, Pumps/Scales, granulate drying, extrusion, filtration and pelletizing. Vendor/process extensions remain sample-local. Connectors and WoT conversion are out of scope. **External source-contract parity and complete deployment qualification are not claimed complete.** The sample and managed tests target **.NET 8, 9 and 10**. The consolidated [recycling guide](samples/Recycling/README.md) explains the actual composition, simulation, observation paths and exposed data; the application-directory README points to that guide. Typed client member extensions are emitted at build time through the default-false `ModelSourceGeneratorClientAccessorsOnly` opt-in. Extrusion uses it for all twelve parts instead of checked-in generated C# and a Python generator. The shared client runtime retains typed values, quality, timestamps and streaming behavior. ### Package layout Six packages contain fourteen models in two model-producing assemblies: | Specifications | Model | Typed client | Server runtime | | --- | --- | --- | --- | | OPC 40083 and OPC 40082-6 | `Opc.Ua.PlasticsRubber` | `Opc.Ua.PlasticsRubber.Client` | `Opc.Ua.PlasticsRubber.Server` | | OPC 40084 parts 1–12 | `Opc.Ua.Extrusion` | `Opc.Ua.Extrusion.Client` | `Opc.Ua.Extrusion.Server` | NuGet IDs use the `OPCFoundation.NetStandard.` prefix. All six packages are registered in `.azurepipelines/expected-packages.txt` and the generated preview-pack solution. Granulate-drying code lives in `GranulateDrying` folders in the PlasticsRubber projects; extrusion uses per-part folders. There are no standalone granulate-drying or per-part extrusion packages. Published model identities and CLR namespaces remain distinct and unchanged. The common 1.03 interface is independently authored on the explicitly MIT-licensed 1.02 implementation basis; the official 1.03 XML is not redistributed. The other imported models retain their individual MIT notices. All 42 pinned model, identifier and basis files retain their manifest-backed bytes, including their staged Git blobs. No formal companion certification is claimed; the disputed Filter profile is not advertised. ## Related Issues No tracking issue is currently linked. ## 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. - [x] I have addressed **all** PR feedback received. Current-head CI and coverage remain pending; the full local `UA.slnx` test matrix is not claimed complete. |
||
|
|
2f72239a72 |
Quickstarts alarms: report one event per Enable instead of two (#4780)
Every Enable call on a reference-server alarm produced **two** condition
events (different EventIds, same Time); Disable produced one.
## Cause
`ConditionTypeHolder.OnEnableDisableAlarm` is registered as the
condition's `OnEnableDisable` hook, which
`ConditionState.ProcessBeforeEnableDisable` calls *before*
`OnEnableCalled`/`OnDisableCalled` change the `EnabledState`. The hook
switched the state (`SetEnableState`) and called `ReportEvent()` itself;
the core then ran `UpdateStateAfterEnable` and `ReportStateChange` for
the same change, because every concrete holder sets
`AutoReportStateChanges` (`AcknowledgeableConditionTypeHolder`). On
Disable the hook's `ReportEvent` is skipped (the condition is already
disabled), so only Enable doubled.
Part 9 §5.5.2: a change of `EnabledState` causes a ConditionType Event
Notification — one per change. The duplicate also made the CTT's *A and
C Enable Test_002* flaky: it took the second notification as the answer
to its next call (worked around in the CTT scripts).
## Fix
The hook keeps setting the `Message` ("Enabling" / "Disabling alarm …",
carried by the core's event) and leaves the state change and its event
to the core. For a condition without `AutoReportStateChanges` it still
changes the state and reports, as before.
## Tests
`AlarmsAndConditionsEnableTests.DisableEnableViaTypeAndInstanceMethodNoEventsDuringDisabledAsync`
now asserts exactly one condition event in the Enable call window —
fails on master (`Expected: 1 But was: 2`), passes with the fix. All A&C
history tests (152, 3 runs) and client alarm tests (55) pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
8c52cba0b2 |
CTT reference server: faster alarm cycle in --ctt mode, MinSubscriptionLifetime 1000 ms (#4766)
Shortens CTT runs against the reference server without changing what the CTT verifies. ## Changes - **Alarm simulation step in `--ctt` mode: 1000 → 500 ms.** `AlarmNodeManagerFactory.SimulationInterval` (default stays 1000 ms) is passed to `AlarmNodeManager`; `ConsoleReferenceServer --ctt` sets it via `Quickstarts.Servers.Utils.ConfigureCttModeNodeManagers()` to `Utils.CttAlarmSimulationInterval` (500 ms) before the node managers are created. A full alarm cycle (40 steps through all limits) takes 20 s instead of 40 s. Every alarm still passes through all states and limits; the alarm NodeIds carry the interval (`…ExclusiveLevelAlarmType.500.Alarm` in CTT mode). - **`Ctt.ReferenceServer.Config.xml`: `MinSubscriptionLifetime` 10000 → 1000 ms.** CTT scripts request short subscription lifetimes on purpose and then wait for them to expire (Subscription Basic 063/016, Durable 012, Minimum 02 004, Publish Min 05 Err-001, Diagnostics 024); the 10 s floor inflated those waits. Part 4 sets no minimum; the server still enforces lifetime ≥ 3 × keep-alive. ## Measured (patched CTT 1.05.513 scripts) | Part | Before | After | | --- | ---: | ---: | | A&C (p18), alarm step only + CTT Alarm Cycle Time 20 | 932 s | 576 s | | Subscriptions (p16), MinSubscriptionLifetime only | 679 s | 579 s | Combined full run with the matching CTT-side changes (ReadyToRun server build, publish interval 150 ms, SecureChannel min lifetime 1000 ms, A&C Refresh script waits): **72 → 54 min**, 0 regressions against the CTT baseline (the only error is the known CTT-binary defect GDS 019). The CTT CI repo derives its `Alarm Cycle Time` from `Utils.CttAlarmSimulationInterval`, so runs against branches without this change keep the old 30 s setting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1bb4bdca1d |
Add Machine Vision Parts 1 and 2 (#4733)
# Description Implement the released [OPC 40100-1](https://reference.opcfoundation.org/specs/OPC-40100-1) and [OPC 40100-2](https://reference.opcfoundation.org/specs/OPC-40100-2) specifications with generated models, provider-backed runtimes, typed clients, MCP workflows, and DI/Machinery integration. - **Part 1 stays in `Opc.Ua.Vision`** and its client/server packages alongside the distinct Perception model. Provide control/acquisition state machines, jobs and synchronized steps, recipe/product/configuration/result management, file transfers, diagnostics, events, and conditions. - **Part 2 is in `Opc.Ua.MachineVision`, `.Client`, and `.Server`.** Provide asset categories, identification, image-handling aspects, health/maintenance data, topology, and transactional updates. The base Vision assemblies do not reference these optional packages; the Part 2 wire model does not depend on Part 1. - Preserve the release-pinned model bytes, wire namespaces and NodeIds. Perception types are in `Opc.Ua.Vision.Perception`; the AMCM generated namespace remains `Opc.Ua.Vision.MachineVision.AMCM`, and asset client/server extension namespaces remain `Opc.Ua.Vision.Client` and `Opc.Ua.Vision.Server`. - Use `AddOpcUaMachineVision` and `AddOpcUaMachineVisionAMCM` for model loading and factory registration. Each has node-collection, encodeable-factory-builder and node-state-factory-builder overloads. These short entry points delegate to the generated implementations; existing namespace-derived generated names remain available for compatibility. - Support multiple independent named systems/assets under one namespace-owning manager. Reuse injectable providers, file binding, bounded memory stores, optional transactional `IFileSystemProvider` persistence, state machines, telemetry and conformance infrastructure. - Keep one `docs/Vision.md` guide, titled `Machine Vision (OPC 40100-1 and OPC 40100-2)`, and one `Opc.Ua.Mcp.Vision` package. Include deterministic server/client samples. - Expose 17 Machine Vision MCP tools, including capabilities/status, bounded relationships and observation, complete small-file uploads/downloads, management, explicit control, conditions, and chunked transfers. Catalogue counts: 39 Vision-specific, 43 with connection tools, 81 Vision+Robotics, 155 Full. Every Machine Vision wire parameter has a schema description. **Compatibility and limits:** `AddVision()` remains Perception-only unless released parts are selected. Standalone Part 2 uses `AddVision().AddMachineVision()`; Machinery composition uses `AddVisionToMachinery()`. Existing tool defaults and command signatures are preserved. Observation owns and cleans up its subscription: 5 seconds/50 notifications by default, capped at 60 seconds/100 notifications. Complete-file tools are capped at 1 MiB; larger files use chunked transfers. Domain Error values remain distinct from UA service failures. **Versioning:** the seven Vision/MachineVision/MCP packages and their Debug variants follow the repository root version instead of being forced to preview. Vision.OpenUsd and its descendants remain preview-only. Development builds still inherit the root prerelease version. No release version, tag, release branch, publication policy, or feed promotion is changed. **Review actions:** all fourteen review threads have action/evidence replies and are resolved. Changes include the unified guide, Perception namespace, separate Part 2 packages and final naming, multi-system ownership explanation, interrupted-write cleanup, observer failure isolation, complete MCP parameter descriptions, and explicit package inventory. The preview-layout migration topic and the requested ReleaseProcess caveat were removed. The test Publish collector now retries recognized transient failures within one cancellation budget and propagates other exceptions. Commit |
||
|
|
e80d0ca9ac |
Use a 4-byte KeyNonce in the reference PubSub sample (#4710)
# Description
The first PubSub exchange in
[docs/PubSub.md](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/docs/PubSub.md#run-a-first-publisher-and-subscriber)
delivers no DataSets on master or in the 2.0.0 source. The subscriber
only logs `DataSetReader Reader 1 faulted on MessageReceiveTimeout`,
while the publisher logs `UADP security wrap failed; dropping message`
with `ArgumentException: AES-CTR nonce must be exactly 12 bytes` for
every NetworkMessage.
Cause: the reference PubSub sample's demo keys use a 12-byte KeyNonce.
Since #4573, the AES-CTR counter block follows OPC 10000-14 Table 157
(`KeyNonce[4] || MessageNonce[8] || BlockCounter[4]`). With the sample's
keys, the publisher builds a 20-byte nonce, and `AesCtrTransform`
rejects it. The 2.0.0 release (release/2.0 at
|
||
|
|
28f1401324 |
Server: cut request-path lock contention and allocation; add load harness (#4707)
Profiling the reference server under load showed most request-path CPU spent spinning on contended locks, plus several avoidable allocations per request or per node. This PR removes them, switches the reference server to Server GC, and adds the load harness used to measure it. ## Results Paired A/B against `master` (3 alternating rounds, medians, 20 sessions, workstation GC on both sides, 24-core machine): | Scenario | Throughput | p50 | p90 | Server CPU / op | Server alloc / op | Lock contention | |---|---|---|---|---|---|---| | Read, 1 value | **+45%** (94k → 137k req/s) | −20% | −46% | −37% | −39% | −94% | | Read, 100 values | **+40%** | −26% | −17% | −24% | −25% | +130% ¹ | | Read, 100 values, SignAndEncrypt | **+48%** | −36% | −20% | −30% | −26% | +106% ¹ | | Browse (Server object) | **+33%** | −31% | −12% | −13% | −14% | −40% | | Write, 100 values | +13% | −12% | −4% | −10% | −19% | −23% | | Data-change subscriptions | ±0 ² | ≈ | ≈ | −14% | −17% | −29% | ¹ The faster server spends more time in the remaining per-node locks. ² Throughput is bounded by the writer rate in this scenario. Separately, Server GC instead of workstation GC doubles Read-100 throughput (21k → 44k req/s) and cuts p90 from 2.1 ms to 0.6 ms, because workstation GC paused the server for about half the wall time under load. The working set grows from ~210 MB to ~430 MB. ## Changes - **`ObjectPool<T>`**: lock-free slot array. `ConcurrentBag.Count` took every per-thread lock on each `Return` of the pooled response value-task source, once per request. - **`FairRequestQueue`**: - drops redundant gate entries: the fail-fast pre-checks and the stopped check read volatile fields and are re-checked under the gate; the owner count is folded into selection; - coalesces wake pulses with a pending flag, so the bounded signal channel's lock is only taken when no token is outstanding. Wake-count semantics in the tests are unchanged. - **`DefaultServerResourceIsolationProvider`**: - compares peer addresses without `GetAddressBytes()` allocations; - builds the unsecured verification context only when a classifier is configured. - **TCP listener / channel**: caches the certificate DER copies per channel, keyed on the certificate instance. `X509Certificate2.RawData` returns a fresh copy on every access, and the listener read it for every request. - **`EndpointBase`**: resolves the request `ActivitySource` once instead of `Assembly.GetCallingAssembly()` per request. - **Node managers**: - one `ServerSystemContext` copy per request (cached on the `OperationContext`) for per-node callbacks: permission metadata, node metadata, view membership, method lookup; - removes a discarded copy in `GetReferenceDescriptionAsync`; - per-request operation caches are plain `Dictionary` instead of a `ConcurrentDictionary` with 24 lock objects; - the validation cache is built in one pass; - fixes zero-capacity error lists. - **`ResourceManager`**: translation lookups read an immutable snapshot that is rebuilt after writes. Browse serialised on the global lock for every display name. - **`MonitoredItem`**: shares the default `DataChangeFilter` instead of allocating one per sampled value. - **Reference server**: `ServerGarbageCollection=true`; new "Garbage collector mode" section in `docs/ServerScalability.md`. - **`perf/`**: `ServerLoadHarness` (out-of-process server plus load driver, server-side CPU/GC/alloc/contention deltas, optional `dotnet-trace` capture), `TraceAnalyzer` (TraceEvent-based CPU/allocation/contention aggregation), and paired A/B scripts. Not part of `UA.slnx`. ### Behaviour notes for custom node managers - The `IDictionary<NodeId, NodeState>` operation cache handed to overrides is now a `Dictionary`. It was a `ConcurrentDictionary`, but only one request flow uses it. - Within one request, per-node callbacks of a node manager share one `ServerSystemContext` copy. ### Later commits on this PR - **Permission checks without `NodeMetadata`** (`9068684c3`): the role-permission and access-restriction validators run on an internal `PermissionMetadata` struct; `AsyncCustomNodeManager` fills it on the stack from the same attributes and namespace defaults. Browse's reference filter reads 3 attributes per target instead of 13. Read 100 values: 120.6 → 95.7 KB/request, ~21k → 26k req/s; Browse 67.6 → 51.8 KB, 26.7k → 29.8k req/s. - **Read-only channel evidence** (`936b28d76`, `dc638ab9f`): `SecureChannelContext.ClientChannelCertificate`, `ServerChannelCertificate` and `ChannelThumbprint` are `ByteString`, so the cached per-channel certificate buffer is shared without a copy and cannot be altered by handlers or classifiers. **Public API change**, see the new section in `docs/MigrationGuide.md`. The isolation provider's revalidation snapshot still copies. - **Session lookup** (`936b28d76`): `SessionManager.TryGetSessionContext` takes the candidate snapshot without the bindings lock; the validating check under the lock is unchanged (2 instead of 4 global lock acquisitions per request). - Unit tests for `ObjectPool`, the per-request system context cache, the translation snapshot, `SecureChannelContext` null semantics and a stopped `FairRequestQueue`; `perf/` follows the repository conventions. ## Testing - `Opc.Ua.Server.Tests` (net10.0): 8386 passed before the `ByteString` change. After it, 13 secure-session and isolation tests failed; both causes are fixed in `dc638ab9f` and those tests (25 with their variants) pass. A full rerun of the Server, Sessions, Core, Redundancy, LDS and GDS suites on the final commit is in progress. - Full solution builds for net10.0 and net48; `dotnet format whitespace` clean. ### o6-automation opcua-benchmarks (sampling, deep pipelines) Replicated both tests of the [o6-automation benchmark](https://github.com/o6-automation/opcua-benchmarks) in the harness (`--scenario counters`, and `read` with 10 clients × N outstanding), paired against `origin/master`: | Test | master | this PR | |---|---|---| | Subscription sampling, 100 Hz, every item ≥ 99 % of samples, 1 item | FAIL (75–94 %) | PASS (100 %) | | same, 1024 items | FAIL (64–73 %) | PASS (99.9 %) | | same, capacity | — | between 4096 and 6144 items (~410–520k samples/s) | | Read, 10 clients × 100 outstanding, None | stalls (0–1.7k req/s) | 121–131k req/s | | Read, 10 clients × 100 outstanding, SignAndEncrypt | stalls (0 req/s) | 89–91k req/s | | Read, 10 clients × 1000 outstanding | channels faulted (`BadTcpNotEnoughResources`) | 0 errors, ~45–54k req/s | - **Fixed-rate sampling** (`30b769d13`): the sampling loop waited one interval after each sample, adding timer overshoot and sampling time to every cycle; samples now have absolute deadlines. - **Async response send** (`cdac5c5ca`): `SendResponse` blocked thread-pool workers on the channel gate; with many requests in flight the receive loop that was handed the gate waited for a pool thread and the channel stalled. The TCP, Kestrel TCP and WebSocket listeners now use `SendResponseAsync`. - **Backpressure instead of channel fault** (`191cd2ba7`): server channels stop reading while more than 100 responses are queued and resume at 50, instead of faulting the channel. Client channels keep reading. At 10,000 outstanding requests throughput is ~50k req/s with or without the limit engaged, so the remaining gap to the 100-per-client case is not the backpressure. ## Not changed - `SentMessageQueue` deep-clones every `NotificationMessage`. Retained messages return their pooled payloads on acknowledgement while the response copy may still be queued for send, and a client can acknowledge a sequence number before receiving it; the clone keeps a recycled payload from reaching another session's response. Removing it needs a send-completion hook from the transport. - Generated responses allocate a default `ResponseHeader` that is replaced (~2-3 % of per-request allocation). Fixing it means making structure fields of every generated type lazily initialized, a generator-wide change to default values, equality and encoding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2ba58a8cbf |
WoT: move GeneratesEvent off Object instances (OPC 10000-3 §7.15) (#4706)
Fixes #4669. OPC 10000-3 §7.15 says the SourceNode of `GeneratesEvent` "shall be an ObjectType, a VariableType or a Method". CTT `Base Info Core Structure 2 001.js` flags every Object that is the source of one. #4667 fixed this in the source generator. This PR fixes the three remaining WoT places. ### 1. WotCon asset registry - `AssetRegistry.BuildEventNode` / `RemoveEventTypes` no longer add or remove `GeneratesEvent` from the asset Object. Event delivery already relies on `EventNotifier = SubscribeToEvents` and root-notifier registration. - Tests now assert the reference is **absent** and the notifier bit is set. The re-apply test asserts that the old EventType node is gone. ### 2. WoT NodeSet converter (`src/Opc.Ua.Types/Wot`) - **TD → NodeSet:** a last pass (`RelocateInstanceGeneratesEvent`) moves forward `GeneratesEvent` references off an Object/Variable root: - An event the bound type already declares needs nothing more. Matching is by qualified BrowseName against the local context's declarations, the same rule as the declaration merge. - Any other event is declared by a synthesized non-abstract `<rootLocal>Type`. It derives from the bound type (or from `BaseObjectType` / `BaseDataVariableType` when the TD binds none) and becomes the instance's `HasTypeDefinition`. A Variable root gets a `UAVariableType` with the instance's DataType and ValueRank. - Thing Models are unchanged: the projected type keeps its `GeneratesEvent`. - **NodeSet → TD:** - An instance's event affordances are now collected from its type definition and that type's supertypes. Instance-level `GeneratesEvent` is still read for third-party NodeSets. - In a single readable document, a type that only carries one instance's events (only `HasSubtype` + `GeneratesEvent`, typing exactly one instance) is not chosen as root. It is folded back: its supertype becomes the type binding, and no link is written when that supertype is the default base type. The TD → NodeSet → TD round trip stays readable, with no `uav:nodes` fallback. - A document set gives the carrier its own Thing Model, and the instance TD binds to it. - **Archive check:** an event affordance is accepted when the root's type definition, or one of its supertypes, generates it. - `docs/WoTNodeSetConversion.md` documents the behaviour in a new "Event source" row. ### 3. Aggregation sample - `SamplePump.NodeSet2.xml`: each pump is typed by its own `PumpType` subtype, `ns=1;s=Pump1.Type` (`Pump_1Type`) and `Pump2.Type`, which declares its alarm EventTypes. - It is per pump because each pump owns its alarm EventTypes. `Pump2.Type` is cloned with the rest of Pump2. - The pumps keep `EventNotifier="1"` and no longer carry `GeneratesEvent`. - The checked-in documents are regenerated, adding `sample-pump-pump-1type.json` / `-2type.json`. The pump TDs still list the alarm event affordances. - The sample binding lookup ignores Thing Models, because the type's TM declares the same EventType as the pump TD. A declaration missing from the instance still fails as before. - Docs: `WoTConnectivity.md` (asset event affordances, §12.4.2) and `WotBindings.md`. The `GeneratesEvent` clause in the sample-limitation note was already stale, so I removed it. ### Runtime check `WotNativeOwnershipIndex` only uses `GeneratesEvent` in its alias table. Projected event routing takes the owner from `uav:componentOf` / `uav:id` and the notifier from Parent/`HasNotifier`. Nothing in `Opc.Ua.WotCon.Server` reads an instance-level `GeneratesEvent`. ### Tests (net10.0) - `Opc.Ua.Types.Tests`: 11446 passed, 1 skipped. New `WotInstanceEventSourceTests` covers object and variable roots, TMs, the TD with no events, the folded round trip, the legacy instance reference and the document set. Two bound-type cases were added to `WotDeclarationMergeBoundaryTests`. - `Opc.Ua.WotCon.Bindings.Tests`: 714 passed. - `Opc.Ua.WotCon.Tests`: 1496 passed, including the byte-exact sample regeneration tests. - net48 build of both test projects is clean, and `dotnet format whitespace` is clean. Not run: CTT `Base Info Core Structure 2 001.js` (p02-base-info) against a WotCon server. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
9b034f8291 |
Enable nullable reference types centrally; migrate all unset and annotations-only projects (#4700)
# Description Nullable reference types are now enabled once, in `common.props`, and every project in the repository builds warning-clean with them. **Configuration** - `<Nullable>enable</Nullable>` lives in `common.props`. All per-project `<Nullable>` settings are removed: 178 project files (enable, annotations, and the `disable` in the three Aot test projects). - 419 per-file `#nullable enable`/`restore` directives and the 4 `#nullable disable` directives are removed. - Kept on purpose: - The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`, `PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the project setting in generated code, so the directive is required there. - `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against the 1.5.378 packages and does not import the repository props. - `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now describe the central setting. **Migration** (the 43 unset and 13 annotations-only projects, roughly 10k diagnostics) - **Production code: annotated at the declarations.** Covers `tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the LDS sample. - Members, parameters and returns are nullable where null actually flows. - Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`, `IsMethodTypeDesign`). - `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take `params object?[]`. `Template.AddReplacement` takes `string?` (rendering already skips null replacements). - `IRegisteredServerStore.RegisterAsync` returns `Task<RegistrationEntry?>`: it returns null when a registration is removed. - Dereferences backed by an invariant (for example, the validated model) use `!`, so runtime behaviour is unchanged. - **Behavioural changes in production code** (small, deliberate): - `NodeSetToModelDesign` throws `InvalidOperationException` when the file is not a NodeSet. Before, it threw a NullReferenceException on the next line. - `ModelDesignValidator` starts its lookup tables empty instead of null. A redundant `if (m_nodes == null) return;` guard is gone. - **Test code: minimal annotations.** - `?` on values that can be null, `!` where the test asserts non-null. - `= null!` for fixture members assigned in SetUp. - `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the `IEncodeable` constraint. - A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`) folded into the adjacent `IsNull`/`Count` checks. **Notes for reviewers** - The diff is large (~1,500 files) but almost entirely mechanical: `?`, `!` and directive removal. The non-annotation changes are listed above. - Known follow-up, not done here: `IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay non-nullable even though the validator returns null before it loads a dictionary. Making them nullable would ripple through every generator call site. ## Related Issues - Fixes #4439 ## 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. - [ ] 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. The "no new warnings" box covers net10.0 and net48: full non-incremental builds of `UA.slnx`, `tools/SourceGeneration.slnx`, `tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0 warnings and 0 errors under the repository's warnings-as-errors settings. net9.0 and net8.0 also had no nullable diagnostics, but they were not rebuilt after the final fixes. Tests have not been run locally yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5572107ab8 |
Push Model compliance: UpdateCertificate issuer check, TrustList audit events, NoConfiguration state, DefaultHttpsGroup (#4684)
# Description
Fixes found by a manual run of the OPC 10000-12 *Push Model for Global
Certificate and TrustList Management* test plan (183 test cases,
TC0–TC182) against the Reference Server. After these changes no test
case fails; the remaining non-pass verdicts are test-plan expectations
that contradict Part 12/Part 4/Part 20 (documented in the run report).
### Server behaviour changes
- **UpdateCertificate requires the issuer in the TrustList**
(`ConfigurationNodeManager.PushValidation.cs`). The validator classifies
an incomplete chain as suppressible; OPC 10000-4 Table 100 does not ("An
error during the chain creation may not be suppressed") and §7.10.5
requires the issuers to already be in the group's TrustList. A
certificate of an unknown CA is now rejected with
`Bad_CertificateChainIncomplete`. Expiry / host-name errors stay ignored
as §7.10.5 requires.
- **TrustList audit events reach the Server Object.**
`TrustListUpdateRequested`/`TrustListUpdated` were reported on the
TrustList node (`EventNotifier=0`, not linked to the Server), so no
client could receive them. `TrustList.SetAuditEventServer` routes them
through `IAuditEventServer`; wired for the Push TrustLists and the GDS
CertificateGroup TrustLists. New `AuditEvents` overloads keep the
node-based ones.
- **Duplicate AddCertificate** no longer makes the following
ApplyChanges fail with a bare `Bad` (store refused the second copy); the
commit is idempotent and its rollback keeps the pre-existing
certificate.
- **CloseAndUpdate validates CRLs** (incl. CRL-only updates): every CRL
must be signed by a CA of the new TrustList (§7.8.2.5). Previously an
unknown/forged CRL passed CloseAndUpdate and only failed ApplyChanges.
- **X.509 v1 certificates are rejected** by
CloseAndUpdate/AddCertificate/UpdateCertificate (OPC 10000-6 §6.2.2; new
`X509Utils.IsX509Version3`).
- **Application setup state (Annex G.2):** new
`StandardServer.StartupServerState` (virtual); the DI host returns
`NoConfiguration` when `ServerConfigurationOptions.InApplicationSetup ==
true`. `IsRunning` and `NodeManagerLifecycle` treat `NoConfiguration` as
operational. The Reference Server `--provision` mode starts in
`NoConfiguration` and exposes `InApplicationSetup=TRUE`.
- **DefaultHttpsGroup:** the group is only created when Https stores are
configured — the Reference Server config had none, so its
`opc.https`/`opc.wss` endpoints used the RSA application certificate
with an empty TLS trust list and the Https certificate could not be
pushed. The sample config now declares
`HttpsIssuerCertificates`/`TrustedHttpsCertificates` and an
`HttpsCertificateType` certificate (new `CertificateTypeString` value
`Https`). An Https certificate is assigned to DefaultHttpsGroup only (no
longer also listed in DefaultApplicationGroup). Fixed
`AddSecurityConfigurationHttpsStore`, which built the trusted Https
store path from the store *type*.
- Minor: AddCertificate logs a warning when it adds a CA without a CRL
while `RejectUnknownRevocationStatus` is enabled; UpdateCertificate
refused for an insufficient SecureChannel raises
`CertificateUpdateRequestedAuditEvent` with Status=false.
### Kept on purpose
- PEM certificates are accepted (converted to DER).
- `Bad_NotWritable` when opening an already open TrustList (OPC 10000-20
§4.2.2) and for AddCertificate while a read handle is open (§7.8.2.6).
- No validation rules for DefaultUserTokenGroup / DefaultHttpsGroup
TrustLists (Part 12 defines them only for ApplicationCertificateType
groups).
### Notes for reviewers
- Existing tests in `ConfigurationNodeManagerPushTests` and
`PushCertificateRequestRegressionTests` signed pushed certificates with
CAs that were not in the TrustList; they now trust their CA first
(behaviour change above).
- Adding the Https certificate to
`Quickstarts.ReferenceServer.Config.xml` gives the Reference Server's
opc.https/opc.wss endpoints their own TLS certificate.
### Tests
-
`tests/Opc.Ua.Server.Tests/Configuration/PushComplianceRegressionTests.cs`
— duplicate add/rollback, PEM, v1/v3, CRL issuer/forged/CRL-only, audit
routing.
- `tests/Opc.Ua.Server.Tests/ApplicationSetupServerStateTests.cs` —
NoConfiguration startup state.
- `tests/Opc.Ua.Gds.Tests/PushComplianceTests.cs` — end-to-end over a
secure channel: TrustList transaction lifecycle, open-handle rules,
masked reads, duplicate/PEM add, unknown CRL issuer, unknown certificate
issuer, cross-Session CSR key, nonce rules, RemoveCertificate chain/CRL
handling. Mutation-checked: reverting the issuer, duplicate-add and CRL
fixes makes the corresponding tests fail.
- Additional cases in `PushCertificateRequestRegressionTests`,
`CertificateIdentifierTests`, `ApplicationConfigurationBuilderTests`.
Local runs (net10.0): Opc.Ua.Gds.Tests 1257 passed / 0 failed / 56
skipped; Opc.Ua.Server.Tests push/TrustList/audit/lifecycle subset 1209
passed; Core and Configuration test classes passed. net48 build of the
affected test projects succeeds; `dotnet format whitespace` clean.
## Related Issues
- None (findings from a manual Push Model compliance run).
## 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.
- [ ] 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.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
e78c648295 |
Images CI: publish samples once natively and stop compiling under QEMU (#4679)
# Description
Images CI took 45–100 min per image. The arm64 half of every
`linux/amd64,linux/arm64` build ran `dotnet restore` and `dotnet
publish` in an arm64 SDK container under QEMU. For `refserver`, publish
took 8 min natively for amd64 and 92 min emulated for arm64. On top of
that, every one of the 10 matrix legs recompiled the whole stack.
The output is framework-dependent portable IL, so the compile is now
done once, natively. The image legs only copy files onto the
per-platform runtime base image.
### Dockerfiles (all 10)
- **SDK stage runs on the build platform.** The `build` stage is pinned
to `--platform=$BUILDPLATFORM`. `-p:UseAppHost=false` keeps an x64 app
host out of the arm64 image.
- **New `prebuilt` stage.** It is chosen with a global `ARG
PUBLISH_SOURCE`, which defaults to `build`. Local `docker build` and the
`docker-compose` files are unchanged.
```dockerfile
FROM scratch AS prebuilt
ARG PUBLISH_DIR=artifacts/docker/publish
COPY ${PUBLISH_DIR}/ /app/publish/
FROM ${PUBLISH_SOURCE} AS publish
```
- **Pump: final-stage `RUN`s moved to a native stage.** The `sed` on
`openssl.cnf` and the `mkdir`/`chown`/`chmod` steps now run in a
`$BUILDPLATFORM` `layout` stage. The final stage copies `/layout/` to
`/`, so `/app` and `/diag` keep the `$APP_UID:0` owner and `g=u` mode
they had before. `openssl.cnf` therefore comes from the build-platform
base image. It is a plain config file, so it is expected to match
between architectures.
### `docker-image.yml`
- **Single image list.** The image list lives once, in `env.IMAGES`
(image, project, Dockerfile). `discover` emits it as the matrix.
- **New `publish-samples` job.** It runs `dotnet publish` for each
sample sequentially, so shared libraries compile once. It uses the same
options and NBGV version properties as the Dockerfiles and uploads the
output as the `docker-publish` artifact.
- **`build-and-push-image` legs.** Each leg:
- downloads the artifact;
- builds with `PUBLISH_SOURCE=prebuilt` and
`PUBLISH_DIR=artifacts/docker/publish/<image>`;
- no longer uses `setup-qemu-action`;
- is named `build-and-push-image (<image>)`.
- **`images summary`.** It now also fails when `publish-samples` fails.
### Docs
- `ContainerReferenceServer.md` describes the publish-once / copy-only
flow and the build arguments.
- `DeveloperGuide.md` mentions the `publish-samples` job in the Images
CI section.
Expected: one native publish of about 7 min, then 10 parallel
copy-and-push legs of a few minutes each. Not yet confirmed on CI: the
sandbox could not pull MCR base images, so the prebuilt multi-platform
builds were checked against a Debian-based stand-in base, and the
build-from-source path was not built.
## Related Issues
## 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.
- [ ] 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 <7413710+romanett@users.noreply.github.com>
|
||
|
|
7212607f16 |
OPC UA Part 3 compliance: permissions, Write semantics, NodeManagement, NodeSet DataTypes, locales (#4675)
# Description Fixes the High and Medium findings of an OPC UA Part 3 (OPC 10000-3, Address Space Model) compliance audit. Each finding was checked against the full spec text (Parts 3, 4, 5, 6, 14 and 18) before it was fixed. Every fix comes with regression tests that fail without it. ### Role permissions (Part 3 §8.55, Part 4 §5.12.2.2) - The type-hierarchy exemption in `MasterNodeManager.ValidateRolePermissions` / `ValidateAccessRestrictions` now covers only Browse, Read and ReadRolePermissions. Type nodes stay visible to generic clients, which was the intent of #3705; every other permission bit is now enforced on them. - `ReceiveEvents` is enforced on the EventType node. The standard NodeSet grants it on `AuditEventType` and its subtypes only to SecurityAdmin, so only SecurityAdmin sessions now receive audit events. - Call checks the `Call` permission and AccessRestrictions on the Object as well as on the Method. - When a Call's `methodId` is the Method declaration of the ObjectType, the Object's Method with the same BrowseName is permission-checked and invoked. The declaration is used only if the Object has no such Method. This applies to `AsyncCustomNodeManager`, `CustomNodeManager2` and the sample node manager. ### Write service (Part 3 §5.6.2, §8.40, §8.57; Part 4 §5.11.4) - The synchronous `OnWriteValue` path (fluent `OnWrite`) checks DataType and ValueRank first (`Bad_TypeMismatch`). After an IndexRange write it merges the slice into the cached value instead of replacing it. - StatusWrite / TimestampWrite are enforced (`Bad_WriteNotSupported`). - Undefined enumeration values and invalid OptionSet writes return `Bad_OutOfRange`. OptionSet writes merge only the valid bits into the stored value. - New `ReportPropertyValueChanged` / `ReportSemanticChange` let server code raise SemanticChangeEvents for changes it makes itself. - Required follow-up (Part 14 §6.2.11.1): if an external target rejects a timestamp, the PubSub subscriber's TargetVariables writer retries without it. ### NodeManagement and Call arguments (Part 3 §5.5.2, §5.7.3, §6.4.2; Part 4 §5.8, §5.12.2) - AddNodes creates the Mandatory InstanceDeclarations of the TypeDefinition and rejects abstract or Interface types (`Bad_TypeDefinitionInvalid`). - AddReferences, DeleteReferences and DeleteNodes bump NodeVersion and raise GeneralModelChangeEvents. - Clients may omit trailing optional input arguments (`HasOptionalInputArgumentDescription`). ### NodeSet DataTypes (Part 3 §5.8.3, §8.48; Part 6 Annex F.12) - `UANodeSet.Import` completes structure definitions with the base type's fields (base fields first). It creates definitions for structures without fields and sets `DefaultEncodingId`. `StructureDefinition.FirstExplicitFieldIndex` marks the subtype's own fields. - `DataTypeState` no longer mutates the stored definition on read. ### Locales (Part 3 §5.2.5, §8.5; Part 4 §5.4) - Read, Browse and data change notifications return the translation that best matches the session's LocaleIds, or all translations for `mul`. The selection lives in the server dispatcher and is built on `ResourceManager`: the new `TranslateValue` calls the overridable `Translate`. - Locale ids are matched case-insensitively, and fallback translations carry their real locale. ### Client complex types (Part 6 §5.1.7) - The Reflection.Emit builder encodes only `IsOptional` subtyped fields as ExtensionObject. It also supports OptionSet subtypes: an unsupported type is skipped instead of aborting the whole load. - `TypeInfo.GetXmlName` uses the data type's XML name for dynamic structures and OptionSets (the XML body element was `<Structure>`). Both builders now produce identical XML. ## Behaviour changes All are documented in `docs/MigrationGuide.md`. - **Audit events:** only SecurityAdmin receives them. The CTT "Auditing Connections" unit needs a SecurityAdmin user (e.g. sysadmin); `AuditingOperationTests` was updated. - **Write timestamps and status:** clients writing a SourceTimestamp or a non-Good StatusCode get `Bad_WriteNotSupported` unless the Variable has StatusWrite/TimestampWrite. No variable in `src/` sets these bits. - **AddNodes:** it rejects abstract types (including `BaseVariableType` and `BaseEventType`), and new instances now come with child nodes. - **Call:** the Object needs the Call permission. The Object's own Method runs even when the call names the type declaration. ## Not included - Low-severity findings: missing validation on optional services and author-side NodeSet rules. - LocalizedText nested inside structures or Variant arrays, and in HistoryRead or Call outputs. ## Testing net10.0, Debug, one suite at a time on the merged branch. All suites below pass: | Suite | Passed | |---|---| | Types | 11430 | | Core.Encoders | 2947 | | Core.Schema | 179 | | Core.Security | 558 | | Client | 2761 | | Server | 8210 | | InformationModel | 1080 | | Features | 145 | | History | 536 | | Di | 455 | | Machinery | 167 | | Robotics | 661 | | Robotics.Intent | 22 | | Pumps | 75 | | Scales | 122 | | Positioning | 69 | | ISA95 | 144 | | Vision | 484 | | PubSub | 1490 | | PubSub.Adapter | 151 | | PubSub.Server | 177 | | Redundancy.Server | 856 | | Lds | 173 | | Configuration | 241 | | WotCon | 1496 | - **Client.ComplexTypes:** one test wrote a SourceTimestamp to a variable without TimestampWrite. It is fixed in this PR, and the suite has not been re-run since. - **Core:** 6068 passed, 1 failure in `ActivityTraceFlags_Metrics_ShouldRecordMetricsOnly`. That telemetry test passed when re-run alone, so it looks flaky. - **Still running:** Sessions, Subscriptions (Classic, Durable, standard) and Interop. Results will follow on this PR. - `UA.slnx` builds with 0 errors for net10.0 and net48, with no new analyzer warnings in changed files. ## Related Issues - Part 3 compliance audit (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. - [ ] 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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cce2004fdd |
CTT: keep GeneratesEvent off Object instances and Views off non-hierarchical references; classify Deadband Err-006 as CTT defect (#4667)
# Description Three findings from the 2026-10-03 CTT run with the patched 1.05.513 scripts. Two are server/model defects and are fixed here. The third is a CTT script defect, recorded as C56 in `plans/ctt-issues.md`. ## 1. Monitored Items Deadband Filter `Err-006.js`: CTT defect (C56, no server change) The script modifies a MonitoredItem on the first static numeric scalar (a plain BaseDataVariable with no EURange) to use a PercentDeadband. It accepts only `Good` or `Bad_MonitoredItemFilterUnsupported`. Since #4629 the server returns `Bad_DeadbandFilterInvalid`, and that is the code the spec defines for this case: - OPC 10000-8 §7.2: PercentDeadband "applies only to AnalogItems with an EURange Property". - OPC 10000-8 §7.3.2 Table 61: `Bad_DeadbandFilterInvalid` is returned when "a PercentDeadband is not supported, since an EURange is not configured". CreateMonitoredItems and ModifyMonitoredItems both go through `ValidateMonitoringFilterAsync` and return the same code. Monitor Basic `Err-022.js` already accepts `Bad_FilterNotAllowed` for non-analog items, so the scripts disagree with each other here. Draft Mantis note (not filed yet): > **Monitored Items Deadband Filter Err-006.js rejects Bad_DeadbandFilterInvalid for a PercentDeadband on a node without EURange** > Err-006.js picks the first node of `/Server Test/NodeIds/Static/All Profiles/Scalar/NumericSettings`, which is normally a BaseDataVariable without an EURange. It then calls ModifyMonitoredItems with a PercentDeadband (10 %) and accepts only Good or Bad_MonitoredItemFilterUnsupported. OPC 10000-8 §7.2 limits PercentDeadband to AnalogItems with an EURange. §7.3.2 Table 61 defines Bad_DeadbandFilterInvalid for "a PercentDeadband is not supported, since an EURange is not configured", so a compliant server fails this test. Monitor Basic Err-022.js already accepts Bad_FilterNotAllowed for the non-analog fallback. > Proposed fix: add StatusCode.BadDeadbandFilterInvalid (and, consistent with Err-022.js, StatusCode.BadFilterNotAllowed) to the ExpectedAndAcceptedResults of the ModifyMonitoredItems call. ## 2. GeneratesEvent on Objects (Base Info Core Structure 2 `001.js`): source generator defect The CTT reported `ns=9;i=234` (the `<ServiceName>` placeholder of AuthorizationServicesFolderType) and `ns=9;s=AuthorizationServices/Default` as Objects that are the source of GeneratesEvent references. OPC 10000-3 §7.15 says: "The SourceNode of References of this type shall be an ObjectType, a VariableType or a Method". `OpcUaGdsModel.xml` declares both references (AccessTokenRequested/Issued audit events) correctly, on `AuthorizationServiceType`. The source generator copied the type's own non-hierarchical references onto every Object or Variable built from the type: onto instance declarations (the placeholder) and into `CreateInstanceOf<Type>`, which `AuthorizationServiceState` initialises from, and that is how the `Default` service gets them. UA-ModelCompiler does the same thing, so the official `Opc.Ua.Gds.NodeSet2.xml` in UA-Nodeset has the same two references on `ns=1;i=234`. Fix: - `TranslateReferences` now marks references declared on a type (`HierarchyReference.DefinedOnType`, which already existed but was never set). - `NodeStateGenerator.GetReferences` drops forward GeneratesEvent references (including subtypes such as AlwaysGeneratesEvent) for Object and Variable nodes when the reference came from the type. - ObjectTypes, VariableTypes and Methods keep these references. For example, the RegisterApplication methods and the Acknowledge/AddComment methods of the alarm types are unchanged. - References that a model declares explicitly on an instance are emitted as before. ## 3. View targeted by HasCause/HasEffect (Base Info Core Structure 2 `001.js`): sample model defect #4485 added `Views_Operations HasCause/HasEffect Views_Engineering` so that View Minimum Continuation Point 01 `012.js` and View Basic 2 `018.js` have at least two references to View nodes on the configured View (`/Server Test/NodeIds/NodeClasses/View` = `Views_Operations`). OPC 10000-3 §5.4 says Views "can be the SourceNode of any hierarchical Reference. They shall not be the SourceNode of any NonHierarchical Reference." `Views_Operations` now Organizes `Views_Engineering` and a new View, `Views_Maintenance`, which organizes `DataAccess`. This keeps two View-class references and adds no loops (ContainsNoLoops stays true). ## Tests - `NodeStateGeneratorTests.TypeGeneratesEventReferencesAreNotCopiedToObjectInstances`: covers the type, the placeholder, the instance, `CreateInstanceOf`, Methods and an explicit instance reference. It fails without the fix. - `ReferenceServerTest.SampleViewsHaveNoNonHierarchicalReferencesAsync`. The existing `NodeClassSampleNodesReferenceTheirOwnNodeClassAsync` still passes. - Results with net10.0: - SourceGeneration.Core.Tests: 4267 passed, 0 failed - InformationModel.Tests: 1078 passed, 0 failed - Di.Tests, including RobotOpenUsdE2e: 438 passed, 0 failed - OpenUsd.Tests: 921 passed, 0 failed - Server.Tests `ReferenceServerTest`: 56 passed, 0 failed - `UA.slnx` builds with net10.0. The changed test projects build with net48. ## CTT (patched 1.05.513 scripts, compared with the 2026-10-03 patched baseline) | Part | Change | | --- | --- | | p02-base-info | Base Info Core Structure 2 `001.js`: Error → Skipped. The GeneratesEvent and View errors are gone; the remaining skips are the CTT's own "type not defined within the test-script" notes. No other change. | | p03-base-view-method | No change. View Basic 2 `018.js` and View Minimum Continuation Point 01 `012.js` still pass. | | p13-monitored-items | Deadband `Err-006.js` still Error (C56). With the script fix it passes. Monitor Basic `007.js` gave a response-timestamp delay warning (>200 ms) caused by machine load; it also appears in the 2026-09-29 base run. | 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b25c1f6ad4 |
Asset Management Basics: the OPC 10000-110 companion SDK (#4662)
# Description Stacked on #4650. Until that is merged, this PR also shows its commits; the six commits of this PR start after `fix(server): keep conformance unit aggregation idempotent and ordered with registrations`. This adds OPC 10000-110 Asset Management Basics 1.01.1 (`http://opcfoundation.org/UA/AMB/`) as a companion SDK, in the pattern of the DI, Machinery, Pumps and Scales packages. The tracking issue #4649 describes the scope, the design and the open questions. In short: | Package | Content | |---|---| | `Opc.Ua.AMB` | the model generated by the source generator from the unmodified NodeSet, plus the contracts server and client share (`AssetFaultSeverity`, `AssetLocationKind`, `MaintenanceStateKind`, `DocumentationLinkProperties`) | | `Opc.Ua.AMB.Server` | a sidecar node manager next to the owner of the Device Integration address space (`AddOpcUaDi`, `AddMachinery`, `AddPumps` or `AddScales`), the asset registry `IAssetManagement` with a fluent builder, `AddAssetManagement()` hosting, memory and file system stores, the AMB conformance units and server facet | | `Opc.Ua.AMB.Client` | `AmbClient`: discovery, identification, health alarm and maintenance streams, documentation links, locations, structure and snapshots, plus `AddAssetManagementClient()` | **Sidecar.** An asset stays in the node manager that owns it. Everything AMB adds to it (`DeviceHealth`, health alarms, maintenance conditions, the DocumentationLinks AddIn, AssetId and the other properties) is created there. The AMB node manager serves the AMB model, the alias categories `Assets`, `AssetsByProductInstanceUri` and `AssetsByAssetId` below `0:Aliases`, the location hierarchies below `0:Locations` and the server-specific alarm and condition types. Owners register assets with the shared registry from their setup, in either startup order. **Implemented conformance units** (Table 54): Asset Identification, Configurable Asset Identification, Discovery by ProductInstanceUri and by AssetId, Health Status Base, Alarms, Root Causes and Alarm Categories, Version Information, Operation Counters (inspected only; AMB defines no counters), DocumentationLinks Base, Edit Base and Edit Advanced, Requirements, Capabilities, Classification, Current and Future Maintenance Activities, Local Time, the three location properties and both location object hierarchies, Sub-assets and Asset relations. The two history units (Health Tracking, Past Maintenance Activities) are not part of this PR. `docs/AMB.md` has the matrix with the rule the server applies for each unit and whether a test checks it over a session. **Dependency on #4650.** The health alarms and maintenance conditions are created through `CreateAlarm<TState>`, and the conformance units follow the registered assets through the recomputation in `ConformanceUnitsManager`. ## Changes Six commits, each building on its own: 1. **`feat(amb)`: model package** (`src/Opc.Ua.AMB/`, about 2.4k lines, of which the NodeSet and the two identifier tables are 1.6k). - The NodeSet is the publication from `UA-Nodeset/latest/AMB`, byte for byte (git blob `21f9475b646e9c5508171277ffe5b74edbeb0a58`). `.gitattributes` checks it out with LF endings, because a test pins its SHA-256. - The published identifier table is vendored unchanged; a derived table pins the symbol-to-NodeId mapping, and the generator fails the build when they disagree. `tools/nodesets/README.md` documents the one new kind of renamed symbol (top-level instances below `0:Aliases` and `0:Locations`). 2. **`feat(amb)`: server package** (`src/Opc.Ua.AMB.Server/`, about 10.7k lines). Node manager, registry and builders, discovery, health, maintenance (`MaintenanceEventStateMachine` with all transitions), documentation links, locations, version information, classification (IRDI dictionary entries below `Server/Dictionaries`) and structure, stores, conformance evaluation, hosting. Log messages use the EventId block 8300 to 8379, after Pumps (8100) and Scales (8200). 3. **`feat(amb)`: client package** (`src/Opc.Ua.AMB.Client/`, about 3.0k lines). Calls AddLink and RemoveLink through the generated `DocumentationLinksTypeClient`. 4. **`test(amb)`: test suite** (`tests/Opc.Ua.AMB.Tests/`, 163 tests, about 8.8k lines; commit 6 adds one more). 5. **`docs(amb)`: guide and samples**: `docs/AMB.md`, links from `README.md`, `docs/README.md`, `docs/Samples.md` and `docs/AliasNames.md`; `samples/AMB/AmbServer` (AMB next to `AddMachinery`: two DI devices and a Machinery press as assets, with a simulation) and `samples/AMB/AmbClient`. The sample server listens on 62553, which no other sample uses. 6. **`feat(amb)`: tell the links users added from the links of the manufacturer.** OPC 10000-110 gives a client no way to see which links `RemoveLink` accepts (§10.5.4: only those `AddLink` created). As §10.5.1 lets vendors add metadata Properties to the links, the server gives every link `AddLink` created, and every such link restored after a restart, the `Boolean` Property `UserLink` (`true`), qualified with its namespace of server-specific types, with a NodeId derived from the persisted link identifier; declared links carry none, and `RemoveLink` deletes it with the link. `DocumentationLinkRecord` gets the additive `IsUserLink`, which `ReadDocumentationLinksAsync` fills with one Browse over all links and one Read of the markers found (comparing the name only, as the namespace is configurable; `false` for a server that does not mark links). The name is shared as `DocumentationLinkProperties.UserLink`. `UA.slnx` and `.azurepipelines/expected-packages.txt` get the new projects and packages in the commit that adds them. All projects target `net48;net8.0;net9.0;net10.0` through the repository properties (`LibTargetFrameworks`, `TestsTargetFrameworks`, `AppTargetFrameworks`); `IsAotCompatible` and `PublishAot` are set from net10.0 on, as in the other companion projects. ## Tests - **`Opc.Ua.AMB.Tests`: 164 tests**, most of them against a hosted server over real client sessions: - the model: the SHA-256 of the NodeSet and the generator behaviours AMB is the first companion model to rely on; - each building block in-process and over a session: discovery through every category with NodeVersion and model change events, AssetId writes, health alarms and maintenance activities through all transitions with acknowledgement, AddLink and RemoveLink, locations and structure, the advertised conformance units and the facet; - a restart with the file system stores (links and AssetId survive); - the `UserLink` marker: present on an added link only, the same NodeId after a restart, removed with `RemoveLink`, and `IsUserLink` on the client in the snapshot, after `AddLink`, after a restart and after `RemoveLink`; - composition with `AddOpcUaDi` (both startup orders), `AddPumps` and `AddMachinery`; - `AmbClient` end to end against a hosted server, including a Machinery machine as an asset. - **Results (macOS, local):** 164 passed / 0 failed on net10.0 and on net8.0. .NET Framework is covered by the fork CI's Windows legs. - **Coverage** of the three AMB assemblies (coverlet with `tests/coverlet.runsettings.xml`, net10.0, generated `*.g.cs`, `*TypeClient` proxies and `*Log` classes excluded): lines 93.9 % (4890 of 5206), branches 84.0 % (1294 of 1539); model 100 %, client 94.5 %, server 93.4 %. - **Build:** the three packages, the tests and the two samples build without warnings for net48, net8.0, net9.0 and net10.0; `UA.slnx` builds in Release (its warnings are all in files this PR does not touch, as on master). `dotnet format` (whitespace, style, analyzers with `--severity info`) reports nothing on the 95 changed C# files. - **Samples:** `AmbServer` and `AmbClient` ran against each other on net10.0: the client discovered the three assets, printed their snapshots and the location hierarchies, added and removed a documentation link and streamed alarms and maintenance. - Fork CI (full matrix incl. macOS) and CodeQL: green on the same five package commits before the rebase onto the current #4650 head — [CI run 37144486107](https://github.com/biancode/UA-.NETStandard/actions/runs/37144486107), [CodeQL run 37144487873](https://github.com/biancode/UA-.NETStandard/actions/runs/37144487873). The run on the current head (rebased, plus the user-link commit; AMB tests 164/164 locally on net10.0) is in progress: [CI run 37203517457](https://github.com/biancode/UA-.NETStandard/actions/runs/37203517457). ## Notes for review - **Size.** About 26.8k lines, larger than each of the companion PRs #4577 to #4580. Without changes to the code it splits into the model, server and server tests, and the client, end-to-end tests, samples and docs on top; the issue asks which you prefer. - **Interfaces on types.** §9.4.1 and §12.1 ask for alarm and condition types that implement `IRootCauseIndicationType` and `IMaintenanceEventType`. The server creates subtypes of the DI alarm types in its own type namespace (`urn:opcua-netstandard:amb:types`); `UseServerDefinedAlarmTypes = false` uses `HasInterface` on the instance instead, and then does not report the units that need the type-level interfaces. - **Conformance unit names** follow Tables 54 to 56; the profile database has no AMB entries yet. The QualifiedNames are in namespace 0, as DI and Machinery publish theirs (question 5 in the issue). - **Generator gap.** Children a NodeSet gives to a top-level instance (here the `NodeVersion` properties of the alias categories) get no identifier constants. The fix changes the identifiers of other generated models, the reference server's among them, so it is not part of this PR; `AmbModelTests.NodeVersionHasNoIdentifierConstant` records it and fails once the generator emits the constants. - **net48.** The code that the SDK so far only built for .NET 8 and later uses the `NET5_0_OR_GREATER` pattern of `JsonEncoder` for `Enum.IsDefined` and API forms that .NET Framework has (`string.Format`, `Guid.ToString(format, provider)`); the tests use a small async file helper instead of `File.WriteAllTextAsync`. - **User links.** The `UserLink` Property is metadata of this server, not an AMB name: commit 6 explains why and how a client uses it. Changed lines of that commit covered: 69/70 (the one line left is the loop continuation for a link that has other Properties but no marker). - **Known limitations** are listed in the guide: the `DeviceHealth` SourceTimestamp across a restart, removing optional maintenance members once set, `PatchIdentifiers`, locations only at the deepest level, and the history units. ## Related Issues - Part of #4649 - Stacked on #4650 ## 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. (The AMB suite on net10.0 and net8.0 locally on macOS; .NET Framework is covered by the fork CI's Windows net48 legs.) - [ ] 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> |
||
|
|
2871355d18 |
GDS: implement the AliasName Server facet (OPC 10000-17 Annex C) (#4668)
## Summary Implements the **GDS AliasName Server facet** (OPC 10000-17 Annex C) in `Opc.Ua.Gds.Server`, so that the CTT conformance unit *GDS AliasName Discovery* passes apart from two CTT script defects (documented, Mantis drafts in `plans/ctt-issues.md`). Spec requirements: - **C.1:** aggregate the AliasNames of registered Servers that expose the `ALIAS` capability (OPC 10000-12 Annex D). Pull all AliasNameCategory instances and merge those whose BrowseNames have the same Name. Provide the ExpandedNodeId of every referenced node and the ServerUri of the Server that contains it. - **C.2:** on registration, merge the Server's AliasNames into a single tree for TagVariables/Topics, combine aliases that share a name, and add the ServerUri to `ServerArray`. - **C.3:** on unregistration, remove that Server's aliases and categories (but keep those other Servers still provide), remove its AliasFor references, and remove its ServerUri from `ServerArray`. ### Design - **`AliasNameAggregator`** (`Opc.Ua.Gds.Server.AliasNames`) holds one snapshot per registered application and builds the merged view: - Categories are keyed by their BrowseName path below `Aliases`. Aliases are merged by name within a category; their targets are the union over all sources. - Targets are stored as namespace-URI ExpandedNodeIds. The ServerIndex is resolved against the server's `ServerArray` each time the view is built, because removing a ServerUri renumbers the entries after it. - Only ServerUris the aggregator added itself are removed again. - Also implements `FindAlias`/`FindAliasVerbose`, recursive over sub-categories. - **`SessionAliasNameSourceReader`** reads a source over a client Session by browsing the `Aliases` (i=23470) hierarchy and the AliasFor references. - It browses instead of calling `FindAlias` because the category structure has to be aggregated too, and a source whose aliases come from a NodeSet (like the CTT's embedded server) does not implement `FindAlias`. - It connects anonymously, to the most secure endpoint first, falling back to SecurityMode None. It runs GetEndpoints once per DiscoveryUrl. - **`ApplicationsNodeManager`** (new partial `ApplicationsNodeManager.AliasNames.cs`): - Hooks RegisterApplication (now `OnCallAsync`), UpdateApplication (background re-read) and UnregisterApplication. - Materializes the merged view as `AliasNameType` and `AliasNameCategoryType` nodes in the GDS application-record namespace, organized by the well-known TagVariables/Topics/Aliases objects. Each category gets a `FindAlias` answered from the merged list. - Changes and node updates are serialized, so a read that finishes after a concurrent unregister cannot re-add the source. - RegisterApplication waits at most `AliasNameRegistrationWait` (5 s) for the read; a slower or unreachable source is finished in the background and never blocks clients past their call timeout. - Optional periodic re-read via `AliasNameRefreshInterval`; it also retries sources that could not be read. - **`IAliasNameStoreRegistry.RegisterContributor`/`UnregisterContributor`** (Opc.Ua.Server): a read-only contributor's aliases are added to the `FindAlias`/`FindAliasVerbose` results of the categories it reports, next to the owning store's. This lets the aggregated aliases share the well-known categories with the server's own aliases, which matters for the reference server, an AliasName Server and a GDS at once. Contributors never receive Add/Delete calls. - **Opt-in:** `GlobalDiscoveryServerConfiguration.EnableAliasNameAggregation` is off by default, because the GDS then opens client connections to the DiscoveryUrls of registered applications. `Ctt.ReferenceServer.Config.xml` enables it. - `Opc.Ua.Gds.Server` now references `Opc.Ua.Client`; the `Opc.Ua` meta-package already depends on both. ### CTT (1.05.513 with the patched scripts), p14-gds, reference server `--ctt`, embedded CTT source + 2 extra AliasName sources | GDS AliasName Discovery | before | after | after, with the C54/C55 script fixes | | --- | --- | --- | --- | | initialize | Skipped (no sources) | Pass | Pass | | 001 | Error | Error (C55) | Pass | | 002 | Error | Error (C54) | Warning (RegisterApplication > 100 ms) | | 003 | Pass | Pass | Pass | | 004 | Error | Error (C54) | Pass | | 005 | Skipped | Error (C55) | Pass | | 006, 007, 009, 011, 012, 014, 015 | Skipped | Error (C54) | Pass | | 008 | Skipped | Pass | Pass | | 010, 013 | Skipped | Error (C55) | Pass | - **C54:** the script compares the GDS's AliasFor target (ServerIndex > 0, namespace URI, as Annex C.1 and Part 4 §7.16 require) with the source's local NodeId using `equals()`, which can never match. - **C55:** "no remote aliases" is checked as "no aliases", so it fails on the reference server's own aliases. Both are documented with draft Mantis texts (not filed). The remaining script checks pass in 15/15 test cases. No regressions: - Application Directory and Query Applications are unchanged against the baseline. The only difference is one ActivateSession timing warning in one run. - p09 (AliasName CUs) passes 32/32, identical to the baseline. ## Tests - `AliasNameAggregatorTests` (unit) covers: - merging across Servers; - categories kept while still referenced and removed after the last source; - ServerArray add, remove and renumbering; - pre-existing ServerUris kept; - local targets; - FindAlias/FindAliasVerbose scope, pattern and reference-type filter. - `GdsAliasNameAggregationTests` (end-to-end) registers a second in-process reference server with `ALIAS` and reads it with the real session reader. It checks: - aggregated nodes and remote AliasFor targets; - the ServerArray entry; - FindAlias on TagVariables and on the aggregated category; - that unregistering removes everything; - that an unreachable source still registers; - that a Server without `ALIAS` is ignored. - Registry contributor tests in `AliasNameStoreRegistryTests`. - Opc.Ua.Gds.Tests: 1246 passed, 0 failed, 56 skipped. Opc.Ua.Server.Tests AliasNames: 198/198. InformationModel and Features alias tests pass. net48 compiles. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4c1c3fd244 |
Aggregates: fix remaining CTT aggregate server defects and seed Bad data for the CTT (#4655)
Fixes the remaining CTT Aggregate failures of the reference server that are server defects or missing test data, using the patched CTT 1.05.513 scripts (all known Mantis defects fixed) as the oracle. ## Server fixes (`src/Opc.Ua.Server`) - **Real-valued bounds for TimeAverage/TimeAverage2/Total/Total2.** `SlopedInterpolate` converted every interpolated bound to the raw data type, so on Int32 nodes these Double aggregates used rounded bounds (ramp 0..23.8: 11.9034 instead of 11.9). The conversion (Part 4 §7.7.3) is now only applied for aggregates that return the raw data type (Interpolative, Start/EndBound, DeltaBounds, Minimum2/Maximum2/Range2, ActualTime2); `AverageAggregateCalculator` keeps the real bound (Part 13 §3.1.8, Table 50). - **Non-numeric values are interpolated stepped.** Boolean, String and other non-numeric values cannot be interpolated with a sloped line (Part 13 §5.4.2.3, Table 50). The calculator now uses stepped interpolation for them whatever the Stepped property says: a String EndBound between "23" and "24" is "23" (was "23.8"), a Boolean bound between false and true is false, and Boolean DurationInState no longer counts the region before the first value as true. - **TreatUncertainAsBad = false in Average and Count.** Both used only strictly Good values for the result but counted Uncertain values as Good for the status. They now include Uncertain values when TreatUncertainAsBad is false (Part 13 §4.2.1.2), like StandardDeviation and NumberOfTransitions already did. - **Interpolative honors TreatUncertainAsBad for a raw value at the interval start**, as the calculator already did for the raw values around a bound. - **NumberOfTransitions**: a Bad result carries no aggregate bits, like Count. - **Processed reads get the bounding values they need** (`HistorianDispatcher`). The raw read only returned the raw value at or beyond each end of the request. When that value is Bad (or Uncertain with TreatUncertainAsBad), the interpolated bound needs the nearest Good value further out, so an interval starting on a Bad value returned `BadNoData` and the next interval lost its first region. The dispatcher now reads outwards to the nearest Good values (reusing the ReadAtTime bound search), only when the end value is not Good. ## Reference server test data and CTT template - Every scalar history node also gets 400 values at a fixed date (`2026-01-01T00:00:00Z`) with a block of five Bad values, laid out for the CTT's Bad-data search (`HAAggregateHelper.GetBadData`). StartOfArchive is the fixed date. The values relative to the server start are unchanged. - `samples/UAReferenceServer.ctt.xml`: all ten `.../Aggregates/StartOfBadData*` settings point at the block (`2026-01-01T00:25:01.234Z`), and `RequestedNodeId` is enabled with namespace 2 (needs the C34 fix). - `plans/ctt-issues.md` / `plans/ctt-testing.md`: updated configuration notes, the oracle differences, U4, and new U5 (TreatUncertainAsBad = false). Also documents a CTT defect: the Bad-data search's "reverse" read sends EndTime = 0001-01-01 (null), which a server reads forward. ## CTT results (p06 Aggregates, 1267 test cases) | | Error cases | | --- | --- | | Before (patched scripts, origin/master) | 253 | | This PR, patched scripts | 125 | | This PR, patched scripts + proposed Int32 oracle corrections for TimeAverage/Total(2) and Range2 | 51 | The 125 include 78 TimeAverage/TimeAverage2/Total/Total2 and 4 Range2 test cases on Int32 nodes where the server now returns the Part 13 value but the patched oracle still truncates the bounds. The remaining 51 are spec questions: U4 (DeltaBounds, 9; TimeAverage2/Total2 with an Uncertain value after the start bound, 8) and U5 (TreatUncertainAsBad = false, 34). p07 Historical Access: no change (0 errors). ## Tests - New `AggregateBoundDataTypeTests` (real bounds on Int32, Interpolative still rounds, Boolean/String stepped bounds, Boolean DurationInState, Average with TreatUncertainAsBad). - `AggregateCttRegressionTests`: direct and live (dispatcher) interval starting on a Bad value, Interpolative with TreatUncertainAsBad, NumberOfTransitions Bad result; the Count test now expects Uncertain values to be counted with TreatUncertainAsBad = false. - `AggregateCalculatorCoverageTests`: an Uncertain raw value at the start with TreatUncertainAsBad is interpolated over. - `Opc.Ua.Server.Tests` and `Opc.Ua.History.Tests` pass on net10.0; net48 builds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
efad0ddeff |
Server subscriptions: fix spec defects in Subscription/MonitoredItem handling found by audit (#4629)
# Description Fixes defects found in a spec-grounded audit of the server **Subscription / MonitoredItem** implementation (`src/Opc.Ua.Server/Subscription/**`, `NodeManager/MonitoredItem/**`, `EventManager`, monitored-item service dispatch and filter validation). Each behaviour change was checked against OPC 10000-4 (§5.13, §5.14, §7.21, §7.22), Part 5 (subscription diagnostics) and Part 9 (ConditionRefresh). Every fix has a regression test. The audit found 56 defects (1 Critical, 3 High, 20 Medium, 32 Low). A follow-up max-effort code review of the fix branch found 27 more issues, mostly introduced by the first round of fixes. All but one are fixed here. ### Most important fixes - **Critical — remote hang:** an `AggregateFilter` with `processingInterval = NaN` was not revised, and `AggregateCalculator` looped forever while holding the node-manager lock, so one CreateMonitoredItems request blocked the node manager. Non-finite, ≤0 and sub-tick intervals are now revised (Part 4 §5.13.2.1), and the calculator rejects them as a backstop. - **Monitored-item limits:** new `ServerConfiguration.MaxMonitoredItemCount` / `MaxMonitoredItemsPerSubscription`; Bad_TooManyMonitoredItems is returned when a limit is hit, and ServerCapabilities now report the limits. Both default to `0` (no limit), so existing behaviour is unchanged. - **Sampling groups:** a client could get one dedicated thread per distinct sampling interval above 1 h; intervals above the last rate group are now rounded, and the loop is async. An item could also end up in two sampling groups and keep being sampled after it was deleted. - **Subscription state machine** (Part 4 §5.14.1.2): - the lifetime counter resets while a Publish is queued and counts otherwise; - the lifetime and keep-alive stay within `MaxSubscriptionLifetime`; - LatePublishRequestCount is counted once per entry into LATE; - messages held while publishing is disabled are no longer sent, and are not offered by Transfer/Republish either; - ConditionRefresh returns Bad_RefreshInProgress for any refresh already queued or running on the subscription. - **Triggering** (Part 4 §5.13.1.6): - linked items are triggered by an item still draining a backlog, by the first value after re-enable, and by an item created Disabled; - links are persisted for durable subscriptions; - Disabled/Sampling items follow the publish rules in the shared pipeline, so custom `IMonitoredItem` implementations follow them too. - **Queues** (Part 4 §5.13.1.5, §7.21): - no duplicate value after a queue-size increase; - EventQueueOverflow events are placed where the events were lost, including on queue shrink; - the duplicate-event check scans the newest entries; - EventQueueOverflowCount and MonitoringQueueOverflowCount are maintained correctly; - changing select clauses rebuilds queued events in the new layout, and is safe on durable queues and on failure. - **Filters:** - per-clause EventFilterResult instead of rejecting the whole item (Part 4 §7.22.3); - corrected status codes: Bad_DeadbandFilterInvalid, Bad_MonitoredItemFilterInvalid, Bad_EventFilterInvalid, Bad_MonitoringModeInvalid, Bad_TimestampsToReturnInvalid; - aggregate filter in subscriptions: processing interval at least 2× the revised sampling interval, revised StartTime on the requested grid, ServerTimestamp = end of interval (Part 4 §7.22.4). - **Lifecycle and races:** - expired subscriptions are always cleaned up, even when the session closes during cleanup; - Create/Transfer racing CloseSession returns Bad_SessionClosed; - session diagnostics counts no longer drift; - a large but valid Publish TimeoutHint no longer fails the request; - a ready subscription is served by queued Publish requests instead of going Late; - restored persisted state is validated. - **Access:** a value change notification is checked against each subscriber's UserAccessLevel, not the writer's. - **Redundancy / durable store:** - the first-unsent sequence number is mirrored; - sequence numbers are no longer reused after a second failover; - triggering links are stored (definition format 6, written only when links exist, so older replicas can still read link-less subscriptions). ### Behaviour changes reviewers should know - With the default config, publishing intervals above `MaxSubscriptionLifetime / 3` (1,200,000 ms) are revised down, so lifetime ≥ 3×keep-alive fits the configured maximum. - The revised keep-alive is capped at a third of the max lifetime. - Idle subscriptions expire after exactly MaxLifetimeCount cycles without a queued Publish. - A partly invalid EventFilter select list is accepted with per-clause errors. The public `EventFilter.Validate` keeps Status Good if at least one clause is valid; callers that cannot return per-clause results must also check the new `EventFilter.Result.HasSelectClauseErrors`. There is a migration note in `docs/migrate/2.0.x/sessions-subscriptions.md`. - Aggregate processing intervals may be revised higher (≥ 2× sampling interval); aggregate ServerTimestamp is now the interval end. - Sampling intervals above 3,600,000 ms are rounded to multiples of 900,000 ms (default rate groups). ### Public API Additive only: - `ServerConfiguration.MaxMonitoredItemCount` and `MaxMonitoredItemsPerSubscription`; - `EventFilter.Result.HasSelectClauseErrors`; - `IStoredSubscriptionTriggering`, `ISubscriptionRetransmissionSendStateStore`, `SubscriptionRetransmissionState.FirstUnsentSequenceNumber` (optional store capabilities, detected with `as`); - new `EventQueueHandler` constructor overloads. ### Known open items (not in this PR) - **Part 4 §5.13.2.1:** CreateMonitoredItems should create an item even when the user lacks Read permission and report the error in Publish. This is not done: the create-time RolePermissions check is the only role check on the initial value, so following the spec would leak values until every node manager re-checks per sample. - **Sampling loop:** it runs on thread-pool threads; node managers with blocking synchronous reads should not use sampling groups heavily. The alternative, a dedicated thread per group, was the thread-exhaustion issue fixed here. - **Restarts:** a pending QueueSize-1 value and a pending overflow flag are not kept across a restart; that needs persistence interface extensions. - **Limit defaults:** decide whether the new limits should get finite defaults (currently 0 = unlimited). ## Related Issues - No tracking issue; the findings come from an internal audit of the server subscription code. ## 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. - [ ] 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. ### Local verification (net10.0, all green) | Suite | Passed | Failed | |---|---|---| | Opc.Ua.Server.Tests | 7963 | 0 | | Opc.Ua.Subscriptions.Tests | 643 | 0 | | Opc.Ua.Subscriptions.Classic.Tests | 57 | 0 | | Opc.Ua.Subscriptions.Durable.Tests | 36 | 0 | | Opc.Ua.Redundancy.Server.Tests | 847 | 0 | | Opc.Ua.Core.Tests (filter tests) | 283 | 0 | net48 build of `Opc.Ua.Server.Tests`: 0 errors. net48 tests and the full UA.slnx were not run locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
61edaa1ed0 |
Remove netstandard2.1 and net472 target frameworks (#4624)
# Description Drops support for **netstandard2.1** and **.NET Framework 4.7.2**. Libraries now target `net48`, `net8.0`, `net9.0` and `net10.0`. `Opc.Ua.Types` and `Opc.Ua.SourceGeneration.Core` still also target `netstandard2.0`, but only because the Roslyn source generators load them. There is no general netstandard2.0 support. **Preprocessor directives** (112 files) - Every `#if`/`#elif` mentioning `NETSTANDARD2_1*`, `NET472*`, `NET_STANDARD_TESTS`, `SKIP_ECC_CERTIFICATE_REQUEST_SIGNING` or `NETSTANDARD*` was evaluated against the remaining TFM set (net48, net8/9/10, plus netstandard2.0 inside `Opc.Ua.Types`/`tools`) and simplified. Dead branches were removed. - Example: `NETSTANDARD2_1 || NET8_0_OR_GREATER` → `NET8_0_OR_GREATER`. - Example: `NET8_0_OR_GREATER || NET472_OR_GREATER` → removed (always true). - Netstandard-only code was removed: `UnixFileModeShim`, the CRDT reflection path in `ReplicatedGossipOptions.CreateReaderOptions`, and the netstandard2.1-only test variants. **Custom test logic** - `targets.props` drops the `net472`, `netstandard2.0` and `netstandard2.1` `CustomTestTarget` profiles, `NetStandardTests` and `SKIP_ECC_CERTIFICATE_REQUEST_SIGNING`. Supported values are now `net48`, `net8.0`, `net9.0`, `net10.0`. - The `NET_STANDARD_TESTS` define is removed from 29 test projects. - The "build netstandard shells as net10.0" overrides are removed from 18 projects. - `RestrictForLegacyTfm` now only applies to `net48`. - `tests/customtest.bat` is updated to the new values. **Build / packaging / CI** - MSBuild TFM conditions are simplified: Kestrel package choice, MQTTnet v4 (net48 only), BouncyCastle, Di/OpenUsd/Vision/Stress test excludes, and `System.IO.FileSystem.AccessControl` (dropped from Core). - The MigrationAnalyzer nuspec drops its net472/netstandard2.1 lib groups. The Azure signing lists are updated. - `get-ci-matrix.ps1` removes the net472/netstandard test profiles (including the PR-gating `linux-netstandard2.1`) and the matching build legs. **Breaking API change (2.0 preview):** removed `HttpsTransportListener.IsWssTransportSupported` and `ReplicatedGossipOptions.IsGossipStateDecodingSupported`. They only returned `false` on netstandard2.1 and are now always `true`. The test guards that used them were removed as well. **Docs** - NuGet READMEs, `README.md`, `DeveloperGuide.md`, `PubSub.md`, the certificate/ECC/identity docs and the 2.0 migration guide (plus its `.agents` skill mirror) are updated. - The migration guide notes that 4.7.2 consumers must move to 4.8 and netstandard2.1 consumers to net8.0+. ### Reviewer notes - The `System.IO.FileSystem.AccessControl` `PackageVersion` pin is left in `Directory.Packages.props`. It has no direct reference any more, but transitive pinning is on, so it was kept to avoid shifting resolved versions. - The `#if` rewrite was done mechanically by a truth-table evaluator. The diff in the `.cs` files is limited to directive lines, removed dead branches, and stale-comment updates. ### Verified locally - `dotnet build UA.slnx -p:CustomTestTarget=net48`: 0 errors. - `dotnet build UA.slnx -p:CustomTestTarget=net10.0`: 0 errors. - Default multi-target builds (no `CustomTestTarget`) of Opc.Ua.Types, Bindings.Https, PubSub.Mqtt and Redundancy.Server succeeded. - net10.0 tests: - Opc.Ua.Redundancy.Server.Tests: 840/840 passed. - Opc.Ua.Types.Tests: 11363 passed, 1 skipped. - ModelDependencyScanner tests: 20/20 passed. - The full test suite was **not** run locally. ## 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. - [ ] 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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dadd609ec0 |
Scales: the OPC 40200 companion SDK with the OPC 30050 PackML model (#4580)
> Tracking issue #4576. #4577, #4578 and #4579 are merged, so this branch is > rebased onto `master` and contains only the Scales and PackML commits. ## Summary Last of the four PRs of #4576. Adds OPC 40200 Weighing Technology 2.00 as a model package plus a server and a client SDK, and the OPC 30050 PackML model it composes. | Package | Content | |---|---| | `Opc.Ua.PackML` | OPC 30050 PackML 1.01, plus `PackMLStateMachineController`, which drives a base state machine together with its nested machine and execute state machines (activation and deactivation of the sub-state machines, the PackML methods with an `Executable` attribute that follows the state, acting states left automatically or by the equipment) | | `Opc.Ua.Scales` | OPC 40200 2.00 (`http://opcfoundation.org/UA/Scales/V2/`), generated from the unmodified UA-Nodeset publication, plus the contracts client and server share | | `Opc.Ua.Scales.Server` | `AddScales()` / `ConfigureScales(...)`; all fourteen scale kinds and scale systems below `DeviceSet` and `Machines`; a runtime that does the weighing arithmetic (ranges, rounding to d or e, zero band, overload/underload, zero, tare, preset tare, registration), production presets with DI locking, statistics, recipes and recipe files, Annex C events and alarms over a `HasNotifier` hierarchy, and the methods of every scale type | | `Opc.Ua.Scales.Client` | discovery, weight reads and streaming, zero/tare/register, products, PackML state and commands, type-specific methods, events and alarms, snapshots | `docs/Scales.md` includes a conformance matrix against the 21 OPC 40200 server facets with test evidence, the places where the specification and the NodeSet disagree, and known limitations. ## Review follow-ups Separate commits after the docs/samples commit, each with a test that fails without it where the defect can be observed. - Copilot review: `CurrentProductsChanged` fires only when the set of current products changes; the new public collection APIs (`ScalesServerOptions.AdditionalNamespaceUris`, `ScaleNamespaceIndices.SearchOrder`, `ScalesModel.AllKinds`) are `ArrayOf<T>`. - Own review with the rubric in `.github/copilot-instructions.md`: - PackML raises `StateChanged` after its lock is released (queued, in order), reads `StartParameters` under the lock, and reports the handler's status when an acting state cannot complete. - Scales: concurrent first creations share one runtime (the lock service refused a second attach); two creations of the same name cannot both publish; a removed product id stays reserved until its node is gone; the zone, target-count and vehicle handlers change products under the preset lock; the product factory and `configure` delegate run outside it; the recipe upload reads `ActiveRecipe` under the lock; resetting the statistics also resets the tolerance percentage. - The client lets a failed browse call propagate instead of reading it as "no children". ## Testing - `tests/Opc.Ua.Scales.Tests`: 115 tests on net8 and net10 and with the netstandard2.1 libraries, including the PackML runtime; the client/server tests connect a real session over `opc.tcp` to a hosted `ScalesNodeManager`. - `UA.slnx` builds for net472, net48, netstandard2.0/2.1 and net8/9/10 with SDK 10.0.401 and no new warnings. - Changed-line coverage of this layer (`check-coverage.ps1`): 94.59 % (4585 of 4847 changed lines, against `master`). - Fork CI, full PR matrix incl. macOS: in progress, [run 36676067489](https://github.com/biancode/UA-.NETStandard/actions/runs/36676067489); CodeQL: in progress, [run 36676070427](https://github.com/biancode/UA-.NETStandard/actions/runs/36676070427). - On the current `master` (with Pumps merged), `UA.slnx` builds for every target framework and the Scales and Pumps tests pass. - Beyond the in-repo tests the implementation has been validated end to end against a separate, non-public OPC UA client and server. ## Reviewing Commits: PackML → Scales model → server → client → tests → docs and samples → review follow-ups. |
||
|
|
281596a24d |
Pumps: the OPC 40223 companion SDK (#4579)
> Tracking issue #4576. #4577 and #4578 are merged, so this branch is rebased > onto `master` and contains only the Pumps commits. ## Summary Third of four stacked PRs. Adds OPC 40223 Pumps and Vacuum Pumps as a model package plus a server and a client SDK, over the Machinery and DI packages. | Package | Content | |---|---| | `Opc.Ua.Pumps` | OPC 40223 1.0.0, generated from the unmodified UA-Nodeset publication, plus the snapshot contracts both sides share (`PumpNameplate`, `PumpConfigurationData`, `PumpOperationalData`, `PumpMaintenanceData`, `PumpSupervisionStatus`, `PumpPortDescriptor`, `MultiPumpConfiguration`, gathered into `PumpSnapshot`) | | `Opc.Ua.Pumps.Server` | `AddPumps()` / `ConfigurePumps(...)`; `PumpsNodeManager : DiNodeManager` places each pump below `DeviceSet` and organizes it into the Machinery `Machines` folder; browse-name-addressed builders reach all ~3000 optional children without a method per child and create them through the model's own `CreateChild` | | `Opc.Ua.Pumps.Client` | discovery in both places (de-duplicated, vendor subtypes included), a reader per `PumpType` group, whole-pump snapshots; the open groups come back as a `PumpValueSet` keyed by the generated `BrowseNames` | The model requires Machinery 1.0.0 and DI 1.02.2 and resolves against the newer Machinery 1.04.1 and DI packages in this repository, since a `RequiredModel` version is a lower bound. The OPC 40223 NodeSet is large, so the model commit is too; the hand-written code starts at the server commit. `samples/DI/PumpDeviceIntegrationServer` is left as it is: its local NodeSet copies are also fixtures of `Opc.Ua.Types.Tests` and `Opc.Ua.WotCon.Tests`. Moving it onto these packages would be a separate follow-up (see #4576). ## Review follow-ups Separate commits after the docs/samples commit, each with a test that fails without it. - Copilot review: `PumpValue.AsStringArray` and `PumpValueSet.Names` return `ArrayOf<string>` with its null sentinel instead of `Nullable<ArrayOf<string>>`; the `PumpBuilder` uses `Variant.Null`. - Own review with the rubric in `.github/copilot-instructions.md`: pump creation is serialized from the duplicate check to the registration, so two concurrent calls with the same name no longer both add a pump; the client lets a failed browse call propagate instead of reading it as "no children"; the `ReadSupervisionAsync` docs say which read options apply. ## Testing - `tests/Opc.Ua.Pumps.Tests`: 73 tests on net8 and net10 and with the netstandard2.1 libraries; the client/server tests connect a real session over `opc.tcp` to a hosted `PumpsNodeManager`. - `UA.slnx` builds for net472, net48, netstandard2.0/2.1 and net8/9/10 with SDK 10.0.401 and no warnings. - Changed-line coverage of this layer (`check-coverage.ps1`): 97.30 % (1550 of 1593 changed lines, against `master`). - Fork CI, full PR matrix incl. macOS: green, all 184 jobs ([run 36622243774](https://github.com/biancode/UA-.NETStandard/actions/runs/36622243774)); CodeQL: green ([run 36622248255](https://github.com/biancode/UA-.NETStandard/actions/runs/36622248255)). - Together with the other layer (Pumps and Scales merged on top of Machinery), built for every target framework and tested on net8, net10 and with the netstandard2.1 libraries, with the samples published. - Beyond the in-repo tests the implementation has been validated end to end against a separate, non-public OPC UA client and server. ## Reviewing Commits: model package → server → client → tests → docs and samples → review follow-ups. |
||
|
|
01d6b5d26c |
Machinery: the OPC 40001 companion SDK with Jobs, Result Transfer, Process Values and Energy (#4578)
> Tracking issue #4576. #4577 is merged, so this branch is rebased onto > `master` and contains only the Machinery commits. ## Summary Second of four stacked PRs. Adds the OPC UA for Machinery series as model packages plus a server and a client SDK, following the companion pattern of DI and Robotics, and the two models it depends on that the repository does not carry yet. | Package | Content | |---|---| | `Opc.Ua.IA` | OPC 10000-200 IA 1.01.4, **moved out of `Opc.Ua.Robotics`** (see below) | | `Opc.Ua.ECM` | OPC 34100 Energy Consumption Management 1.0.1 | | `Opc.Ua.PADIM` | OPC 30081 PA-DIM 1.02.0 with the IRDI dictionary | | `Opc.Ua.Machinery` | OPC 40001-1 Basic Building Blocks 1.04.1 | | `Opc.Ua.Machinery.ProcessValues` | OPC 40001-2 Process Values 1.00.0 | | `Opc.Ua.Machinery.Jobs` | OPC 40001-3 Job Management 1.0.1 | | `Opc.Ua.Machinery.Energy` | OPC 40001-4 Energy 1.00 | | `Opc.Ua.Machinery.Result` | OPC 40001-101 Result Transfer 1.01.0 | | `Opc.Ua.Machinery.Server` | `AddMachinery()` / `ConfigureMachinery(...)`, fluent machine builder, server-driven state machines, process values, job management over the shared ISA-95 V2 binder, energy, result management incl. `GenerateFileForRead` | | `Opc.Ua.Machinery.Client` | machine discovery, identification, state observation, process values, job and result access | OPC 40001-101 `ResultTransferType` redeclares `GenerateFileForRead` with its own arguments; the generator fix for such overrides, merged with #4577, is what makes it appear on the `ResultTransfer` instances. All NodeSets are byte-identical to `UA-Nodeset/latest`. Each model carries a pinned `Model/*.NodeIds.csv` the generator validates, next to the unmodified upstream table; `tools/nodesets/derive-identifier-table.py` derives the former from the latter. ## Changes to existing code - **IA moves out of Robotics.** Machinery needs IA as well (OPC 40001-1 types `Stacklight` with `BasicStacklightType`), and two assemblies generating the same model under the same prefix collide at any consumer referencing both. The NodeSet moves verbatim, so no generated type changes; the prefix stays `Opc.Ua.IA`. Source-compatible, but IA types now live in `Opc.Ua.IA.dll` rather than `Opc.Ua.Robotics.dll` — binary-breaking for consumers of the Robotics package. - **`samples/OpenUsd/GeneratorServer`** drops its hand-reduced Machinery NodeSet copy and the script that produced it (a workaround for the generator defect fixed by #4577) and references `Opc.Ua.Machinery` instead. - `README.md` and `docs/README.md` list the new models; the README also corrects the Industrial Automation entry (Part 200, not OPC 40001-1). ## Review follow-ups Separate commits after the docs/samples commit, so the reviewed state stays comparable. Each fix comes with a test that fails without it. - Copilot review: a transition is committed only while the state machine is still in its source state; optional Jobs/Result namespaces the server does not load are reported as absent, as documented; resources of registered machines (result transfer, job-management change pumps) are released with the node manager (`DisposeMachineryResourcesAsync` for a custom DI manager); `NodeId`/`LocalizedText` use their null sentinels instead of `Nullable<T>`. - Own review with the rubric in `.github/copilot-instructions.md`: a failed browse call propagates from the client instead of reading as "no children"; the predefined result metadata compares `CreationTime` against the `DateTimeUtc` sentinels (the `DateTime` conversion shifted `DateTime.MaxValue` by the host's UTC offset); process values below `Monitoring` are found; a second metering point named `Main` is rejected; the zero-point event is reported when the handler throws; smaller state-sharing and documentation corrections. ## Testing - `tests/Opc.Ua.Machinery.Tests`: 162 tests on net8 and net10 and with the netstandard2.1 libraries; the integration tests connect a real client session over `opc.tcp` to a hosted server. - With IA extracted: Robotics 636, OpenUsd 921, DI 433 and SourceGeneration 263 tests pass. - `UA.slnx` builds for net472, net48, netstandard2.0/2.1 and net8/9/10 with SDK 10.0.401 and no warnings. - Changed-line coverage of this layer (`check-coverage.ps1`): 91.68 % (6059 of 6609 changed lines, against `master`). - Fork CI, full PR matrix incl. macOS, on the current master: green, all 184 jobs ([run 36582685653](https://github.com/biancode/UA-.NETStandard/actions/runs/36582685653)). CodeQL: green ([run 36582689589](https://github.com/biancode/UA-.NETStandard/actions/runs/36582689589)). - Beyond the in-repo tests the implementation has been validated end to end against a separate, non-public OPC UA client and server. ## Reviewing Commits are ordered so each builds: IA extraction → identifier-table tool → ECM/PA-DIM → Machinery models → server → client → tests → docs/samples → GeneratorServer sample → review follow-ups. The model commits are large only because of the NodeSets and identifier tables; the hand-written code starts at the server commit. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
87c35b84b6 |
Fix Opc.Ua.Client audit findings (security fallback, reconnect, subscriptions, redundancy, browse, files, WebApi) (#4572)
## Summary
Fixes the findings of a bug audit of `Opc.Ua.Client` and
`Opc.Ua.Client.ComplexTypes`, done on master plus #4516 (now merged).
- **Audit:** 82 findings across 12 areas, each checked against the OPC
UA specification. Final severities: 2 High, 37 Medium, 43 Low.
- **Fixes:** 76 fixed, 4 partial, 2 skipped (see [Not
fixed](#not-fixed--partial)).
- **Review:** a follow-up review of the fixes found 15 more defects in
them, all fixed here.
- **Tests:** every fix has a regression test, except a few races that
cannot be reproduced deterministically; the commit messages say which.
The finding IDs (`L<area>-<n>` for the audit, `G<n>` for the review)
appear in the commit titles.
## Behaviour and user-facing changes
### Security and endpoint selection
- **Reconnect no longer drops to weaker security (L3-1, High).** When
the server rejects the configured security policy or mode during
reconnect, `SessionReconnectHandler` no longer switches to whatever
endpoint the server advertises, None included. It only picks an endpoint
at least as strong as the configured one, judged by a locally computed
security level. The server-advertised `SecurityLevel` is not trusted. If
nothing qualifies, reconnect fails with the original error (new log
event `SessionReconnectHandler + 13`) and keeps retrying with the
original settings.
- **`CoreClientUtils.SelectEndpoint(useSecurity: true)` never returns a
SecurityMode None endpoint over opc.tcp (L9-9, High).** It now returns
`null` when no secure endpoint matches.
- When the discovery URL is `https`/`opc.https`, an HTTPS endpoint with
SecurityMode None is still accepted as a last resort, because TLS
protects it (Part 6 §7.4.1) (G17).
- Endpoints with SecurityMode `Invalid` no longer count as secure.
- `GlobalDiscoveryServerClient` and `ServerPushConfigurationClient` now
throw `BadSecurityPolicyRejected` when no secure endpoint matches,
instead of passing `null` on. The ConsoleReferenceClient sample reports
this with a clear error.
- **No Bearer/Basic credentials over `http://` (L12-2).**
`WebApiClient.Create` and WebApi channel open/reconnect reject them with
`BadSecurityChecksFailed`. The public `WebApiClient(HttpClient, …)`
constructor also rejects them when the client's `BaseAddress` is not
https, and every send checks the address it actually goes to (covers a
`BaseAddress` set later).
### Session, keep-alive and identity
- **Faster detection of a silent server (L1-1).** The keep-alive Read is
now bounded by its `TimeoutHint` (2 × `KeepAliveInterval`), which is
what Part 4 §7.32 says the client should do. It then fails with
`BadNoCommunication`, so reconnect starts after about 2 ×
`KeepAliveInterval` instead of `OperationTimeout` (120 s by default).
- **Operations fail fast during a channel-manager recovery (L1-2, G9).**
`UpdateIdentityAsync`, `TransferSubscriptionsAsync` and
`ReactivateSubscriptionsAsync` fail with `BadSecureChannelClosed`
(retryable) instead of deadlocking. This covers the operation holding
the reconnect lock and any queued behind it.
- **Closing or disposing from a handler no longer waits on itself (L1-3,
G5, G6).**
- `Session` and `ManagedSession` Close/Dispose from a Notification
handler no longer wait 30 s for their own background drain.
- Disposing a `ManagedSession` from a keep-alive `PublishStatusChanged`
handler no longer deadlocks.
- Tasks spawned by a handler no longer count as "the handler" (the owner
marker is scoped to the work item).
- **Switching to an ECC user-token policy takes two ActivateSession
calls (L1-4, G4).** This follows Part 6 §6.8.2 (fetch the ephemeral key,
then activate).
- If the server returns no key, the switch fails with
`BadSecurityPolicyRejected`.
- If the second activation is rejected, the old identity stays on the
override policy with the key just fetched. Restoring the old key was
tried: against the reference server the next reconnect then failed to
decrypt the identity token.
- **`ManagedSession.ConfiguredEndpoint` follows failover (L2-6).** It
now reports the inner session's endpoint, so it changes after a failover
or network-path rotation.
- **An explicit identity update replaces the identity provider (L2-7).**
`ManagedSession.UpdateSessionAsync(identity)` stops the provider's
refresh loop and drops the provider for good. If the update fails, the
loop is restarted.
- **Reconnect waits for in-flight calls on a healthy channel (L2-3,
G8a).** Reconnect/failover on a healthy channel (certificate or
trust-list change, manual reconnect) waits for in-flight service calls.
The wait is bounded (max(`KeepAliveInterval`, 1 s)) only once the
channel is unhealthy (keep-alive stopped, channel not Ready), so a dead
channel no longer holds recovery for `OperationTimeout`.
- **Smaller fixes:**
- Restore reads the server certificate as a chain; this failed on macOS
(L1-5).
- Session recovery can no longer run twice at once (L1-6).
- Identity expiries more than 49.7 days out no longer throw (L2-2).
- Keep-alive reconnect suppression is recalculated on a lease swap
(L2-4).
### Reconnect state machine and reverse connect
- **⚠ `MaxTotalReconnectTime = 0` now means "no time limit" (L3-7).
Reviewer decision needed.** Before, 0 disabled reconnect entirely.
Negative values other than `Timeout.InfiniteTimeSpan` now throw
`ArgumentOutOfRangeException` when the state machine is constructed;
before, they crashed the worker. The XML docs of
`ReconnectPolicy`/`ReconnectPolicyOptions` are updated. If 0 should keep
meaning "no reconnect", this is a one-line revert.
- **A close racing the end of the reconnect budget is no longer lost
(L3-4).** It goes through the normal close instead of switching to
failover.
- **The "reconnect worker" marker covers one step only (L2-1).** It
lasts for a single connect/reconnect/failover/close step. Keep-alive,
publish and identity loops started in a step no longer count as the
worker afterwards.
- **Reverse-connect strategies `Undefined` and `Any` alone are removed
after the first callback (L8-1).** This matches the documentation;
before, they behaved like `Always`.
- **`AddEndpoint` for a URL that is already added disposes the replaced
listener (L8-2).**
### Service calls
- **Batched calls clean up after a failed later batch (L3-2).** When a
later batch of
`Browse`/`BrowseNext`/`HistoryRead`/`CreateMonitoredItems` fails, the
continuation points and monitored items created by earlier batches are
released or deleted before the exception is rethrown. This adds one
request. `AddNodes`/`RegisterNodes` are not undone.
- **Batched diagnostic infos point at the right strings (L3-3).** The
string-table indexes are rebased when diagnostics are padded.
- **`ReadBytesAsync` rejects oversized responses (L3-5).** It throws
`BadUnknownResponse` when the server returns more bytes than requested,
and `BadEncodingLimitsExceeded` beyond `int` range.
- **`ReadByteStringInChunksAsync` handles an unknown server limit
(L3-6).** When the server's `MaxByteStringLength` is 0 (absent) or
larger than `int`, it falls back to the client's
`MessageContext.MaxByteStringLength`. It throws only when both are
unknown.
### Browse and NodeCache
- **Empty browse pages with a continuation point are followed (L9-1,
L9-4, L9-5, G16).** Part 4 §5.9.2.1 allows a server to return an empty
page with a continuation point, so browsing keeps following it. After 10
consecutive empty rounds it gives up with `BadNoData` and releases the
continuation point.
- `BrowseAsync(NodeId)` now throws instead of silently returning a
truncated list.
- `BrowseStreamAsync` yields a `BadNoData` result for that node.
- The same cap, through one shared helper, now also protects
`RoleManagementClient.ListRolesAsync` and the FileSystem
enumerate/empty-check loops.
- **Continuation points are released on failure (L9-3).**
`BrowseAsync(NodeId)` releases them on any exception, and
`BrowseStreamAsync` releases all points of the current page when the
consumer stops early.
- **NodeCache lookups propagate cancellation (L9-6, L9-8).**
`FindAsync`, `IsKnownAsync`, `FetchNodeAsync`,
`FindReferenceTypeNameAsync`, the reference-table fill and the
type-hierarchy search now let `OperationCanceledException` through.
Before, a cancelled lookup looked like "node does not exist".
- **A NodeClass mismatch fails only that node (L9-7).** It returns
`BadNodeClassInvalid` for that node instead of throwing
`KeyNotFoundException` for the whole batch.
- **Smaller fixes:**
- `MaxNodesPerBrowse` ≥ 2^31 no longer breaks the managed browse (L9-2).
- Null discovery URLs are skipped (L9-10).
- `ModelChangeTracker` no longer cancels a joined start (L9-11).
- **Log changes:** `BrowserContinuationPointExistsButBrowse` (Browser +
0) was removed, and `BrowserServerReturnedEmptyReferencesBut` now
includes the round count.
### Subscriptions (default engine)
- **Recovery after a transfer delivers each message exactly once (L5-1,
G22).** Messages already dispatched are acknowledged instead of
republished and delivered again. Messages whose earlier republish failed
are now republished and delivered, with the Republish flag and after
newer messages, instead of being acknowledged and lost.
- **A keep-alive that proves a gap triggers republish at once (L5-5,
G23).** The dedup mark moves only past messages that were actually
recovered, so a failed republish is retried by the next data message.
- **Item changes that fail are retried with backoff (L5-3).** They are
retried on the next apply pass after the state manager's 250 ms–5 s
backoff, instead of six times back to back. The six attempts now span
about 13 s.
- **Extreme revised intervals no longer throw (L5-4, L6-1).** Very large
or NaN intervals and counts revised by the server are capped (NaN falls
back to the requested value). Before, the overflow could stop every
publish worker.
- **`SetTriggeringAsync` from a callback no longer hangs (L5-7, G21).**
While the subscription is deleted, recreated or disposed, it returns
`BadOperationAbandoned`. A cancelled reset no longer makes later calls
fail.
- **Publish responses for unknown subscription ids (L6-3, L6-4, G20).**
The client no longer re-sends acknowledgements the server already
processed. An unknown id seen while a creation is pending is left alone
instead of being deleted.
- **Smaller fixes:**
- A null and an empty `IndexRange` are treated as equal, and restored
snapshots use `null` (L5-6).
- A failed partition attach during restore is rolled back (L6-2).
- `StreamingSubscription` throws `ObjectDisposedException` after dispose
(L6-6).
### Subscriptions (classic `Subscription`)
- **Republish no longer abandons other missing messages (L7-1).** A
Republish response no longer overwrites `AvailableSequenceNumbers`, so
other missing messages are not given up while the server still holds
them.
- **Late copies are no longer delivered twice (L7-2).** Copies of
already-processed sequence numbers are ignored, and creating a new
server subscription resets the sequence state.
- **Disposing from a keep-alive handler no longer deadlocks (L7-3).**
`Dispose()` from a keep-alive `PublishStatusChanged` handler can return
while that callback is still finishing.
- **Transfer of a restored subscription (L7-4, G27).**
- It succeeds even when some saved items were never created.
- The server subscription is deleted only when this session's own
TransferSubscriptions just moved it and adoption failed definitively
(item count mismatch or a non-transient status). Transient errors and
`ReactivateSubscriptionsAsync` never delete it.
- The public `Subscription.TransferAsync` never deletes.
- **Smaller fixes:**
- Cloned items keep their triggering links (L7-5).
- `CreateAsync` clears stale server ids (L7-6).
- A `Good_SubscriptionTransferred` for the client's own transfer is
ignored (L7-7).
- An item removed while its create is in flight is deleted afterwards
(L7-8).
- The publish timeout for very long keep-alives no longer overflows
(L7-9).
- A negative `MaxMessageCount` is handled (L7-10).
- `LastMessage` tolerates a wrong notification type (L7-11).
- Pending acks exactly 2^31 away no longer throw (L4-1).
- **Log changes:** new log events `Subscription + 68..70`.
### Redundancy
- **Warm/Hot demotion (L4-2, G1, G3).**
- Without token reuse, the demoted leader closes its session and
reconnects as a fresh standby.
- With token reuse, it abandons the shared server session locally,
without CloseSession, so the new leader can reactivate it and keep its
subscriptions. This uses the new internal methods
`ManagedSession.AbandonServerSessionAsync` and
`Session.AbandonServerSession`.
- `RoleChanged(false)` is now raised before the standby connect and no
longer depends on it, so `CurrentSession` can briefly be `null`.
- Exceptions from `RoleChanged` handlers are logged and do not stop the
teardown.
- **Promotion (L4-3, G2).** A promoted standby that adopts the leader's
session first closes its own server session. A retried promotion keeps
the session it already adopted.
- **Client failover takeover (L4-4, partial).** Subscriptions the backup
prepared in advance (not yet created, `TransferId` set to the active
client's id) are moved through the session's own transfer and keep
publishing. `docs/HighAvailability.md` is updated.
- **`ShouldFailover` through the interface (L4-6).** Calling it through
`IServerRedundancyHandler` no longer fails over away from a server that
is still Healthy.
### Fluent / dependency injection
- **Shared DI session (L10-5).** The shared session from `AddClient` is
reconnected after a caller disposes it, and is disposed with the
container. After that the connect delegate throws
`ObjectDisposedException`.
- **New public API:
`ManagedSessionBuilder.WithServerRedundancyOptions(ServerRedundancyOptions)`
(L10-6).** This is additive. The `Session.ServerRedundancy` options are
now actually applied.
- **`AddDiscoveryAndConnect` rewrites the advertised address (L10-7).**
It replaces the host and port the server advertises with those of the
discovery URL (Part 4 §5.6.2.1, NAT/containers), and prefers endpoints
with the same scheme.
### State machines and role management
- **`GetSubStateMachineAsync` returns the instance's own sub-state
machine (L10-1).** Before, it returned the type's placeholder.
- **`WaitForStateAsync` reports failures with OPC UA status codes
(L10-2).** It throws `BadNotFound` when `CurrentState/Id` cannot be
resolved, and `BadInvalidState` when observation ends without reaching
the state. Before, both surfaced as `OperationCanceledException`.
- **`RoleManagementClient.ListRolesAsync` pages correctly (L10-3).** It
follows continuation points, releases them on any failure, and throws on
a Bad browse status instead of returning an empty list.
- **Role properties are read into the right fields (L10-4).** Properties
whose namespace cannot be resolved are skipped instead of shifting the
other values.
### File system, temporary files and alias names
- **Path escaping (L11-8, G12).**
- Path segments escape `:` and `/` with `&`, and `&` itself as `&&`
before those characters or at the end of a name.
- `Format` and `Parse` round-trip all names. Typed input ending in `&/`
now reads the slash as part of the name.
- A segment without a namespace prefix also matches a namespace-0 child
when nothing matches in the parent's namespace.
- **Stale path cache (L11-1).** When a cached lookup fails,
`GetInfoAsync` retries once from the server. `ExistsAsync` and
`GetFileAsync` report "not found" instead of throwing.
- **`GenerateFileForReadAsync` waits for the file (L11-2).** It waits
until the file-generation state machine reaches ReadTransfer (`Idle`
counts as not started yet), and throws `IOException` if it reports
Error. The wait is bounded only by the caller's token.
- **Non-recursive delete refuses hidden children (L11-3).** It refuses
when the directory holds children that the subtype options hide from
enumeration. Before, the server deleted them all.
- **Alias names (L11-5, L11-6, L11-7).**
- `BadMethodInvalid` maps to `NotSupportedException` for all alias
calls, so `UseVerbose` falls back to `FindAlias`.
- A duplicate alias name resolves to all its targets.
- If the refresh strategy fails to start, the resolver logs a warning
and keeps working without automatic invalidation. New log event block
`AliasNameResolver = 560`.
- **Null profile entries (L11-4).** Null `ServerProfileArray` entries no
longer crash `GetConformanceInfoAsync`.
### WebApi transport
- **Failures come back as `ServiceResultException` (L12-1).**
- A timeout becomes `BadRequestTimeout`.
- HTTP 429/503 becomes `BadServerTooBusy` with the Retry-After hint, on
every target framework. `WebApiClient` checks the status itself (reusing
`HttpsTransportChannel.CreateServerTooBusyException`), so callers of
`WebApiClient` also get a `ServiceResultException` instead of an
`HttpRequestException` for these two statuses (G15).
- An IO error becomes `BadNotConnected`.
- **Timeouts (L12-1, G14).**
- `OperationTimeout` applies to each request. When `RequestTimeout` is
unset, HttpClient's 100 s default no longer cuts off long Publish
requests.
- The GetEndpoints call at open is bounded by `OperationTimeout`, and
cancelling the open now throws.
- **Core change:** `HttpsTransportChannel.MapRequestFailure` went from
private to internal so the WebApi channel can reuse it.
### ComplexTypes
- **Out-of-range union selector (L12-3).** A union whose SwitchField is
larger than its field count now fails with `BadDecodingError` when
decoded and `BadEncodingError` when encoded (Part 6 §5.2.8).
- **Verbose JSON unions (L12-4).** They now decode their selected field
instead of `null`.
## Not fixed / partial
- **L2-5 (skipped):** an identity provider combined with a
caller-supplied reverse connection still can't connect to an
undiscovered endpoint. Fixing it means choosing the identity after
CreateSession, a design change.
- **L4-5 (skipped):** the one-time KeyCredential token needs a fresh
token on each reactivation. Today reactivation fails and the client
falls back to a new session.
- **L4-4 (partial):** only subscriptions the backup prepared in advance
survive a takeover.
- **L3-2 (partial):** `AddNodes`/`RegisterNodes` batches are not rolled
back.
- **L2-3 (partial):** service calls still hold the session lock for
their whole round trip.
- **Known from the review, not fixed here:**
- G7: the batch cleanup request can't be cancelled.
- G11: the DI session's close at host shutdown never reaches the server,
and a closed session keeps being handed out.
- G13: a file renamed by another client is still found under its old
path.
- G25: a clone-based transfer doesn't mark the original subscriptions.
- G27b: the transfer item check matches by count, not by ID.
- **Outside this PR:** the reference server ignores `ECDHPolicyUri` on
ActivateSession (`SessionSecurityPolicyHelper`), which breaks Part 6
§6.8.2. As a result, the L1-4 two-step ECC switch ends in
`BadSecurityPolicyRejected` against our own server.
## Testing
All suites on net10.0, on the branch before the master merge. Every
suite had 0 failures:
| Suite | Passed | Skipped |
|---|---|---|
| Opc.Ua.Client.Tests | 2698 | 0 |
| Opc.Ua.Redundancy.Client.Tests | 134 | 0 |
| Opc.Ua.Subscriptions.Classic.Tests | 57 | 0 |
| Opc.Ua.Bindings.Https.WebApi.Tests | 385 | 0 |
| Opc.Ua.Sessions.Tests | 782 | 379 |
| Opc.Ua.Subscriptions.Tests | 640 | 6 |
| Opc.Ua.Client.ComplexTypes.Tests | 2858 | 0 |
After merging current master (net10.0), every suite had 0 failures
except one, a flaky new test fixed in
|
||
|
|
a0d569d756 |
Fix Server and GDS audit findings, review findings and spec-conformance gaps (#4571)
## Summary Fixes the findings of a bug audit of `Opc.Ua.Server`, `Opc.Ua.Gds.Server` and `Opc.Ua.Gds.Client`. Rebased on master after #4516. What went in: - **Audit:** 93 findings, each checked against the OPC UA specification. - **Code review of those fixes:** 35 further findings. - **Spec grounding of every fix commit:** 18 conformance gaps, now closed. Commits are one per finding, named `Fix <ID>`, `Fix review <ID>` or `Fix spec gap <name>`. The IDs refer to the audit report (S = Server areas, G1 = GDS server, G2 = GDS client). Validation: - Each finding was traced in code and checked against OPC 10000 (parts 3, 4, 5, 9, 11, 12, 13, 18, 20). - Every fix commit was checked against the spec text again afterwards: 0 non-conformant, remaining gaps fixed. - Regression tests were added for most fixes. - Suites run on net10.0 before the rebase: Server, GDS, History, Configuration, Features, Core.Security, DI, Redundancy, LDS, InformationModel, PubSub.Server, Client, Sessions and the three Subscriptions suites, all green. The same suites on the rebased branch are still running; results will be posted as a comment. ## Behaviour and user-facing changes ### Secure defaults - **`Historize()` / `HistorianBuilder`** grant HistoryRead only. History writes need an explicit `historyAccessLevel: HistoryRead | HistoryWrite`. - **File-system mount:** - Anonymous sessions can no longer create, delete, move or open files for writing (`Bad_UserAccessDenied`). - UserWritable and UserExecutable now reflect the user. - `AllowAnonymousWrite` restores the old behaviour; the ReferenceServer sample turns it on. - **Default PKI root** for the hosted server, hosted GDS, hosted LDS and `OpcUaApplicationConfigurationProvider`: - Moved from the shared temp directory to `{LocalApplicationData}/OPC Foundation/{ApplicationName}/pki`, and startup fails if no per-user directory exists. - When certificate stores of an earlier version still exist under temp, a warning names both paths. Existing deployments that relied on the default get a new application certificate (and the hosted GDS a new CA) unless `PkiRoot` is set to the old path. - **GDS:** - `GdsServerOptions.AutoApprove` defaults to `false`. - The default KeyCredential store grants no roles unless `AuthorizeRoles` allows them. - Certificate requests (StartSigningRequest, StartNewKeyPairRequest, FinishRequest) need an encrypted channel. - **Hosted server anonymous logins:** - Anonymous is rejected (`Bad_IdentityTokenRejected`) when the default authenticator options disable it. - A custom `AddIdentityAuthenticator<T>()` on its own no longer disables anonymous. - With anonymous disabled and no configured token policies, the endpoint advertises the authenticators' token types instead of Anonymous. - **Runtime-added roles:** their properties and methods need SecurityAdmin over an encrypted channel, like the well-known roles. - **Disabled users:** users disabled through Part 18 UserManagement can no longer log in through `RegisterDefaultAuthenticators(IUserDatabase)`. ### GDS server (Part 12) - **Certificate requests:** - FinishRequest / ReadRequest with another application's RequestId returns `Bad_InvalidArgument`. - Every Start*Request returns a new RequestId. - A CSR without the ApplicationUri is rejected (`Bad_CertificateUriInvalid`). - An abstract or unsupported certificate type is rejected at Start (`Bad_InvalidArgument`). - **Private-key password:** no longer persisted in the JSON applications database. A password saved by an earlier version is read into memory once and dropped on the next save. - **CheckRevocationStatus:** validates offline against the GDS's own trust lists and CRLs. It returns Good for good GDS-issued certificates and `Bad_CertificateInvalid` for input that is not a certificate. - **Certificate-group trust lists:** - Writes need CertificateAuthorityAdmin; SecurityAdmin and application admins are read-only. - Writes are validated with the Part 4 certificate validation. - **AuthorizationService:** - The legacy RequestAccessToken applies `AccessControl` and issues the token for the session identity. - StartRequestToken passes the session identity to every identity-aware provider. - Unknown ResourceIds return `Bad_NotFound`. - Expired token requests and refresh tokens are pruned, with at most 10 000 pending requests (`Bad_TooManyOperations`). - **KeyCredential:** requests with PublicKey + SecurityPolicyUri are rejected with `Bad_SecurityPolicyRejected` (secret encryption is not implemented); a PublicKey without SecurityPolicyUri is rejected with `Bad_InvalidArgument`. - **Audit events:** access-token audit events no longer contain the password or token data. - **RegisterTickets / UnregisterTickets:** need the RegistrarAdmin role when called from a session. - **Managed application nodes:** moved from namespace 0 to their own namespace (`ManagedApplicationsNamespaceUri`); the NodeId is the ApplicationUri, and the mandatory child properties exist. - **Hosted GDS:** - New `GdsServerOptions.CertificateGroups` (`GdsCertificateGroupOptions`), bindable from configuration. - The mandatory DefaultApplicationGroup is always present. - The default CA subject uses the resolved application name. - **JSON applications database:** - A corrupt file throws instead of being silently replaced by an empty database. - Saves are atomic and write in place on bind mounts; on net8+ builds they also follow symlinks and keep the Unix file mode. - Issued certificates and trust-list links survive a reload. ### GDS client - **Trust-list download:** - Reads until an empty chunk (Part 20 short reads). - A size-limit rejection of the final end-of-file read, as sent by released servers, counts as end of file. - **Default certificate group:** a null group means DefaultApplicationGroup, and vendor groups in other namespaces are no longer mistaken for well-known groups. - **Status codes and errors:** - Missing directory proxies report `Bad_ServerNotConnected`. - Short or wrongly typed method outputs throw `BadUnexpectedError` / `BadTypeMismatch`. - **GdsAccessTokenProvider:** no longer caches a failed client task, and one caller's cancellation no longer fails other callers. ### Trust lists, push and ConfigurationFile (Part 12 / Part 20) - **AddCertificate:** - `IsTrustedCertificate=false` returns `Bad_CertificateInvalid`. - Added certificates are validated (Part 4), and a certificate revoked by a current CRL returns `Bad_CertificateRevoked`. - **CloseAndUpdate:** validates the uploaded list against its own certificates and CRLs, not the stores it replaces. Validation failures are no longer recorded in the server's Rejected store. - **RemoveCertificate:** removing a trusted or issuer CA that other certificates still need returns `Bad_CertificateChainIncomplete`. - **Handles:** - Several read handles may be open at once. - Any open while a write handle exists returns `Bad_NotReadable` / `Bad_NotWritable`. - A write open while any handle is open returns `Bad_NotWritable`. - Handles of closed sessions are released. - Idle handles close after ActivityTimeout (default 60 s). - **Status codes:** - Read clamps to the remaining bytes and no longer enforces the size limit. - Oversized Write / AddCertificate returns `Bad_RequestTooLarge`. - Unsupported open modes return `Bad_NotSupported`. - **ConfigurationFile.CloseAndUpdate:** - Empty Targets return `Bad_InvalidArgument`. - The ConfigurationUpdatedAuditEvent is raised with the configuration owner as source. - With the new `IApplicationConfigurationFileTargetProvider`, providers receive the Targets, a bad target returns Uncertain with per-target results, and session-interrupting updates are applied after RestartDelayTime. - **Default namespace permissions:** namespaces registered after the first permission lookup now get their NamespaceMetadata default RolePermissions and AccessRestrictions (no stale negative cache). ### Sessions, identity and roles (Part 18) - **Session timeouts:** a failure closing one timed-out session no longer stops the session-timeout monitor. - **ChangePassword:** an empty old password returns `Bad_IdentityTokenInvalid`. - **Removing or disabling a user:** closes only that user's UserName-token sessions. - **Role configuration changes:** roles granted by an `ImpersonateUser` mapping are kept. - **X509Subject rules:** a certificate subject value containing a quote never matches an X509Subject rule. - **`ImpersonateUser` callback:** no longer runs under the global session lock. - **Session diagnostics:** LocaleIds are kept as the client sent them, while null LocaleIds are ignored for translation. ### Subscriptions and monitored items (Part 4) - **Acknowledgements:** a failing acknowledgement returns a per-entry Bad status instead of failing the whole Publish. - **`DiscardedMessageCount`:** counts messages dropped from the retransmission queue and unsent overflow messages. - **Durable subscriptions:** transferring a restored anonymous subscription works (no NullReferenceException). - **Transferred subscriptions:** sampling and permission checks use the item's current owner session. - **Access denied:** a monitored item whose user may no longer read the attribute gets a `Bad_UserAccessDenied` notification instead of going silent. Non-Value attributes need Browse permission (RolePermissions needs ReadRolePermissions), and User* attributes are evaluated per subscriber. - **ModifyMonitoredItems:** a SamplingInterval of -1 uses the subscription's publishing interval. - **AggregateFilter priming:** needs HistoryRead / ReadHistory permission and is bounded (100 000 values, bounded page size). - **Resource leak:** event monitored items no longer leak a consumer task per create/delete cycle. ### Node management services (Part 3 / Part 4) - **Reference types:** AddNodes, AddReferences, DeleteReferences and Browse reject ids that are not ReferenceTypes with `Bad_ReferenceTypeIdInvalid`. AddNodes, AddReferences and DeleteReferences also reject abstract ReferenceTypes; Browse still accepts them (e.g. `HierarchicalReferences` with includeSubtypes). - **Concurrent AddNodes:** duplicate BrowseNames under one parent get `Bad_BrowseNameDuplicated`. - **TranslateBrowsePathsToNodeIds:** - Requires Browse permission on intermediate nodes and final targets (RolePermissions and AccessRestrictions). - A long RelativePath no longer rejects the whole request. - **SemanticChangeEvent:** - Raised once per actual change of a semantic property, whether or not it is monitored. - Also raised for properties with the SemanticChange access-level bit. - Reports the owning node's TypeDefinition as AffectedType. - **Localized diagnostics:** operation-level text is translated when OperationLocalizedText is requested. ### History and aggregates (Part 11 / Part 13) - **DeleteRawModified:** an unspecified or reversed range returns `Bad_InvalidTimestampArgument`, and StartTime == EndTime deletes the value at StartTime for every provider. - **ReadAtTime fallback:** reads only the values around each requested time. A time whose bound is not found within the search limit returns `Bad_BoundNotSupported` instead of failing the request. - **ReadProcessed:** a ProcessingInterval shorter than one tick returns `Bad_AggregateInvalidInputs`. - **Delta / DeltaBounds:** widen to the next larger signed type when the result does not fit the source type (e.g. a decreasing unsigned counter, or an Int16 overflow). - **Range / Range2:** return the source data type. ### Alarms and fluent API (Part 9) - **Suppression:** an alarm stays suppressed while any suppression group is active, and re-evaluation without a state change does not move TransitionTime. - **`MonitorVariable(variable)`:** sets SourceNode, SourceName and InputNode to the variable and makes it a ConditionSource (HasCondition to the alarm, HasEventSource from the object). - **Fluent input/output arguments:** InputArguments / OutputArguments added through the fluent API to a registered method are readable. - **Root notifiers:** a failed root-notifier registration no longer leaves an orphaned event worker. - **State-machine handlers:** exceptions are logged at Error. ### File system (Part 20) - **Delete / MoveOrCopy:** - Only act on direct children of the directory. - A NodeId whose kind does not match the entry returns `Bad_NotFound` (a file NodeId can no longer delete a directory). - Copying a directory into its own subtree returns `Bad_InvalidArgument`. - **Locking:** - An open file, or a directory containing one, cannot be deleted or moved (`Bad_InvalidState`). - The check is atomic with opens. - While a Delete or Move runs, read opens get `Bad_NotReadable` and write opens `Bad_InvalidState`. - **Open:** - Reserved mode bits or EraseExisting without Write return `Bad_InvalidArgument`. - Read+Write returns `Bad_NotSupported`. - Read while a writer is open returns `Bad_NotReadable`. - **Client `FileSystemErrors`:** maps `Bad_NotReadable` to `IOException`. - **RuntimeNodeSet:** well-formed NodeSets with a stray `Model` element, or two models in one file where one requires the other, now load. ### Server core, hosting and lifecycle - **`SessionEstablishmentQueueLimit` > 0:** CreateSession/ActivateSession now queue for a permit. The permit is acquired before the request is registered, and a waiting handshake releases its worker through the request-parking policy. - **Node manager lifecycle:** - Add, reload and remove no longer wait for parked Publish requests. - A Migrate reload racing with CreateMonitoredItems no longer blocks later lifecycle operations. - Late items on retired or removed managers get `Bad_NodeIdUnknown`. - **HistoryUpdate:** a batch mixing data and event details is bounded by both limits. - **Server stop/start:** stopping the server disposes the configuration watcher and certificate subscription; starting recreates them. - **Monitored-item queue factory:** a factory supplied by the caller or the DI container is no longer disposed by the server. - **Audit events:** each certificate validation error raises its own AuditCertificate event. A failed OpenSecureChannel without a service result is audited as failed. ## New public API (additive) - `IQueuedSessionEstablishmentLimiter` - `StandardServer.OwnsMonitoredItemQueueFactory` (protected virtual) - `DefaultPkiRoot.Get` (Opc.Ua.Configuration) - `TrustList.SetCertificateValidation` - `NamespaceMetadataRegistry.Invalidate` - `IApplicationConfigurationFileTargetProvider` and `ApplicationConfigurationUpdatePlan` - `GdsServerOptions.CertificateGroups` / `GdsCertificateGroupOptions` - `InMemoryKeyCredentialRequestStore.AuthorizeRoles` - `DefaultManagedApplicationsNodeManager.ManagedApplicationsNamespaceUri` - `AuthorizationServiceManager.RequestAccessTokenAsync(..., IUserIdentity?, ...)` - `AllowAnonymousWrite` on the file-system node manager and its factory - `IFileSystemHost.TryBeginMutation` / `EndMutation`, replacing `HasOpenHandles` ## Not fixed / known limitations - **KeyCredential:** secret encryption with the client's PublicKey (requests needing it are rejected). - **CSR SAN restriction:** restricting subject / SAN values copied from a CSR (CA policy). - **Ticket signatures:** ticket signature verification (needs a trusted-signer configuration). - **FileType Read+Write open:** needs a read/write stream contract in `IFileSystemProvider`. - **Custom `IUserDatabase` implementations:** the disabled-user check still scans all users per login. The built-in stores do a single lookup. - **64-bit Delta precision:** limited by the calculator's double arithmetic. ## Tests with changed expectations Existing tests were updated where they asserted the old behaviour: - File-system status codes - Trust-list handle eviction - `DiscardedMessageCount` - Localized diagnostics bits - Delta on unsigned sources - SourceNode of fluent alarms - ReferenceServer transfer permission test (now expects `Bad_UserAccessDenied` notifications) - `UserDeactivationShutdownReviewTests` (the revoked session is a UserName login) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c4e0721a29 |
Add a documentation learning path and fix issues found while verifying it (#4555)
## Summary
### Documentation
- Stage the documentation index as a learning path: a first application,
independent client, server, and PubSub paths, then data and services,
companion models, production, advanced integration, and draft or
experimental capabilities.
- Add Getting started (repository samples or NuGet packages), OPC UA
concepts, Your first information model, and a production readiness
checklist.
- Add Native AOT application publishing, a first PubSub publisher and
subscriber exchange, and the UDP transport options.
- Add or expand contents navigation throughout `docs/`, and keep
contributor documentation separate.
- Correct outdated SDK examples and capability guidance, consolidate
repeated explanations into canonical guides, and repair local links and
heading structure.
- Rewrite What's New in 2.0 as a concise feature overview with short
linked details; keep migration instructions in the Migration Guide.
- Expand the feature catalogue with specification IDs, GDS, LDS, the OPC
10000-21 onboarding registrar, ISA-95, xRegistry, OpenUSD, and
sample-local models; distinguish draft status, explain crypto
pluggability, and bring performance guidance forward.
- Address review feedback on wording, active monitored items during
shadow reload, and the `Samples.md` filename and references.
- Regenerate the `opcua-v20-migration` plugin's bundled copy of the
migration guides so it matches the edited `docs/migrate/2.0.x` guides.
- Merge the latest `master`, preserving upstream resource-isolation
guidance within the task-based documentation index.
- Retain deferred Lingity findings where appropriate; this PR does not
claim that every document meets the Lingity score thresholds.
### Fixes found while verifying the guides
Each fix has regression tests that fail without it.
- **Client:** V2 subscriptions compared their lifetime with the
operation timeout instead of the session timeout, and logged a spurious
warning. `ISubscriptionEngineContext`, which is new in 2.0, gains a
`SessionTimeout` property; implementations must add it.
- **PubSub:** a subscriber now registers the `DataSetMetaData`
configured on its readers (OPC 10000-14 6.2.9.4), and the UADP decoder
decodes DataSetMessages that carry no ConfigurationVersion. Before, a
secured subscriber of the reference sample received no DataSets.
- **Source generator:** a ModelDesign method that declares its arguments
inline, without a method type, produced code that did not compile; it
now uses `MethodState`. The generated code of existing models is
unchanged.
- **MinimalClient sample:** waits for its first data-change notification
before it disconnects.
### Known issues, not addressed here
- UDP multicast: the publisher does not send on the interface that the
subscriber joins, and `MulticastLoopback` defaults to false. The PubSub
guide uses unicast for the exchange on one computer.
- The UDP metadata announcement is sent unsecured on secured writer
groups; a secured subscriber drops it with a warning.
- A server does not start in globalization-invariant mode, because
`ResourceManager` creates `CultureInfo("en-US")`. The Native AOT guide
cautions against the mode.
## Validation
- `git diff --check` and
`./.azurepipelines/validate-migration-plugin.ps1` (15 bundled migration
docs validated, plugin manifests synchronized).
- Local Markdown links, anchors, file-link casing, and contents lists
checked across all 87 documents under `docs/`. The index links every
top-level guide and every target of the previous index.
- Both Getting started routes, Your first information model, the PubSub
exchange, and the Native AOT publish of the Getting started client and
server were run as documented. The NuGet route and the tutorial use the
public `2.0.0-preview.5` packages.
- The changed libraries build for all target frameworks in Release
without warnings.
- Tests on .NET 10: Opc.Ua.SourceGeneration.Tests 199 passed;
Opc.Ua.SourceGeneration.Core.Tests 3,977 passed, 8 skipped;
Opc.Ua.Server.Tests `Fluent` category 621 passed; Opc.Ua.Client.Tests
subscription-engine tests 70 passed; Opc.Ua.PubSub.Tests 1,374 passed, 2
skipped; PubSub fuzz replay 145 passed. The new and affected tests also
pass on .NET Framework 4.8.
- Earlier merge validation: 174 resource-isolation tests passed on .NET
10.
- The full solution suite was not run locally.
Review and validation artifacts are maintained outside the repository.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
||
|
|
d6356766c5 |
Resolve the open CTT findings: aggregate fixes, de-DE ApplicationName and CTT configuration (#4545)
# Description Resolves the open items of `plans/ctt-issues.md` (*Needs clarification*, *Open server findings*, *CTT project configuration notes*). Each item was checked against the OPC 10000 spec text. A server change was made only where the server was wrong, and every server change has a regression test. CTT script and oracle defects are filed in Mantis instead. ### Server changes (`Opc.Ua.Server`) - **NumberOfTransitions honours TreatUncertainAsBad.** - Part 13 §5.4.3.24 does not count Bad values. §4.2.1.2 makes Uncertain values "equivalent to Bad" when TreatUncertainAsBad is True, and the aggregate definition doesn't override that. - Uncertain values are therefore no longer counted, and no longer used as the previous value, when the setting is True. The now unused `TimeSlice.NonBadEarlyBound` is removed. - **DurationInStateZero/NonZero status follows the variable's interpolation.** - §5.4.3.2.2: "A region is Uncertain if a region ends in a Bad or Uncertain value and SlopedInterpolation is used." - The calculator always built stepped status regions. So on a sloped variable, the last region stayed Good while TimeAverage2 and Total2 over the same interval were already Uncertain. - The duration value doesn't change. - **AnnotationCount returns `Bad_NoData` and sets Partial.** - Table 68 requires `Bad_NoData` for intervals before the start or after the end of data. §5.3.3.2 requires the Partial bit for incomplete intervals. - `HistorianDispatcher` now derives the start and end of data from the raw history (read with bounds) plus the annotation timestamps. It passes them to a new `CalculateAnnotationCounts` overload; the old overload is unchanged. - Three existing dispatcher tests change their expectations. For example, a node without any data now returns `Bad_NoData` instead of Good zeros. ### Reference server sample and CTT configuration - **`de-DE` ApplicationName.** The server advertises `de-DE` in LocaleIdArray, but FindServers and GetEndpoints always returned the en-US name. The sample now keys the ApplicationName and registers en-US and de-DE translations (Part 4 §5.5.2.2 / §5.5.4.2 localeIds). - **Subscription limit.** `Max Supported Subscriptions` in `samples/UAReferenceServer.ctt.xml` and `MaxSubscriptionCount` in `Ctt.ReferenceServer.Config.xml` go from 100 to 200. Subscription Publish Min 05 `003.js` needs 190 subscriptions. - **Alarm Cycle Time.** The CTT template's Alarm Cycle Time is 30 s. At 60 s it equalled `RequestedSessionTimeout`, so a Limit/Level CU that starts a run finds its session expired (C40). ### Tests only - **AddNodes duplicate BrowseName test:** `AddNodeAsync_DuplicateBrowseNameUnderParent_ReturnsBadBrowseNameDuplicatedAsync` never ran. Its items had no TypeDefinition, so its `Assume` always made it inconclusive. The finding it belongs to (AddNodes after a rename returning `BadNodeIdExists`) was already fixed by #4477. - **RequestHandle echo:** a new test covers the ServiceFault for an oversized argument in a chunked request (already fixed by #4488). - **Value-based status:** a new test covers the aggregate status with TreatUncertainAsBad = false (already fixed by #4503). ### Not changed - **GDS QueryApplications/QueryServers still return records with ServerCapability `NA`.** OPC 10000-12 §6.5.10/§6.5.11 says they "shall not", but the CTT GDS scripts expect those records. Implementing the exclusion turned 40 GDS test cases from pass to error, so it waits for the script fix ([Mantis 11458](https://mantis.opcfoundation.org/view.php?id=11458)). - **Idle SecureChannels are still closed after `ChannelLifetime`.** Keeping them open would be security relevant. The CTT configuration's `ChannelLifetime` of 120000 (#4502) covers the CTT case. ### Mantis issues filed | Id | Issue | | --- | --- | | C51 | [11458](https://mantis.opcfoundation.org/view.php?id=11458) GDS scripts expect NA records in query results | | C52 | [11459](https://mantis.opcfoundation.org/view.php?id=11459) A & C Refresh scripts leak subscriptions on the shared alarm session | | C53 | [11460](https://mantis.opcfoundation.org/view.php?id=11460) `StartThreadPublish.js` defaults misspelled properties | | U2 | [11461](https://mantis.opcfoundation.org/view.php?id=11461) Minimum2 oracle on reverse reads | | U4 | [11462](https://mantis.opcfoundation.org/view.php?id=11462) Part 13 clarification: TreatUncertainAsBad and simple bounds (DeltaBounds) | The A & C `BadSubscriptionIdInvalid` failures (Refresh, Refresh2, Shelving) come from CPU load: with other processes at 100 % CPU, all subscriptions of the CTT alarm session expire within one second. This happens with and without this PR (3 of 3 loaded runs, 0 of 2 idle runs). ### CTT verification CTT 1.05.06, scripts 1.05.513, compared at test-case level against a build of the starting commit: | Group | Before | After | | --- | --- | --- | | Aggregates | 4,562 error messages | 4,186. NumberOfTransitions: 12 test cases go from error to pass. AnnotationCount `005-01.js`/`005-02.js` pass. NumberOfTransitions `003-02.js`/`004-02.js` now fail because of C1 ([Mantis 11420](https://mantis.opcfoundation.org/view.php?id=11420)): they request TreatUncertainAsBad = false, but the harness makes the server use its default of true. | | Discovery | 10 warnings | 0. Find Servers Filter `003.js`/`006.js` and Get Endpoints `002.js` pass. | | Subscription | – | Publish Min 05 `003.js` goes from warning to pass. | | Historical Access, GDS, Node Management | – | no new failures | `plans/ctt-issues.md` is updated: resolved items leave the open sections, and the new Mantis issues are recorded. ## Related Issues - Related to #4479 (CTT configuration notes) - Follows #4502 ## 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. - [ ] 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. Tests run locally on net10.0 after merging master: | Project | Filter | Passed | | --- | --- | --- | | `Opc.Ua.Server.Tests` | Aggregate, Historian, NodeManagement, Publish, Subscription | 1,419 | | `Opc.Ua.Core.Tests` | TcpServerChannelBuffer | 65 | | `Opc.Ua.History.Tests` | Aggregate, Annotation | 67 | | `Opc.Ua.Sessions.Tests` | discovery locale tests | 8 | Before the merge, the whole `Opc.Ua.Sessions.Tests` ClientTest fixture passed (372 passed, 0 failed). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
96c5ef1623 |
Run the fluent configure pipeline in FluentNodeManagerBase (#4537) (#4538)
## Proposed changes Closes #4537. The configure pipeline now lives in `FluentNodeManagerBase`, so a hand-written fluent node manager only overrides `ConfigureAsync` and no longer writes `CreateAddressSpaceAsync` itself. `FluentNodeManagerBase.CreateAddressSpaceAsync` now: 1. loads the predefined nodes (as `AsyncCustomNodeManager` did before), 2. creates a builder for `NamespaceIndex` with `CreateFluentBuilder`, 3. awaits `ConfigureAsync(builder, ct)`, 4. runs `RegisterAuthoredNodesAsync` and then `CompleteConfigureAsync`, 5. finishes with `SealConfigurationAsync`, which seals the builder, replays `NotifyNodeAdded`, then activates behaviors and simulations. ### Breaking change (2.0) Some managers need a different order, for example a post-setup runner before sealing, a builder for another namespace, or work after sealing. They must stop calling `base.CreateAddressSpaceAsync`, because that would now run a second, empty pipeline and seal the manager-owned registries too early. These managers now call `LoadPredefinedNodesAsync(SystemContext, externalReferences, ct)` and keep driving their own pipeline: - the source-generated `NodeManager` template (the ordering test in `NodeManagerGeneratorTests` is updated) - `DiNodeManager`, `Isa95NodeManager`, `PositioningNodeManager`, `RobotIntentNodeManager`, `VisionNodeManager` - `samples/OpenUsd/SiteCompositionServer/SiteNodeManager` These now use the base pipeline: - `samples/FluentApi/BasicFluentApiServer/BasicFluentNodeManager` becomes a single `ConfigureAsync` override. Before this change it never sealed its builder. - the hosting `FluentNodeManager` (from `AddNodeManager(namespaceUri, build)`) becomes a `ConfigureAsync` override. It now seals through `SealConfigurationAsync`, so `OnNodeAdded` handlers wired in the build delegate fire. Before, it called `builder.SealAsync()` directly and they did not. Docs (`NodeManagers.md`, `DeviceIntegration.md`) are updated. `NodeManagers.md` gets a short hand-written `ConfigureAsync` example. ## Types of changes - [x] Breaking change (fix or feature that would cause existing functionality to change) - [x] Enhancement (non-breaking change which adds functionality) ## Checklist - [x] I have read the CONTRIBUTING doc. - [x] I have signed the CLA. - [x] I ran tests locally with my changes, all passed. - [x] I fixed all failing tests in the CI pipelines. - [x] I fixed all introduced issues with CodeQL and LGTM. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added necessary documentation (if appropriate). ## Tests New `FluentNodeManagerBasePipelineTests`: nodes authored in `ConfigureAsync` get registered, the Objects-folder reference is mirrored into `externalReferences`, `OnNodeAdded` fires once, and the builder is sealed afterwards. Local run on net10.0, all passing: - Opc.Ua.Server.Tests (Fluent/Hosting/StateMachine/RuntimeNodeSet filter): 1286 - Opc.Ua.SourceGeneration.Core.Tests (NodeManager filter): 40 - Opc.Ua.Di.Tests 426, ISA95 136, Positioning 69, Robotics 636, Robotics.Intent 22, Vision 484 (+1 skipped), Vision.Intent 14, OpenUsd 921 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
13f96fd4ba |
Raise ChannelLifetime for the CTT reference server and record the full CTT run of the merged CTT PRs (#4502)
# Description Findings of a full CTT run (23 parts, 3,752 test cases, CTT 1.05.06 / scripts 1.05.513) against the merge of #4477, #4482, #4485 and #4486, which are all on master now, plus the CTT defects filed in Mantis. No server code changes: the PR changes only the CTT configuration of the reference server sample and the `plans/` documentation. ## CTT configuration: raise `ChannelLifetime` to 120 s CTT Security None `007.js` and Security Basic256Sha256 `005.js` failed (also on master): they keep SecureChannels without a Session idle for about 51 s (60 s token, stretched by the CTT's CloseSession delay, C50) and expect the newest one to close with Good. `TcpTransportListener.DetectInactiveChannels` had already closed it after `ChannelLifetime` (30 s) without traffic (`CloseSecureChannel().Result received BadInvalidState`). `samples/Reference/ConsoleReferenceServer/Ctt.ReferenceServer.Config.xml` now sets `ChannelLifetime` to 120000. The default of 30000 and the server's inactivity cleanup are unchanged. An earlier revision of this PR changed the inactivity cleanup instead, keeping idle Session-less channels open until their token expired (Part 4 §5.6.2.1). That change was withdrawn because it lets an unauthenticated client hold a channel for up to the `SecurityTokenLifetime` (one hour by default) instead of 30 s. The difference to Part 4 §5.6.2.1 is recorded as an open server finding in `plans/ctt-issues.md`. Validated on 2026-09-24 with the reverted code and the new configuration (full run, compared with the run of 2026-09-22 at test-case level): - Security None `007.js` passes and Security Basic256Sha256 `005.js` has only the C50 CloseSession delay warnings (both failed with 30 s). - All other parts have the same error signatures as before; the remaining differences are *"Response Timestamp shows a delay"* warnings from a loaded machine (a back-to-back Session Services run with 30 s and 120 s on an idle machine is identical). - A & C Refresh2 `Err_003.js`/`Err_004.js` and Shelving `initialize.js` report `BadSubscriptionIdInvalid` in today's runs with both 30 s and 120 s on the same build, so they are unrelated to the configuration (both passed again in a run on the idle machine with 120 s). ## Mantis 32 CTT defects filed on 2026-09-24 (11420–11454, script issues in *CTT UA Scripts*, native aggregate oracle issues in *CTT UA Binary*); 12 drafts were already covered by existing reports (including GDS issues 11406–11413 by node-opcua and 11102 for the `opc.https`/`opc.wss` endpoint selection); 5 are held back for further investigation. `plans/ctt-issues.md` links every entry. ## CTT documentation (`plans/ctt-issues.md`, `plans/ctt-testing.md`) - `ctt-issues.md` lists every failing CTT test case with a short abstract and its Mantis issue; the details live in Mantis. It keeps the defects not filed yet, the open questions (U1–U4), the open server findings and the CTT project configuration notes, and no longer lists findings the server has fixed. - Notable CTT findings of this run: the aggregate oracle lacks the Part 13 Uncertain status and Calculated bit rules that #4477 implements (C48/C49, 24 test cases); the CloseSession "delay in excess of 600ms" warnings are a CTT client artifact (C50: the server answers in 2–5 ms, the CTT stops the Session's SessionThread between stamping and sending); the CTT's `FindTokenType` rewrites its cached endpoints, so Security User Name Password 2 `015.js` reports duplicate PolicyIds (C36); A & C Comment passes or skips depending on the date (C39). - Open server finding: AddNodes after a child rename returns `BadNodeIdExists` (#4477 × #4486 interaction, not covered by the CTT). - `ctt-testing.md` is a runbook for agents: setup, headless runs, selection generation, reading and comparing results, triage, recipes per conformance group (A & C, Session/Subscription, Monitored Items, security groups with an isolated PKI) and a full partitioned run of every group (about 80 minutes). It points to `ctt-issues.md` for the failures to expect. ## Related Issues - Follow-up to #4477, #4482, #4485, #4486 ## 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. - [ ] 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. Validated locally with a full partitioned CTT run of the Release net10.0 reference server (reverted code, `ChannelLifetime` 120000). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
490d15cb20 |
Add support for adding input/output arguments to methods using fluent api (#4519)
# Description Added extensions for adding arguments to method in fluent api style Added BasicFluentApiServer project to samples ## Related Issues Add support for adding input/output arguments to methods using fluent api #4518 ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [x] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
e9313ef957 |
Only use GitHub action for CI and nightly, establish release-branch-only package publication (#4513)
## Description Two related changes to how the 2.0 line is built and shipped. ### 1. Release-branch-only package publication - `master` produces development previews only. - Stable packages can only be built from a canonical `release/<major>.<minor>` branch and are promoted manually from the exact signed candidate artifact. - Core packages use explicit `major.minor.patch` versions (`2.0.0`, `2.0.1`, then `2.1.0`); git height never changes a stable package patch. - XRegistry, WoT Connectivity, Vision, Robotics, Redundancy, Positioning, OpenUSD, ISA95, AI, DI, the relevant MCP extensions, and OpenUSD connector tools remain `-preview.N` while following the core numeric version. - nuget.org receives only non-`.Debug` package IDs; GitHub Packages retains the complete signed Release/Debug candidate. - `docs/ReleaseProcess.md` is a reproducible runbook for developers and agents, with each step stating its completion evidence. Promotion is source-bound and byte-checked: `release.yml` checks out nothing until the candidate SHA is known, and `assert-published-packages-match.ps1` proves that any version already on a feed holds *this* candidate's content before `--skip-duplicate` is allowed to skip it. Canonical release-branch naming is matched case-sensitively with no leading zeroes, and `version.json`'s `publicReleaseRefSpec`, both policy helpers and the copy `release.yml` inlines are pinned to one expression by tests. ### 2. PR CI moved to GitHub-hosted runners - `.github/workflows/buildandtest.yml` (**CI**) now owns pull-request validation outright, covering the Windows Debug/Release net48 + net10.0 solution builds and fast tests that only Azure Pipelines ran, alongside the existing Linux/macOS, all-target and NativeAOT legs. `build-and-test summary` remains the stable required check name. - `.github/workflows/nightly.yml` is a new **manual-only** (`workflow_dispatch`) full-scope workflow covering the Azure nightly workload: all solutions x seven target profiles x Debug/Release, the net472/netstandard profiles, Windows + Linux NativeAOT, and the long-running and durable tiers. - Shared deterministic discovery/batching lives in `.github/scripts/get-ci-matrix.ps1` with a local composite action, so both entry points expand the same workload manifest instead of duplicating a large embedded script. - `azure-pipelines.yml` is set to `pr: none`. - The standalone pump sample image workflow is folded into the consolidated **Images CI** workflow, which now builds all ten images and publishes an `images summary` rollup suitable as a required check. ## Related Issues No related issue provided. ## 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. ## Validation - `Opc.Ua.Tools.Tests` full `net10.0` suite: **959 passed, 1 skipped, 0 failed**. - Version/package-policy regression subset (`PackageVersionPolicyTests`): **60 passed, 0 failed**. - GitHub Actions **CI** run 35719807620: **149/149 jobs green** on GitHub-hosted runners. - Canonical release-branch rule verified against the pinned nbgv 3.7.115 by building throwaway branches: `release/2.0`, `release/0.9`, `release/10.42` -> `PublicRelease=True`; `release/02.0`, `release/2.00`, `Release/2.0` -> `False`. - Package identity gate verified against a live nuget.org package: served packages carry a repository `.signature.p7s`, so equality is decided on content excluding that part. Re-signing and zip reordering produce an identical digest; a one-byte content change does not. - Synthetic Release-only, Debug-only, combined, preview, duplicate, orphaned-symbol, mismatched-root and accidental-stable-family package sets exercise the shared validator. - Stable-release rehearsal in an isolated `release/2.0` clone: `Opc.Ua.Di` packed as `2.0.0-preview.6` depending on stable Core `2.0.0`; `Opc.Ua.Di.Client` likewise against stable Client `2.0.0`. - All workflow YAML, `version.json`, the package-policy PowerShell scripts and the internal release-runbook anchors parse. ## Known validation limits - Full `UA.slnx` `net48` and `net10.0` local validation remains blocked by pre-existing `NETSDK1005` and dependency-version failures in unmodified fuzz/sample projects; the corresponding checklist items intentionally stay open. - The TRX-driven test verdict added for the intermittent at-exit host stall is proven by `CiTestVerdictTests`; the stall itself has not recurred since. - `nightly.yml` has not yet been run against a full-scope Azure run for tuple-level parity — see below. ## Operational follow-up This remains a preparation PR targeting `master`. It does **not** authorize a release. Release sequence: 1. Merge this PR. 2. Create canonical `release/2.0`, advance `master` to `2.1.0-preview.{height}`, and open a separate `2.0.0` version PR against `release/2.0`. 3. Follow [`docs/ReleaseProcess.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/docs/ReleaseProcess.md) for candidate dry-run, environment approval, promotion, tag/release finalization, and next-patch preview setup. CI migration steps still needing administrator access are tracked in [`docs/DeveloperGuide.md#migration-status`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/docs/DeveloperGuide.md#migration-status): proving `nightly.yml` parity on a trusted SHA with the private fuzz corpus provisioned, turning off definition 14's service-side PR override, retiring the Azure full-scope schedules and push trigger, and retiring the duplicate `azure-pipelines-preview.yml` publisher. > **Before cutting `release/2.0`:** the `Release` ruleset requires the Azure context for *every* `refs/heads/release/*`, but a new 2.x branch inherits `pr: none`, so that context would never report and would block every PR into the new line. Exclude the new ref from that ruleset, or add a 2.x ruleset requiring `build-and-test summary`, as part of creating the branch. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
138086863b |
Fix audit findings and review regressions across the OPC UA stack (#4503)
## Scope Audit fixes and review follow-ups across the OPC UA Core, Client, Server, transport, and redundancy components. This includes implemented Tier 1, Tier 2, and Tier 3 work; it does not claim that every item in the 244-finding audit backlog is complete. The review batch fixes mandatory full-key record protection and owned plaintext buffers; transactional pending-certificate claims; shared-channel nonce and certificate ownership; discovery and token-policy validation; reconnect/callback ordering without changing unbounded buffering defaults; server permissions, filters, monitored-item lifetime, and alarm topology; historian paging, retention, and annotations; atomic user/password metadata; and canonical file identities with bounded alias and role refresh workers. ## Review-batch verification at `ab686ccae` The following results come from fresh test builds using `dotnet test`, `--no-restore`, `-m:1`, and an explicit `CustomTestTarget`. They replace the earlier description's library-build/`--no-build` totals, which did not establish the state of the merged test sources. | Scope | Target | Passed | Skipped | |---|---|---:|---:| | Core review regressions | net10.0 | 827 | 0 | | Client and lifecycle regressions | net10.0 | 630 | 0 | | Server review regressions | net10.0 | 1970 | 0 | | Redundancy Server / Client / PubSub | net10.0 | 276 / 10 / 11 | 0 | | Sessions / file-and-alias / streaming / HTTPS-WSS integration | net10.0 | 8 / 21 / 30 / 20 | 0 | | Core review regressions | net48 | 786 | 39 | | Client recovery and policy regressions | net48 | 54 | 0 | | Server ownership, aliases, queues, and user-state regressions | net48 | 137 | 0 | Every listed run exited successfully. The 39 net48 skips are existing platform/capability cases. New key-agreement tests assert unavailable-policy behavior on unsupported targets rather than adding skips or pretending those targets performed an unsupported handshake. Additional exact node/permission/filter/alarm checks passed 13 Core and 46 Server cases. The PR-scoped formatter verification covers 26 projects and 389 non-generated C# files. A Roslyn syntax audit covers 53 changed interfaces and 263 members, with no missing parameter/type-parameter/return contracts and a 120-column limit for their declarations and documentation. No full-solution, all-target-framework, or NativeAOT matrix is claimed from these local runs; the new CI run must establish its own result. ## CI follow-up at `e1bc3ac3b` The follow-up fixes the failures reported by Azure build 18400. TLS certificates retain configured DNS names, including mixed-case URI schemes, without relaxing hostname validation. Reverse-WSS fixtures use the DNS identity in the client certificate. Reconnect completion is published only after cycle cleanup, so a subsequent reconnect cannot join an old completed cycle and retain old certificates. Certificate-alarm roots now support event subscriptions. The pump sample has one continuous notifier path, with no duplicate shortcut. Historian and aggregate fixtures share the provider's retention clock. Alarm collection lets the server expire idle Publish requests rather than canceling only the client wait. The corrected server, WoT, and managed-session fixtures exercise valid requests and initialized lifecycle contracts. Downlevel crypto tests preserve real credential and key-ownership assertions without enabling unsupported raw-ECDH policies or adding skips. Fresh local evidence after integration includes: | Scope | Target | Passed | Skipped | |---|---|---:|---:| | Client lifecycle / channel ownership / builder fixtures | net10.0 and net48, each | 96 / 103 / 123 | 0 | | Combined server lifecycle, events, aggregates, crypto, and contracts | net48 | 358 | 0 | | Full lifecycle plus certificate-alarm and aggregate fixtures | net10.0 | 116 | 0 | | Final server-contract and history-routing delta | net10.0 and net48, each | 84 | 0 | | Subscription / XRegistry / WoT event regressions | net10.0 | 7 / 2 / 3 | 0 | | Full Sessions project | net10.0 | 780 | 379 | | Full ComplexTypes project / downlevel resolver fixtures | net10.0 / net48 | 2854 / 15 | 0 | | Full WoT project / downlevel structured-write fixtures | net10.0 / net48 | 1496 / 27 | 0 | | TLS domain-name regressions | net10.0 and net48, each | 21 | 0 | All listed runs exited 0. The Sessions skips come from existing endpoint and policy capability guards; they are not counted as passes. No skip was added. Worker validation also covered full DI (426 passes), full History (540 passes, 21 existing skips), 38 exact downlevel event/history failures, and 60 repeated certificate-rotation race cases. The final crypto fixtures passed 73 cases on each target with the unchanged leak detector; a temporary premature-disposal mutation made all three borrower tests fail and was then removed. All 29 C# files in this follow-up pass included-file formatting at info severity across 12 projects. The complete PR interface audit now covers 54 interfaces and 271 members with zero missing contracts or line-width findings. No full-solution or NativeAOT matrix is claimed. New-head CI has started and remains authoritative for the remote result. ## Test-result gate follow-up at `4b6e1203a` Azure build 18401 reported the net48 Client job as successful despite a test-host crash after only 264 passing tests. Its old evaluator counted per-test failures but ignored the failed run verdict. This was a partial run, not a benign exit after the suite had completed. The shared result evaluator now rejects aborted, errored, incomplete, malformed, or inconsistent TRX reports. Each file must describe a completed run, so a passing file cannot mask another file's failure. It retains support for completed runs followed by a benign nonzero host exit, existing skipped-result conventions, and no-TRX MTP jobs. All 78 regression cases pass after integration using Windows PowerShell 5.1. Real completed Sessions and Client reports also pass. The process-test fixture follows the existing NET10_0-only tools-test pattern; its net48 build is not counted as a test run. Formatting passes for the added test file. The Client host crash itself has not reproduced locally. The isolated fixture passed 59 tests, and two fresh full fast-CI selections each completed with 2,392 passes and three existing skips, including a run with crash collection enabled. The CI merge tree is identical to the tested source tree. No speculative runtime change was made, and the original partial CI run is not claimed as a full-suite pass. Azure build 18402 has now completed the full net48 Client run: 2,392 passed, three existing console skips, and a successful test-host exit. The corrected evaluator independently validated outcome=Completed. Across the matrix, 119 test-project jobs succeeded; Sessions net10.0 was canceled when the remote provider deprovisioned its runner, so the overall Azure pipeline was canceled. That cancellation is not claimed as a code failure or a successful run. CodeQL and three Docker image jobs were still running at the latest check. The migration discovery failure remains separate. ## Documentation follow-up at `57ed02bea` The environment blocker has been removed by the maintainer. The three bundled migration references now exactly match the canonical documents after deterministic link rewriting; all 15 bundled documents pass `validate-migration-plugin.ps1`. The canonical migration prose was not rewritten. `FileSystemClient.md` now documents that extended Windows roots preserve literal trailing-dot names, completing the filesystem review follow-up. The filesystem thread has been replied to and resolved after publication. The review pass now has 108 of 110 threads resolved. The two explanation-only threads remain open as requested; no unfinished change request is being represented as resolved. ## Cross-platform follow-up at `0d9d936a8` With migration discovery restored, the Linux and macOS test matrix exposed two stale durable-subscription expectations. The expected value 83,442 encoded the old 32-bit overflow when converting 3,600 hours to milliseconds. The existing overflow-safe server correctly returns 14,400,000 lifetime intervals at a 900 ms publishing interval. Only those two expected constants changed; production behavior, negotiated-hour checks, and all other assertions remain unchanged. The exact local lifetime selection reproduced both failures before the correction. The full durable suite now passes 36 cases with 11 existing skips on each of net10.0 and net48. All four lifetime cases executed and passed on both targets, and included-file info-severity formatting passes. ## Session shutdown follow-up at `8daf7f091` A late net48 Sessions failure exposed a shutdown race. Canceling an in-flight keepalive read can produce a transport `ServiceResultException`. The retiring worker treated its own cancellation as proof that the peer was unavailable, so `CloseAsync` or `DisposeAsync` could skip the server-side `CloseSession` request. The worker now ignores that service error only when its own token is canceled; genuine peer errors still update liveness. The full integration fixture reproduced the original error-code mismatch. Four deterministic mock-transport cases then produced three failures and one passing genuine-error control before the fix. They verify successful close, returned server error, asynchronous disposal, and unchanged behavior for a real keepalive failure. Fresh related runs pass 75 client and 101 integration cases on each of net48 and net10.0, with no skips. Ten repetitions of the formerly flaky full net48 fixture pass all 1,010 cases. All three changed files pass info-severity formatting, and temporary diagnostic instrumentation was removed. ## Atomic listener snapshot follow-up at `82dc22b5d` Ubuntu Core CI exposed an unsafe formatter rewrite in TCP listener disposal: `ConcurrentDictionary.ToArray()` had become a collection spread. A concurrent channel removal between the spread's count and copy operations could leave a default entry, causing a null dereference during disposal. The listener now uses the dictionary's atomic `Values` snapshot and disposes those channels outside the listener lock. Deterministic regressions close zero, one, or all channels at the copy boundary. Before the fix, the one/all-removal cases reproduced the exact null dereference; the unchanged-count control passed. All 23 admission/listener cases now pass on net10.0 and net48, and the full fast Core net10.0 suite passes 5,267 cases with 88 existing platform skips. Both changed files pass info-severity formatting. A bounded audit of the formatter's other snapshot rewrites found no further verified unsafe concurrent-registry copies. ## Shutdown-test observation follow-up at `58403ee8b` The net48 shutdown-order test raced its own observation: it read `m_publishWorkerTask` after starting shutdown, which can legitimately drain the worker and clear that field immediately. The test now captures the worker before shutdown starts and confirms it is initially running. It still requires the worker to finish while shutdown remains blocked by the held manager semaphore. Production behavior is unchanged. A temporary scheduling perturbation reproduced the exact null-field failure. The corrected capture passed 100 perturbed iterations; both the temporary repeat and yield were then removed. The final 126 subscription/lifecycle cases pass on each of net48 and net10.0, and formatting passes. A complete local Sessions net10.0 run also passed 780 cases with 379 existing capability skips in 7m20s after the earlier keepalive correction. ## Retirement and alias-startup follow-up at `1a048ff39` The shadow-generation test now observes its documented asynchronous cleanup through the existing bounded helper. Removing the replacement does not join a background drain that already claimed the retired generation. The test still checks live-registration removal immediately and requires the old node to disappear; no timeout or production lifecycle behavior changed. All 81 lifecycle cases pass on each of net10.0 and net48. Initial alias-node materialization now retries one timed-out, read-only snapshot query before creating nodes for that root. The retry is logged through source-generated logging. Each query retains the original shared 100 ms deadline; repeated timeouts, other errors, and cancellation still abort startup. Client searches and live refresh do not gain this immediate retry. Fault-injection tests cover success after one timeout, a persistent timeout, a different store failure, and cancellation. All 187 related alias/hosting cases and the original certificate-rotation integration case pass on each target; the positive test records exactly one retry warning. The alias documentation now accurately describes the shared request budget. All changed C# files pass formatter checks, and the complete 54-interface/271-member documentation audit passes. Fresh CI is running on `1a048ff39`; the previous completed matrix exposed only the two failures corrected in this batch and their dependent summaries. ## Sample endpoint allocation follow-up at `d08a94925` The strong-cluster test harness allocated OPC UA and Raft endpoints in separate released-port batches. Uniqueness was enforced only inside each batch, so the same OS-assigned port could appear in both protocol groups. The harness now requests one distinct batch for both groups and splits it before launching any replicas. Deterministic one- and three-replica allocation cases reproduced overlapping groups with the old plan and pass with the corrected plan. The complete 17-case short-haul sample suite passes without skips; all failover, historian-continuation, and service assertions remain unchanged. Both changed files pass info-severity formatting. This prevents intra-cluster self-collisions but does not claim to reserve released ports against unrelated external processes. ## Code-review follow-up at `085fd9636` A review of the complete branch diff found five defects this PR introduced. All are now fixed, each in its own commit. `RelatedTo`'s recursive-to-iterative rewrite defaulted the optional `includeTypeDefinitionSubtypes` and `includeReferenceTypeSubtypes` operands to `false`. Part 4 §7.7.4 specifies both default to `true`, so a filter that omitted them silently stopped matching subtypes. Dynamically created key-credential nodes derived their namespace from the containing folder, which is the standard `i=18155` node in namespace 0 — the namespace reserved for the OPC UA standard address space; they now resolve the server-owned namespace and fail with `BadInternalError` rather than guessing index 1. The retained audience-less `CreateTokenData` overload produced tokens that verification always rejects, so it is now `[Obsolete]` and throws instead. A failed reference to a remote parent left the new node registered with a one-sided inverse reference, and is now rolled back. An existing `.pfx` is restricted before it is rewritten, rather than after its new key bytes are on disk. One further reported finding was rejected after investigation. `JwksIssuerKeyResolver` deliberately does not dispose retired key snapshots: `RetiredKeysAreCollectibleWhileResolverRemainsAliveAsync` proves the resolver retains no retired snapshot, and `ReturnedKeysSurviveRepeatedRefreshAndResolverDisposalAsync` requires already-returned keys to keep verifying across refreshes and resolver disposal. Adding disposal would have broken that tested contract, so the exploratory change was reverted. Validated on net10.0 and net48: 256 Core and 633 Server cases pass, with one pre-existing net48 skip. The build reports zero compiler and analyzer diagnostics, all eight included-file info-format checks pass, the contract audit covers 58 interfaces and 310 members with no findings, and the 15 bundled migration documents validate. All 122 review threads are resolved; the key-credential namespace thread was reopened to retract an earlier incorrect answer and then resolved against the real fix. ## Review-body follow-up at `da0d59cfb` The re-review at `8daf7f091 + 82dc22b5d` listed four items in its summary that had no inline thread of their own, so they had not been picked up. All four are now fixed. Both event-dispatch permission gates had been tightened from not-Bad to `IsGood`, which silently dropped events whose permission verdict is Uncertain; the data-change path in the same file still treated Uncertain as deliverable, so the two had diverged. `EventSourceRegistry` only cleared `ConsecutiveFailures` when a source was deactivated, so a source that had backed off to the 30-second cap kept that delay for the rest of the process even after starting successfully. `UserNameIdentityTokenHandler` built a temporary issuer chain whose `Add` calls take independent certificate references, and never released them. The fourth was the receiver-side sequence rollover, which the reviewer had correctly re-flagged as still open despite its thread being resolved. `m_sequenceRollover` was set permanently, so the second legal wrap on a long-lived channel was rejected and faulted the channel along with the transport-reconnect OPN. OPC 10000-6 does not cap a SecureChannel at one wrap. The flag is now cleared once an accepted number has traversed the normal range, which keeps rejecting a replayed low number that never left the rollover window. Validated on net10.0 and net48: 636 / 593 Core cases and 135 / 134 Server cases pass, including the new `UaSCBinaryChannelSequenceRolloverTests` and the Uncertain-permission delivery cases. The build reports zero compiler and analyzer diagnostics and the changed files pass included-file info-format checks on both targets. The previous head `085fd9636` was fully green: 279 checks passed and Azure build 18414 succeeded with 137 of 137 jobs, after the platform automatically retried a job whose NuGet tool download had hit a transient TLS error. ## Reported issues #4508 and #4509 at `6ad1ecf16` **#4508 (V2 subscription recreate deadlocks against a faulted transport) was not yet fixed and is now closed by `bac260a06`.** Both drain sites the report identifies were still unbounded. A publish worker that started its request before the quiesce parks inside the service call on the channel ready gate while holding an active publish request; the recreate that would make the channel ready is the very operation awaiting the drain, so the wait never ends. The drain now cancels in-flight attempts through a per-attempt token linked to the worker token. An aborted worker rolls its acknowledgements back, releases the request and returns to the paused park instead of terminating, so it resumes when the caller leaves the quiesce. Publish ingress stays quiesced for the caller's operation, so `DropPendingForSubscription` keeps the guarantee the report asks to preserve and no blind timeout is added. The new regression parks a worker on a cancellable gate that never completes on its own: it reproduces the hang for the full 30-second budget before the change and passes after it. **#4509 (identity provider fails the initial connect) was already fixed for its primary symptom.** `HandleConnectAsync` materializes the provider's identity through `CreateIdentitySelectionContext` before the first `CreateAsync`, so Anonymous is no longer substituted against a UserName-only endpoint. `6ad1ecf16` adds the two remaining details from the report: the enabled-policy set now includes the negotiated channel policy, so a client cannot reject the policy it already accepted as `NotEnabledByClient`; and when discovery has not populated the endpoint's token policies the connect keeps the configured identity so the attempt fails at the transport layer for the reconnect policy to retry, instead of failing at the identity layer. Validated on net10.0 and net48: 477 / 473 client session and subscription cases pass, the build is free of compiler and analyzer diagnostics, and the changed files pass included-file info-format checks on both targets. The full `Opc.Ua.Sessions.Tests` suite also passes locally on net10.0 with 782 passed and 379 pre-existing capability skips. GitHub checks are green on this head: Build and Test .NET 10.0, Docker Sample Images CI, and NuGet Publish all succeeded. Azure build 18417 completed 134 of its 135 jobs successfully; its net10.0 Sessions job was cancelled by the hosted-agent provider ("Received request to deprovision"), the same infrastructure flake that hit build 18409 earlier on a head predating these changes. That job is not claimed as passing, and the local full-suite run above covers the same project. The previous head `da0d59cfb` completed Azure build 18415 with every job succeeded. ## Review follow-up at `a08f0c12b` Eleven new review threads arrived, nearly all documentation. Before acting on each "this is new in 2.0" remark I checked the API against `master378` rather than assuming, because a genuine 1.5 API still needs its migration entry. `ManagedSession`'s server-redundancy refresh had two hard-coded two-second bounds. The new `ServerRedundancyOptions` exposes `RefreshTimeout` for the metadata read and `PeerDiscoveryTimeout` for each peer lookup, defaults both to the previous two seconds, validates that a bound is positive or `Timeout.InfiniteTimeSpan`, and flows through `ManagedSessionOptions`, `ManagedSession.CreateAsync`, the fluent builder, and `DefaultServerRedundancyHandler`. `AsyncCustomNodeManager` and `KeyCredentialBridgeAuthenticator` are both absent from `master378`, so their obsolete members had no compatibility constraint and are removed rather than carried: `AsyncCustomNodeManager.ReadInitialValue`, the audience-less `CreateTokenData` that only threw, and the audience-less `CreateProof` that produced a proof no server accepts. `CustomNodeManager2.ReadInitialValue` and the `MonitoredNode2` migration section are genuine 1.5 APIs and are untouched. The documentation now states master's behaviour instead of the delta from 1.5.378, with each moved item already covered under `docs/migrate/2.0.x/`. `IHistorianAnnotationProvider` is described by its annotation-time key rather than as "legacy" — it is new in 2.0, so the label implied a compatibility constraint that does not exist. The KeyCredential bridge payload's `"version"` field is explained as a vendor-extension format version with no OPC UA counterpart, and its version-1 upgrade narrative is gone. `CertificateManager.md`'s migration walkthrough is replaced by the resolver API it targets. `NodeManagers.md`, `Sessions.md`, `AsyncServerSupport.md`, `Certificates.md`, and `Profiles.md` no longer announce features relative to 1.5. `Benchmarks.md`, `WhatsNewIn2.0.md`, `DeveloperGuide.md`, and `docs/migrate/` keep their 1.5.378 references, which are their subject. CI then exposed a defect this batch introduced: five fixtures resolve `ManagedSession`'s private constructor by its exact positional parameter types, so the added parameter compiled cleanly but made `GetConstructor` return `null` at run time. `7df1d5c40` updates every site; `Redundancy.Client` passes 126 of 126 and the affected client fixtures 112 of 112. The BouncyCastle question is answered and tracked in #4510: `Opc.Ua.Core` already project-references `Opc.Ua.Security.Certificates`, which already carries the conditional BouncyCastle reference, so the raw-ECDH polyfill belongs there rather than as a new Core dependency. The `IUserDatabase` metadata question is addressed to another maintainer and is deliberately left open. ## Reported issue #4509 follow-up at `5d4d13d63` The reporter verified `6ad1ecf16` and found one case still failing, correctly. The empty-token-policies branch kept the configured identity so a server that is down surfaces a transport failure rather than an identity error. A provider-only configuration has no configured identity, so it kept `null`: `OpenCoreAsync` turned that into Anonymous and `OpenValidateIdentity` rejected it with `BadIdentityTokenInvalid`, silently ignoring the configured provider. The branch now requires a configured identity to defer to. Without one the provider is consulted, so its own `BadIdentityTokenRejected` names the offered and rejected policy set instead. Two regression tests cover both configurations; the first fails against the previous behaviour by reaching the session factory with a `null` identity, which is the exact step that produced the reported error. That branch had shipped without a test, which is why the case was missed. ## Review and CI status at `5d4d13d63` **All actionable review threads are resolved: 132 of 133.** The single open thread is a question the reviewer addressed to another maintainer about moving user metadata into `IUserDatabase`; it is left open for them rather than resolved silently. Every resolved thread carries a reply naming its fix commit. **Latest-head CI is green on `5d4d13d63`: 279 checks passed, zero failed, zero pending**, with one conditional signed-package publishing job skipped. Local validation for this batch: the full `Opc.Ua.Client.Tests` suite passes 2,430 of 2,430 on net10.0, `Opc.Ua.Redundancy.Client.Tests` 126 of 126, and `Opc.Ua.Server.Tests` 590 of 590 for the affected selections. Both net10.0 and net48 build clean for `Opc.Ua.Client`, `Opc.Ua.Server`, and the changed test projects, and all changed files pass included-file info-severity formatting on both targets. The PR remains open; no merge was performed. As stated at the top, this does not claim that every item in the 244-finding audit backlog is complete. ## Latest CI follow-up at `b6f2dfa43` Azure build 18442 exposed a net48 compile regression in the Kafka adapter: `RuntimeHelpers.GetUninitializedObject` is unavailable on .NET Framework. Commit `2cf1e0eed` now uses normal `ProducerMessage` construction on `NETFRAMEWORK` while retaining reflection-based construction for modern targets that can load a different Dekaf runtime asset. The exact net48 CI selection passes 92 of 92 tests, and the netstandard2.1 adapter guard selection passes 11 of 11. After merging the latest `master`, GitHub Actions run 35643592139 is green. The initial Ubuntu Core.Schema job built successfully and passed 160 of 160 tests, then hit an external 403 while finalizing its test-result artifact; its failed-job rerun completed successfully. Azure build 18451 was canceled when its remote provider deprovisioned the net10.0 Sessions runner (`Received request to deprovision: The request was cancelled by the remote provider`). Azure attached no test task log or source error, and the canceled parent left that child check stale as in-progress. No source workaround, test skip, or check relaxation was made for either infrastructure failure. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 68f26810-22b8-476f-abcd-0a8c82d6e208 |
||
|
|
331a71ac2c |
Fix AI model version and inference argument handling (#4512)
## Summary
- Derive the AI server''s advertised specification version from
generated NodeSet metadata (new source-generated `ModelVersions.Target`
constant), so `AINodeManager.SpecificationVersion` can no longer drift
from the shipped model.
- Preserve inference parameters through inline, asynchronous, fallback,
and oversized-transfer execution instead of silently discarding them.
- Reject unsupported `PayloadUri` input explicitly with
`BadNotSupported` before any backend call, job creation, or transfer
allocation.
- Apply the supported `temperature` / `max_tokens` / `top_p` parameters
in both built-in backends via a shared parser.
Fixes #4505
Fixes #4506
## Review follow-ups (
|
||
|
|
d7dd1b0369 |
Fix telemetry component assembly identification (#4490)
# Description Telemetry extensions resolved meters and activity sources as `Opc.Ua.Types`, with results dependent on helper inlining. This prevented reliable attribution to the emitting component. - **Existing telemetry contract**: Add `CreateMeter(Assembly)` and `GetActivitySource(Assembly)` directly to `ITelemetryContext`. The separate `IAssemblyTelemetryContext` interface has been removed. - **Reliable component attribution**: Capture the caller assembly in non-inlined `CreateMeter`, `GetActivitySource`, and `StartActivity` extensions, then forward it to the existing telemetry context. - **Call-site routing and fallback**: Keep non-null meter calls on `telemetry.CreateMeter()`. Use explicit `TelemetryExtensions.CreateMeter(telemetry)` for nullable-context fallback. Preserve default meter/activity-source fallback when a context returns null. - **Coverage and documentation**: Cover caller-assembly naming, direct-context and extension routing, activity creation, and null-result fallback. Update diagnostics examples, OpenTelemetry filters, and migration guidance. **Compatibility:** Direct implementations of `ITelemetryContext` must implement the two new assembly-aware members. `TelemetryContextBase` supplies both. This interface addition is documented in the 2.0 migration guide; legacy implementer compatibility is not claimed. ## Local validation The Release build and focused `TelemetryExtensionsTests` fixture pass on both target frameworks: | Target | Result | | --- | --- | | net10.0 | Passed: 6, Failed: 0, Skipped: 0 | | net48 | Passed: 6, Failed: 0, Skipped: 0 | Both builds emit existing CA5394 warnings in `NodeStateChildNameIndexTests.cs`, which is unchanged by this PR. The full solution test suite was not run during this feedback pass. ## Related Issues ## 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. <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #4483 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> |
||
|
|
fa8d45f2cf |
Fix server/core validation, lifecycle and protocol regressions (#4477)
# Description **Server/core audit fixes with regression tests. Latest review-fix validation, merge validation and earlier acceptance results are distinguished below.** This addresses a 95-finding server/core report. Every finding was reviewed with the OPC UA specification knowledge base or, where the standard does not prescribe a C# implementation, the corresponding ownership/configuration contract. Incorrect or withdrawn subclaims remain qualified rather than being presented as additional fixes. The original checkpoint covers session identity/certificate validation and revocation; certificate/key ownership and transactional updates; TCP reconnect, receive/admission and buffer lifetime; HTTP quotas; subscription admission, durability and pooled payloads; file-operation lifecycle; historian aggregates; fluent/state-machine behavior; and related utilities. It includes targeted regressions and directly related documentation. The approved acceptance cleanup is separate in the commit history: | Cleanup | Verified result | | --- | --- | | Invalid identity-policy and KeyCredential fixture inputs | Fixtures explicitly advertise Username and use its generated policy ID. Credential persistence uses valid clear mode inside the existing encrypted admin context. Original permission, lockout and negative encryption assertions remain. | | Native GDS lifetime and concurrent connection failures | Retain ApplicationInstance until client teardown. A deterministic native reproducer proved the test-only channel cap of 10 was exhausted; removing that override restores the existing bounded default of 1000. No global serialization or production quota change. | | Stress certificate ownership and close accounting | Balance two helper references and the fake transport's server-certificate ownership. Replace the invalid event-count bound with exactly-once physical disposal, opens = closes, zero active entries and one logical entry lifetime. | | Participant deadlines | Use the injected TimeProvider with the existing cross-framework WaitAsync overload. Tests pin one tick before, exact expiry, terminal notification, recovery and timely completion. | | Reproducibility and platform assertions | Preserve canonical WoT bytes/hashes, explicit display-culture contracts and exact numeric-vector assertions. Accept Apple's CryptographicException subtype without weakening disposal balance. | | Missing user-signature proof | Reject null/empty proofs before OS crypto. Return BadUserSignatureInvalid per Part 4, 5.7.3.3. None-policy and valid-signature controls remain. A CI-only test nullable-context error was corrected without changing production behavior. | | Durable sample restore and shared test storage | Dequeue waits for actual batch residency instead of treating failed restore completion as success, and restored files close before deletion. Five deterministic regressions failed before the fix. The two fixtures that clear the shared batch directory are isolated; all original 3,000-item assertions remain unchanged. | ## Latest review follow-up Commit `828856850219518970e8e2f580b8f94b3c1fc748` fixes the deferred robotics disposal reported in the latest review. `RobotIntentNodeManager.DisposeAsync` now joins a shared controller-host drain and then explicitly awaits base node-manager disposal. A controller's bounded shutdown wait may defer its cleanup, but no longer lets the node-manager override finish early. Concurrent callers, including an asynchronous call after synchronous `Dispose`, cannot bypass the drain after the host list has been detached. Synchronous disposal remains nonblocking for deferred work. Deferred initialization failures are logged and retained, and base cleanup failures propagate to asynchronous callers. The existing controller-level bounded disposal contract is unchanged; the node-manager contract is documented in `docs/Robotics.md`. | Regression requirement | Evidence | | --- | --- | | Wait for deferred hosts; preserve synchronous compatibility and concurrent joins | `NodeManagerAsyncDisposalWaitsForDeferredHostsAsync`: four cases covering both entry-point orders and zero/positive host shutdown waits. All four failed before the fix. | | Wait through base cleanup and observe failures | `NodeManagerDisposalWaitsForBaseCleanupAndReportsItsFailureAsync`: four cases hold the actual base-resource semaphore, verify pending callers after the host finishes, then check success or the same propagated exception and exactly-once cleanup. | The focused `IntentHostingCoverageTests` and `IntentControllerHostTests.Dispose` selection passed **30/30 on net10.0 and 30/30 on net48**, with no failures or skips. The final net10 solution build passed with zero errors and only the four previously documented CA2249 warnings in unchanged fuzz-test code. No new warnings, analyzer suppressions, dependencies, or weakened assertions were added. The review thread received a commit-specific reply and was explicitly resolved. This verification is scoped to the robotics feedback; the earlier full Subscriptions-run limitation remains recorded in the merge section below. ## Documentation review follow-up (2026-09-14) Commit `f9a9686fcc5d3cfd19cbbc8c2d826e20f5c7e72f` addresses the six follow-up threads on documentation and implementation rationale. Each has an in-thread reply citing the commit and an explicit resolution. Multiline XML documentation now covers the declarations introduced or modified throughout the entire PR, including internal contracts, samples, tests, and public members of related private helpers. A syntax-aware inventory checked **2,031 declarations across all 256 PR C# paths with zero documentation gaps**. A supplementary check also covers **75 previously unchanged public members** of private helpers involved in the changed code. Token comparison across the conditional-compilation branches proves this follow-up changes **no executable C# or test assertions**. Project-file changes are rationale comments only. The implementation questions are documented at their source: | Question | Resolution | | --- | --- | | Certificate assembly friendship | Server Push configuration reuses the internal explicit-subject CSR helper with the existing key; no public crypto API was added. | | Core.Types assembly friendship | Server builders use internal transition callbacks/revision guards; Server.Tests checks the revision deterministically without creating a public compatibility contract. | | Always-single-reader notifications | One dequeuer preserves event order; optional concurrency fans out within one event and joins before the next event. | | Asynchronous disposal completion | `DisposeAsync` already joins the completion published after admitted operations and all cleanup attempts. The compatibility `Dispose` remains nonblocking to avoid deadlocking an admitted callback against its own drain. | The requested detailed review disposition follows. It distinguishes actual fixes from confirmed policies and incorrect subclaims rather than counting every comment as a new runtime change. ### Detailed disposition of review 5191795284 The [SDK review](https://github.com/OPCFoundation/UA-.NETStandard/pull/4477#pullrequestreview-5191795284) was submitted against `1c4a9df6c`, before the subsequent merge and feedback fixes. Its **15 inline threads** have each received an action-specific reply and an explicit resolution. A fresh GraphQL query verified those replies and resolution states; the fixes in `b8a59990f` remain ancestors of the current branch. | Review concern | Action and disposition | Concrete regression evidence | | --- | --- | --- | | Injected certificate-store providers bypassed | **Fixed.** Registration, configuration snapshots, and validation use the injected providers. Explicit custom types remain authoritative; the built-in Directory catch-all is deferred so native X509 paths and later custom providers are not captured. Failed store opens dispose the created store. | `InjectedProviderResolvesRegisteredAndConfiguredTrustStores` covers default-provider ordering and both registration paths; provider validation/open-failure cases and the unchanged `TrustedCertificateInjectedViaIndependentX509StoreIsObservedAsync` cover real use and independent-store visibility. | | Unguarded timer-thread leadership callbacks | **Fixed.** Each subscriber's exception is logged independently and cannot suppress subsequent subscribers or fail lease operations. `IsLeader` reads the deadline without invoking application callbacks. | `ThrowingLeadershipSubscriberDoesNotFailAcquireOrDisposeAsync`, `ExpiryNotifiesOtherSubscribersWhenOneThrowsAsync`, and `IsLeaderDoesNotDispatchExpiryNotificationsAsync` were all demonstrated failing before the fix. | | Legacy pending-key provider accepts an unfinishable regenerated CSR | **Fixed by explicit capability validation.** A provider implementing only `IPendingCertificateKeyStore` receives `BadNotSupported` before key generation or persistence when regeneration is requested. Existing-key CSR/update remains supported. Regeneration requires matching-only consumption; no unsafe destructive probing was introduced. | `LegacyPendingProviderRejectsRegenerationBeforeCreatingOrSavingAKeyAsync` checks no key or store mutation; `LegacyPendingProviderCompletesExistingKeySigningAndUpdateAsync` proves the supported flow completes. | | Shutdown waits behind user deactivation | **Fixed.** Accepted deactivations drain before the master's lifecycle gate is taken. Configuration retains its binding until the shared drain completes, including concurrent shutdown callers and direct master disposal. | Four cases of `ShutdownDrainsDeactivationOutsideTheSessionClosingGateAsync` exercise real configuration/binding/ServerInternalData/master reentry, retained address space, exact session/subscription closure, and no production cancellation shortcut. | | Subclass cleanup runs under the operation-lifetime lock | **Fixed.** Cleanup starts outside that lock. A pre-published completion joins all disposers, and only the active asynchronous teardown context may use guarded helpers. Captured contexts lose that privilege when the callback exits. | `SynchronousOwnedCleanupLetsSamplingWorkerReachClosedAdmissionAsync`, `DisposeCoreCanUseGuardedMembersWithoutReopeningCallerAdmissionAsync`, captured-context rejection, and exact cleanup-failure/ownership assertions cover both synchronous and suspended cleanup. | | Discovery requestId-zero chunk read after pool return | **Fixed by the earlier merge.** Discovery validation precedes `SaveIntermediateChunk`; no read occurs after that path can return the buffer. The ordering is retained. | `DiscoveryChannelReadsTheFirstChunkBeforeItIsReturnedAsync` uses a poisoning pool and the requestId-zero path. | | T1.8 OPN cleanup misses post-decryption throws | **Fixed by the earlier merge and pinned with new regressions.** Both sequence rejection and certificate mismatch release the decrypted body and input ownership. | `OpenSecureChannelRejectionAfterDecryptionReturnsEveryRentalAsync` checks the exact audit status and pool conservation. Omitting either corresponding release in an isolated snapshot made its case fail; no mutation remains. | | T1.2 mount-root protection depends on the provider | **Fixed at the host boundary.** Separator-only aliases resolve to the protected root. Traversal, empty/whitespace segments, backslashes, and drive/stream qualifiers are rejected before arbitrary providers are invoked. Valid nested paths and root destinations remain supported. | A deliberately permissive provider is used by `NonCanonicalObjectPathsCannotDeleteMoveOrCopyAsync` and invalid-target cases; exact zero-call checks are paired with legitimate nested-path/root-destination controls. | | Inline trusted/issuer entries now face CRL checks | **Intentional strict behavior, documented and tested.** A configured backing store with strict unknown-status rejection requires current revocation information. Explicit-only lists without a store retain their no-store behavior. Actual revocation cannot be suppressed; a matching store entry's policy takes precedence. | `InlineIssuerWithStoreHonorsStrictUnknownRevocationPolicyAsync`, `InlineIssuerRevocationIsNeverSuppressedAsync`, store-precedence cases, and explicit-only controls preserve the missing-information versus actual-revocation distinction in Part 4 6.1.3. | | RSA minimum applied to unspecified ECC identifiers | **Fixed at the application-certificate check.** The RSA minimum is compared only with an actual RSA public key. An unspecified ECC identity is not replaced simply because its key size is below an RSA threshold. | `SilentCertificateCheckPreservesUnspecifiedEccIdentityAsync` checks DER, thumbprint, store contents, and private-key retention; explicit RSA and HTTPS minimum controls remain. | | Failed directory refresh wedges corrective mutations | **Fixed.** Recoverable automatic pre-refresh failures are logged and leave reconciliation pending, but do not block corrective provider mutations. Explicit refresh errors, capacity admission, caller cancellation, and actual provider denials remain enforced. | `CorrectiveDeleteRecoversAfterPersistentOverLimitRefreshFailureAsync` converges an externally over-limit directory back to its exact allowed contents. Capacity, cancellation/token, and six refresh-error controls check negative paths. | | Timed-transition rejection logs at 10 Hz | **Fixed.** Repeated `(state, revision, status)` warnings are deduplicated without changing the retry cadence. A different status or state entry permits another warning. | `RepeatedTimedRejectionLogsOnceAndRecoversAsync` checks one warning over eleven rejections and success on the next attempt. Status-change and same/different-state reentry controls pin reset behavior. | | Bad unsubscribe strands retired-generation bookkeeping | **Fixed.** Retired all-event bookkeeping completes even when unsubscribe reports Bad; the first Bad is still returned to the caller. | Good and BadCommunicationError cases of `RetiredAllEventsDeletionCompletesBookkeepingAndReleasesGenerationAsync` check event/snapshot removal, generation release, and exactly one unsubscribe without finalization replay. | | CSR subject parsing and fallback | **Fixed for existing and regenerated keys.** Omitted subjects fall back to the actual stored certificate. Regeneration resolves the public certificate's subject before creating a key. Malformed or unresolvable subjects return `BadInvalidArgument` before pending-key mutation. | Store-only fallback, real-generator null/empty cases, and missing-subject/malformed-DN controls verify the signed CSR, matching fresh pending key, unchanged active identity, and no destructive side effects. | | Committed RemoveUser change reported as failed | **Fixed for RemoveUser and ModifyUser.** Accepted cleanup is still awaited and close failures remain logged, but cannot replace the committed mutation result. Precommit failures retain their original result and side effects. | `CommittedUserChangeKeepsResultAndPublishedUsersWhenCloseFailsAsync` checks published Users, Good result, logging, and continued closes. `FailedUserChangeKeepsPublishedUsersAndDoesNotCloseSessionsAsync` checks BadNotFound with no publication or close. | | Alleged pooled decoded-request aliasing | **No runtime redesign needed; reviewer rebuttal confirmed.** The decoder copies the relevant input data. Misleading ownership comments were corrected rather than introducing a new dispatch model. | `DecodedRequestRetainsValuesAfterInputBuffersAreReturnedAsync` checks the exact decoded NodeId and ByteString after returned arrays are poisoned. | The current documentation-only follow-up rechecked **104 Core, 473 Server, and 4 Configuration cases** covering these concerns: **581 passed, zero failed or skipped**. The final net10 solution build also passed with **zero warnings and errors**. The complete earlier Core/Server/Configuration and net48 results remain listed separately rather than being presented as rerun current-head acceptance. ## Earlier review follow-up Commit `b8a59990fe2e8e967a12ffd5d9158beed7831143` addresses all 19 review threads. Each thread has an in-thread reply naming this commit and was explicitly resolved after verification; the final paginated query found 19 resolved and zero unresolved threads. The follow-up fixes injected certificate-store selection (including default provider ordering and native X509 paths), actual-RSA minimum enforcement, and CSR capability/subject validation before key mutation. Node-manager teardown uses simpler scoped ownership and drains accepted user revocation outside the master gate; committed user results and retired-event cleanup remain correct when cleanup reports failure. File mutations can recover after failed refresh without bypassing capacity or cancellation, provider paths protect mount roots, and repeated timed rejections no longer flood logs. Leadership subscriber failures are isolated without callbacks from `IsLeader`. Regressions also pin the already-merged OPN/discovery buffer fixes and prove decoded request values do not borrow returned input buffers. The strict inline issuer revocation policy is documented and tested, not weakened. Existing tests were changed only for the reviewed behavior: the incorrect committed-user result was corrected, and inline-issuer/root-alias controls were expanded. ### Review-fix validation | Suite | Scope | Framework | Passed | Existing TRX NotExecuted | | --- | --- | --- | ---: | ---: | | Core | Complete project | net10.0 | 4,970 | 88 | | Server | Complete project | net10.0 | 6,414 | 69 | | Configuration | Complete project | net10.0 | 231 | 0 | | Core | Certificate, lease and buffer compatibility slice | net48 | 153 | 0 | | Server | Reviewed lifecycle, user, file, timer and CSR compatibility slice | net48 | 692 | 0 | | Configuration | Complete project | net48 | 231 | 0 | | CSR provider regressions | Final portable-mock follow-up | net10.0 | 11 | 0 | All runs exited successfully with normal concurrency and leak detection. The Server console reports 5 skips; its TRX additionally records existing assumption/explicit cases, hence the 69-record count above. The Framework build caught two modern-only `ValueTask.CompletedTask` usages in new mock setup; only those expressions changed to the equivalent portable `default(ValueTask)`. The complete modern suites precede that test-only correction; all production files remained byte-identical, and the corrected fixture passed on both targets. The final net10 solution build succeeded with 0 errors and the same 4 CA2249 warnings in unchanged fuzz-test code. Diff checks and changed-line whitespace checks are clean; whole-file formatting still identifies 10 unchanged baseline lines, which were not reformatted outside the feedback scope. The committed 35-file tree was hash-verified against the validation inputs. This is the review-feedback verification scope, not a rerun or sign-off of the historical complete platform/native/soak acceptance campaign below. ## Latest upstream merge Merge `9d9bf679f317164c5b2b90924cd3cdc27a86d451` integrates `master` through `162fc5ebb` (#4486), including the AddNodes child-name index, shared certificate-error reporting, CTT sample-data corrections, and the accompanying regressions and documentation. The CreateSession conflict preserves this branch's certificate ownership, required certificate, application-URI, and channel-certificate checks. The validation result now retains nested errors through `ThrowIfInvalid`, allowing the upstream shared reporting helper to mask trust, policy, and revocation failures consistently without losing audit detail. The new indexed AddNodes regression initially attempted to reuse a deterministic NodeId after renaming its existing node. Renaming frees the BrowseName, not that NodeId. `IndexedBrowseNameReusePreservesExistingNodeIdentityAsync` now proves both contracts: the derived-ID collision leaves the original node intact, then a fresh explicitly requested ID can reuse the old name while the new name remains protected against duplicates. No production identity rule was relaxed. The incoming seeded random-index test uses the existing `UnsecureRandom` wrapper with the same seed and sequence; no new analyzer suppression was added. | Local merge validation | Framework | Passed | Existing exclusions | | --- | --- | ---: | ---: | | Core, complete project | net10.0 | 5,081 | 88 | | NodeState / BaseInstanceState | net10.0 | 579 | 0 | | Server node management, certificate and session-security selection | net10.0 | 112 | 0 | | InformationModel node-management services | net10.0 | 29 | 1 | | NodeState / BaseInstanceState | net48 | 578 | 0 | | Core certificate validation, error reporting and TCP buffers | net48 | 111 | 0 | | Server node management, certificate and session-security selection | net48 | 112 | 0 | All selected runs exited successfully at normal concurrency: **6,602 passing test executions**. Modified test assemblies were explicitly rebuilt, and the combined name/identity regression was verified by name in both net10 and net48 results. No skips or weakened assertions were introduced. The final `dotnet build UA.slnx -c Release --no-restore -m:1` with `CustomTestTarget=net10.0` succeeded with **zero warnings and zero errors**. The resolved source and adjusted tests pass whitespace verification, and their source hashes match the validation inputs. This is merge-specific verification. Earlier review results and the previously reported incomplete full Subscriptions runs below remain scoped to their own commits; they were not reopened or relabeled as passing by this operation. ## Previous upstream merge (2026-09-15 morning) Merge `c4694e20ab089c555af233642042133ff3a03ac8` integrates `master` through `484d6c887`, retaining #4481's shared Like parser/GDS query fixes, #4488's RequestHandle recovery in ServiceFaults, and #4487's expired-session and durable-transfer corrections. Three conflict resolutions preserve both sides: - Alias searches now use the shared `LikePattern` implementation, including upstream malformed-list validation. A timeout overload retains this branch's 100 ms per-alias deadline and `BadTimeout` reporting without keeping a second wildcard translator. The shared matcher's original unlimited overload remains unchanged for existing GDS/ContentFilter callers. New regressions cover bounded prefix/list scans, reuse after timeout, and the alias service result. - Expired activation closes outside the session-manager gate using upstream's once-only timeout claim, audit, and diagnostic accounting. This branch's original-channel and security-policy checks remain in place. - TCP buffer tests retain this branch's ownership, poisoning-pool and post-decryption checks alongside the upstream RequestHandle/fault overload tests; neither set was discarded. The upstream HTTPS binary behavior is intentionally retained: a decoder `ServiceResultException` produces HTTP 200 with an OPC UA ServiceFault carrying the decoder status, response timestamp, and recoverable RequestHandle, rather than the previous HTTP 500 text response. Other exceptions retain their existing HTTP error handling. | Local validation | Framework | Passed | TRX NotExecuted | | --- | --- | ---: | ---: | | Core, complete project | net10.0 | 5,054 | 88 | | Sessions, complete project | net10.0 | 780 | 610 | | Server aliases, expiry/security and subscriptions | net10.0 | 288 | 0 | | GDS database, paging and live directory calls | net10.0 | 263 | 0 | | Shared Like patterns and bounded evaluation | net10.0 | 109 | 0 | | Subscriptions transfer/durability/service selection | net10.0 | 23 | 1 | | HTTPS WebApi, complete project | net10.0 | 346 | 0 | | InformationModel aliases | net10.0 | 54 | 5 | | Shared Like patterns and bounded evaluation | net48 | 109 | 0 | | Core fault handles, TCP/HTTPS and filters | net48 | 268 | 0 | | Server aliases, expiry/security and subscriptions | net48 | 288 | 0 | These selected runs exited successfully with normal test concurrency: **7,582 passing test executions**. Sessions' console reports 379 skips; the TRX also contains conditional/assumption exclusions, so the table uses the actual 610 NotExecuted records. No skips or weakened assertions were added. The new `EvaluationTimeoutBoundsWorkAndPreservesPatternReuse` and `SharedPatternTimeoutRemainsAnAliasServiceError` tests passed on both targets. **Validation limitation:** two unfiltered Subscriptions runs did not produce a final result and were stopped, including a retry with a hang guard. They are not counted as passing. The isolated failover fixture passed 2/2, and the transfer/durability/service selection above passed. The stall's cause was not established; no unrelated code or test was changed to conceal it. The final `dotnet build UA.slnx -c Release --no-restore -m:1` with `CustomTestTarget=net10.0` succeeded with **0 warnings and 0 errors**. Whitespace verification passed for all hand-reconciled files, and staged source hashes match the validation inputs. These are merge-specific checks, not a new full platform/native/soak acceptance sign-off. Earlier review and acceptance evidence below remains dated to its own commit. ## Previous upstream merge (2026-09-14) Merge `c3e09553e4d1509502d4ba751960d58491bea2cf` integrates `master` through `6313e38a0` (#4478), including the CTT-driven Part 13 aggregate corrections and FindAlias argument validation. The upstream tests and CTT documentation are retained. The single conflict in `AliasNameWildcardMatcher` is reconciled through the branch's shared regex compiler: upstream malformed-pattern rejection and `IsValidPattern` coexist with OPC UA character escaping, whole-string/newline matching, the 100 ms timeout, and service-level error reporting. The upstream ReferenceTypeFilter checks and chronological aggregate behavior are preserved. Local merge verification: | Suite / selection | Framework | Passed | Existing skips | | --- | --- | ---: | ---: | | Server aliases, aggregates and historian | net10.0 | 1,011 | 0 | | Server aliases, aggregates and historian | net48 | 1,011 | 0 | | InformationModel aliases | net10.0 | 54 | 5 | | Features aliases | net10.0 | 11 | 0 | | Client aliases | net10.0 | 28 | 0 | | History aggregate/historical-access consumers | net10.0 | 265 | 0 | All selected tests passed at normal concurrency. The final net10 solution build passed with 0 errors and 0 warnings. The resolved file's whitespace check and the staged diff check passed. These are merge-specific checks, not a new full platform/native/soak acceptance claim. ## Previous upstream merges Integrates `master` through `8e874d073`, including the Client, Types, Core and source-generator audit fixes in #4471, #4473, #4474 and #4475, plus the event queue-size revision from #4480. The merge commits are `150ea0439` (26 conflict resolutions) and `a0e17c07c` (the base update that arrived during validation). The overlapping fixes are reconciled rather than replacing either branch: - **Rejected-certificate retention follows this branch, as explicitly requested:** zero means unlimited history; negative values disable new storage while preserving existing backend trimming behavior (Directory clears prior history; shared key/value leaves it intact). The processor and manager policy are not redefined, and documentation/tests describe the distinction. - Preserve owned certificate snapshots and borrowed validation-core lifetimes alongside upstream cache invalidation, store-first CRL checks and `BadCertificateRevocationUnknown` for unsupported revocation lookup. - Combine exactly-once chunk ownership, receive-loop cancellation, stale-epoch rejection and detach-before-adoption with upstream signature-before-padding verification and secret cleanup. - Preserve pooled `HttpClient` per-request configuration together with bounded response reads and deadlines that remain active through body consumption. Regression cases exercise already-used clients and both timeout precedence cases. - Retain the binary-compatible channel-key constructor and normalize omitted profiles; preserve exact profile filtering and upstream binary-first HTTPS/WSS endpoint ordering. - Preserve monotonic lease expiry and stale-confirmation fencing while adding ordered, non-reentrant leadership notifications outside the state lock. ## Validation Local validation of the conflict-resolution merge: | Suite | Framework | Passed | Existing skips | | --- | --- | ---: | ---: | | Core | net10.0 | 4,940 | 88 | | Client | net10.0 | 2,278 | 0 | | Server | net10.0 | 6,088 | 5 | | Types | net10.0 | 10,394 | 1 | | SourceGeneration.Core | net10.0 | 3,940 | 8 | | SourceGeneration | net10.0 | 185 | 0 | | Core | net48 | 4,855 | 104 | After the subsequent four-file #4480 update, the related Server slice passed 811 tests with 5 existing skips, the real durable queue-size cases passed 6/6, and EventManager's 21 cases passed again after analyzer-only expected-array cleanup. The final `dotnet build UA.slnx -c Release` with `CustomTestTarget=net10.0` succeeded: 0 errors and 4 pre-existing CA2249 warnings in unchanged fuzz-test code. Whitespace verification passed for the resolved files and the follow-up. Merge-specific proof includes `RequestTimeoutRemainsActiveWhileReadingTheOpenApiBodyAsync` on already-used shared clients with either the request or HttpClient timeout shorter, the existing ownership/expiry regressions from both branches, and both branches' retention tests adjusted only to the explicitly chosen zero/negative contract. Earlier acceptance at `1c4a9df6c` passed hosted Linux/macOS CI, CodeQL, image builds and the canonical coverage gate (87.15% line, 77.27% branch, 92.14% patch). Both Framework project matrices, all three Windows native suites (183 cases), and the unfiltered net10 stress project (230 cases, including full 60-minute chaos and 30-minute memory soaks) also passed. Those are **pre-merge results**, not a claim that the full framework/platform/native/soak matrix has been rerun on this merge. The previous acceptance workflow remains separate from this review-feedback operation. Security checks, meaningful assertions, leak detection, default native concurrency, full soak durations and coverage thresholds remain intact. The user-set ready-for-review state is unchanged. No architectural or complete merged-head acceptance sign-off is claimed. ## Related Issues - Fixes #4476 ## 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. |
||
|
|
2415503ab9 |
Close CTT skips in the reference server sample address space, diagnostics EnabledFlag and project settings (#4485)
Closes #4479 The CTT skipped a number of reference-server test cases because the sample address space, the diagnostics EnabledFlag or the CTT project settings did not provide what the scripts need. This PR fixes the sample data and the server behaviour behind those skips, updates `samples/UAReferenceServer.ctt.xml`, and records the CTT script defects that the newly running cases expose. ## Changes ### Sample address space (`ReferenceServer.NodeSet2.xml`, `ReferenceNodeManager`) | # | Skip | Fix | | --- | --- | --- | | 1 | Base Info Engineering Units `004.js`: ArrayItemType `EngineeringUnits` have an empty NamespaceUri | UNECE `mV` (12890) EUInformation, and the axis definitions use `s` (5457219) instead of UnitId 0. | | 2 | Data Access PercentDeadband `006/016/017`: `DataAccess_AnalogType_Float` EURange 0..0 | EURange 0..100 like the other analog items. | | 3 | Attribute Read `033.js` (19 nodes): second dimension of 3 | All `Scalar_Static_Arrays2D_*` are 5x5 (generated values; the Variant matrix holds scalar Int32 elements). The code-seeded Integer/Number/UInteger matrices are 5x5 too. | | 4 | View Minimum Continuation Point 01 `012.js`, View Basic 2 `018.js`: Method/View nodes need two references of their own NodeClass | `Methods_Void` HasCause/HasEffect to two methods, `Views_Operations` HasCause/HasEffect to `Views_Engineering`. | | 5 | Base Info Server Capabilities 2 `002.js`: no node without MinimumSamplingInterval | `DataAccess_DataItem_String` reports `Bad_AttributeIdInvalid` for the optional attribute (Part 3 §5.6.2). A DA item is used because Address Space WriteMask writes every attribute of the static scalars back. | | 6 | Historical Access Read Raw `Err-025.js`: no non-historizing Static Scalar | New `Scalar_Static_NonHistorizing_Boolean` (HistoryRead access, Historizing = false). See the historian change below. | | 10 | Address Space Atomicity `001.js`: no NonatomicRead node found | New `AccessRights_AccessAll_NonatomicReadWrite`. The script examines only the first 10000 variables sorted by NodeId string; the 5000 mass variables sort before `Scalar_Static_NonatomicReadWrite`. | ### Server library - **Diagnostics EnabledFlag (item 7, Base Info Diagnostics `018-1/2/3`).** `Server.ServerDiagnostics.EnabledFlag` is writable for SecurityAdmin or ConfigureAdmin over SignAndEncrypt (UserAccessLevel hook, asynchronous write handler); other users get `Bad_UserAccessDenied`. Transitions are serialized, including the node removal. Disabling returns `Bad_NotReadable` for ServerDiagnosticsSummary, its children and the diagnostics arrays, and removes the session and subscription diagnostics nodes (Part 5 §6.3.3). The server keeps the registrations: enabling restores the nodes of the live sessions and subscriptions with the same NodeIds (sessions created while disabled included) and resets the arrays to Good. Counters stay cumulative (required by `018-1.js`). Subscriptions created while diagnostics are disabled still get no diagnostics node. - `DiagnosticsNodeManager.IsDiagnosticsStructureNode` had an inverted NodeId check, so monitored diagnostics nodes never started the scan timer; `OnMonitoringModeChangedAsync` counted every node and replaced a running timer. Both fixed. - **Non-historized nodes (item 6).** `HistorianDispatcher` raw/modified, processed and at-time reads ask the resolved provider `IsHistorizingAsync` and return `Bad_HistoryOperationUnsupported` for a node it does not historize, instead of the empty result the in-memory historian gave for any node. - **Browse order (item 8).** `ReferenceDictionary.Find` returned references of a type from hash-based target maps, so a Browse filtered by ReferenceType returned references in a different order than an unfiltered Browse. It now returns them in insertion order (no extra allocation; a single match keeps the fast path). View Basic 2 `015.js` compares both lists by index. The characterization test in `NodeStateReferenceStorageTests` is updated accordingly. ### CTT project settings (`samples/UAReferenceServer.ctt.xml`) | Setting | Old | New | | --- | --- | --- | | `/Server Test/NodeIds/Static/All Profiles/Scalar/Bool` | `ns=2;s=Scalar_Static_Boolean` | `ns=2;s=Scalar_Static_NonHistorizing_Boolean` | | `/Server Test/NodeIds/References/Has References of a ReferenceType and SubType` | `ns=2;s=References_HasReferenceTypeAndSubType` | `i=2253` | | `/Server Test/NodeIds/NodeClasses/Object` | `ns=2;s=Scalar` | `i=2253` | `References_HasReferenceTypeAndSubType` loses its HasComponent/HasOrderedComponent references in the source generator; that is tracked in #4484 and not changed here. The Server object has HasComponent and HasAddIn references. ### Decisions - **Item 11, Attribute Write StatusCode & Timestamp `003/006/008` (writing ServerTimestamp): unchanged.** The stack rejects writes that contain a ServerTimestamp (`NodeState`/`BaseVariableState`), which Part 4 §5.11.4.2 allows (`Bad_WriteNotSupported`); the CTT reports it as "Not Supported", not as an error. Side finding, not changed: the StatusWrite/TimestampWrite AccessLevel bits are not enforced. - **Item 6:** Part 4 §5.11.3.4 (Table 52) specifies `Bad_HistoryOperationUnsupported`; `Err-025.js` accepts only `Bad_NotSupported`, so the case now runs and reports that as an error. Recorded as a CTT defect in `plans/ctt-issues.md`. - **Item 12:** removed the stale Historical Access `Err-012.js` entry (fixed by #4026). `plans/ctt-issues.md` adds C32 (`Err-025.js` status code), C33 (Atomicity `001.js`: 10000 cap, operator precedence, substring matching of `UaNodesToIgnore`) and C34 (View Basic 2 `015.js` index comparison), and replaces the Err-025/Err-012 configuration notes with the new settings and the EnabledFlag access requirement. Monitor Basic `039.js` is already C17. The run procedure in `plans/ctt-testing.md` is unchanged. ## Verification CTT 1.05.06, scripts 1.05.513, `ConsoleReferenceServer --ctt -a`, fresh server per conformance unit/group. "master" = |
||
|
|
93559d06d3 |
Fix sample A&C refresh and alarm timing found by slow CTT Alarms and Conditions runs (#4482)
## Why the CTT *Alarms and Conditions* group was so slow
I measured every A&C CU with a fresh `ConsoleReferenceServer --ctt` (CTT
1.05.06, scripts 1.05.513), read the per-test `ResultNode` timestamps,
and added `addLog` timing/counter instrumentation to a copy of the CTT
project.
| Cause | Effect |
| --- | --- |
| **CertificateExpiration** opens a modal dialog in `initialize.js`
("adjust the clock on the server"), and it also appears in `--hidden`
runs | The CTT never exits. This is why the whole group "did not finish
in 40 minutes". |
| **Limit/Level CUs:** `initialize.js` idles the CU session for one
Alarm Cycle Time (60 s) during the initial capture.
`RequestedSessionTimeout` is also 60 s. | The server correctly closes
the session. `LimitHelper` then gets `BadSessionIdInvalid` for its 5
filter subscriptions, and Test_003–006 each run 180 s, about 14 min per
CU. |
| Alarm `Test_002` never gives the AcknowledgeableCondition type a
result | Always runs 3 × cycle (180 s). |
| Enable `Test_003` needs every alarm type to go active within cycle/10
| Timed out in 6 of 7 runs, because boolean and analog alarms changed
state on different ticks. |
| Enable `Err_004` answers each event with Disable/Disable/Enable |
Feedback burst, after which the CTT alarm thread sometimes returns no
events. Details below. |
| Comment `Test_001`–`004` compare against
`CallHelper.ServerTimeOfCall`, which the CTT never sets (`0001-01-01`) |
Every comment event counts as unrelated, and the tests skip after 3
retries. The server's events carry the expected comment. |
Measured CU by CU, the group took **about 87 min plus the hang**. With
the fixes below, running the whole group in one CTT process without
CertificateExpiration takes **18:45** at Alarm Cycle Time 30. The per-CU
table is in `plans/ctt-testing.md` §6.
## Server changes
- **Refresh one subscription per request, not one per Call**
(`AlarmNodeManager.CallAsync`). The sample answered every
ConditionRefresh/ConditionRefresh2 after the first one in a Call with
`Bad_RefreshInProgress`, even for other subscriptions, and never
forwarded them. So CTT Refresh `Err_004` (10 subscriptions per Call)
only ever refreshed one of them. The check is now keyed by
SubscriptionId and MonitoredItemId.
- Regression tests:
`AlarmsAndConditionsRefreshTests.ConditionRefresh[2]OfDifferentSubscriptionsInOneCallSucceedsAsync`.
Both fail without the fix.
- **Boolean alarm source now uses the analog source's steps and limits**
(`AlarmController`, `ConditionTypeHolder.GetSeverity`). OffNormal,
Discrete and SystemOffNormal alarms now change state on the same tick as
the limit alarms (5 per second, 70/90/30/10), and the shortest state
still lasts 4 s.
- With this, Enable `Test_003` passed in 4 s instead of a timeout.
Confirm `Test_001` for the boolean types also stopped failing in both
group runs.
- Test:
`AlarmControllerTests.BooleanAndAnalogSourcesChangeStateOnTheSameStep`
(fails without the change).
- **Alarm controllers step on shared interval boundaries**
(`AlarmController.SetNextInterval`). Scheduling relative to the last
step let the two controllers drift up to one 100 ms simulation tick
apart, so the boolean events sometimes landed in a later publish and
Enable `Test_003` still timed out in 3 of 5 runs. With shared boundaries
it passed in 6 of 6 runs.
- Test:
`AlarmControllerTests.ControllersScheduleTheirStepsOnSharedIntervalBoundaries`.
I did not make the simulation faster. Acknowledge, Confirm and Comment
tests call methods with the EventId of the event they just received, and
a shorter state would make them race the next transition.
## Not reproduced / not a server defect
- **Refresh `Err_004` `BadSubscriptionIdInvalid` cascade.** In the
original run the alarm-thread session's subscriptions (15.5 s lifetime)
were gone after the script's 30 s wait. Nine further CTT runs passed all
28 Refresh/Refresh2 cases. An in-process replay with a sequential
publisher kept every subscription; that replay is what exposed the
refresh defect above.
- **Enable event stall (C43) is inside the CTT.** A server build that
logs every returned notification message showed that during a stall the
server sent 435 and 420 events to the two CTT event items in 173
messages, and the CTT acknowledged all of them, while its alarm-thread
buffers returned nothing. It only happens after `Err_004`'s
Disable/Enable feedback burst, in 8 of 28 runs.
## Docs
- `plans/ctt-testing.md`: new §6 *Alarms and Conditions*, covering the
recommended run, where the time goes, how to instrument a run and the
expected result. Also a note that a hidden CTT run can block on dialogs,
so always use a timeout.
- `plans/ctt-issues.md`: the A&C "open server findings" are replaced
with these results, and there are new CTT script defects C39–C44 plus
configuration notes (Shelving chattering alarms, Alarm Cycle Time vs
session timeout).
## Tests
- `Opc.Ua.History.Tests` A&C + AlarmClient: 155 passed.
- `Opc.Ua.Server.Tests` `AlarmControllerTests` /
`QuickstartsServerUtilitiesTests`: 4 passed.
- CTT whole-group runs (27 CUs, 128 cases): 18:45 at cycle 30 and 29:23
at cycle 60 on this branch; 23:43 and 24:21 with only the refresh fix.
The spread comes from the Enable stall (C43).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
|
||
|
|
162fc5ebb3 |
Fix AddNodes scaling and OpenSecureChannel revocation errors found by CTT Monitored Item, Node Management and security runs (#4486)
# Description Findings from running the CTT conformance groups **Monitored Item Services**, **Node Management Services**, **Security General** and **Security User Token** against `ConsoleReferenceServer --ctt` (CTT 1.05.06, scripts 1.05.513). The security groups ran **without `-a`**, against an isolated server PKI built from the CTT's `copyToServer` folders, so the negative certificate tests are meaningful. ## Server fixes ### AddNodes below one parent was quadratic (Node Management Delete Node `Err-002.js`) The CTT adds 15,000 variables below `ns=2;s=CTT` in batches of 5,000 and warned that AddNodes responses took 400–800 ms. For every added node the server searched all children of the parent twice: once in the BrowseName duplicate check in `AsyncCustomNodeManager.AddNodeCoreAsync`, which also copied the children list, and once in the NodeVersion lookup of `FilterChangesByNodeVersion`. - `NodeState.FindChild` now searches children lists with ≥ 64 entries through a BrowseName index kept in a `ConditionalWeakTable`, so nodes with few children get no extra field. - It keeps first-match semantics and is maintained by `AddChild`/`RemoveChild`. - It is dropped on replacement and invalidated when a child is renamed or moves to another parent. - Lists whose children point at another parent (clones) are still searched linearly. - The duplicate check uses `FindChild` instead of copying the list. | 5,000-node AddNodes batch (in-process, Debug) | 1st | 2nd | 3rd | | --- | --- | --- | --- | | before | 769 ms | 2,083 ms | 4,011 ms | | after | 141 ms | 166 ms | 172 ms | In the CTT, AddNodes of 5,000 items now answers in 51–72 ms and the warning is gone. ### OpenSecureChannel leaked revocation errors (Security Certificate Validation `002.js`) Part 4 §6.1.3, Table 106: - For the revocation check (`Bad_CertificateRevoked`, `Bad_CertificateIssuerRevoked`), `Bad_SecurityChecksFailed` **shall** be reported to the client. - For a missing revocation list (`Bad_CertificateRevocationUnknown`, `Bad_CertificateIssuerRevocationUnknown`), it **should** be. `TcpServerChannel` masked only `Bad_CertificateRevoked`, and `StandardServer.OnApplicationCertificateError` (CreateSession) had its own list that missed `Bad_CertificatePolicyCheckFailed`. Both now use one helper, `CertificateErrorReporting` in Opc.Ua.Core, which also inspects the nested errors of the certificate validator (both paths now throw the validation result with `ThrowIfInvalid`). Only validity period, host name, URI and usage errors keep their own code, and audit events still carry the specific result. The CTT warning is gone. ### Sample data - **Monitor Value Change V2 `020.js`** skipped because `Scalar_Static_Arrays_ByteString` had a 1-byte and a 3-byte element. The script checks every element, and the simulation only padded elements 0–2. The seed value now has no element shorter than four bytes, and the simulation pads every element. - **Monitor Value Change V2 `042.js`** got 18 of 19 notifications. The missing item is the Float matrix: its element [1,1] was `-1.03179664E+33`, where adding 1 is below Float precision, so the value really did not change. The server did not drop a notification. The element is now `-1031.79664`. ## CTT results after the fixes | Group | Result | | --- | --- | | Monitored Item Services (208) | Monitor Value Change V2 44/44. Remaining: Monitor Basic `039.js` (C17, missing include), `038.js` warning (C32), and 1–2 sporadic timestamp-tolerance warnings that move between test cases from run to run. | | Node Management Services (15) | Only Add Node `Err-008.js` (Mantis 11266). | | Security General (314) | Certificate Validation: 23 pass, 24 not implemented, 3 not-applicable skips. None CreateSession/ActivateSession: all pass. The other 50 CUs are *Not Implemented* in scripts 1.05.513. | | Security User Token (66) | Anonymous `002.js` (C35) and User Name Password 2 `015.js` (C36) fail because of CTT defects. X509: 18/18 automated cases pass. | ## CTT defects documented (`plans/ctt-issues.md`) - **C32** `038.js` compares a RevisedSamplingInterval of 0 with a project setting instead of the node's MinimumSamplingInterval or the server's MinSupportedSampleRate. - **C33** `020.js` requires every ByteString element to be ≥ 4 bytes, but its message says elements 0–2. - **C34** `RequestedNewNodeId()` always sends `ns=1`, regardless of `RequestedNodeId_Namespace`, so client NodeIds can't be enabled. - **C35** Security User Anonymous picks the `opc.wss` endpoint, which the CTT cannot open (`BadNotSupported`). - **C36** `UaEndpointDescription.FindTokenType` rewrites the cached endpoints' token policies, so `015.js` reports PolicyIds as reused. The server's GetEndpoints output was verified compliant (PolicyIds unique per configuration, Mantis 11258). - Evidence added to **C9** (042.js) and **issue 17** (015.js). `plans/ctt-testing.md` gains how to run the Monitored Item/Node Management groups and a recommended setup for the security groups: isolated PKI from `copyToServer`, no `-a`, PKI restore per part, and waiting for `Server started`. Other findings recorded there: - the Create/DeleteMonitoredItems timing warnings are jitter; - `LoginNameAccessDenied` is not a real user; - `samples\UAReferenceServer.ctt.xml` needs no change (enabling `RequestedNodeId` breaks six Add Node cases because of C34). **Note:** `plans/ctt-testing.md` and the refreshed `plans/ctt-issues.md` are introduced by #4482. The plans files here are based on #4482's versions, and new entries start at C32 because #4481 uses C19–C31. Please merge after #4482 (or rebase the plans files onto it). ## Tests - `NodeStateChildNameIndexTests` (Opc.Ua.Types.Tests): index semantics (duplicates, remove, rename, replace, re-parent, clone, shrink/grow, 2,000 random operations against a linear search). Also a scan guard: 5,000 misses against 50,000 children take 97 ms with the index and 7,083 ms with it disabled. - `AsyncCustomNodeManagerNodeManagementTests.AddNodeAsync_DuplicateBrowseNameUnderParentWithManyChildren…` - `TcpServerChannelCertificateErrorTests` (Opc.Ua.Core.Tests): masked vs reported certificate errors. - Opc.Ua.Server.Tests 5,553 passed, Opc.Ua.Core.Security.Tests 556 passed, Opc.Ua.Core.Tests (Security + Stack) 3,561 passed, InformationModel NodeManagement 26 passed (net10.0). - Opc.Ua.Types.Tests 10,404 passed and 1 failed: `WotSpecFixtureManifestTests.EveryEmbeddedExampleMatchesItsRecordedHash`. That test hashes WoT assets which are `eol=lf` in the index but CRLF in this worktree's checkout; no WoT file is touched by this PR. ## Checklist - [x] I have signed the CLA and read the CONTRIBUTING doc. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added all necessary documentation. - [ ] I ran all tests locally using UA.slnx against .NET Framework and .NET 10 (net10.0 suites listed above only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> |
||
|
|
ea93c86da2 |
Fix the Opc.Ua.Client defects reported by the client stack audit (#4471)
# Description
Fixes the 78 `Opc.Ua.Client` findings of a read-only bug audit of
`Opc.Ua.Client` / `Opc.Ua.Types` / `Opc.Ua.SourceGeneration` taken at
|
||
|
|
a05a360550 |
Adopt the fluent builder's node-creation surface in the remaining hand-rolled node managers (#4460)
Closes #4429. PR #4428 added the node-creation surface to `INodeManagerBuilder`. This picks up the remaining candidates that needed a base-class change first, and reports on the two that turn out not to be candidates at all. ## Converted **`samples/WotCon/FlatTagServer/FlatTagNodeManager.cs`** — now derives from `FluentNodeManagerBase`. The private `CreateObject` / `CreateVariable` / `CreateMethod` helpers are gone, along with every `pumpNodeId + ".Operational.Measurements.MassFlow"` concatenation; nodes are staged with `AddObject` / `AddVariable<T>` / `AddMethod` and the hand-maintained `externalReferences[ObjectsFolder]` entry is replaced by `CompleteConfigureAsync`. The `EventNotifier` bits and `AddRootNotifierAsync(pump)` are kept verbatim — the pump is the notifier an aggregating server subscribes to, and OPC 10000-3 only delivers the supervision conditions to a client that can reach one. `SupervisionSignal` loses its `namespaceIndex` and `tagPath` parameters and derives both from the supervision Object it hangs off, so the signal follows wherever that Object was staged. **`samples/Redundancy/RedundantServer/HaSampleNodeManager.cs`** — same shape. The `Counter` still reaches `EnableDistributedValueParticipation` through `INodeBuilder.Node`. The variables keep their `Organizes` reference type, which the fluent default would otherwise have made `HasComponent`. **`samples/DI/PumpDeviceIntegrationServer/OpenUsdComposition.cs`** — both `AddPredefinedNodeAsync` sites stage on the builder now, threaded through `ConfigureAsync`; `DiNodeManager.CreateAddressSpaceAsync` already calls `RegisterAuthoredNodesAsync` between `ConfigureAsync` and the reverse-reference pass, so no new plumbing was needed. Both methods became synchronous. The NodeIds are unchanged because the hand `SystemContext.NodeIdFactory.New(...)` calls and `AssignInstanceChildNodeIds` were already doing exactly what staging does. `CreateRepresentedComponent` is left alone: it sits on `CreatePumpAsync`, a public runtime API, and so is out of scope for the same reason as the DI device builders. ## NodeIds are part of two samples' contracts The issue expected the staged path to mint through the manager's `INodeIdFactory`, and it does — but for two of these samples the identifiers are a published contract, not an implementation detail: - the aggregating client's checked-in Thing Descriptions under `samples/WotCon/AggregationClient/Documents/` spell out `nsu=…SourceA;s=Pump1.Identification.Manufacturer`; - `samples/Redundancy/RedundantClient/Program.cs` addresses `new NodeId("Counter", ns)` directly. Both managers therefore override `New` for those nodes instead of taking the default factory's derived identifier, which satisfies the acceptance criterion that each manager keeps its current address-space shape. FlatTag mints the full dotted browse path; the HA sample mints the bare browse name, but only for the sample folder and its direct children. Two things are worth knowing for anyone writing such an override. **It has to keep an identifier the caller already chose.** `DefaultNodeIdFactory.New` returns `node.NodeId` unchanged when it is non-null and in the manager's own namespace. Without that same guard, `NodeState.Create(context, nodeId, …, assignNodeIds: true)` sets the explicit id and then `AssignNodeIds` overwrites it from the factory — which silently moved `…SupervisionProcessFluid.Cavitation.Alarm` to `…SupervisionProcessFluid.CavitationAlarm` and broke five alarm tests until the guard was added. **It has to be narrow.** A browse name is unique only among its siblings. The HA override originally named every node the manager minted, and the historian hangs an `HA Configuration` object off each node it historizes — so the Counter's and the HistoryEvents' one both landed on `ns=2;s=HA Configuration` and the strong active/passive replicas refused to start. It is now limited to the nodes the redundant client actually spells out. ## Merge with master Merged `origin/master` (`0e600c0f0`). The one conflict was in `HaSampleNodeManager.CreateAddressSpaceAsync`, where #4445 added `AddFactoryAssignedNodes` next to the `HistoryEvents` object this branch had converted. Both are kept, and the factory-assigned subtree stays hand-built and unstaged on purpose: it exists to show what an identity minted straight through `NodeIdFactory` looks like beside the sample's named ones. It is attached once the folder has been staged, so those identifiers survive untouched and the subtree is still registered as part of the folder's. Its parent's identifier is `s=HighAvailability` on both sides, so the ids it mints are unchanged from master. ## Not candidates **`src/Opc.Ua.PubSub.Server/PubSubNodeManager.cs`** is out for the issue's own DI-device-builder reasoning. All three of its build paths are reachable from runtime Method calls — `OnAddDataSetFolder` and `OnRemoveDataSetFolder` → `RebuildConfigurationAddressSpaceAsync`, `OnAddPushTarget` / `OnRemovePushTarget` → `RebuildKeyPushTargetAddressSpaceAsync`, `OnGetSecurityGroup` / `OnAddSecurityGroup` / `OnRemoveSecurityGroup` → `RebuildSecurityGroupAddressSpaceAsync` — plus `ConfigurationChanged`. Each removes its previous roots and rebuilds them, and `Add*` throws `BadInvalidState` once `RegisterAuthoredNodesAsync` has run. Independently of that, its parents (`PublishSubscribe`, `PublishedDataSets`, `SecurityGroups`, `KeyPushTargets`) are owned by the diagnostics node manager, so `AttachToParent` would reject them as `BadNodeIdUnknown`. **`src/Opc.Ua.OpenUsd.Server/Scene/UsdSceneMaterializer.Properties.cs`** — the issue asked whether a builder is reachable at those call sites. It is not. `MaterializeUsdStage` is a public, documented `ISystemContext` extension (`docs/OpenUsd.md`, the package README) whose only input is the context; it returns a detached subtree for the caller to register. It already mints through `context.RequireNodeIdFactory()`, which is the same factory the staged path uses, so there is nothing to gain and a public signature to break. ## Verification Run against the merged tree: - `Opc.Ua.Di.Tests` — 425/425 pass (covers `PumpOpenUsdE2eTests`, `PumpHostedReferenceTests`). - `Opc.Ua.Redundancy.Samples.Tests` — 14/14 pass, including both variants of master's new `FactoryAssignedNodeIdsSurviveActiveReplicaFailureAsync` and `StrongHistorianContinuationsSurviveActiveReplicaFailureAsync`. - `Opc.Ua.WotCon.Samples.Tests` — 25 pass, 4 fail. The same 4 fail on a clean baseline worktree (`CompanionTypeDefinitionsMatchNativePumpServerAsync`, `WotPumpInstanceMatchesNativePumpSubsetAsync`, `RealSamplesAggregateSubscribeAndReplaceGenerationAsync`, `RealSamplesRouteManagementAndConditionActionsToEachSourceAsync` — all `BadConfigurationError` out of the aggregation client). Pre-existing, unrelated to this change. - All three sample projects build with 0 warnings and 0 errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7d5f7a7076 |
Add shared stack APIs and fixes used by UaLens (#4464)
# Description Extract shared stack, regression-test, sample and documentation changes from draft #4462 into a separate prerequisite PR. The PR now contains **19 shared paths**: the original eighteen extracted files plus a focused user-management regression file added during review. It does not include UaLens source, desktop dependencies, solution registrations or application documentation. ## Changes - Correct the V2 ConditionRefresh target and response validation. Bound disposal-time remote subscription deletion with an independent five-second TimeProvider-backed cancellation deadline, allowing local cleanup after a server outage. This is a per-delete limit, not a global application shutdown deadline. - Preserve configured PubSub writer message/header settings, canonical numeric JSON publisher matching, and flattened DataValue quality/timestamps. - Add asynchronous user-management client wrappers over the generated standard proxy, and set file/directory method declaration IDs needed by typed client workflows. - Enable AI client NativeAOT compatibility and add focused AI client regressions. - Enable the ConsoleReferenceServer file-system node manager and configure 1 ms publishing/sampling demonstrations. These sample defaults do not promise hard real-time delivery. - Document the subscription and PubSub behavior. Make the new subscription/PubSub regression helpers portable to .NET Framework without weakening their assertions. ## Review follow-up Commit `812297ce1` aligns both public `ListUsersAsync` signatures with `ValueTask<ArrayOf<UserManagementUser>>`. The implementation constructs its result directly from an owned snapshot array, without an intermediate list. Five focused cases pass on **net10.0 and net48**: `ListUsersReturnsArrayOfSnapshotsAsync` checks both interface and concrete entry points and preserves snapshot order, values and flags; `ListUsersReturnsEmptyArrayAsync` covers typed-empty and missing values; `ListUsersPreservesBadReadStatusAsync` checks the exact server failure status. All **eight single-line XML summaries** were converted to multi-line form. A complete scan of the fourteen changed C# files, including the new test, finds zero remaining single-line summaries. Scoped whitespace/line-ending verification passes. The sole UaLens caller was adapted separately in draft #4462 (`4caf69f33`). A fresh combined dependency-graph build and all **924 UaLens net10.0 tests** pass with both changes. Each of the three review threads received an action-taken reply and was resolved only after the fixes were pushed and verified. ## Split boundary The branch originated at `a45105375`; the subsequent master merge `f209f3804` incorporates upstream `4633aa7a3`. The original shared changes and review regression remain separate from the app. Draft #4462 retains `tools\Opc.Ua.Lens`, `tests\Opc.Ua.Lens.Tests`, and its six solution/package/documentation integration files. **Merge this prerequisite before the UaLens draft.** The draft consumes the extracted APIs. Published history is preserved; neither PR has been merged automatically. ## Initial split validation The following results describe the initial split validation at `fa7199afa`; current review-specific validation is recorded above. | Scope | net10.0 | net48 | |---|---:|---:| | Client subscription/file-system tests | 168 passed | 168 passed | | Server file-system/user-management tests | 244 passed | 244 passed | | PubSub writer/reader regressions | 34 passed | 34 passed | | ConsoleReferenceServer build | Passed | Passed | The corrected portable regression helpers were rechecked on net10.0 after the net48 fixes. The combined UaLens application also passed its 924-case net10.0 suite before extraction. **Focused NativeAOT: 16 passed, zero failed/skipped.** A session-local TUnit runner linked the exact committed `AIClientAotTests.cs` and referenced the real AI client project. It published without warnings or suppressions and executed as a genuine Windows native binary with no CLR header. The canonical `Opc.Ua.Aot.Tests` project was blocked by pre-existing CS8632 nullable-annotation errors in unchanged upstream `SchemaAotTests.cs` (lines 82, 127, 129 and 132). Its existing nullable configuration and all upstream tests were preserved; the focused result is not a claim that the full AOT test project builds. The net48 hosting selection uses a short NUnit WorkDirectory: the existing fixture builds long PKI paths that otherwise exceed the platform's path limit. The original full selection passed without test exclusions, source changes or increased timeouts. Scoped builds reported inherited warnings in unchanged upstream files: CA1508 in `ReferenceBuilderExtensions.cs`, CA1861 in `NodeSetImportIntegrationTests.cs`, and net48 CA1823 in `DefaultNodeIdFactory.cs`. They were not suppressed or folded into this split. Full-solution, coverage and CI/CodeQL gates remain unasserted. ## Related Issues - Tracking/design review: #4461. - Prerequisite extracted from draft #4462. ## 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. - [ ] 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. Tests and scoped results are documented above; measured coverage, inherited-warning qualification, CLA status, full-solution results and CI completion are not asserted by these checkboxes. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
4633aa7a34 |
Harden HA address-space hydration and replica-consistent NodeIds (#4445)
# Description Follow-up to #4390 for distributed address-space ownership, authoritative hydration, and replica-consistent wire NodeIds. Tracks #4444. Identity implementation: `a78d59067` (Server policy/lifecycle/adapter seam) and `07faf5cfd` (replica identity module, runtime wiring, examples and proofs). ## Replica identity - `UseReplicaNodeIdentity` / `ReplicaNodeIdFactory` reserve fixed shared namespace indexes before node-manager construction and reuse `DefaultNodeIdFactory`. ApplicationUri and built-in diagnostics/configuration remain local. - Replicated startup requires an explicit identity contract. Namespace layout, deterministic mode, replica-set identity and writer policy must agree. Standard factory views, replacement and registration preserve the policy. - Active/passive writer-assigned identities are replicated unchanged. Independent creation requires stable keys or explicit IDs. Live IDs and retained tombstones are reserved before writer allocation; no network/storage call runs in `New`. - Protected store descriptors use strong routing, including customized hybrid prefixes. Unknown legacy state and empty eventual scans cannot authorize automatic adoption. Verified-new hybrid provisioning is explicit. - Active/active frames compare the descriptor before CRDT merge. Rejected frames are not reported as applied; incompatible admission remains at ServiceLevel 0 through the standard service-level wiring. - Shared metadata validation covers type definitions, data types, references, permissions, NodeId/ExpandedNodeId/QualifiedName values, arrays/matrices, DataValues and encodeable structures/arguments, without reflection. - Runtime preparation hydrates retained IDs before replacement routes publish. Rebinding keeps the existing store/election or gossip transport/map and attaches to the new node-manager sources. Normal node-manager mutations, including AddNodes, reach the cached local-address-space adapter. - WoT materialization retains the stable configured factory. Samples expose genuinely factory-created nodes and a client `--identity` failover workflow. ## Preserved HA repairs The published `d99995003` and `79da74f14` fixes remain in place: - Shared linearizable sequence/lease coordination; no pretend CAS on CRDT and no private counter substituted for shared coordination. - Strict authoritative record decoding, protected pending reservations, safe snapshot publication and required delta retention. - Accepted topology state controls authoritative membership; stale deletion cannot remove a newer incarnation. - Independent subtree attachment tracking and correct disposal of the final snapshot task. - Values wait for their topology, and parent tombstone horizons prevent older descendant values crossing incarnations. A new deterministic regression also covers writer hydration preparation: temporary reconciliation removals must remain inbound application, not be recaptured as new shared deletion records. ## Validation ### Review feedback fixes `3a682155b` addresses the active-node notification, WoT namespace-index bounds, and retired-manager XML documentation feedback. The address-space adapter now notifies listeners with the registered active node, and both WoT managers reject missing/out-of-range namespace indexes before ownership is established. Targeted feedback validation passes on net10.0 and net48: 8 address-space adapter cases and 74 WoT cases per framework, plus 17 hydration/gossip/live-client integration cases on net10.0. The wrong-instance notification and missing namespace regressions were reproduced before the fixes. ### Identity implementation evidence | Selected scope | net10.0 | net48 | | --- | --- | --- | | HA repair, identity, metadata, peer admission and real-client identity/reload fixtures | 215 passed | 215 passed | | Factory, fluent authoring/import, NodeManagement and address-space adapter fixtures | 213 passed | 213 passed | | WoT node-manager fixtures | 68 passed | 68 passed | - Real UA-TCP clients compare full NodeIds/reference targets across independent servers in all four deterministic modes, with different manager/creation order and local allocations. Saved IDs work after endpoint failover and restart. - Real AddNodes-created IDs remain readable through successive live manager replacements in both replication modes, including nodes created after a reload. - Two process-level cases start three sample replicas, kill the serving process, verify a different serving ServerArray URI, and reuse cached factory-generated IDs for Read and CreateMonitoredItems without remapping or rebrowsing. - The identity/hydration scenario runs from the actual published win-x64 NativeAOT executable in the existing historian companion host. - The focused writer-recapture regression was observed failing without its fix, then passing. The real reload scenario was additionally repeated while diagnosing that race. - Changed-file formatting/style/analyzers were applied. Existing CA1508, CA1823, CA1861 and CA1850 warnings in untouched upstream files were not suppressed. The selected checks above do not replace full-solution CI, coverage gates or maintainer review, and do not claim that the entire solution or every native platform was run. #4444 remains the tracking issue. ## Contract and consistency limits Fixed identity does not make eventual payload reads linearizable. Hybrid views remain non-destructive, use explicit tombstones, and do not infer empty-store bootstrap or partition completion from absence. Compacted snapshots require authoritative storage. Failed/uncertain reservations require reconciliation. There is no automatic namespace-slot allocation, persistent logical-key mapping database, live renumbering, legacy-store wipe/adoption, or new hash algorithm. Opaque shared structures require a registered codec so their namespace-bearing fields can be validated. Application callbacks are still application-owned. ## Related issues - Tracks #4444. - Follow-up to #4390. - Builds on the merged NodeId factory (#4433), configure/seal (#4442) and lifecycle/import (#4427) integration at `aedd3f738`. ## Checklist - [ ] I have signed the CLA and read CONTRIBUTING. - [x] Added targeted regression and integration tests. - [x] Updated directly related API, HA, migration and sample documentation. - [x] Ran scoped formatting and the targeted framework checks listed above. - [ ] Full UA.slnx matrix and coverage gates completed. - [ ] All CI and maintainer review completed. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9dbd9527-d818-47a0-984c-fbb1472d3d6b |
||
|
|
36f51cb49d |
Add per-type, resource-owning node behaviors to the fluent builder (#4441)
# Description
The fluent builder resolves exactly **one node per call**.
`NodeFromTypeId` is exact-type-only and throws `BadBrowseNameDuplicated`
the moment two instances match, and handlers land in single-occupant
`NodeState` slots — a second handler for an occupied slot is an error by
design.
So the fluent surface cannot express four things:
- **fan-out** over the instances of a type,
- **subtype matching**, so a registration for `AnalogItemType` also
covers a user's derived type,
- **composition** of two behaviors on one node,
- **teardown** for a behavior that *owns* something — a semaphore, a
file handle, a store, a timer.
Three hand-rolled registries already work around exactly this —
`EventSourceRegistry` (730 lines), `MonitoredSourceRegistry` (998),
`SimulationRegistry` (328) — each with its own register → activate →
reconcile → deactivate → dispose loop and its own failure policy,
written three times because the slot model had no lifecycle to hang them
on.
This PR adds that lifecycle and moves the registries onto it.
## The new surface
Ten public members. A registration returns an `IAsyncDisposable` that
the node manager owns:
```csharp
builder.AttachToType<AlarmConditionState>(
ObjectTypeIds.AlarmConditionType,
async (alarm, ctx, ct) =>
{
MySubscription sub = await Source.SubscribeAsync(alarm, ctx.Lifetime).ConfigureAwait(false);
return sub; // released on teardown, in reverse order
});
```
- `AttachToType<TState>(NodeId, attach, NodeAttachOptions?)` — one
behavior per matching instance, subtypes included by default.
- `Attach<TState>(attach)` on a node builder — one behavior on an
already-resolved node.
- `Attach(attach)` on the manager builder — manager-scoped, owns no
node.
- `AcquireWhileMonitored<TValue>(acquire)` — acquire on the first
subscriber, release on the last **and at shutdown**.
- `INodeAttachContext`, `NodeAttachOptions`, and `protected
ActivateNodeBehaviorsAsync` on `FluentNodeManagerBase`.
**Guarantees.** Behaviors activate deepest node first, and on one node
from the base-registered behavior to the derived-registered one;
manager-scoped behaviors run last, so they can drive what the node
behaviors prepared. Any failure — including cancellation — unwinds every
lease recorded so far in **exact reverse order**, and aggregates cleanup
failures *with* the original rather than replacing it.
**The engine stays internal.** Its vocabulary is not worth freezing into
public API, and `INodeAttachContext` + `IAsyncDisposable` is the whole
contract a caller needs.
Two deliberate decisions worth a reviewer's attention:
- **Method nodes are excluded from type matching.** `MethodState`
aliases `MethodDeclarationId` onto `TypeDefinitionId`, so without an
explicit filter every method node would match a registration for its
declaration — a surprise nobody asked for. Method behavior stays on the
call handler. Pinned by a test.
- **A CLR type mismatch throws `BadTypeMismatch`**, naming node,
expected and actual type, rather than silently skipping the instance. A
whole set of alarms quietly getting no behavior is worse than a startup
failure.
`Dispose(bool)` is **signal-only**. Real release belongs in
`DeleteAddressSpaceAsync`, which `MasterNodeManager.ShutdownAsync`
invokes before it disposes managers on every production path — including
the synchronous one, where `StandardServer.Dispose` blocks on
`DisposeAsyncCore` → `StopAsync` → `ShutdownAsync` first. Blocking in
`Dispose(bool)` would sit under a sync-over-async dispose and
`MasterNodeManager`'s semaphore `Wait`.
## The registry migrations
**`SimulationRegistry` — release moved, plus two latent bugs.**
`Dispose` cancelled the loops then blocked up to five seconds draining
them, from a synchronous dispose. Release now runs on the async teardown
path and `Dispose` only trips the token. Loops are driven from the
server `TimeProvider` instead of `PeriodicTimer`/`Stopwatch`, so they
are testable on a fake clock rather than with sleeps. Fixed alongside:
the running loop enumerated the handler list **by reference** while a
late `OnTick` could still mutate it (handlers are now snapshotted, and a
late `OnTick` is rejected rather than silently dropped); and a handler
throwing `OperationCanceledException` for its own reasons killed the
whole loop in silence.
**`EventSourceRegistry` — ownership only; the reconcile loop stays.** It
cannot collapse: `OnSubscribeToEventsAsync` runs while the
monitored-item semaphore is held and `AddRootNotifierAsync` re-enters
`SubscribeToEventsAsync`, so awaiting anything lease-shaped from that
hook would self-deadlock. What moves is ownership of the two side
effects nothing reversed — the `EventNotifier` bit ORed into the
notifier, and the root-notifier registration. A node that *already*
carried the flag is not ours to clear, so only what registration
actually changed is recorded and undone.
**`MonitoredSourceRegistry` — ownership only, and the real leak.**
Rerouting teardown to `DisposeAsync` turned out to be necessary but
**not sufficient**: that method only clears the item map, cancels the
worker and disposes the lock, while the last-subscriber handler runs
*solely* from the reconcile path's `Deactivate` arm — which server
shutdown never reaches, because subscriptions dispose their items
locally without deleting them. Anything acquired on the first subscriber
therefore outlived the server. `ReleaseAsync` now runs those handlers
for still-active sources, and `AcquireWhileMonitored` is the demand verb
over the pair, with a test that proves release at shutdown with a live
subscription.
**Alarms.** Attaching an alarm enabled the condition, promoted the whole
notifier chain and registered a root notifier, with no inverse anywhere.
`RegisterAlarmEventSource` now reports what it changed and an
`AlarmRelease` behavior undoes it. While wiring that up: the parent's
`EventNotifier` was being promoted **twice** — once directly and once
inside `RegisterAlarmEventSource` — which left the reversible path with
nothing recorded. The redundant promotion is gone.
Alarms still work on managers that never opted into the fluent surface;
those simply get no automatic release, exactly as before, rather than a
hard failure.
## What this deliberately does not do
**Registry and alarm *setup* stays where it is.** Only release moved.
`NodeManagerBuilder.Seal()` is synchronous and is called from sites that
never call `CompleteConfigureAsync` — `Isa95NodeManager.cs:1046` seals
from inside `private void ConfigureStatusEvents()` — so making behavior
activation the sole setup path would silently disable simulations, event
sources and alarms in ISA-95, Pump, Generator and RuntimeNodeSet.
That same synchrony is what forced the `OnAddressSpaceReadyAsync` hook
in #4434 (*"`Configure` is a `partial void` and cannot await"*), which
`Opc.Ua.Di.Server/DiNodeManager.cs` had already invented privately. **A
separate PR making the fluent configure path async is planned to land
first**; after it, setup can move onto activation here and both copies
of that workaround can go.
Also out of scope: incremental attach on runtime node add and release on
node removal (the ledger needs real synchronisation first), and the
Quickstart `AlarmNodeManager` port that would give the alarm work
end-to-end coverage.
## Related Issues
No tracking issue yet — this came out of a design review of an earlier
behavior-activation prototype, which was rejected for being unreachable
from any application and hooked into a loading path rather than the node
manager. Happy to open one and link it if maintainers want the ADR on
file before review.
## 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. *(XML doc comments on
every new public member, including why method nodes are excluded and why
`Dispose` is signal-only. No `docs/` page — say the word if one is
wanted.)*
- [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. *(net10.0
only — see Validation. Not run against .NET Framework locally.)*
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
## Validation
net10.0:
- `UA.slnx` builds clean, 0 errors.
- `Opc.Ua.Server.Tests`: **4,984 passed, 0 failed**, 5 skipped.
- 23 new tests:
- **17 on the behavior surface** — child-first ordering,
base-before-derived on one node, exact-reverse unwind, fan-out over
three instances, subtype vs exact-type matching, zero-match diagnostics,
declined instances, mid-activation rollback, lifetime-token ordering,
second-pass unwind order, drained registrations, method-node exclusion,
alarm release, `Publish` release, `Dispose` tripping an activated
behavior's lifetime, and the two aggregation contracts (several release
failures all surface and do not stop the remaining releases; a rollback
that itself fails does not hide the activation failure that triggered
it).
- **4 on the simulation lifecycle** — fake-clock ticking, teardown
awaiting an in-flight tick, non-blocking `Dispose`, late-handler
rejection.
- **2 on `AcquireWhileMonitored`** — release on the last subscriber, and
release at shutdown with a live subscription.
- No new analyzer warnings.
Not run locally: .NET Framework targets, and suites outside
`Opc.Ua.Server.Tests`.
**On the advisory coverage gate.** The first report put patch coverage
at 47.65%, listing whole files as uncovered that these tests exercise
directly — `NodeBehaviorRegistry.cs` end to end, for instance. That
report is assembled from whichever legs published Cobertura fragments,
and the `Server` leg had not. It did point at three paths that genuinely
had no test, all now covered: `EventSourceRegistry.ReleaseAsync`,
`SignalShutdown` (reachable only via `Dispose` on a manager whose
behaviors actually activated), and the two failure-aggregation paths.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ececea15c8 |
Reimplement the GDS ApplicationsNodeManager as a generated fluent node manager (#4434)
# Description Builds on #4428, which is now merged; this branch is rebased onto master. The GDS `ApplicationsNodeManager` becomes a source-generated `[NodeManager]`: the companion model, its loader and the fluent plumbing are emitted, and what stays hand-written is the behaviour. Getting there needed three things from the shared surface, which are the first two commits. ## Commits **1. Add the fluent hooks a hand-written manager needs at startup** - `OnReadRolePermissions` / `OnReadUserRolePermissions` on the node builder. Every other `NodeState` `On*` hook was already fluent; these were not, and a server that grants access on something the static model cannot express has to compute the permission set per request. - `Node<TState>(TState)` returns a builder over a node the caller already holds, for nodes materialised after `Configure` resolved its graph. - `OnAddressSpaceReadyAsync` on `FluentNodeManagerBase` — the one seam between the address space existing and the wiring being applied, for managers whose wiring depends on asynchronous setup. `Configure` is a `partial void` and cannot await. `DiNodeManager` had already invented the hook privately, so its declaration becomes an `override`. **2. Let a `[NodeManager]` bind to a model a referenced assembly supplies** A model belongs to the assembly that emits its types, which leaves a node manager for it homeless whenever that assembly cannot reference `Opc.Ua.Server` — a model-only package shared with clients, for instance. Binding was rejected twice over: the design was skipped before the binding was matched (`MODELGEN010`), and fluent-accessors-only mode refused to run alongside manager generation (`MODELGEN014`). The binding is now matched before the skip decision, and a matched binding puts that design into accessors-only mode. A design nothing binds to is still skipped, so this cannot start duplicating types by accident. The emitted manager also gains what a real manager needs: `DefaultNamespaceUris()` plus a protected constructor taking a replacement set; `GenerateDefaultConstructor=false` to suppress the two-argument form; its own logger category; and the call to `OnAddressSpaceReadyAsync`. Generated fluent accessors also stop re-enabling nullable warnings — a bare `#nullable enable` undid the shared header's `disable warnings`, so any model with a structure-typed method argument reported CS8600 on every generated `out T` in a project that builds warnings as errors. **3. Reimplement the GDS `ApplicationsNodeManager` as a generated node manager** The design stays owned by `Opc.Ua.Gds.Common`; the server assembly only binds a manager to it. **NodeIds do not move.** `NamespaceIndexes[0]` must stay the application record namespace — it is the one the application database, the certificate request store and the base allocator mint ids in — so the constructor passes the namespace order explicitly rather than adopting the generated model-first default. A test pins both ends against a live server. Four overrides go, leaving `OnAddressSpaceReadyAsync` and `Dispose(bool)` — no address-space overrides at all: | Removed | Why it was unnecessary | | --- | --- | | `New` | The base allocator already mints into `NamespaceIndexes[0]`, with a thread-safe counter. The override existed only because `Create(assignNodeIds: true)` asks the factory with the declaration id still set; `AssignInstanceNodeId` nulls it and re-asks, documented as being there for exactly this allocator. | | `AddBehaviourToPredefinedNodeAsync` | Its `AuthorizationServiceType` branch re-wired a node that was already wired, and its `KeyCredentialServiceType` branch had nothing to wire — the model ships that folder empty. | | `GetManagerHandleAsync`, `ValidateNodeAsync` | Verbatim reimplementations of the base that also blocked the fluent base's virtual-node support. | | `DeleteAddressSpaceAsync` | A `// TBD` passthrough. | Everything else — all sixteen directory methods with their self-administration permissions, the certificate groups' concrete types and trust-list flags, and the authorization service (created through this PR's `Add` surface and wired in the same place) — is one `Configure` pass. **4. Bind the certificate groups from the `Configure` pass** Acquiring a certificate group and binding it to its nodes are different kinds of work: the first is I/O (`InitAsync` opens certificate stores and creates CA certificates), the second only touches the address space. Each now runs in the phase it belongs to, which puts the whole of the wiring in one place. Ownership no longer rides on the binding. A group joins `m_ownedCertificateGroups` the moment it is constructed, before `InitAsync` can throw, so a group that fails half way through is still disposed — which the previous code could not manage, keying ownership off a dictionary it filled only after initialization succeeded. > Commit 3's message says this move is impossible and blames `PushTest`. **That was a wrong diagnosis, and this commit supersedes it.** Two real defects were hiding behind those failures: - **`Create(..., NodeId.Null, ..., assignNodeIds: false)` does not null the NodeId.** `NodeState.Create` overrides it only when handed a non-null one, so a custom group node kept the namespace-0 id of its type declaration. `AssignInstanceNodeId` hid this by re-asking the factory with the id nulled; the builder validates instead, and rejected it. The node is now created with an id in the manager's own namespace, as the `Default` authorization service already was. - **The custom certificate group node was not browseable.** It answered `GetCertificateGroups` and `ReadNode`, but a type-filtered recursive browse from the Objects folder never returned it — which OPC 10000-12 §7.8.2 requires of a certificate group. Staging the node through the builder gives it the reference it was missing. ### Two `PushTest` methods change — please review these closely The second defect had been masking what those tests actually did. Selecting the group positionally, `groups[3]` resolved to the server's **own `ServerConfiguration` group** while the custom one stayed invisible, so `UpdateTrustListOfCustomGroupAsync` and `AddRemoveCertOfCustomGroupAsync` were exercising a *transactional* trust list under a custom-group name. Browse results, before and after: | # | Before (baseline) | After | | --- | --- | --- | | 0-2 | GDS Directory groups (`ns=3`) | GDS Directory groups (`ns=3`) | | 3 | **`DefaultApplicationGroup` `i=14156`** — the server's own | **`MyCustomGroup` `ns=2`** | | 4 | — | `DefaultApplicationGroup` `i=14156` | They now resolve the group by browse name — which asserts it is browseable at all — and expect the immediate-apply semantics the GDS actually gives the groups of its own `Directory`: `CloseAndUpdate` writes the stores at once and reports `applyChangesRequired == false`, so the `ApplyChanges` calls go away (they would return `Bad_NothingToDo`). The read-back assertions are what prove the writes landed, and they are unchanged. This is a genuine loss of the *transactional* trust-list coverage those two methods were accidentally providing — the server's own group is still covered by the other `PushTest` methods, which target it deliberately. **5. Select `GetCertificates` results by type instead of by position** `GetCertificatesAsync` checked entry zero of a parallel-array result. Which entry that is depends on the order the server reports its certificate types in, an empty result throws `IndexOutOfRange` rather than failing an assertion, and a second type going missing or unparseable would not be noticed. Every returned pair is now checked, and a failure names the certificate type. **6. Drop the `.Common` suffix from the three GDS projects** Every other companion specification ships as `<Family>` / `<Family>.Client` / `<Family>.Server` — Di, ISA95, Positioning, Robotics, Vision, WotCon, XRegistry. GDS was the exception, with a `.Common` suffix that named nothing. | Before | After | | --- | --- | | `src/Opc.Ua.Gds.Common` | `src/Opc.Ua.Gds` | | `src/Opc.Ua.Gds.Client.Common` | `src/Opc.Ua.Gds.Client` | | `src/Opc.Ua.Gds.Server.Common` | `src/Opc.Ua.Gds.Server` | **No source changes were needed.** `RootNamespace` was already the final name in all three projects — the namespaces have always been `Opc.Ua.Gds`, `Opc.Ua.Gds.Client` and `Opc.Ua.Gds.Server`, and only the assembly and package ids disagreed. Nothing compiling against these assemblies changes a `using`. The package ids do change, which is consumer-visible, so `docs/migrate/2.0.x/packages.md` gains a renamed-packages section — it is the document that undertakes to cover NuGet renames — and the migration skill's package table gains the two rows a 1.5.378 consumer needs. Build infrastructure that names the projects or their output moves with them: both solution files, `expected-packages.txt` (re-sorted — it is maintained alphabetically), both signing manifests, the reference-server Dockerfile, the metapackage nuspecs, and the analyzer's package dependencies. `preview-pack.slnx` is derived from `UA.slnx` and needs no edit. The migration analyzer's shim tree mirrors the source layout by its own documented convention, so `Gds.Client.Common/` becomes `Gds.Client/`. Prose naming past work is left alone — plan 23 still records that its deferral happened during the "GDS Client.Common modernization", which is the name that effort had. ## Validation `net10.0`, on master + these four commits, from a clean GDS PKI: - `UA.slnx`: 0 errors, 0 warnings in changed files (the solution's 30 pre-existing warning lines are CA1861/CA1850/CA1307 in `XRegistry`, `Types.Tests` and `WotCon.Tests` — same rules and projects as the base). - `Opc.Ua.Gds.Tests`: 1,117 passed, 0 failed, 54 skipped — identical to the base branch. - `Opc.Ua.Server.Tests`: 4,961 passed, 0 failed, 5 skipped (measured before commit 4, which touches only the GDS assembly and its tests). - `Opc.Ua.SourceGeneration.Tests`: 169 passed. - `Opc.Ua.SourceGeneration.Core.Tests`, NodeManager/Generators/Fluent: 84 passed; `GenerateCode`: 21 passed, 4 skipped. - `Opc.Ua.MigrationAnalyzer.Core.Tests`: 10 passed, 3 skipped. - After the rename: assemblies build as `Opc.Ua.Gds.dll` / `.Client.dll` / `.Server.dll`, with no `*.Common.dll` produced anywhere. - Commits 1-3 build standalone. - `Opc.Ua.Gds.Server.Common` also builds for `net48` and `netstandard2.1` (checked before the rebase onto #4428). Not yet run: the full `Opc.Ua.SourceGeneration.Core.Tests` suite (~3,800), and the older target frameworks since the rebase. ## Checklist - [x] Existing tests pass - [x] New tests cover the new surface (role permissions, resolved-node builder, referenced-model binding, constructor/namespace control, GDS namespace invariant, custom-group node namespace) - [ ] Full Core.Tests sweep and net48/netstandard2.1 re-run after the rebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
105508bcbd |
Mint runtime NodeIds through a single DefaultNodeIdFactory (#4433)
# Description
Nodes created at runtime now get their NodeIds from a single
`DefaultNodeIdFactory` rather than from a `New` override per
NodeManager.
The factory derives a deterministic identifier from a canonical,
length-prefixed browse path, which is injective where the classic
`{parent}_{child}` concatenation was not — `A_B` + `C` and `A` + `B_C`
both produced `A_B_C` — and it also records the parent's identifier type
and qualifies cross-namespace segments by URI, so identifiers no longer
shift with namespace-table ordering.
A NodeManager selects its style declaratively instead of writing
assignment code:
```csharp
NodeIdFactory = NodeIdFactory.WithMode(NodeIdAssignmentMode.Counter);
```
`NodeIdAssignmentMode` offers `String` (the canonical path verbatim, the
default), `Numeric` / `Guid` / `Opaque` (that path through SHA-256),
`Counter` (sequential, for nodes whose browse paths repeat — per-session
diagnostics, inference jobs, rediscovered assets) and `None`. The
counter
starts above `0x40000000`, out of reach of any authored model, so a
long-running server cannot walk a runtime instance onto a type node.
`New` overrides are gone from every NodeManager the stack ships except
two whose identifiers are a domain scheme rather than an assignment
policy: `FileSystemNodeManager`, whose NodeIds encode the file path they
resolve back to, and `RoboticsNodeManager`, whose build coordinator
reserves identifiers across managers with ownership tracking.
**Namespace rule.** Every identifier is minted into the NodeManager's
own
namespace. Neither the parent's namespace nor the browse name's is
consulted: a parent can belong to a companion-specification model whose
NodeIds are fixed by its NodeSet (`DeviceSet` in the DI namespace), and
a
browse name only names the type that declared the child. NodeManagers
whose instance namespace is not their first one rebase with
`WithDefaultNamespaceIndex`.
**Types vs instances.** An authored NodeId on a node that stands on its
own is kept. A node hanging off a parent is re-minted, because it
reached
the factory through `AssignNodeIds` walking a subtree copied from a type
declaration. Keeping those would alias every instance onto the type's
own
nodes, and the predefined-node index takes the last writer, so the type
would quietly become an instance rather than the clash being reported.
New `ISystemContext.CreateInstance(...)` builds an instance of a
generated
type and rebases its subtree through `AssignInstanceNodeId` — the path
the
generated `CreateInstanceOf<Type>` helpers already took. The AI
NodeManager was the one place calling `NodeState.Create` directly, so
its
roots kept the NodeId their state object is born with (the type's own);
it now uses the shared path.
## Breaking changes
- `IAsyncNodeManager` extends `INodeIdFactory` and gains `AddNode` and
`AddRootNotifier`. This lets the fluent surface mint and register
through the interface instead of type-testing for
`AsyncCustomNodeManager` and silently falling back to a concatenated
identifier when the test failed — that fallback was itself a source of
divergent NodeIds.
- `AsyncNodeManagerAdapter` delegates `New` to the wrapped NodeManager,
so
**`CustomNodeManager2` behaves exactly as before**: it mints nothing and
a node keeps whatever NodeId it already has. Servers built on it are
unaffected.
## Notes for reviewers
- `docs/NodeIdAssignment.md` is new and describes the whole mechanism:
the
factory contract, identifier formats, type declarations vs instances,
the generated helpers and their parameters, per-NodeManager behaviour,
and an inventory of every member involved.
- The three commits are independently revertible.
- Two pieces that look redundant are deliberately kept, with the
reasoning
recorded in the doc: `assignInstanceNodeIds` (it *suppresses* minting,
where the rebase rule *forces* it) and the generator's
`NodeId.Equals(TypeNodeIdConstant)` guard (it distinguishes a
caller-assigned NodeId from a declaration's).
- Fluent builder tests previously passed only because of the removed
fallback; they now run against a real `DefaultNodeIdFactory` through a
shared `FluentTestNodeManager` helper.
## Related Issues
_No tracking issue yet._
## 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.
> Test runs were on **net10.0 only** — .NET Framework was not exercised
> locally. Suites run green on an idle machine: Server 4950/4955, GDS
> 1116/1170, WotCon 1225/1225, Vision 484/485, DI 424/424, AI 174/174,
> ISA95 136/136, Positioning 69/69, RobotIntent 22/22. One unrelated
> pre-existing failure remains in `Opc.Ua.Types.Tests`
> (`EveryEmbeddedExampleMatchesItsRecordedHash`) — a WoT fixture checked
> out with CRLF, fixed by
> `git add --renormalize tests/Opc.Ua.Types.Tests/Wot/Assets`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
668310f21f |
Make the fluent node manager's configure/seal path genuinely async (#4442)
# Description The fluent node-manager pipeline had two synchronous chokepoints, and two independent efforts have now hit both of them. `Configure` is a `partial void`, so it cannot `await`. A manager whose wiring depends on asynchronous setup had to invent a second hook outside the pipeline, create its own builder there, and hand-roll `CreateFluentBuilder(...).Configure(...).Seal()` inside it. `Seal()` was synchronous, so nothing a registration staged during `Configure` could be completed by awaiting. Registrations that needed to await either blocked — `EventSourceRegistry` did `.GetAwaiter().GetResult()` on the manager's root-notifier registration — or could not be expressed at all. ## What changed **`FluentNodeManagerBase.ConfigureAsync(INodeManagerBuilder, CancellationToken)`** is the awaitable wiring seam. It runs once per activation, with the manager's attached builder, immediately *before* the synchronous `Configure` partial(s) — so instances it materialises are in the address space by the time `Configure` wires callbacks against them. The default implementation is a no-op. It is a `virtual` method rather than a second `partial` declaration because a partial method can be optional (`partial void`, no return value) or awaitable (an extended partial method, which must be implemented) — not both, and the generator would otherwise have to detect which one the user declared. `partial void Configure(INodeManagerBuilder)` keeps working unchanged; a manager uses either hook or both. **`NodeManagerBuilder.Seal()` becomes `SealAsync(CancellationToken)`.** Sealing is now the single point where registrations that could not finish inside `Configure` get their turn to await. Every seal site awaits it, so a later change can move registry *setup* onto the same seam. **`DiNodeManager` now runs the standard pipeline itself** — create and attach the builder, `await ConfigureAsync`, `RegisterAuthoredNodesAsync`, `CompleteConfigureAsync`, the post-setup runner, then `await builder.SealAsync` — instead of handing subclasses a bare callback and leaving them to build and seal on their own. DI subclasses gain node registration and the reverse-reference pass, which they previously skipped. Sealing deliberately comes **after** the post-setup runner. The fluent registries are owned by the manager, not by a single builder, and sealing starts them — so sealing first locks post-setup configurators out of registering simulation loops of their own, which is exactly what `AddRobotics`' configurators do. Sealing before the runner makes `MinimalRobotServer` fail to start with *"Cannot add a simulation loop after the registry has started"*. Worth knowing for follow-up work: with several builders attached to one manager, the first seal freezes the shared registry for all of them. ## Workarounds removed - **`DiNodeManager.OnAddressSpaceReadyAsync`** — deleted. Its three overriders (`RoboticsNodeManager`, and the `PumpNodeManager` / `GeneratorNodeManager` samples) now override `ConfigureAsync` and no longer hand-roll `CreateFluentBuilder(...).Configure(Configure).Seal()`. - **`EventSourceRegistry`'s `.GetAwaiter().GetResult()`** on `AddRootNotifierAsync` — gone. `Publish(..., RegisterAsRootNotifier: true)` now stages the registration and `SealAsync` drains it, so the wait happens on an awaited path. A source whose root-notifier registration fails is rolled out of the registry before the failure surfaces, and a registry disposed before its builder sealed drops what it staged. - **`Isa95NodeManager.ConfigureStatusEvents()`** — the synchronous second builder pass is now `ConfigureStatusEventsAsync(CancellationToken)`, awaited from `CreateAddressSpaceAsync`. (`ConfigureCatalogChanges()` stays synchronous: it starts a background task and never seals a builder, so there was nothing to convert.) - **`IRoboticsBuildContext.Seal()`** — now `SealAsync(CancellationToken)`. These really were `NodeManagerBuilder.Seal()` calls behind a same-named method. The state transition still happens under the context lock; the builder's asynchronous completion runs outside it, so no `await` is taken while the lock is held. - The Pump / Generator incremental registration callbacks (`onRegistered`) changed from `Action<T>` to `Func<T, CancellationToken, ValueTask>` so their per-instance builder pass can await its own seal. No sync-over-async was introduced anywhere. `Dispose(bool)` is unchanged and stays non-blocking; teardown remains in `DeleteAddressSpaceAsync`. `NodeManagerBuilder.Seal()` and `IRoboticsBuildContext.Seal()` are removed rather than kept as synchronous overloads. A synchronous seal that silently skipped the asynchronous completion would reintroduce exactly the failure mode this change exists to remove. ## Generator `NodeManagerTemplates` emits, in order: predefined nodes load → `await ConfigureAsync(__m_builder, cancellationToken)` → `Configure(__m_builder)` and the typed `Configure` → `RegisterAuthoredNodesAsync` → `CompleteConfigureAsync` → `await SealConfigurationAsync(__m_builder, cancellationToken)`. `NodeManagerGeneratorTests` asserts that order literally, and that neither `__m_builder.Seal()` nor the synchronous `SealConfiguration(__m_builder)` is emitted. ### Merging with #4432 #4432 landed on `master` while this was in review and reworked the same seam from the other side: it split `Seal()` into `SealGraphAuthoring()` + `StartSimulations()` so a manager can replay `NotifyNodeAdded` *between* the two halves — sealing first stops a lifecycle handler authoring nodes nothing would register, starting the simulations last stops a simulated value change preceding its own node''s `OnNodeAdded` — and added `FluentNodeManagerBase.SealConfiguration(builder)` as the one call the generator emits. Both intents are kept. That split is precisely why the asynchronous completion could not simply live inside `SealAsync`: a manager reaching the seam through the split path would silently skip it, which is the failure mode this PR exists to remove. So the asynchronous half became its own step: - `NodeManagerBuilder.CompleteSealAsync(ct)` drains the staged root-notifier registrations, then starts the simulations. `SealAsync(ct)` is `SealGraphAuthoring()` followed by it. - #4432''s synchronous `StartSimulations()` is gone, folded into `CompleteSealAsync`. It had exactly one production caller after the merge, and leaving it standing would have kept a second, synchronous way to activate a builder that skips the staged registrations — the very hazard this PR removes. Activation is now one step with one entry point; the *split* itself stays, because the replay has to sit between the halves. - `SealConfiguration` becomes `SealConfigurationAsync(builder, ct)`: seal the graph, replay `NotifyNodeAdded`, then `CompleteSealAsync`. Activation still happens last, so #4432''s ordering guarantee holds unchanged. ## Consumers unblocked - **#4434 ("Reimplement the GDS ApplicationsNodeManager as a generated fluent node manager")** adds `OnAddressSpaceReadyAsync` to `FluentNodeManagerBase` purely because, in its own words, *"`Configure` is a `partial void` and cannot await"*. That hook is unnecessary once `ConfigureAsync` exists — the GDS manager overrides `ConfigureAsync` instead, and the ordering it wants (predefined nodes → async setup → `Configure`) is what the generator now emits. #4434 has not merged yet, so it can drop its addition rather than have it simplified afterwards. - **Branch `romanett/fluent-node-behaviors`** moved `SimulationRegistry`, `EventSourceRegistry`, `MonitoredSourceRegistry` and the alarm wiring onto `AttachToType`/`Attach` for *release* only, because activation ran from `CompleteConfigureAsync` and most managers only called the synchronous `Seal()`. Every seal site now reaches `SealAsync`, so registry and alarm *setup* can move onto behavior activation too — the remaining half of that work. ## Tests - New `DiNodeManagerConfigureAsyncTests` drives the real `DiNodeManager.CreateAddressSpaceAsync` and asserts the hook runs once with an attached builder, that nodes it stages are registered, that their references to externally owned nodes are published, and that the builder is sealed before `CreateAddressSpaceAsync` returns. - `PublishTests` now pins the root-notifier deferral contract: registration is staged during `Configure`, lands only on the seal, and draining twice does not double-register. - Every `Seal()` call site in the test suites became `await ...SealAsync()`, including the three #4432 added in `NodeSetImportBuilderTests` and `SimulationBuilderExtensionsTests`. ## Related Issues No tracking issue was opened for this; it is the shared prerequisite extracted from #4434 and branch `romanett/fluent-node-behaviors`, both of which had independently worked around the same synchrony. Happy to open one if maintainers want the ADR trail. ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. ### What was actually run locally `UA.slnx` builds clean. Post-merge on **net10.0**: `Opc.Ua.Server.Tests` 5020/0 and `Opc.Ua.SourceGeneration.Core.Tests` 3813/0. Pre-merge the same run plus `Opc.Ua.Di.Tests` 425/0, `Opc.Ua.Robotics.Tests` 627/0, `Opc.Ua.ISA95.Tests` 136/0, `Opc.Ua.Positioning.Tests` and `Opc.Ua.Robotics.Intent.Tests` were all green; `MinimalRobotServer` was also started directly to confirm the hosted robotics path comes up. Three caveats, so the unchecked box above is not a mystery: the full suite was **not** run against .NET Framework locally; `Opc.Ua.OpenUsd.Tests` has one pre-existing failure on this machine (`StructuredCartesianCoordinatesAreAccepted` expects `"1.5"` and gets `"1,5"` — a German-locale decimal separator, unrelated to this change); and one pre-existing `CA1861` warning comes from `NodeSetImportIntegrationTests.cs`, which is byte-identical to `master` on this branch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7411654c8d |
Complete OPC UA Part 11 Historical Access (#4390)
# Description Completes the OPC UA Part 11 Historical Access work requested by #4387 and the historian framework integration requested by #4400. The conformance/evidence catalog covers all 37 released UACore 1.05 Historical Access profiles: 15 Server facets and 22 Client facets, together with the standard aggregate functions and their conformance units. Server facets are advertised only when the resolved providers expose their required interfaces and capabilities. ## Server and provider support - Completes raw, modified, at-time, processed, annotation, structured, and event history. - Dispatches best-effort, bulk, and transactional updates with aligned statuses, old values, diagnostics, and rollback state. - Supports mixed history-update detail types and batched structured/annotation operations. - Uses composite identities and exclusive cursors for same-timestamp structured, modified, and event values. - Captures live data and events, installs historical configuration objects, and emits typed audit payloads. - Derives aggregate monitoring filters from per-node historian capabilities and rejects unsupported processed aggregates before provider dispatch. - Supports tri-state `Historizing`: set, clear, or preserve provider-owned state. ## Client support - Exposes modified values together with `ModificationInfo`. - Adds event-history reads and event insert/replace/update/delete operations. - Adds structured and annotation batching with strict response validation. - Shares continuation handling and release-on-abandon across read families. - Accepts inherited standard event fields rooted at event subtypes. ## Review hardening - Registers DI-selected historians before node-manager reconciliation and capability publication, while preserving provider ownership. - Uses committed Raft read barriers for linearizable `TryGetAsync` and `ScanAsync`. - Keeps claimed continuation state immutable until a fresh successor is saved, restoring the original after transient failures. - Uses exact-incarnation compare-and-delete so delayed cleanup cannot remove a restored same-ID continuation. - Cleans up indeterminate initial and replacement saves, and propagates shutdown cancellation through cleanup resolution. - Preserves legacy continuation envelopes/cursors and distinct annotation parent/property identities. - Deletes every structured entry at a requested timestamp and uses the complete modified-history ordering tuple. - Shares native/fallback at-time and AnnotationCount calculations, including reverse endpoints and cross-framework fractional intervals. - Primes modified aggregate items before buffered live delivery and deduplicates history/live overlap using provider-specific identities. - Keeps committed Modify results successful while reporting priming failures through the monitored item's notification queue. - Keeps protected-notification state live-only for queues larger than one. Queue size one retains the newest notification without pinning errors or transferring priority during resizing. Durable restore retains ordinary definitions, last values/errors, and raw queues without recreating priority. - Isolates aggregate calculation, modification preparation, and historical/live handoff in `AggregationFilterHandler`, retaining existing provider/factory wiring and supported past-start behavior. The larger-queue protection policy is unchanged pending the open review discussion. - Uses one `IMonitoringFilter` owner for protocol filters or the aggregation handler, eliminating parallel effective-filter storage and retaining the current definition during preparation. The original client filter stays distinct. - Integrates current master, retaining custom monitored-item creation decisions and per-node history callbacks. ## Redundant-server alignment and samples - Adds a protected shared historian with immutable segments, manifest CAS publication, writer fencing, and restart recovery. - Persists portable continuation state before exposing it to clients and transfers ownership during mirrored-session takeover. - Rejects eventual-consistency and active/active multi-writer historian configurations rather than silently degrading. - ReferenceServer and ConsoleReferenceClient demonstrate discovery, paging, raw/modified/at-time/processed reads, annotation/structured CRUD, event capture, and event CRUD. - RedundantServer and RedundantClient demonstrate active/passive history, continuation recovery after replica termination, and post-promotion writes. - Updates Historical Access, High Availability, profile, migration, and sample documentation. ## Compatibility - New 2.0 collection boundaries use `ArrayOf<T>` and public byte payloads use `ByteString`. - Removes the legacy public virtual `SamplingGroupManager.CreateMonitoredItem` / `ModifyMonitoredItem` entry points and protected creation factory, as agreed in review. Migration guidance points to the monitored-item manager and construction-decision APIs. - Retains established persisted formats and current write layouts. Retired interim notification-state formats (sample v2 / shared v4) are rejected, as agreed in review. - Keeps the historian integration compatible with .NET Framework and NativeAOT. ## Validation ### Latest CI source repair `73c619435` forwards the existing cancellation token at both predefined-node creation calls in the PR-added History event fixture. The merged lifecycle overload exposed these two CA2016 errors, which blocked History jobs, the solution build matrix, and CodeQL on GitHub and Azure. The exact compiler failure was reproduced locally; the repaired fixture passes 4/4 integration cases on net10.0 and 4/4 on net48 with clean scoped diagnostics. No assertions, tests, coverage thresholds, or CI configuration were weakened. The repaired History jobs on Linux/macOS and all four Azure solution builds now pass. The Windows all-TFM GitHub job instead lost its hosted runner's connection; no build log was recoverable, and GitHub recorded a runner-loss annotation rather than a compiler/test diagnostic. One unchanged retry was requested. Remaining checks are monitored with `gh pr checks --watch --fail-fast --interval 30`; CI is not yet reported as green. ### Latest base integration `bd52e5fea` merges upstream `master` at `da7412c42`. The lifecycle conflict retains upstream's shared cancellation-aware registration helper and the historian behavior. The hosted-historian fixture forwards its existing token to the new lifecycle overload. GDS project renames and all three AOT test hosts are retained, with their project references resolving after the merge. | Integrated scope | net10.0 | net48 | | --- | --- | --- | | Server lifecycle, fluent authoring/import, historian, monitored-item and hosting regressions | 732 passed | 732 passed | | NodeState lifecycle regressions | 216 passed | 216 passed | | Automatically merged Raft client fixtures | 28 passed | 28 passed | No selected tests failed or were skipped. The manually resolved files pass scoped formatting/analyzer checks. One CA1861 warning remains in the incoming `NodeSetImportIntegrationTests.cs`; that file is byte-identical to upstream master and was not rewritten or suppressed during integration. ### Latest feedback follow-up `102954eaa` replaces split filter references with the shared `IMonitoringFilter` contract implemented by protocol filters and `AggregationFilterHandler`. Targeted server regressions pass **103/103 on net10.0 and 103/103 on net48**; scoped formatting/analyzers are clean, and the updated XML-documentation inventory has zero gaps across 188 non-generated PR C# files. `93c5b5db8` renames `AggregationFilterHandler`, makes it the owner of the server-revised aggregate filter while preserving the original client filter, and removes the retired interim notification-state readers. Server regressions pass **101/101** and shared-store regressions **64/64**, on both net10.0 and net48, with no failures or skips. Runtime-file formatting/analyzers are clean. `5ccf45630` completes the public/internal XML-documentation sweep. The Roslyn inventory finds zero undocumented eligible declarations across all 186 non-generated PR C# files; the 110-file documentation slice changes only XML comments and spacing. `f17765475` applies the three requested Markdown edits. ### Earlier feedback follow-ups `a53950737` removes single-slot notification pinning and extracts aggregate implementation from MonitoredItem. Targeted sampling, aggregate modification, history/live handoff, queue/lifecycle, restore, and serialization cases pass **101/101 on net10.0 and 101/101 on net48**, without failures or skips. Scoped formatting/analyzers are clean; the focused review found no significant issues. The final formatted monitored-item fixtures also pass 45/45 on net48. The preceding `b21555568` removes the legacy sampling API bridge and durable notification-priority state. Its targeted Server cases passed 97/97 and shared subscription-store cases passed 64/64 on each framework. The broader evidence below remains pinned to its original tested commit, not attributed to these follow-ups. ### Earlier integrated-implementation validation Validated implementation commit: **`3efae597f`**. The following commit, `f6dbefb1c`, only adds two explanatory comment lines for the final standards review; it changes no executable code. | Scope | net10.0 | net48 | | --- | --- | --- | | Server | 5,187 passed; 56 skipped | Affected surface: 2,160 passed; 1 reproduced baseline failure; 56 skipped | | Client | 2,202 passed; 2 skipped | 2,204 passed; 5 skipped | | History integration | 519 passed; 21 skipped | 519 passed; 21 skipped | | Redundancy server | 641 passed | 641 passed | | Redundancy client | 124 passed | 124 passed | | Core shared-store regression suite | 23 passed | 23 passed | All six affected samples passed non-incremental Release/net10.0 builds with zero warnings and errors: ConsoleReferenceClient, ConsoleReferenceServer, RedundantServer, RedundantClient, MinimalBoilerServer, and PumpDeviceIntegrationServer. The published self-contained win-x64 NativeAOT executable is 116,924,928 bytes and has no CLR header or managed runtime dependency. Direct execution passed **HistoryAotTests 3/3** and **RaftAotTests 2/2**. Final standards and specification reviews found no confirmed outstanding high/medium functional gaps or hard standards violations. The remaining constructor-purpose clarification is addressed in `f6dbefb1c`. ### Reproduced baseline exceptions The net48 hosted-startup timeout also reproduces in a pristine worktree at the previous PR head, `24cf1416d`, in `RegisterPostStartRegistriesWiresHistorianAndAliasStoresOnPlainServerAsync`, with the same subsequent certificate-leak assertion. The corresponding renamed case fails before startup in the reviewed branch as well. This is reported separately from the net48 affected-surface results, not counted as a passing full Server suite. The net48 matrix selected the historian, aggregate, monitored-item, fluent, and continuation surfaces and excluded the Hosting namespace. `DurableDataValueQueueVerifyReferenceBatchingAsync` failed at item 1000 in the net48 affected-surface run and passed its single isolated rerun. The same failure at the same assertion reproduces on pristine `849e16acc`; the test, sample queue, queue factory, and batch persistor are unchanged. The original failure remains in the table rather than being erased by the rerun. ### CI source repairs - `4be7eb8f7` keeps diagnostics/configuration managers out of distributed address-space replication. Those managers inherit the source interface, but their live server state and redundancy metadata must remain replica-local. The ownership regression passes on net10.0 and net48, and all nine redundancy sample CI tests pass locally. - `d17f467b3` completes the sample startup repair: core managers are also replica-local; the startup banner no longer advertises readiness before initialization; the producer starts after distributed services; and native Raft startup no longer forces disruptive extra elections. The full sample suite passed 9/9, with initialized `RedundancySupport=Hot`, and focused Raft/address-space regressions passed 8/8 and 3/3. - `81b0b72be` moves the unchanged distributed-historian NativeAOT scenario into a smaller, always-run companion executable to bound Apple's linker input. All 137 AOT tests remain present; the moved test and helpers are unchanged. GitHub and Azure publish/run both native executables, with separate result directories where they share a job. The companion passed actual Windows NativeAOT execution. Linux and Intel macOS CI passed both executables, but this split alone did not fix ARM64 macOS linking. The remaining historian graph is rooted by ReferenceServer startup, not just the relocated test. No test exclusion or speculative linker setting was added to conceal it. - `8a5842913` isolates the independent MCP test graph into an always-run native host. The existing MCP source and assembly leak hooks are linked unchanged, and evaluated compiler inputs prove exactly one owner for every test source: 133 main + 3 MCP + 1 historian = 137 declarations. All three hosts run on every AOT platform. The main Windows native object is 9.35% smaller; both changed publishes had zero diagnostics. MCP passed 3/3; the identical main executable passed 133/133 on one unfiltered followup after two initial endpoint-discovery connection failures, which remain recorded in the local evidence. - The previous Intel macOS job successfully published and executed its native tests, then failed on artifact-upload DNS (`ENOTFOUND`). No upload gate, shared environment setting, or cache configuration was disabled. The first repair round also encountered the unrelated `MetricsAreEmittedForChannelLifetimeAsync` timing failure on macOS Core. Neither that test nor channel-manager code is changed by this PR, and its assertions remain intact. It passed unchanged on the final repair head. On `d17f467b3`, the Linux and macOS redundancy sample jobs pass; the remaining failure was ARM64 Apple's `too many large addends` assertion. The verified upstream relocation-anchor fix (dotnet/runtime#124721, backport dotnet/runtime#132171) is not present in the shipping ILCompiler 10.0.11. An experimental multi-module probe failed and was rejected, not added to CI. On `8a5842913`, [`aot-macos-latest`](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/34102158680/job/101678934057) successfully publishes and executes all three native hosts on ARM64: 136 main runtime cases, 1 historian case, and 3 MCP cases, with no skips. The previous Apple linker blocker is resolved. Linux NativeAOT also passes. No assertion, test, shared environment setting, cache, or required gate was weakened. The same CI run reports a separate macOS Sessions teardown failure: all 662 executed tests pass, but the global leak assertion detects one undisposed certificate. The MCP partition changes no runtime, Sessions, or test-fixture code. A single unchanged diagnostic rerun is queued once GitHub permits rerunning that job; the failure is not suppressed or counted as green. Other CI checks are still pending. ## Related issues - Fixes #4387 - Fixes #4400 ## Checklist - [ ] I have signed the CLA and read the contributing guide. - [x] I have added regression tests and directly related documentation. - [x] I have retained working reference and redundant-server sample wiring. - [x] Final validation and review evidence is complete, including the baseline exceptions above. - [ ] All required CI checks pass; the fail-fast rerun for `73c619435` is pending. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9dbd9527-d818-47a0-984c-fbb1472d3d6b |
||
|
|
90840b3e48 |
Register HTTPS/WSS transport bindings in ConsoleReferenceServer (#4436)
## Description
The refserver container fails to start with `No OPC UA transport
listener is registered for endpoint scheme 'opc.https'`.
`StandardServer` now validates that every scheme configured in
`BaseAddresses` has a registered transport binding, but
`ConsoleReferenceServer`'s config files
(`Quickstarts.ReferenceServer.Config.xml`,
`Ctt.ReferenceServer.Config.xml`) list `opc.https`/`opc.wss` base
addresses while the sample never registered those bindings on the server
— it relied on the `opc.tcp`-only default.
### Fix
- `UAServer<T>.CreateServerWithTransportBindings()`: shared helper (used
by both `Create()` and `StartAsync()`'s lazy-creation fallback) that
assigns `Server.TransportBindings` before start, so both call sites
can't drift out of sync.
- `CreateTransportBindings()`: builds the registry by reusing the
existing production DI extensions
(`AddOpcUa().AddOpcTcpTransport().AddHttpsTransport().AddWssTransport()`)
via a small `ServiceCollection`, instead of duplicating
factory-registration logic.
### Lifecycle
- The backing `ServiceProvider` is kept alive in `m_transportServices`
for the life of the registry (not disposed immediately after resolving
it, which would invalidate factories relying on DI-owned services) and
is disposed in `StopAsync()` once the server has stopped.
- `CreateTransportBindings()` disposes any stale provider before
building a new one, guarding against repeated `Create()`/`StartAsync()`
calls without an intervening `StopAsync()`.
```csharp
Server = CreateServerWithTransportBindings();
// ...
private ITransportBindingRegistry CreateTransportBindings()
{
m_transportServices?.Dispose();
var services = new ServiceCollection();
services.AddOpcUa().AddOpcTcpTransport().AddHttpsTransport().AddWssTransport();
m_transportServices = services.BuildServiceProvider();
return m_transportServices.GetRequiredService<ITransportBindingRegistry>();
}
```
## Related Issues
## 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.
- [ ] 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.
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes #4435
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
|
||
|
|
1af191ec26 |
Drop the .Common suffix from the three GDS projects (#4438)
# Description Every other companion specification in the stack ships as `<Family>`, `<Family>.Client` and `<Family>.Server` — `Opc.Ua.Di`, `Opc.Ua.ISA95`, `Opc.Ua.Positioning`, `Opc.Ua.Robotics`, `Opc.Ua.Vision`, `Opc.Ua.WotCon`, `Opc.Ua.XRegistry`, and `Opc.Ua.Lds.Server` without a family package. GDS was the one exception, carrying a `.Common` suffix that named nothing. | Before | After | | --- | --- | | `src/Opc.Ua.Gds.Common` | `src/Opc.Ua.Gds` | | `src/Opc.Ua.Gds.Client.Common` | `src/Opc.Ua.Gds.Client` | | `src/Opc.Ua.Gds.Server.Common` | `src/Opc.Ua.Gds.Server` | ## No source changes were needed `RootNamespace` was already the final name in all three projects — the namespaces have always been `Opc.Ua.Gds`, `Opc.Ua.Gds.Client` and `Opc.Ua.Gds.Server`, and only `AssemblyName` and `PackageId` disagreed. Nothing that compiles against these assemblies changes a `using` directive; the diff outside the moves is entirely ids, paths and prose. Directories were moved with `git mv`, and every one of the 73 file moves is detected as a rename at 100% similarity (bar the handful that also had a name in their text), so `git log --follow` and blame survive. ## Consumer impact The package ids change, which is visible to anyone consuming the NuGet packages: | 1.5.378 package | 2.0 package | | --- | --- | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds` | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Client.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds.Client` | | `OPCFoundation.NetStandard.Opc.Ua.Gds.Server.Common` | `OPCFoundation.NetStandard.Opc.Ua.Gds.Server` | `docs/migrate/2.0.x/packages.md` gains a renamed-packages section — it is the document that undertakes to cover NuGet renames, and had no GDS entry — and the migration skill's package table gains the two rows a 1.5.378 consumer needs. That is the only hand-written content in this PR; everything else is mechanical. Assembly names follow the package ids, so the section also notes that binding redirects, trimming descriptors and signing manifests naming the files directly have to be updated. ## Build infrastructure Everything naming the projects or their output moves with them: - `UA.slnx` and `tools/MigrationAnalyzer.slnx` (both re-sorted — the project lists are alphabetical, and `Gds` now sorts before `Gds.Client`). - `.azurepipelines/expected-packages.txt` — 3 ids, also re-sorted for the same reason. - `.azurepipelines/signlistDebug.txt` and `signlistRelease.txt` — 18 paths each. Their internal ordering is left alone: that group was already unsorted, and reordering a signing manifest is diff noise. - `samples/Reference/ConsoleReferenceServer/Dockerfile`, the two metapackage nuspecs, the analyzer nuspec's package dependencies, and 8 `.csproj` references across tests, samples and tools. `preview-pack.slnx` is generated from `UA.slnx` by `generate-slnx.ps1`, so there is nothing static to edit. Nothing under `.github/workflows`, and no `.ps1`/`.yml` in `.azurepipelines`, names these projects. The migration analyzer's shim tree mirrors the source project layout by its own documented convention, so `Gds.Client.Common/` becomes `Gds.Client/` and its readme table follows. ## Deliberately not changed Prose naming past work: `plans/23` still records that its deferral happened during the "GDS Client.Common modernization", which is the name that effort had. File *paths* in the same document were updated. ## Validation `net10.0`: - `UA.slnx`: 0 errors. 14 warnings, all pre-existing CA1861/CA1850/CA1307 in `XRegistry`, `Types.Tests` and `WotCon.Tests` — none in anything this PR touches. - Assemblies build as `Opc.Ua.Gds.dll`, `Opc.Ua.Gds.Client.dll`, `Opc.Ua.Gds.Server.dll`, with no `*.Common.dll` produced anywhere. - `Opc.Ua.Gds.Tests`: 1,116 passed, 0 failed, 54 skipped. - `Opc.Ua.MigrationAnalyzer.Core.Tests`: 10 passed, 3 skipped. - A repo-wide grep for the old names returns only the intentional old → new mappings in the migration docs. ## Checklist - [x] Existing tests pass - [x] No new tests — this is a rename with no behaviour change - [x] Consumer-visible package rename documented in the migration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |