[WotCon] Add the WoT protocol binding abstractions and planners - #4140
[WotCon] Add the WoT protocol binding abstractions and planners#4140marcschier wants to merge 19 commits into
Conversation
Introduces the protocol-agnostic half of the WoT binding runtime: the binder, planner, executor and channel factory contracts, the payload codec and credential provider seams, the binding plan model, and the polling subscription that drives forms without native observation. Eight protocol planners are included - HTTP, CoAP, MQTT, Modbus TCP, BACnet, PROFINET, LoRaWAN and OPC UA. A planner validates a form and produces a binding plan; it performs no transport I/O, so this layer can be reviewed and tested without any network stack. The concrete executors follow separately. 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
|
Fixed the net10.0 solution build failure on this PR. This branch carries the The fix lands in #4132 (where the generator change belongs) and is cherry-picked here so Verified locally: |
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
…bindings-core # 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
|
Security fixes pushed in
Follow-up note: once Verification:
|
The Opc.Ua.WotCon.Bindings library and its test project were added without being listed in UA.slnx, so the solution build never compiled them and their tests never ran in CI. Registering both restores that coverage: 310 binding tests now execute. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
An empty collection expression binds to ImmutableDictionary<TKey, TValue> only on the newest framework, so the binding plan, credential and plan-model types failed to compile on net9.0 and below. Naming the Empty singleton explicitly works on every target. This was invisible until the project was added to the solution, because nothing built it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
There was a problem hiding this comment.
Pull request overview
This PR introduces the protocol-agnostic core of WoT Connectivity protocol bindings by adding a new Opc.Ua.WotCon.Bindings library (binder/planner/registry abstractions + planners/validators) and a matching test project, and wires both into the main solution.
Changes:
- Add
src/Opc.Ua.WotCon.Bindingswith binder identity/capability models, plan model, diagnostics, endpoint policy/validator, registry, codecs, and polling subscription helper. - Add planner/validator implementations for multiple protocols (HTTP, CoAP, MQTT, Modbus TCP, BACnet, PROFINET, LoRaWAN, OPC UA).
- Add
tests/Opc.Ua.WotCon.Bindings.Testsand include both projects inUA.slnx.
Reviewed changes
Copilot reviewed 53 out of 53 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| UA.slnx | Adds the new bindings library and its test project to the solution. |
| src/Opc.Ua.WotCon.Bindings/Opc.Ua.WotCon.Bindings.csproj | New bindings/planners package project definition and TFM-conditional compilation. |
| src/Opc.Ua.WotCon.Bindings/NugetREADME.md | Package readme describing scope, TFMs, and executor/planner split. |
| src/Opc.Ua.WotCon.Bindings/Properties/AssemblyInfo.cs | Assembly-level attributes for the new bindings library. |
| src/Opc.Ua.WotCon.Bindings/OpcUaWotBindingBuilderExtensions.cs | DI/builder registration helpers for binders/executors/credentials/policy. |
| src/Opc.Ua.WotCon.Bindings/IWotBinderRegistry.cs | Registry seam + Null registry baseline implementation. |
| src/Opc.Ua.WotCon.Bindings/IWotBindingChannelFactory.cs | Narrow runtime seam for opening channels for executable compiled forms. |
| src/Opc.Ua.WotCon.Bindings/IWotBindingExecutor.cs | Executor/channel/subscription contracts + operation result models. |
| src/Opc.Ua.WotCon.Bindings/IWotBindingPlanner.cs | Planning context + compiled form model + compilation result model. |
| src/Opc.Ua.WotCon.Bindings/IWotProtocolBinder.cs | Core binder interface + shared binder base with common validation helpers. |
| src/Opc.Ua.WotCon.Bindings/IWotCredentialProvider.cs | Credential reference/provider seam (secret-free references). |
| src/Opc.Ua.WotCon.Bindings/IWotPayloadCodec.cs | Payload codec + codec registry and built-in codecs (JSON/text/octet-stream). |
| src/Opc.Ua.WotCon.Bindings/WotAffordanceForm.cs | Immutable extracted form model with pointer helpers and typed term accessors. |
| src/Opc.Ua.WotCon.Bindings/WotFormExtractor.cs | Extracts forms/security/ops defaults from TD/TM JSON without transport I/O. |
| src/Opc.Ua.WotCon.Bindings/WotBindingPlan.cs | Plan request builder from document + immutable plan result container. |
| src/Opc.Ua.WotCon.Bindings/WotBindingPlanModel.cs | Transport-neutral descriptors (endpoint/addressing/operation/payload) and op mapping helpers. |
| src/Opc.Ua.WotCon.Bindings/WotBindingBounds.cs | Central safety bounds applied during planning/execution. |
| src/Opc.Ua.WotCon.Bindings/WotBindingIdentity.cs | Binder identity model with stable id@version key semantics. |
| src/Opc.Ua.WotCon.Bindings/WotBindingIdentification.cs | Matching model for deterministic binder selection + selection context pins. |
| src/Opc.Ua.WotCon.Bindings/WotBindingCapability.cs | Browseable capability snapshot model projected to generated UA datatype. |
| src/Opc.Ua.WotCon.Bindings/WotBindingDiagnostic.cs | Structured diagnostics with stable codes + JSON pointers. |
| src/Opc.Ua.WotCon.Bindings/WotBindingMaturity.cs | Spec maturity enum + pinned spec source descriptor. |
| src/Opc.Ua.WotCon.Bindings/WotEndpointPolicy.cs | Operator-configurable endpoint allow/deny policy. |
| src/Opc.Ua.WotCon.Bindings/WotEndpointValidator.cs | Fail-closed endpoint validation (scheme/host/IP-literal checks) without DNS resolution. |
| src/Opc.Ua.WotCon.Bindings/WotProtocolBinderRegistry.cs | Aggregating registry: selection, planning, target-mapping validation, executability gating, activation tracking, channel opening. |
| src/Opc.Ua.WotCon.Bindings/PollingWotSubscription.cs | Polling-based observe/event subscription helper with retry/backoff integration. |
| src/Opc.Ua.WotCon.Bindings/ModbusDataTypes.cs | Modbus scalar type normalization and register-width mapping. |
| src/Opc.Ua.WotCon.Bindings/ModbusProtocolLimits.cs | Shared Modbus protocol limit constants. |
| src/Opc.Ua.WotCon.Bindings/Planners/WotBuiltInBinders.cs | Factory for the eight built-in planner binders. |
| src/Opc.Ua.WotCon.Bindings/Planners/WotBindingSources.cs | Pinned source catalog for each shipped binding/planner. |
| src/Opc.Ua.WotCon.Bindings/Planners/HttpBindingPlanner.cs | HTTP planner/validator and compiled-form production. |
| src/Opc.Ua.WotCon.Bindings/Planners/CoapBindingPlanner.cs | CoAP planner/validator (non-executable in this split). |
| src/Opc.Ua.WotCon.Bindings/Planners/MqttBindingPlanner.cs | MQTT planner/validator and addressing compilation. |
| src/Opc.Ua.WotCon.Bindings/Planners/ModbusBindingPlanner.cs | Modbus planner/validator and addressing/operation compilation. |
| src/Opc.Ua.WotCon.Bindings/Planners/BacnetBindingPlanner.cs | BACnet planner/validator (non-executable in this split). |
| src/Opc.Ua.WotCon.Bindings/Planners/ProfinetBindingPlanner.cs | PROFINET planner/validator (non-executable in this split). |
| src/Opc.Ua.WotCon.Bindings/Planners/LoRaWanBindingPlanner.cs | LoRaWAN planner/validator (non-executable in this split). |
| src/Opc.Ua.WotCon.Bindings/Planners/OpcUaBindingPlanner.cs | OPC UA planner/validator and NodeId/endpoint addressing compilation. |
| tests/Opc.Ua.WotCon.Bindings.Tests/Opc.Ua.WotCon.Bindings.Tests.csproj | New test project for bindings/planners/registry. |
| tests/Opc.Ua.WotCon.Bindings.Tests/Properties/AssemblyInfo.cs | Assembly-level attributes for the new test assembly. |
| tests/Opc.Ua.WotCon.Bindings.Tests/WotProtocolBinderBaseAdditionalTests.cs | Additional binder-base helper coverage (ops/security/href bounds). |
| tests/Opc.Ua.WotCon.Bindings.Tests/WotEndpointValidatorTests.cs | Endpoint policy/validator coverage (SSRF fail-closed behavior). |
| tests/Opc.Ua.WotCon.Bindings.Tests/WotBinderRegistryDiTests.cs | DI wiring tests for EnsureWotBinderRegistry semantics. |
| tests/Opc.Ua.WotCon.Bindings.Tests/PollingWotSubscriptionTests.cs | Polling subscription retry/backoff + disposal semantics tests. |
| tests/Opc.Ua.WotCon.Bindings.Tests/OpcUaWotBindingBuilderExtensionsTests.cs | Builder extension registration tests. |
| tests/Opc.Ua.WotCon.Bindings.Tests/ModbusBindingPlannerAdditionalTests.cs | Additional Modbus planner coverage (pollingTime, function codes, unit-id parsing). |
Suppressed comments (2)
src/Opc.Ua.WotCon.Bindings/OpcUaWotBindingBuilderExtensions.cs:89
- ServiceDescriptor.Singleton(binder) registers only the concrete binder type, not IWotProtocolBinder. This prevents WotProtocolBinderRegistry (constructed from GetServices()) from seeing custom binders added via AddWotBinder.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(binder));
EnsureRegistry(builder.Services);
return builder;
src/Opc.Ua.WotCon.Bindings/OpcUaWotBindingBuilderExtensions.cs:129
- AddWotCredentialProvider uses AddSingleton(provider), which registers the provider under its concrete type only. EnsureWotBinderRegistry resolves IWotCredentialProvider, so a custom provider added here won't be used (registry falls back to NullWotCredentialProvider).
public static IOpcUaBuilder AddWotCredentialProvider(
this IOpcUaBuilder builder, IWotCredentialProvider provider)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
if (provider is null)
{
throw new ArgumentNullException(nameof(provider));
}
builder.Services.AddSingleton(provider);
return builder;
}
The generator now emits a distinct handler delegate and result type per declaring type, so a method that appears on more than one type no longer shares a single generic name. CloseAndUpdate on the WoT file and the five WoTAssetConnectionManagement methods each have their own type now, and the server still referenced the generic names, so it no longer compiled. Points the asset file manager at the WoT file's CloseAndUpdate handler and the connectivity node manager at the management type's result types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
…bindings-core # Conflicts: # src/Opc.Ua.WotCon.Server/Assets/WotAssetFileManager.cs
Register WoT binders and executors explicitly under their interfaces so registry resolution does not depend on overload inference. Correct the endpoint allow-list documentation to reflect that scheme and address-range checks still apply, and make polling subscription disposal best-effort for residual non-OOM loop faults while logging them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Replace the public PollingWotSubscription logger parameter with optional ITelemetryContext so disposal diagnostics follow the repository observability convention. Carry telemetry through WotExecutorContext and the DI-created binder registry so executors can pass the runtime context into polling subscriptions without introducing a bare ILogger API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Reading TelemetryExtensions.InternalOnly__TelemetryHook from a binding assembly reaches for an API whose name reserves it for the core to install a process-wide default. Fall back to AmbientMessageContext.Telemetry instead, matching how Subscription resolves an optional telemetry context, and let the null-tolerant telemetry extension methods supply the default logger factory when no context is in scope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
…into marcschier/wot-11-bindings-core
Summary
PR 11 of the stacked split for integration PR #4093. This adds the protocol-agnostic core of
Opc.Ua.WotCon.Bindings: binder, planner, executor and channel-factory contracts; payload codec and credential provider seams; the binding plan model; polling subscriptions for forms without native observation; and DI/builder registration helpers.This PR is stacked on #4139, which itself depends on #4132 and #4134.
Scope
The included planners validate forms and produce binding plans without transport I/O:
Keeping concrete transport executors out of this PR makes the abstractions and planning layer reviewable without a network stack.
Validation
dotnet build tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 -v:mdotnet test tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --no-builddotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj -c Release -v:m