jvs_packets/
packet.rs

1use std::io::{self, Read, Write};
2/// SYNC byte indicates the beginning of the packet.
3///
4/// Readers should skip bytes until the SYNC byte is found.
5pub const SYNC_BYTE: u8 = 0xE0;
6
7/// MARK byte is used for escaping the [`SYNC_BYTE`] and [`MARK_BYTE`] bytes.
8///
9/// Since [`SYNC_BYTE`] is reserved for indicating the beginning of the packet,
10/// it is escaped in the actual data by prepending [`MARK_BYTE`] and subtracting one from the byte's value.
11///
12/// [`SYNC_BYTE`] and [`MARK_BYTE`] bytes are escaped as `D0 DF` and `D0 CF` respectively.
13/// Although any bytes can be escaped, only these 2 bytes required escaping.
14pub const MARK_BYTE: u8 = 0xD0;
15
16/// JVS response report codes.
17///
18/// When slave sending response to master, it will always contain a report code, which is placed before first DATA byte.
19///
20/// The Report byte indicates whether a request was completed successfully.
21///
22/// Check variants documentation if you need to know what which code does.
23#[derive(Debug, Clone)]
24pub enum Report {
25    /// Request was processed successfully.
26    Normal = 1,
27    /// Incorrect number of parameters were sent.
28    IncorrectDataSize = 2,
29    /// Incorrect data was sent
30    InvalidData = 3,
31    /// The device I/O is busy.
32    Busy = 4,
33    /// Unknown report code.
34    Unknown,
35}
36
37impl From<u8> for Report {
38    fn from(value: u8) -> Self {
39        match value {
40            1 => Report::Normal,
41            2 => Report::IncorrectDataSize,
42            3 => Report::InvalidData,
43            4 => Report::Busy,
44            _ => Report::Unknown,
45        }
46    }
47}
48
49/// A trait for all packets structures
50pub trait Packet: AsRef<[u8]> + AsMut<[u8]> {
51    const SIZE_INDEX: usize;
52    const DATA_BEGIN_INDEX: usize;
53    const DESTINATION_INDEX: usize;
54
55    fn len_of_packet(&self) -> usize {
56        Self::SIZE_INDEX + self.as_ref()[Self::SIZE_INDEX] as usize + 1
57    }
58
59    /// Returns a slice of the packet until SUM byte.
60    fn as_slice(&self) -> &[u8] {
61        &self.as_ref()[..self.len_of_packet()]
62    }
63
64    /// Returns a mutable slice of the packet until SUM byte.
65    fn as_mut_slice(&mut self) -> &mut [u8] {
66        let len = self.len_of_packet();
67        &mut self.as_mut()[..len]
68    }
69
70    /// Returns a first byte in the slice.
71    fn sync(&self) -> u8 {
72        self.as_ref()[0]
73    }
74
75    fn set_sync(&mut self) -> &mut Self {
76        self.as_mut()[0] = SYNC_BYTE;
77        self
78    }
79
80    /// Returns a SIZE byte at [`Packet::SIZE_INDEX`]
81    fn size(&self) -> u8 {
82        self.as_ref()[Self::SIZE_INDEX]
83    }
84
85    /// Sets a size byte at [`Packet::SIZE_INDEX`].
86    ///
87    /// Don't use this method unless you know what you're doing. Use [`Packet::set_data`] instead.
88    fn set_size(&mut self, size: u8) -> &mut Self {
89        self.as_mut()[Self::SIZE_INDEX] = size;
90        self
91    }
92
93    /// Returns a destination byte at [`Packet::DESTINATION_INDEX`].
94    fn dest(&self) -> u8 {
95        self.as_ref()[Self::DESTINATION_INDEX]
96    }
97
98    /// Sets a destination byte at [`Packet::DESTINATION_INDEX`] and calculates a new checksum.
99    fn set_dest(&mut self, dest: u8) -> &mut Self {
100        self.as_mut()[Self::DESTINATION_INDEX] = dest;
101        self
102    }
103
104    /// Returns a slice of the packet data.
105    fn data(&self) -> &[u8] {
106        &self.as_ref()[Self::DATA_BEGIN_INDEX..self.len_of_packet() - 1]
107    }
108
109    /// Sets the packet data.
110    ///
111    /// This method will also set the size byte and calculate a new checksum.
112    fn set_data(&mut self, data: &[u8]) -> &mut Self {
113        let size = data.len() + Self::DATA_BEGIN_INDEX;
114        self.as_mut()[Self::DATA_BEGIN_INDEX..size].copy_from_slice(data);
115        self.set_size((size - Self::SIZE_INDEX) as u8);
116        self
117    }
118
119    /// Calculates checksum.
120    ///
121    /// The checksum is calculated by summing all bytes in the packet except the [`SYNC_BYTE`].
122    fn calculate_checksum(&mut self) -> &mut Self {
123        self.set_checksum(
124            self.as_slice()
125                .iter()
126                .skip(1)
127                .take(self.len_of_packet() - 2)
128                .fold(0, |acc: u8, &x| acc.wrapping_add(x)),
129        );
130        self
131    }
132
133    /// Returns a checksum.
134    fn checksum(&self) -> u8 {
135        self.as_ref()[self.len_of_packet() - 1]
136    }
137
138    /// Sets a checksum in the end of the packet.
139    ///
140    /// Don't use this method unless you know what you're doing. Use [`Packet::calculate_checksum`] instead.
141    fn set_checksum(&mut self, checksum: u8) -> &mut Self {
142        let len = self.len_of_packet();
143        self.as_mut()[len - 1] = checksum;
144        self
145    }
146}
147
148/// A trait that adds additional setters for Response Packets.
149///
150/// All responses from jvs has report code that will indicate whether the request was processed successfully or not.
151pub trait ReportField: Packet {
152    const REPORT_INDEX: usize;
153
154    /// Returns a report code.
155    fn report(&self) -> Report {
156        self.as_ref()[Self::REPORT_INDEX].into()
157    }
158
159    fn report_raw(&self) -> u8 {
160        self.as_ref()[Self::REPORT_INDEX]
161    }
162
163    /// Sets a report code.
164    fn set_report(&mut self, report: impl Into<u8>) -> &mut Self {
165        self.as_mut()[Self::REPORT_INDEX] = report.into();
166        self
167    }
168}
169
170/// Additional methods for [`std::io::Read`] trait to read a single (escaped) byte.
171pub trait ReadByteExt: Read {
172    /// Reads a single byte.
173    fn read_u8(&mut self) -> io::Result<u8> {
174        let mut buf = [0; 1];
175        self.read_exact(&mut buf)?;
176        Ok(buf[0])
177    }
178
179    /// Check if the first byte is [`MARK_BYTE`] and if it is, it will read the next byte and add one to it.
180    fn read_u8_escaped(&mut self) -> io::Result<u8> {
181        let mut b = self.read_u8()?;
182        if b == MARK_BYTE {
183            b = self.read_u8()?.wrapping_add(1);
184        }
185        Ok(b)
186    }
187}
188
189impl<R: Read + ?Sized> ReadByteExt for R {}
190
191/// Additional methods for [`std::io::Write`] trait to write a single byte.
192pub trait WriteByteExt: Write {
193    /// Writes a single byte to the writer.
194    fn write_u8(&mut self, b: u8) -> io::Result<()> {
195        self.write_all(&[b])
196    }
197
198    /// Writes [`MARK_BYTE`] and [`b`] if [`b`] equals to either [`MARK_BYTE`] or [`SYNC_BYTE`],
199    /// otherwise only [`b`] is written.
200    fn write_u8_escaped(&mut self, b: u8) -> io::Result<usize> {
201        if b == SYNC_BYTE || b == MARK_BYTE {
202            self.write_all(&[MARK_BYTE, b.wrapping_sub(1)])?;
203            Ok(2)
204        } else {
205            self.write_all(&[b])?;
206            Ok(1)
207        }
208    }
209}
210
211impl<W: Write + ?Sized> WriteByteExt for W {}
212
213/// A helper trait which implemented for [`std::io::Read`]. Contains methods for reading [`Packet`]s from the Reader.
214///
215/// It is better to use [`std::io::BufReader`] to avoid unnecessary syscalls, since we have to read one byte at a time to check for [`MARK_BYTE`].
216pub trait ReadPacket: Read {
217    fn read_packet<P: Packet>(&mut self, packet: &mut P) -> io::Result<u8> {
218        let sync = self.read_u8()?;
219
220        if sync != SYNC_BYTE {
221            return Err(io::Error::new(
222                io::ErrorKind::InvalidData,
223                format!("Expected SYNC byte (0xE0), found: {:#04x}", sync),
224            ));
225        }
226        let buf = packet.as_mut();
227        buf[0] = sync;
228
229        // Read to the SIZE byte first
230        for b in &mut buf[1..=P::SIZE_INDEX] {
231            *b = self.read_u8_escaped()?;
232        }
233
234        let len = buf[P::SIZE_INDEX] as usize + P::SIZE_INDEX;
235
236        for b in &mut buf[P::SIZE_INDEX + 1..=len] {
237            *b = self.read_u8_escaped()?;
238        }
239
240        Ok(packet.len_of_packet() as u8)
241    }
242}
243
244impl<R: Read + ?Sized> ReadPacket for R {}
245
246/// A helper trait which implemented for [`std::io::Write`]. Contains methods for writing [`Packet`]s to the Writer.
247///
248/// It is better to use [`std::io::BufWriter`] to avoid unnecessary syscalls, since we have to read one byte at a time to check for escaped by [`MARK_BYTE`] bytes.
249pub trait WritePacket: Write {
250    /// Writes a packet to the Writer.
251    ///
252    /// The function doesn't calculate checksum and instead writes whatever is present in the packet itself. So you have to use [`Packet::calculate_checksum`] before writing.
253    /// Use [`Self::write_packet`] to calculate checksum while writing bytes.
254    ///
255    /// # Errors
256    /// Will return [`Err`] if [`Packet::len_of_packet`] less than [`Packet::DATA_BEGIN_INDEX`] + 1 which is nonsense.
257    fn write_packet_unchecked<P: Packet>(&mut self, packet: &P) -> io::Result<usize> {
258        if packet.len_of_packet() < P::DATA_BEGIN_INDEX + 1 {
259            return Err(io::Error::new(
260                io::ErrorKind::InvalidInput,
261                format!(
262                    "The size of packet is can't be less than {}",
263                    P::DATA_BEGIN_INDEX + 1
264                ),
265            ));
266        }
267        let mut bytes_written = 1;
268
269        self.write_u8(SYNC_BYTE)?;
270
271        for &b in &packet.as_slice()[1..] {
272            bytes_written += self.write_u8_escaped(b)?;
273        }
274
275        Ok(bytes_written)
276    }
277
278    /// Similar to [`WritePacket::write_packet_unchecked`], but it will calculate checksum while writing bytes to the writer.
279    ///
280    /// # Errors
281    /// Will return [`Err`] if [`Packet::len_of_packet`] less than [`Packet::DATA_BEGIN_INDEX`] + 1 which is nonsense.
282    fn write_packet<P: Packet>(&mut self, packet: &P) -> io::Result<usize> {
283        if packet.len_of_packet() < P::DATA_BEGIN_INDEX + 1 {
284            return Err(io::Error::new(
285                io::ErrorKind::InvalidInput,
286                format!(
287                    "The size of packet is can't be less than {}",
288                    P::DATA_BEGIN_INDEX + 1
289                ),
290            ));
291        }
292
293        self.write_u8(SYNC_BYTE)?;
294        let mut bytes_written: usize = 2;
295        let mut checksum: u8 = 0;
296        for &b in &packet.as_slice()[1..packet.len_of_packet() - 1] {
297            bytes_written += self.write_u8_escaped(b)?;
298            checksum = checksum.wrapping_add(b);
299        }
300
301        self.write_u8_escaped(checksum)?;
302
303        Ok(bytes_written)
304    }
305
306    #[deprecated(since = "1.1.0")]
307    fn write_packet_with_checksum<P: Packet>(&mut self, packet: &P) -> io::Result<usize> {
308        self.write_packet(packet)
309    }
310}
311
312impl<W: Write + ?Sized> WritePacket for W {}