Add Reverse Connect support#1716
Conversation
Implement OPC UA Reverse Connect at the stack and transport layers. Add ReverseHelloMessage encoding/decoding, client-side reverse connect transport with channel FSM, and server-side reverse connect transport that initiates connections to clients behind firewalls.
Extend OpcUaClient and DiscoveryClient with reverse connect factory methods that accept a pre-established transport. Add ReverseConnectManager and ReverseConnectHandle to OpcUaServer for managing outbound connections to clients. Update SessionFsm to support reverse connect lifecycle.
Add unit tests for ReverseHelloMessage encoding/decoding and ReverseConnectChannelFsm state transitions. Add integration tests for reverse connect client sessions and discovery. Include a Prosys reverse connect client example.
There was a problem hiding this comment.
Pull request overview
This PR adds OPC UA Reverse Connect support end-to-end (stack-core protocol message + transport implementations/FSMs + SDK server/client APIs), including documentation, examples, and a comprehensive set of unit/integration tests to validate the handshake, reconnection behavior, and discovery flows.
Changes:
- Introduces
ReverseHelloMessage(RHE) support in stack-core encoding/decoding and message type mapping. - Adds reverse-connect transports + FSMs for client (listening) and server (outbound connector/backoff + idle-socket invariant signaling).
- Adds SDK integration (
ReverseConnectManager,OpcUaServerAPIs,OpcUaClient.createReverseConnect()andDiscoveryClientoverloads) plus docs/examples/tests.
Reviewed changes
Copilot reviewed 37 out of 38 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| opc-ua-stack/transport/src/test/java/org/eclipse/milo/opcua/stack/transport/server/uasc/UascServerReverseHelloHandlerTest.java | Unit tests for server-side RHE handler behavior (RHE send, Hello/Ack, deadline). |
| opc-ua-stack/transport/src/test/java/org/eclipse/milo/opcua/stack/transport/server/tcp/ReverseConnectConnectionFsmTest.java | Unit tests for server reverse-connect connection FSM (backoff, stop, transitions). |
| opc-ua-stack/transport/src/test/java/org/eclipse/milo/opcua/stack/transport/server/tcp/OpcTcpReverseConnectServerTransportTest.java | Unit tests for outbound reverse-connect server transport connect success/failure. |
| opc-ua-stack/transport/src/test/java/org/eclipse/milo/opcua/stack/transport/client/uasc/UascClientReverseHelloHandlerTest.java | Unit tests for client-side RHE handler (whitelist, timeouts, error handling). |
| opc-ua-stack/transport/src/test/java/org/eclipse/milo/opcua/stack/transport/client/tcp/ReverseConnectChannelFsmTest.java | Unit tests for client reverse-connect channel FSM (connect/get/disconnect/reconnect). |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/uasc/UascServerReverseHelloHandler.java | Server Netty handler that sends ReverseHello on channel activation. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/uasc/UascServerHelloHandler.java | Adjusts visibility to enable clean subclassing for reverse-connect handshake. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/uasc/UascServerAsymmetricHandler.java | Fires a user event after OpenSecureChannelResponse to signal SecureChannel opened. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/uasc/SecureChannelOpenedEvent.java | Defines the Netty user event used by reverse-connect server FSM. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/tcp/ReverseConnectConnectionFsm.java | Server reverse-connect connection lifecycle FSM (connect/backoff/active/stop). |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/tcp/ReverseConnectConfigBuilder.java | Builder for server reverse-connect timing/backoff configuration. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/tcp/ReverseConnectConfig.java | Interface for server reverse-connect config values. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/server/tcp/OpcTcpReverseConnectServerTransport.java | Outbound Netty connector wiring server pipeline for reverse-connect sockets. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/uasc/UascClientReverseHelloHandler.java | Client reverse-connect handshake handler (RHE → Hello → Ack → UASC handler install). |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/tcp/ReverseConnectChannelFsm.java | Client reverse-connect channel FSM (accepted connection → handshake → connected/reconnect). |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/tcp/OpcTcpReverseConnectTransportConfigBuilder.java | Builder for client reverse-connect transport config (listen addr, whitelist, timeouts). |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/tcp/OpcTcpReverseConnectTransportConfig.java | Client reverse-connect transport config interface. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/tcp/OpcTcpReverseConnectTransport.java | Client transport that listens and accepts inbound server reverse-connect sockets. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/tcp/OpcTcpClientTransport.java | Adds ChannelStateObservable support for forward-connect transport. |
| opc-ua-stack/transport/src/main/java/org/eclipse/milo/opcua/stack/transport/client/ChannelStateObservable.java | Transport-agnostic connection state observer interface for session reconnection. |
| opc-ua-stack/stack-core/src/test/java/org/eclipse/milo/opcua/stack/core/channel/messages/ReverseHelloMessageTest.java | Unit tests for ReverseHello message encode/decode and limits. |
| opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/channel/messages/TcpMessageEncoder.java | Adds encoding support for ReverseHello. |
| opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/channel/messages/TcpMessageDecoder.java | Adds decoding support for ReverseHello. |
| opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/channel/messages/ReverseHelloMessage.java | New protocol message type for reverse-connect initiation. |
| opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/channel/messages/MessageType.java | Adds ReverseHello message type mapping (“RHE”). |
| opc-ua-sdk/sdk-server/src/main/java/org/eclipse/milo/opcua/sdk/server/ReverseConnectManager.java | SDK manager enforcing idle socket invariant and dynamic registration lifecycle. |
| opc-ua-sdk/sdk-server/src/main/java/org/eclipse/milo/opcua/sdk/server/ReverseConnectHandle.java | Handle type for reverse-connect registrations. |
| opc-ua-sdk/sdk-server/src/main/java/org/eclipse/milo/opcua/sdk/server/OpcUaServer.java | Server integration: configure/start/stop reverse-connect manager + add/remove APIs. |
| opc-ua-sdk/sdk-client/src/main/java/org/eclipse/milo/opcua/sdk/client/session/SessionFsmFactory.java | Uses ChannelStateObservable for transport-agnostic connection loss detection. |
| opc-ua-sdk/sdk-client/src/main/java/org/eclipse/milo/opcua/sdk/client/session/SessionFsm.java | Stores transport listener with new ChannelStateObservable key. |
| opc-ua-sdk/sdk-client/src/main/java/org/eclipse/milo/opcua/sdk/client/OpcUaClient.java | Adds createReverseConnect() factory (two-pass discovery, keep listener open). |
| opc-ua-sdk/sdk-client/src/main/java/org/eclipse/milo/opcua/sdk/client/DiscoveryClient.java | Adds reverse-connect overloads for getEndpoints() and findServers(). |
| opc-ua-sdk/integration-tests/src/test/java/org/eclipse/milo/opcua/stack/transport/client/tcp/OpcTcpReverseConnectTransportTest.java | Transport integration tests for reverse-connect client transport against real server. |
| opc-ua-sdk/integration-tests/src/test/java/org/eclipse/milo/opcua/sdk/client/ReverseConnectTest.java | End-to-end SDK integration tests for reverse-connect sessions and reconnection. |
| opc-ua-sdk/integration-tests/src/test/java/org/eclipse/milo/opcua/sdk/client/ReverseConnectDiscoveryTest.java | Integration tests for reverse-connect discovery helpers on a fixed listen port. |
| milo-examples/client-examples/src/main/java/org/eclipse/milo/examples/client/ReverseConnectExampleProsys.java | Example showing reverse-connect client usage with Prosys Simulation Server. |
| docs/architecture/reverse-connect.md | Architecture/design document for reverse-connect layering, FSMs, and config. |
| .gitignore | Ignores .mcp.json. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment was marked as resolved.
This comment was marked as resolved.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tack/core/channel/messages/ReverseHelloMessage.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tack/core/channel/messages/ReverseHelloMessage.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Make ReverseConnectManager thread-safe with a ReentrantLock that protects the connections and handlesByClientUrl maps, firing FSM events outside the lock to prevent lock-ordering deadlocks. Change OpcUaServer.shutdown() to stop the reverse connect manager non-blockingly so shutdown can be called from I/O or FSM executor threads without deadlocking. In ReverseConnectChannelFsm, introduce a ConnectFuture wrapper to eliminate raw-type warnings, execute pipeline operations on the channel's event loop, and pass the pre-decoded ReverseHelloMessage directly to the handler instead of re-encoding and replaying bytes. In OpcUaClient, use the FSM Disconnect event instead of closing the raw channel, and add an orTimeout to disconnectAsync's closeSession.
Add shutdownCompletesWithoutDeadlock integration test that verifies OpcUaServer.shutdown() completes when called from a single-threaded executor, catching the deadlock fixed in the previous commit. Add ReverseConnectChannelFsm tests for Disconnect event transitions. Add ReverseConnectManagerTest with unit tests for lifecycle, handle management, idle connection spawning, and concurrent stop safety.
Add a section to testing.md explaining -pl, -am, and -amd flags with examples and a rule of thumb for when to use each.
|
@kevinherron I've opened a new pull request, #1717, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@kevinherron I've opened a new pull request, #1718, to work on those changes. Once the pull request is ready, I'll request review from you. |
Corrects 6 discrepancies found by cross-referencing the doc against the current implementation: RHE dispatch mechanism (direct call, not pipeline re-encode), shutdown order, connect() signature, KEY_CF type, missing test entries, and missing test classes.
…reChannelResponse (#1717) * Initial plan * Fire SecureChannelOpenedEvent from writeAndFlush success listener Co-authored-by: kevinherron <340273+kevinherron@users.noreply.github.com> Agent-Logs-Url: https://github.com/eclipse-milo/milo/sessions/4b9455b2-5b0d-443e-a0b7-4cb32f655663 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kevinherron <340273+kevinherron@users.noreply.github.com>
* Initial plan * Replace assert with runtime UaException checks in TcpMessageDecoder Co-authored-by: kevinherron <340273+kevinherron@users.noreply.github.com> Agent-Logs-Url: https://github.com/eclipse-milo/milo/sessions/6f91137a-a9a1-424e-a62a-ebea7b8ddf6d --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kevinherron <340273+kevinherron@users.noreply.github.com>
…nnect transport - Fail pending ConnectFuture on Disconnect in NotConnected state in ReverseConnectChannelFsm, preventing orphaned futures when disconnectAsync() is called before a server connects. - Reset serverBootstrap/serverChannel on bind failure in OpcTcpReverseConnectTransport.startListening() so subsequent connect() calls can retry instead of being permanently stuck. - Return a failed CompletableFuture from connect() on bind failure instead of throwing synchronously, ensuring downstream future chains (disconnect cleanup, timeouts) are properly constructed.
- Add configurable connectTimeout (default 60s) to OpcTcpReverseConnectTransportConfig for bounding one-shot discovery flows without affecting long-lived connections. - Apply .orTimeout() to connectAsync() in DiscoveryClient.getEndpoints, DiscoveryClient.findServers, and OpcUaClient.createReverseConnect so callers don't hang indefinitely waiting for a server to connect. - Chain disconnectAsync() to future completion in DiscoveryClient using handle/thenCompose instead of fire-and-forget whenComplete, ensuring the listening socket is fully closed before the result future completes.
- Move the !running check inside synchronized(pendingRegistrations) in addReverseConnect to close a TOCTOU race with start() that could orphan registrations. - Clean pendingRegistrations in removeReverseConnect so handles removed before start() don't get materialized into FSMs. - Rebuild stale FSMs on restart: snapshot handles from the prior start/stop cycle and replace their terminal-state FSMs with fresh ones, restoring connectivity without requiring re-registration.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 42 out of 43 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Use the shared owner lifecycle for multiplexed reverse-connect channels so listener stop, duplicate connect, and early offered channels complete through the same bounded cleanup rules as the one-to-one path. Expose the multiplexed connect timeout needed by the owner and keep session keep-alive channel close on the transport-facing active-channel seam.
Keep unknown-server resolution, discovery cleanup, and on-demand client callbacks outside the listener so dispatch remains a transport boundary and callbacks run on the configured executor.
Use per-test listen ports and deterministic session-state waits so reverse-connect verification does not depend on fixed ports or busy waiting.
Introduce a transport-level attempt bridge so server reverse-connect policy can distinguish connect failure, ReverseHello write failure, client rejection, secure-channel-open, and channel-close outcomes. Keep the existing FSM and manager path compatible through the protected attempt seam until target ownership takes over retry policy.
Move per-target retry, rejection backoff, idle attempt replacement, and stop quiescence behind a stack transport owner before wiring the SDK manager to it.
SessionFsm needs to force reconnects without concrete transport checks. Route current-channel access through a shared transport interface and keep channel-state reactions on the configured executor. Add regression coverage for stalled session close, activation, and reactivation disconnect paths.
Add regressions for startup rollback and client reverse-hello timing, and clarify the remaining P3 API contracts captured by the review matrix.
Describe the settled serialized lifecycle owner model in the architecture reference, including discovery/pass-2 behavior and server target ownership. Align reverse-connect examples with final factory/listener APIs and cleanup ordering.
The owner-backed reverse connect lifecycle is the only active architecture, so drop the unused pre-owner FSM source and tests and keep the architecture doc phrased as current design rather than migration history.
Treat public connect timeouts as failed lifecycles so stale reverse dial-ins cannot establish a channel after callers see Bad_Timeout. Coalesce overlapping disconnect and listener-stop cleanup around the same close operation, and make active channel publication visible to external readers.
Server-side reverse connect only establishes OPC TCP channels, so reject invalid client listener URLs at registration time and avoid advertising non-TCP endpoints in ReverseHello.
On-demand multiplexed clients can see mixed discovery profiles, reconnect before application code calls connectAsync(), and enable session endpoint validation. Keep discovery scoped to OPC TCP, preserve the discovery endpoint list, and hand pass-2 reconnects to the pending transport so these flows match the reverse connect lifecycle.
Completing stop futures before restart state was consumed allowed callbacks to re-enter start and orphan target owners. Keep stop completion outside the manager lock, serialize stopping-attempt close completion through the owner mailbox, and reject invalid timing config before startup can hang. This also includes Spotless formatting for reverse connect listener tests required by the current formatter.
Reject offered channels whose ReverseHello ServerUri does not match the transport registration, and keep on-demand handoffs owned by the created client until it connects or disconnects. This prevents stale or delayed reconnects from crossing server boundaries or creating duplicate clients.
Reject pass-2 ReverseHello messages that do not match the selected endpoint, and filter disallowed pre-context accepts before they can occupy the pending slot. Also surface reverse discovery disconnect failures so one-shot discovery callers see cleanup problems.
Keep listener startup retryable after customizer failures, reserve consumers before async channel handoff, and send UA TCP Error responses for post-ReverseHello rejections so servers can apply reject backoff.
Use an absolute ReverseHello deadline so partial inbound bytes cannot keep accepted sockets open indefinitely. Reject disallowed ServerUri values before starting the reverse handshake so stray servers do not fail an active connect waiter, and align the connect timeout documentation with transport behavior.
Treat a secure-channel close from the current idle reverse-connect attempt as an idle attempt end when the secure-open event is still buffered behind TCP connect completion. This keeps the target from retaining a dead idle attempt and ensures reconnect scheduling resumes.
Ensure one-shot discovery waits for temporary transport cleanup before completing, preserving primary discovery failures while still surfacing cleanup failures when appropriate. Align reverse-connect discovery timeout handling so the configured connect timeout bounds both the inbound wait and discovery service request.
Changing the datatype tree factory should invalidate dynamic datatype state too, so dynamic managers and encoding contexts do not keep using a manager built from the previous tree.
Document the ReverseConnectAttempt public contract and align remaining comments with the owner-based reverse connect flow.
Reverse-connect listeners now drop disallowed server URIs before the UASC handshake, so connection attempts fail by timing out rather than surfacing Bad_TcpEndpointUrlInvalid. The tests also need endpoint metadata to carry the server ApplicationUri so ReverseHello validation matches the selected endpoint.
Update the lifecycle state diagram and prose to match how Disconnecting and Stopped actually behave (connect-timeout cleanup, listener-stop during disconnect, stop-cause propagation), and document the multiplexed pending-handoff behavior for both 1-shot cached and 2-shot live discovery paths. Drop the unused activeSecureChannel field and removeTransitionListener method on the owner, and add Javadoc on addTransitionListener clarifying that owner-internal listeners share the owner lifetime.
Drop the dead reverseHelloTimeoutMs parameter that flowed from ReverseConnectChannelOwner through HandshakeStarter into UascClientReverseConnectHandshake — the multiplexed handshake always forced it to 0 because the listener has already decoded ReverseHello. Inline the always-constant message arg in the private shutdown() helpers, remove misplaced @nullable annotations on local variables, guard array element access in OpcUaClient namespace/server-table reads, make awaitOutbound use return fail(...), inline the trivial beginTerminal indirection, drop the unused serverUri field on PendingTransportHandoff, switch !isPresent() to isEmpty() in NodeFactory, replace printStackTrace with logger.error in the example runner, drop the unused binaryEncodingId local in the UA example, and remove the redundant jspecify dependency from sdk-client/pom.xml (inherited from the parent).
Reverse Connect server dials use an outbound Bootstrap, so they were bypassing the normal server transport customization path. Mirror the configured pipeline hook and bootstrap channel options/attributes so reverse-connected channels get the same Netty settings as regular server listener channels.
ServerBootstrap parent options and attrs configure the listening socket, which doesn't exist for outbound reverse connect dials. Only the child options and attrs—intended for accepted connections—should propagate to the client Bootstrap.
Closes #1715
Summary
Adds OPC UA Reverse Connect (Part 6 §7.1.3) end-to-end across protocol, transport, and
SDK layers. The server dials a listening client, sends
ReverseHello, and the clientdrives the normal Hello/Acknowledge/OpenSecureChannel flow from there.
Two client topologies are supported:
reverse-connected server.
Surfaced through
OpcUaClient.createReverseConnect().MultiplexedReverseConnectListeneraccepts inboundconnections for many servers and dispatches to per-server transports by
ServerUri.An optional
EndpointResolverenables on-demand client creation for unknown servers(1-shot cached or 2-shot live discovery).
The server side maintains the Part 6 idle-socket invariant: at least one open socket
without an active Session per registered client URL, with a replacement idle attempt
started as soon as one is promoted to active.
Architecture
Authoritative design reference:
docs/architecture/reverse-connect.md.
It covers the protocol flow, the layering between
stack-core/transport/opc-ua-sdk, the client and server state models, discovery semantics, configuration,and the rationale for the major design decisions.
High-level data flow:
flowchart LR subgraph "Server SDK" Server["OpcUaServer"] Manager["ReverseConnectManager"] end subgraph "Server Transport" Target["ReverseConnectTargetOwner"] Attempt["ReverseConnectAttempt"] ServerRHE["UascServerReverseHelloHandler"] end subgraph "Client Listener" One["One-to-one listener"] Mux["MultiplexedReverseConnectListener"] Controller["On-demand client controller"] end subgraph "Client Transport" Owner["ReverseConnectChannelOwner"] ClientRHE["UascClientReverseHelloHandler"] Session["SessionFsm"] end Server --> Manager --> Target --> Attempt --> ServerRHE ServerRHE -->|"TCP connect + ReverseHello"| One ServerRHE -->|"TCP connect + ReverseHello"| Mux One --> Owner Mux -->|"known ServerUri"| Owner Mux -->|"unknown ServerUri"| Controller --> Owner Owner --> ClientRHE ClientRHE -->|"Hello / Ack / OpenSecureChannel"| ServerRHE Owner --> Session Session -->|"CreateSession / ActivateSession / services"| Server Target -->|"replacement idle connection after SecureChannel opens"| AttemptHeadline points:
channel and each configured server target is driven by a single owner that serializes
state transitions, timers, cleanup, and future completion through a mailbox.
Listeners and handshake helpers do not own lifecycle policy.
shared multiplexed listener decode just far enough to obtain
ReverseHello, then handthe accepted channel off through a
ChannelConsumerRegistryseam that keeps thetransport module independent from
opc-ua-sdk.ClientApplicationContext(installed byconnect()) before the UASC handshakestarts, so the unsecured discovery context cannot leak into the real session.
An accepted channel that arrives before context is buffered at most once and bounded
by
connectTimeout.ReverseConnectTargetOwnerowns retrytimers, the idle/active attempt sets, and replacement spawn on promotion.
ReverseConnectManageris a registration and lifecycle facade only.OpcUaClient.createReverseConnect(),DiscoveryClientreverse overloads, and the multiplexed 2-shot path all useSecurityPolicy.None. Secured reverse discovery is a separate feature.Implementation
Protocol —
stack-coreReverseHelloMessagerecord with encode/decode (4096-byte field limits).MessageType.ReverseHello(RHE) andTcpMessageDecoder/Encoderwiring.Transport —
opc-ua-stack/transportClient:
OpcTcpReverseConnectTransport— one-to-one transport; owns a privateOneToOneReverseConnectListenerand oneReverseConnectChannelOwner.OpcTcpMultiplexedReverseConnectTransport— per-server adapter that registers with ashared listener through
ChannelConsumerRegistry.ReverseConnectChannelOwner— serialized client-side lifecycle owner(
Idle/Armed/Handshaking/Connected/Disconnecting/Stopped). Used by bothclient transports.
UascClientReverseConnectHandshake— installs UASC handlers on the Netty event loopand feeds the pre-decoded RHE into
UascClientReverseHelloHandler.ChannelStateObservableandCurrentChannelProvider— common transport seams used bySessionFsmfor reconnect and keep-alive channel close, implemented by both forwardand reverse client transports.
Server:
OpcTcpReverseConnectServerTransport— outbound connector and pipeline installer;exposes a public
ReverseConnectTargethandle.ReverseConnectTargetOwner— serialized per-target owner; owns retry timers, theidle/active attempt sets, generation/attempt ids for stale-callback rejection, and
stop/remove cleanup.
ReverseConnectAttempt— outcome bridge for one outbound attempt(
TcpConnectFailure,ReverseHelloWriteFailure,ClientRejected,SecureChannelOpened,CloseBeforeSecureChannel,CloseAfterSecureChannel).UascServerReverseHelloHandlerand aSecureChannelOpenedEventobservation pointinstalled before the handler so fast-open and early-error events are not missed.
SDK —
opc-ua-sdkClient (
sdk-client):OpcUaClient.createReverseConnect()— two-pass factory (bind listener, accept,unsecured
GetEndpoints, disconnect just the discovery child channel, returnunconnected client; caller drives
connectAsync()for the real session).MultiplexedReverseConnectListener— shared listener andServerUridispatch table;implements
ChannelConsumerRegistry.MultiplexedReverseConnectClientController— unknown-server resolver flow, optionaldiscovery, on-demand
OpcUaClientcreation,ClientListenernotification; bounded bymaxPendingConnectionsandresolverTimeout.EndpointResolver,ClientCustomizer,ClientListener,ReverseConnectRejection—public extension points for the on-demand path.
DiscoveryClient.getEndpoints(...)andfindServers(...)reverse overloads.Server (
sdk-server):ReverseConnectManager— durable registration store, materialization on startup,dynamic add/remove while running, and serialized stop coordination with
OpcUaServer.shutdown().OpcUaServer.setReverseConnectManager(...),addReverseConnect(...),removeReverseConnect(...), returningReverseConnectHandle.Existing integration points modified
OpcUaClientadds the one-to-one reverse-connect factory and reuses the sametransport between discovery and the real session.
DiscoveryClientadds reverse-connectGetEndpointsandFindServershelpers.OpcUaServerwiresReverseConnectManagerinto startup/shutdown and exposesadd/remove registration APIs.
SessionFsm/SessionFsmFactorynow observe generic transport channel state, soforward and reverse transports both trigger normal reconnect/reactivation.
OpcTcpClientTransportnow implementsChannelStateObservableandCurrentChannelProvider, matching the new reverse transports.Examples
milo-examples/client-examples/.../ReverseConnectExample.java— self-containedone-to-one example (server + client in the same process).
milo-examples/client-examples/.../ReverseConnectMultiplexedExample.java—self-contained multiplexed example with on-demand client creation.
milo-examples/client-examples/.../prosys/ProsysReverseConnect{,Multiplexed}Example.java— third-party interop examples against the Prosys simulation server.
Testing
Primary integration coverage:
ReverseConnectTest— one-to-one sessions, reconnection, multiple clients, rejection,and pass-2 buffering.
ReverseConnectDiscoveryTest— reverse-connectGetEndpointsandFindServers.MultiplexedReverseConnectListenerTest— pre-registered multiplexed clients, multipleservers, reconnection, and on-demand 1-shot / 2-shot client creation.
Focused unit coverage was added for
ReverseHelloencoding/decoding, listeners, channelowners, UASC reverse handlers, endpoint resolution, server attempt outcomes, target
retry/stop behavior, manager lifecycle, and Session FSM channel-loss handling.
References