Marc Schier 87f253bb47 Support unbounded monitored items per subscription via automatic partitioning (#3869)
# Description

Adds **unbounded monitored items per subscription** to the V2
`ManagedSession` subscription engine via automatic, transparent
partitioning. Top-level public API and runtime semantics for callers
that stay under the server's `MaxMonitoredItemsPerSubscription` cap are
unchanged; callers that exceed the cap continue to succeed instead of
failing with `BadTooManyMonitoredItems`.

## Approach (OPC UA Part 4 1.05.07 conformant)

- The public `ISubscription` is now a `LogicalSubscription` that owns
one **primary** and zero-or-more **secondary** `Subscription`
partitions, each a real server-side subscription.
- `CompositeMonitoredItemCollection` aggregates the per-partition
`MonitoredItemCollection` views so `Items`, indexer access, and
enumeration look unsharded to the caller.
- `PartitionPlacementPolicy` decides the target partition at
`TryAddMonitoredItem` time:
- **`StrictAffinity`** — keep items tagged with the same `Affinity`
co-located until the partition fills, then split with a one-time
warning.
- **`BestEffortAffinity`** (default) — prefer co-location but spill
silently when the partition is full.
- No affinity → fall back to the least-loaded partition (or the
primary).
- `PartitionForwardingHandler` proxies notifications from secondary
partitions to the caller's handler with the **primary partition's**
`ISubscription` identity, so handler code never observes the split.
- **Durable subscriptions (Part 4 §5.13.9):** when `SetAsDurableAsync`
runs before any items are added, the wrapper records the intent and
installs an `OnAfterCreateAsync` hook on every existing partition. Each
newly-minted secondary inherits the hook so `SetSubscriptionDurable` is
invoked between `CreateSubscription` and `CreateMonitoredItems` — the
only order the standard allows.
- **Snapshot/restore** preserves the partition layout so reconnect and
failover restore items into the same partition they were created in,
with affinity tags intact.
- Secondary partitions that go idle (no items, no pending
`CreateMonitoredItems`) are deleted to release server resources.

## Performance

Hot-path dispatch goes through the primary partition unchanged for the
unsharded case (single-partition fast path); the forwarding handler only
allocates on the multi-partition path. The performance smoke test in
`Tests/Opc.Ua.Stress.Tests` shows no regression.

## New configuration

```csharp
session.AddSubscription(handler, new SubscriptionOptions
{
    MaxMonitoredItemsPerPartition = 5000,          // optional cap per server-side subscription
    PartitionPlacementPolicy =                      // optional placement policy
        PartitionPlacementPolicy.StrictAffinity
});

sub.TryAddMonitoredItem(
    name,
    nodeId,
    o => o with { Affinity = "machine-A" },         // optional co-location hint
    out _);
```

Defaults preserve pre-change behaviour (`MaxMonitoredItemsPerPartition =
uint.MaxValue`, `BestEffortAffinity`).

## New types (Libraries/Opc.Ua.Client/Subscription)

| File | Purpose |
| --- | --- |
| `CompositeMonitoredItemCollection.cs` | Aggregate collection view |
| `ILogicalSubscription.cs` / `IPartitionedSubscription.cs` | Internal
contracts used by tests and infrastructure |
| `LogicalSubscription.cs` | Wrapper that owns the partitions |
| `PartitionForwardingHandler.cs` | Notification proxy |
| `PartitionPlacementPolicy.cs` | Affinity-aware item placement |

Modified: `ISubscriptionNotificationHandler`, `MonitoredItemManager`,
`MonitoredItemOptions` (adds `Affinity`), `Subscription`,
`SubscriptionManager`, `SubscriptionOptions` (adds
`MaxMonitoredItemsPerPartition`, `PartitionPlacementPolicy`),
`SubscriptionManagerSerializer`, `MonitoredItemStateSnapshot`,
`SubscriptionStateSnapshot`, `ManagedSessionExtensions`.

## Documentation

- New: `Docs/UnboundedSubscriptions.md` — feature overview, affinity
semantics, snapshot/durable interaction.
- Cross-links added in `Docs/Sessions.md`, `Docs/MigrationGuide.md`,
`Docs/README.md`.

## Validation

| Suite | Result |
| --- | --- |
| Unit tests for new types — `Opc.Ua.Client.Tests.Subscription.*`
(net10.0 + net48) | **39 / 39** |
| All `Opc.Ua.Client.Subscriptions.*` unit tests (net48) | **152 / 152**
|
| Integration — `Opc.Ua.Subscriptions.Tests.UnboundedSubscriptionTests`
(net10.0 + net48) | **3 / 3** (1 durable-only skipped) |
| Integration — `Opc.Ua.Subscriptions.Durable.Tests.*V2Async` (net10.0)
| **6 / 6** (incl. new `DurableLifetimeAppliesToAllPartitionsV2Async`) |
| Adjacent — `Opc.Ua.Sessions.Tests` (net10.0) | **690 / 690** |
| Adjacent — `Opc.Ua.Subscriptions.Classic.Tests` (net10.0) | **32 /
32** |
| Adjacent — `Opc.Ua.Features.Tests` (net10.0) | **128 / 128** |
| Adjacent — `Opc.Ua.Stress.Tests` (net10.0) | **159 / 159** |
| NativeAOT smoke — `Opc.Ua.Aot.Tests.SubscriptionAotTests` (win-x64) |
**5 / 5** (incl. new `UnboundedSubscriptionWithCapAotAsync`) |

## Compatibility

- **No public type removed**, **no obsoletions added**.
- Public surface on `ISubscription` / `ManagedSession` unchanged.
- New options non-breaking by default (`MaxMonitoredItemsPerPartition =
uint.MaxValue` preserves prior behaviour; `BestEffortAffinity` matches
the closest analogue to the prior single-partition semantics).
- Classic V1 subscription engine unchanged.

## Related Issues

- _No tracking issue filed — please reference the appropriate one if any
exists._


## Security review

Scoped against Microsoft Service Tree
``59eec07a-6f20-42b9-b41b-d20e0a6322da`` (Azure Industrial IoT) using a
combination of the security-agent MCP (code-integrity, secrets-usage,
tls, policy-governance, ai-chat) and the ``security-review`` SAST
sub-agent on the PR diff. Bar: SDL secure-coding for a client library +
OPC UA Part 2 / Part 4 §5.5 / §5.13 spec.

Fixed in commit ``c6997689``:

| # | Sev | Title | Fix |
| --- | --- | --- | --- |
| 1 | CRITICAL | Unbounded partition fan-out under hostile server
(CWE-400/770) | Added ``SubscriptionOptions.MaxPartitionCount`` (default
``32``), enforced in ``PartitionPlacementPolicy``, surfaced as
``PlacementDecision.RejectMaxPartitionCountReached`` |
| 2 | HIGH | Snapshot ``LogicalGroupId`` option-consistency not
validated (CWE-502) | ``ValidateAndSortGroup`` now asserts all snapshots
in a group agree on every subscription-wide option |
| 3 | MEDIUM | Restore-path secondaries bypassed
``PartitionForwardingHandler`` (CWE-362) |
``AttachSecondaryPartitionAsync`` now reuses the wrapper's forwarding
handler when present |

Findings explicitly assessed and dismissed (sub-agent confirmed):
* Affinity-index growth — uses ``StringComparer.Ordinal`` (no
CVE-2022-29117 class); growth is application-controlled, not
attacker-controlled.
* Idle-timer race vs ``DisposeAsync`` — race exists; worst case is a
swallowed ``ObjectDisposedException``. No attacker trigger.
* Cross-wrapper notification routing — each ``Add`` allocates its own
forwarding handler; ``BindLogical`` rejects re-binding; no structural
path for crosstalk.
* OPC UA Part 4 §5.13.6 (``SetTriggering`` cross-partition) — enforced
by ``LogicalSubscription.SetTriggeringAsync``.
* OPC UA Part 4 §5.13.9 (durable hook ordering) — installed uniformly on
every minted partition.

Validation: ``Opc.Ua.Client`` builds clean on net10.0; 43 V2 unit tests
+ 3 unbounded integration tests + 6 V2 durable integration tests all
pass.
## 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.
2026-06-12 18:57:38 +02:00
2020-05-15 19:41:55 +02:00
2021-07-26 10:06:51 +03:00

OPC UA .NET Stack

Github top language Github stars Github forks Github size Github release Nuget Downloads Azure DevOps Github Actions Tests CodeQL Coverage Status Connection Stability

Overview

This OPC UA reference implementation targets .NET Framework, .NET, and .NET Standard 2.1.

.NET allows to develop apps that run on all common platforms available today, including Linux, iOS, Android (via Xamarin) and Windows without requiring platform-specific modifications.

One of the reference implementations inside this project has been certified for compliance through an OPC Foundation Certification Test Lab to prove its high quality. Fixes and enhancements since the certification process have been tested and verified for compliance using the latest Compliance Test Tool (CTT).

For a comprehensive list of supported OPC UA Profiles and Facets, see the dedicated documentation.

More samples based on the official Nuget packages can be found in the OPC UA .NET Samples repository. For development there is also a preview Nuget feed available. For local testing a Docker container of the Reference Server is available for preview and release builds.

Features included

Core and Libraries

  • Fully ported Core OPC UA Stack and Libraries (Client, Server, Configuration, Complex Types & GDS assemblies).
  • Reference sample Server and Client.
  • X.509 Certificate support for client and server authentication.
  • SHA-2 support (up to SHA512) including security profile Basic256Sha256, Aes128Sha256RsaOaep and Aes256Sha256RsaPss for configurations with high security needs.
  • ECC Security policies ECC_nistP256, ECC_nistP384, ECC_brainpoolP256r1 and ECC_brainpoolP384r1.
  • Anonymous, username and X.509 certificate user authentication.
  • UA-TCP & HTTPS transports (client and server).
  • Reverse Connect for the UA-TCP transport (client and server).
  • Folder & OS-level (X509Store) Certificate Stores with Global Discovery Server and Server Push support.
  • Sessions and (durable) Subscriptions.
  • A PubSub library with samples.

New in 2.0

  • Type safe built in types
  • Source generator generated code behind during build
  • See MigrationGuide for details.
  • New AsyncCustomNodeManager (successor of CustomNodeManager2) with improved Locking Strategy, see Server Async (TAP) Support
  • In our Load Test the Server shows at least 2.5x higher throughput under load with 750 subscriptions totaling 450k Monitored items and write times for 600 items below 5 seconds were before > 10 seconds were needed. Also for event at least 3x faster event reporting was observed.
  • Unified Dependency Injection surface — services.AddOpcUa() with fluent AddServer() / AddClient() / AddComplexTypes() / AddAlarms() / AddGdsServer() / AddLdsServer() / AddWotConServer() extensions for Microsoft.Extensions.DependencyInjection and the .NET Generic Host (AOT compatible via the Action<TOptions> overloads).

New in 1.05.378

  • Support for AsyncNodeManagers in the Server Library, see Server Async (TAP) Support
  • Reworked Observability via ITelemetryContext in preparation for better DI support. See documentation for breaking changes.
  • Fully async APIs. Deprecated all sync and APM API.

New in 1.05.376

New in 1.05.375

New in 1.05.374

  • CRL Support for the X509Store on Windows

New in 1.05.373

Key Features and Updates in OPC UA 1.05

  • Thread Safety and Locking: Improved thread safety and reduced locking in secure channel operations.
  • Audit and Redaction: New interfaces for auditing and redacting sensitive information.
  • Security Enhancements: Improved encryption and authentication mechanisms.
  • CRL Support: Added Certificate Revocation List support for X509Store on Windows.
  • Performance Improvements: Faster binary encoding and decoding, reducing memory usage and latency.
  • Role-Based Management: Support for WellKnownRoles and RoleBasedUserManagement WellKnownRoles & RoleBasedUserManagement.
  • Improved Logging: Enhanced logging with ILogger and EventSource.
  • ECC Profiles: Support for NIST & Brainpool Security Profiles.
  • Durable Subscriptions: Support for Durable Subscriptions Durable Subscriptions.

New in 1.04.368

  • Improved support for Logging with ILogger and EventSource.
  • Support for custom certificate stores with refactored ICertificateStore and CertificateStoreType interface.
  • Client and Server support for TransferSubscriptions.
  • How to use Container support with reference server.

Samples and Nuget packages

Getting Started

All the tools you need to build .NET libraries and application come with the .NET SDK. You need the .net 10 SDK to build the project. To run applications you need one of the target .NET runtimes installed. See Get started with .NET for what you need.

How to build and run the reference samples in Visual Studio on Windows

  1. Open the UA.slnx solution file using Visual Studio 2026. Ensure you have all target frameworks enabled, including .NET 4.8.x, .NET 8.0, 9.0, and 10.0 (LTS) but Visual Studio will prompt you to install what is missing.
  2. Choose a project in the Solution Explorer and set it with a right click as Startup Project.
  3. Hit F5 to build and execute the sample.

How to build and run the console samples on Windows, Linux and iOS

This section describes how to run the ConsoleReferenceServer sample application.

Please follow instructions in this article to setup the .NET 10 SDK which provides the dotnet command that allows you to build and run the samples on your platform. Once the dotnet command is available, navigate to the root folder in your local copy of the repository and execute dotnet restore 'UA.slnx'. This command calls into NuGet to restore the tree of dependencies.

Start the server

  1. Open a command prompt.
  2. Navigate to the folder Applications/ConsoleReferenceServer.
  3. To run the server sample type dotnet run --project ConsoleReferenceServer.csproj -a.
    • The server is now running and waiting for connections.

Contributing

We strongly encourage community participation and contribution to this project. First, please fork the repository and commit your changes there. Once happy with your changes you can generate a 'pull request'.

You must agree to the contributor license agreement (CLA) before we can accept your changes. The CLA and "I AGREE" button is automatically displayed when you perform the pull request.

Remarks

Self signed certificates for the sample applications

All required application certificates for OPC UA are created at the first start of each application in a directory or OS-level certificate store and remain in use until deleted from the store. Please read Certificates for more information about certificates and stores

Local Discovery Server

By default all sample applications are configured to register with a Local Discovery Server (LDS). A reference implementation of a LDS for Windows can be downloaded here. To setup trust with the LDS the certificates need to be exchanged or registration will fail.

S
Description
OPC Unified Architecture .NET Standard
Readme
286 MiB
Languages
C# 99.4%
PowerShell 0.2%