Skip to content

[WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding executors - #4144

Merged
marcschier merged 172 commits into
marcschier/wot-11-bindings-corefrom
marcschier/wot-12-executors
Aug 5, 2026
Merged

[WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding executors#4144
marcschier merged 172 commits into
marcschier/wot-11-bindings-corefrom
marcschier/wot-12-executors

Conversation

@marcschier

Copy link
Copy Markdown
Collaborator

Summary

  • Adds the concrete WoT binding transport executors for HTTP, Modbus TCP, OPC UA and MQTT.
  • Includes executor/channel tests and in-process HTTP, Modbus, MQTT and OPC UA harness coverage.
  • Leaves CoAP, BACnet, PROFINET and LoRaWAN planner-only because this stack split intentionally lands validation/planning before executable transports.

Stack context

This is PR 12 of the stacked split of integration PR #4093. It is stacked on #4140, where the protocol-agnostic planner layer landed separately, so this PR is limited to the concrete transport implementations.

Validation

  • dotnet build tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:m
  • dotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-build (512 passed)
  • dotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj -c Release -v:m
  • dotnet build src\Opc.Ua.WotCon.Bindings.Mqtt\Opc.Ua.WotCon.Bindings.Mqtt.csproj -c Release -v:m

marcschier and others added 12 commits July 31, 2026 10:55
Adds the client-side surface for the WoT Connectivity registry: browsing and reading registry groups, resources and versions, uploading documents through FileTransfer, and invoking the registry methods.

WotRegistryClient derives from the shared XRegistryClient so the generic registry traversal is not reimplemented here, and it takes an explicit registry root so a server that hosts the registry somewhere other than the well-known location can still be used.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Adds the concrete transports for the WoT binding runtime introduced by the
planner layer: HTTP, Modbus TCP, OPC UA and MQTT.

The OPC UA executor supports Read, Write, native data change observation,
Method invocation and Event subscription, including portable nsu= NodeIds. The
Modbus client reconnects a faulted connection on its next transaction and
honours the standard modv:pollingTime interval, with backoff left to the polling
subscription so there is only one retry loop. MQTT enables TLS for mqtts,
defaults to port 8883, and resolves credentials and trust through the injected
providers.

CoAP, BACnet, PROFINET and LoRaWAN remain planner and validation only; they
report their non-executable capability explicitly rather than failing at run
time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Reverts the temporary client-only compile exclusions so the WotCon test project keeps the existing server, provider and hosting tests from the base branch.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Adds a stable, injectable registry for Thing Description and Thing Model documents, with in-memory and file-backed stores behind one interface.

The file-backed store commits atomically: content-addressed blobs are verified and skipped rather than rewritten, blob writes are staged under a temporary name and published in a single indivisible step through the file-system atomic-replace capability, and a commit that is interrupted leaves either the previous state or the new one, never a partially written blob that the manifest already references. Recovery artefacts left by an interrupted commit are detected and rolled back on load.

Documents are versioned with epochs and desired/active state, labels and bounds are persisted, and blob storage is delegated to the shared xRegistry resource store contract rather than reimplemented.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
The fluent builder generator emits calls to INodeManagerBuilder.VariableFromDataTypeId
and to a NodeManagerBuilder constructor overload that carries the data-type lookup, but
the runtime side of that API was missing, so every generated node manager failed to
compile with CS1729 and CS1061.

Adds the VariableFromDataTypeId resolution to the builder interface and implementation,
along with NodeStateLookupExtensions.FindByDataType, which is the lookup the builder
delegates to and has no other consumer.

Resolution reports BadNodeIdInvalid for a null data type, BadNodeIdUnknown when nothing
matches, BadBrowseNameDuplicated when the match is ambiguous, and BadTypeMismatch when
the resolved node is not a variable. An optional browse name disambiguates a data type
that is carried by more than one variable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
The fluent builder generator emits calls to INodeManagerBuilder.VariableFromDataTypeId
and to a NodeManagerBuilder constructor overload that carries the data-type lookup, but
the runtime side of that API was missing, so every generated node manager failed to
compile with CS1729 and CS1061.

Adds the VariableFromDataTypeId resolution to the builder interface and implementation,
along with NodeStateLookupExtensions.FindByDataType, which is the lookup the builder
delegates to and has no other consumer.

Resolution reports BadNodeIdInvalid for a null data type, BadNodeIdUnknown when nothing
matches, BadBrowseNameDuplicated when the match is ambiguous, and BadTypeMismatch when
the resolved node is not a variable. An optional browse name disambiguates a data type
that is carried by more than one variable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
The fluent builder generator emits calls to INodeManagerBuilder.VariableFromDataTypeId
and to a NodeManagerBuilder constructor overload that carries the data-type lookup, but
the runtime side of that API was missing, so every generated node manager failed to
compile with CS1729 and CS1061.

Adds the VariableFromDataTypeId resolution to the builder interface and implementation,
along with NodeStateLookupExtensions.FindByDataType, which is the lookup the builder
delegates to and has no other consumer.

Resolution reports BadNodeIdInvalid for a null data type, BadNodeIdUnknown when nothing
matches, BadBrowseNameDuplicated when the match is ambiguous, and BadTypeMismatch when
the resolved node is not a variable. An optional browse name disambiguates a data type
that is carried by more than one variable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
@marcschier

Copy link
Copy Markdown
Collaborator Author

Fixed the net10.0 solution build failure on this PR.

This branch carries the FluentBuilderGenerator change, which emits calls to
INodeManagerBuilder.VariableFromDataTypeId and to a NodeManagerBuilder constructor
overload carrying the data-type lookup. The runtime side of that API was not present on
any branch in the stack - it had been grouped into a later server PR that does not exist
yet - so every generated node manager failed to compile:

BoilerNodeManager.NodeManager.g.cs(97,31): CS1729: 'NodeManagerBuilder' does not
  contain a constructor that takes 7 arguments
BoilerNodeManager.FluentBuilders.g.cs(140,24): CS1061: 'INodeManagerBuilder' does not
  contain a definition for 'VariableFromDataTypeId'

The fix lands in #4132 (where the generator change belongs) and is cherry-picked here so
this branch builds standalone. It adds VariableFromDataTypeId to the builder interface
and implementation, NodeStateLookupExtensions.FindByDataType, and eight tests covering
the resolution rules.

Verified locally: MinimalBoilerServer - one of the two projects failing in CI - builds
clean on this branch.

marcschier and others added 9 commits July 31, 2026 14:57
DiNodeManager constructed NodeManagerBuilder without the data-type resolver, so
VariableFromDataTypeId reported BadNodeIdUnknown ("no predefined variable has DataType")
for every DI node manager - a misleading error, since the lookup had simply never
been supplied rather than the variable being absent.

Delegates to NodeStateLookupExtensions.FindByDataType rather than hand-rolling the
scan a fourth time in this file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
DiNodeManager constructed NodeManagerBuilder without the data-type resolver, so
VariableFromDataTypeId reported BadNodeIdUnknown ("no predefined variable has DataType")
for every DI node manager - a misleading error, since the lookup had simply never
been supplied rather than the variable being absent.

Delegates to NodeStateLookupExtensions.FindByDataType rather than hand-rolling the
scan a fourth time in this file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
DiNodeManager constructed NodeManagerBuilder without the data-type resolver, so
VariableFromDataTypeId reported BadNodeIdUnknown ("no predefined variable has DataType")
for every DI node manager - a misleading error, since the lookup had simply never
been supplied rather than the variable being absent.

Delegates to NodeStateLookupExtensions.FindByDataType rather than hand-rolling the
scan a fourth time in this file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
…client

# Conflicts:
#	src/Opc.Ua.Di.Server/DiNodeManager.cs
…-executors

# Conflicts:
#	src/Opc.Ua.Di.Server/DiNodeManager.cs
…-registry

# Conflicts:
#	src/Opc.Ua.Di.Server/DiNodeManager.cs
#	src/Opc.Ua.WotCon.Server/WotConModelPartition.cs
#	src/Opc.Ua.WotCon.Server/WotConnectivityNodeManager.cs
Allow lazy client connection retries after failed attempts while keeping concurrent callers on the same in-flight task. Validate malformed method-call responses before indexing result arrays, avoid redundant resource lookups during bulk load, and add bounded spinning to virtual file replacement contention paths.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Thing Descriptions are remote-supplied, so the host in a form's href was an
unvalidated outbound request target: the executors would connect to loopback,
link-local and private-range addresses, including the cloud instance metadata
service, and return the response body to the caller as a readable value.

Adds WotEndpointPolicy and WotEndpointValidator and enforces them in
WotProtocolBinderRegistry.OpenChannelAsync, the single point through which every
executor opens a channel. Loopback and private ranges are denied by default and
can be re-enabled per deployment. As with the asset endpoint validator, DNS is
deliberately not resolved during validation, because resolving at validation time
and again at connect time is itself a request-forgery vector.

Also rejects control characters in a form's declared content type, which could
otherwise be injected verbatim into outbound request headers, and MQTT topic
wildcards, which would subscribe the server to an entire broker namespace under
its own identity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Validate HTTP content types with MediaTypeHeaderValue before assigning request
content, and add parser-backed validation for configured default headers and
credential headers so CRLF-injected values are rejected instead of written to the
wire.

Reject MQTT wildcard publish topics at the executor sink even when planning has
explicitly opted into wildcard subscribe topics, and add the missing Modbus
multiple-register write null and range guards.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
@marcschier

Copy link
Copy Markdown
Collaborator Author

Security fixes pushed in 47dd8ba6d + b48558b01:

  • Cherry-picked the shared bindings endpoint policy and planner hardening from PR [WotCon] Add the WoT protocol binding abstractions and planners #4140.
  • Replaced HTTP Content-Type TryAddWithoutValidation with MediaTypeHeaderValue.TryParse + Headers.ContentType assignment; malformed values now fail instead of reaching the wire.
  • Validated HttpWotBindingOptions.DefaultHeaders and resolved WotCredential.Headers with the normal HttpHeaders.Add parser. These values come from trusted operator configuration / injected credential providers rather than the TD, but they are now sink-validated too.
  • Reject MQTT wildcard publish topics at the channel sink even if planning explicitly allows wildcard subscribe topics.
  • Added the missing Modbus FC16 null guard and MaxWriteRegisters range guard.

Verification:

  • dotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -p:CustomTestTarget=net10.0 --no-build --logger "console;verbosity=minimal" => 543 passed.
  • dotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -p:CustomTestTarget=net48 --logger "console;verbosity=minimal" => project skipped by its .NET 8+ guard for net48.

The endpoint policy was applied to a form's own target before the channel opened,
but a redirect selects a new target after that check. A permitted origin could
therefore bounce the request to a loopback or link-local address - including the
cloud instance metadata service - that the initial validation would have refused.

Redirect resolution now re-validates each hop against the same policy, so the
scheme, downgrade, loop and endpoint gates all apply for the whole chain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
@marcschier

Copy link
Copy Markdown
Collaborator Author

Follow-up: closed a bypass in the SSRF fix.

The endpoint policy added in the previous commit is applied to a form's own target in
WotProtocolBinderRegistry.OpenChannelAsync, but the HTTP executor follows redirects (up
to MaxAutomaticRedirects) and ResolveRedirectTarget only checked the scheme and the
https->http downgrade. A Thing Description naming a permitted public host that answers with
302 Location: http://169.254.169.254/latest/meta-data/ would therefore still reach the
instance metadata service - the initial validation never sees the redirect target.

ResolveRedirectTarget now re-validates every hop against the same WotEndpointPolicy, so
the scheme, downgrade, loop and endpoint gates all apply across the whole chain.

Verified by mutation testing rather than by the tests merely passing: with the new check
disabled, all four RedirectToPolicyBlockedEndpointIsRejectedAsync cases
(169.254.169.254, 10.0.0.1, 192.168.1.1, [fc00::1]) fail; with it enabled they pass.
RedirectToPrivateEndpointIsAllowedWhenPolicyOptsInAsync confirms an operator opt-in still
permits the redirect, so the gate is not a hard block.

Opc.Ua.WotCon.Bindings.Tests: 548 passed, 0 failed on net10.0 (was 543), 0 warnings.
The test project targets net8.0/net9.0/net10.0 only, so there is no net48 run for it.

marcschier and others added 15 commits August 4, 2026 13:31
Master replaced the ambient AsyncLocal re-entrancy guard with an explicit IOperationContext parameter on the lifecycle add, reload and remove operations, so the WoT projection host no longer compiled: its cancellation token was binding to the new caller-context parameter.

The host passes null, which states that these calls are not made on behalf of an executing OPC UA request. The shadow and immediate reload paths are unchanged because those overloads deliberately take no caller context; they are the reload modes that are safe to invoke from a request callback.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Merging master added an IOperationContext parameter to the NodeManager lifecycle add operation, so these tests no longer bound their argument to the intended overload. They pass null because the registration is created by the test fixture rather than on behalf of an executing request.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
## Summary
- add optional WoT Connectivity asset TD mirroring into the WoT
xRegistry
- add DI opt-in via AddWotRegistryBridge and direct
WotConnectivityServerOptions.RegistryBridge fallback
- document default-off, best-effort failure policy and usage
- add NUnit/Moq coverage for create, update, delete, disabled and
failure paths

## Validation
- dotnet build src\\Opc.Ua.WotCon.Server\\Opc.Ua.WotCon.Server.csproj -c
Release -v:m (all TFMs, 0 warnings/errors)
- dotnet test tests\\Opc.Ua.WotCon.Tests\\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 (0 failed / 983 passed)

---------

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
@marcschier marcschier added the ready Ready to merge once CI Passes label Aug 5, 2026
…ion (#4156)

Adds the runnable WoT Connectivity samples and completes the
documentation. This is **PR 16 of a
stack** splitting #4093, and the last one.

## Samples

Three projects under `samples/WotCon/`:

* **FlatTagServer** — a plain OPC UA server exposing flat tags, used as
an aggregation source.
* **AggregationServer** — registers Thing Descriptions with the WoT
registry and materializes two
flat sources into a runtime-loaded DI/Machinery/Pumps Pump model. It
also ships a small in-memory
protocol binding, which is the worked example the binding contributor
guide walks through.
* **AggregationClient** — drives the aggregated model: commands,
`Refresh`, monitoring, and
  generation replacement.

## Documentation

* New `docs/WotBindings.md` — the bindings that ship today
(planner/executor architecture, bundled
vs separate packages, operation coverage, target mapping, lazy channels,
generation lifetime) plus
the contributor guide for adding your own, with diagnostics, tests,
packaging, TFM, trimming and
  NativeAOT guidance.
* Expanded `docs/WoTConnectivity.md` covering the registry,
dependency-closure materialization and
  runtime NodeSet projection.
* `docs/RuntimeNodeSets.md` gains the shadow-reload section and
`docs/XRegistry.md` the shared
byte-store section. Both document APIs introduced earlier in the stack;
they are collected here
  because this is the documentation PR.
* `docs/README.md` links the new pages.

## Solution registration

Registers the three samples and `Opc.Ua.WotCon.Samples.Tests` in
`UA.slnx` so CI actually builds
and runs them.

## Stack position

Sits on top of **#4154** (materialization) and additionally merges
**#4142** (registry client) and
**#4144** (protocol executors), because the samples exercise the whole
stack — `AggregationServer`
needs the OPC UA executor and the client sample needs the registry
client. Its diff therefore shows
their content until they land.

It also carries `WotRegistryProjectionLiveTests.cs`, which #4154
deliberately left out: those tests
need both the materialization runtime *and* the registry client, so this
is the first branch where
they compile.

## Validation

* All three samples build clean on every target framework.
* `Opc.Ua.WotCon.Tests`: **830 passed**, 0 failed (769 from #4154 plus
61 from the live projection
  tests).
* `Opc.Ua.WotCon.Samples.Tests`: 6 of 7 pass locally; the remaining one
fails with
`CryptographicException: The system cannot find the path specified`, a
known certificate-store
problem on this machine that was previously reproduced against a clean
baseline worktree and is
  not related to this change. CI will confirm.
* 0 warnings, 0 errors.

One fix was needed while bringing the sample across: its memory binding
called `ResolveCodec` with
the pre-`out WotPayloadDescriptor` signature. The API gained the payload
out-parameter and a
`bool` result during review of the binding PRs, so the sample now
follows the same
`if (!ResolveCodec(...)) return Unsupported(...)` pattern as the shipped
planners.

Integration PR: #4093.
…pace (#4154)

PR 14 of the stacked series splitting the large WoT Connectivity
integration PR #4093.

This PR adds the server-side materialization runtime that turns WoT
Thing Description resources from the xRegistry snapshot into OPC UA
address-space projections. The coordinator builds the dependency closure
for each resource, plans binding metadata, activates binding channels,
and publishes the resulting projection through the registry NodeManager
so refresh, retire, and reload operations remain observable and
deterministic.

Projection intentionally goes through the NodeManager lifecycle reload
API instead of mutating live nodes directly. That keeps complex type
reload, runtime NodeSet publication, shadow reload, immediate reload,
and retirement on the same lifecycle path as other dynamic server
address-space changes.

This branch has a triple dependency. It is based on #4146 (registry),
and additionally needs #4147 (lifecycle reload API) and #4128
(2-argument TryGetStructure). Both #4147 and #4128 are merged into this
branch, so this PR's diff will show their content until those
dependencies land.

Validation performed:
- dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -f net10.0 -v:m
- dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m
- dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build
- dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net48 -v:m
- dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net48 --no-build
- dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m
- dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build (one known inherited
failure: LiveNodeManagerAddRefreshesTheClientNamespaceTable)
PR 15 of a stacked split of integration PR #4093 into independently
reviewable pieces.

This branch is stacked on #4139 (`marcschier/wot-10-wotcon-model`) and
additionally has #4128 (`marcschier/wot-01-core-types`) and #4130
(`marcschier/wot-09-xregistry-client`) merged in so this client slice
can build.

Those extra dependencies are intentional:
- Without #4130: `WotRegistryClient.cs:83` — `error CS1729:
'XRegistryClient' does not contain a constructor that takes 4
arguments`.
- Without #4128: `WotRegistryClient.cs:346,375` and
`WotRegistryResourceClient.cs:144` — `error CS1501: No overload for
method 'TryGetStructure' takes 2 arguments`.

The extra diff from #4128 and #4130 disappears once those PRs land.

This PR adds the WoT Connectivity registry client surface for browsing
and reading registry groups, resources and versions, uploading documents
through FileTransfer, invoking registry methods, and registering the
client through DI/builder helpers.
Adds the WoT Connectivity registry service and stores for Thing
Description and Thing Model documents.

This is PR 13 of the stacked split of integration PR #4093 and is
stacked on #4140 (`marcschier/wot-11-bindings-core`). The
materialization runtime that consumes this registry follows separately.

The registry has no dependency on the binding runtime or the NodeManager
lifecycle; the stacking is only because this change shares the WoT
Connectivity server project file with the preceding PRs.

Validation:
- `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -f net10.0 -v:m`
- `dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build -v:q`
- `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -v:m`
@marcschier
marcschier merged commit 7d60213 into marcschier/wot-11-bindings-core Aug 5, 2026
1 check passed
marcschier added a commit that referenced this pull request Aug 5, 2026
…ation, Server and xRegistry work it builds on (#4128)

## Summary

Adds OPC UA WoT Connectivity 1.1 to the stack, together with the Types,
source generation, Server and xRegistry work it is built on. This branch
is the merge point for the fourteen-PR stack that replaced integration
PR #4093; every constituent PR was reviewed and approved separately and
is listed below.

414 files changed, +108,936 / -3,199. Roughly 46k added lines of product
code, 57k of tests, plus samples, tools and documentation.

## Types

- Complete `ExtensionObject` raw-body decoding for binary, XML and JSON
bodies by resolving the concrete type through the message context's
encodeable factory, and simplify the `Variant` structure helpers by
delegating to it (#4128).
- Add `IAtomicFileReplace` as an optional `IFileSystem` capability so
existing external implementations keep working, with atomic publish for
`LocalFileSystem` via `File.Replace`/`File.Move` and for
`VirtualFileSystem` by re-keying the in-memory entry (#4128).
- Add lossless conversion between WoT documents and NodeSet2
(`Opc.Ua.Wot.WotNodeSetConverter`). A byte-exact `uav:nodeSet` envelope
is preserved when requested, the structured `uav:nodes` projection is
used when the readable vocabulary is incomplete, and NodeSet2 is
otherwise synthesized from readable WoT terms. Unmapped JSON members
survive a round trip as pointer-addressed residue (#4131).

## Source generation

- Improve the NodeSet to ModelDesign conversion and the generated node
state (#4132).
- Generate OPC UA models directly from WoT Thing Description files
(#4134).

## Server

- Add live NodeManager shadow and immediate reload. A reload
materializes a new generation beside the active one and switches
atomically; the superseded generation is retired gracefully so existing
MonitoredItems keep being served until they drain, or immediately when
the caller asks for it. Includes the request admission and drain
machinery that lets an orderly shutdown wait for admitted requests
instead of tearing down underneath them (#4147).
- Make subscription transfer between sessions transactional, so a failed
transfer leaves neither session holding a partially moved subscription
(#4150).

## xRegistry

- Allow clients to use explicit registry roots rather than assuming a
well-known location (#4130).

## WoT Connectivity

- Add the WoT Connectivity 1.1 information model: a registry-first
revision layered on the abstract xRegistry base model, incorporating the
complete published OPC 10100-1 v1.02 surface as deprecated nodes in the
same namespace so existing 1.02 clients keep working (#4139).
- Add the protocol binding abstractions and planners that compile WoT
forms into executable plans (#4140).
- Add the HTTP, Modbus, OPC UA and MQTT binding executors (#4144).
- Add the registry and its stores, including document versioning,
validation, dependency resolution and refresh (#4146).
- Add the registry client (#4142).
- Materialize WoT Thing Descriptions into the server address space,
deriving types from Thing Models and instances from Thing Descriptions
(#4154).
- Restore the WoT asset registry bridge so the deprecated 1.02 asset
surface is backed by the registry (#4171).
- Add the WoT Connectivity samples and complete the documentation
(#4156), and restore the remaining coverage and docs (#4172).

## New projects

`src/Opc.Ua.WotCon.Bindings`, `src/Opc.Ua.WotCon.Bindings.Mqtt`,
`tests/Opc.Ua.WotCon.Bindings.Tests`,
`tests/Opc.Ua.WotCon.Samples.Tests`, and the `samples/WotCon`
AggregationServer, AggregationClient and FlatTagServer samples.

## Constituent pull requests

Merged into this branch top-down, each independently approved:

| PR | Title |
| --- | --- |
| #4128 | [Types] Complete ExtensionObject decoding and add an atomic
file replace capability |
| #4131 | [Types] Add lossless conversion between WoT documents and
NodeSet2 |
| #4132 | [SourceGeneration] Improve NodeSet to ModelDesign conversion
and generated node state |
| #4134 | [SourceGeneration] Generate OPC UA models from WoT Thing
Description files |
| #4147 | [Server] Add live NodeManager shadow and immediate reload |
| #4150 | [Server] Make subscription transfer between sessions
transactional |
| #4130 | [XRegistry] Allow clients to use explicit registry roots |
| #4139 | [WotCon] Add the WoT Connectivity 1.1 information model |
| #4140 | [WotCon] Add the WoT protocol binding abstractions and
planners |
| #4144 | [WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding
executors |
| #4146 | [WotCon] Add the WoT Connectivity registry and its stores |
| #4142 | [WotCon] Add the WoT Connectivity registry client |
| #4154 | [WotCon] Materialize WoT Thing Descriptions into the server
address space |
| #4156 | [WotCon] Add the WoT Connectivity samples and complete the
documentation |
| #4171 | Restore WoT asset registry bridge |
| #4172 | Restore WoT coverage and docs |

Supersedes #4093.

## Follow-up

The WoT Connectivity and WoT Binding drafts have moved to 1.1-draft2
since this work was authored. Aligning with that revision - the removed
group vocabulary, the new projection/View construct, the remaining model
vocabulary terms and the two new portable-identity validation rules - is
tracked separately and will follow in its own pull request.

## Validation

Built for every target framework with zero warnings and zero errors, and
validated at the tip of the stack with `Opc.Ua.WotCon.Tests` at 999
passed / 0 failed and `Opc.Ua.Server.Tests` at 4024 passed / 0 failed.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants