Skip to content

Add Reverse Connect support#1716

Open
kevinherron wants to merge 89 commits into
mainfrom
feature/reverse-connect
Open

Add Reverse Connect support#1716
kevinherron wants to merge 89 commits into
mainfrom
feature/reverse-connect

Conversation

@kevinherron
Copy link
Copy Markdown
Contributor

@kevinherron kevinherron commented Mar 25, 2026

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 client
drives the normal Hello/Acknowledge/OpenSecureChannel flow from there.

Two client topologies are supported:

  • One-to-one — a transport binds its own listening socket and serves a single
    reverse-connected server.
    Surfaced through OpcUaClient.createReverseConnect().
  • Multiplexed — one shared MultiplexedReverseConnectListener accepts inbound
    connections for many servers and dispatches to per-server transports by ServerUri.
    An optional EndpointResolver enables 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"| Attempt
Loading

Headline points:

  • Serialized lifecycle owners over async Netty I/O. Each logical reverse-connected
    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.
  • Listener decoupled from client lifecycle. Both the one-to-one listener and the
    shared multiplexed listener decode just far enough to obtain ReverseHello, then hand
    the accepted channel off through a ChannelConsumerRegistry seam that keeps the
    transport module independent from opc-ua-sdk.
  • Context-before-handshake. Accepted channels wait for the real
    ClientApplicationContext (installed by connect()) before the UASC handshake
    starts, 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.
  • Server idle policy lives with the target. ReverseConnectTargetOwner owns retry
    timers, the idle/active attempt sets, and replacement spawn on promotion.
    ReverseConnectManager is a registration and lifecycle facade only.
  • Reverse discovery is unsecured. OpcUaClient.createReverseConnect(),
    DiscoveryClient reverse overloads, and the multiplexed 2-shot path all use
    SecurityPolicy.None. Secured reverse discovery is a separate feature.

Implementation

Protocol — stack-core

  • ReverseHelloMessage record with encode/decode (4096-byte field limits).
  • MessageType.ReverseHello (RHE) and TcpMessageDecoder/Encoder wiring.

Transport — opc-ua-stack/transport

Client:

  • OpcTcpReverseConnectTransport — one-to-one transport; owns a private
    OneToOneReverseConnectListener and one ReverseConnectChannelOwner.
  • OpcTcpMultiplexedReverseConnectTransport — per-server adapter that registers with a
    shared listener through ChannelConsumerRegistry.
  • ReverseConnectChannelOwner — serialized client-side lifecycle owner
    (Idle/Armed/Handshaking/Connected/Disconnecting/Stopped). Used by both
    client transports.
  • UascClientReverseConnectHandshake — installs UASC handlers on the Netty event loop
    and feeds the pre-decoded RHE into UascClientReverseHelloHandler.
  • ChannelStateObservable and CurrentChannelProvider — common transport seams used by
    SessionFsm for reconnect and keep-alive channel close, implemented by both forward
    and reverse client transports.

Server:

  • OpcTcpReverseConnectServerTransport — outbound connector and pipeline installer;
    exposes a public ReverseConnectTarget handle.
  • ReverseConnectTargetOwner — serialized per-target owner; owns retry timers, the
    idle/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).
  • UascServerReverseHelloHandler and a SecureChannelOpenedEvent observation point
    installed before the handler so fast-open and early-error events are not missed.

SDK — opc-ua-sdk

Client (sdk-client):

  • OpcUaClient.createReverseConnect() — two-pass factory (bind listener, accept,
    unsecured GetEndpoints, disconnect just the discovery child channel, return
    unconnected client; caller drives connectAsync() for the real session).
  • MultiplexedReverseConnectListener — shared listener and ServerUri dispatch table;
    implements ChannelConsumerRegistry.
  • MultiplexedReverseConnectClientController — unknown-server resolver flow, optional
    discovery, on-demand OpcUaClient creation, ClientListener notification; bounded by
    maxPendingConnections and resolverTimeout.
  • EndpointResolver, ClientCustomizer, ClientListener, ReverseConnectRejection
    public extension points for the on-demand path.
  • DiscoveryClient.getEndpoints(...) and findServers(...) 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(...), returning ReverseConnectHandle.

Existing integration points modified

  • OpcUaClient adds the one-to-one reverse-connect factory and reuses the same
    transport between discovery and the real session.
  • DiscoveryClient adds reverse-connect GetEndpoints and FindServers helpers.
  • OpcUaServer wires ReverseConnectManager into startup/shutdown and exposes
    add/remove registration APIs.
  • SessionFsm / SessionFsmFactory now observe generic transport channel state, so
    forward and reverse transports both trigger normal reconnect/reactivation.
  • OpcTcpClientTransport now implements ChannelStateObservable and
    CurrentChannelProvider, matching the new reverse transports.

Examples

  • milo-examples/client-examples/.../ReverseConnectExample.java — self-contained
    one-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-connect GetEndpoints and FindServers.
  • MultiplexedReverseConnectListenerTest — pre-registered multiplexed clients, multiple
    servers, reconnection, and on-demand 1-shot / 2-shot client creation.

Focused unit coverage was added for ReverseHello encoding/decoding, listeners, channel
owners, UASC reverse handlers, endpoint resolution, server attempt outcomes, target
retry/stop behavior, manager lifecycle, and Session FSM channel-loss handling.

References

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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 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, OpcUaServer APIs, OpcUaClient.createReverseConnect() and DiscoveryClient overloads) 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.

Comment thread docs/architecture/reverse-connect.md Outdated
@kevinherron

This comment was marked as resolved.

kevinherron and others added 7 commits March 24, 2026 20:12
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.
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

@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.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

@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.

kevinherron and others added 7 commits March 25, 2026 06:13
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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.
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.

Add Reverse Connect support

3 participants