mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-28 22:55:49 +08:00
The WSS transport constants used the unregistered uawss- prefix for the UA-SecureConversation/UA-Binary and UA-JSON profiles. Use the profile URIs registered by the OPC Foundation: https://profiles.opcfoundation.org/profile/254 https://profiles.opcfoundation.org/profile/253 The WebSocket mappings and sub-protocols are specified in OPC UA Part 6 section 7.5.2: https://reference.opcfoundation.org/Core/Part6/v105/docs/7.5.2 Update the transport-profile helpers, regression tests, and documentation accordingly so endpoint discovery publishes identifiers recognized by conforming OPC UA clients and servers. Tests: dotnet test tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj -f net10.0 --filter FullyQualifiedName~TransportProfileHelpersTests (39 passed) # Description _Describe the changes here to communicate to the maintainers why they should accept this pull request. By default - this will become the Commit message after merging and thus define history._ ## Related Issues _Reference all GitHub issues this PR addresses. If there is no issue yet, open one and link it here._ _If this is a relatively large or complex change, a design must have been discussed in the related tracking issue and signed off (which becomes the Architectural Decision Record (ADR))._ - Fixes #github-issue-number, ... ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [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. - [ ] 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. Co-authored-by: Julius Pfrommer <julius.pfrommer@o6-automation.com>
451 lines
21 KiB
Markdown
451 lines
21 KiB
Markdown
# Transport Profiles
|
|
|
|
This document is the practical companion to [`Profiles.md`](Profiles.md) and
|
|
describes how the OPC UA .NET Standard stack ships each of the transport
|
|
profiles defined in OPC UA Part 6 §7. It is aimed at developers who need
|
|
to host or connect to OPC UA servers over something other than the
|
|
default `opc.tcp://` transport — for example to traverse firewalls or
|
|
to integrate with web-based tooling.
|
|
|
|
## Transport profile matrix
|
|
|
|
| Profile | URL scheme | Wire format | UA Secure Conversation | Security modes |
|
|
|---|---|---|---|---|
|
|
| `uatcp-uasc-uabinary` | `opc.tcp://` | UA Binary | yes | `None`, `Sign`, `SignAndEncrypt` |
|
|
| `https-uabinary` | `opc.https://`, `https://` | UA Binary in HTTP body (`application/octet-stream`) | yes (one chunk per POST) | `None`, `Sign`, `SignAndEncrypt` |
|
|
| `https-uajson` | `opc.https://`, `https://` | UA JSON in HTTP body (`application/opcua+uajson`) | **no** — TLS only | `None` only |
|
|
| [`https-uajson-openapi`](https://profiles.opcfoundation.org/profile/2338) | `opc.https://`, `https://` | OpenAPI Mapping (Part 6 §G.3): per-service `POST /<service>` with body = `<Service>Request` JSON (`application/json; encoding=compact\|verbose`) | **no** — TLS only | `None` only |
|
|
| `wss-uasc-uabinary` | `opc.wss://`, `wss://` | UA Binary in WebSocket binary frame (sub-protocol `opcua+uacp`) | yes | `None`, `Sign`, `SignAndEncrypt` |
|
|
| `wss-uajson` | `opc.wss://`, `wss://` | UA JSON in WebSocket text frame (sub-protocol `opcua+uajson`) | **no** — TLS only | `None` only |
|
|
| [`wss-uajson-openapi`](https://profiles.opcfoundation.org/profile/2339) | `opc.wss://`, `wss://` | OpenAPI Mapping over WebSocket text frame (sub-protocol `opcua+openapi` / `opcua+openapi+<accesstoken>`) | **no** — TLS only | `None` only |
|
|
|
|
The JSON / OpenAPI profiles do not negotiate a UA SecureChannel; transport
|
|
security is provided exclusively by the surrounding TLS connection.
|
|
Servers MUST advertise these endpoints with `MessageSecurityMode.None`
|
|
and `SecurityPolicyUri = None`. The `https-uajson` and WSS JSON
|
|
profiles use the Compact (reversible) flavour mandated by
|
|
Part 6 §5.4.9 (`JsonEncoderOptions.Compact`). The OpenAPI profiles
|
|
select between Compact (default, mandatory) and Verbose via
|
|
the `application/json; encoding=compact|verbose` media-type parameter
|
|
on `Content-Type` / `Accept` — see [`WebApi.md`](WebApi.md) for the
|
|
full mapping table.
|
|
|
|
The OPC UA Profiles surface exposes the two OpenAPI profile URIs as
|
|
`Profiles.HttpsOpenApiTransport` (profile/2338) and
|
|
`Profiles.WssOpenApiTransport` (profile/2339); the `HttpsServiceHost`
|
|
emits the HTTPS twin as a discovery-only `EndpointDescription`
|
|
alongside each `SecurityMode.None` HTTPS-binary endpoint so discovery
|
|
clients see the OpenAPI route without hard-coding the URL.
|
|
|
|
## Assembly layout
|
|
|
|
* **`Opc.Ua.Core`** (this is what every Server / Client application
|
|
references):
|
|
- `IUaSCByteTransport` — the runtime transport boundary used by the
|
|
UA Secure Conversation channel pipeline.
|
|
- `TcpByteTransport`, `TcpByteTransportFactory` — TCP implementation.
|
|
- `TcpTransportChannel` + `TcpTransportChannelFactory` — client-side
|
|
channel for `opc.tcp://`.
|
|
- `HttpsTransportChannel` + `HttpsTransportChannelFactory`,
|
|
`OpcHttpsTransportChannelFactory` — polymorphic client-side channel
|
|
that handles both `https-uabinary` and `https-uajson` based on the
|
|
endpoint's `TransportProfileUri`.
|
|
* **`Opc.Ua.Bindings.Https`** (Kestrel-dependent; pulled in
|
|
automatically when an HTTPS or WSS endpoint is configured):
|
|
- `HttpsTransportListener` — Kestrel-hosted listener that dispatches
|
|
requests on `(Upgrade, ContentType)` to the binary / JSON / WSS
|
|
handlers.
|
|
- `HttpsTransportListenerFactory`, `OpcHttpsTransportListenerFactory`
|
|
— listener factories for `https://` and `opc.https://`.
|
|
- `WssTransportListenerFactory`, `OpcWssTransportListenerFactory` —
|
|
listener factories for `wss://` and `opc.wss://`.
|
|
- `WebSocketClientByteTransport` + `WebSocketClientByteTransportFactory`,
|
|
`WssTransportChannel`, `WssTransportChannelFactory`,
|
|
`OpcWssTransportChannelFactory` — client-side WSS+uacp.
|
|
- `WssJsonTransportChannel`, `WssJsonTransportChannelFactory` —
|
|
client-side WSS+uajson (per-request `ClientWebSocket`).
|
|
- `JsonRequestMapper` — shared JSON encode / decode helper used by
|
|
both server handlers.
|
|
|
|
For both WSS variants the HTTPS / WSS factories live in
|
|
`Opc.Ua.Bindings.Https`; consumers register them via the
|
|
`AddHttpsTransport()` / `AddWssTransport()` DI extensions on
|
|
`IOpcUaBuilder`, or by constructing a `DefaultTransportBindingRegistry`
|
|
and calling `RegisterListenerFactory` / `RegisterChannelFactory`
|
|
directly for non-DI hosts.
|
|
|
|
`AddHttpsTransport(options => ...)` is available as a one-shot registration
|
|
for the Kestrel HTTPS stack. It registers HTTPS and WSS by default and can
|
|
attach the WebApi binding and authentication callbacks in the same fluent
|
|
chain. `AddWebApiTransport()` and `AddKestrelOpcTcpTransport()` are
|
|
order-independent with the underlying HTTPS/TCP registrations.
|
|
|
|
## Server-side configuration
|
|
|
|
Servers declare endpoints via `ApplicationConfiguration` →
|
|
`ServerConfiguration` → `BaseAddresses`. Each base address yields a
|
|
single listener for its scheme.
|
|
If a discovery or hosted server has a non-TCP base address but the matching
|
|
transport binding has not been registered, startup throws a clear error that
|
|
names the missing scheme and suggested `Add*Transport()` method.
|
|
|
|
```xml
|
|
<BaseAddresses>
|
|
<ua:String>opc.tcp://localhost:62541/MyServer</ua:String>
|
|
<ua:String>opc.https://localhost:62540/MyServer</ua:String>
|
|
<ua:String>opc.wss://localhost:62543/MyServer</ua:String>
|
|
</BaseAddresses>
|
|
```
|
|
|
|
* The `opc.tcp` listener uses the lightweight raw-socket
|
|
`TcpTransportListener` by default (no ASP.NET Core dependency). An
|
|
opt-in alternative shipped in `Opc.Ua.Bindings.Https` (`net8.0`+) is
|
|
described below.
|
|
* The `opc.https` and `opc.wss` listeners both share a single
|
|
`HttpsTransportListener` per `(host, port)` — Kestrel routes the
|
|
inbound HTTP request to the right handler based on the
|
|
`Upgrade` / `Content-Type` headers. Concretely:
|
|
- `POST` with `Content-Type: application/octet-stream` →
|
|
`https-uabinary` handler.
|
|
- `POST` with `Content-Type: application/opcua+uajson` →
|
|
`https-uajson` handler.
|
|
- HTTP Upgrade with `Sec-WebSocket-Protocol: opcua+uacp` →
|
|
full UASC WebSocket session.
|
|
- HTTP Upgrade with `Sec-WebSocket-Protocol: opcua+uajson` →
|
|
request/response JSON WebSocket session.
|
|
|
|
### Security configuration
|
|
|
|
The HTTPS and WSS listeners share the same TLS configuration via
|
|
`HttpsConnectionAdapterOptions`. By default the listener serves
|
|
`SecurityMode.None`; setting
|
|
`ServerConfiguration.HttpsMutualTls = true` requires the client to
|
|
present a TLS certificate which is matched against the
|
|
`ClientCertificate` field of the UASC `OpenSecureChannelRequest`.
|
|
|
|
The JSON sub-profiles (`https-uajson` and the WSS `opcua+uajson`
|
|
sub-protocol) only accept `SecurityMode.None` regardless of the
|
|
configured security policies — see Part 6 §7.4.5 / §7.5.2 for the
|
|
spec rationale.
|
|
|
|
## Client-side usage
|
|
|
|
The client API is the standard `Session` + `EndpointDescription` flow;
|
|
the transport is selected automatically from
|
|
`EndpointDescription.TransportProfileUri`:
|
|
|
|
```csharp
|
|
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
|
var application = new ApplicationInstance(telemetry)
|
|
{
|
|
ApplicationName = "MyClient",
|
|
ApplicationType = ApplicationType.Client
|
|
};
|
|
ApplicationConfiguration configuration = await application
|
|
.Build("urn:localhost:MyClient", "urn:localhost:product")
|
|
.AsClient()
|
|
.AddSecurityConfiguration("CN=MyClient")
|
|
.Create()
|
|
.ConfigureAwait(false);
|
|
|
|
// 1) Direct connect to a known opc.wss endpoint (binary UASC):
|
|
EndpointDescription wssEndpoint = CoreClientUtils.SelectEndpoint(
|
|
application,
|
|
"opc.wss://server.example.com:62543/MyServer",
|
|
useSecurity: true);
|
|
ISession session = await Session.CreateAsync(
|
|
configuration, new ConfiguredEndpoint(null, wssEndpoint), false,
|
|
"WssSession", 60_000, null, null).ConfigureAwait(false);
|
|
|
|
// 2) HTTPS-JSON endpoint (Security Mode None):
|
|
var jsonEndpoint = new EndpointDescription
|
|
{
|
|
EndpointUrl = "opc.https://server.example.com:62540/MyServer",
|
|
SecurityMode = MessageSecurityMode.None,
|
|
SecurityPolicyUri = SecurityPolicies.None,
|
|
TransportProfileUri = Profiles.HttpsJsonTransport
|
|
};
|
|
ISession jsonSession = await Session.CreateAsync(
|
|
configuration, new ConfiguredEndpoint(null, jsonEndpoint), false,
|
|
"JsonSession", 60_000, null, null).ConfigureAwait(false);
|
|
```
|
|
|
|
A few notes:
|
|
|
|
* The `opc.wss://` scheme is the OPC UA alias; the stack normalises it
|
|
to `wss://` (the IETF scheme) and falls back to
|
|
`Utils.UaWebSocketsDefaultPort` (4843) when no explicit port is
|
|
supplied.
|
|
* The WSS client requires the server to select the requested WebSocket
|
|
sub-protocol. If the server returns anything else the connection
|
|
fails with `BadNotConnected`.
|
|
* The WSS-JSON client opens a fresh `ClientWebSocket` per request
|
|
(simplest correct behaviour). A pooled / persistent-WebSocket
|
|
variant can be added without changing the public shape.
|
|
* The HTTPS client (binary or JSON) reuses a single `HttpClient` per
|
|
channel; the encoding is selected from
|
|
`EndpointDescription.TransportProfileUri` at request time.
|
|
|
|
## Discovery
|
|
|
|
`GetEndpoints` and `FindServers` return the `EndpointDescription` list
|
|
declared by the listener for each base address. For TCP, HTTPS-binary,
|
|
and WSS+uacp the description includes the correct `TransportProfileUri`
|
|
(`uatcp-uasc-uabinary`, `https-uabinary`, `wss-uasc-uabinary`
|
|
respectively). The JSON sub-protocols are reachable on the same URL as
|
|
their binary counterparts and clients select them explicitly via the
|
|
`Content-Type` header (HTTPS) or the `Sec-WebSocket-Protocol` header
|
|
(WSS). The HTTPS / WSS service hosts also emit a discovery-only
|
|
**JSON-twin** `EndpointDescription` (`TransportProfileUri =
|
|
Profiles.HttpsJsonTransport` / `Profiles.UaWssJsonTransport`,
|
|
`SecurityMode = None`) for every `SecurityMode = None` binary endpoint,
|
|
so JSON-aware clients see the profile in `GetEndpoints` without needing
|
|
to construct the description themselves. The twin is discovery-only
|
|
(no separate listener) — the existing binary listener handles content
|
|
negotiation.
|
|
|
|
## Opt-in: Kestrel-hosted `opc.tcp`
|
|
|
|
By default the stack ships **two** listener implementations for
|
|
`opc.tcp://`:
|
|
|
|
| Package | Listener | When to use |
|
|
| --- | --- | --- |
|
|
| `Opc.Ua.Core` (default) | `TcpTransportListener` | Raw `Socket` + `SocketAsyncEventArgs`. No ASP.NET Core dependency. The right choice for trimmed / AOT deployments and for environments that already avoid pulling in `Microsoft.AspNetCore.App`. |
|
|
| `Opc.Ua.Bindings.Https` (opt-in, `net8.0`+) | `KestrelTcpTransportListener` | Hosts `opc.tcp://` on Kestrel via `Microsoft.AspNetCore.Connections.ConnectionHandler`. Lets a single `IHost` serve `opc.tcp`, `opc.https`, and `opc.wss` so consumers manage one HTTP-style runtime, share TLS plumbing, and share observability middleware. |
|
|
|
|
The Kestrel-TCP listener uses the same `IUaSCByteTransport` runtime
|
|
boundary as the raw-socket listener, so the UASC channel pipeline is
|
|
unchanged. It supports the full feature set the raw-socket listener
|
|
does, including:
|
|
|
|
* forward (server) and **reverse-connect** listener modes,
|
|
* per-listener TLS certificate hot-update via
|
|
`ITransportListenerCertificateRotation`,
|
|
* discovery (`EndpointDescription` emission via the shared
|
|
`TcpServiceHost` base).
|
|
|
|
Swap it in by registering the factory through the DI container
|
|
**before** opening any `opc.tcp` listener (typically at application
|
|
startup):
|
|
|
|
```csharp
|
|
// Standalone non-DI consumers:
|
|
DefaultTransportBindingRegistry registry =
|
|
DefaultTransportBindingRegistry.WithDefaultTcp();
|
|
registry.RegisterListenerFactory(new KestrelTcpTransportListenerFactory());
|
|
// Pass to ServerBase (via ctor / TransportBindings setter) before StartAsync.
|
|
|
|
// Microsoft.Extensions.DependencyInjection consumers:
|
|
services
|
|
.AddOpcUa()
|
|
.AddOpcTcpTransport() // raw-socket opc.tcp default
|
|
.AddKestrelOpcTcpTransport(); // last-writer-wins: overrides with Kestrel
|
|
```
|
|
|
|
`AddKestrelOpcTcpTransport()` installs an `ITransportBindingConfigurator`
|
|
that runs after `AddOpcTcpTransport()`, so the registry resolves the
|
|
Kestrel listener factory for `opc.tcp://`. The same pattern applies to
|
|
`AddHttpsTransport()` / `AddWssTransport()` (HTTPS / WSS) and any custom
|
|
binding registered via `AddCustomTransport<TListener, TChannel>()`.
|
|
|
|
> **`AddOpcTcpTransport()` is optional.** `AddOpcUa()` already seeds the mandatory raw-socket `opc.tcp` listener and channel factories into the transport binding registry, so clients and servers reach `opc.tcp://` without it. Call it only to be explicit; the `Add*Transport()` extensions above still override the seeded defaults (last-writer-wins per URI scheme) whether or not it was called.
|
|
|
|
The package targets net8.0+ only (the ASP.NET Core `ConnectionContext`
|
|
API surface used to bridge `Socket`-like semantics — `LocalEndPoint`,
|
|
`RemoteEndPoint`, `ConnectionClosed` — is not consistently available
|
|
on older TFMs). Consumers on net472 / netstandard2.x continue to use
|
|
the raw-socket default.
|
|
|
|
## Implementing a custom byte transport
|
|
|
|
The OPC UA Secure Conversation (UASC) binary channel pipeline talks to
|
|
the wire through a narrow byte-level abstraction —
|
|
[`IUaSCByteTransport`](../src/Opc.Ua.Core/Stack/Tcp/IUaSCByteTransport.cs).
|
|
It is the public extension point for plugging in custom transports
|
|
beyond the built-in TCP and WebSocket implementations.
|
|
|
|
### When you need this
|
|
|
|
Use cases that are good fits for a custom byte transport:
|
|
|
|
- **Named pipes / Unix domain sockets** for fast loopback IPC.
|
|
- **QUIC / HTTP/3** to replace the TCP transport's congestion behaviour.
|
|
- **In-process bridges** for unit tests or co-located server/client pairs
|
|
that want to skip the network stack entirely.
|
|
- **Tunnels** (SSH, custom L4) that wrap the UASC chunks in another
|
|
framing.
|
|
|
|
Use cases that should NOT use this:
|
|
|
|
- A new sub-protocol that changes UASC framing — that lives inside the
|
|
channel implementation, not the transport.
|
|
- HTTPS-binary or HTTPS-JSON variants — those are handled in
|
|
`Opc.Ua.Bindings.Https` outside the UASC pipeline.
|
|
|
|
### Contract summary
|
|
|
|
```csharp
|
|
public interface IUaSCByteTransport
|
|
{
|
|
string Implementation { get; } // diagnostic id
|
|
TransportChannelFeatures Features { get; } // optional capabilities
|
|
EndPoint? LocalEndpoint { get; } // may be null
|
|
EndPoint? RemoteEndpoint { get; } // may be null
|
|
|
|
ValueTask ConnectAsync(Uri url, CancellationToken ct);
|
|
ValueTask SendChunkAsync(ReadOnlyMemory<byte> chunk, CancellationToken ct);
|
|
ValueTask SendChunkAsync(BufferCollection buffers, CancellationToken ct);
|
|
ValueTask<ArraySegment<byte>> ReceiveChunkAsync(CancellationToken ct);
|
|
void Close();
|
|
}
|
|
|
|
public interface IUaSCByteTransportFactory
|
|
{
|
|
string Implementation { get; }
|
|
IUaSCByteTransport Create(
|
|
BufferManager bufferManager,
|
|
int receiveBufferSize,
|
|
ITelemetryContext telemetry);
|
|
}
|
|
```
|
|
|
|
Each Send / Receive operates on **exactly one** complete UASC
|
|
`MessageChunk` (Part 6 §6.7.2). The channel pipeline above the transport
|
|
owns chunk-level framing — your job is to move chunk bytes across the
|
|
wire.
|
|
|
|
### Implementation checklist
|
|
|
|
- [ ] Buffer ownership. Buffers returned by `ReceiveChunkAsync` are
|
|
rented from the supplied `BufferManager`; the caller (the UASC
|
|
channel) returns them via `BufferManager.ReturnBuffer` once the
|
|
chunk has been processed. Do **not** pool or reuse the same array
|
|
across receives.
|
|
- [ ] Idempotent `Close`. The channel may dispose a transport from
|
|
multiple paths (normal shutdown, fatal error, channel-state race);
|
|
every implementation MUST treat `Close()` as a no-op when already
|
|
closed.
|
|
- [ ] Cancellation. `ReceiveChunkAsync` should observe the cancellation
|
|
token while awaiting peer data — long-lived sessions rely on this
|
|
to tear down cleanly.
|
|
- [ ] Error mapping. Map transport-layer errors to
|
|
[`ServiceResultException`](../src/Opc.Ua.Core/Types/Result/ServiceResultException.cs)
|
|
with the matching `StatusCodes.BadXxx` (`BadConnectionClosed`,
|
|
`BadTcpMessageTypeInvalid`, `BadTcpMessageTooLarge`, …) so the
|
|
channel can route them through normal UA fault paths.
|
|
- [ ] Vectored Send. The `BufferCollection` overload is called by the
|
|
channel for chunks that span multiple buffers (typical for the
|
|
asymmetric handshake). If your transport does not support
|
|
vectored writes, concatenate the segments into a single buffer
|
|
before sending (this is what `WebSocketByteTransportBase` does).
|
|
- [ ] Client vs server. Client transports implement `ConnectAsync` to
|
|
dial outbound; server transports built from an
|
|
already-accepted connection should throw `NotSupportedException`
|
|
from `ConnectAsync` (this is the contract enforced by
|
|
`UaSCUaBinaryClientChannel`).
|
|
|
|
### Wiring the transport into the channel pipeline
|
|
|
|
#### Client side
|
|
|
|
Implement [`IUaSCByteTransportFactory`](../src/Opc.Ua.Core/Stack/Tcp/IUaSCByteTransport.cs)
|
|
and hand the factory to a subclass of
|
|
`UaSCUaBinaryTransportChannel`:
|
|
|
|
```csharp
|
|
internal sealed class MyTransportFactory : IUaSCByteTransportFactory
|
|
{
|
|
public string Implementation => "UA-MY";
|
|
public IUaSCByteTransport Create(BufferManager bm, int rxSize, ITelemetryContext tel)
|
|
=> new MyByteTransport(bm, rxSize, tel);
|
|
}
|
|
|
|
public sealed class MyTransportChannel : UaSCUaBinaryTransportChannel
|
|
{
|
|
public MyTransportChannel(ITelemetryContext telemetry)
|
|
: base(new MyTransportFactory(), telemetry)
|
|
{
|
|
}
|
|
}
|
|
|
|
public sealed class MyTransportChannelFactory : ITransportChannelFactory
|
|
{
|
|
public string UriScheme => "opc.my";
|
|
public ITransportChannel Create(ITelemetryContext telemetry)
|
|
=> new MyTransportChannel(telemetry);
|
|
}
|
|
```
|
|
|
|
If your transport needs channel-level state (TLS validator, client cert,
|
|
custom credentials) plumbed in, override
|
|
`UaSCUaBinaryTransportChannel.OnSettingsSaved(TransportChannelSettings,
|
|
ChannelQuotas)` — that hook fires after the channel binds settings but
|
|
before it tries to connect. The WSS implementation uses this pattern;
|
|
see `WssTransportChannel.OnSettingsSaved` for a reference.
|
|
|
|
#### Server side
|
|
|
|
Implement [`ITransportListener`](../src/Opc.Ua.Core/Stack/Transport/ITransportListener.cs)
|
|
plus an `ITransportListenerFactory`. For each accepted connection,
|
|
construct your transport directly (not through the
|
|
`IUaSCByteTransportFactory`, which is client-side only), then hand it to
|
|
a `TcpServerChannel` via `Attach(channelId, transport)`. The WSS server
|
|
path in `HttpsTransportListener.AcceptWebSocketAsync` is the canonical
|
|
template.
|
|
|
|
#### Registering by URL scheme
|
|
|
|
Install your custom listener and channel factories into the host's
|
|
`ITransportBindingRegistry`. The simplest path uses the DI extension:
|
|
|
|
```csharp
|
|
services
|
|
.AddOpcUa()
|
|
.AddCustomTransport<MyCustomListenerFactory, MyCustomChannelFactory>();
|
|
```
|
|
|
|
Both factories are resolved from the `IServiceProvider` (so they may
|
|
have constructor-injected dependencies) and installed under the URI
|
|
scheme reported by `MyCustomListenerFactory.UriScheme`. Non-DI consumers
|
|
construct a `DefaultTransportBindingRegistry` and call
|
|
`RegisterListenerFactory` / `RegisterChannelFactory` directly, then hand
|
|
the registry to `ServerBase` (via `TransportBindings`) or to
|
|
`ReverseConnectManager.TransportBindings`.
|
|
|
|
### Worked example
|
|
|
|
[`InProcessTransport`](../src/Opc.Ua.Core/Stack/Tcp/InProcessTransport.cs)
|
|
is the public reference implementation that ships in `Opc.Ua.Core`. It
|
|
uses only public API: two paired transports communicate over a pair of
|
|
in-memory `System.Threading.Channels.Channel<byte[]>` (one per
|
|
direction). Use it directly for unit tests or co-located client/server
|
|
pairs that want to skip the network stack entirely, or as a
|
|
copy-paste starting point for a custom transport.
|
|
|
|
```csharp
|
|
using Opc.Ua.Bindings;
|
|
|
|
var buffers = new BufferManager("inproc", 8192, telemetry);
|
|
(InProcessTransport client, InProcessTransport server) =
|
|
InProcessTransport.CreatePair(buffers, receiveBufferSize: 8192, telemetry);
|
|
|
|
await client.SendChunkAsync(payload, ct).ConfigureAwait(false);
|
|
ArraySegment<byte> received = await server.ReceiveChunkAsync(ct).ConfigureAwait(false);
|
|
```
|
|
|
|
Note that `InProcessTransport.ConnectAsync` throws
|
|
`NotSupportedException` — the pair is created up front via
|
|
`CreatePair`. A real network transport would implement `ConnectAsync`
|
|
to dial the wire.
|
|
|
|
## See also
|
|
|
|
* [`Profiles.md`](Profiles.md) — supported profiles, security policies,
|
|
message encodings.
|
|
* [`MigrationGuide.md`](MigrationGuide.md) — note on the breaking
|
|
removal of `IMessageSocket` and the new `IUaSCByteTransport`
|
|
contract.
|
|
* OPC UA Part 6 §7.4 (HTTPS) / §7.5 (WebSockets) for the wire-format
|
|
specification.
|