Krolyxon
Commit 8ec1f82

Branches

Refs →

budctlcratesbudctl-coresrcreader.rs

95 lines · 2.8 KB · Rust

1//! A cursor over bytes from the buds. Every read returns a `Result`, so a short or malformed
2//! message becomes an [`Error::Protocol`] instead of a panic.
3
4use crate::error::{Error, Result};
5
6pub(crate) struct Reader<'a> {
7 bytes: &'a [u8],
8 /// What is being parsed, for error messages ("battery reply", "SDP response"…).
9 what: &'static str,
10}
11
12impl<'a> Reader<'a> {
13 pub(crate) fn new(bytes: &'a [u8], what: &'static str) -> Self {
14 Self { bytes, what }
15 }
16
17 pub(crate) fn u8(&mut self) -> Result<u8> {
18 let [byte] = self.array::<1>()?;
19 Ok(byte)
20 }
21
22 pub(crate) fn i8(&mut self) -> Result<i8> {
23 let [byte] = self.array::<1>()?;
24 Ok(i8::from_le_bytes([byte]))
25 }
26
27 pub(crate) fn u16_le(&mut self) -> Result<u16> {
28 Ok(u16::from_le_bytes(self.array()?))
29 }
30
31 /// Only SDP, which is Linux-only, reads big-endian numbers so far.
32 #[cfg(target_os = "linux")]
33 pub(crate) fn u16_be(&mut self) -> Result<u16> {
34 Ok(u16::from_be_bytes(self.array()?))
35 }
36
37 #[cfg(target_os = "linux")]
38 pub(crate) fn u32_be(&mut self) -> Result<u32> {
39 Ok(u32::from_be_bytes(self.array()?))
40 }
41
42 /// The next `n` bytes.
43 pub(crate) fn take(&mut self, n: usize) -> Result<&'a [u8]> {
44 let (head, tail) = self.bytes.split_at_checked(n).ok_or_else(|| self.too_short())?;
45 self.bytes = tail;
46 Ok(head)
47 }
48
49 /// Everything not read yet.
50 pub(crate) fn rest(&mut self) -> &'a [u8] {
51 std::mem::take(&mut self.bytes)
52 }
53
54 /// Only SDP checks that it read everything so far.
55 #[cfg(target_os = "linux")]
56 pub(crate) fn is_empty(&self) -> bool {
57 self.bytes.is_empty()
58 }
59
60 /// The next `N` bytes, as an array.
61 pub(crate) fn array<const N: usize>(&mut self) -> Result<[u8; N]> {
62 let (head, tail) = self.bytes.split_first_chunk::<N>().ok_or_else(|| self.too_short())?;
63 self.bytes = tail;
64 Ok(*head)
65 }
66
67 fn too_short(&self) -> Error {
68 Error::Protocol(format!("{} is too short", self.what))
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn reads_in_order_and_reports_short_input() {
78 let mut r = Reader::new(&[0x01, 0x34, 0x12, 0x12, 0x34, 0xFF], "test");
79 assert_eq!(r.u8().unwrap(), 0x01);
80 assert_eq!(r.u16_le().unwrap(), 0x1234);
81 assert_eq!(r.u16_be().unwrap(), 0x1234);
82 assert_eq!(r.i8().unwrap(), -1);
83 assert!(r.is_empty());
84 assert!(matches!(r.u8(), Err(Error::Protocol(_))));
85 }
86
87 #[test]
88 fn take_and_rest() {
89 let mut r = Reader::new(&[1, 2, 3, 4], "test");
90 assert_eq!(r.take(2).unwrap(), &[1, 2]);
91 assert!(r.take(3).is_err());
92 assert_eq!(r.rest(), &[3, 4]);
93 assert_eq!(r.rest(), &[] as &[u8]);
94 }
95}