mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-20 00:31:32 +08:00
fix/encoder-stack-depth-json-flake
33 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
ac8dc531ee |
Source generator, ISA-95 Job Control and DI changes for companion SDKs built on referenced models (#4577)
## Summary First of four stacked PRs that contribute complete implementations of the Machinery (OPC 40001-1 with Job Management, Result Transfer, Process Values and Energy), Pumps (OPC 40223) and Scales (OPC 40200, with PackML OPC 30050) companion specifications — tracked in #4576. It follows the split used for Robotics (#4123–#4127): this PR carries only the changes to **existing shared code** those SDKs need, and adds no project. Each change stands on its own and is covered by tests in this PR; the companion models are what surfaced them. Rebased onto master after #4569. The one conflict was in `NodeSetToModelDesign.cs`: #4569's shared `AssignSymbolicIds` already makes `Import` and `GetImportedSymbols` derive the same ids, and `NormalizeParentNodeId` moves into its pre-pass. The Copilot review is addressed in the last six commits (see "Review follow-up"). ## Changes ### Source generator (`tools/Opc.Ua.SourceGeneration.Core`) - **VariableType data type across assemblies.** A model that consumes a dependency through a referenced assembly's `[ModelDependency]` payload lost a VariableType's `DataType`/`ValueRank`, so `GetNodeStateClassName` dereferenced null and reported `MODELGEN003`. First hit: DI's `LifetimeVariableType` as used by Machinery. - **Method declaration ↔ method state identity.** A consumer re-declaring an inherited method referenced an `OwnerType_MethodMethodState` class the producer never generated. First hit: OPC 34100 ECM re-declaring the DI `LockingServices` methods. - **Browse names and reference types of inherited children.** An inherited child was emitted with `ReferenceTypeId = NodeId.Null` (invisible to a filtered Browse over the wire) and, where it lives in another namespace, with the declaring model's namespace. When the carried namespace is one the consuming model does not declare, the generated code names it by URI instead of emitting an empty `GetIndexOrAppend()`. - **Payload compatibility.** The three additions above travel in a length-prefixed, versioned declaration trailer after the existing trailers, so the node and child records keep their layout: an older reader ignores the capability bit and stops after the trailers it knows, and a payload without declarations is byte-identical to before. - **Symbolic ids of children listed before their parent.** Every `ParentNodeId` is normalized before any symbolic id is derived. No NodeSet in the repository has such an ordering today, so no generated symbol changes; OPC 30050 PackML (later in the stack) declares seven method `InputArguments` before their methods and failed with `MODELGEN025`. - **Methods overridden with another method-state class.** The subtype declares its own field and property for such a method, but `GetChildren` and `RemoveExplicitlyDefinedChild` skipped every overridden child and the supertype's field stays empty, so the method was missing from every instance (not browsed, no per-instance NodeId, not callable). The existing `MethodTypeOverride` test model has the gap; since #4555 generates a method with inline arguments as `MethodState`, OPC 40001-101 `ResultTransferType.GenerateFileForRead` hits it. It also affects models already in the repository: DI `SoftwareFolderType.Delete`, ISA-95 V2 `JobOrderReceiver.Update`, WoTCon `WoTDocument.Validate` and XRegistry `Group.Delete` / `Resource.Delete` now appear on their instances; the DI, ISA-95, WoTCon and XRegistry tests pass. ### ISA-95 Job Control (`src/Opc.Ua.ISA95.Server`) - **`Isa95JobControlV2Binder`** — the V2 receiver/response-provider binding moves out of `Isa95NodeManager` so OPC 40001-3 JobManagement can bind the same eleven verbs on its own instances instead of copying them. `Isa95NodeManager` behaviour is unchanged. - **`Isa95AddressSpaceOwnership`** — a second registration that would load the ISA-95 models fails immediately and names the current owner, as `DiAddressSpaceOwnership` already does for DI. - **`IIsa95JobResponseCatalog`** — a source for `JobOrderResponseList`, which the two state-filtered request methods cannot express. - **`IIsa95JobResponseChangeSource`** — a change stream of received responses, and of responses the provider drops after `ResponseRetention`; a response changes neither the order's state nor the order catalog, so the existing streams cannot report it. Both interfaces are implemented by `InMemoryIsa95JobControlProvider`, registered by `AddInMemoryIsa95JobControlProvider`, and count in `AddIsa95Server`'s provider detection and mixed-provider rejection. ### Device Integration (`src/Opc.Ua.Di.Server`, `src/Opc.Ua.Di.Client`) - **`IDiPostSetupContext.GetService<T>()`** — an optional lookup next to `GetRequiredService<T>()`, so a companion SDK can probe for a seam the server may not have filled without catching `InvalidOperationException`. The context still exposes no raw `IServiceProvider`. - **`DiNodeManager.ConformanceUnits` / `ServerProfiles` are virtual**, so a companion subclass appends the units its own model satisfies. - **DI client browse enumerators follow `BrowseNext`.** `DiTopologyClient.BrowseChildrenAsync` and `DiDeviceClient.BrowseFunctionalGroupsAsync` discarded the continuation point and returned an incomplete list from a paginating server. A bad status for the browsed node still means "no children" and a failed service call propagates; an outstanding continuation point is released whenever the enumeration stops early. ## Review follow-up | Copilot finding | Commit | |---|---| | Payload additions break older readers (both High) | `fix(sourcegen): move the dependency declarations into a trailer` | | Browse failures reported as empty, broad exception filter (Medium ×2); missing pagination tests (Low ×2) | `fix(di-client): let a failed browse call propagate` | | New provider facets missing from detection (Medium) | `fix(isa95): count the response facets as Job Control providers` | | Second review: continuation point kept after a failed BrowseNext | `fix(di-client): release the continuation point after a failed BrowseNext` | | Second review: purged responses not reported | `fix(isa95): report dropped job responses on the response change stream` | | Second review: missing V2 namespace cast to 65535 | `fix(isa95): reject a missing Job Control V2 namespace in the binder` | ## Points for the reviewer - `GetService<T>()` adds a member to a public 2.0 interface. The only implementation is the internal `DiPostSetupContext`; an external implementer would have to add it. - `DiDeviceClient.BrowseFunctionalGroupsAsync` used to throw for a bad status of the device node; like `DiTopologyClient` it now yields nothing. - The commits are self-contained and ordered; reviewing commit by commit is the easiest route. ## Testing - New and extended tests: `ModelDependencyV1Tests`, `NodeStateGeneratorTests`, `NodesetIdentifierSidecarTests`, `InMemoryIsa95JobControlProviderV2Tests`, `Isa95ServerBuilderTests`, `Isa95JobControlV2BinderTests`, `DiDeviceClientTests`, `DiTopologyClientTests`, `OpcUaServerDiBuilderExtensionsTests`. - `UA.slnx` builds for net472, net48, netstandard2.0/2.1 and net8/9/10 with SDK 10.0.401 and no warnings. - Tests on net10: SourceGeneration.Core 4266 (8 skipped), SourceGeneration 263, ISA95 144, DI 433; the first three also on net8. The changed projects build for net48 and netstandard2.0 without warnings. - Changed-line coverage (`check-coverage.ps1`): 91.1 % (594 of 652 lines). - The companion stack on top (#4578, #4579, #4580) is rebased onto this head. - Fork CI, full PR matrix incl. macOS, for this head (running): https://github.com/biancode/UA-.NETStandard/actions/runs/36558506011. CodeQL: https://github.com/biancode/UA-.NETStandard/actions/runs/36558509242. The previous head (before the second review round) passed every Windows and Linux job: https://github.com/biancode/UA-.NETStandard/actions/runs/36542929585. |
||
|
|
442889cba5 |
Fix source generator audit findings; conform structure-field matrices and type metadata to OPC UA 1.05 (#4569)
# Description
This PR fixes a bug audit of the source generation projects
(`tools/Opc.Ua.SourceGeneration*`: model loading, NodeSet import, all
emitters, the Roslyn hosts and packaging). It also fixes a max-effort
code review of those fixes, and a check of every spec-relevant change
against the OPC UA 1.05 text.
- **Scope:** about 85 audit findings, 48 review findings and 32
spec-grounding items were addressed. Each fix has a regression test that
fails without it.
- **Wire change:** it includes one deliberate, stack-wide wire change:
**structure-field matrices now follow OPC 10000-6 5.2.5 Table 28**.
Most changes are internal correctness fixes. The sections below list
every change a user can observe: wire formats, generated code and API,
generated address-space content, diagnostics and runtime behaviour.
## Behaviour / user-facing changes
### Wire format (encoders)
- **Binary inline matrices** (OPC 10000-6 5.2.5 Table 28).
- **Layout:** a multi-dimensional *structure field* is encoded as
`Int32[] Dimensions`, then the values **without a length prefix**, with
at least 2 dimensions.
- **Empty and null:** any dimension <= 0 means no values follow, and a
null matrix is `-1`. An empty matrix is written as `[0,0]`.
- **Compatibility:** this is **not compatible with earlier builds of
this SDK** for matrix fields. It applies to generated types,
`[DataType]` types, `Structure`, client ComplexTypes and
`WriteEncodeableMatrix`.
- **Unchanged:** Variant matrices (5.2.2.16) keep their old encoding.
- **Declared rank decides matrix vs array.** A 1-D `MatrixOf<T>` in an
array field or a Variable value is written as an array again.
Matrix-declared fields go through the new public
`EncoderExtensions.WriteInlineMatrixValue`, which generated code also
uses. A non-empty 1-D value in a matrix field throws `BadEncodingError`.
- **Decoder hardening:**
- Before allocating, the decoder bounds all matrix dimensions against
overflow, `MaxArrayLength` and the remaining message length.
- The decoder rejects:
- a matrix with fewer than 2 dimensions;
- a populated matrix whose rank differs from the field's declared rank;
- a union SwitchField greater than the number of fields;
- an EncodingMask with unassigned bits set.
- **Encoder `MaxArrayLength`:** the encoder enforces `MaxArrayLength`
for inline matrices, as it already did for arrays.
- **XML structure fields** (5.3.1, 5.3.4, 5.3.5).
- **Fields:** `Structure` and client ComplexTypes write fields like
typed fields (`<A>1</A>`, array elements directly) instead of a Variant
body.
- **Matrix fields:** `Dimensions` and `Elements` go directly inside the
field element, with no `<Matrix>` wrapper.
- **Empty matrices:** written as nil, because XML dimensions must be
greater than 0.
- **Compatibility:** decoders still accept the old shapes.
- **Enum fixes:** enum array elements with value 0 are no longer
dropped, and enum array element names are fixed.
- **Other encoding fixes:**
- A null `DiagnosticInfo` structure field writes `0x00` instead of
nothing.
- A null scalar field in a runtime `Structure` is written as its default
instead of zero bytes, which used to shift every following field.
- A Variant holding an empty matrix encodes as an empty array instead of
throwing.
- `Variant.Copy` keeps the matrix identity of null and empty matrices.
- **NodeSet export and the DataContract surrogate** write Variable
values with the Variant rules again. A null or empty `MatrixOf` variable
no longer produces invalid XML or unreadable NodeSets.
- **`JsonEncoder.CanOmitFields`** is `true` only for Compact encoding.
Verbose and RawData JSON include default-valued fields.
### `[DataType]` source-generated types
- **Default-value handling** (OPC 10000-6 5.4.1, Table 1).
- **Encode:** a field is omitted only when its value *and* its declared
default are the type's default (0, false, null). Fields with a
non-default initializer or constructor default, and empty strings, are
always written, so configuration files get slightly larger.
- **JSON decode:** a field missing from JSON decodes to the type
default.
- **XML decode:** a field missing from XML keeps its declared default,
which older configuration files rely on.
- **Why:** this fixes silent round-trip loss, for example
`SamplingInterval = 0` read back as `-1`.
- **Nested `[DataType]` types:** they are now generated inside their
containing types. Without an explicit id, their default TypeId and
XmlName are qualified (e.g. `s=Outer.Foo`); **wire identity change** for
nested types. Colliding activator names get a `_2` suffix.
- **Activators:** they are `internal` when the type, or any containing
type, is not public (was CS0060).
- **Matrix properties:**
- `MatrixOf<T>` properties use the inline matrix encoding.
- Matrices of non-sealed structures, or with
`StructureHandling.ExtensionObject`, are written as ExtensionObjects.
- Their published ValueRank is 2, because the rank can't be known from
`MatrixOf<T>`.
- **Published StructureDefinitions:**
- They include base-class fields.
- Nullable properties are no longer marked optional.
- Inline structure fields name their concrete type.
- **`[Flags]` enums:** they are published as plain enumerations with
their member values, which matches how they are encoded.
- **Other generated code:**
- `Clone` deep-copies arrays and matrices of structures, Variants,
ExtensionObjects and DataValues.
- `IsEqual` and `GetHashCode` are consistent: EncodingMask, active union
member and NaN handling agree.
- Pooled `Reuse()` restores the freshly constructed state through the
new `protected virtual ResetForReuse()`, and returns an instance to its
runtime type's pool.
- **Enum capture:** enum member values are captured culture-invariantly.
Negative values were lost on cultures that use U+2212 as the minus sign.
### Model-driven generator: generated code, API and file names
- **Removed API:** generated `*_Placeholder` proxy accessors (e.g.
`PowerTrainTypeClient.GetMotorIdentifier_PlaceholderAsync`) and fluent
`X_Placeholder()` accessors are removed; they could never resolve
anything.
- Proxy accessors now look children up by BrowseName with the right
namespace.
- `RoboticsClient` finds motors and gears by type and drives via
`IsDrivenBy`. Its topology snapshots used to be empty.
- **Renamed files:** generated files for a bound `[NodeManager]` are now
named `{Namespace}.{Class}.NodeManager.g.cs` (also `.Factory` and
`.FluentBuilders`).
- **Generated NodeState code:**
- Attributes are set through `baseState`/`nodeState` locals, so children
named like base attributes (e.g. `DataType`, `EventNotifier`) compile.
- Children that hide base members are declared `public new`, using names
reflected from the runtime.
- `global::` qualification is used throughout.
- **Proxies and fluent builders:** a proxy whose supertype is excluded
(e.g. Draft) derives from the nearest emitted ancestor. Fluent wrappers
and NodeState factories fall back the same way; they used to fail with
CS0246.
- **Derived structures:**
- The optional-field `EncodingMask` continues across inheritance.
- A derived structure over a base type from another model no longer
relies on `base.ResetForReuse`.
- **Default values:**
- NodeId, QualifiedName and ExpandedNodeId defaults resolve by namespace
URI (`context.NamespaceUris.GetIndexOrAppend`) and server URI.
- StatusCode, negative enum, `double`/`float` (shortest round-trip
literal) and escaped string defaults now compile.
- **Event records:**
- Vendor event records have `Decoder.GetStandardFields(NamespaceTable)`,
and their browse paths use the model's namespace.
- A field whose namespace is unknown to the session no longer matches a
namespace-0 field.
- Companion `StandardFields` is `[Obsolete]`.
- Vendor simple subtypes such as `Percent : Double` map to their
built-in type.
- **Other fixes:**
- `MethodState.CallAsync` passes the real `objectId` to synchronous
`OnCallMethod2` handlers; it used to pass `NodeId.Null`.
- Enumeration fields with an explicit `Identifier` keep it, instead of
taking a trailing `_<digits>`.
- Explicit NumericIds are no longer reused by automatic numbering.
- Dependencies are loaded in topological order.
### Generated address-space content
- **Arguments:**
- Methods without arguments no longer get empty
`InputArguments`/`OutputArguments` properties (OPC 10000-3 5.7).
- About 2,660 automatically assigned namespace-0 NodeIds of 15000 and
above shift. Every NodeId in `StandardTypes.csv` is unchanged.
- **Permissions:** type nodes and method declarations on types now carry
the model's `RolePermissions`/`AccessRestrictions`.
- **Restored nodes:** NodeSet import keeps nodes that were silently
dropped before:
- `NamespaceMetadata` objects and the NodeSet's own TypeDictionary
variables;
- ISA95 placeholders linked only non-hierarchically;
- instances whose parent is in another namespace.
- **Import details:**
- ValueRank > 1 without ArrayDimensions keeps its rank.
- `svr=` indexes map correctly.
- Invalid MinimumSamplingInterval values import as -1, not 0.
- Localized texts prefer the invariant or English translation.
- **StructureDefinitions:**
- They include inherited fields, including for subtypes without their
own fields (e.g. `AnonymousIdentityToken` now publishes PolicyId).
- The StructureType kind covers inherited AllowSubTypes fields.
- Unions ignore IsOptional.
- StructureFields never publish ValueRank 0; a OneOrMoreDimensions field
without dimensions becomes rank 2.
- Fields with ValueRank -2/-3 are published as scalar BaseDataType,
which is what is on the wire.
- **OptionSets:**
- Implicit fields get the next free bit.
- `OptionSetValues` uses null for undefined bits instead of "Reserved",
and structure OptionSets publish all their bits (no 32/96-bit cap).
- Numeric OptionSets are limited to their base type's width, and UInt64
bit 63 works.
- **Schemas:**
- Structures with inline-matrix fields are left out of the `.bsd`
(5.2.5).
- In the BSD, Variant-encoded and non-structure subtyped fields are
described as `ua:Variant`.
- In the `.xsd`, matrix fields are typed `ua:Matrix` with
`Dimensions`/`Elements`, and `EncodingMask` is required.
### NodeSet / model input handling
- **Model selection:**
- The newest NodeSet of a model is chosen by `ModelVersion` (SemVer 2.0
precedence), then PublicationDate. The `Version` attribute, which Annex
F says is not for comparison, is only a tie-break.
- The order is total and does not depend on input order; referenced
assemblies use the same rule.
- **Default names:**
- Invalid default Name/Prefix values are repaired (`201301ISA95` becomes
`_201301ISA95`, `class` becomes `class_`).
- Valid names are unchanged.
- Sanitising no longer depends on the OS.
- **`ModelUri` metadata** that doesn't match the NodeSet raises a
warning instead of silently losing the model.
- **Input detection:**
- Only XML files whose root is `ModelDesign` or `UANodeSet` are treated
as models.
- A malformed NodeSet stays a NodeSet input, so it no longer breaks the
ModelDesign pass.
- **`IdentifierFile` paths:** they are normalized (`./`, `../`), and the
suffix match requires a folder boundary.
- **Dependency payloads** (`ModelDependencyV1`) carry field IsOptional,
AllowSubTypes, ArrayDimensions and the producer's namespace tables in
skippable trailers, so old and new readers stay compatible.
### Roslyn host, diagnostics, packaging
- **New diagnostics:**
- MODELGEN036: `[NodeManager]` target not supported (nested, generic,
file-local or global namespace).
- MODELGEN037: `[DataType]` target not supported (struct, generic,
abstract, file-local or global namespace).
- MODELGEN038: the definition of an encodeable base type can't be found.
- **New validator errors:**
- a structure with both optional and subtyped fields (OPC 10000-6 F.13);
- multi-bit or duplicate OptionSet masks.
- **Diagnostic locations:** `[DataType]`/`[NodeManager]` diagnostics
point at real source locations again, so `#pragma` and `.editorconfig`
apply. MODELGEN010 is the exception.
- **Incremental caching:** pipeline values compare by value, so
unrelated edits no longer regenerate the whole model or all
`[DataType]`s.
- **`ModelSourceGeneratorIgnore`** on a ModelDesign input is honoured;
the design still resolves as a dependency.
- **Cancellation:** a cancelled generation run propagates
`OperationCanceledException` instead of reporting a MODELGEN003 error.
- **Stack package:** the `Stack` NuGet package now ships
`build/OPCFoundation.NetStandard.Opc.Ua.SourceGeneration.Stack.props`,
so `StackSourceGeneratorMode` and `StackSourceGeneratorExclude` reach
the compiler.
## Related Issues
- No tracking issue. The audit, review and spec-grounding reports are
available on request. Contains the matrix wire-format change discussed
for D-5 (OPC 10000-6 5.2.5 Table 28).
## 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.
(`docs/SourceGeneratedDataTypes.md`, `docs/NodeManagers.md`, generator
readme)
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings. (`UA.slnx` 0 warnings / 0 errors on net48 and
net10.0)
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. The
affected suites were run on net10.0: SourceGeneration Core/Roslyn/Stack,
Types, Core.Encoders, Encoders.Fuzz, Client, Client.ComplexTypes,
PubSub, Core, Server, GDS, Configuration, Di, Robotics, ISA95,
Positioning, OpenUsd, XRegistry. net48 was built; its test legs, and the
post-rebase verification, run in CI.
- [ ] 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>
|
||
|
|
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>
|
||
|
|
9b8a3b1188 |
Preserve node initialization and copied child ownership (#4544)
# Description Preserve populated derived node children through optional initialization and give copied children independent ownership by their copied parent. - Track initialization from a source in `NodeState` and guard generated optional-default recreation without removing the optional initialization hook. - Reuse `CloneChild` for dynamic children, generated typed children, and method argument properties. Preserve the clone root's existing external parent. - Add cross-model initialization/copy regressions, method-argument and nested-child ownership tests, and inherited alarm-subtree checks. This PR contains only the six files for this fix group. Certificate-store, runtime-type-loader, publish-queue and transport-exception fixes are separate work. ## Related Issues Fixes #4525. Fixes #4526. ## Validation Fresh focused Release/net10.0 checks on the final base: | Fixture | Passed | Failed | Skipped | | --- | ---: | ---: | ---: | | CrossModelInstanceTypeDefinitionGeneratorTests | 9 | 0 | 0 | | Types NodeStateTests | 227 | 0 | 0 | | Core StateTypesTests | 13 | 0 | 0 | All three test commands exited 0. `git diff --check` and the changed-file diagnostic check are clean. Regression proof was also executed in the isolated worktree: with the three production changes reversed, six generator cases failed with the missing derived Temperature child while three controls passed; two copy cases failed parent-identity assertions while the external-parent/absent-arguments control passed. The production fix was restored and the focused checks are green. The retained red runs preceded the later upstream synchronization; the affected production paths were unchanged by that synchronization. Windows win-x64 NativeAOT publish and native execution also passed: main 178/178, Historian 2/2, MCP 3/3, no skips, every invocation exit 0. These native runs preceded the final CTT-configuration/notes-only fast-forward; this PR's six files were unchanged. Local validation is intentionally change-scoped for this work, with the full matrix and repository-wide checks left to PR CI. Repository guidance and test assertions/timeouts were not modified. Local commands used a NuGet v2 source override and disabled audit only on the command line; CI feed/audit policy is unchanged. Earlier broad attempts are not claimed as a completed local gate: an unmodified-base durable restart timeout was reproduced before synchronization (the later durable run passed), and the net48 runner stalled after recording passing client tests. A direct client retry subsequently exited 0, but the full net48 matrix was not completed. Those results are not substituted for the focused evidence above. ## Checklist - [x] I have signed the CLA and read the CONTRIBUTING doc. - [x] I have added regression tests and demonstrated failure without the production fix. - [x] The focused tests pass; no new compiler diagnostics were reported by those builds. - [ ] Coverage and the full net48/net10.0 matrix are verified by PR CI. - [ ] All CI, CodeQL, and review feedback is addressed. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
02fb72e2d3 |
Support guid/opaque nodeids in NodeSet import (#4507)
# Description
`NodeSetToModelDesign.ImportNode` only stored Numeric and String NodeId
identifiers. Guid and Opaque identifiers (both valid per OPC 10000-3
5.2.2) fell through to an `Information` log line, so the node was
registered in `NodesByQName`/`NodesById` with no identifier at all and
code generation continued against a node whose identity had been
discarded.
The `ModelDesign` schema has no representation for Guid/Opaque
identifiers (`NodeDesign` exposes only `NumericId`/`StringId`), so the
import now fails loudly instead of emitting output that looks correct.
- **Importer**: the fall-through branches in `ImportNode` are replaced
with an `InvalidDataException` naming the NodeId, browse name and
identifier type. This matches the existing unsupported-input handling in
the file and surfaces through `ModelCompilation` as a generator
diagnostic.
- **Logger removal**: `m_logger` had no remaining use in
`NodeSetToModelDesign` and was removed along with its
`Microsoft.Extensions.Logging` import.
- **Tests**: `ImportUnsupportedNodeIdTypeThrowsInvalidDataException`
covers Guid, empty Guid and Opaque identifiers via an in-memory NodeSet.
- **Docs**: the source generator readme states the NodeSet2 identifier
restriction.
```csharp
else
{
// The model design can only carry numeric and string identifiers.
// Guid and Opaque identifiers (OPC 10000-3 5.2.2) would be dropped
// and code would be generated for a node without any identity, so
// the import fails instead of silently producing wrong output.
throw new InvalidDataException(
$"NodeId ({input.NodeId}) of node '{input.BrowseName}' uses the " +
$"{nodeId.IdType} identifier type which is not supported by the model " +
"design. Only Numeric and String identifiers can be imported.");
}
```
Note on behaviour: a null/unspecified NodeId parses as a Numeric
identifier (`i=0`) and is still handled by the numeric branch, so the
removed `IsNull` branch was only ever reachable for an empty Guid or
Opaque identifier, which is now reported as unsupported.
## Related Issues
## Checklist
_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [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.
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes #4504
---------
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>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
||
|
|
e639932b95 |
Fix source generator defects from the Client/Types/SourceGeneration audit (#4473)
# Description
Fixes the SourceGeneration findings of a read-only bug audit of
`a05a36055` — sections 15 (host generators, Roslyn side), 16
(SourceGeneration.Core schema / ModelDesign loader) and 17
(SourceGeneration.Core generators). 36 of the 39 findings are fixed;
each has a regression test.
### Generated-code correctness
- **State machine NodeId constants** were emitted as `const uint` even
when the aliased `Objects` constant is a `string`, so every model using
string NodeIds with a `FiniteStateMachineType` subtype failed with
CS0029. The constant type now follows the aliased node, looked up from
the same source `NodeIdGenerator` emits from — top-level nodes **plus
each node's instance hierarchy**, since a state or transition only ever
exists as a hierarchy entry.
- `NodeIdGenerator` emitted string identifiers into C# string literals
without escaping them.
- Structure field names are authored data, so they can be C# keywords or
collide with the members every generated data type carries. Properties
and `{Type}Fields` members now go through a sanitizing,
collision-breaking mapping (including two siblings that sanitize to the
same identifier). **Wire names are untouched** — OPC 10000-3 §8.51
requires the StructureField name to match the BrowseName, and the
`DataMember`/encode/decode names still come from `Parameter.Name`.
- `NodeStateGenerator`'s copy-on-write cast now uses the fully qualified
data type name, which a cross-model structure needs.
- `GetScalarValueAsCode` emitted `(double)NaN` and the infinities, which
do not compile.
- `TemplateWriter` formatted with the current culture, so a machine with
a comma decimal separator emitted invalid generated source.
### Served schema documents
- The binary schema described a **different wire layout than the
generated `Encode`/`Decode`**: unions carried no
`SwitchField`/`SwitchValue`, and structures with optional fields carried
no encoding-mask presence bits. Both are now emitted per **OPC 10000-6
§5.2.7**, matching the layout the runtime `BsdSchemaGenerator` produces.
A structure with more than 32 optional fields now fails generation
instead of silently wrapping `1 << 32` back onto the first field's bit.
- Documentation text and attribute values are XML-escaped.
### ModelDesign loading and validation
- Identifiers pinned in a CSV were dropped whenever any other loaded
namespace declared the same symbolic name. Removal is now scoped to the
file's target namespace, and explicit `NumericId`s are keyed by
`(namespace, id)` so the same number in two namespaces is legal.
- Declaration order inside a design file no longer matters — base types
are imported and validated ahead of their subtypes.
- OptionSet `OptionSetValues` were truncated at bit 31 (signed shift)
and bits 32–63 of a 64-bit option set were never emitted.
- An authored `ValueRank` is no longer overwritten by the shape of the
default value (OPC 10000-3 §6.2.8: a ValueRank may only be further
restricted), and a VariableType data type may be narrowed any number of
levels deep rather than exactly one.
- NodeSet import: `AccessRestrictions="0"` means "no restrictions", not
`EncryptionRequired` (OPC 10000-3 §8.56) — an unrecognised mask stays
**fail-closed**; a NodeSet without `<Models>` reports what is wrong; an
optional `<References>` no longer throws; the parent/child reference
scan looks at the parent's references; and `IsTypeOf` terminates on the
self-referential root reference types NodeSets routinely declare.
- Version comparison is numeric per component (ordinally `"1.05.9"`
sorts above `"1.05.10"`) and now lives on `SemVer`, so the NodeSet
collection and the dependency tie-break cannot pick different winners; a
publication-date version no longer outranks a real one.
### Host generator
- `[DataType]` on an `enum` is matched again (an enum is not a
`TypeDeclarationSyntax`), MODELGEN003 renders its arguments instead of
the raw template, and the Types hint name keeps the namespace's dots so
`A.BC` and `AB.C` cannot claim the same one.
- Compilation-derived pipeline stages carry value comparers, so editing
a file no longer re-runs the entire model generation on every keystroke.
- A NodeSet that fails to load skips that file rather than every
remaining one; a file listed twice as an `AdditionalFile` is deduped;
superseded NodeSet versions are still recognised as NodeSet inputs and
not generated a second time by the ModelDesign pass.
- `LogLevel.Critical` maps to a diagnostic instead of being silently
dropped.
### Client
- `EventRecordFieldReaders` gained the readers the event record
generator needs (including `Variant`, enumerations and the array forms).
Fields whose type had no reader were declared on the record but never
populated, so they read as `null`/`default` for every event.
## Not fixed (deliberately)
- **Forward same-namespace hierarchical references in
`NodeSetToModelDesign.ImportReferences`** — the audit flags the
inverse-only handling as asymmetric. Making it symmetric compiles and
passes every generator unit test, but regresses the address space: all
17 `RobotOpenUsdE2eTests` fail in `OneTimeSetUpAsync` because the hosted
server never finishes starting. Reverted, with a comment at the site
recording why.
- **§17.10 and §15.10** (both low + PLAUSIBLE) — each needs a wider
refactor than this PR: a consistent NodeId-constant rename across ~6
cross-reference sites, and replacing `Location` with a serializable
equivalent for incremental cache equality.
## Spec cross-check
The encoding/schema changes were checked against the specification: OPC
10000-6 §5.2.7 (32-bit EncodingMask, one bit per optional field in
order, padded), the union `SwitchField` encoding, OPC 10000-3 §8.56
(AccessRestrictionType bit mask), §6.2.8 and §5.6.2/§5.6.5 (ValueRank),
§8.51 (StructureField name == BrowseName).
`GenerateStackTests.OptionalFieldBitsAgreeBetweenBsdAndGeneratedEncoder`
pins the BSD presence-bit order against the generated encoder's
`LogRecordFields` so the two cannot drift apart again.
## Related Issues
_No tracking issue — this PR implements the SourceGeneration sections of
a bug audit. Happy to open one if maintainers prefer._
## Checklist
- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [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.
> Note on the test checkbox: verified on **net10.0 only** — `UA.slnx`
builds clean (0 warnings, 0 errors) and
`Opc.Ua.SourceGeneration.Core.Tests` (3915),
`Opc.Ua.SourceGeneration.Tests` (184), `Opc.Ua.Di.Tests` (425, including
the `RobotOpenUsdE2eTests` end-to-end fixture that catches address-space
content changes) and the `EventRecordFieldReaders` tests (16) all pass.
.NET Framework has **not** been run locally; CI needs to confirm it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.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> |
||
|
|
da7412c429 |
Add native xRegistry events and distinct Resource versions (#4396)
## Summary - support the xRegistry 0.6.0 distinct Resource/Version hierarchy: `Registry -> Group -> Resource -> Versions -> Version` - instantiate stable logical Resource nodes separately from exact Version nodes while preserving existing Version NodeIds - delegate logical Resource FileType operations and Version-view metadata to the selected default Version with handle pinning and shared writer reservations - make Resource and Version delete behavior role-stable and use `MetaEpoch` / `Epoch` respectively - use stable Resource SourceNodes, exact Version SourceNodes and the `Server -> Registry -> Group -> Resource -> Version` notifier chain - keep a model-metadata-gated client fallback for 0.5 flat Servers; 0.6 Servers expose no legacy aliases - target `HasWoTProjection` at the real stable logical Resource node - use `AsyncCustomNodeManager` for the registration, fast-path, and federation managers, with awaited lifecycle, node mutations, event delivery, and cleanup - handle startup rollback, reentrant deletion callbacks, undisclosed file handles, and retryable cleanup without blocking unrelated file operations ## Authoritative dependencies - companion specification: OPCF-Members/spec-drafts#32 at `d3399fca025025a73e5a6b599f850a81d985c060` - OPC UA binding: xregistry/spec#568 - xRegistry event semantics: xregistry/spec#563, merged as `66ee31e47fbadff6ab982ae1516f7d9065e46b64` - companion NodeSet SHA-256: `369f5f3f1814b333e4eeb52bfb4b6a730cdff372505a5f91a54c94f0016ac4cf` ## Review feedback - keep the WoT Connectivity information-model designation at `1.1`; draft revisions update the publication date without changing the model version - eliminate all 198 unique CA1861 diagnostics across 54 files and remove seven obsolete project suppressions - fix the analyzer-clean touched-path CA1307 and the hierarchy follow-up CA1846/CA1851 diagnostics without dropping legacy-TFM compatibility - recover the reconcile queue after callback faults/cancellation and continue accepted pending changes - remove migration guidance for the xRegistry managers, which are new in 2.0 ## Compatibility - public WoT Connectivity model designation remains `1.1` - generated event-record ABI, constructors, options, NodeIds, and stored documents remain compatible - the generic xRegistry managers are new in 2.0 and implement `IAsyncNodeManager`; use `IAsyncNodeManagerFactory` and the async lifecycle hooks (see `docs\XRegistry.md`) - persistence schema and document blobs remain compatible unless noted below ## Validation - merge validation at `980422f76` with upstream `e8bec27d5`: 454 targeted WoT/materialization/model/registry cases and all 327 XRegistry cases passed on both net10.0 and net48; all 29 net10 aggregation sample cases passed - feedback regressions at `308e8ba74`: 55 targeted queue and live registry event/projection/lifecycle cases passed on each of net10.0 and net48 - prior CI: all 266 checks passed at `6489a0908`; the unchanged Ubuntu Core certificate-counter test and dependent jobs passed on the one-time retry ([run 34117304035, attempt 2](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/34117304035/attempts/2)) - `Opc.Ua.XRegistry.Tests` — 327 passed on net10.0 and net48 for migration commit `32c914aaf` - `Opc.Ua.WotCon.Tests` net10.0 — 1348 passed - `Opc.Ua.WotCon.Bindings.Tests` net10.0 — 683 passed - `Opc.Ua.SourceGeneration.Core.Tests` net10.0 — 3807 passed, 8 skipped - `Opc.Ua.SourceGeneration.Tests` net10.0 — 145 passed - `Opc.Ua.SourceGeneration.Stack.Tests` net10.0 — 94 passed - affected XRegistry and WoT client/server projects build successfully - xRegistry NodeSet is byte-identical to the companion artifact - repository-wide CA1861 inventory — 198 unique diagnostics across 54 files reduced to zero - targeted changed-fixture validation — 1,098 passed, 3 skipped, 0 failed - constrained full `UA.slnx` net10.0 Release build — CA1861 = 0 - `Opc.Ua.XRegistry.Client` net472 and netstandard2.1 builds — 0 warnings, 0 errors - independent shared-array safety review — no significant issues - `git diff --check` The final review regressions cover concurrent reconcile/open, synthetic logical handle collisions across default switches, cross-session Close isolation, shared writer reservations, logical FileType property/change propagation, logical Labels delegation, role-fixed deletion, event sources, model-version gating, and stable `HasWoTProjection` targets. The companion specification and protocol binding are reviewed in the linked PRs above. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5c79a1ed-6dab-4522-a9fc-6548caa78ad5 Copilot-Session: b605797f-a97a-4cd0-8f12-016d33c2fd88 Copilot-Session: 7ef369a5-2307-4f71-8a11-15af0e72f240 Copilot-Session: e716d455-8397-4d22-9c5a-398847ab7317 |
||
|
|
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> |
||
|
|
02ea934a31 |
Complete fluent node manager extensibility (#4418)
# Description Complete the fluent node-manager extensibility surface needed by dynamic and externally backed address spaces. This change: - adds virtual node families with cheap NodeId ownership checks, async materialization, per-operation caching, custom browsing, and family-level callbacks; - adds monitored-item pre-creation decisions, complete lifecycle hooks, async batch hooks, stack-managed custom monitored items, and subscription-gated polling; - exposes the startup `ApplicationConfiguration` to generated manager partials through `FluentNodeManagerBase`; - fixes typed builder proxy support, concrete object wrapper types, and fully-qualified generated model composition; - reports unresolved `[NodeManager]` namespace expressions as `MODELGEN035` instead of silently dropping them; - documents the new APIs and compatibility behavior. ## Related Issues - Fixes #4397 - Fixes #4398 - Fixes #4399 - Fixes #4403 - Fixes #4404 - Fixes #4405 ## Validation - `Opc.Ua.Server.Tests` on `net10.0`: 4,886 passed, 5 skipped. - `Opc.Ua.SourceGeneration.Core.Tests` on `net10.0`: 3,807 passed, 8 skipped. - `Opc.Ua.SourceGeneration.Tests` on `net10.0`: 164 passed. - Runtime and generator regression selections passed on `net48`. - Review-feedback validation: 376 fluent tests passed on `net10.0`; 22 focused tests passed on `net48`. - The CI timing regression was repeated five times on `net48` and once on `net10.0`. - All 266 applicable GitHub Actions and Azure Pipelines checks, including CodeQL and coverage, pass on `83c571423`. - AppVeyor reports an immediate zero-duration no-run failure because `appveyor.yml` whitelists only the `appveyor` branch; it did not execute this PR. - `UA.slnx` builds successfully for `net10.0`; affected project builds are warning-free. - The repository-wide `dotnet test UA.slnx` invocation is currently blocked by the solution's mixed VSTest/Microsoft.Testing.Platform configuration under the .NET 10 SDK, so the affected test projects were run individually. ## 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. - [x] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. --------- Copilot-Session: afaa67c9-fc97-4185-a785-edd54a937d95 |
||
|
|
e7542b7f5f |
Fix generated structured defaults and dependency metadata (#4414)
# Description Fix two source-generator paths that lost variable values or metadata: - XML `ExtensionObject` defaults without a `TypeId` now resolve their body through the encodeable factory's registered XML qualified name, decode to the generated structure, and retain the XML encoding identifier. - The existing preview `ModelDependencyV1` payload now carries effective child access and user-access values, minimum sampling interval, historizing state, and serialized default-value XML across referenced assemblies. The dependency validator rehydrates this metadata before inherited instance factories are generated. Explicit non-null extension-object TypeIds remain authoritative, unknown XML bodies remain raw XML, and explicit design files remain authoritative over referenced payloads. ## Related Issues - Fixes #4401 - Fixes #4402 ## Validation - Focused decoder, payload, validator, and Roslyn producer/consumer regressions on `net10.0` and `net48` - `tools/SourceGeneration.slnx` Debug builds on `net10.0` and `net48` - `tools/SourceGeneration.slnx` Release build on `net10.0` - Scoped `UA.slnx` Debug build on `net10.0` ## 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. --------- Copilot-Session: 66e6d342-d4a0-4d53-822b-524982c62f15 |
||
|
|
b97fd8d4b5 |
Fix OPC 10000-21 onboarding interoperability (#4385)
# Description Fix the four blockers that prevented the OPC 10000-21 onboarding registrar and client from working end to end: - generate every `ModelDesign` target independently, including multiple models in one directory, while preserving stable identifier CSV matching and cross-model dependencies; - ship the generated `Opc.Ua.Onboarding` constants, node states, encodeables, and typed clients from `Opc.Ua.Gds.Common`; - correct the standard `EncodedTicket` datatype to derive from `ByteString`; - align the new 2.0 `OnboardingClient` API with the generated `ArrayOf<ByteString>` / `ArrayOf<StatusCode>` contract; - make `BindToTicketStore` fully asynchronous, cancellation-aware, and assign the declared output slot instead of appending a second output; - add generated-model, client fallback, server binding, and in-process end-to-end regressions. The change also adds `samples/Gds/OnboardingRegistrar`, `samples/Gds/OnboardingClient`, and `run-onboarding-demo.ps1`. The demo loads the generated GDS and Onboarding models, restricts ticket administration to an ephemeral `RegistrarAdmin` user, proves anonymous access is denied, and completes a secure register/unregister round trip. The History CI follow-up rejects negative and non-finite processed-history intervals before provider dispatch and enforces the buffered aggregate output cap while calculator results are flushed. This prevents malformed requests from running until the client timeout, exhausting memory, delaying server teardown, and causing later alarm-event tests to fail. ## Related Issues - Fixes #4378 - Unblocks OPCFoundation/UA-.NETStandard-Samples#819 - Tracks the canonical model correction in OPCFoundation/UA-Nodeset#122 ## Validation - Full `net10.0` test projects: - `Opc.Ua.SourceGeneration.Core.Tests`: 3803 passed, 8 skipped - `Opc.Ua.SourceGeneration.Tests`: 143 passed - `Opc.Ua.Gds.Tests`: 1114 passed, 42 skipped - `Opc.Ua.History.Tests` with the CI coverage settings: 506 passed, 27 skipped - `HistorianDispatcher` tests: 88 passed on `net10.0`. - Invalid processed-history interval regressions passed on `net10.0` and `net48`. - Focused onboarding/source-generation regressions passed on `net48`. - `dotnet build UA.slnx -p:CustomTestTarget=net10.0 --no-restore` succeeded; the reported analyzer warnings are pre-existing in unchanged XRegistry/WoT files. - Packed all three GDS packages and verified the packaged `Opc.Ua.Gds.Common` assembly contains the generated Onboarding model surface. - `pwsh samples/Gds/run-onboarding-demo.ps1` verified anonymous denial and the authenticated secure ticket lifecycle. ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2d2c52c7-7040-4bd6-98de-aaf82ba7846b |
||
|
|
14fba2d120 |
Fix instance TypeDefinition across dependency ModelDesign boundaries (#4355)
# Description The model source generator crashed with `MODELGEN003` / `NullReferenceException` whenever a `ModelDesign` instantiated an `ObjectType` (or `VariableType`) declared in a **dependency** design file, as soon as that type had children. The sibling of #4332 on the instance-declaration path: #4335 linked dependency *data types*, but an *instance* whose `TypeDefinition` lives in another design still crashed in `NodeStateGenerator` on the type's inherited children. Root cause: dependency design files load with `validateDictionary: false`, so `ValidateInstance` / `ValidateType` never ran on them and their types' children kept `TypeDefinitionNode` / `DataTypeNode` at `null`. When the target instance merged those children into its hierarchy (`BuildInstanceHierarchy` re-walks the dependency type's `Children.Items`), the null references were copied along, and `GetNodeStateClassName` dereferenced them while building the node state factory list. Changes: - A new best-effort `LinkDependencyInstances` pass in `ModelDesignValidator` links dependency-design instances the way `ValidateInstance` / `ValidateType` do for the target model: the `TypeDefinitionNode` / `DataTypeNode` of the types' children and standalone instances (recursively), variable `DataType` / `ValueRank` inheritance from the variable type, `DefaultValue` decoding, `VariableTypeDesign.DataTypeNode` restrictions, and method arguments — including creation of the `InputArguments` / `OutputArguments` argument properties so method children materialise into the target hierarchy exactly like local ones. Resolution is best effort (unresolvable references stay `null`) and NodeSet2 dependencies remain excluded, consistent with `LinkDependencyDataTypes`. - The pass runs alongside `LinkDependencyDataTypes`: for upstream dependencies after all of them are loaded and before the target validates (so the hierarchy merge picks up the links), and for downstream dependencies after they load. - `docs/ModelDependencies.md` documents the instance linking. Tests (all verified to fail with the exact reported NRE stack before the fix): - `CrossModelInstanceTypeDefinitionTests` (Core, 4 tests): the issue's minimal repro end to end; an equivalence test asserting the cross-model instance emits the **identical node-state factory set** as a locally declared type (this is the issue's stated expectation, and it drove the method-argument-property part of the fix); the flipped upstream/downstream ordering; and validator-level assertions that the merged hierarchy carries linked `TypeDefinitionNode` / `DataTypeNode` references, including the data type inherited from a dependency `VariableType`. - `CrossModelInstanceTypeDefinitionGeneratorTests` (Roslyn driver, 2 tests): the issue's csproj repro shape — both `AdditionalFiles` orderings, asserting no diagnostics and that the combined generated output of both models **compiles** (the instance factories reference the dependency model's typed state classes). Verified locally on net10.0: `Opc.Ua.SourceGeneration.Core.Tests` 3800 passed, `Opc.Ua.SourceGeneration.Tests` 141 passed, `Opc.Ua.SourceGeneration.Stack.Tests` 94 passed; 0-warning Release build of `Opc.Ua.SourceGeneration.Core` (all TFMs incl. netstandard2.0); `dotnet format whitespace --verify-no-changes` clean. This removes the last generator blocker for source generating the instance model of the `Workshop/DataTypes` sample (OPCFoundation/UA-.NETStandard-Samples#814): its `ParkingLot` / `DriverOfTheMonth` are instances of types declared in the other design file. ## Related Issues - Fixes #4353 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> |
||
|
|
24ba4855e6 |
Fix [DataType] generator Clone() dropping every inherited field of a derived structure (#4354)
# Description
The `[DataType]` source generator emitted a `Clone()` that created a
fresh instance and copied only the fields declared on the annotated type
itself, so every field inherited from an `IEncodeable` base was silently
dropped. Since the server clones node values while loading an address
space, a derived structure served by a node manager came back out of a
Read with its inherited fields empty, even though encode/decode was
correct.
This PR makes the `[DataType]` path emit the same
`MemberwiseClone`-based shape as the model-based generator:
```csharp
/// <inheritdoc/>
public override object Clone()
{
return (DerivedStruct)this.MemberwiseClone();
}
/// <inheritdoc/>
public new object MemberwiseClone()
{
DerivedStruct clone = (DerivedStruct)base.MemberwiseClone();
// deep copy of the own reference-typed fields
return clone;
}
```
`Clone()` delegates to a generated `public new object MemberwiseClone()`
that starts from `base.MemberwiseClone()` — a shallow copy of the whole
object, inherited fields included — and then deep copies the type's own
reference-typed fields (encodeables, `DataValue`, `Variant`,
`ExtensionObject`). Derived classes hide `MemberwiseClone` and chain
into the base implementation, so deep-copy semantics compose across the
hierarchy, including across assemblies into model-generated base types
(the `BicycleType` scenario that surfaced this). The two generator paths
now agree.
Because the clone now starts from a full shallow copy
(`base.MemberwiseClone()` for classes, `with { }` for records), the
per-field clone emitter only writes the deep-copy assignments; the
redundant per-field plain copies are gone. This also fixes a latent side
bug in the class path: init-only simple fields were silently dropped by
the fresh-instance clone as well.
Records were already correct (`with { }` carries every field) and are
unchanged in shape.
Tests:
- `GenerateClassCloneUsesMemberwiseClone` /
`GenerateDerivedClassCloneCarriesInheritedState` assert the emitted
shape for root and derived classes.
- `DerivedClassCloneKeepsInheritedFields` runs the exact repro from the
issue through the Roslyn driver, emits the assembly, and executes
`Clone()` at runtime to prove the inherited field survives (verified on
net10.0 and net48).
Verified locally: `Opc.Ua.SourceGeneration.Core.Tests` (3798 passed),
`Opc.Ua.SourceGeneration.Tests` (140 passed),
`Opc.Ua.SourceGeneration.Stack.Tests` (94 passed), and
`Opc.Ua.Client.Tests` (2161 passed, exercises the regenerated in-tree
`[DataType]` record types). The in-tree `[DataType]` consumers
(Opc.Ua.Client, both GDS common projects) regenerate and build without
warnings.
## Related Issues
- Fixes #4352
## Checklist
_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [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 Fable 5 <noreply@anthropic.com>
|
||
|
|
766c5558e8 |
Fix structure subtyping across dependency ModelDesign boundaries (#4335)
# Description The model source generator crashed whenever a `ModelDesign` declared a structure that subtypes a structure from **another** design file (`MODELGEN003` with a `NullReferenceException`), and the cross-assembly `[assembly: ModelDependency]` flow failed even earlier with `The BaseType reference ... is not the expected type: DataTypeDesign`. Structure inheritance across model boundaries — the primary use case of companion-spec layering — could not be source-generated at all. Three root causes, all in the dependency-loading path of the validator: 1. **Dependency design files were never linked.** They load with `validateDictionary: false`, so their `DataTypeDesign`s kept `BasicDataType` at the enum default (`Boolean`) and their fields' `DataTypeNode` stayed `null`. `BinarySchemaGenerator`'s inherited-field merge then dereferenced the null `DataTypeNode`, and `XmlSchemaGenerator` / `DataTypeGenerator` silently emitted `xs:boolean` / `bool` from the default classification. 2. **Dependencies that import the target crashed at load.** Each generation group receives every design file of the compilation as a dependency, including downstream models that import the group's target — those loaded *before* the target they import and failed resolving their `BaseType`. This produced the confusing `AdditionalFiles`-order-dependent failure modes. 3. **The payload flow never reached ModelDesign targets.** `ModelCompilation` supplied `referencedDependencies` only to the NodeSet2 pass, never to the design-file pass, and payload-materialised data types lacked the classification and field links needed to serve as base types of local structures. Changes: - `ModelDesignValidator.ValidateModel` loads dependencies in two phases around the target: upstream models first (topological, referenced models before their referrers), then the target, then models that import the target. - A new `LinkDependencyDataTypes` pass classifies dependency data types (`IsStructure` / `IsEnumeration` / `BasicDataType`) and resolves field `DataTypeNode` / `Parent` best effort. It runs for payload-imported declarations and upstream design-file dependencies before target validation, and for downstream dependencies after they load. NodeSet2 dependencies are excluded — `NodeSetToModelDesign` already links and classifies them with its own semantics. - Payloads for models that are backed by a design file in the compilation are skipped — the explicit file is authoritative (previously, importing both crashed with duplicate symbolic ids). - Payload-materialised data types honor the payload's `IsEnumeration` flag. - The design-file `GenerateCode` pass now receives the referenced assemblies' dependency payloads (`ModelCompilation` + new optional `referencedDependencies` parameter). - `docs/ModelDependencies.md` updated accordingly. Tests (all verified to fail with the exact reported errors before the fix): - `CrossModelStructureSubtypeTests` (Core, 6 tests): the issue's minimal repro end to end — asserting the emitted `.bsd` carries inherited fields with correct types and `SourceType`, the `.xsd` extends the dependency structure (not `xs:boolean`), and the generated C# derives correctly — plus the reversed target/dependency order, both payload flows at generation and validator level, and a three-level chain (target → payload structure → design-file structure). - `CrossModelStructureSubtypeGeneratorTests` (Roslyn driver, 3 tests): both `AdditionalFiles` orderings compiling the generated output, and the full cross-assembly flow (producer assembly generated from the upstream design; consumer resolves the base structure from the reference's payload without re-emitting it). Verified locally on net10.0: `Opc.Ua.SourceGeneration.Core.Tests` 3786 passed, `Opc.Ua.SourceGeneration.Tests` 135 passed, `Opc.Ua.SourceGeneration.Stack.Tests` 94 passed; 0-warning Release builds of `Opc.Ua.SourceGeneration.Core` (all TFMs incl. netstandard2.0), both Roslyn generator variants, Stack and Tester. This unblocks the `[NodeManager]` migration of the DataTypes workshop sample (OPCFoundation/UA-.NETStandard-Samples#757). ## Related Issues - Fixes #4332 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcschier <11168470+marcschier@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> |
||
|
|
41db5aca60 |
Fluent: publish Configure-created nodes' references on foreign-manager nodes (Objects folder placement) (#4331)
# Description A node created inside the fluent `Configure` hook (source-generated `[NodeManager]` partial, or the hosting `AddNodeManager(uri, build)` route) had no way to appear below the Objects folder — or below any node owned by another node manager. `AsyncCustomNodeManager.AddReverseReferencesAsync` mirrors inverse references into the `externalReferences` dictionary **before** the `Configure` partials run, and the fluent surface never sees the dictionary, so a configure-created node with an inverse `Organizes` reference to `ObjectsFolder` existed and simulated but never showed up under Objects. This implements the three coordinated pieces proposed in #4329, plus the companion namespace gap: **1. Idempotent mirroring pass** (independently worthwhile hardening) - `AddExternalReference` in `AsyncCustomNodeManager` and `CustomNodeManager2` now has if-missing semantics (linear scan of the per-source list). - `TypeTable.AddEncoding` no longer appends a duplicate encoding id; re-registering the same encoding is a succeeding no-op. (`AddRootNotifierAsync` and the node-side `AddReferenceIfMissing` branch were already idempotent.) **2. Re-run the pass after `Configure`** - New protected `FluentNodeManagerBase.CompleteConfigureAsync(externalReferences, ct)` wraps `AddReverseReferencesAsync` so hand-written managers using `CreateFluentBuilder` benefit too. - The generator-emitted `CreateAddressSpaceAsync` and the hosting `FluentNodeManager` invoke it once between the `Configure` callbacks and `Seal()`. Timing is safe: the master distributes `externalReferences` only after every manager's `CreateAddressSpaceAsync` returns. Side benefit: inverse `HasNotifier` references on configure-created event sources get root-notifier registration for free. - The hosting `FluentNodeManager`'s hand-rolled `externalReferences` workaround for its root folder is deleted — the root's inverse `Organizes` reference now flows through the shared pass (mirrored exactly once, covered by a test). **3. Discoverable fluent sugar** - `OrganizedBy(parentId)` / `UnderObjectsFolder()` on `INodeBuilder` write the inverse `Organizes` reference; with piece 2, placement is just "write the inverse reference". - A parentless `CreateInstance<TState>` on `INodeManagerBuilder` takes a constructor-style factory (`p => new BoilerState(p)`, keeping the surface reflection-free and AOT-safe), materializes the subtree from the type model via `NodeState.Create`, rebases all NodeIds through the manager's `INodeIdFactory` (`AssignInstanceNodeId`/`AssignInstanceChildNodeIds` — the same pair the generated factories use, so declaration-id children cannot collide with the type model), and registers via `AddPredefinedNodeSynchronously`. The Boiler #2 shape becomes fully fluent: ```csharp builder.CreateInstance( new QualifiedName("Boiler #2", NamespaceIndexes[1]), p => new BoilerState(p)) .Configure(n => n.UnderObjectsFolder()); ``` **Companion gap, same theme:** `[NodeManager]` grows `AdditionalNamespaceUris`, flowing through attribute discovery → binding → generator so the generated constructor reports a second (instance) namespace at construction and the generated factory advertises it in `NamespacesUris` — today `SetNamespaces` after construction is not enough because `MasterNodeManager` builds its namespace routing from what the manager reported when it was built. **Scope boundary** (as discussed in the issue): startup-time configuration only; for nodes created after startup the correct primitive remains `IMasterNodeManager.AddReferencesAsync`. **Reviewer notes** - The generated-code change was validated against the real pipeline: a forced fresh compile of `Opc.Ua.Server.Tests` (whose CoverageNodeSet managers are `[NodeManager]`-generated) emits `await CompleteConfigureAsync(externalReferences, cancellationToken)` between `Configure` and `Seal()` and compiles against the real base class. - The distribution consumer (`AddReferencesAsync`) already used `AddReferenceIfMissing`, so the whole chain is defense-in-depth idempotent. - 15 new tests: double-run idempotence, second-pass pickup of late-registered nodes, the new reference sugar, root-instance creation incl. child-id rebasing and reference remapping, hosting end-to-end Objects-folder placement, emitted-sequence and `AdditionalNamespaceUris` generator tests, and a full Roslyn-pipeline attribute test. Docs updated in `docs/NodeManagers.md` (new "Creating nodes under other managers' nodes" section, attribute docs, fluent-surface sections). - Verified locally: all touched projects build with 0 warnings; full `Opc.Ua.Server.Tests` (4,725 passed / 0 failed / 5 skipped, net9.0), `TypeTableTests` (113), `Opc.Ua.SourceGeneration.Core.Tests` (41), `Opc.Ua.SourceGeneration.Tests` (132), and the Quickstarts.Servers sample all pass. ## Related Issues - Fixes #4329 - Motivating case: OPCFoundation/UA-.NETStandard-Samples#756 / OPCFoundation/UA-.NETStandard-Samples#795 (Workshop/Boiler migration) ## 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 Fable 5 <noreply@anthropic.com> |
||
|
|
24399f1808 |
Fix source generator MODELGEN003 diagnostic dropping message placeholders (#4310)
## Problem
`LoggerAdapter.Log<TState>` in
`Tools/Opc.Ua.SourceGeneration/SourceGeneratorTelemetry.cs` reported an
**already fully-formatted** logger `message` against a looked-up
`DiagnosticDescriptor` whose `messageFormat` may declare more positional
placeholders than the single formatted string carries.
For `EventId == 3` the descriptor is `SourceGenerator.Exception`
(`MODELGEN003`) with:
```
messageFormat = "Exception during model generation '{0}': {1}"
```
That format has **two** placeholders but only **one** argument
(`message`) was supplied. Roslyn's `Diagnostic.GetMessage()` runs
`string.Format(format, args)`, which throws `FormatException` because
`{1}` has no argument; Roslyn's internal catch then returns the **raw,
unsubstituted template**. The user therefore saw the literal text
`Exception during model generation '{0}': {1}` with the placeholders
unreplaced, masking the real error.
## Fix
Route the pre-formatted message through a new
`CreateFormattedDiagnostic` helper that reports it via a **passthrough
descriptor whose `messageFormat` is exactly `"{0}"`**, while preserving
the original descriptor's `Id`, `Title`, `Category`, `DefaultSeverity`,
`IsEnabledByDefault`, `HelpLinkUri` and custom tags:
```csharp
public static Diagnostic CreateFormattedDiagnostic(
DiagnosticDescriptor descriptor,
string message)
{
var passthrough = new DiagnosticDescriptor(
descriptor.Id,
descriptor.Title,
"{0}",
descriptor.Category,
descriptor.DefaultSeverity,
descriptor.IsEnabledByDefault,
helpLinkUri: descriptor.HelpLinkUri,
customTags: descriptor.CustomTags.ToArray());
return Diagnostic.Create(passthrough, Location.None, message);
}
```
`LoggerAdapter.Log` now calls this helper instead of
`Diagnostic.Create(descriptor, Location.None, message)`. This renders
the message verbatim under the correct diagnostic id regardless of the
descriptor's placeholder arity, and is safe even when the message itself
contains `{`/`}` characters (the format string is only `"{0}"`).
> Note: the public `descriptor.CustomTags` (`IEnumerable<string>`) +
`.ToArray()` is used because Roslyn's `ImmutableCustomTags` property is
`internal`.
### Why the helper lives in `SourceGeneratorHelpers.cs` (not
`SourceGenerator.cs`)
`SourceGeneratorTelemetry.cs` and `SourceGeneratorHelpers.cs` are shared
(glob-linked) into **both** the model generator
(`Opc.Ua.SourceGeneration`) and the stack generator
(`Opc.Ua.SourceGeneration.Stack`). The stack generator has its **own**
`SourceGenerator` partial class and does **not** compile the model
generator's `SourceGenerator.cs`. Placing the helper in
`SourceGenerator.cs` broke the stack build with `CS0117:
'SourceGenerator' does not contain a definition for
'CreateFormattedDiagnostic'`. `SourceGeneratorHelpers.cs` is the shared
partial (where `Guard` also lives), so the helper is available to both
generators.
### Guard path untouched
The other emission path, `SourceGenerator.Guard(...)` in
`SourceGeneratorHelpers.cs`, uses the same `Exception` descriptor but
correctly passes **two** args (`ex.Message`, `ex.ToString()`), so that
path was already fine and is **not** changed.
## Tests
New
`tests/Opc.Ua.SourceGeneration.Tests/SourceGeneratorTelemetryTests.cs`
(4 tests):
1. **`DirectReportAgainstMultiPlaceholderDescriptorLosesMessage`** —
documents the pre-fix behaviour: the raw `Diagnostic.Create(Exception,
..., "boom")` render drops `"boom"` and leaks `{1}`.
2. **`CreateFormattedDiagnosticRendersMessageVerbatim`** — the fix
renders `"boom"` exactly, with no `{0}`/`{1}`.
3. **`CreateFormattedDiagnosticPreservesDescriptorMetadata`** — id
(`MODELGEN003`), severity, category, help link, IsEnabledByDefault and
custom tags are preserved.
4. **`CreateFormattedDiagnosticIsSafeForBracesInMessage`** — a message
containing `{`/`}` is emitted verbatim.
## Validation
- `tools/Opc.Ua.SourceGeneration` (and the stack variant) build with **0
warnings / 0 errors**.
- Full `Opc.Ua.SourceGeneration.Tests` suite on net10.0: **132/132
passing**, including the 4 new tests.
## Files changed
- `tools/Opc.Ua.SourceGeneration/SourceGeneratorHelpers.cs` — added
`using System.Linq;` + `CreateFormattedDiagnostic` helper.
- `tools/Opc.Ua.SourceGeneration/SourceGeneratorTelemetry.cs` —
`LoggerAdapter.Log` calls the helper.
- `tests/Opc.Ua.SourceGeneration.Tests/SourceGeneratorTelemetryTests.cs`
— new regression tests.
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
||
|
|
afbd10e3e3 |
Expose runtime capability probes to fix TFM-skew test failures (#4282) (#4295)
# Description The nightly `Test .NETStandard 2.1 Tests (net8.0)` leg compiles the stack as `netstandard2.1` but runs the test assemblies as `net8.0`. Features gated with `#if NET8_0_OR_GREATER` / `#if NET5_0_OR_GREATER` in the stack are therefore absent at runtime, while the tests assume they exist because a `#if` in a test reflects the *test's* TFM (always net8.0 on this leg), not the stack's compiled TFM. This causes the affected tests to fail on that leg. This PR fixes it by exposing stack-side runtime capability probes. Each probe lives in the same assembly as the feature it gates, so its `#if` reflects that assembly's actual compilation. Affected tests now `Assert.Ignore(...)` or branch based on the probe instead of relying on their own compile-time `#if`. ## Approach New public probes: - `DtlsRecordProtection.IsAeadSupported` (+ `IsAeadCipherSuite` made public) - Opc.Ua.PubSub.Udp, net8 gate - `DtlsEcdheKeyExchange.IsRawSharedSecretSupported` - Opc.Ua.PubSub.Udp, net8 gate - `AdditionalEntropyCertificateKeyGenerator.IsEccKeyRegenerationSupported` - Opc.Ua.Server, net5 gate - `HttpsTransportListener.IsWssTransportSupported` - Opc.Ua.Bindings.Https, `NET5_0_OR_GREATER || NETFRAMEWORK` gate (false only for the netstandard2.1 build) Test updates: - DTLS record/handshake/ECDHE tests skip AEAD/raw-shared-secret cases when unsupported, preserving HMAC-suite coverage in parametrized tests. - Server ECC key-regeneration tests skip when unsupported. - The GDS push tests now *validate* the `BadNotSupported` result at runtime via the probe rather than compiling out the assertion, which also preserves the prior net48 behavior. - The ExperimentalAttribute test probes the stack assembly's `TargetFrameworkAttribute` at runtime instead of a meaningless compile-time flag. - The three WSS integration fixtures skip when WSS is unavailable on the loaded runtime. ## Notes for reviewers - On netstandard2.1 the `DtlsRecordProtection` constructor does not throw for AEAD suites; the `NotSupportedException` surfaces later in Seal/Open, so the skip is keyed on the cipher suite being AEAD *and* `!IsAeadSupported`. - The WSS probe gate returns false only for the netstandard2.1 build (which defines neither `NET5_0_OR_GREATER` nor `NETFRAMEWORK`), precisely isolating the build whose legacy Kestrel hosting cannot accept WebSocket connections on a modern runtime. Validated with clean builds (0 warnings, 0 errors) for the changed source projects and for PubSub.Udp.Tests, Server.Tests, Sessions.Tests and Gds.Tests on both net8.0 and net48. ## Related Issues - Fixes #4282 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [ ] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> |
||
|
|
2a4da11a9f |
Fix SourceGeneration tests to probe actual Opc.Ua.Server TFM output folder (#4293)
# Description `ModelDependencyScannerTests.GetStackReferences()` reconstructed the `Opc.Ua.Server` output path from the **test assembly's own TFM** (`bin/Release/net8.0`). On the netstandard2.1 nightly leg the tests run as `net8.0` while `Opc.Ua.Server` is built as `netstandard2.1`, so the hardcoded `net8.0` folder does not exist and every stack-compilation test threw `System.IO.DirectoryNotFoundException`. Because this only happens on a nightly-only leg, it was not caught by per-commit CI and remained on `master`. The fix stops assuming the test's TFM. A new `ResolveServerOutputFolder()` helper probes `src/Opc.Ua.Server/bin/<config>` for whichever TFM subfolder actually exists, preferring an exact match to the test TFM, then falling back to `netstandard2.1`, `netstandard2.0`, or any subfolder containing `Opc.Ua.Server.dll`. It also throws a clear, actionable message when the server has not been built yet. This is test-only; no product code changes. Build passes with 0 warnings and the previously failing tests pass locally. ## Related Issues - Fixes #4283 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> |
||
|
|
333de8305b |
Add kitchen-sink coverage NodeSet2 with three load pipelines (#4251) (#4272)
# Description Regression fence for the NodeSet2 import + code-generation surface. This adds a single, purpose-authored "kitchen-sink" NodeSet2 (`Opc.Ua.CoverageTest.NodeSet2.xml`) plus a small dependent secondary model that deliberately flip a broad set of the knobs defined by `UANodeSet.xsd` (every node class, every `Definition` shape, custom reference types, all five modelling rules, scalar/array/matrix values, a deep branching instance tree, and a wide set of reference relations). The same bytes are then loaded into **real** servers (`ServerFixture<T>` + `ReferenceServer`) through three independent pipelines and validated node-by-node, attribute-by-attribute, reference-by-reference: 1. **Source-generation, hand-written manager** - the XML is consumed at compile time via `<AdditionalFiles ModelSourceGeneratorPrefix="Opc.Ua.CoverageTest">`; the generated `AddOpcUaCoverageTest(context)` composes the predefined nodes inside a `CustomNodeManager2`, so every node materialises as its generated `NodeState` subclass. 2. **Runtime import** - the identical XML is imported at startup through `RuntimeNodeSetNodeManagerFactory` / `AddRuntimeNodeSet`. 3. **Source-generation, generated fluent manager** - a `[NodeManager]`-attributed partial makes the generator emit the whole `FluentNodeManagerBase` manager, factory and typed fluent-builder surface. A shared, data-driven `[TestCaseSource]` battery runs the identical assertions against all three servers, and a cross-pipeline equivalence fixture asserts the three address spaces agree. A dependent **second namespace** (`.../Secondary/`) exercises cross-namespace `HasTypeDefinition`, `HasSubtype` and `HasComponent` references in every pipeline. Authoring the model surfaced two latent source-generator bugs, both fixed here: - `NodeSetToModelDesign` crashed on a top-level `<ServerUris>` table (`StringTable.Append(null)` for the reserved local-server slot). Added a null guard plus a focused regression test (`NodesetServerUrisTests`). - `FluentBuilderGenerator` emitted colliding typed-builder accessors for any model with more than one structure encoding, because `DataTypeEncoding` objects hang off their `DataType` and surfaced as parent-less "root" instances sharing the reserved `DefaultBinary`/`DefaultXml`/`DefaultJson` symbolic names. They are encoding metadata, never fluent-wired, so they are now excluded from the typed builder. A few edges are intentionally not asserted for cross-pipeline equivalence because the pipelines legitimately differ (documented in `CoverageNodeSet/README.md`): type-dictionary / `DataTypeDescription` nodes (the generator regenerates schema rather than emitting them), a handful of ambiguous duplicate-hierarchical / View-`Organizes` edges, and runtime structure stand-ins (structure definitions are validated via the `DataTypeDefinition` round-trip instead). ## Related Issues - Fixes #4251 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [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: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com> |
||
|
|
e73e71184d |
[WotCon] Add OPC UA WoT Connectivity 1.1 with the Types, source generation, Server and xRegistry work it builds on (#4128)
## Summary Adds OPC UA WoT Connectivity 1.1 to the stack, together with the Types, source generation, Server and xRegistry work it is built on. This branch is the merge point for the fourteen-PR stack that replaced integration PR #4093; every constituent PR was reviewed and approved separately and is listed below. 414 files changed, +108,936 / -3,199. Roughly 46k added lines of product code, 57k of tests, plus samples, tools and documentation. ## Types - Complete `ExtensionObject` raw-body decoding for binary, XML and JSON bodies by resolving the concrete type through the message context's encodeable factory, and simplify the `Variant` structure helpers by delegating to it (#4128). - Add `IAtomicFileReplace` as an optional `IFileSystem` capability so existing external implementations keep working, with atomic publish for `LocalFileSystem` via `File.Replace`/`File.Move` and for `VirtualFileSystem` by re-keying the in-memory entry (#4128). - Add lossless conversion between WoT documents and NodeSet2 (`Opc.Ua.Wot.WotNodeSetConverter`). A byte-exact `uav:nodeSet` envelope is preserved when requested, the structured `uav:nodes` projection is used when the readable vocabulary is incomplete, and NodeSet2 is otherwise synthesized from readable WoT terms. Unmapped JSON members survive a round trip as pointer-addressed residue (#4131). ## Source generation - Improve the NodeSet to ModelDesign conversion and the generated node state (#4132). - Generate OPC UA models directly from WoT Thing Description files (#4134). ## Server - Add live NodeManager shadow and immediate reload. A reload materializes a new generation beside the active one and switches atomically; the superseded generation is retired gracefully so existing MonitoredItems keep being served until they drain, or immediately when the caller asks for it. Includes the request admission and drain machinery that lets an orderly shutdown wait for admitted requests instead of tearing down underneath them (#4147). - Make subscription transfer between sessions transactional, so a failed transfer leaves neither session holding a partially moved subscription (#4150). ## xRegistry - Allow clients to use explicit registry roots rather than assuming a well-known location (#4130). ## WoT Connectivity - Add the WoT Connectivity 1.1 information model: a registry-first revision layered on the abstract xRegistry base model, incorporating the complete published OPC 10100-1 v1.02 surface as deprecated nodes in the same namespace so existing 1.02 clients keep working (#4139). - Add the protocol binding abstractions and planners that compile WoT forms into executable plans (#4140). - Add the HTTP, Modbus, OPC UA and MQTT binding executors (#4144). - Add the registry and its stores, including document versioning, validation, dependency resolution and refresh (#4146). - Add the registry client (#4142). - Materialize WoT Thing Descriptions into the server address space, deriving types from Thing Models and instances from Thing Descriptions (#4154). - Restore the WoT asset registry bridge so the deprecated 1.02 asset surface is backed by the registry (#4171). - Add the WoT Connectivity samples and complete the documentation (#4156), and restore the remaining coverage and docs (#4172). ## New projects `src/Opc.Ua.WotCon.Bindings`, `src/Opc.Ua.WotCon.Bindings.Mqtt`, `tests/Opc.Ua.WotCon.Bindings.Tests`, `tests/Opc.Ua.WotCon.Samples.Tests`, and the `samples/WotCon` AggregationServer, AggregationClient and FlatTagServer samples. ## Constituent pull requests Merged into this branch top-down, each independently approved: | PR | Title | | --- | --- | | #4128 | [Types] Complete ExtensionObject decoding and add an atomic file replace capability | | #4131 | [Types] Add lossless conversion between WoT documents and NodeSet2 | | #4132 | [SourceGeneration] Improve NodeSet to ModelDesign conversion and generated node state | | #4134 | [SourceGeneration] Generate OPC UA models from WoT Thing Description files | | #4147 | [Server] Add live NodeManager shadow and immediate reload | | #4150 | [Server] Make subscription transfer between sessions transactional | | #4130 | [XRegistry] Allow clients to use explicit registry roots | | #4139 | [WotCon] Add the WoT Connectivity 1.1 information model | | #4140 | [WotCon] Add the WoT protocol binding abstractions and planners | | #4144 | [WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding executors | | #4146 | [WotCon] Add the WoT Connectivity registry and its stores | | #4142 | [WotCon] Add the WoT Connectivity registry client | | #4154 | [WotCon] Materialize WoT Thing Descriptions into the server address space | | #4156 | [WotCon] Add the WoT Connectivity samples and complete the documentation | | #4171 | Restore WoT asset registry bridge | | #4172 | Restore WoT coverage and docs | Supersedes #4093. ## Follow-up The WoT Connectivity and WoT Binding drafts have moved to 1.1-draft2 since this work was authored. Aligning with that revision - the removed group vocabulary, the new projection/View construct, the remaining model vocabulary terms and the two new portable-identity validation rules - is tracked separately and will follow in its own pull request. ## Validation Built for every target framework with zero warnings and zero errors, and validated at the tip of the stack with `Opc.Ua.WotCon.Tests` at 999 passed / 0 failed and `Opc.Ua.Server.Tests` at 4024 passed / 0 failed. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8 |
||
|
|
ae241b6625 |
Robotics: the OPC 40010 companion SDK, its client and the robot sample (#4127)
**Stack 5/5** — base #4126. Part of the #4096 split; see #4096 for the overall picture. Closes the API work for #3827. > Review after #4123, #4124, #4125 and #4126. This branch is content-identical to the original #4096 branch, so merging the stack lands exactly what #4096 contained. ## The problem this solves OPC 40010 1.02 defines a rich robot **topology** but **no motion verbs at all** — its actuation surface is the SystemOperation and TaskControl state machines plus a Controller `Programs` directory. Meanwhile [URML](https://github.com/URML-MARS/URML) names OPC UA Robotics as its canonical non-ROS substrate and asks for `MoveTo` / `Grasp` / `Release` methods the companion spec does not define, so today it needs a hand-maintained per-deployment NodeId mapping file. This closes that gap in two clearly separated tiers, and never presents the second as standard. ## Contents **Server** — stock `RoboticsNodeManager`, ordered `IRoboticsModelProvider` composition, `IRoboticsConfigurator` code-behind, and validated fluent topology builders for systems, controllers, motion devices, axes, power trains, motors, gears, drives, safety states, software, task controls and task modules, with the correct OPC 40010 semantic references. **Standard operations** — `AddSystemOperation` and `AddTaskControlOperation` drive the Part 16 state machines. An illegal cause is rejected with `BadInvalidState` before the handler runs; a handler returning a bad `ServiceResult` does not move the machine; and a transition already in flight rejects a concurrent one rather than letting both commit. **Programs** — binds the optional `FileDirectoryType` through the reusable `IFileDirectoryBinder` added in 1/5. **Operation conventions** — opt-in and explicitly non-normative `MoveTo` / `MoveJ` / `MoveL` / `Grasp` / `Release` / `PickFrom` / `PlaceAt` / `SwapTool` / `SetOutput` / `CallProgram`, created **only** in an application-owned namespace — the OPC UA, DI, IA and Robotics namespaces are rejected — plus a generic `AddOperation<TRequest, TResponse>` extension point. **Client** — `RoboticsClient` extends the DI client, is subtype aware throughout, and provides typed topology snapshots, the standard operation and task-control clients, `ProgramsAsync`, streaming observation and a DI factory. Verbs resolve by BrowseName, so no NodeId mapping file is needed. **Sample** — `MinimalRobotServer` composes five models (Robotics, IA, DI, OpenUSD, RSL/GPOS) purely through `IRoboticsModelProvider` contributions, which is the real proof that the stock hosting is sufficient. It follows the published KUKA KR 16-2 kinematics and axis limits with an eased pick-and-place cycle. Also restores the vendor-subtype scanner test held back from 2/5, which uses the Robotics NodeSet as its fixture. ## Defects found and fixed along the way - **`ControllerType.CurrentUser`** is Mandatory in OPC 40010 but was neither built nor validated. Now has a builder and a validation error. - **`MotionDeviceType.TaskControlReference`** was absent because it must target a `TaskControlOperationType` instance that did not exist. Now wired. ## Validation `dotnet build UA.slnx` — 0 errors, 0 warnings. | Suite | Result | | --- | --- | | Opc.Ua.Robotics.Tests | 103 / 0 | | Opc.Ua.OpenUsd.Tests | 635 / 0 | | Opc.Ua.Di.Tests | 342 / 0 | | Opc.Ua.SourceGeneration.Tests | 94 / 0 | | Opc.Ua.Aot.Tests (NativeAOT) | 120 / 0 | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485 |
||
|
|
0357380335 |
Source generator: typed NodeSet method signatures and referenced model accessors (#4124)
**Stack 2/5** — base #4123. Part of the #4096 split; see #4096 for the overall picture. > Review after #4123. The diff shown against `master` will include #4123 until that merges. ## What this is Source generator only; no runtime library changes. ## Contents - **Typed NodeSet method signatures.** A method imported from a NodeSet now gets a typed signature. Methods that share a declared signature are grouped onto one generated `MethodType`, named after its owner when the plain name would collide, so two unrelated `Execute` methods no longer share a class. - **An override that keeps the signature it inherits keeps the declaration it overrides.** Pointing `MethodDeclarationNode` at the override itself made `MethodDeclarationId` self-referential. OPC 10000-4 lets a caller invoke a method with the NodeId of its declaration in the ObjectType *or any supertype*, and `MethodState.MethodDeclarationId` is what the server matches on, so `Call` stopped resolving and returned `Bad_MethodInvalid` for every alarm subtype. An override that genuinely changes its signature still becomes its own declaration. - **`StateNumbers` / `TransitionNumbers` were emitted empty for every NodeSet-sourced model**, because such children arrive as `VariableDesign` rather than `PropertyDesign` and were filtered out. Any companion model built from a NodeSet was writing `StateNumber = 0` on the wire — a Part 16 defect well beyond the models in this stack. The ModelDesign path is unchanged. - **Referenced models can contribute fluent accessors**, carried by a model dependency payload so a downstream model can extend an upstream one. ## Notes for the reviewer - `tests/Opc.Ua.SourceGeneration.Tests` is left at its `master` content here. Its new vendor-subtype test uses the Robotics NodeSet as its fixture, so it can only land with the Robotics change at the top of the stack (5/5). - `PumpInstanceNodeIdRegressionTests` gains six `InputArguments`/`OutputArguments` entries for argument-less standard condition methods. Typed method signatures materialise those properties from the `ConditionType` declaration even where the method takes none. That is a real deviation, pinned rather than hidden so it stays visible; it is worth a follow-up. ## Validation `dotnet build UA.slnx` — 0 errors. | Suite | Result | | --- | --- | | Opc.Ua.SourceGeneration.Core.Tests | 3739 / 0 | | Opc.Ua.SourceGeneration.Tests | 81 / 0 | | Opc.Ua.Di.Tests | 304 / 0 | | Opc.Ua.History.Tests | 506 / 0 | | Opc.Ua.Gds.Tests | 1095 / 0 | History and GDS are listed because they are what the method-declaration fix above is load-bearing for: without it they fail with 10 and 370 errors respectively. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485 |
||
|
|
76d33cb139 |
Remove SourceGenerator.Foundations, ship analyzers under Roslyn-versioned folders for VS2022 and VS2026, and validate the packages (#4120)
# Description The two OPC UA source generators were built on `SourceGenerator.Foundations` (SGF), whose only job was getting the generator's dependency closure loaded inside the compiler. It does so with a **process-wide, first-generator-wins `AppDomain.AssemblyResolve` hook backed by a non-concurrent `Dictionary` mutated from `AssemblyLoad` callbacks**. Concurrent compilations in a shared `VBCSCompiler` race it, which is the root cause of the intermittent clean-build failure: ``` CSC : error CS8784: Generator 'StackSourceGeneratorHoist' failed to initialize. ... Exception was of type 'FileNotFoundException' with message 'Could not load file or assembly 'SourceGenerator.Foundations.Contracts, Version=2.0.14.0, ...' ``` The repository already solves that problem deterministically — #4041 made the NuGet packages ship the full closure under `analyzers/dotnet/.../cs`, and `Directory.Build.targets` registers the same closure as `Analyzer` items for in-repo `ProjectReference` consumers. SGF was therefore pure liability and is removed. ## What changed **1. SourceGenerator.Foundations removed.** `ModelSourceGenerator` and `StackSourceGenerator` are plain `[Generator(LanguageNames.CSharp)] IIncrementalGenerator` implementations again. SGF's implicit crash handling is replaced by an explicit `SourceGenerator.Guard` that reports `MODELGEN003` / `STACKGEN003` and rethrows cancellation. Its logger sink was already dead code and is dropped. `Opc.Ua.SourceGeneration.Stack.dll` shrank from **18 MB** (embedded closure) to **29 KB**. **2. Analyzers now ship under Roslyn-versioned folders.** `analyzers/dotnet/roslyn4.14/cs` and `analyzers/dotnet/roslyn5.0/cs` instead of `analyzers/dotnet/cs`, driven by `roslyn.props` at the repository root, with new `*.Pack` projects owning the package identity. The versioned folder matters: a host older than the band **skips** the analyzer instead of loading it and failing inside generator initialization. **3. Dependencies flow automatically.** Both delivery paths discover the payload by globbing the generator's output directory, so adding a package to a generator's dependency tree requires **no** `.csproj`, `.targets`, `.nuspec` or validation-script edit. Verified by temporarily adding a `PackageReference` and watching it appear in the payload with no other file touched. ## Two Roslyn bands: VS2022 and VS2026 Analyzers ship twice, once per band. The .NET SDK loads the highest band its compiler supports and **silently skips** anything above it, so a single-band package leaves every consumer below that band with no diagnostics and no generated code. | Band | Built against | Loaded by | | --- | --- | --- | | `roslyn4.14` | `Microsoft.CodeAnalysis.CSharp 4.14.0` | Visual Studio 2022 17.14+ / .NET 9 SDK | | `roslyn5.0` | `Microsoft.CodeAnalysis.CSharp 5.0.0` | Visual Studio 2026 18.0+ / .NET 10 SDK | Getting this wrong is invisible, which is most of why it took three attempts. The rule: > An analyzer must reference the **lowest** `System.Collections.Immutable` / `System.Reflection.Metadata` across all hosts it claims to support, and must never ship them. .NET binds a reference *upward* but never downward. Every failure mode is a compiler **warning**: | Diagnostic | Cause | | --- | --- | | `CS9057` | Built against a newer Roslyn than the host, so it is skipped entirely | | `CS8784` | Loaded, then `MissingMethodException` - a shipped S.C.I bound a second `ImmutableArray<T>` | | `CS8032` | Analyzer instance could not be created | This PR first shipped a `roslyn4.8` band that **never loaded** - confirmed against a real Roslyn 4.8 compiler: ``` warning CS8784: Generator 'ModelSourceGenerator' failed to initialize ... MissingMethodException: Method not found: 'IncrementalValueProvider`1<ImmutableArray`1<!!0>> IncrementalValueProviderExtensions.Collect(IncrementalValuesProvider`1<!!0>)' ``` That band was removed, and **this is not a regression**: master had the same defect, pinned to Roslyn 4.14 while referencing `System.Collections.Immutable 10.0.0.0`, so no 4.x host ever worked there either. The band that landed instead is `4.14`, and it is nearly free: `Microsoft.CodeAnalysis 4.14` depends on the **same S.C.I 9.0.0 as 5.0.0** and its `csc` runs on `net9.0`. Both bands therefore share `$(RoslynRuntimeVersion)`, so `Opc.Ua.Types` and `Opc.Ua.SourceGeneration.Core` are compiled **once** and serve both; only the five Roslyn-referencing assemblies get a second build, each from a 12-line project that glob-links the primary sources and overrides `RoslynApiVersion`. Going lower still - 4.8 wants S.C.I 7.x - would mean rebuilding that whole closure, `Opc.Ua.Types` included, and remains out of scope. (`System.Collections.Immutable 7.0.0` also does not resolve in this repository: `NU1603`, 8.0.0 substituted.) **Nothing else in the repository exercises the down-level payload**, and since every failure is a warning, a broken band would ship silently - exactly as the 4.8 one did. So `validate-source-generator-packages.ps1` now restores a real `Microsoft.Net.Compilers.Toolset 4.14.0`, runs its `csc.dll` over the packed `roslyn4.14` payload, and asserts **both** that none of `CS9057`/`CS8784`/`CS8032`/`CS8034` is emitted **and** that `/reportanalyzer` lists the generator as having executed - silence alone would also pass if the generator were never handed to the compiler. The migration analyzer, whose payload comes from a hand-written nuspec rather than `SourceGeneratorPack.targets`, gets the same check plus a band/assembly manifest assertion. To stop the trap recurring, `Microsoft.CodeAnalysis*`, `System.Collections.Immutable` and `System.Reflection.Metadata` are excluded from both the package payload and the in-repo analyzer closure, and asserted against in the validation script. `roslyn.props` and `docs/DeveloperGuide.md` document what adding a band requires. ## The shipped package set is unchanged Evaluating `PackageId`/`IsPackable` across every project in `preview-pack.slnx` gives the **same 47 package ids** on `master` and on this branch, and a full Release build plus `dotnet pack --no-build` produces exactly those 47. Only the owning project changed: | Package | master | this PR | | --- | --- | --- | | `…Opc.Ua.SourceGeneration` | `Opc.Ua.SourceGeneration.csproj` | `Opc.Ua.SourceGeneration.Pack.csproj` | | `…Opc.Ua.SourceGeneration.Stack` | `Opc.Ua.SourceGeneration.Stack.csproj` | `Opc.Ua.SourceGeneration.Stack.Pack.csproj` | `.azurepipelines/expected-packages.txt` pins the expected set and the validation script fails when the packed output drifts. ## Two new package checks **`build/<PackageId>.props` naming.** NuGet only auto-imports that exact name, and the file declares every `CompilerVisibleProperty` / `CompilerVisibleItemMetadata` the model generator relies on. It was previously packed as `build/OPCFoundation.Opc.Ua.SourceGeneration.props`, which does **not** match the package id — so **no NuGet consumer ever imported it**, and every `ModelSourceGenerator*` property and `AdditionalFiles` metadata was silently invisible. In-repo projects were unaffected because they import it by relative path. **An end-to-end consumer that actually drives the generator.** The pre-existing clean-consumer check only proved the analyzer *loads*. `Test-SourceGeneratingConsumer` builds a standalone project that feeds a real NodeSet2 in as an `AdditionalFile`, pins a `ModelSourceGeneratorPrefix` that cannot be derived from the model, and references the emitted types from hand-written code. It deliberately does **not** import the props by path, so the package's auto-import is what is under test. Both checks were verified to fail against a package rebuilt with the misnamed props. ## CI: parallelism re-enabled `buildandtest.yml` pinned three `dotnet build` invocations to `/p:UseSharedCompilation=false` and `-maxcpucount:1`. `dotnet build` defaults to all logical processors, so `-maxcpucount:1` actively disabled parallelism. Both flags were introduced silently in unrelated PRs (`f358078b1`, `208c601b0`) and suppress exactly the conditions the SGF race needed — now structurally gone. Compilation is ~72 % of what that workflow executes (233 of 323 job-minutes in run `29916768976`), so the queue-independent `Build` step durations are the baseline: | Measurement | Baseline | | --- | --- | | `Build` step, 26 `test-*` jobs | mean 5.53 min, sum 143.9 min | | `build-linux-all-tfm` | 56.3 min | | `build-windows-all-tfm` | 32.5 min | `codeql-analysis.yml` keeps the flag — the CodeQL tracer requires it. ## Incidental fixes - `Microsoft.CodeAnalysis.CSharp` declares an exact `[version]` range on `Microsoft.CodeAnalysis.Common`, so with `CentralPackageTransitivePinningEnabled` the central pins must move in lockstep or restore fails with `NU1109`. They now derive from `$(RoslynApiVersionVS2026)`. - `MigrationGeneratorTests` created its generator driver without parse options — silently fine under Roslyn 4.14, `Inconsistent language versions` under 5.0. - The migration analyzer props injected `<Analyzer>` items by hand from `analyzers/dotnet/cs`; NuGet registers analyzers itself and selects the matching folder, so the injection was obsolete and would now force the wrong assembly to load. - Exception diagnostics reported `ex.StackTrace` (null for an exception that never left its throw site, and carrying neither type nor inner chain) — now `ex.ToString()` at all three call sites. - The pack guard tested the flattened assembly list, so it only fired when *no* variant produced output; it now checks each declared variant contributed assemblies. - `GeneratePackageOnBuild` is disabled for the generators; packing happens in CI or on an explicit `dotnet pack`. ## Note for reviewers **Building this repository requires a Roslyn 5.x host** (the .NET 10 SDK or Visual Studio 2026). That is already the documented prerequisite and every CI leg installs `10.0.x` last, but it is now a hard requirement. ## Related Issues _No tracking issue: this is a follow-up to the CS8784 clean-build failure reported against #4041, which fixed the packaged delivery path but left the in-repo project-reference path relying on the SGF resolver._ - Follow-up to #4041 ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [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. ### Verification detail | Check | Result | | --- | --- | | `dotnet build UA.slnx` (clean, and after merging master) | 0 errors; 18 warnings, all pre-existing `CA1873` in `Opc.Ua.Client` | | `Opc.Ua.SourceGeneration.Tests` | 81/81 on net10.0 and net48 | | `Opc.Ua.SourceGeneration.Stack.Tests` | 92/92 on net10.0, 89/89 on net48 | | `Opc.Ua.SourceGeneration.Core.Tests` | 3722 passed, 8 skipped | | `Opc.Ua.MigrationAnalyzer.Tests` | 121/121 on net10.0 and net48 | | Package id set, master vs branch | identical, 47 packages | | Release build + `dotnet pack --no-build` | 47 packages, matching the manifest; both `roslyn4.14` and `roslyn5.0` present; no host-provided assemblies shipped | | `validate-source-generator-packages.ps1` | passes, including manifest, props-naming, end-to-end generator consumer and down-level host checks | | Down-level band under real csc 4.14.0 | all three analyzer packages load **and run** | | Negative tests | misnamed props → both checks fail as designed; hidden variant output → pack guard fires; 5.0 build placed in `roslyn4.14` → real `CS9057`; non-running generator name → execution assertion fires; expecting a `roslyn4.8` band → manifest check fires | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9ed53262-5cfa-429a-820e-bf6146a00d8b |
||
|
|
cfd15f08e1 |
Assign instance NodeIds in generated CreateOrReplace<Child> helpers (#4104)
# Description
The model source generator emits two families of instance helpers with
inconsistent NodeId behaviour:
* `ISystemContext.CreateInstanceOf<Type>(parent, browseName)` rebases
the whole materialised subtree onto per-instance NodeIds.
* `<Owner>.CreateOrReplace<Child>(context, replacement)` — the plumbing
behind `NodeState.CreateChild` / `ReplaceChild` — did not. The child it
produced was either bare (null NodeId, no browse name) or kept the
type-level NodeId of the replacement.
Any node manager mixing the two had to hand-roll a recursive re-stamp
before registration, or sibling instances collided on NodeIds — a
failure that only surfaces at registration time.
`samples/PumpDeviceIntegrationServer` did exactly this.
## What changed
**Generator** (`tools/Opc.Ua.SourceGeneration.Core`)
* `CreateOrReplace<Child>` gains `bool assignInstanceNodeIds = true`.
When enabled and the context supplies an `ISystemContext.NodeIdFactory`,
a child whose NodeId is null or still equal to its declaration NodeId —
and its descendants — are rebased through
`NodeInstanceExtensions.AssignInstanceNodeId` /
`AssignInstanceChildNodeIds`, with the owner as reference root. **A
NodeId the caller already assigned is never overwritten.**
* The helper also stamps the new child's `SymbolicName` / `BrowseName` /
`DisplayName`. Without an identity, a path-based `INodeIdFactory`
derives `{parent}_` for *every* child of a parent, so the assignment
would have introduced collisions rather than removed them. `BrowseName`
stamping is guarded on `context.NamespaceUris != null` — event-reporting
paths pass a context without a namespace table.
* All generated `Create*` factory call sites pass
`assignInstanceNodeIds: false`: they build declaration subtrees whose
NodeIds must stay at their type-level values, and the enclosing
`CreateInstanceOf<Type>` factory rebases the finished subtree in a
single pass.
* The `CreateInstanceOf<Type>` rebase guard drops the `parent != null`
requirement — supplying a `browseName` is what marks a dynamically
materialised instance. Calling it without one (as the generated
`NodeStateActivator`s do, and when replacing a well-known singleton)
still keeps the declaration NodeIds.
* `replacement` is now emitted as `BaseInstanceState?` on
`CreateOrReplace<Child>` and on the generated `FindChild` override,
matching the documented contract and the base `NodeState.FindChild`
signature.
**Hand-written twins** —
`MethodState.CreateOrReplace{Input,Output}Arguments` and
`BaseDataVariableState.CreateOrReplaceEnumStrings` gain the same
parameter and behaviour, sharing a new internal
`NodeInstanceExtensions.AssignNewChildInstanceNodeIds`. This is also
required so the generated `public new` overrides keep hiding a matching
base signature (otherwise `CS0109` with `TreatWarningsAsErrors`).
**Sample** — `PumpDeviceIntegrationServer` now passes the real
`DeviceSet` parent to the factory and drops `AssignChildNodeIds` plus
the manual root stamp.
**Docs** — new *"Materialising instances at runtime — NodeId
assignment"* section in `SourceGeneratedNodeManagers.md` and a refreshed
`DeviceIntegration.md`.
## Design note: why not the guard proposed in the issue
Gating on *"the owner already has an instance NodeId"* cannot be
implemented as `owner.NodeId != <ownerTypeNodeId>`. The generated child
factories call `<Owner>.CreateOrReplace<Child>` on **declaration**
instances — `CreatePumpType_Operational` runs on a
`FunctionalGroupState` whose NodeId is `ObjectIds.PumpType_Operational`,
not `ObjectTypeIds.FunctionalGroupType` — so the comparison would be
true while the *type template* is being materialised (`forInstance:
false`) and would rewrite the address space's declaration NodeIds. The
explicit opt-out separates the two callers unambiguously.
## Compatibility
Adding an optional parameter is source-compatible. Behaviour changes for
callers of `NodeState.CreateChild` / `ReplaceChild` when the context
carries a `NodeIdFactory`: the new child now carries a browse name and a
per-instance NodeId instead of nothing.
## Related Issues
- Fixes #4099
> [!NOTE]
> The issue asks to delete `AssignChildNodeIds` from
`samples/MinimalRobotServer`. That sample does not exist on `master` —
it only lives on the still-open #4096 branch and carries no such helper.
The equivalent boilerplate on `master` is
`PumpDeviceIntegrationServer/PumpNodeManager`, which this PR cleans up.
Details in [this
comment](https://github.com/OPCFoundation/UA-.NETStandard/issues/4099#issuecomment-5102275507).
> [!NOTE]
> One commit is an unrelated one-line-scope build-hygiene fix: two
`CA2000` warnings in `ConsoleReferenceClient` where subscription
ownership transfers to the session. Happy to split it out if preferred.
## Checklist
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.
### Tests added
**Generator emission** (`NodeManagerGeneratorTests`) —
`CreateOrReplaceChildAssignsInstanceNodeIdsByDefault`,
`TypeFactoriesOptOutOfCreateOrReplaceNodeIdAssignment`,
`CreateInstanceOfFactoriesRebaseWithoutAnExplicitParent`.
**Helper behaviour** (`NodeInstanceExtensionsTests`) —
`CreateOrReplaceArgumentsAssignsInstanceNodeIds`,
`CreateOrReplaceArgumentsKeepsCallerAssignedNodeId`,
`CreateOrReplaceArgumentsHonoursTheAssignmentOptOut`,
`CreateOrReplaceEnumStringsAssignsInstanceNodeIds`.
**Pump DI server regression** (`PumpInstanceNodeIdRegressionTests`,
added on review feedback):
| Test | Asserts |
| --- | --- |
| `ConfiguredPumpExposesTheFullModelSurface` | The exact set of **77
model identifiers** a configured pump materialises. |
| `EveryPumpInstanceExposesTheSameGeneratedModelSurface` | A second pump
exposes the identical generated surface, minus what the fluent
`Configure` pass adds. |
| `MaterialisedPumpNodeIdsComeFromTheNodeIdFactory` | Every NodeId is
`{parentIdentifier}_{symbolicName}` in the parent's namespace — minted
by `PumpNodeManager.New`. |
| `MaterialisedPumpNodeIdsAreUniqueAcrossInstances` | No NodeId is null
or shared between two instances of the type. |
| `GeneratedFactoryAssignsInstanceNodeIdsBeforeRegistration` | The
factory output already carries per-instance NodeIds before
`AddPredefinedNodeAsync` applies its defensive rebase. |
| `AlarmSubtreeKeepsStandardDeclarationNodeIds` | Pins the alarm
subtree's standard-namespace ids — a pre-existing gap outside the
generated helpers. |
The 77-identifier baseline was captured by running the same subtree walk
against the pre-change commit and diffing it against the post-change
run: **identical** — same node set, same NodeIds.
### Local test runs
| Suite | net10.0 | net48 | net472 |
| --- | --- | --- | --- |
| `Opc.Ua.SourceGeneration.Core.Tests` | 3722 passed | 3721 passed | — |
| `Opc.Ua.SourceGeneration.Tests` | 81 passed | 81 passed | — |
| `Opc.Ua.SourceGeneration.Stack.Tests` | 90 passed | — | — |
| `Opc.Ua.Types.Tests` | 8083 passed | 8076 passed | — |
| `Opc.Ua.Di.Tests` | 303 passed | 303 passed | 277 passed |
| `Opc.Ua.Core.Tests` | 4134 passed | — | — |
| `Opc.Ua.Gds.Tests` | 1095 passed | — | — |
| `Opc.Ua.History.Tests` | 506 passed | — | — |
| `Opc.Ua.Server.Tests` | 3626 passed, 1 failed | 3574 passed, 1 failed
| — |
The single `Opc.Ua.Server.Tests` failure —
`ServerFluentApiHostingTests.ConfigureApplicationBuildsSharedClientAndServerConfigurationAsync`
— is pre-existing and unrelated; it reproduces on a stashed clean
`master` tree.
> [!NOTE]
> The source-generation test harness compiles generated model code
against a hand-written `Opc.Ua` API stub
(`tests/Opc.Ua.SourceGeneration.Core.Tests/CompilerUtils.cs`, shared by
three test projects). Its 73 `CreateOrReplace<Child>` declarations were
updated to the new signature — without that, every generated call site
passing `assignInstanceNodeIds: false` failed to bind.
`NodesetMethodArgumentTests` pinned the exact emitted call and was
updated to the new one for the same reason as the
`NodeStateGeneratorTests` expectations.
> [!NOTE]
> `master` has been merged into this branch. The only conflict was in
`NodeStateGeneratorTests.cs`, where master rewrapped the expected
call-site strings while this branch appended `assignInstanceNodeIds:
false` to them — kept master's wrapping and re-applied the argument.
Master's two new templates (`Create_MethodArguments`,
`Assign_MethodArgumentValues`) call
`CreateOrReplace{Input,Output}Arguments` while building a declaration
subtree, so they now pass `assignInstanceNodeIds: false` to preserve
their stated intent of leaving the property node's identity untouched.
> [!TIP]
> Changing a source generator leaves dependent projects' cached
generated output stale. If a local build reports `CS1739: ... does not
have a parameter named 'assignInstanceNodeIds'` or `CS0109` on a
generated `CreateOrReplace*`, run `dotnet build <project> -t:Rebuild`.
Clean CI builds are unaffected.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
|
||
|
|
5b9bcedc62 |
Add OPC UA ISA-95 Common Model and Job Control support (#4089)
# Description Adds first-class support for the OPC UA ISA-95 companion specifications: - OPC-10030 Common Model. - OPC-10031-4 Job Control V1. - OPC-10031-4 Job Control V2. The change introduces `Opc.Ua.ISA95`, `Opc.Ua.ISA95.Client`, and `Opc.Ua.ISA95.Server`, together with a NativeAOT-compatible minimal server sample and comprehensive tests. ## Highlights - Compiles the three canonical NodeSets and identifier mapping CSV files through the source generator. - Adds NodeSet identifier mapping validation, typed method arguments/results, companion event records, and generated method argument nodes. - Provides typed Common Model builders with normative relationships and an injectable `GeoSpatialLocationType` provider. - Implements lossless V1/V2 Job Control providers over a shared version-neutral store, including state paths, status events, comments, response queries, catalog changes, and cohesive DI registration. - Adds subtype-aware typed clients, standalone encodeable registration, `IIsa95ClientFactory`, and V2 status-event streaming. - Adds the `MinimalIsa95Server` sample, package documentation, and an ISA-95 guide with conformance and HA guidance. - Tracks typed RSL/GPOS integration as follow-up issue #4095. ## Validation - `Opc.Ua.ISA95.Tests`: 137/137 passed on `net10.0` and `net48`. - Source-generator suites: 81/81 model, 90/90 stack, 3718 passed / 8 skipped core. - `EventRecordFieldReaders` feedback tests: 6/6 on `net10.0` and `net48`. - `EventRecordFieldReaders` changed-method patch coverage: 100% line coverage (36/36) and 100% branch coverage (22/22). - `Opc.Ua.ISA95`, `.Client`, and `.Server` build across all configured target frameworks without new diagnostics. - Cobertura line coverage: 88.08% model, 90.04% client, 91.19% server. - NativeAOT sample publishes and passes a live typed V2 Store/status-event validation. - The ISA-95 NativeAOT smoke test passes in the source-generated native test executable. - OPC UA KB NodeSet validation reports zero errors for Common, V1, and V2. The broader NativeAOT executable still reports unrelated existing GDS and complex-type test failures; the ISA-95-specific native test passes. ## Node state generator fixes Generating method arguments inline for imported nodesets regressed every model that already emits `InputArguments` / `OutputArguments` child node factories: - The inline block ran before the child factories. `MethodState.CreateOrReplaceInputArguments` keeps the property that was assigned first, so the child carrying the well-known NodeId was dropped and a NodeId-less placeholder remained in the address space. This broke browsing (reference descriptions without a target) and made method arguments unreachable at their well-known NodeIds. - The `MethodDeclarationId` fallback keyed on an autogenerated display name redirected every declared method of the standard address space to its own NodeId, which made method calls fail. The inline block is now emitted after the child factories and reduced to a value assignment when a child factory exists. The value is only re-emitted when an argument references a namespace other than OPC UA, because the value of an imported property is the verbatim XML of the source nodeset whose namespace indices do not survive the import. The `MethodDeclarationId` fallback is restricted to declarations without a resolvable NodeId. The generated OPC UA address space is byte-identical to `master` again. ## Related Issues - Fixes #4088 - Follow-up: #4095 |
||
|
|
20d9d65a64 |
Fix generated configuration namespace resolution (#4019)
# Description Fix source-generated configuration types whose explicit namespace references constants emitted by the same generator pass. Roslyn cannot resolve those constants while `[DataType]` attributes are analyzed, so the generated XML codec silently fell back to `urn:memorybuffer` / `urn:testdata` while the shipped configuration uses the model namespace URIs. This change: - adds `MODELGEN021` to fail when an explicitly supplied `[DataType.Namespace]` expression cannot be resolved; - uses compile-time-available model namespace URIs for the MemoryBuffer and TestData configuration types; - restores production-shaped XML fixtures and exercises the `UpdateExtension<T>()` / `ParseExtension<T>()` path; - documents the source-generator namespace requirement and migration guidance. Targeted validation passed: - `TypeGeneratorTests`: 17 tests on `net10.0` and `net48`; - `ConfigurationRoundTripTests`: 4 tests on `net10.0` and `net48`. ## Related Issues - Fixes #4014 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received. |
||
|
|
12ebfba603 |
Add ISA95 JobControl and ManagedSession regression coverage (#3998)
## Summary - Adds the official ISA95 JobControl 2.0.0 NodeSet2 as a complete source-generator regression fixture. - Extends the lightweight generated-stack test stub with `NamespaceMetadataState`, required by the complete model. - Adds a deterministic ManagedSession cancellation regression proving that a started connect attempt is stopped. The ManagedSession cleanup implementation and Quickstarts utility coverage now come from `master`; this PR retains the additional deterministic cleanup regression. ## ISA95 fixture provenance and integrity The fixture is byte-for-byte the official file: - Source: `OPCFoundation/UA-Nodeset@e0b5d80cfff698f0276393e28df138a7fbc4b541` - Path: `ISA95-JOBCONTROL/opc.ua.isa95-jobcontrol.nodeset2.xml` - Git blob: `82f008cd126ade54e5f2c737bcc116621a8c3087` - SHA-256: `52327e736beb604c7253a5a5393d1ad525b9ecf35b71fbacb4f429cacb334064` The fixture test retains the OPC Foundation MIT header and verifies the 2.0.0 model/date, 258 nodes (including 134 variables), representative late variables, and that every `ns=1` reference target resolves locally. ## Validation - ManagedSession tests: 17 passing on net10.0; 15 passing and 2 framework-gated skips on net48. - Quickstarts server utility tests: 3 passing on net10.0 and net48. - ISA95 generator and fixture integrity regression: 5 passing on net10.0. |
||
|
|
8538f531e7 | Update paths for the consolidated repository layout | ||
|
|
7cec2d614d | Move repository files to the consolidated layout |