# Full OPC UA Part 14 (PubSub) — v1.05.06
This PR replaces the legacy PubSub stack with a modern, **OPC UA Part 14
v1.05.06**-compliant reimplementation built on current .NET: fully
`async`, dependency-injection-first with a fluent builder
(`services.AddOpcUa().AddPubSub(...)`), NativeAOT/trim-safe, and
designed for high-throughput, highly-available, distributed deployments.
The libraries — `Opc.Ua.PubSub`, `Opc.Ua.PubSub.Udp`,
`Opc.Ua.PubSub.Mqtt`, `Opc.Ua.PubSub.Server`, and
`Opc.Ua.PubSub.Adapter` — keep source compatibility with 1.5.378 where
practical (obsolete shims) and ship a complete migration path. See the
**[PubSub
guide](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md)**
and the **[2.0.x migration
guide](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/migrate/2.0.x/pubsub.md)**.
**Encodings & transports.** Both wire encodings are implemented in full
— **UADP** (key/delta/event/keepalive, RawData, chunked,
signed+encrypted) and **JSON** (verbose/compact, single-message,
metadata; migrated from `Newtonsoft.Json` to `System.Text.Json`) —
across **UDP** (uni/multi/broadcast + discovery), **MQTT** (3.1.1/5.0,
retained metadata, TLS), and a from-scratch **DTLS 1.3** secured
datagram transport (`opc.dtls://`) implemented on the .NET BCL crypto
primitives (ECDHE, HelloRetryRequest cookies, certificate auth, AEAD
record protection, HKDF key schedule), wired through
`AddUdpTransport().WithDtls(...)`. Details in
[Transports](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#transports)
and
[Encodings](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#encodings).
A raw **Ethernet (Layer 2)** transport (`opc.eth://`, EtherType
`0xB62C`, optional 802.1Q VLAN) carries UADP NetworkMessages directly in
Ethernet II frames with no IP/UDP, via `AddEthTransport()`
(`.WithPcap()` for cross-platform capture). See [Ethernet
transport](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#ethernet--uadp-opceth).
**Runtime schema generation.** A new schema subsystem
(`Opc.Ua.Core.Schema`, `Opc.Ua.PubSub.Schema`) generates **XSD**, **OPC
Binary (BSD)**, and **JSON Schema** for OPC UA data types at runtime —
including PubSub NetworkMessage/DataSet message schemas — driven from
the type system and source-generated metadata (no reflection;
NativeAOT/trim-safe). It is consumable from the client, server, and
PubSub stacks. See the **[Schema generation
guide](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/SchemaGeneration.md)**.
**Security, fail-closed.** Message-level security is wired end-to-end
through `IPubSubSecurityWrapperResolver`: publishers are fail-closed (a
secured group with no key source throws rather than emitting plaintext)
and subscribers enforce per-reader inbound security. It includes AES-CTR
(128/256) policies with HMAC-SHA-256, a deterministic 96-bit nonce with
a messages-per-key cap, a monotonic sliding replay window, and a
Security Key Service (SKS) pull client + in-memory server with
SecurityGroups and key rotation. Two independent security assessments
(IEC 62443-4-2 × OPC UA Part 2/4/14, plus a Microsoft SDL/SFI delta
pass) drove remediation of all actionable findings — including orphaned
message security, chunked-frame security bypass, unauthenticated Action
invocation, pre-auth reassembly DoS, and DTLS hardening. See
[Security](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#security)
and
[SKS](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#security-key-service-sks).
**Server-side & high availability.** The runtime can optionally surface
through a standard in-process OPC UA address space
(`Opc.Ua.PubSub.Server`) with configuration methods, per-component
diagnostics, the full PubSubState model, KeepAlive and
MessageReceiveTimeout. Pluggable configuration/runtime-state stores, an
id allocator, and HA runtime-state providers with failover support
running publishers/subscribers in a distributed system. See [Server-side
address
space](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#server-side-address-space)
and [High
availability](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#high-availability-state-providers).
**External-server adapter (bridge) with hot-reload.** A new
`Opc.Ua.PubSub.Adapter` binds PubSub publishers, subscribers, and Action
responders to an **external** OPC UA server over `ManagedSession` —
cyclic Read or subscription-backed sources, browse-path node mapping,
and metadata that resolves/retries and re-emits on model-change events.
Adapter and PubSub configuration **hot-reload incrementally** (only
changed writers/readers/responders are rewired; unchanged sessions are
reused), driven by `IPubSubConfigurationStore.Changed` and
`IOptionsMonitor`, with a pluggable change-feed source (e.g. etcd). See
[Binding PubSub to an external
server](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#binding-pubsub-to-an-external-opc-ua-server-client-session-adapters).
**Samples, packaging & quality.** One combined sample,
`ConsoleReferencePubSubClient`, demonstrates publisher, subscriber, and
external-server modes on `Host.CreateApplicationBuilder` with full
fluent/DI wiring; all libraries publish **NativeAOT with zero
IL2026/IL3050 warnings** ([Native
AOT](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/PubSub.md#native-aot),
[DI](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/DependencyInjection.md)).
The change ships comprehensive NUnit/integration suites and a libFuzzer
harness, maintains ≥80% coverage on the new libraries, and is documented
end-to-end ([What's new in
2.0](https://github.com/OPCFoundation/UA-.NETStandard/blob/part14pubsub/Docs/WhatsNewIn2.0.md)).
# Description
Introduces a central `IClientChannelManager` that owns client-side
transport channels with reference counting, sharing, coalesced
reconnect, and asynchronous participant notification. Sessions,
discovery clients, and registration clients targeting the same
`ConfiguredEndpoint` (with the same reverse-connect identity) now share
a single underlying `ITransportChannel`; reconnect is transparent to
callers; and the previous `AttachChannel`/`DetachChannel` +
`SessionReconnectHandler` patterns are obsoleted.
## What lands
### Core types (`Stack/Opc.Ua.Core/Stack/Client/`)
- `ManagedChannelKey`, `ChannelState`, `ChannelStateChange`,
`ParticipantReconnectResult`
- `IReconnectParticipant`, `IManagedTransportChannel`,
`IClientChannelManager`
- `IChannelReconnectPolicy` + `ExponentialBackoffChannelReconnectPolicy`
(default mirrors historical `SessionReconnectHandler`: 500 ms → 30 s
exponential)
- `IRetryBudget` + `RetryBudget` — shared retry deadline so the
two-level retry no longer compounds multiplicatively
- `ClientChannelManager` — single non-partial sealed class with
SOLID-extracted internal types under
`Stack/Opc.Ua.Core/Stack/Client/Channels/Internal/`: `ChannelEntry`
(behind `IChannelEntryHost`), `ManagedTransportChannelLease`,
`ClientChannel`, `ClientChannelManagerMetrics` (IMeter),
`ClientChannelManagerCertRotation` (auto `ReconnectAllAsync` on
`CertificateManager` rotation), `ClientChannelManagerDiagnostics`
(ActivitySource + EventSource). Refcount, coalesced reconnect, and
transparent faulted-entry recovery via `SwapFaultedEntryAsync` (swaps a
lease's underlying entry on subsequent `ReconnectAsync` when the prior
cycle Faulted/Closed, preserves participant, back-off via policy
`GetDelay(SwapCount)`)
### Three-state lifecycle gate
`Disconnected` → `TransportConnecting` / `TransportReconnecting` →
`TransportConnectedSessionReactivating` → `Ready` (or `Faulted` /
`Closed`). Only `Ready` releases the service-call gate; the manager
bypasses the gate internally for participant `ActivateSession` traffic
during reactivation via an `AsyncLocal` scope.
### Session integration (`Libraries/Opc.Ua.Client/Session/`)
- New `Session.CreateAsync(IClientChannelManager, …)` overload
- `Session` implements `IReconnectParticipant`; reactivation handler
distinguishes `Reactivated` / `RequiresSessionRecreate` /
`TransientFailure` / `FatalForParticipant` / `FatalForChannel`
- `Session.ReconnectAsync(ct)` automatically delegates to the channel
manager when wired
- `Session.RecreateInPlaceAsync` swaps managed-channel leases for
failover-to-different-endpoint via a new atomic
`IClientChannelManager.GetAsync(endpoint, participantFactory, …)`
overload; same-key recreates delegate to manager reconnect;
explicit-channel/connection callers retain legacy behavior
- KA failure routes through
`IClientChannelManager.ReconnectAsync(channel)`
### ManagedSession integration
- `WithChannelManager(...)` builder + ctor param; DI-resolved
automatically
- Two-level retry: channel-mgr handles transparent reconnect; outer
`ConnectionStateMachine` + `IReconnectPolicy` only triggers on terminal
channel `Faulted`. Outer-state churn suppressed during channel-mgr
cycles via `_channelReconnectInProgress` flag wired through
`IManagedTransportChannel.StateChanged`
- New `ManagedSession.ChannelStateChanged` event surfaces transparent
reconnects
- `ConnectionStateChangedEventArgs.UnderlyingChannelState` populated
when outer reconnect was triggered by a channel fault
- Shared `IRetryBudget` propagated so the layers enforce a single
max-total-time
### Discovery / Registration / GDS
Channel-manager-aware `CreateAsync` overloads on `DiscoveryClient`,
`RegistrationClient`, `ServerPushConfigurationClient`,
`LocalDiscoveryServerClient`, `GlobalDiscoveryServerClient`. Each
registers a minimal `IReconnectParticipant`; co-located sessions and
discovery probes targeting the same endpoint share a channel.
### HTTPS transport (`Stack/Opc.Ua.Bindings.Https/`)
- New `IOpcUaHttpClientFactory` abstraction
- HTTPS / OPC-HTTPS channels migrated to `IHttpClientFactory` +
`Microsoft.Extensions.Http.Resilience` (10.6.0; AOT-compatible on net10
via source generators; legacy direct-HTTP path retained for older TFMs /
no-DI consumers)
### DI
`AddOpcUa().AddClient(...)` registers `IClientChannelManager` and the
named HTTPS `HttpClient` + standard resilience handler automatically.
`ManagedSessionBuilder` resolves the manager from DI when present.
### Obsoletions (functional, `[Obsolete]` with guidance)
- `IClientBase.AttachChannel` / `DetachChannel`
- `SessionReconnectHandler`
- `SessionExtensions.ReconnectAsync(connection, ct)` and `(channel, ct)`
legacy overloads
### Tests
- Core: `ClientChannelManagerManagedTests`,
`ClientChannelManagerCertRotationTests`, `RetryBudgetTests`,
`OpcUaHttpClientFactoryTests` — **56 / 56 pass**
- Client: `ManagedSession*Tests` updated; legacy
`SessionReconnectHandlerTests` + `SessionExtensionsTests` preserved with
`#pragma warning disable CS0618` — **129 / 129 pass**
- Sessions integration: `ChannelManagerSharing` / `TransparentReconnect`
/ `SessionLifecycle` / `CertRotation` — **7 pass, 1 skipped**
(`ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns`
un-`[Ignore]`d and implemented in this PR via `SwapFaultedEntryAsync`;
remaining skip is end-to-end `RequiresSessionRecreate` plumbing —
carry-forward)
- Stress (consolidated `Tests/Opc.Ua.Stress.Tests/Subscriptions/` +
`Channels/`): chaos / contract / soak / lifecycle / leak-accuracy / gap
/ participant-result-aggregation tests merged from the
previously-separate `Opc.Ua.Channels.Stress.Tests` project; nightly run
via `.github/workflows/stress-test.yml`
Builds clean (0 warnings / 0 errors) across all six TFMs: `net472`,
`net48`, `netstandard2.1`, `net8.0`, `net9.0`, `net10.0`.
### Docs
- `Docs/Sessions.md` new § 4 documents the channel manager, three-state
gating, two-level retry with shared `IRetryBudget`, HTTPS resilience
layering
- `Docs/MigrationGuide.md` migration recipes for the four obsoleted API
groups
- `Docs/DependencyInjection.md` channel-manager and HTTPS factory
registration
## Carry-forward (separate PRs)
- End-to-end `ParticipantReconnectResult.RequiresSessionRecreate` →
`Session.RecreateAsync` lifecycle wiring (currently incomplete — flagged
in the 1 remaining skipped integration test)
## Related Issues
- Fixes#3288
- Builds on #3282 (PR that introduced the initial
`ITransportChannelManager` skeleton)
## 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.
*(Channel-manager-specific tests + ManagedSession/client tests verified
across all 6 TFMs; full UA.slnx run is pending CI.)*
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
* 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
The goal is to reduce the overall number of builds, stop using end of life platforms in the build pipeline and to support
build of future/deprecated build targets in a custom build step, which is also triggered weekly in the ci builds.
In addition:
- added a doc which describes the release and versioning strategy of this project
- produce the Nuget packges with .NET 8 assemblies.
- produce a Docker build based on a .NET 8 based container.
- add custom test targets to test outdated platforms. Need a manual step to enable for VS or command line.
- bug fixes: the latest move to an alternate crypto library for Bouncy Castle, which is only used for the GDS support and .NET Framework 4.6.2./netstandard2.0, revealed already a few issues which were previously not found.
- only .NET8/.NET6/.NET Framework 4.8 are tested in Pull requests. Remaining builds in weekly scheduled builds.
- Deprecate support for Nuget package support of .NET Framework 4.6.2
- Bump sample applications to 4.8
- update some copyright headers / supporting files (editorconfig etc.)
- fix a few warnings
- fix an issue with `SessionReconnectHandler`, in certain combination of reconnect period and exponential backoff the .NET sample app never reconnected.
- complete build in Dockerfile, not in ci pipeline
- Use recommended build tasks for github actions
- build multiarch with linux/amd64,linux/arm64
- docker signing support with cosign (yet disabled)
- support for Microsoft.Extensions.Logger.ILogger in Opc.Ua.Core as the new logging interface
- backward compatible with config and TraceEvents
- new API Utils.SetLogger allows to set ILogger
- updated Console samples to demonstrate ILogger support with Serilog
- support for EventSource logging
- see Docs/Logging.md for more info