Skip to main content

yog_data/
io.rs

1//! Yog Data over a byte stream — a socket, a pipe: each message a frame,
2//! its length (a little-endian `u32`) and then its binary form. What Yog's
3//! programs say to each other (the game's control channel, `yog serve`).
4
5use std::io::{self, Read, Write};
6
7use crate::Schema;
8
9/// How a frame's length is written.
10pub(crate) type Len = u32;
11/// The longest frame read: anything longer is a broken or hostile peer.
12pub const MAX_FRAME: usize = 256 << 20;
13
14/// Writes `message` as one frame, and flushes.
15pub fn write_frame<T: Schema>(w: &mut impl Write, message: &T) -> io::Result<()> {
16    write_frame_bytes(w, &crate::to_bytes(message))
17}
18
19/// Writes `bytes` (a message's binary form) as one frame, and flushes.
20pub fn write_frame_bytes(w: &mut impl Write, bytes: &[u8]) -> io::Result<()> {
21    let len = Len::try_from(bytes.len())
22        .ok()
23        .filter(|_| bytes.len() <= MAX_FRAME)
24        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "frame too long"))?;
25    // One write: a reader never sees a length without its body behind it.
26    let mut frame = Vec::with_capacity(size_of::<Len>() + bytes.len());
27    frame.extend_from_slice(&len.to_le_bytes());
28    frame.extend_from_slice(bytes);
29    w.write_all(&frame)?;
30    w.flush()
31}
32
33/// The next frame's bytes; `None` where the stream ends between frames.
34pub fn read_frame_bytes(r: &mut impl Read) -> io::Result<Option<Vec<u8>>> {
35    let mut len = [0; size_of::<Len>()];
36    match r.read_exact(&mut len) {
37        Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
38        other => other?,
39    }
40    let len = Len::from_le_bytes(len) as usize;
41    if len > MAX_FRAME {
42        return Err(io::Error::new(io::ErrorKind::InvalidData, "frame too long"));
43    }
44    let mut bytes = vec![0; len];
45    r.read_exact(&mut bytes)?;
46    Ok(Some(bytes))
47}
48
49/// The next message; `None` where the stream ends between frames.
50pub fn read_frame<T: Schema>(r: &mut impl Read) -> io::Result<Option<T>> {
51    let Some(bytes) = read_frame_bytes(r)? else {
52        return Ok(None);
53    };
54    crate::read_bytes(&bytes)
55        .map(Some)
56        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{e:?}")))
57}
58
59#[cfg(test)]
60mod tests;