Skip to main content

offline_protocol_transport/
stream_framing.rs

1//! Peer-stream framing: where a frame ends, and who the peer is.
2//!
3//! The chapter is `docs/spec/stream-framing.md`. A peer stream is a byte
4//! stream the platform established to exactly one other device (a Wi-Fi
5//! Direct group socket, a TCP connection over a LAN, AWDL or a routed
6//! mesh), and the engine holds every such stream in the slot the FFI
7//! names `wifi_direct`. A stream gets two things from its platform for free,
8//! bytes in order and a close; this module is the one Rust implementation of
9//! the two it does not: a frame boundary, and the peer's proved address.
10//!
11//! # Invariants
12//!
13//! - **Every frame is length-prefixed.** A `u32` big-endian length, then that
14//!   many bytes. A length of zero, or one above
15//!   [`PEER_STREAM_MAX_FRAME_BYTES`], closes the stream before a byte of the
16//!   body is read, so a hostile prefix costs four bytes and never an
17//!   allocation.
18//! - **The first frame in each direction is the identity assertion.** It is
19//!   verified before anything is announced, and a stream whose preamble did
20//!   not verify carries nothing: no frame from it is ever delivered, and the
21//!   stream is closed. There is no accept-but-flag state, because the address
22//!   a preamble proves becomes the transport-verified peer id the receiving
23//!   core matches `Message.sender` against.
24//! - **Position decides what a body is.** The first body is the preamble and
25//!   every later body is a message. No byte in a body announces its kind.
26//! - **The framing is not authenticated.** The prefix comes from whoever
27//!   holds the other end. Every bound here keeps a hostile length from costing
28//!   memory; none of them establishes trust, which is the preamble's job and
29//!   then the message's own.
30//!
31//! # Who calls this
32//!
33//! The platform managers that already speak this framing in Kotlin, Swift and
34//! Python are conforming implementations of the chapter, not callers of this
35//! module: bodies cross the FFI whole (`wifi_direct_message_received`,
36//! `wifi_direct_get_next_message`), so a manager reads and writes the prefix
37//! itself and verifies the preamble through `verify_identity_assertion`. This
38//! module serves a Rust host that owns its own sockets, and it is the
39//! reference the chapter's conformance vectors are checked against, in
40//! `tests/stream_framing_vectors.rs`.
41//!
42//! # What this module does not do
43//!
44//! It never verifies a signature. The verifier is injected as a
45//! [`PreambleVerifier`], because the one verifier lives beside the identity
46//! key in `offline-protocol-mls` and this crate holds no Ed25519. And it never
47//! decodes a message body: what the core does with a delivered body (decode,
48//! verify, route, refuse) is above this layer.
49
50use core::fmt;
51
52use crate::constants::{
53    PEER_STREAM_LENGTH_PREFIX_LEN, PEER_STREAM_MAX_FRAME_BYTES, PEER_STREAM_PREAMBLE_FLOOR,
54};
55use crate::{Error, Result};
56
57/// Wraps one body as one frame: the big-endian `u32` length, then the body.
58///
59/// Refuses what a conforming receiver would refuse on the prefix, an empty
60/// body or one above [`PEER_STREAM_MAX_FRAME_BYTES`], so a sender never
61/// emits a frame that closes the stream at the other end. The ceiling is
62/// inclusive.
63///
64/// # Errors
65///
66/// [`Error::SendFailed`] for an empty body, [`Error::MessageTooLarge`] above
67/// the ceiling.
68pub fn frame(body: &[u8]) -> Result<Vec<u8>> {
69    if body.is_empty() {
70        return Err(Error::SendFailed(
71            "peer stream: a frame body is at least one byte".to_string(),
72        ));
73    }
74    if body.len() > PEER_STREAM_MAX_FRAME_BYTES {
75        return Err(Error::MessageTooLarge(
76            body.len(),
77            PEER_STREAM_MAX_FRAME_BYTES,
78        ));
79    }
80    let mut out = Vec::with_capacity(PEER_STREAM_LENGTH_PREFIX_LEN + body.len());
81    out.extend_from_slice(&(body.len() as u32).to_be_bytes());
82    out.extend_from_slice(body);
83    Ok(out)
84}
85
86/// Why a reader closed its stream.
87///
88/// Every variant is terminal: a closed reader delivers nothing more, and the
89/// host closes the socket. None of them is a peer's fault the host should
90/// report upward; a stream that never proved a peer was never announced, so
91/// there is nothing to report lost.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum CloseReason {
94    /// A prefix of zero. No encoding produces an empty message.
95    ZeroLength,
96    /// A prefix above [`PEER_STREAM_MAX_FRAME_BYTES`], refused before the body.
97    OverCeiling(usize),
98    /// A preamble prefix under [`PEER_STREAM_PREAMBLE_FLOOR`], refused before
99    /// the body: a key and most of a signature cannot verify.
100    PreambleUnderFloor(usize),
101    /// The first body did not verify as an identity assertion. A well-formed
102    /// message arriving first lands here too: position decides what a body
103    /// is, and a message is not a preamble.
104    PreambleRejected,
105    /// The preamble verified, but the stream was opened toward a different
106    /// address (a peer-list entry, an invite, a discovery record's `addr`).
107    /// The comparison is exact, never case-folded.
108    AddressMismatch {
109        /// The address the stream was opened toward.
110        expected: String,
111        /// The address the preamble proved.
112        derived: String,
113    },
114}
115
116impl fmt::Display for CloseReason {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            Self::ZeroLength => write!(f, "zero-length frame"),
120            Self::OverCeiling(len) => write!(
121                f,
122                "frame length {len} is above the {PEER_STREAM_MAX_FRAME_BYTES}-byte ceiling"
123            ),
124            Self::PreambleUnderFloor(len) => write!(
125                f,
126                "preamble length {len} is under the {PEER_STREAM_PREAMBLE_FLOOR}-byte floor"
127            ),
128            Self::PreambleRejected => {
129                write!(f, "the first frame is not a verifiable identity assertion")
130            }
131            Self::AddressMismatch { expected, derived } => write!(
132                f,
133                "the preamble proves {derived} but the stream was opened toward {expected}"
134            ),
135        }
136    }
137}
138
139impl std::error::Error for CloseReason {}
140
141/// What one direction of a stream yielded, in order.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum StreamEvent {
144    /// The preamble verified. The peer is announced to the core under this
145    /// address, the derived one, never a claimed string: it is what
146    /// `wifi_direct_peer_connected` takes.
147    Announced(String),
148    /// A message body from the announced peer, to hand to the core attributed
149    /// to `from`: what `wifi_direct_message_received` takes.
150    Message {
151        /// The address the stream's preamble proved.
152        from: String,
153        /// Exactly one message in the hop-local encoding, undecoded.
154        body: Vec<u8>,
155    },
156    /// The stream is closed. Always the last event a reader yields, and
157    /// nothing after it is delivered. Whether the peer was announced before
158    /// this decides whether the host reports it lost.
159    Closed(CloseReason),
160}
161
162/// Steps one to three of the chapter's preamble verification: parse, verify
163/// the signature under the key, derive the address. `None` refuses.
164///
165/// The one implementation is `offline_protocol_mls::verify_identity_assertion`;
166/// a host passes it as `Box::new(|b| verify_identity_assertion(b).ok().map(|a| a.to_string()))`.
167pub type PreambleVerifier = Box<dyn Fn(&[u8]) -> Option<String> + Send + Sync>;
168
169/// The receiving side of one direction of a peer stream.
170///
171/// Feed it the bytes each socket read returned, in order, and act on the
172/// events it yields. It holds at most one incomplete frame between calls,
173/// whose prefix has already passed the bounds, so memory per stream is
174/// bounded by the caller's read buffer plus one body.
175///
176/// One reader is one direction of one stream. It has no view of other
177/// streams, so the chapter's one-announced-stream-per-address rule (a second
178/// stream proving an address already announced is refused or supersedes the
179/// first, and either way the core sees one announcement and one loss) is the
180/// host's, which is the only place that can see both streams.
181pub struct PeerStreamReader {
182    verify: PreambleVerifier,
183    expected: Option<String>,
184    buffer: Vec<u8>,
185    announced: Option<String>,
186    closed: bool,
187}
188
189impl fmt::Debug for PeerStreamReader {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        f.debug_struct("PeerStreamReader")
192            .field("expected", &self.expected)
193            .field("buffered", &self.buffer.len())
194            .field("announced", &self.announced)
195            .field("closed", &self.closed)
196            .finish()
197    }
198}
199
200impl PeerStreamReader {
201    /// A reader for a stream with no claim about who is at the other end: an
202    /// inbound connection, or a peer-list entry naming only a host and port.
203    /// The derived address is the peer's id, and there is nothing to compare.
204    pub fn new(verify: PreambleVerifier) -> Self {
205        Self {
206            verify,
207            expected: None,
208            buffer: Vec::new(),
209            announced: None,
210            closed: false,
211        }
212    }
213
214    /// Requires the preamble to prove exactly `address`, the chapter's fourth
215    /// step, for a stream opened toward a known address. A mismatch closes
216    /// the stream with [`CloseReason::AddressMismatch`]. The announced address
217    /// is still the derived one; the claim is a hint about whom to expect,
218    /// never a name to announce.
219    pub fn expecting(mut self, address: impl Into<String>) -> Self {
220        self.expected = Some(address.into());
221        self
222    }
223
224    /// The address this stream proved, once it has.
225    pub fn peer(&self) -> Option<&str> {
226        self.announced.as_deref()
227    }
228
229    /// Whether the reader has closed the stream. A closed reader yields
230    /// nothing more, whatever it is fed.
231    pub fn is_closed(&self) -> bool {
232        self.closed
233    }
234
235    /// Feeds the bytes one read returned and yields every event they complete.
236    ///
237    /// Frames are yielded in stream order. A [`StreamEvent::Closed`] is
238    /// terminal and always last: frames decoded before the fault are still
239    /// delivered, because they arrived on a stream whose peer was proved,
240    /// and nothing after it ever is.
241    pub fn feed(&mut self, bytes: &[u8]) -> Vec<StreamEvent> {
242        let mut events = Vec::new();
243        if self.closed {
244            return events;
245        }
246        self.buffer.extend_from_slice(bytes);
247
248        // Frames are consumed through an offset and the buffer is compacted
249        // once per call: draining per frame would move the remaining bytes
250        // once for every frame, quadratic in the frames one read carries.
251        let mut consumed = 0;
252        loop {
253            let pending = &self.buffer[consumed..];
254            if pending.len() < PEER_STREAM_LENGTH_PREFIX_LEN {
255                break;
256            }
257            let mut prefix = [0u8; PEER_STREAM_LENGTH_PREFIX_LEN];
258            prefix.copy_from_slice(&pending[..PEER_STREAM_LENGTH_PREFIX_LEN]);
259            let length = u32::from_be_bytes(prefix) as usize;
260
261            // Bounds on the prefix, before a byte of the body: the whole
262            // point of reading the length first.
263            if let Some(reason) = self.refuse_prefix(length) {
264                events.push(self.close(reason));
265                return events;
266            }
267
268            let end = PEER_STREAM_LENGTH_PREFIX_LEN + length;
269            if pending.len() < end {
270                break;
271            }
272            let body = pending[PEER_STREAM_LENGTH_PREFIX_LEN..end].to_vec();
273            consumed += end;
274
275            match self.accept(body) {
276                Ok(event) => events.push(event),
277                Err(reason) => {
278                    events.push(self.close(reason));
279                    return events;
280                }
281            }
282        }
283        self.buffer.drain(..consumed);
284        events
285    }
286
287    fn refuse_prefix(&self, length: usize) -> Option<CloseReason> {
288        if length == 0 {
289            return Some(CloseReason::ZeroLength);
290        }
291        if length > PEER_STREAM_MAX_FRAME_BYTES {
292            return Some(CloseReason::OverCeiling(length));
293        }
294        if self.announced.is_none() && length < PEER_STREAM_PREAMBLE_FLOOR {
295            return Some(CloseReason::PreambleUnderFloor(length));
296        }
297        None
298    }
299
300    fn accept(&mut self, body: Vec<u8>) -> core::result::Result<StreamEvent, CloseReason> {
301        if let Some(from) = &self.announced {
302            return Ok(StreamEvent::Message {
303                from: from.clone(),
304                body,
305            });
306        }
307        let derived = (self.verify)(&body).ok_or(CloseReason::PreambleRejected)?;
308        if let Some(expected) = &self.expected {
309            if *expected != derived {
310                return Err(CloseReason::AddressMismatch {
311                    expected: expected.clone(),
312                    derived,
313                });
314            }
315        }
316        self.announced = Some(derived.clone());
317        Ok(StreamEvent::Announced(derived))
318    }
319
320    fn close(&mut self, reason: CloseReason) -> StreamEvent {
321        self.closed = true;
322        self.buffer.clear();
323        StreamEvent::Closed(reason)
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    /// A verifier that accepts one fixed body and names it; everything else
332    /// is refused. The real verifier is exercised by the conformance test
333    /// in `tests/stream_framing_vectors.rs`.
334    fn verifier(accept: &'static [u8], as_address: &'static str) -> PreambleVerifier {
335        Box::new(move |b| (b == accept).then(|| as_address.to_string()))
336    }
337
338    const PREAMBLE: &[u8] = &[7u8; 96];
339
340    #[test]
341    fn frame_prefixes_the_length_big_endian() {
342        assert_eq!(frame(b"abc").unwrap(), [0, 0, 0, 3, b'a', b'b', b'c']);
343        assert!(frame(b"").is_err(), "an empty body is not a frame");
344        let at_ceiling = vec![0u8; PEER_STREAM_MAX_FRAME_BYTES];
345        assert_eq!(
346            &frame(&at_ceiling).unwrap()[..4],
347            &[0x00, 0x10, 0x00, 0x00],
348            "the ceiling is inclusive"
349        );
350        assert!(matches!(
351            frame(&vec![0u8; PEER_STREAM_MAX_FRAME_BYTES + 1]),
352            Err(Error::MessageTooLarge(_, PEER_STREAM_MAX_FRAME_BYTES))
353        ));
354    }
355
356    #[test]
357    fn the_first_frame_announces_and_later_frames_deliver() {
358        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
359        assert_eq!(r.peer(), None);
360        let mut bytes = frame(PREAMBLE).unwrap();
361        bytes.extend(frame(b"hello").unwrap());
362        bytes.extend(frame(b"world").unwrap());
363        assert_eq!(
364            r.feed(&bytes),
365            vec![
366                StreamEvent::Announced("off1peer".into()),
367                StreamEvent::Message {
368                    from: "off1peer".into(),
369                    body: b"hello".to_vec()
370                },
371                StreamEvent::Message {
372                    from: "off1peer".into(),
373                    body: b"world".to_vec()
374                },
375            ]
376        );
377        assert_eq!(r.peer(), Some("off1peer"));
378        assert!(!r.is_closed());
379    }
380
381    #[test]
382    fn chunking_does_not_change_what_is_delivered() {
383        let mut whole = frame(PREAMBLE).unwrap();
384        whole.extend(frame(b"hello").unwrap());
385        let expected = PeerStreamReader::new(verifier(PREAMBLE, "off1peer")).feed(&whole);
386
387        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
388        let mut byte_at_a_time = Vec::new();
389        for b in &whole {
390            byte_at_a_time.extend(r.feed(std::slice::from_ref(b)));
391        }
392        assert_eq!(byte_at_a_time, expected);
393        assert!(r.buffer.is_empty(), "nothing left over after whole frames");
394    }
395
396    #[test]
397    fn a_preamble_that_does_not_verify_closes_the_stream_with_nothing_announced() {
398        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
399        let mut bytes = frame(&[9u8; 100]).unwrap();
400        bytes.extend(frame(b"never delivered").unwrap());
401        assert_eq!(
402            r.feed(&bytes),
403            vec![StreamEvent::Closed(CloseReason::PreambleRejected)]
404        );
405        assert!(r.is_closed());
406        assert_eq!(r.peer(), None);
407        assert!(
408            r.feed(&frame(PREAMBLE).unwrap()).is_empty(),
409            "closed is closed"
410        );
411    }
412
413    #[test]
414    fn a_preamble_under_the_floor_is_refused_on_the_prefix() {
415        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
416        // Only the prefix arrives; the body never has to.
417        assert_eq!(
418            r.feed(&[0, 0, 0, 95]),
419            vec![StreamEvent::Closed(CloseReason::PreambleUnderFloor(95))]
420        );
421    }
422
423    #[test]
424    fn a_message_frame_may_be_short_once_the_peer_is_announced() {
425        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
426        r.feed(&frame(PREAMBLE).unwrap());
427        assert_eq!(
428            r.feed(&frame(b"x").unwrap()),
429            vec![StreamEvent::Message {
430                from: "off1peer".into(),
431                body: b"x".to_vec()
432            }],
433            "the floor applies to the preamble only"
434        );
435    }
436
437    #[test]
438    fn a_zero_or_oversize_prefix_closes_before_the_body() {
439        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
440        r.feed(&frame(PREAMBLE).unwrap());
441        assert_eq!(
442            r.feed(&[0, 0, 0, 0]),
443            vec![StreamEvent::Closed(CloseReason::ZeroLength)]
444        );
445
446        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
447        r.feed(&frame(PREAMBLE).unwrap());
448        assert_eq!(
449            r.feed(&[0x00, 0x10, 0x00, 0x01]),
450            vec![StreamEvent::Closed(CloseReason::OverCeiling(
451                PEER_STREAM_MAX_FRAME_BYTES + 1
452            ))]
453        );
454        assert!(r.buffer.is_empty(), "a closed reader holds nothing");
455    }
456
457    #[test]
458    fn a_prefix_of_exactly_the_ceiling_is_accepted() {
459        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
460        r.feed(&frame(PREAMBLE).unwrap());
461        assert!(r.feed(&[0x00, 0x10, 0x00, 0x00]).is_empty());
462        assert!(
463            !r.is_closed(),
464            "the ceiling is inclusive; the body is awaited"
465        );
466    }
467
468    #[test]
469    fn frames_before_a_fault_are_still_delivered() {
470        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer"));
471        let mut bytes = frame(PREAMBLE).unwrap();
472        bytes.extend(frame(b"first").unwrap());
473        bytes.extend([0, 0, 0, 0]);
474        bytes.extend(frame(b"after the close").unwrap());
475        let events = r.feed(&bytes);
476        assert_eq!(events.len(), 3);
477        assert!(matches!(&events[1], StreamEvent::Message { body, .. } if body == b"first"));
478        assert_eq!(events[2], StreamEvent::Closed(CloseReason::ZeroLength));
479    }
480
481    #[test]
482    fn an_expected_address_must_match_exactly() {
483        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer")).expecting("off1other");
484        assert_eq!(
485            r.feed(&frame(PREAMBLE).unwrap()),
486            vec![StreamEvent::Closed(CloseReason::AddressMismatch {
487                expected: "off1other".into(),
488                derived: "off1peer".into(),
489            })]
490        );
491        assert_eq!(r.peer(), None, "a mismatched preamble announces nothing");
492
493        let mut r = PeerStreamReader::new(verifier(PREAMBLE, "off1peer")).expecting("off1peer");
494        assert_eq!(
495            r.feed(&frame(PREAMBLE).unwrap()),
496            vec![StreamEvent::Announced("off1peer".into())]
497        );
498    }
499
500    #[test]
501    fn close_reasons_name_the_bound() {
502        assert!(CloseReason::OverCeiling(5).to_string().contains("1048576"));
503        assert!(CloseReason::PreambleUnderFloor(1)
504            .to_string()
505            .contains("96"));
506    }
507}