Opcua client ext reverse connect
Skill php-opcua/ai-skills/skills/opcua-client-ext-reverse-connect
Accept OPC UA Reverse Connect (ReverseHello / RHE) handshakes in PHP using php-opcua/opcua-client-ext-reverse-connect v4.4.0 — the client-side half of OPC UA Part 6 §7.1.2.3. The server dials the client; this package listens, decodes and whitelists the RHE frame, then hands a fully connected Client back through the standard ClientBuilder. Use this skill whenever a task involves Reverse Connect, ReverseHello, RHE frames, server-initiated OPC UA connections, NAT/firewall traversal for opc.tcp://, edge gateways calling home, or extending php-opcua/opcua-client with a reverse listener.From its SKILL.md
npx -y skills add php-opcua/ai-skills --skill opcua-client-ext-reverse-connectAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.8 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
php-opcua/opcua-client-ext-reverse-connect — v4.4.0 skill
An optional extension of php-opcua/opcua-client implementing the client side of OPC UA Reverse Connect (Part 6 §7.1.2.3). In Reverse Connect the server initiates the TCP connection to the client and announces itself with a ReverseHello (RHE) frame; from there the normal UA-TCP handshake proceeds on the same socket. This package binds the listener, decodes and validates the RHE, and bridges the live socket into the standard ClientBuilder flow.
Everything lives under the PhpOpcua\Client\ExtReverseConnect\* namespace. Applications that do not need Reverse Connect take no extra dependency.
When to use this skill
Activate when any of these apply:
- The task mentions Reverse Connect, ReverseHello,
RHE, server-initiated OPC UA connections, or "the server connects to the client" - An OPC UA edge gateway / PLC behind NAT or a firewall needs to "call home" to a central client over
opc.tcp:// - A
ReverseConnectListener,ReverseHelloValidator,ReverseConnectClientFactory, orReverseConnectSessionappears in code - The task references OPC UA Part 6 §7.1.2.3, or the
StartReverseConnect/StopReverseConnecttest-suite methods
Do NOT activate for: ordinary client-initiated OPC UA connections (use the core opcua-client skill), generic PHP/networking work, or opc.https:// transport (use opcua-client-ext-transport-https).
The 60-second mental model
(server dials the client)
OPC UA Server ───TCP connect──► ReverseConnectListener (bind host:port)
│ │ accept(timeout) ── stream_select()
│ sends RHE frame ────────────────►│ readFrame()
│ │ ReverseHelloParser::parse()
│ ▼
│ ReverseHelloValidator::ensureAccepted()
│ (ServerUri whitelist + opc.tcp:// scheme)
│ ▼
│ ReverseConnectSession {serverUri, endpointUrl, socket}
│ ▼
└──── same socket ────────► ReverseConnectClientFactory::buildClient()
│ TcpTransport::fromConnectedSocket()
▼
Client (core opcua-client, fully connected)
Four things to know:
- The listener is blocking and single-threaded.
accept(float $timeoutSeconds)waits viastream_select()for one inbound connection, reads the RHE, validates it, and returns aReverseConnectSession. Loop overaccept()to service multiple servers. There is no event loop. - The validator is the security boundary. Anyone who can reach the listener port can send an RHE, so the
ServerUriis whitelisted before the secure channel opens. The whitelist is fail-secure: empty whitelist ⇒ every message rejected. - The socket is reused, not re-dialed. The factory wraps the already-connected socket with
TcpTransport::fromConnectedSocket(); the core'sManagesConnectionTrait::performConnect()detectsisConnected()and skips the redundant outboundconnect(), jumping straight to HEL/ACK on the inherited socket. - This package speaks no OPC UA services itself. Once
buildClient()returns aClient, everything else (read/write/browse/subscribe) is the coreopcua-clientAPI.
Quick start (the canonical shape)
use PhpOpcua\Client\ClientBuilder;
use PhpOpcua\Client\ExtReverseConnect\ReverseConnectClientFactory;
use PhpOpcua\Client\ExtReverseConnect\ReverseConnectListener;
use PhpOpcua\Client\ExtReverseConnect\ReverseHelloValidator;
use PhpOpcua\Client\Security\SecurityMode;
use PhpOpcua\Client\Security\SecurityPolicy;
// 1. Whitelist the servers you trust (exact, case-sensitive ApplicationUri).
$validator = new ReverseHelloValidator(['urn:opcua:testserver:nodes']);
// 2. Bind a listener. Use 0.0.0.0 when the server is remote / containerised;
// 127.0.0.1 only when the server can reach loopback.
$listener = new ReverseConnectListener('0.0.0.0', 4840, $validator);
$listener->listen();
try {
// 3. Block until a trusted server announces itself (or time out).
$session = $listener->accept(timeoutSeconds: 30.0);
// 4. Bridge the live socket into a standard, fully connected Client.
$client = (new ReverseConnectClientFactory())->buildClient(
$session,
static function (ClientBuilder $b): void {
$b->setSecurityPolicy(SecurityPolicy::None)
->setSecurityMode(SecurityMode::None);
},
);
// 5. From here it's the ordinary opcua-client API.
$value = $client->read('ns=2;s=Sensors/Temp')->getValue();
$client->disconnect();
} finally {
$listener->close();
}
When to load deeper references
| If the task involves... | Read |
|---|---|
| Understanding the components, the RHE wire frame, and the socket-handoff into the core transport | references/ARCHITECTURE.md |
Driving the listener: bind addresses, accept() timeouts, multi-server loops, frame-size limits, the factory $configure callback | references/LISTENER.md |
| The whitelist trust model, fail-secure defaults, what validation does and does not protect, certificates vs. bind interface | references/SECURITY.md |
Writing unit tests over loopback, or the Docker-based integration suite (host.docker.internal, readiness, StartReverseConnect) | references/TESTING.md |
Debugging timeouts, BadServerHalted, parse errors, rejections, or unexpected behaviour | references/PITFALLS.md |
| A complete working example for a specific task (single accept, multi-server loop, PSR-14 audit, secure reverse connect) | assets/recipes.md |
Public API surface (must-know)
All classes are in PhpOpcua\Client\ExtReverseConnect.
| Class | Role |
|---|---|
ReverseConnectListener | Binds the TCP server socket; accept() returns a validated ReverseConnectSession. Ctor: (string $bindHost, int $bindPort, ReverseHelloValidator $validator, ?LoggerInterface $logger = null, ?EventDispatcherInterface $dispatcher = null, int $maxFrameSize = 65535). Methods: listen(), accept(float $timeoutSeconds), close(), getBindAddress(), isListening(). listen()/close() are idempotent. |
ReverseHelloParser | Pure decoder. parse(string $frame, int $maxFrameSize = 65535): ReverseHelloMessage. Consts MIN_FRAME_SIZE = 16, DEFAULT_MAX_FRAME_SIZE = 65535. No I/O. |
ReverseHelloMessage | final readonly DTO: public string $serverUri, public string $endpointUrl. Null OPC UA strings (length -1) normalised to ''. |
ReverseHelloValidator | Whitelist + scheme guard. Ctor (iterable<string> $allowedServerUris). ensureAccepted(msg) (throws), isAccepted(msg): bool, getAllowedServerUris(): list<string>. Empty whitelist = reject all. |
ReverseConnectSession | final readonly value object: public string $serverUri, public string $endpointUrl, public mixed $socket (live, connected TCP stream resource). |
ReverseConnectClientFactory | Bridge to the core. buildClient(ReverseConnectSession $session, ?Closure $configure = null, ?float $readTimeout = null): Client. |
Events (PSR-14) — dispatched only when a dispatcher is supplied to the listener
| Event | When | Payload |
|---|---|---|
Event\ReverseHelloReceived | after a successful decode, before validation | public ReverseHelloMessage $message |
Event\ReverseConnectAccepted | after the validator approves, before accept() returns | public ReverseHelloMessage $message |
Event\ReverseConnectRejected | when the validator refuses a syntactically valid frame | public ReverseHelloMessage $message, public string $reason |
Exceptions — all extend Exception\ReverseConnectException (which extends RuntimeException)
| Exception | Meaning |
|---|---|
ReverseConnectException | Base: bind failure, accept() before listen(), stream_select()/stream_socket_accept() failure. |
ReverseHelloParseException | Wire-format / framing error (bad MessageType, size mismatch, truncated frame, malformed OPC UA String, trailing bytes). |
ReverseConnectRejectedException | Validator rejection. Carries public readonly ReverseHelloMessage $rejectedMessage. |
ReverseConnectTimeoutException | accept() budget elapsed with no inbound connection. |
Idiomatic patterns AI agents should follow
- Bind
0.0.0.0for remote/containerised servers,127.0.0.1only for loopback-reachable ones. A server in a Docker container reaches the host viahost.docker.internal→ the bridge gateway IP, which cannot reach a127.0.0.1-bound listener. Seereferences/PITFALLS.md. - Always whitelist real
ServerUris. Never pass an empty whitelist expecting "accept all" — it does the opposite (fail-secure). Never disable validation. - Treat
accept()as one-shot per call. Wrap it in a loop for multiple servers; each call returns one session. There is no built-in concurrency. - Reuse the socket via the factory — don't re-
connect()toendpointUrlyourself. That would open a second outbound channel and defeat the whole point of Reverse Connect (the server may be behind NAT and unreachable outbound). close()the listener infinally;disconnect()the client infinally. The session owns the socket until the factory's transport takes it over.- Logs via PSR-3 (
$loggerctor arg), events via PSR-14 ($dispatcherctor arg). Events are not even constructed when no dispatcher is supplied — zero overhead. - Match the security config on both ends. The
$configurecallback setsSecurityPolicy/SecurityMode/identity exactly as a normal client would; the announcedendpointUrland the server certificate drive validation, not the listener bind interface.
Common pitfalls (read before generating code)
Don't write code that:
- Binds
127.0.0.1when the server lives in another host/container — the RHE never arrives andaccept()throwsReverseConnectTimeoutException. - Passes an empty
ReverseHelloValidator([])thinking it accepts everything — it rejects everything. - Calls
ClientBuilder::connect($session->endpointUrl)directly instead offactory->buildClient($session)— re-dials outbound and ignores the inherited socket. - Fails the test/run on the first connect against a freshly booted server (
BadServerHalted,0x800E0000) instead of retrying through the boot window. - Leaks the listener or session socket by not closing in
finally. - Assumes
accept()returning means the OPC UA session is up — it only means the RHE was received and validated; the UA handshake happens inbuildClient().
Full catalog in references/PITFALLS.md.
Related packages in the php-opcua ecosystem
opcua-client— the core client this package extends. OncebuildClient()returns, you are using its API. Load theopcua-clientskill for read/write/browse/subscribe/history.opcua-client-ext-transport-https—opc.https://wire transport (Part 6 §7.4). Sibling extension on the sameClientTransportInterfaceseam.opcua-session-manager— keeps sessions alive across PHP requests; pair with reverse connect when the acceptedClientmust outlive a single request.uanetstandard-test-suite— Docker test servers exposingTestServer/ReverseConnect/StartReverseConnect/StopReverseConnectmethods to drive the integration suite. Requires v1.4.0+ (methods) / v1.5.1+ (readiness-gated healthcheck).
What ships with it: 6 files
33.2 KB alongside SKILL.md
assets/
- recipes.md6.5 KB
references/
- ARCHITECTURE.md5.8 KB
- LISTENER.md5.9 KB
- PITFALLS.md5.5 KB
- SECURITY.md4.4 KB
- TESTING.md5.1 KB