rmodem/packet/
xmodem_one_k.rs

1use core::iter::Chain;
2
3use crate::{Control, Crc16, Error, OneKData, Result, Sequence};
4
5/// Represents an `XMODEM-1K` (compatible) packet.
6#[repr(C)]
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct XmodemOneKPacket {
9    start: Control,
10    seq: Sequence,
11    cmpl_seq: Sequence,
12    data: OneKData,
13    csum: Crc16,
14}
15
16impl XmodemOneKPacket {
17    const START: usize = 0;
18    const SEQ: usize = 1;
19    const CMPL_SEQ: usize = 2;
20    const DATA_START: usize = 3;
21    const DATA_END: usize = 3 + OneKData::LEN;
22    const CSUM: usize = 1027;
23
24    const PRELUDE_LEN: usize = 3;
25
26    /// Represents the full byte length of an [XmodemOneKPacket].
27    pub const LEN: usize = Self::CSUM + Crc16::LEN;
28
29    /// Creates a new [XmodemOneKPacket].
30    pub const fn new() -> Self {
31        Self {
32            start: Control::Stx,
33            seq: Sequence::new(),
34            cmpl_seq: Sequence::new().complement(),
35            data: OneKData::new(),
36            csum: Crc16::new(),
37        }
38    }
39
40    /// Gets the start [Control] byte.
41    pub const fn start(&self) -> Control {
42        self.start
43    }
44
45    /// Gets the [Sequence] number.
46    pub const fn sequence(&self) -> Sequence {
47        self.seq
48    }
49
50    /// Sets the [Sequence], and complement [Sequence] numbers.
51    pub fn set_sequence(&mut self, seq: Sequence) {
52        self.seq = seq;
53        self.cmpl_seq = seq.complement();
54    }
55
56    /// Builder function that sets the [Sequence], and complement [Sequence] numbers.
57    pub const fn with_sequence(self, seq: Sequence) -> Self {
58        Self {
59            start: self.start,
60            seq,
61            cmpl_seq: seq.complement(),
62            data: self.data,
63            csum: self.csum,
64        }
65    }
66
67    /// Gets the complement [Sequence] number.
68    pub const fn complement_sequence(&self) -> Sequence {
69        self.cmpl_seq
70    }
71
72    /// Gets a reference to the [OneKData].
73    pub const fn data(&self) -> &OneKData {
74        &self.data
75    }
76
77    /// Sets the [OneKData], and calculates the [Checksum](Crc16).
78    pub fn set_data(&mut self, data: OneKData) {
79        self.csum = Crc16::calculate(data.as_ref());
80        self.data = data;
81    }
82
83    /// Builder function that sets the [OneKData], and calculates the [Checksum](Crc16).
84    pub const fn with_data(self, data: OneKData) -> Self {
85        let csum = Crc16::calculate(data.inner());
86
87        Self {
88            start: self.start,
89            seq: self.seq,
90            cmpl_seq: self.cmpl_seq,
91            data,
92            csum,
93        }
94    }
95
96    /// Gets the [Checksum](Crc16).
97    pub const fn checksum(&self) -> Crc16 {
98        self.csum
99    }
100
101    /// Validates the invariants of the [XmodemOneKPacket] format.
102    ///
103    /// Consumes and returns the [XmodemOneKPacket], if valid.
104    ///
105    /// Returns [Error] otherwise.
106    pub const fn validate(self) -> Result<Self> {
107        let cmpl_seq = self.seq.complement();
108        let csum = Crc16::calculate(self.data.inner());
109
110        if !matches!(self.start, Control::Stx) {
111            Err(Error::InvalidStart((
112                self.start.into_u8(),
113                Control::Stx.into_u8(),
114            )))
115        } else if self.cmpl_seq.into_u8() != cmpl_seq.into_u8() {
116            Err(Error::InvalidComplementSequence((
117                self.cmpl_seq.into_u8(),
118                cmpl_seq.into_u8(),
119            )))
120        } else if self.csum.into_u16() != csum.into_u16() {
121            Err(Error::InvalidCrc16((self.csum.into_u16(), csum.into_u16())))
122        } else {
123            Ok(self)
124        }
125    }
126
127    /// Infallible conversion from [XmodemOneKPacket] into bytes.
128    ///
129    /// **NOTE**: does not validate [XmodemOneKPacket] before conversion. For the validating version, use
130    /// [XmodemOneKPacket::try_into](TryFrom<XmodemOneKPacket>).
131    pub fn into_bytes(self) -> [u8; Self::LEN] {
132        let mut out = [0u8; Self::LEN];
133
134        out.iter_mut().zip(self).for_each(|(dst, src)| *dst = src);
135
136        out
137    }
138}
139
140impl Default for XmodemOneKPacket {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146// Convenience alias for the [XmodemOneKPacket] prelude `IntoIter`.
147type XmodemOneKPacketPreludeIntoIter = core::array::IntoIter<u8, { XmodemOneKPacket::PRELUDE_LEN }>;
148// Convenience alias for the [XmodemOneKPacket] data `IntoIter`.
149type XmodemOneKPacketDataIntoIter = core::array::IntoIter<u8, { OneKData::LEN }>;
150// Convenience alias for the [XmodemOneKPacket] checksum `IntoIter`.
151type XmodemOneKPacketCsumIntoIter = core::array::IntoIter<u8, { Crc16::LEN }>;
152
153// Convenience alias of the [XmodemOneKPacket] [IntoIterator::IntoIter] associated type.
154type XmodemOneKPacketIntoIter = Chain<
155    Chain<XmodemOneKPacketPreludeIntoIter, XmodemOneKPacketDataIntoIter>,
156    XmodemOneKPacketCsumIntoIter,
157>;
158
159impl IntoIterator for XmodemOneKPacket {
160    type Item = u8;
161    type IntoIter = XmodemOneKPacketIntoIter;
162
163    fn into_iter(self) -> Self::IntoIter {
164        [
165            self.start.into_u8(),
166            self.seq.into_u8(),
167            self.cmpl_seq.into_u8(),
168        ]
169        .into_iter()
170        .chain(self.data)
171        .chain(self.csum)
172    }
173}
174
175impl TryFrom<&[u8]> for XmodemOneKPacket {
176    type Error = Error;
177
178    fn try_from(val: &[u8]) -> Result<Self> {
179        Self {
180            start: Control::try_from(
181                val.first()
182                    .copied()
183                    .ok_or(Error::InvalidPacketLen((Self::START, Self::LEN)))?,
184            )?,
185            seq: Sequence::from(
186                val.get(Self::SEQ)
187                    .copied()
188                    .ok_or(Error::InvalidPacketLen((Self::SEQ, Self::LEN)))?,
189            ),
190            cmpl_seq: Sequence::from(
191                val.get(Self::CMPL_SEQ)
192                    .copied()
193                    .ok_or(Error::InvalidPacketLen((Self::CMPL_SEQ, Self::LEN)))?,
194            ),
195            data: OneKData::try_from(
196                val.get(Self::DATA_START..Self::DATA_END)
197                    .ok_or(Error::InvalidPacketLen((val.len(), Self::LEN)))?,
198            )?,
199            csum: Crc16::try_from(
200                val.get(Self::CSUM..Self::LEN)
201                    .ok_or(Error::InvalidPacketLen((val.len(), Self::LEN)))?,
202            )?,
203        }
204        .validate()
205    }
206}
207
208impl<const N: usize> TryFrom<&[u8; N]> for XmodemOneKPacket {
209    type Error = Error;
210
211    fn try_from(val: &[u8; N]) -> Result<Self> {
212        val.as_ref().try_into()
213    }
214}
215
216impl<const N: usize> TryFrom<[u8; N]> for XmodemOneKPacket {
217    type Error = Error;
218
219    fn try_from(val: [u8; N]) -> Result<Self> {
220        val.as_ref().try_into()
221    }
222}
223
224impl TryFrom<XmodemOneKPacket> for [u8; XmodemOneKPacket::LEN] {
225    type Error = Error;
226
227    fn try_from(val: XmodemOneKPacket) -> Result<Self> {
228        Ok(val.validate()?.into_bytes())
229    }
230}
231
232impl TryFrom<&XmodemOneKPacket> for [u8; XmodemOneKPacket::LEN] {
233    type Error = Error;
234
235    fn try_from(val: &XmodemOneKPacket) -> Result<Self> {
236        (*val).try_into()
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_packet() {
246        let start = Control::Stx;
247        let seq = Sequence::new();
248        let cmpl_seq = seq.complement();
249        let data = OneKData::from_block([0x1a; OneKData::LEN].as_ref());
250        let csum = Crc16::calculate(data.as_ref());
251
252        let mut raw = [0u8; XmodemOneKPacket::LEN];
253        raw.iter_mut()
254            .zip(
255                [start.into_u8(), seq.into_u8(), cmpl_seq.into_u8()]
256                    .into_iter()
257                    .chain(data)
258                    .chain(csum.into_bytes()),
259            )
260            .for_each(|(dst, src)| *dst = src);
261
262        let exp = XmodemOneKPacket::new().with_sequence(seq).with_data(data);
263
264        assert_eq!(XmodemOneKPacket::try_from(raw), Ok(exp));
265
266        assert_eq!(exp.start(), start);
267        assert_eq!(exp.sequence(), seq);
268        assert_eq!(exp.complement_sequence(), cmpl_seq);
269        assert_eq!(exp.data(), &data);
270        assert_eq!(exp.checksum(), csum);
271    }
272
273    #[test]
274    fn test_packet_invalid() {
275        let packet = XmodemOneKPacket::new();
276
277        let mut raw = packet.into_bytes();
278
279        let stx = packet.start().into_u8();
280
281        // test invalid packet lengths
282        (0..XmodemOneKPacket::LEN).for_each(|invalid| {
283            assert_eq!(
284                XmodemOneKPacket::try_from(raw[..invalid].as_ref()),
285                Err(Error::InvalidPacketLen((invalid, XmodemOneKPacket::LEN)))
286            );
287        });
288
289        // test invalid start control byte
290        (0..=u8::MAX).filter(|r| r != &stx).for_each(|invalid| {
291            raw[XmodemOneKPacket::START] = invalid;
292
293            assert!(match XmodemOneKPacket::try_from(raw.as_ref()) {
294                Err(Error::InvalidControl(i)) => i == invalid,
295                Err(Error::InvalidStart((i, s))) => i == invalid && s == stx,
296                _ => false,
297            });
298        });
299
300        raw[XmodemOneKPacket::START] = stx;
301
302        let cmpl_seq = packet.complement_sequence().into_u8();
303
304        // test invalid complement sequence byte
305        (0..=u8::MAX)
306            .filter(|r| r != &cmpl_seq)
307            .for_each(|invalid| {
308                raw[XmodemOneKPacket::CMPL_SEQ] = invalid;
309
310                assert_eq!(
311                    XmodemOneKPacket::try_from(raw.as_ref()),
312                    Err(Error::InvalidComplementSequence((invalid, cmpl_seq)))
313                );
314            });
315
316        raw[XmodemOneKPacket::CMPL_SEQ] = cmpl_seq;
317
318        let csum = Crc16::calculate(
319            raw[XmodemOneKPacket::DATA_START..XmodemOneKPacket::DATA_END].as_ref(),
320        )
321        .into_u16();
322
323        assert_eq!(
324            raw.get(XmodemOneKPacket::CSUM..XmodemOneKPacket::LEN),
325            Some(csum.to_be_bytes().as_ref())
326        );
327
328        // test invalid checksum
329        (0..=u16::MAX).filter(|c| c != &csum).for_each(|invalid| {
330            raw[XmodemOneKPacket::CSUM..].copy_from_slice(invalid.to_be_bytes().as_ref());
331
332            assert_eq!(
333                XmodemOneKPacket::try_from(raw.as_ref()),
334                Err(Error::InvalidCrc16((invalid, csum)))
335            );
336        });
337    }
338}