5.5 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| ssh-session-call-protocol-bridge | Bridge SshSession recv/send to call protocol via alknet-control:0 channel | completed |
|
moderate | medium | component | implementation |
Description
Implement SshSession::recv() and SshSession::send() to bridge SSH channel data to and from the call protocol's InterfaceEvent/EventEnvelope frames. Currently both methods are stubs: recv() always returns None and send() silently discards.
Per the integration plan section 2.1 and interface.md (OQ-IF-01, resolved):
The bridge works as follows:
- When
SshHandler::channel_open_direct_tcpipdetects a destination starting withalknet-, it currently accepts the channel but doesn't bridge the data. TheControlChannelRouterexists incontrol_channel.rsbut has no handler wired. SshSession::recv()should readEventEnvelopeframes from thealknet-control:0channel stream (using the 4-byte length prefix + JSON wire format fromcall::frame::{encode, decode}), wrap them inInterfaceEventwith the session'sIdentity(obtained during SSH auth).SshSession::send()should writeEventEnvelopeframes to thealknet-control:0channel stream using the same framing format.- The
ControlChannelRoutershould be wired to bridge incoming channel data to the call protocol handler.
Current state of the code:
SshSession::recv()returnsNone(stub)SshSession::send()discards silently (stub)ControlChannelRouterincontrol_channel.rshasroute()andhas_handler()but no handler is registeredcall::frame::{encode, decode}functions exist and are well-tested (4-byte BE length prefix + JSON)SshHandlerdetectsalknet-*destinations inchannel_open_direct_tcpipbut doesn't bridge dataSshHandlerstoresauthenticated_identity: Option<Identity>from SSH authInterfaceEventstruct carriesEventEnvelope+Option<Identity>— already defined
Key design considerations:
- The
SshSessionneeds access to the SSH channel's data stream to read/writeEventEnvelopeframes. This requires getting therussh::Channeldata stream and framing it. - The
ControlChannelRoutercurrently usesBox<dyn DuplexStream>— it can be wired as aControlChannelHandlerthat reads frames from the stream and producesInterfaceEvents. - The
alknet-control:0channel is the first SSH direct-tcpip channel with thealknet-controldestination. Additionalalknet-*channels may follow. - The session's
Identity(from SSH auth) must be attached to everyInterfaceEventproduced byrecv().
Acceptance Criteria
SshSession::recv()readsEventEnvelopeframes from the SSH channel data stream and producesInterfaceEventwith the session'sIdentitySshSession::send()writesEventEnvelopeframes to the SSH channel data stream usingcall::frame::encodeControlChannelRouteris wired as the handler foralknet-control:0channels, bridging SSH channel data to the call protocol- Frame encoding matches
call::frame::{encode, decode}— 4-byte big-endian length prefix + UTF-8 JSON body - The session's
Identity(fromSshHandler::authenticated_identity) is attached to everyInterfaceEventproduced byrecv() SshHandler::channel_open_direct_tcpipcorrectly routesalknet-control:0channels to theControlChannelRouterhandler- Unit test:
SshSessioncan round-trip anEventEnvelopethroughsend()andrecv()(using a mock channel stream) - Unit test:
ControlChannelRouter.with_handler()successfully routes channel data - All existing server/auth/transport tests continue to pass
- No behavioral changes for non-
alknet-*channel forwarding (port proxy logic unchanged)
References
- docs/research/integration-plan.md — Phase 2.1
- docs/architecture/interface.md — OQ-IF-01 resolution, InterfaceEvent model
- docs/architecture/call-protocol.md — EventEnvelope, frame encoding
- crates/alknet-core/src/interface/ssh.rs — SshSession stubs (recv/send)
- crates/alknet-core/src/server/control_channel.rs — ControlChannelRouter
- crates/alknet-core/src/call/frame.rs — frame encode/decode
- crates/alknet-core/src/interface/session.rs — InterfaceEvent, InterfaceSession traits
Notes
This is the highest-risk task in Phase 2. The
russhchannel data stream API needs careful handling — getting aChannel's data stream for async reading/writing is non-trivial and may require understanding russh'sdata()callback pattern vs. theChannel::into_stream()method.
Consider implementing incrementally: first wire the
ControlChannelRouterhandler to produceInterfaceEvents from raw channel data, then connect that toSshSession::recv()/send(). Each step should have passing tests before proceeding.
The
SshSessionstruct currently holds aserver::Handleand aJoinHandle. It may need additional fields to track the control channel stream and the authenticated identity for producingInterfaceEvents with identity attached.
Summary
Implemented SshSession recv/send bridge to call protocol via alknet-control:0 channel. Added FrameFramedReader/FrameFramedWriter for async length-prefixed EventEnvelope I/O. SshSession::recv() reads InterfaceEvents from mpsc channel bridged from SSH channel. SshSession::send() writes EventEnvelopes to mpsc channel bridged to SSH channel. ControlChannelBridge implements ControlChannelHandler. SshHandler routes alknet-control:0 channels to bridge task using tokio::select!. Session Identity attached to every InterfaceEvent.