Files
alknet/tasks/integration/phase2/raw-framing-interface-implementation.md

5.5 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
raw-framing-interface-implementation Implement RawFramingInterface accept/recv/send with first-frame auth completed
stream-interface-message-interface-split
narrow low component implementation

Description

Implement RawFramingInterface and RawFramingSession to handle length-prefixed EventEnvelope frames over a byte stream, with first-frame authentication. Currently RawFramingInterface::accept() returns an error and RawFramingSession stubs exist.

Per the integration plan section 2.2 and interface.md:

RawFramingInterface: Reads 4-byte length-prefixed JSON EventEnvelope frames from a transport stream (TCP, TLS, iroh, etc.). No SSH wrapping — the raw framing interface carries call protocol events directly.

First-frame auth: The first InterfaceEvent on a RawFramingSession carries an auth token in the InterfaceEvent.identity field or a dedicated auth event type. After IdentityProvider::resolve_from_token() verifies the token and produces an Identity, the session is authenticated. Subsequent frames are call protocol EventEnvelope data. If auth fails, the session is terminated immediately.

Current state of the code:

  • RawFramingInterface accepts any TransportStream but returns an error
  • RawFramingSession is an empty struct with stub recv() (returns None) and send() (returns error)
  • call::frame::{encode, decode, decode_with_remainder} already implement the wire format
  • IdentityProvider::resolve_from_token() exists but is not yet wired to AuthToken verification (that's coming in the API keys task)

Implementation approach:

  1. RawFramingInterface::accept() takes a TransportStream, wraps it in a BufReader for buffered reading, stores it in RawFramingSession. The RawFramingSession is created in an "unauthenticated" state.
  2. RawFramingSession::recv() reads frames from the stream:
    • If unauthenticated: read the first frame, extract the auth token, call IdentityProvider::resolve_from_token(). On success, transition to "authenticated" with the resolved Identity. On failure, return an error (session terminated).
    • If authenticated: read EventEnvelope frames, wrap in InterfaceEvent::with_identity(envelope, identity).
  3. RawFramingSession::send() writes EventEnvelope frames to the stream using call::frame::encode.

The RawFramingSession needs:

  • A BufReader<Box<dyn TransportStream>> for reading framed data
  • A Box<dyn TransportStream> (or WriteHalf) for writing framed data
  • An Option<Identity> tracking auth state
  • A reference to IdentityProvider for token resolution
  • A buffer for partial frame reads (decode_with_remainder pattern)

Acceptance Criteria

  • RawFramingInterface::accept() takes a TransportStream and StreamInterfaceConfig::RawFraming config, creates a RawFramingSession wrapping the stream
  • RawFramingSession holds a buffered reader and writer over the transport stream, an auth state (Option<Identity>), and a reference to IdentityProvider
  • RawFramingSession::recv() reads length-prefixed EventEnvelope frames from the stream using call::frame::decode_with_remainder
  • First-frame auth: the first recv() call resolves the auth token via IdentityProvider::resolve_from_token() and stores the resulting Identity
  • Subsequent recv() calls produce InterfaceEvent::with_identity(envelope, identity) using the authenticated identity
  • Auth failure terminates the session: recv() returns an error result on bad tokens
  • RawFramingSession::send() writes EventEnvelope frames to the stream using call::frame::encode
  • Unit test: RawFramingInterface::accept() succeeds with a valid stream
  • Unit test: RawFramingSession round-trips an EventEnvelope through send() and recv() (after mock auth)
  • Unit test: First-frame auth with a valid token transitions to authenticated state
  • Unit test: First-frame auth with an invalid token returns an error
  • Integration test: RawFramingSession over a tokio::io::duplex stream (simulated TCP) sends and receives multiple frames

References

  • docs/research/integration-plan.md — Phase 2.2
  • docs/architecture/interface.md — RawFramingInterface, first-frame auth model
  • crates/alknet-core/src/interface/raw_framing.rs — Current stubs
  • crates/alknet-core/src/call/frame.rs — Frame encode/decode
  • crates/alknet-core/src/auth/identity.rs — IdentityProvider, resolve_from_token

Notes

The frame format is already implemented and tested in call::frame. This task is primarily about wiring the frame reader/writer to the InterfaceSession trait and adding first-frame auth logic.

Consider using tokio::io::BufReader for buffered reading and tokio::io::BufWriter for buffered writing. The decode_with_remainder function handles partial reads by returning how many bytes were consumed — the session needs to maintain a read buffer for reassembly.

The RawFramingInterface config should include an Arc<dyn IdentityProvider> for first-frame auth. This follows the same pattern as SshInterfaceConfig.

Summary

Implemented RawFramingInterface::accept() and RawFramingSession with first-frame auth. RawFramingConfig now has auth: Arc. RawFramingSession splits stream via tokio::io::split, recv() implements first-frame auth with AuthToken resolution via IdentityProvider::resolve_from_token(), send() encodes via call::frame::encode with buffered writes, read_frame() uses decode_with_remainder for partial reassembly. 7 new tests.