SSL as a first-class TCPConnection concern #157
Replies: 5 comments
-
Send lifecycle and failure semanticsSend before connectedIn an async actor system, "create connection then send data" is a natural pattern — the send can arrive before the connection is established. The connection should handle this gracefully:
This means Explicit send failure notificationEvery send gets an explicit outcome: either
Note: This separation of SSL handshake failure is a connection failureSSL handshake failure (auth failure, protocol error) maps to Possible future work: expanding |
Beta Was this translation helpful? Give feedback.
-
SSL-specific callbacks: noneSSL is invisible to the actor's callback interface. There are no SSL-specific callbacks:
The actor's callback interface is identical for SSL and non-SSL connections: This means |
Beta Was this translation helpful? Give feedback.
-
DataInterceptor: removedThe The Direction A summaryAll design questions are resolved:
The actor's experience with SSL is: use Next step: Direction B — protocol adapters on top of this foundation, driven by a concrete simple protocol use case. |
Beta Was this translation helpful? Give feedback.
-
|
Discussion #158 has the concrete implementation plan for Direction A. Eleven steps covering send queueing, failure notification, SSL constructors, internal SSL logic, removal of DataInterceptor infrastructure, and example/test updates. Includes a phased approach if incremental merging is preferred. |
Beta Was this translation helpful? Give feedback.
-
|
This design was implemented across two PRs:
Deviations from the original design:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Context
This discussion explores Direction A from Discussion #156 (Rethinking SSL and protocol transforms in lori): making SSL a first-class connection concern built into
TCPConnection. Direction A serves as the foundation — Direction B (protocol adapters above the SSL layer) will be incorporated on top of this once the foundation is solid.Motivation
Lori's
DataInterceptortrait (Discussion #149, PR #154) solves the data ordering problem for protocol layering but introduces a separate object into lori's mixin-pattern architecture. That object is essentially a notifier — it can't receive messages, can't participate in the actor's state machine, and sits outside the compile-time safety the mixin pattern provides. For a single interceptor (just SSL), this is manageable. But composition of multiple interceptors introduces ordering mistakes, setup/ready signaling complexity, error propagation ambiguity, and reintroduces the fundamental limitation lori was created to avoid. See Discussion #156 for the full analysis.The solution: SSL is not an interceptor. It's a transport-level concern that belongs inside the connection itself.
Design
Four constructors
TCPConnectiongets two new SSL-specific constructors alongside the existing two:Separate constructors rather than optional parameters because:
(SSLContext | None)parameters that add a runtime branch for something known at construction time.SSLContext as the dependency injection point
The SSL constructors receive an
SSLContext(from theponylang/ssllibrary), not raw configuration. This keeps SSL configuration knowledge in the ssl library where it belongs. The user configures the context however they want (certs, CA, ciphers, ALPN, verification settings), and lori just consumes it.SSLContextis avalclass, so it can be shared across connections — create one context, use it for many connections.TCPConnectioninternally callsssl_ctx.client(host)orssl_ctx.server()to create the per-connectionSSLsession.Internal SSL session management
TCPConnectionowns theSSLsession and manages the full lifecycle:SSL.receive(), checksSSL.state(), sends handshake data viaSSL.can_send()/SSL.send(). Defers_on_connected/_on_starteduntilSSL.state()returnsSSLReady.SSL.receive(), decrypted plaintext comes out viaSSL.read(), and is delivered to the actor's_on_received.send()with plaintext.TCPConnectionencrypts viaSSL.write()and sends ciphertext viaSSL.can_send()/SSL.send().SSL.dispose()is called on connection close.This is the same logic currently in
SSLClientInterceptor/SSLServerInterceptor— it moves from the interceptor intoTCPConnectionitself.No changes needed in ponylang/ssl
The
SSLclass's existing API is exactly whatTCPConnectionwould use internally:SSLContext.client(host)/SSLContext.server()— session creationSSL.receive(data)— feed encrypted bytes from the wireSSL.read(expect)— extract decrypted plaintextSSL.write(data)— encrypt plaintext for sendingSSL.can_send()/SSL.send()— extract ciphertext for the wireSSL.state()— check handshake stateSSL.alpn_selected()— get negotiated ALPN protocolSSL.dispose()— cleanupThe ssl library doesn't need to change. The reference capabilities work:
SSLContextisval(shareable),SSLisref(owned byTCPConnection, which is arefclass owned by one actor).Open questions to resolve
SSL-specific callbacks. Currently
NetSSLLifecycleEventReceiverdeliverson_alpn_negotiated,on_ssl_auth_failed,on_ssl_errorto the actor. How should these reach the actor with SSL built into the connection? Options: a new SSL-specific lifecycle trait the actor implements, additional methods on the existing lifecycle receiver traits, or fold them into existing callbacks (e.g., auth failure becomes_on_connection_failure).Send behavior during handshake. Currently
send()returnsSendErrorNotReadyif the interceptor handshake isn't complete. With SSL built in, does the connection queue data during handshake, return an error, or is it impossible to call send before_on_connected(which is deferred until handshake completes)?What happens to
DataInterceptor? With SSL built into the connection, the interceptor's original motivating use case is gone. Does the trait remain for Direction B (protocol adapters), get redesigned for that purpose, or get removed entirely and replaced with something different when we get to Direction B?Connection failure semantics. If the SSL handshake fails (auth failure, protocol error), should the client get
_on_connection_failure()(same as TCP connection failure) or a distinct callback? The current interceptor design uses_on_connection_failurefor handshake failures before_on_connectedwas delivered.adjust_expectequivalent. The currentDataInterceptorhasadjust_expect()because SSL changes data framing (returns 0 to read all available, tracks the application's value internally). With SSL insideTCPConnection, this needs to be handled internally — doesTCPConnectionmanage expect differently when SSL is active?Beta Was this translation helpful? Give feedback.
All reactions