Skip to content

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

Open
marcschier wants to merge 22 commits into
marcschier/wot-11-bindings-corefrom
marcschier/wot-12-executors
Open

[WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding executors#4144
marcschier wants to merge 22 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 2 commits July 31, 2026 11:22
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
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 4 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
…-executors

# Conflicts:
#	src/Opc.Ua.Di.Server/DiNodeManager.cs
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 4 commits July 31, 2026 19:53
Opc.Ua.WotCon.Bindings.Mqtt was added without being listed in UA.slnx, so the
solution build never compiled it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Copilot AI review requested due to automatic review settings August 1, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds concrete WoT Connectivity binding executors (HTTP, Modbus TCP, OPC UA, MQTT) on top of the previously-landed planner/abstraction layer, and wires in end-to-end + unit test coverage including in-process protocol harnesses.

Changes:

  • Add new executor implementations and DI builder extensions for HTTP, Modbus TCP, and OPC UA in Opc.Ua.WotCon.Bindings.
  • Introduce a new Opc.Ua.WotCon.Bindings.Mqtt project containing the MQTT executor implementation and packaging metadata.
  • Expand Opc.Ua.WotCon.Bindings.Tests with protocol harnesses and end-to-end tests covering HTTP/Modbus/MQTT/OPC UA execution.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
UA.slnx Adds the new MQTT executor project to the solution.
tests/Opc.Ua.WotCon.Bindings.Tests/Support/TestModbusServer.cs Adds an in-process Modbus TCP simulator used by Modbus executor tests.
tests/Opc.Ua.WotCon.Bindings.Tests/Support/TestHttpServer.cs Adds an in-process HTTP/1.1 server used by HTTP executor tests.
tests/Opc.Ua.WotCon.Bindings.Tests/OpcUaWotExecutorTests.cs Adds end-to-end OPC UA executor tests using an in-process ReferenceServer.
tests/Opc.Ua.WotCon.Bindings.Tests/OpcUaWotBindingBuilderExtensionsTests.cs Extends DI builder extension tests to cover new transport executors.
tests/Opc.Ua.WotCon.Bindings.Tests/Opc.Ua.WotCon.Bindings.Tests.csproj Updates test project dependencies and references to include new executors and harnesses.
tests/Opc.Ua.WotCon.Bindings.Tests/MqttWotExecutorTests.cs Adds end-to-end MQTT executor tests against an in-process broker.
tests/Opc.Ua.WotCon.Bindings.Tests/MqttWotConnectionTests.cs Adds unit tests for MQTT connection security policy (mqtt vs mqtts, credential handling).
tests/Opc.Ua.WotCon.Bindings.Tests/ModbusWotExecutorTests.cs Adds end-to-end Modbus executor tests using the in-process Modbus simulator.
tests/Opc.Ua.WotCon.Bindings.Tests/ModbusDataConverterTests.cs Adds unit tests for Modbus register/value conversions.
tests/Opc.Ua.WotCon.Bindings.Tests/ModbusDataConverterAdditionalTests.cs Adds additional Modbus conversion tests covering aliases and byte/word order combinations.
tests/Opc.Ua.WotCon.Bindings.Tests/HttpWotExecutorTests.cs Adds end-to-end HTTP executor tests using the in-process HTTP server.
tests/Opc.Ua.WotCon.Bindings.Tests/HttpWotBindingChannelTests.cs Adds channel-level HTTP executor tests (status mapping, bounds, decoding, polling observe, etc.).
tests/Opc.Ua.WotCon.Bindings.Tests/HttpStatusMapperTests.cs Adds unit tests for HTTP status-code to OPC UA StatusCode mapping.
tests/Opc.Ua.WotCon.Bindings.Tests/HttpCredentialResolutionTests.cs Adds concurrency/race tests for HTTP credential resolution behavior.
tests/Opc.Ua.WotCon.Bindings.Tests/ExecutorUnitTests.cs Adds unit tests for executor identity/dispatch and HTTP sink hardening scenarios.
src/Opc.Ua.WotCon.Bindings/OpcUa/OpcUaWotBindingOptions.cs Adds OPC UA executor options including session factory + observe/event configuration.
src/Opc.Ua.WotCon.Bindings/OpcUa/OpcUaWotBindingExecutor.cs Implements OPC UA executor activation via an injected session factory.
src/Opc.Ua.WotCon.Bindings/OpcUa/OpcUaTargetWotBindingBuilderExtensions.cs Adds DI builder extension to register OPC UA executor + planners.
src/Opc.Ua.WotCon.Bindings/Modbus/OpcUaModbusWotBindingBuilderExtensions.cs Adds DI builder extension to register Modbus executor + planners.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusWotBindingOptions.cs Adds Modbus executor options and Modbus exception→StatusCode mapping helper.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusWotBindingExecutor.cs Implements Modbus executor activation and connection lifecycle.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusWotBindingChannel.cs Implements Modbus channel read/write/observe behavior and status mapping.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusException.cs Adds Modbus protocol exception type carrying device exception codes.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusDataConverter.cs Adds Modbus register↔Variant conversion logic with byte/word-order handling.
src/Opc.Ua.WotCon.Bindings/Modbus/ModbusAddressing.cs Adds validated Modbus addressing parsing/validation from compiled forms.
src/Opc.Ua.WotCon.Bindings/Http/OpcUaHttpWotBindingBuilderExtensions.cs Adds DI builder extension to register HTTP executor + planners.
src/Opc.Ua.WotCon.Bindings/Http/HttpWotBindingOptions.cs Adds HTTP executor options (client factory, redirect policy flags, polling, etc.).
src/Opc.Ua.WotCon.Bindings/Http/HttpWotBindingExecutor.cs Implements HTTP executor activation and caller-client safety fail-closed behavior.
src/Opc.Ua.WotCon.Bindings/Http/HttpStatusMapper.cs Implements HTTP status-code to OPC UA StatusCode mapping.
src/Opc.Ua.WotCon.Bindings.Mqtt/Properties/AssemblyInfo.cs Adds assembly metadata for the new MQTT executor project.
src/Opc.Ua.WotCon.Bindings.Mqtt/OpcUaMqttWotBindingBuilderExtensions.cs Adds DI builder extension to register MQTT executor + planners.
src/Opc.Ua.WotCon.Bindings.Mqtt/Opc.Ua.WotCon.Bindings.Mqtt.csproj Introduces the MQTT executor project (TFMs, packaging, references).
src/Opc.Ua.WotCon.Bindings.Mqtt/NugetREADME.md Adds NuGet package README documenting MQTT executor behavior/security rules.
src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotConnection.cs Implements MQTT connection planning with transport security + credential/trust resolution.
src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotBindingOptions.cs Adds MQTT executor options (client factory, TLS validation, timeouts, etc.).
src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotBindingExecutor.cs Implements MQTT executor activation and connection lifecycle.
src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotBindingChannel.cs Implements MQTT channel read/write/invoke/observe and message dispatch.

Comment thread src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotBindingChannel.cs
Comment thread src/Opc.Ua.WotCon.Bindings.Mqtt/MqttWotBindingChannel.cs
Comment thread src/Opc.Ua.WotCon.Bindings/OpcUa/OpcUaWotBindingExecutor.cs Outdated
Comment thread tests/Opc.Ua.WotCon.Bindings.Tests/Support/TestHttpServer.cs Outdated
Comment thread tests/Opc.Ua.WotCon.Bindings.Tests/Support/TestModbusServer.cs Outdated
Validate hand-built MQTT subscribe topics, reject concurrent MQTT reads, preserve synthetic OPC UA endpoint ports, and remove blocking waits from test helper teardown.

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

Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
The HTTP and Modbus channels are the only production callers that create a
PollingWotSubscription, so without forwarding the runtime context the disposal
diagnostic could never reach a real logger. Both channels already receive a
WotExecutorContext, so hand its telemetry to the subscription; the Modbus channel
now retains that context the same way the HTTP channel does.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants