60 Commits

Author SHA1 Message Date
Marc Schier a81d981755 dotnet format sweep (whitespace, IDE, RCS) + code-style enforcer agent fix (#3865)
# Description

Pure code-style sweep across the repo — no behavioral changes. Drives
`dotnet format` info-level diagnostics down on a large slice of the
codebase (Stack, Libraries, Tests, Tools).

Six commits, applied bottom-up in safety order so each batch leaves the
build green:

1. **`style: fix whitespace and final-newline formatting (WHITESPACE,
FINALNEWLINE)`**
   197 hits across ~30 files; pure mechanical cleanup.

2. **`style: apply dotnet format style fixes (IDE rules)`**
IDE-rule batch via `dotnet format style --severity info` —
`var`/explicit type, simplify names, collection expressions, expression
bodies, unused usings, etc.

3. **`style: apply dotnet format analyzers fixes (RCS blank-line
formatting)`**
   Roslynator formatting (RCS0xxx) blank-line and punctuation rules.

4. **`style: convert comments to doc comments and inline local vars
(RCS1181, RCS1124)`**
24 files. RCS1181 promotes `// summary`-style comments above
public/internal members to `/// <summary>` doc comments; RCS1124 inlines
locals used exactly once. Generated `Tools/` files were reverted to keep
the source-generator outputs deterministic.

5. **`docs(agent): correct --diagnostics flag form and verify Roslynator
4.x code fixers`**
Updates `.github/agents/opc-ua-codestyle-enforcer.agent.md` with two
corrections discovered while running this sweep:
- `dotnet format --diagnostics "RULE1 RULE2"` (single quoted,
space-separated) silently fixes nothing on `dotnet format` 10.x — it
parses as one unknown ID and exits 0. The correct form is `--diagnostics
RULE1 --diagnostics RULE2 …`, repeated per rule.
- Roslynator 4.15.0 **does** ship code fixers for
RCS1078/1118/1124/1140/1142/1181/1221/1260 (the previous "no auto-fixer"
list was outdated). RCS1140/1142 default to `Hidden` so need `--severity
info` to surface and fire; RCS1078 requires
`roslynator_empty_string_style = field` (already set in
`.editorconfig`). Only NUnit4002/2046/2010 genuinely lack auto-fixers in
this stack.

6. **`style: format new code from master merge (dotnet format sweep)`**
After merging master into the branch (commit `e3ebc8742`), re-ran the
same three-phase format sweep on the 151 .cs files brought in by the
merge, scoped per project via repeated `--include`. 81 files changed;
same rule families as commits 1–4.
- Hand-fixed `Tests/Opc.Ua.MigrationAnalyzer.Tests/AnalyzerHarness.cs`
for `RCS1174` (redundant `async`/`await` removal) because the auto-fixer
didn't apply for that specific case.
   - Reverted files that the formatter would break:
- `Libraries/Opc.Ua.Server/Server/ServerInternalData.cs` — multi-TFM
analysis disagreement injected `<<<<<<<` / `=======` / `>>>>>>>`
markers.
-
`Tools/Opc.Ua.MigrationAnalyzer.Core/{Core/Stack/Client/ChannelBase.cs,
Core/Stack/Types/IUserIdentityTokenHandler.cs,
Client/Session/Session.cs}` — IDE0005 removed `using
Opc.Ua.Security.Certificates` that IS used (the files are 1.5.378
reference fixtures for the analyzer).
- `Tests/Opc.Ua.Gds.Tests/PushTest.cs` — IDE0002 over-simplified
`OpcUa.ObjectTypeIds` to `ObjectTypeIds`, which then resolves to a
conflicting type.

### What this PR does **not** touch

- No behavioral or semantic logic changes.
- No `.editorconfig` rule promotions (rules stay at their current
severities; this is a backlog-burndown pass, not a rule tightening).
- Generated source under `Tools/` is intentionally excluded.
- Higher-risk rule families (`RCS1090`/`CA2007` `ConfigureAwait`,
`RCS1249` null-forgiving cleanup, `IDE0270`/`IDE0059`) are left for
follow-up sweeps because they need per-TFM build verification or
semantic review.

## Related Issues

_No tracking issue — this is a non-functional style sweep and the
agent-doc correction is a follow-up to the same work. Happy to open one
if the maintainers prefer._

## Checklist

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage. _(N/A — style-only, no
behavioral change)_
- [x] I have added all necessary documentation. _(agent doc updated with
the two corrections)_
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings. _(verified clean on `net10.0` and `net48`; only
pre-existing `NETSDK1005`/`NETSDK1207` failures on Tools/AOT projects)_
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed. _(build
verified on net10 and net48 per batch; full test suite not yet run
locally — relying on CI)_
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: agent <agent@local>
2026-06-10 08:35:44 +02:00
Marc Schier 0fbc900919 Add opt-in SubscriptionRecoveryPolicy for unsolicited Good_SubscriptionTransferred (#3540) (#3847)
# Description

Adds an opt-in `SubscriptionRecoveryPolicy` for both the Classic V1 and
V2 subscription engines that lets the client recover from server-emitted
`Good_SubscriptionTransferred` notifications which arrive against a
freshly created subscription (observed against Kepware after a server
re-initialise, very likely a leak from the pre-restart session's pending
status notifications onto the new session — abetted by Kepware re-using
`SubscriptionId = 1` across generations).

Per [OPC UA Part 4
§5.14.7](https://reference.opcfoundation.org/Core/Part4/v105/docs/5.14.7),
`Good_SubscriptionTransferred` is supposed to be delivered to the *old*
Session whose subscription was transferred away via
`TransferSubscriptions`. The existing V1 behaviour of stopping the
per-subscription publish loop on receipt is therefore
**spec-compliant**; the issue is that some servers deliver the
notification on a Session that is still actively using the subscription,
leaving the per-subscription dispatch silently disabled.

The fix is **opt-in** and **non-breaking**:

- New `SubscriptionRecoveryPolicy` enum (`ReportOnly` default,
`RecreateOnUnsolicitedTransfer`).
- V1 Classic `Subscription.RecoveryPolicy` property; V2
`Subscriptions.SubscriptionOptions.RecoveryPolicy` init property.
- When the policy is `RecreateOnUnsolicitedTransfer`, the SDK drops
queued acks for the dead subscription id (avoiding
`BadSubscriptionIdInvalid` ack errors on servers that re-use ids) and
recreates the subscription in place on the same Session.

## What changed

**Public API additions (non-breaking, defaults preserve current
behaviour):**
- `SubscriptionRecoveryPolicy` enum (`ReportOnly`,
`RecreateOnUnsolicitedTransfer`).
- V1 `Subscription.RecoveryPolicy` property.
- V2 `Subscriptions.SubscriptionOptions.RecoveryPolicy` init property.
- Internal `IMessageAckQueue.DropPendingForSubscription(uint)` and
protected `MessageProcessor.AckQueue` accessor.

**V1 Classic —
`Libraries/Opc.Ua.Client/Subscription/Classic/Subscription.cs`:**
- Replaces the inline `Good_SubscriptionTransferred` branch with
`HandleGoodSubscriptionTransferred`. `ReportOnly` preserves the original
behaviour (stop publish timer/worker); `RecreateOnUnsolicitedTransfer`
triggers `TriggerUnsolicitedTransferRecovery`, guarded by
`m_recreateAfterTransferInProgress` for idempotency.
- Recovery drains pending acks via the new
`ClassicSubscriptionEngine.RemoveAcknowledgementsForSubscription`
**before** resetting the subscription, then runs `DeleteSubscription()`
+ `CreateAsync()` on the same session so the publish worker restarts via
`StartKeepAliveTimer`.
- Discriminator (`Session != null && !Session.Reconnecting && Created`)
defers to the reconnect pipeline if it is already running.

**V2 — `Libraries/Opc.Ua.Client/Subscription/Subscription.cs`:**
- Completes the `OnStatusChangeNotificationAsync` TODO: now always fires
`OnPublishStateChanged(mask)` so `PublishState.Transferred` actually
reaches handlers (previously the no-op override silently swallowed the
flag).
- Under `RecreateOnUnsolicitedTransfer`, calls
`AckQueue.DropPendingForSubscription(deadId)` then existing
`ResetToRecreateAsync` on a worker task — no service calls from the
dispatch path.
- V2 `SubscriptionManager` implements `DropPendingForSubscription` via
drain-and-refill on the prioritised ack channel.

**Tests — `Tests/Opc.Ua.Client.Tests/Subscription`:**
- V1 in `Classic/SubscriptionUnitTests.cs`:
`ReportOnlyPolicyDoesNotRecreateOnUnsolicitedTransferAsync`,
`RecreatePolicyRecreatesSubscriptionOnUnsolicitedTransferAsync`,
`RecreatePolicyCollapsesConcurrentNotificationsAsync`,
`RecreatePolicyDoesNotRecreateWhileSessionReconnectingAsync`.
- V2 in `SubscriptionTests.cs`:
`GoodSubscriptionTransferredRaisesPublishStateTransferredAsync`,
`GoodSubscriptionTransferredRecreatesSubscriptionWhenPolicyEnabledAsync`,
`GoodSubscriptionTransferredCollapsesConcurrentRecreatesAsync`.
- `FakeMessageAckQueue` gained a `DroppedSubscriptions` recorder so the
V2 test can assert ack pruning ran.

**Docs — `Docs/TransferSubscription.md`:**
- New "Recovering from unsolicited `Good_SubscriptionTransferred`"
section with spec context, usage examples for both engines, and the
caveat that recovery is recreate-based — **not** lossless failover; the
server-side retransmission queue and triggering relationships tied to
the invalidated subscription id are not preserved.

## Validation

| Scope | Result |
| --- | --- |
| `Libraries/Opc.Ua.Client` build (`net10.0`, `net48`) | 0 warnings, 0
errors |
| `Tests/Opc.Ua.Client.Tests` build (`net10.0`, `net472`) | 0 warnings,
0 errors |
| New tests on `net10.0` and `net472` | 7/7 pass on each |
| Full subscription unit-test suite (`SubscriptionUnitTests` +
`SubscriptionTests` + `MessageProcessorTests` +
`SubscriptionManagerTests`) | 110/110 pass on each TFM |
| Entire `Opc.Ua.Client.Tests` suite (`net10.0`) | 1268/1268 pass |
| Classic transfer integration tests
(`Tests/Opc.Ua.Subscriptions.Classic.Tests`, `net10.0`) | 20/20 pass |
| V2 transfer compliance tests (`Tests/Opc.Ua.Subscriptions.Tests`,
`net10.0`) | 9/9 pass |

> Marked as **draft** because the maintainer wanted to first confirm
whether the stack is spec-compliant before deciding which knobs to ship;
this PR documents the investigation (stack is compliant), and surfaces
an opt-in recovery policy that addresses the field symptom without
changing the default behaviour. Convert to ready-for-review once the
design intent is acknowledged.

## Related Issues

- Fixes #3540

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: marcschier <marc.schier@github.com>
2026-06-07 17:38:58 +02:00
Marc Schier 2966f693b2 2.0 Migration Analyzer and codefixer with Agent update (#3820)
Introduces `OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer` — a
single NuGet package that ships **three Roslyn components + a runtime
shim** to make the 1.5.378 → 2.0 upgrade as close to "install package,
run `dotnet format`, remove package" as possible:

| DLL | Where | Purpose |
|-----|-------|---------|
| `Opc.Ua.MigrationAnalyzer.dll` | `analyzers/dotnet/cs/` | 19 Roslyn
analyzers (`UA0001`–`UA0022`) covering every breaking-change pattern in
[`Docs/MigrationGuide.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md).
Built against the stable Roslyn 4.14 analyzer API — loads cleanly in
csc.exe (no `Microsoft.CodeAnalysis.Workspaces` reference). |
| `Opc.Ua.MigrationAnalyzer.CodeFixer.dll` | `analyzers/dotnet/cs/` | 14
companion `CodeFixProvider`s; loaded only by Workspaces-aware hosts
(Visual Studio, `dotnet format analyzers`). |
| `Opc.Ua.MigrationAnalyzer.Generator.dll` | `analyzers/dotnet/cs/` |
Roslyn `IIncrementalGenerator` that emits per-consumer `internal sealed
[Obsolete] class <Name>Collection : List<TElement>` shims for every
`<Type>Collection` wrapper the consumer references — including
**arbitrary model-compiled `<UserType>Collection` patterns**, not just
the 30 well-known built-in ones. |
| `Opc.Ua.MigrationAnalyzer.Core.dll` | `lib/<tfm>/` for `net472, net48,
netstandard2.1, net8.0, net9.0, net10.0` | 1.5.378 → 2.0 runtime
compatibility shim. Re-exposes the obsolete extension surface so
1.5.378-style call sites keep compiling against 2.0 with warnings
instead of errors. |

### Migration workflow the package enables

1. **Bump every `OPCFoundation.NetStandard.Opc.Ua.*` PackageReference to
`2.0.*-*`.**
2. **Add the migration package** as a build-only dependency:
   ```xml
<PackageReference
Include="OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer"
                     Version="2.0.*-*"
                     PrivateAssets="all" />
   ```
3. **Run `dotnet build`.** The Core shim DLL re-supplies obsolete
extension members; the source generator covers `<Type>Collection`
references; what remains are `[Obsolete]` warnings + `UA00xx` analyzer
diagnostics + (occasionally) `MIG01` for unresolvable model-compiled
element types.
4. **Apply auto-fixes** in the IDE or from the command line:
   ```bash
   dotnet format analyzers <solution>.sln \
       --diagnostics UA0002 UA0003 UA0004 UA0005 UA0006 UA0007 UA0008 \
                     UA0009 UA0010 UA0012 UA0014 UA0019 UA0020 UA0022 \
       --severity warn
   ```
5. **Walk the residual** `UA0001` / `UA0011` / `UA0015` / `UA0018` /
`UA0021` diagnostics manually — they cover patterns that require human
judgement.
6. **Remove the migration package.** You are now on clean 2.0 with no
shim dependency.

### Source generator (`Opc.Ua.MigrationAnalyzer.Generator.dll`)

Per-consumer Roslyn `IIncrementalGenerator` that emits `.g.cs` files for
every `<Name>Collection` reference in the consumer's compilation that
fails to bind:

- **Catalog override (30 entries)** — pins element types that renamed
across the 1.5.378 → 2.0 boundary: `DateTime`→`DateTimeUtc`,
`Guid`→`Uuid`, `byte[]`→`ByteString`, `XmlElement`→`Opc.Ua.XmlElement`,
plus the primitives and built-in opc types whose element name didn't
rename.
- **Semantic lookup** — for model-compiled `<UserType>Collection`
patterns the model compiler historically emitted for any user-defined
complex type. Strips the `Collection` suffix, runs
`Compilation.GetSymbolsWithName` against the unique element short name,
emits the shim with the fully-qualified type reference.
- **`MIG01` diagnostic** — fires when the element type can't be uniquely
resolved (zero or multiple candidates). User adds the missing `using` or
migrates the site manually.

Each generated file emits a class shaped exactly like the 1.5.378
wrapper:

```csharp
[global::System.Obsolete("'Int32Collection' was removed in 2.0. Use 'List<int>' or 'ArrayOf<int>' instead. (UA0002)")]
internal sealed class Int32Collection : global::System.Collections.Generic.List<int>
{
    public Int32Collection() { }
    public Int32Collection(int capacity) : base(capacity) { }
    public Int32Collection(global::System.Collections.Generic.IEnumerable<int> collection) : base(collection) { }
    public static implicit operator global::Opc.Ua.ArrayOf<int>(Int32Collection? value)
        => value is null ? default : value.ToArrayOf();
}
```

**Design choice — `internal` (not `public`):** the generated shim never
leaks through the consumer's public API surface. Consumers with *public*
methods or properties returning a `<Type>Collection` will hit `CS0050:
Inconsistent accessibility` — the intended signal that the public
surface must migrate to `List<T>` / `ArrayOf<T>` first; internal call
sites keep compiling under the shim so iteration is incremental.

### 19-rule analyzer reference

| Rule | Replaces | Default | Auto-fix |
|------|----------|---------|----------|
| `UA0001` | `Utils.Trace` / `Utils.LogX` | Info | No (telemetry
plumbing requires manual review) |
| `UA0002` | Removed `<Type>Collection` wrappers | Warning | Yes (→
`List<T>` / `ArrayOf<T>`); the source generator keeps the call site
compiling in the meantime |
| `UA0003` | `x == null` on now-struct built-ins | Warning | Yes (→
`x.IsNull`) |
| `UA0004` | `?.` on now-struct built-ins | Warning | Yes (drop the `?`)
|
| `UA0005` | `byte[]` where `ByteString` is expected | Warning | Yes
(append `.ToByteString()`) |
| `UA0006` | `new Variant(object\|DateTime\|Guid\|byte[])` | Warning |
Yes (→ `Variant.From(...)`) |
| `UA0007` | `new NodeId(string)` / `new ExpandedNodeId(string)` |
Warning | Yes (→ `Parse`) |
| `UA0008` | `Session.Call(..., params object[])` | Warning | Yes (wrap
with `Variant.From`) |
| `UA0009` | `[DataContract]` / `[DataMember]` on config extensions |
Warning | Yes (→ `[DataType]` / `[DataTypeField]`) |
| `UA0010` | `using` / `Dispose` on `CertificateIdentifier` /
`UserIdentity` / `IUserIdentityTokenHandler` | Warning | Yes (drop the
disposable) |
| `UA0011` | Sync
`IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}` | Info | No
(async promotion required) |
| `UA0012` | `CertificateFactory.*` static helpers | Warning | Yes (→
instance methods) |
| `UA0014` | `DataValue.IsGood(dv)` static helper | Warning | Yes (→
`dv.IsGood`) |
| `UA0015` | Sync / APM members on GDS / LDS clients | Info | No (async
promotion required) |
| `UA0018` | `CertificateIdentifier.Certificate` getter | Info | No (→
`LoadCertificate2Async`) |
| `UA0019` | `new DataValue(StatusCode[, ts])` | Warning | Yes (→
object-initializer form) |
| `UA0020` | `EncodeableFactory.GlobalFactory` / `Create()` | Warning |
Yes (→ `ServiceMessageContext.Factory` / `Fork()`) |
| `UA0021` | `CertificateValidator` / `CertificateValidationEventArgs`
(structural rename) | Info | No (see migration guide §Certificate
validation) |
| `UA0022` | `ApplicationConfiguration.CertificateValidator` /
`ServerBase.CertificateValidator` property rename | Warning | Yes (→
`.CertificateManager`) |

Plus one generator-only diagnostic:

| Rule | Source | Default | Triggers |
|------|--------|---------|----------|
| `MIG01` | Generator | Warning | Generator can't uniquely resolve the
element type for a `<Foo>Collection` reference (zero or multiple
candidates). The user adds a `using` for the namespace defining `Foo`,
or migrates the call site manually. |

### Runtime shim coverage (`Opc.Ua.MigrationAnalyzer.Core.dll`)

C# 14 `extension` members so 1.5.378 call sites continue to compile:

- **Moved obsolete extensions** the 2.0 libraries no longer carry
inline: `NodeId` / `Variant` / `DataValue` null-check helpers, `Session`
sync helpers, `Subscription` sync helpers, `ApplicationInstance`
helpers, `ServerBase.Start` / `Stop`, `TransportChannel` APM,
`ChannelBase` static factory methods, etc.
- **New shims for genuinely-removed members**:
`EncodeableFactory.GlobalFactory`, `CertificateIdentifier.Certificate`
(throws), sync wrappers for
`IUserIdentityTokenHandler.{Encrypt,Decrypt,Sign,Verify}`, sync + APM
wrappers for the GDS / LDS client APIs, `GlobalDiscoverySampleServer`
1.5.378-shape ctor (in-tree `Libraries/Opc.Ua.Gds.Server.Common/`).

> ⚠ The sync shims wrap their `*Async` counterparts via
`Task.Run(…).GetAwaiter().GetResult()`. Intended as a **migration aid
only** — port to `await` before shipping.

### Repo layout

```
Tools/Opc.Ua.MigrationAnalyzer/                  # analyzer DLL
  Analyzers/UA00xxAnalyzer.cs                    # 19 DiagnosticAnalyzer impls
  Diagnostics/{DiagnosticIds,Descriptors,WellKnownProperties}.cs
  Helpers/{SymbolExtensions,UaSymbols}.cs
  Opc.Ua.MigrationAnalyzer.nuspec                # ships all 3 analyzer DLLs + 6-TFM Core
  OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props

Tools/Opc.Ua.MigrationAnalyzer.CodeFixer/        # code-fix DLL
  CodeFixes/UA00xxCodeFix.cs                     # 14 CodeFixProvider impls

Tools/Opc.Ua.MigrationAnalyzer.Generator/        # source generator DLL  (NEW)
  MigrationGenerator.cs                          # IIncrementalGenerator
  CollectionShimCatalog.cs                       # 30-entry element-type overrides
  Diagnostics/GeneratorDiagnostics.cs            # MIG01

Tools/Opc.Ua.MigrationAnalyzer.Core/             # runtime shim (multi-TFM)
  Client/, Configuration/, Core/, Gds.Client.Common/, Types/, Marker/

Tools/Roslyn.slnx                                # source-gen + migration projects

Tests/Opc.Ua.MigrationAnalyzer.Tests/            # 142 tests
  Analyzers/UA00xxTests.cs                       # 107 analyzer + code-fix tests
  Generators/MigrationGeneratorTests.cs          # 35 generator tests (NEW)
Tests/Opc.Ua.MigrationAnalyzer.Core.Tests/       # 13 runtime-shim tests
```

### Documentation

-
[`Docs/MigrationGuide.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/Docs/MigrationGuide.md)
— §3 Collection Type Migration gains a tip pointing at the generator so
CS0246 is replaced with `[Obsolete]` warnings + UA0002.
-
[`.github/agents/opcua-v20-migration.agent.md`](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/.github/agents/opcua-v20-migration.agent.md)
— Strategy section mentions the three Roslyn components + runtime shim
explicitly, plus the MIG01 diagnostic and the "internal-by-design"
CS0050 hint.

### Central package pinning

`Directory.Packages.props` pins `Microsoft.CodeAnalysis.*` (Common,
CSharp, CSharp.Workspaces, Workspaces.Common) to **4.14.0** — the stable
analyzer API surface. The earlier 5.3 pin was the *csc-internal*
version, which is not safe for analyzer hosts.

### Dogfood story

The full 42-sample dogfood against
`OPCFoundation/UA-.NETStandard-Samples` surfaced a **critical
analyzer-loader bug** in an earlier iteration: the analyzer DLL
initially co-shipped its code-fix providers in one assembly, which
transitively referenced `Microsoft.CodeAnalysis.Workspaces.dll`.
csc.exe's analyzer host ships only `Microsoft.CodeAnalysis.dll` +
`CSharp.dll` in its bincore and **silently swallows** load failures —
net result: zero diagnostics across all samples. Fix landed in commit
`861fa6ee1`:

1. **Split** the assembly: analyzer in `Opc.Ua.MigrationAnalyzer.dll`
(Workspaces-free, csc-safe), code-fixes in
`Opc.Ua.MigrationAnalyzer.CodeFixer.dll` (Workspaces-aware, IDE-only).
2. **Downgrade** to Roslyn 4.14 — the stable analyzer API.
3. `/p:ReportAnalyzer=true` confirms all 19 analyzers initialise and
execute on real consumer code.

This PR's new generator was smoke-tested against a minimal consumer that
exercises all 30 catalog entries + a model-compiled element type + the
unresolvable case:
- 30 catalog patterns + model-compiled `MyThingCollection` → all emit
shims, fire CS0618 with `(UA0002)` marker
- Element-type renames verified: `DateTimeCollection` → `DateTimeUtc`,
`ByteStringCollection` → `ByteString`, `GuidCollection` → `Uuid`, etc.
- `NeverHeardOfItCollection` → `MIG01` fires with help link to the
migration guide

Known compatibility gaps (documented in the migration agent):
- Legacy WinForms `.Net4` sample projects use pre-SDK MSBuild XML and
**ignore** `Directory.Build.targets` `<PackageReference>` injection —
consumers in this format must add the migration package to their csproj
inline.

### Packaging design notes

- Hand-authored nuspec ships 3 DLLs in `analyzers/dotnet/cs/` + 6 TFMs
of Core (shim) DLLs under `lib/`. All 6 OPC UA runtime package
dependencies are declared per-TFM-group.
- `build/OPCFoundation.NetStandard.Opc.Ua.MigrationAnalyzer.props` is
auto-imported by NuGet (file name must match package id exactly) and
injects all three analyzer DLLs as `<Analyzer>` items.
- `DiagnosticIds.cs` / `DiagnosticDescriptors.cs` /
`WellKnownProperties.cs` / `Helpers/*.cs` are **shared via `<Compile
Link=…>`** between the analyzer and code-fixer projects to break what
would otherwise be a NuGet restore cycle. The generator has no compile
dependency on the analyzer; the 30-entry override table is duplicated in
`CollectionShimCatalog.cs` (the analyzer table also drives diagnostic
messages, so the duplication is small and intentional).
- `Opc.Ua.MigrationAnalyzer.Core` is multi-TFM (`net472, net48,
netstandard2.1, net8.0, net9.0, net10.0`) and **non-packable on its
own**; the parent migration package's nuspec consumes each TFM's DLL by
path.
2026-06-05 12:01:53 +02:00
Marc Schier dbc09b8e22 Adopt System.TimeProvider across the stack (fixes #2458) (#3829)
# Description

Adopt
[`System.TimeProvider`](https://learn.microsoft.com/dotnet/api/system.timeprovider)
across the stack so that every place that creates timers, runs duration
math, or schedules periodic work is mockable, deterministic in tests,
and immune to wall-clock changes.

The migration touches Core, Core.Types, Server, Client, PubSub,
Configuration, Gds (server + client), Lds, and the Quickstarts/Console
reference apps. Per-component changes follow the same pattern:

1. Add a `private readonly TimeProvider m_timeProvider;` field.
2. Accept a nullable `TimeProvider? timeProvider = null` as the **last**
constructor parameter (new overload; legacy constructor stays for binary
compatibility and delegates with `timeProvider: null`).
3. Initialise the field with `m_timeProvider = timeProvider ??=
TimeProvider.System;`.
4. Replace `new Timer(...)`, `Stopwatch`, `HiResClock.*`,
`DateTime.UtcNow`-based duration math, and `Task.Delay(delay, ct)` in
timing loops with the matching `m_timeProvider` API (`CreateTimer`,
`GetTimestamp` / `GetElapsedTime`, `GetUtcNow().UtcDateTime`,
`Task.Delay(delay, m_timeProvider, ct)`).
5. Wire the provider through DI: `AddOpcUaServerBuilder` /
`AddOpcUaClientBuilder` register `TimeProvider.System` via
`TryAddSingleton<TimeProvider>()` so tests can swap in
`Microsoft.Extensions.Time.Testing.FakeTimeProvider`.

`HiResClock` itself is kept (so the
`ApplicationConfiguration.DisableHiResClock` schema flag still
round-trips), but **every public member** is now `[Obsolete]` with a
single shared message pointing to the new migration-guide section. The
few internal call sites that still need to set/read `HiResClock` are
wrapped with `#pragma warning disable CS0618`.

### Highlights per component

- **Core / Stack** — `Opc.Ua.Core`, `Opc.Ua.Core.Types`, channel
pipeline (`UaSCBinaryClientChannel`, `ChannelToken`,
`UaSCBinaryChannel`), `ApplicationConfiguration`, certificate
validators, `JwksIssuerKeyResolver`, `TimeProviderExtensions` polyfills
(`GetTimestampMilliseconds`, `GetTickCount`,
`CancellationTokenSource(timeout, timeProvider)`, `Task.Delay(delay,
provider, ct)` polyfill for older TFMs).
- **Server** — session lockout, subscription pipeline
(publish/sample/keep-alive/lifetime), `MonitoredItemQueue`, alarm
sampling rate, `IntervalRunner`, server diagnostics, configuration state
machines, hosted services.
- **Client** — `Session`, managed/V2 subscription engines, reconnect
handlers, channel keep-alive, periodic publishing, complex-type
discovery polling.
- **PubSub** — `IntervalRunner` pipeline, message-receive timers,
MQTT/UDP keep-alive, publishers/readers.
- **Gds / Lds** — `LdsServer`, `LinqApplicationsDatabase` expiry
housekeeping, GDS clients (push, pull), `DirectoryCertificateStore`,
`ConnectionStateMachine`, classic subscription engine.
- **Quickstarts / Console samples** — `SampleNodeManager`,
`ReferenceNodeManager`, `AlarmNodeManager`, `BoilerState`,
`MemoryBufferState`, `PublishedValuesWrites`. Samples resolve via
`(Server as ITimeProviderProvider)?.TimeProvider ?? TimeProvider.System`
so existing ctor signatures stay binary-compatible.
- **Tests** — `Opc.Ua.PubSub.Tests` adds `IntervalRunnerTests`; existing
`SubscriptionTests`, `UaPublisherTests`, and `TestDataSystem` move off
`HiResClock`. Added `Microsoft.Extensions.TimeProvider.Testing` package
for `FakeTimeProvider` (project-load warning surfaces on net48/net472
only — runtime is .NET 8+).

### Migration guide

New section [**Time and Timer abstraction
(`TimeProvider`)**](Docs/MigrationGuide.md#time-and-timer-abstraction-timeprovider)
documents:

- The canonical replacement table (`HiResClock.UtcNow` →
`timeProvider.GetUtcNow().UtcDateTime`, `HiResClock.TickCount64` →
`timeProvider.GetTimestamp()`, `new Timer` → `timeProvider.CreateTimer`,
`Task.Delay(delay, ct)` → `Task.Delay(delay, timeProvider, ct)`, …).
- The `TimeProvider? timeProvider = null` + `??=` constructor pattern.
- DI wiring and how to swap in `FakeTimeProvider` in tests.
- Before/after snippets for `HiResClock.TickCount64`,
`HiResClock.UtcNow`, and `new Timer(...)`.

## Related Issues

- Fixes #2458

## 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.
2026-06-03 22:38:32 +02:00
Marc Schier 49305d9085 V2 subscription engine close gap for test and feature parity with classic engine (#3824)
# Description

V2 subscription engine feature parity round to make the classic engine
eligible for deprecation. Splits the test surface so the classic and V2
engines can evolve independently, ports the gaps the classic-engine
integration tests had been exercising, and lands the public V2 API
additions needed to close them.

- Splits `Tests/Opc.Ua.Subscriptions.Tests` into a classic
(`Opc.Ua.Subscriptions.Classic.Tests`) and a V2
(`Opc.Ua.Subscriptions.Tests`) project to shrink CI time and let the two
engines diverge cleanly.
- Adds the V2 public-surface gaps required by the test ports
(`Subscription.ServerId` on the class, `SetSubscriptionDurableAsync`,
`SendInitialValuesOnTransfer`, unified `OnSubscriptionStateChangedAsync`
handler callback, per-item `ConditionRefreshAsync`, fluent
`LoadSubscriptionsAsync(Stream, …)` and `Subscription.Snapshot()` /
`RestoreAsync()` for save/load + transfer).
- Lands handler-centric design (no `PublishingStopped` property handlers
maintain derived state); always-sequential per-subscription delivery
documented + tested.
- **Adds short and long haul stability tests for the V2 `ManagedSession`
+ subscription engine. Closes #3744** (see "Issue #3744 Haul tests"
below).
- **Migrates SubscriptionStateSnapshot / MonitoredItemStateSnapshot to
`[DataType]` source-generator codecs**.
- `SubscriptionDurableV2Tests.cs` 5 V2 ports of the classic durable test
patterns.
- `V2FollowUpCoverageTests.cs` handler state-change callback, fluent
stream-based `LoadSubscriptionsAsync`, `SendInitialValuesOnTransfer`,
snapshot edge cases (empty / `DataChangeFilter` round-trip / concurrent
  mutation).
- `SubscriptionFailoverV2Tests.cs` forced channel-break with and without
`WithTransferSubscriptionsOnRecreate`.
- `MonitoredItemConditionRefreshLiveV2Tests.cs` ΓÇö live
`ConditionRefresh` against the reference server's event source; asserts
`RefreshStart` / `RefreshEnd` events flow through the handler.

## Fixes

- #3744 Haul tests (V2 ManagedSession + subscription engine)
`Tests/Opc.Ua.Sessions.Tests/ManagedSessionStabilityTest.cs` adds the
short/long haul coverage requested by
[#3744](https://github.com/OPCFoundation/UA-.NETStandard/issues/3744)
plus the bonus fault-injection variant:

| Test | Duration | Category | Purpose |
|---|---|---|---|
| `ShortHaulManagedSessionV2Async` | 2 minutes | `ManagedSessionHaul`
(`[Explicit]`) | Quick local sanity check of the V2 monotonicity
contract. |
| `LongHaulManagedSessionV2Async` | 90 minutes (configurable via
`TEST_DURATION_MINUTES`) | `ManagedSessionHaul` (`[Explicit]`) |
Long-running V2 stability + monotonicity. |
| `LongHaulManagedSessionWithFaultInjectionV2Async` | 90 minutes
(configurable) + fault every `FAULT_INJECTION_INTERVAL_SECONDS` (default
60s) | `ManagedSessionHaul` (`[Explicit]`) | **Bonus** ΓÇö force-closes
the subscriber transport channel periodically; verifies V2 manager
transfers/recreates the subscription and continues monotonic delivery
across reconnects. |

**Pattern.** A writer `ManagedSession` increments a `uint32` counter and
writes it into `Scalar_Static_Mass_UInt32_UInt32_00` every 250ms. A
subscriber `ManagedSession` runs a V2 subscription with a single
monitored item on the same node. `MonotonicCounterHandler` asserts each
received sample is *strictly greater than* the previous one. V2
`ISubscriptionNotificationHandler` per-subscription ordering guarantee.
Skipped values (sampling rate < write rate) are tolerated; duplicates
and reorderings are violations.
2026-06-03 04:39:08 +02:00
Marc Schier 0000513cde Pluggable user-identity provider model (#3807)
# Description

Introduces a pluggable user-identity provider model covering all OPC UA
Part 6 §6.5 mechanisms (Anonymous, UserName, X509, IssuedToken/JWT) on
both client and server, with full
`Microsoft.Extensions.DependencyInjection` integration, plus the Part 12
§9 `AuthorizationService` two-phase token-request flow **including
refresh-token rotation**, the Part 12 §8 KeyCredential Push model, and a
clean provider model for OPC 10000-12 §7.2 `ApplicationSelfAdmin`. The
work is split across phase-by-phase commits so it can be reviewed
independently; nothing here changes wire-level behaviour or forces
existing applications to migrate.

### Documentation

All under `Docs/`:

- `IdentityProviders.md` — full developer guide: three-layer model,
recipes, configuration reference, "what gets auto-wired" matrix, JWS
algorithm table, identity-augmenters section, **"Implementing your own
provider"** how-to subsections for Entra / OIDC / Windows / AspNetCore
(sibling files merged in per review feedback).
- `DependencyInjection.md` — `Identity` sections with `appsettings.json`
examples, identity-extension matrix, `WithAuthorizationService` +
`WithKeyCredentialPush` + `AddIdentityAugmenter` extensions documented.
- `RoleBasedUserManagement.md` — claim-criteria callout describing
`IIdentityClaims` probe surface.
- `AuthorizationService.md` — Architecture diagram +
`IAccessTokenProvider` conceptual narrative restored from master; modern
`StartRequestToken` / `FinishRequestToken` flow leads; refresh-token
recipe added.
- `KeyCredentialService.md` — Push model + Pull vs Push vs Bridge
decision matrix + Hybrid (Push + Pull) section; explicit experimental
warning for the bridge.
- `GDS.md` — KeyCredential Push, `StartRequestToken`,
`GdsApplicationSelfAdminProvider`, **shipped Refresh-token support with
rotation policy and audit semantics** all documented.
- `MigrationGuide.md` — User Identity Providers section listing
`[Obsolete]` markers + mechanical recipes; SelfAdmin migration via
`IIdentityAugmenter`; `EnableRefreshTokens` default note.
- `README.md` index entries.

## 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.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.
2026-06-02 01:14:54 +02:00
Marc Schier 01d7162313 Part 9 Alarms & Conditions: complete server + client implementation (#3805)
Complete implementation of **OPC UA Part 9 ΓÇö Alarms & Conditions** for
both server and client, **OPC UA Part 16 ΓÇö State Machines** (generic
extensible client + server API), and the supporting infrastructure
(source-generated event records, source-generated ObjectType proxies,
`IAsyncEnumerable` streaming subscriptions, address-space model change
tracking, per-node `INodeCache` invalidation, unified DI integration).

### Server-side (Part 9 complete)

- **`AlarmConditionState`** ΓÇö every Part 9 method wired automatically
in `OnAfterCreate`: `Silence`, `Suppress`/`Suppress2`,
`Unsuppress`/`Unsuppress2`, `RemoveFromService`/`RemoveFromService2`,
`PlaceInService`/`PlaceInService2`, `Reset`/`Reset2`,
`GetGroupMemberships`. Each method generates the spec-mandated audit
event and supports an optional veto-delegate hook.
- **Latched alarms** ΓÇö `ActiveState` reflects real process state;
`LatchedState` persists until explicit `Reset` (with full precondition
validation).
- **Re-alarming** ΓÇö
`ProcessReAlarm`/`ResetReAlarmRepeatCount`/`IsReAlarmEnabled` helpers
(opt-in, no hidden timer).
- **Audible alarms** ΓÇö `UpdateAudibleState` for
`AudibleEnabled`/`AudibleSound`/`SilenceState` lifecycle.
- **Suppression + groups** ΓÇö `AlarmGroup`, `AlarmSuppressionEngine`
(with first-in-group), `AlarmRateTracker` (for `AlarmMetricsType`).
- **Spec-compliance fixes** ΓÇö `SuppressedOrShelved` now includes
`OutOfServiceState`; `GetRetainState` includes `LatchedState`;
`EffectiveDisplayName` composition includes `OutOfServiceState` +
`LatchedState`.

### Client-side Part 9 API

- **`AlarmClient`** ΓÇö typed Part 9 operations. Each method delegates
to the matching source-generated `*TypeClient` proxy
(`ConditionTypeClient`, `AcknowledgeableConditionTypeClient`,
`AlarmConditionTypeClient`, `DialogConditionTypeClient`,
`ShelvedStateMachineTypeClient`) ΓÇö one source of truth for every
method NodeId.
- **Interface hierarchy** — `IConditionOperations` →
`IAcknowledgeableConditionOperations` → `IAlarmOperations` +
`IDialogConditionOperations`.
- **Source-generated event records** ΓÇö new `EventRecordGenerator`
(alongside `ObjectTypeProxyGenerator`) emits `{Type}TypeRecord` per OPC
UA event type: `ConditionTypeRecord` →
`AcknowledgeableConditionTypeRecord` → `AlarmConditionTypeRecord` →
`LimitAlarmTypeRecord`/`ExclusiveLimitAlarmTypeRecord`/… all the way
to vendor subtypes. **Vendor alarm types get records automatically.**
- **`AlarmEventDecoder`** ΓÇö decodes `EventNotification.Fields` into
the most-specific generated record.
- **`AlarmEventFilterBuilder`** ΓÇö fluent builder for Part 9
`EventFilter`s.
- **`ISession.GetAlarmClient(telemetry)`** convenience extension.

### Part 16 state machines (generic, extensible)

- **Client**: extension methods on the source-generated
`StateMachineTypeClient` / `FiniteStateMachineTypeClient` ΓÇö
`GetCurrentStateAsync`, `ObserveFiniteTransitionsAsync`
(`IAsyncEnumerable<FiniteStateSnapshot>`), `WaitForStateAsync`,
`GetAvailableStatesAsync` / `GetAvailableTransitionsAsync`. **Vendor
proxies inherit the API automatically** through the generated
inheritance chain.
- **Server**: `StateMachineBuilder` (fluent: `AddState` /
`AddTransition` / `OnCause` / validation) +
`FluentFiniteStateMachineState` ΓÇö declare a Part 16 state machine
without subclassing the abstract base.
- **Alarms alignment**: `AlarmClient.GetShelvingStateAsync` /
`ObserveShelvingTransitionsAsync` surface the API for every alarm's
`ShelvingState` child.

### IAsyncEnumerable streaming subscription

- **`IStreamingSubscription`** with
`SubscribeDataChangesAsync`/`SubscribeEventsAsync` returning
`IAsyncEnumerable<T>` (Channel-backed).
- **`ManagedSession.DefaultStreaming`** ΓÇö lazy shared subscription;
disposed with the session.
- **Helpers** ΓÇö `TakeUntilAsync`, `WithTimeoutAsync`, `TakeAsync`,
`BufferedAsync` for state-machine waits and bounded observation windows.
- **`AlarmStreamExtensions`** ΓÇö
`SubscribeAlarmsAsync`/`SubscribeConditionsAsync`/`SubscribeDialogsAsync`
returning the source-generated record types.

### Address-space model change tracking

- **Server**: `RaiseGeneralModelChangeEvent` on both `CustomNodeManager`
and `AsyncCustomNodeManager`; `ModelChangeAggregator` (in
`Opc.Ua.Server.NodeManager`, used by every node manager) batches changes
per service call; **auto-emit** from `CreateNode`/`DeleteNode` (opt-out
via `ModelChangeEmissionEnabled = false`).
- **Client**: `IModelChangeTracker` subscribes to
`BaseModelChangeEventType` via the streaming subscription, decodes
`ModelChangeStructureDataType` payloads, invalidates the `INodeCache`
per-node.
- **`INodeCache.InvalidateNode(NodeId)`** ΓÇö new interface member (true
per-node eviction in `NodeCache`).
- **`ManagedSession.EnableModelChangeTrackingAsync()`** +
`ManagedSessionOptions.ModelChangeTracking` +
`ManagedSessionBuilder.WithModelChangeTracking()` fluent method.

### Dependency Injection integration

- **`services.AddOpcUa().AddClient(...).AddAlarms()`** ΓÇö registers
`AlarmClientFactory` so DI-hosted apps can resolve a Part 9 client per
session without `new`-ing one manually.
- Cross-link added to the unified DI surface (`AddServer` / `AddClient`
/ `AddComplexTypes` / `AddGdsServer` / `AddLdsServer` /
`AddWotConServer`).

### Quickstart server modernization

- `Quickstarts.Servers/Alarms/AlarmNodeManager.cs` ported to
`AsyncCustomNodeManager` and demonstrates the new `AlarmGroup` +
`AlarmSuppressionEngine` end-to-end via a writable `MaintenanceMode`
boolean that suppresses every member alarm.

### Documentation

- New: `Docs/AlarmsAndConditions.md`, `Docs/StreamingSubscription.md`,
`Docs/ModelChangeTracking.md`, `Docs/StateMachines.md` ΓÇö full
developer guides.
- Updated: `Docs/DependencyInjection.md` (`AddAlarms()` row + section),
`Docs/README.md` index, `Docs/MigrationGuide.md` (Part 9 / Part 16 /
model change tracking sections covering behavior changes + breaking API
surface).
- Sample: `Applications/ConsoleReferenceClient/AlarmClientSample.cs`.
2026-05-29 23:24:53 +02:00
romanett e49d17d706 [Server] Make MonitoredNode aware of IAsyncNodeManager and introduce channel-based queueing under load (#3804)
## Proposed changes

This PR updates the server monitored-item pipeline to be
`IAsyncNodeManager`-aware and introduces channel-based FIFO queueing in
the monitored-node path to improve behavior under load.

It also includes follow-up fixes from review/CI feedback:
- Adjusted
`AsyncCustomNodeManagerTests.WriteAsync_PublishesValueToMonitoredItemQueueAsync`
to account for channel-based delivery timing/coalescing while still
asserting the written value is observed.
- Merged latest `master` into this branch and resolved merge conflicts
(notably in `ConfigurationNodeManager`), keeping async address-space
lookup behavior intact.
- Applied small compile-safety corrections needed for successful
validation.
2026-05-27 10:12:17 +02:00
Marc Schier 889572472f Convert DataValue from class to readonly struct for further GC relief (#3796)
Convert `DataValue` from a mutable reference type (`class`) to a `readonly struct` to eliminate per-allocation GC pressure on every attribute read, subscription publish, and history sample.

### Key API changes (user-visible)

| Before | After | Pattern |
|---|---|---|
| `class DataValue` | `readonly struct DataValue : INullable` | Foundation |
| `Equals(DataValue?)` / `operator ==(DataValue?, DataValue?)` | `Equals(DataValue)` / `operator ==(DataValue, DataValue)` | Value semantics |
| `IEncoder.WriteDataValue(string?, DataValue?)` | `IEncoder.WriteDataValue(string?, DataValue)` | Non-nullable wire API |
| `IDecoder.ReadDataValue(...) -> DataValue?` | `IDecoder.ReadDataValue(...) -> DataValue` | Same |
| `IDataChangeMonitoredItemQueue.PeekLast/Oldest()` | `TryPeekLastValue/Oldest(out DataValue)` | TryGet pattern |
| `IAggregateCalculator.GetProcessedValue(bool)` | `TryGetProcessedValue(bool, out DataValue)` | Same |
| `IUaPubSubDataStore.ReadPublishedDataItem(...)` | `TryReadPublishedDataItem(NodeId, uint, out DataValue)` | Same |
| `Dictionary<uint, DataValue?>` (NodeCacheContext) | `Dictionary<uint, DataValue>` | Default + IsNull |
| `IHistoryDataSource.FirstRaw/NextRaw` | `TryFirstRaw/TryNextRaw(out DataValue)` | TryGet pattern |
| `NodeState.ReadAttribute(... DataValue)` | `NodeState.ReadAttribute(... ref DataValue)` | Sync mutation via ref |
| `NodeState.ReadAttributeAsync(... DataValue)` | `NodeState.ReadAttributeAsync(...) -> ValueTask<(ServiceResult, DataValue)>` | Async return tuple |
| `obj as DataValue` (heterogeneous Dictionary<string, object>) | `obj is DataValue dv ? dv : default(DataValue)` | Safe struct cast |
| `Assert.That(dv, Is.Not.Null)` (tests) | `Assert.That(dv.IsNull, Is.False)` | Struct test pattern |
| `[Obsolete] Value` setter, all property setters | Removed; use ctor or `With*()` | Immutability |
| `[DataContract]` + `[DataMember]` | Removed; surrogate via `SerializableDataValue` + `DataValueSurrogateProvider` | Serialization |

### Wire format

Wire format is **byte-identical** to the previous class-based implementation across binary / JSON / XML encoders. Default DataValue (`.IsNull == true`) is treated as the equivalent of the old null reference (writes JSON `null`, binary `byte 0`, XML omitted element).

### Why TryGet pattern (instead of leaving `DataValue?`)

For methods that previously returned `DataValue?` to signal "no value available" (queue peek, aggregate result, data store lookup, history navigation), the conversion to `bool TryGet(out DataValue)` matches BCL conventions (`Dictionary.TryGetValue`, `Queue.TryPeek`, `Int32.TryParse`) and gives the caller an explicit "may be absent" affordance without `Nullable<DataValue>` overhead.

### Breaking changes summary

- `DataValue` is now a `readonly struct` - boxing/unboxing semantics change for callers using `object`-typed APIs.
- `IEncoder.WriteDataValue` / `IDecoder.ReadDataValue` / `ReadDataValueArray` interface signatures lose the `Nullable` wrapper.
- `IDataChangeMonitoredItemQueue.PeekLastValue` / `PeekOldestValue` -> `TryPeekLastValue` / `TryPeekOldestValue` (out parameter).
- `IAggregateCalculator.GetProcessedValue` -> `TryGetProcessedValue` (out parameter).
- `IUaPubSubDataStore.ReadPublishedDataItem` -> `TryReadPublishedDataItem` (out parameter).
- `NodeState.ReadAttribute` takes `ref DataValue`; `ReadAttributeAsync` returns `ValueTask<(ServiceResult, DataValue)>`.
- `DataValue` property setters removed (use constructor + `With*` mutators).
2026-05-23 11:38:49 +02:00
Marc Schier 239a40b183 GC reduction via IPooledEncodeable activator pooling (#3793)
* GC reduction via IPooledEncodeable activator pooling

Add opt-in activator-level pooling for IEncodeable instances decoded
during V2 subscription publish dispatch. The design replaces the
earlier IAllocator-on-IServiceMessageContext approach (which had a
cross-contamination hazard for non-publish decodes) with a clean
factory-based model where pooling lives on the encodeable activator.

New public API:
- IPooledEncodeable : IEncodeable - opt-in marker with Reuse() method
- PooledEncodeableType<T> : EncodeableType<T> - activator base with
  BoundedObjectPool<T> (lock-free, no new package dependency)
- ISubscriptionManager.PoolNotifications { get; set; }
- ManagedSessionOptions.PoolNotifications { get; init; }
- ManagedSessionBuilder.WithPoolNotifications(bool)

Source-gen:
- All concrete non-abstract structure types (including service
  request/response types) are generated with PooledEncodeableType<T>
  activators and IPooledEncodeable.Reuse() bodies by default
- Derived types use 'new' to hide inherited Reuse/ClearPooledSentinel
  so each concrete type routes through its own activator's pool
- No changes to external design XML files (UA Core Services.xml etc.)
  - poolable selection is hardcoded in the generator

Benchmarks (MonitoredItemNotification, 1000 ops):
- Allocations: 128 KB -> 408 B (~315x reduction)
- Gen0 collections: 29.75 -> 0.09 per 1000 ops

No interface changes to IEncodeable, IDecoder, IServiceMessageContext,
or ISubscriptionNotificationHandler. Handlers retain by copy when
pooling is enabled.
Call IPooledEncodeable.Reuse() on decoded IServiceRequest and
IServiceResponse after the server is done with them:

- TcpTransportListener.OnRequestReceivedAsync: reuse both request
  and response in a finally block after ProcessRequestAsync +
  SendResponse complete
- EndpointBase.InvokeServiceAsync (HTTPS path): same pattern after
  ProcessRequestAsync + BinaryEncoder.EncodeMessage

Walk acknowledged, overflow-evicted, and disposed NotificationMessage
objects and call IPooledEncodeable.Reuse() on their notification
payloads (MonitoredItemNotification, DataChangeNotification,
EventFieldList, EventNotificationList).

Insertion points in Subscription.cs:
- Acknowledge(): after RemoveAt from m_sentMessages
- InnerPublish() overflow: before RemoveRange/Clear on m_sentMessages
- Dispose(): before m_sentMessages.Clear()
- InnerPublish() unsent-message overflow: before RemoveRange on
  the messages list

Replace new T() with XxxActivator.Instance.CreateInstance() for all
server-side notification payload types so instances are rented from
the activator pool instead of heap-allocated:

New static helper ReuseNotificationPayloads() walks the
NotificationMessage.NotificationData, extracts DataChangeNotification
and EventNotificationList via TryGetValue, iterates their items, and
calls Reuse() on each pooled payload.
2026-05-22 12:42:33 +02:00
Marc Schier 3e5f129c6f Enable nullable in Stack, Libraries (9), and Applications projects (#3732) 2026-05-17 12:55:24 +02:00
Marc Schier 70b4498db8 Certificate manager and ref counted certificate type (#3688)
* New certificate and secrete abstraction with certificate infrastructure and certificate/secret stores. 
* leak testing for certificate resources to validate new ref counting in every test assembly
* Migration guide updated and documentation added for all
* Remove IDisposable from Nodestate and UserIdentity.
* UserCertificateTokenHandler now just points into certificate registry/stores, no dispose required
* Fix CA2000 warnings and cleanup of other diagnostics, increase warning levels in .editorconfig
2026-05-12 07:56:38 +02:00
Marc Schier 13612c03b9 Fix CreateInstanceOf<X> access restrictions emission and PrioritizedChannel polyfill TryRead bug (#3747) 2026-05-09 20:35:00 +02:00
Marc Schier bd4543e961 ManagedSession, Pluggable Subscription engine, and fluent builder + DI (#3730)
- Add new managed session, wrapping session and providing connection management and failover handling
- Add new Subscription engine that can act as alternative to current engine, but is based on channels with worker tasks
- Add a fluent builder for managed session clients
- Replace NodeCache with LruNodeCache, rename ILruNodeCache and LruNodeCache to INodeCache and NodeCache
- Update migration guide and document new functionality.
2026-05-05 19:09:07 +02:00
Copilot 54cc3d88b1 Prepare for C# 15 union types: UnionAttribute polyfill, TryGetValue rename, HasValue (#3727) 2026-05-02 18:33:46 +02:00
Anatoly Kochnev 84161c120c [Client] Fixed excessive tasks spawning during session connection loss (#3707)
Added checks for Session.KeepAliveStopped before trying to execute Publish for subscriptions.
2026-04-27 14:50:27 +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 26a0b21881 Replace session state encoder with IEncodable generation using source generators (#3673)
* Replace SessionStateEncoder with IEncodeable on session/subscription models

- Source-gen [DataType]/[DataTypeField] on: MonitoredItemOptions, MonitoredItemState,
  SubscriptionOptions, SubscriptionState (auto-generated Encode/Decode/IsEqual/Clone)
- Manual IEncodeable on: SessionOptions, SessionState, SessionConfiguration
  (ConfiguredEndpoint and IUserIdentity require manual encoding)
- Delete SessionStateEncoder.cs (488 lines removed)
- Remove [DataContract]/[DataMember]/[KnownType]/[CollectionDataContract] attributes
- Remove JsonSerializerContext classes
- Change init -> set, byte[] -> ByteString, DateTime -> DateTimeUtc
- MonitoredItems: ArrayOf<MonitoredItemState> with StructureHandling.Inline
- Subscriptions: ArrayOf<SubscriptionState>

* Source generator: support partial init properties with backing fields

Add IsInitOnly detection to the [DataType] source generator so that
partial properties with init-only setters can be used in IEncodeable types.

For each init-only partial property, the generator:
- Emits a private backing field (__PropertyName)
- Emits the partial property implementation (get => __field; init => __field = value)
- Uses the backing field in Decode() instead of the property
- Skips simple init-only fields in Clone() (record 'with' already copies them)
Detection uses both IPropertySymbol.SetMethod.IsInitOnly and syntax-tree
fallback (SyntaxKind.InitAccessorDeclaration) for partial definitions.
* docs: add partial init properties and session state migration sections

- SourceGeneratedDataTypes.md: new 'Partial Init Properties' section
  documenting backing field generation for init-only partial properties
- MigrationGuide.md: new 'Session State Persistence' section covering
2026-04-19 13:39:01 +02:00
Marc Schier 6c8c1a6d29 Enable dispose analyzers CA2000 and CA2213 and fix all issues (#3670) 2026-04-17 13:39:34 +02:00
Marc Schier 03777ecb09 New Json decoder/encoders, array/matrix abstractions, ByteString, and more. (#3619)
- **Collections overhaul** — new/rewritten collection types (`ArrayOf`, `Collections` commits)
- **`Variant` builder/accessor** — new APIs for constructing and accessing Variants
- **`NodeId` operators made explicit** — breaking change for implicit conversions
- **Version bumped to 1.6** with preview build support
- **NumericRange** uses Variant
- **object** type accessors and general usage removed.

### 🏗️ Infrastructure
- Build config updated; `.NET 10` targeting
- InternalsVisibleTo moved to `.csproj`
- Pipeline/CI adjustments
2026-03-18 12:23:30 +01:00
Anatoly Kochnev 6a09c1c7b7 [Client] Fixed freezing of Subscription with sequential publishing (#3565)
* [Client] Fixed freezing of Subscription with sequential publishing

- Fixed freezing of Subscription (messages were not moving from Subscription.m_incomingMessages to Subscription.m_messageCache). Was happening when sequentialPublishing is active and first received publish response had SequenceNumber greater when 1.
- Constant Subscription.kRepublishMessageTimeout was made public (Subscription.REPUBLISH_MESSAGE_TIMEOUT) so it can be used in tests.
- Added unit tests to cover fixed problem.
- Added unit test to cover invalid handling of reordered message queue for sequential publishing (UnorderedMessagesWouldBeLostForSequentialPublishingAsync). For now, test was made [Explicit].

* [PR] fix naming convention violation

* [PR] removed region in tests
2026-02-28 07:47:42 +01:00
Marc Schier 601cabf86f Replace all use of object in code and API with Variant (#3550) 2026-02-15 06:19:38 +01: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
Marc Schier 43ce7ed229 Code clean up and improvements (#3532)
This pull request modernizes the OPC UA .NET codebase and tests, removes .NET Standard 2.0 support from certificate/security code, and updates test code to use C# 12 and .NET 8+ features for clarity and maintainability.
- Certificate and BouncyCastle interop files (e.g., CertificateBuilder.cs, X509Extensions.cs, X509SignatureFactory.cs) are refactored to drop NETSTANDARD2_0 support and use modern .NET APIs and conditional compilation.
- Test code across the codebase is updated to use default, strongly-typed Parse methods, collection expressions, and improved async/await patterns.
- Exception handling and assertions in tests are modernized, and test method naming is made consistent with Async suffixes.
- The project and build files are updated to clarify Roslynator analyzer usage and remove obsolete netstandard tags.
- General code and test cleanup includes improved null safety, type safety, and removal of deprecated or unused code.
2026-02-09 13:37:06 +01:00
Tobias Frick 05f7023d29 Fix two bugs in MonitoredItem (client side) (#3447)
- Fix bug in MonitoredItem.Publish (for loop was never executed)
- MonitoredItem.CacheQueueSize property was not serialized
- Add unit tests for MonitoredItems
2026-01-12 08:05:05 +01:00
Copilot bf67f39e2b Fix race condition causing duplicate MonitoredItems in concurrent CreateItemsAsync calls (#3399)
* Initial plan

* Fix race condition in MonitoredItem creation

Add Creating flag to prevent duplicate items when CreateItemsAsync is called concurrently.
- Add internal Creating flag to MonitoredItemStatus
- Check Creating flag in PrepareItemsToCreateAsync
- Set Creating flag before releasing lock
- Clear Creating flag in SetCreateResult (success or failure)
- Add exception handling in CreateItemsAsync to clear Creating flag on errors
- Add test for concurrent CreateItemsAsync calls

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Address code review feedback - add comment for concurrent tasks constant

Co-authored-by: romanett <7413710+romanett@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>
2025-12-09 19:49:36 +01:00
Copilot f970392177 Fix SourceTimestamp and ServerTimestamp mismatch in ServerStatus children (#3386)
* Initial plan

* Fix ServerTimestamp to match SourceTimestamp in CoreNodeManager

Set ServerTimestamp equal to SourceTimestamp after node read operations
to ensure consistency. Previously, ServerTimestamp was set early (before
the node's read callback), while SourceTimestamp was set during the
callback, causing timestamp mismatches for nodes like ServerStatus children.

Added test to verify timestamp matching (currently failing - more investigation needed)

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Apply timestamp fix to CustomNodeManager

Extended the ServerTimestamp fix to CustomNodeManager which handles
ServerStatus and other diagnostic nodes. CustomNodeManager was setting
ServerTimestamp early and separately from SourceTimestamp, causing the
mismatch. Now both timestamps are synchronized after reading values.

All tests pass including the new ServerStatusTimestampsMatchAsync test.

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Improve comments based on code review feedback

Clarified that timestamp synchronization applies specifically to Value
attributes, and explained the difference in handling between Value and
non-Value attributes (non-Value attributes only use ServerTimestamp).

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Fix and update serilog package

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@hotmail.com>
2025-12-05 17:56:10 +01:00
Copilot 0f196ba7a5 Add support for restoring triggered monitored items on session reconnect (#3377)
* Initial plan

* Add triggering item tracking and restoration support

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

* Fix test to use proper subscription/monitored item creation pattern

Co-authored-by: romanett <7413710+romanett@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>
2025-12-05 14:18:14 +01:00
Marc Schier 2960388560 Update headers and license files (#3388)
Update license headers to copy right year 2025, replace RCL/GPL headers with MIT, replace the nuget license with MIT (via license expression).
2025-12-04 19:44:40 +01:00
Rico Suter 0e8273e729 performance: Reduce allocations (logger factory reuse, check enabled) (#3387)
* Performance: Reduce memory allocations (logging, etc.)
2025-12-04 15:30:28 +01:00
Marc Schier 35db52a7a6 Move from .net 9 to .net 10 SDK and build new target framework .net 10 (#3333) 2025-11-13 10:45:47 +01:00
Marc Schier 712e770fbe Restructure core and built in types into base assembly and nuget (#3326)
This change adds a **new Opc.Ua.Types** nuget that contains core types that do not rely on generated code.  Why the name?  Because almost all code that was in the Types subfolder is part of it, and because we do not want to change the name of the Opc.Ua.Core nuget/assembly because code relies on it.  Any other name like base or bootstrap felt out of place due to the current structure already defining the name "types".  Key design is to depend on only a small set of libraries with limited transitive dependencies, that includes hashcode, logging.abstraction, and for non standard20 builds System.Text.Json (needed for localizedText mul parsing).  Also, key is to support .netstd 2.0 to support source generators and to allow removal of EEC_SUPPORT defines by removing std2.0 from core and all libraries.

The changes are mostly restructuring, and thus small but contain some key changes:

- **Status code** struct now comes with a Symbol field, which is used to carry symbols in case the code is not generated.  Currently (until generated) the status code struct must be manually created with a symbol or the ToSymbolicId extension must be called in the core project.  Future will generate Status code constants just like NodeId that contain the symbol which also removes the reflection-based lookup.  Several tests needed to be adjusted as they checked the status code symbol strings instead of the code.
- **Core identifiers** are copied as they were needed for code to compile, that includes attributes, a subset of status codes and node identifiers.  These will never change so no need to generate them.
- The types folder now contains **encoders**, except for json which is still in core.  Once the json encoder is ported to system.text.json it will also be included and Newtonsoft.Json dropped as dependency (-> #3329).  
- Since types relies on **IOperationContext** and **ISystemContext**, and these export IUserIdentity which relies on crypto, these were split and extended in core with ISessionOperationContext and ISessionSystemContext, which mean changes to ServerSystemContext and introduction of SessionOperationContext and SessionSystemContext classes that extend OperationContext and SystemContext.  Some runtime checks in the server are therefore needed to get at the session and auth info, however, those are anyway optional fields and are not on the critical paths, and thus a type check is in order (and was done in the past already, but against OperationContext class).
- **Manifest resources** are now split between Core (nodeset zip, some generated resoures) and Types (built in bsd/xsds).  This needs to be revisited later if the bsd/xsd need to be regenerated during type generation.  Note that the embedded resource names changed as a result. Since these are only used internally and do not constitute public API this feels reasonable vs duplication of resources in 2 assemblies.
- Reflection however now needs to load not just the built in **encodeables** from Opc.Ua.Types assembly, but also the generated encodeables from Opc.Ua.Core assembly.  For this a new helper Utils method was added to get the Core assembly when linked against Opc.Ua.Core otherwise null.
- Whatever utils methods needed to be split out into the new Opc.Ua.Types assembly, they are now in **CoreUtils** static class in Opc.Ua.Types, and Utils.xxxx forwards to them to ensure minimal changes.  Later we would want to obsolete them in favor of CoreUtils, although it might make sense to split them again into domain specific utilities, such as networking, files, etc
2025-11-10 19:33:24 +01:00
Marc Schier bad6bf416b Trace and metrics for send/receive, more test coverage and additional nullable annotations (#3314)
Refactored client-side logic to support batching of service requests, ensuring compliance with operation limits and improving scalability. Added telemetry and activity tracing using `ActivityTraceFlags` for better observability, including metrics collection and logging. Enhanced error handling with descriptive exception messages and improved certificate validation.

Introduced a comprehensive suite of unit tests for `SessionClientBatchTests.cs` and `ClientBaseTests.cs`, covering batching, error scenarios, and edge cases. Added helper classes for testing logging, metrics, and activity tracing. Updated tests to align with modern C# practices, including nullable reference types and collection initializers.

Improved code consistency, readability, and maintainability by standardizing method signatures, simplifying redundant patterns, and removing legacy constructs. Updated XML documentation and added conditional compilation for .NET 8.0+ compatibility.
2025-11-05 11:16:28 +01:00
Marc Schier 1c178402ad Refactor client state persistence and add more tests (#3303)
* State handling and better serialization
* Nullable for almost everything  inclient library
* Additional testing of session
2025-11-01 17:18:48 +01:00
Marc Schier f8042e445c Check bounds for switched on values and roslyn cleanup (#3264) 2025-10-21 09:28:19 +02:00
Marc Schier ff0095c93d Make GDS tests stable and add better logging to unit tests (#3271)
- Allow setting ReturnDiagnostics on the session factory to ensure the diagnostic settings are part of "Create" also.  Set them to SymbolicIdAndText by default.
- Better logging in unit tests, now also logs to progress including exception traces, also prefixes SERVER and TEST on console to attach the logs to the server or client side.
- GDS client stability improvements to ensure client reliably reconnects between calls when connection is lost.
- Do not dispose the application certificate when it is updated from GDS client to avoid existing sessions to crash with CryptographicException.  This needs more improvements later on because it is rather hacky today.
- Remove the certificate cache in CertificateFactory (as per discussion and agreement) which allowed re-enabling the older UserIdentity API
- Logging improvements here and there to analyze issues in unit tests better
2025-10-20 18:02:45 +02:00
Marc Schier 4c651d05d2 Improve test stability of ValidateTransfer test, ensure thread safety in MonitoredItem (#3243)
* Improve thread safety and logging in MonitoredItem

Replaced Queue with ConcurrentQueue in MonitoredItemDataCache
for thread-safe operations. Updated Publish method to use
List<DataValue> for better handling of dequeued values. Enhanced
logging to include SourceTime and added logs for dropped values.

In DurableSubscriptionTest, added try-finally block to ensure
proper session closure and disposal, preventing memory leaks.
Introduced DurableSubscriptionTestLooper for stress testing
under debug mode. Refactored test code for improved readability.

* Improve logging and session management

Updated logging in `Session.cs`, `ServerBase.cs`, and `UaSCBinaryClientChannel.cs` to pass exceptions directly to the logger, enhancing error handling. Added connection state checks in `Session.cs` to prevent operations when the session is closed or never connected. Updated `Subscription.cs` to reflect session disconnection in `PublishingStopped`. Enhanced test outputs and renamed `DurableSubscriptionTestLooper` to `DurableSubscriptionTestDebug` in `DurableSubscriptionTest.cs`, reducing loop count for faster debug testing.

* Fix monitored item id factory producing collisions by first trying to allocate a free monitored item id in the table, then committing.

* Better diffing

* Enhance logging and session management

Added detailed logging in `AlarmNodeManager.cs` for lifecycle events and simulation control. Introduced a `Reconnecting` property in the `ISession` interface and `Session.cs` to manage session reconnection state, replacing the `m_reconnecting` field. Enhanced logging in `Session.cs` for reconnection and publish operations. Updated `DataGenerator.cs` to use `FrozenDictionary` for performance improvements. Replaced `SyncRoot` with `m_syncRoot` in `NamespaceTable.cs` for synchronization. Improved exception message formatting in `ServiceResult.cs`. Added necessary namespaces for new data structures. Made corrections and added debugging in `DurableSubscriptionTest.cs` for better session handling.

* Dispose simulation
2025-10-11 19:51:38 +02:00
Marc Schier 01ffcee410 Immutable Encodeable factory and lookup performance improvements (#3241) 2025-10-08 09:45:32 +02:00
Marc Schier 845c31d33b Add API back as Obsolete for samples to compile (almost) unchanged. (#3234)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-03 14:20:21 +02:00
Marc Schier 8e7a73c7df Introduce telemetry context and proper use of ILogger (#3213) 2025-09-25 20:25:31 +02:00
romanett 7cbf817f1b Improve thread safe Utils methods for incrementing identifiers (#3221) 2025-09-22 17:33:54 +02:00
Marc Schier 25ba2a2d08 Make sync client api obsolete (#3174) 2025-08-27 06:05:36 -07:00
Marc Schier b6b63c996f removing strong namer which does not work and fork async add auto and manual reset event (again) (#3175) 2025-08-25 06:52:03 -07:00
Marc Schier 9830e5d53e Enable roslynizer and fix all analyzer issues (#3157)
* Fix warnings and suggestions
* Enable roslynator
2025-08-16 04:10:24 -07:00
Marc Schier 96efa0a88a Add a LRU node cache for complex type system (#3137) 2025-08-06 12:54:44 +02:00
Marc Schier 876cf28c32 Fix subscription leaking due to not completed PublishResponseMessageWorkerAsync tasks (#3133) 2025-07-31 17:31:21 +02:00
Marc Schier 5eb9ed8918 Backporting changes in preparation of adding additional fuzz tests (in separate PR) (#3125)
- Replaced `IList<INode>` with `List<INode>` in
  `ComplexTypeSystem.cs` and `BaseComplexType.cs` for
  better type safety.
- Updated string comparison in `ReverseConnectManager.cs`
  to `StringComparison.Ordinal` for improved performance.
- Added a method to `ISession.cs`, `Session.cs`, and
  `SessionAsync.cs` to trigger reloading instance certificates.
- Minor fixes in error handling in `Subscription.cs` and using pattern matching for type checks in several locations to improve code readability.
- Made minor adjustments to comments and documentation
  for clarity and improved code quality.
- Updated `Variant` handling in preparation for immutable struct.
- Changed return types in `BaseObjectState.cs` and `BaseVariableTypeState.cs` to use `ObjectTypeIds` for iperf
- Cleaned up `ExpandedNodeId`
- Enhanced memory management  error handling and readabilityand error handling in encoders
2025-07-31 15:54:42 +02:00
romanett 573e1b0107 fix Client removing subscription if publish response is received before createSubscriptionResponse (#3120) 2025-07-17 07:19:51 +03:00
romanett ef9387b525 Enable Durable Subscriptions (#2978)
* Enable Durable Subscriptions (#9)

* [Server] Durable Subscriptions (#2683)

* Implement a system test for DurableSubscriptions (#2839)
* Client Side
Co-authored-by: ALTERNATE-DEV\Archie <archie@dreamsandadventures.ca>

* make a static Method CalculateRevisedQueueSize

* Adress review feedback

* fix FilterRetainTests

* Add MonitoredItem Id to Queues
Use NodeIdDictionary where possible
2025-02-28 13:46:49 +02:00
Martin Regen 61056d0d30 Some .NET 9 maintenance and JSON encoder improvements (#2922)
- Reduce memory allocations in JSON encoder by using stackalloc/ArrayPool
- Fix some new warnings in .NET9 code analyzers
- Fix fuzzing error in JsonDecoder
- do not use .NET9 libraries with .NET8 LTS builds #2931, #2940
- Bump langversion to 9 (max VS2019)
- Use official ASP.Net 2.3. libraries for .NET Framework (rel. Jan 15th)
2025-01-20 08:45:12 +01:00