When implementing a custom protocol over Iroh connections, you are working with raw byte streams. To send discrete messages (rather than a continuous stream of bytes), you must implement "framing" to define message boundaries.
In this pattern, you wrap the Iroh SendStream and RecvStream (obtained via iroh::Connection::open_bi) using tokio_util::codec to transform them from byte-oriented streams into message-oriented Stream and Sink objects.
The Framing Workflow:
- Obtain Streams: Use
iroh::Connection::open_bi to get SendStream and RecvStream. These implement AsyncWrite and AsyncRead respectively. - Apply Codec: Wrap the streams using
tokio_util::codec::FramedRead and tokio_util::codec::FramedWrite with a LengthDelimitedCodec. - Define Boundaries: The
LengthDelimitedCodec prefixes each message with a big-endian encoded u32 representing the length of the following message, allowing the receiver to identify message boundaries. - Serialize Data: Use a serialization format like
postcard and serde to convert your data structures (e.g., a Move struct) into bytes::Bytes before sending them through the Sink.
// Conceptual workflow summary:
// 1. iroh::Connection::open_bi -> SendStream (AsyncWrite), RecvStream (AsyncRead)
// 2. Wrap with tokio_util::codec::FramedWrite<SendStream, LengthDelimitedCodec> -> Sink<Bytes>
// 3. Wrap with tokio_util::codec::FramedRead<RecvStream, LengthDelimitedCodec> -> Stream<Bytes>
// 4. Use postcard/serde to serialize structs into Bytes