Skip to main content

topcoat_core/
cursor.rs

1/// Const-friendly cursor for writing primitives into a fixed byte buffer.
2pub struct ConstWriter<'a> {
3    buf: &'a mut [u8],
4    pos: usize,
5}
6
7impl<'a> ConstWriter<'a> {
8    pub const fn new(buf: &'a mut [u8]) -> Self {
9        Self { buf, pos: 0 }
10    }
11
12    pub const fn write_bytes(&mut self, bytes: &[u8]) {
13        let mut i = 0;
14        while i < bytes.len() {
15            self.buf[self.pos] = bytes[i];
16            self.pos += 1;
17            i += 1;
18        }
19    }
20
21    pub const fn write_u16_le(&mut self, v: u16) {
22        self.write_bytes(&v.to_le_bytes());
23    }
24
25    pub const fn write_u64_le(&mut self, v: u64) {
26        self.write_bytes(&v.to_le_bytes());
27    }
28
29    #[allow(clippy::cast_possible_truncation)]
30    pub const fn write_str(&mut self, s: &str) {
31        let len = s.len() as u16;
32        self.write_u16_le(len);
33        self.write_bytes(s.as_bytes());
34    }
35
36    pub const fn write_str_opt(&mut self, s: Option<&str>) {
37        match s {
38            Some(s) => {
39                self.write_bytes(&[1]);
40                self.write_str(s);
41            }
42            None => self.write_bytes(&[0]),
43        }
44    }
45}
46
47/// Const-friendly cursor for reading primitives out of a byte buffer.
48///
49/// Reads return slices borrowed from the underlying buffer, which keeps the
50/// reader allocation-free; callers can `.to_owned()` if they need ownership.
51pub struct ConstReader<'a> {
52    buf: &'a [u8],
53    pos: usize,
54}
55
56impl<'a> ConstReader<'a> {
57    #[must_use]
58    pub const fn new(buf: &'a [u8]) -> Self {
59        Self { buf, pos: 0 }
60    }
61
62    pub const fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
63        let Some((_, rest)) = self.buf.split_at_checked(self.pos) else {
64            return None;
65        };
66        let Some((head, _)) = rest.split_at_checked(n) else {
67            return None;
68        };
69        self.pos += n;
70        Some(head)
71    }
72
73    pub const fn read_u16_le(&mut self) -> Option<u16> {
74        let Some(bytes) = self.read_bytes(2) else {
75            return None;
76        };
77        Some(u16::from_le_bytes([bytes[0], bytes[1]]))
78    }
79
80    pub const fn read_u64_le(&mut self) -> Option<u64> {
81        let Some(bytes) = self.read_bytes(8) else {
82            return None;
83        };
84        Some(u64::from_le_bytes([
85            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
86        ]))
87    }
88
89    pub const fn read_str(&mut self) -> Option<&'a str> {
90        let Some(len) = self.read_u16_le() else {
91            return None;
92        };
93        let Some(bytes) = self.read_bytes(len as usize) else {
94            return None;
95        };
96        match std::str::from_utf8(bytes) {
97            Ok(s) => Some(s),
98            Err(_) => None,
99        }
100    }
101
102    /// Reads an optional string, encoding three states: `None` when the buffer
103    /// is exhausted, `Some(None)` for an absent string, and `Some(Some(_))` for
104    /// a present string.
105    #[allow(clippy::option_option)]
106    pub const fn read_str_opt(&mut self) -> Option<Option<&'a str>> {
107        let Some(tag) = self.read_bytes(1) else {
108            return None;
109        };
110        match tag[0] {
111            0 => Some(None),
112            1 => match self.read_str() {
113                Some(s) => Some(Some(s)),
114                None => None,
115            },
116            _ => None,
117        }
118    }
119
120    pub const fn skip(&mut self, n: usize) -> Option<()> {
121        match self.read_bytes(n) {
122            Some(_) => Some(()),
123            None => None,
124        }
125    }
126}