Commit Graph

7 Commits

Author SHA1 Message Date
Marc Schier dc441a8b95 Source generator option to override instance modelling rules with type definition modelling rules (default opt in for stack) (#3799)
## Proposed changes

When the source generator creates code for "instance" definitions (e.g.,
the `Server` object which has `ServerType` as its type definition), it
currently uses the modelling rules specified on the **instance's
children**, which may differ from the **type definition's children**.
For example:

- `ServerType` defines `UrisVersion` as `ModellingRule="Optional"`
- `Server` instance overrides it to `ModellingRule="Mandatory"`

This PR introduces two layers of modelling rule enforcement:

### 1. OPC UA promotion semantics (always active)

Regardless of any configuration, the generator now enforces OPC UA
modelling rule promotion semantics. Instances may only **promote** the
type definition's rule:

| Type Definition Rule | Allowed Instance Overrides |
|---|---|
| `Optional` | `Mandatory` |
| `OptionalPlaceholder` | `Mandatory`, `MandatoryPlaceholder` |
| `MandatoryPlaceholder` | `Mandatory` |

Any other instance override (demotion) is silently rejected and the type
definition's rule is used instead. This applies to both structural code
generation decisions and the emitted runtime `ModellingRuleId`.

### 2. `UseTypeDefinitionModellingRules` option (opt-in)

When enabled, the generator uses the type definition's modelling rules
**unconditionally** — instance overrides are ignored entirely (even
valid promotions). This is useful for stack generation where the type
definition's semantics should be preserved exactly.

- **Enabled by default** for stack generation
(`Opc.Ua.SourceGeneration.Stack`)
- **Disabled by default** for the Roslyn source generator
(`Opc.Ua.SourceGeneration`), configurable via
`ModelSourceGeneratorUseTypeDefinitionModellingRules` MSBuild property

### Key design decisions

- A `TypeDefinitionModellingRule` property on `HierarchyNode` captures
the type definition's original rule before instance overrides, updated
through base→derived type-hierarchy merges but frozen once explicit
instance overrides appear
- A `GetEffectiveModellingRule()` helper with `IsValidPromotion()`
centralizes the rule resolution logic across all structural check sites
and runtime `ModellingRuleId` emission
- Factory method emission (`LoadTemplate_ReplaceChild`) uses the
instance's own modelling rule for mandatory/optional list classification
so children the instance marks Mandatory are always created

## Related Issues

- N/A

### Types of changes

- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] Enhancement (non-breaking change which adds functionality)
- [ ] Test enhancement (non-breaking change to increase test coverage)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected, requires version increase of
Nuget packages)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf).
- [x] I ran tests locally with my changes, all passed.
- [ ] I fixed all failing tests in the CI pipelines.
- [ ] I fixed all introduced issues with CodeQL and LGTM.
- [ ] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added necessary documentation (if appropriate).
- [ ] Any dependent changes have been merged and published in downstream
modules.

## Further comments

### Files changed:

