offline_protocol_transport/
stream_framing.rs1use 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
57pub 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#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum CloseReason {
94 ZeroLength,
96 OverCeiling(usize),
98 PreambleUnderFloor(usize),
101 PreambleRejected,
105 AddressMismatch {
109 expected: String,
111 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#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum StreamEvent {
144 Announced(String),
148 Message {
151 from: String,
153 body: Vec<u8>,
155 },
156 Closed(CloseReason),
160}
161
162pub type PreambleVerifier = Box<dyn Fn(&[u8]) -> Option<String> + Send + Sync>;
168
169pub 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 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 pub fn expecting(mut self, address: impl Into<String>) -> Self {
220 self.expected = Some(address.into());
221 self
222 }
223
224 pub fn peer(&self) -> Option<&str> {
226 self.announced.as_deref()
227 }
228
229 pub fn is_closed(&self) -> bool {
232 self.closed
233 }
234
235 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 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 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 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 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}