| File | Change |
|------|--------|
| `Schema/ModelDesign.cs` | Added `TypeDefinitionModellingRule` property
to `HierarchyNode` |
| `Schema/ModelDesignValidator.cs` | Populates
`TypeDefinitionModellingRule` during hierarchy merge; updates through
base→derived type merges, freezes on instance overrides |
| `GeneratorOptions.cs` | Added `UseTypeDefinitionModellingRules` option
with full XML doc |
| `NodeStateGenerator.cs` | `GetEffectiveModellingRule()` +
`IsValidPromotion()` helpers; updated structural check sites and runtime
`ModellingRuleId` emission; `NodeToGenerate` extended with
`TypeDefinitionModellingRule` |
| `StackGeneration.cs` | Enabled option for stack generation |
| `ModelCompilationOptions.cs` | Added option + MSBuild property read |
| `ModelCompilation.cs` | Wired option through to `GeneratorOptions` |
| `OPCFoundation.Opc.Ua.SourceGeneration.props` | Registered MSBuild
`CompilerVisibleProperty` |
| `readme.md` | Documented new MSBuild property with promotion semantics
|
2026-05-24 21:00:10 +02:00
Marc Schier 94653f1aee Add a MCP server that enables LLM/Copilot to be a OPC UA client (#3701)
* Opc ua client Mcp Server
* fix for #3699
2026-04-24 19:03:45 +02:00
Marc Schier 39fa3030fa Add a fluent api for server, a sample and generate node managers to use with it. (#3684)
* Add source-generated NodeManagers and fluent wiring API

Adds a fluent INodeManagerBuilder API for wiring per-node callbacks and a model-aware source generator that emits a partial CustomNodeManager2 plus an INodeManagerFactory for each design file.

Opt-in is per class via [Opc.Ua.Server.Fluent.NodeManager] on a partial class (recommended) or project-wide via the ModelSourceGeneratorGenerateNodeManager MSBuild property (legacy). The attribute supports NamespaceUri / Design selectors for multi-design projects and a GenerateFactory toggle. Diagnostics MODELGEN010 / MODELGEN011 surface unmatched bindings and non-partial classes.

Adds the ConsoleBoilerServer NativeAOT sample driving the new pipeline end-to-end with Microsoft.Extensions.Logging.Console and updated docs in Docs/SourceGeneratedNodeManagers.md.

* Add BoilerServer AOT smoke test

Verifies the source-generated Boiler.BoilerNodeManagerFactory loads
the boiler address space, registers its namespace, and dispatches the
fluent OnRead callbacks wired in BoilerNodeManager.Configure.cs.
All 3 new tests pass in JIT mode and against the NativeAOT-published
binary; total Aot.Tests suite: 71/71 pass.
2026-04-22 16:45:07 +02:00
Marc Schier d14cd10a86 Model dependency annotations using source generator + flaky test fixes (#3687)
* Fix publish/dispose race that hangs Client tests

Subscription.Dispose pre-disposed m_publishTimer and m_messageWorkerCts
before calling ResetPublishTimerAndWorkerState, causing Cancel() to throw
ObjectDisposedException and leaving the publish worker stuck. Removed the
duplicate disposal so the reset method is the single owner of those resources,
and guarded Cancel/Dispose with try/catch ObjectDisposedException for defense
in depth.

Session.OnPublishComplete could run after Session.Dispose disposed
m_reconnectLock, throwing ObjectDisposedException from SemaphoreSlim.Wait()
and re-arming via the throttle path indefinitely. Added an early Disposed
guard, wrapped Wait/Release in try/catch ObjectDisposedException, and added
a Disposed guard in QueueBeginPublish so the throttle cannot reschedule
publishes after disposal.

Fixes the macOS net10.0 hang seen in Azure pipeline build 13257
(TransferSubscriptionAsync, ReconnectWithSavedSessionSecretsAsync).

* fix lockout test and use UserName token for SuccessfulAuthenticationClearsFailedAttempts test

After c1e11b1ab made anonymous activations no-op for clearing the lockout
counter, the test must use a real (non-anonymous) credential to demonstrate
that successful authentication resets failed attempts. Use user1/password
with PolicyId=1 (UserName_1 in the reference server config).

* Fix DirectoryCertificateStore cache staleness across instances

When two DirectoryCertificateStore instances point at the same directory (e.g. a CertificateValidator's internal store and a separate store opened by a caller), the directory LastWriteTimeUtc seen by one DirectoryInfo handle does not reliably reflect modifications made through another handle on Windows, even after Refresh(). The mtime-based cache check therefore returned stale entries indefinitely, causing VerifyRejectedCertsDoNotOverflowStoreAsync to flake when the test fixture's RejectedStore failed to observe deletes performed by the validator's internal store.

Add a cheap on-disk file count cross-check in the cache-hit branch: if the count of cert files on disk does not match the cached entry count, force a reload. This avoids re-parsing every certificate when nothing has changed but reliably detects external modifications.

* test: fix keep-alive mock to return Int32 ServerState

The mock for the Server_ServerStatus_State read returned new Variant(0u)
(UInt32). The keep-alive worker expects Int32 (ServerState enum is
wire-encoded as Int32), so ClientBase.ValidateDataValue logged a
BadTypeMismatch warning on every keep-alive throughout the test run.

Real servers always emit Int32 for enum variables (BinaryEncoder
downgrades the enum encoding byte to Int32), so the warning was purely a
test-mock artifact. Switch the mock to new Variant((int)ServerState.Running)
to eliminate the spurious warnings.

* Source generator: emit and consume ModelDependencyAttribute. Emit one [assembly: ModelDependencyAttribute(...)] per model the
  generated assembly emits and per upstream model it transitively
  consumes (new ModelDependencyGenerator in
  Tools/Opc.Ua.SourceGeneration.Core).
* Replace the unused PEReader-based ReferenceMetadataScanner with a
  Roslyn IAssemblySymbol.GetAttributes() scan
  (ReferencedModelDependencyScanner in
  Tools/Opc.Ua.SourceGeneration). Hooks the per-symbol incremental
  cache, works for both PortableExecutableReference and
  CompilationReference, AOT-safe.
* Wire the scanner result through ModelSourceGenerator into
  ModelCompilation. The reduced URI-keyed map is passed to the public
  Generators.GenerateCode extension methods.
* NodesetFileCollection.GetDesignFileListForModel now consults the
  referenced-model map, so transitive nodeset dependencies that are
  satisfied by a referenced assembly no longer raise the
  missing-dependency error.
* Override resolution: when a local AdditionalFile and a referenced
  assembly both declare the same model URI under the same C# prefix
  the local generation is silently skipped (avoids CS0101); different
  prefixes still generate so downstream consumers can intentionally
  override types under a different namespace.
* New diagnostics MODELGEN010 (override skipped) and MODELGEN012
  (multiple referenced assemblies provide same model URI -> tie-break
  on highest version+publication date).
* Bump asserted GeneratedSources counts in ModelGeneratorTests for the
  new *.ModelDependencies.g.cs files (one per generated model).
* Fix unrelated CS1574 broken cref in NodeId.TryParseLongForm xmldoc
  that was blocking the Opc.Ua.Types build.
* Document the new flow in Docs/ModelDependencies.md.
* tests for ModelDependencyAttribute flow

* Refactor recently added "parsing" constructors to ParseLongForm static method. Method delegates to Parse(IServiceMessageContext)

NodeId, ExpandedNodeId and QualifiedName ParseLongForm overloads are now thin wrappers around Parse(context, options) using NodeIdParsingOptions with RequireResolvedUris=true and updateTables=false. Adds the FallbackToStringIdentifier opt-in to preserve the legacy lenient quirk for callers that need it (off by default for strict parity).

Tests for the refactored long-form parsers were added to the existing per-type test fixtures.

* Update nuget dependencies to latest
2026-04-22 10:15:12 +02:00
Marc Schier 09e44453a4 Generate type safe (method) proxies for ObjectTypes inside models (#3685)
Add interop test agent

The new stack-pass ObjectMethodProxyGenerator emits proxies that derive
from Opc.Ua.ObjectTypeClient and reference Session.MessageContext.NamespaceUris.
Neither type exists in the in-memory test compilation, which only references
Opc.Ua.Types.dll. Moving ObjectTypeClient to Opc.Ua.Types would cascade ISessionClient,
IClientBase, ITransportChannel and the generated service interfaces with it, so the
type stays in Opc.Ua.Core and we add a minimal stub to OpcUaCoreStubs instead.

Wire Microsoft.Extensions.Options 10.0.5 + DependencyInjection.Abstractions 10.0.5 into Directory.Packages.props and the Gds.Client.Common project to enable upcoming options-driven configuration work.

 Modernize Opc.Ua.Gds.Client.Common (DI, options, interfaces, async dispose)
2026-04-22 05:59:06 +02:00
Marc Schier c71f2e7733 Fix source generator generated structures with optional fields (#3674)
Split validator load/validate; split DesignFileCollection Targets/Dependencies

ModelDesignValidator.LoadNodes now skips ValidateDictionary when a model is loaded purely as a dependency. The flag is plumbed through LoadModelDesign and LoadDesignFile; the dep loop in ValidateModel passes false, the primary target passes true. This lets callers supply unrelated or reverse dependencies (deps that import the target) without triggering premature validation failures.

DesignFileCollection exposes Targets + Dependencies; generators and tests updated. MergeNamespace preserves FilePath across PublicationDate-based replacement so later loads can still find the source file. Tester simplified to treat every other xml/csv pair as a dependency of each target.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 22:36:53 +02:00
Marc Schier f66ceffd75 Immutable built in types and code generation (#3534)
Generate stack and application code using source generators
Change built in types to readonly structs
  Update localized text implementation to use JSON text serializer
  Status code with symbolic names
  Immutable node ids
  No boxing for all built in types <= 8 bytes in Variant.
Split user identity token and token handling via token handler
Add migration guide for breaking changes
2026-02-12 16:27:07 +01:00