ethercrab_wire/
impls.rs

1//! Builtin implementations for various types.
2
3use crate::{
4    EtherCrabWireRead, EtherCrabWireReadSized, EtherCrabWireSized, EtherCrabWireWrite,
5    EtherCrabWireWriteSized, WireError,
6};
7
8macro_rules! impl_primitive_wire_field {
9    ($ty:ty, $size:expr) => {
10        impl EtherCrabWireWrite for $ty {
11            fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
12                let Some(chunk) = buf.first_chunk_mut::<$size>() else {
13                    unreachable!()
14                };
15
16                *chunk = self.to_le_bytes();
17
18                chunk
19            }
20
21            fn pack_to_slice<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf [u8], WireError> {
22                let Some(chunk) = buf.first_chunk_mut::<$size>() else {
23                    return Err(WireError::WriteBufferTooShort);
24                };
25
26                *chunk = self.to_le_bytes();
27
28                Ok(chunk)
29            }
30
31            fn packed_len(&self) -> usize {
32                $size
33            }
34        }
35
36        impl EtherCrabWireRead for $ty {
37            fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
38                buf.first_chunk::<$size>()
39                    .ok_or(WireError::ReadBufferTooShort)
40                    .map(|chunk| Self::from_le_bytes(*chunk))
41            }
42        }
43
44        impl EtherCrabWireSized for $ty {
45            const PACKED_LEN: usize = $size;
46
47            type Buffer = [u8; $size];
48
49            fn buffer() -> Self::Buffer {
50                [0u8; $size]
51            }
52        }
53
54        impl EtherCrabWireWriteSized for $ty {
55            fn pack(&self) -> Self::Buffer {
56                self.to_le_bytes()
57            }
58        }
59
60        // MSRV: generic_const_exprs: Once we can do `N * T::PACKED_BYTES` this impl can go away and
61        // be replaced by a single generic one.
62        impl<const N: usize> EtherCrabWireSized for [$ty; N] {
63            const PACKED_LEN: usize = N * $size;
64
65            type Buffer = [u8; N];
66
67            fn buffer() -> Self::Buffer {
68                [0u8; N]
69            }
70        }
71    };
72}
73
74// Thank you `serde::Deserialize` :D
75macro_rules! impl_tuples {
76    ($($len:tt => ($($n:tt $name:ident)+))+) => {
77        $(
78            #[allow(non_snake_case)]
79            impl<$($name: EtherCrabWireReadSized),+> EtherCrabWireRead for ($($name,)+) {
80                #[allow(unused_assignments)]
81                fn unpack_from_slice(mut buf: &[u8]) -> Result<Self, WireError> {
82                    $(
83                        let $name = $name::unpack_from_slice(buf)?;
84
85                        if buf.len() > 0 {
86                            buf = &buf[$name::PACKED_LEN..];
87                        }
88                    )+
89
90                    Ok(($($name,)+))
91                }
92            }
93
94            #[allow(non_snake_case)]
95            impl<$($name: EtherCrabWireWrite),+> EtherCrabWireWrite for ($($name,)+) {
96                #[allow(unused_assignments)]
97                fn pack_to_slice_unchecked<'buf>(&self, orig: &'buf mut [u8]) -> &'buf [u8] {
98                    {
99                        let mut buf = &mut orig[..];
100
101                        $(
102                            let (chunk, rest) = buf.split_at_mut(self.$n.packed_len());
103                            let _packed = self.$n.pack_to_slice_unchecked(chunk);
104                            buf = rest;
105                        )+
106                    }
107
108                    &orig[0..self.packed_len()]
109                }
110
111                fn packed_len(&self) -> usize {
112                    0
113                    $(
114                        + self.$n.packed_len()
115                    )+
116                }
117            }
118        )+
119    }
120}
121
122impl_tuples! {
123    1  => (0 T0)
124    2  => (0 T0 1 T1)
125    3  => (0 T0 1 T1 2 T2)
126    4  => (0 T0 1 T1 2 T2 3 T3)
127    5  => (0 T0 1 T1 2 T2 3 T3 4 T4)
128    6  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5)
129    7  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6)
130    8  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7)
131    9  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8)
132    10 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9)
133    11 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10)
134    12 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11)
135    13 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12)
136    14 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13)
137    15 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14)
138    16 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15)
139}
140
141impl_primitive_wire_field!(u8, 1);
142impl_primitive_wire_field!(u16, 2);
143impl_primitive_wire_field!(u32, 4);
144impl_primitive_wire_field!(u64, 8);
145impl_primitive_wire_field!(i8, 1);
146impl_primitive_wire_field!(i16, 2);
147impl_primitive_wire_field!(i32, 4);
148impl_primitive_wire_field!(i64, 8);
149
150impl_primitive_wire_field!(f32, 4);
151impl_primitive_wire_field!(f64, 8);
152
153impl EtherCrabWireWrite for bool {
154    fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
155        buf[0] = if *self { 0xff } else { 0x00 };
156
157        &buf[0..1]
158    }
159
160    fn packed_len(&self) -> usize {
161        1
162    }
163}
164
165impl EtherCrabWireRead for bool {
166    fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
167        // NOTE: ETG1000.6 5.2.2 states the truthy value is 0xff and false is 0. We'll just check
168        // for greater than zero to be sure.
169        Ok(*buf.first().ok_or(WireError::ReadBufferTooShort)? > 0)
170    }
171}
172
173impl EtherCrabWireSized for bool {
174    const PACKED_LEN: usize = 1;
175
176    type Buffer = [u8; Self::PACKED_LEN];
177
178    fn buffer() -> Self::Buffer {
179        [0u8; 1]
180    }
181}
182
183impl EtherCrabWireWriteSized for bool {
184    fn pack(&self) -> Self::Buffer {
185        // NOTE: ETG1000.6 5.2.2 states the truthy value is 0xff and false is 0.
186        [if *self { 0xff } else { 0x00 }; 1]
187    }
188}
189
190impl EtherCrabWireWrite for () {
191    fn pack_to_slice_unchecked<'buf>(&self, _buf: &'buf mut [u8]) -> &'buf [u8] {
192        &[]
193    }
194
195    fn packed_len(&self) -> usize {
196        0
197    }
198}
199
200impl EtherCrabWireRead for () {
201    fn unpack_from_slice(_buf: &[u8]) -> Result<Self, WireError> {
202        Ok(())
203    }
204}
205
206impl EtherCrabWireSized for () {
207    const PACKED_LEN: usize = 0;
208
209    type Buffer = [u8; 0];
210
211    fn buffer() -> Self::Buffer {
212        [0u8; 0]
213    }
214}
215
216impl EtherCrabWireWriteSized for () {
217    fn pack(&self) -> Self::Buffer {
218        [0u8; 0]
219    }
220}
221
222impl<const N: usize> EtherCrabWireWrite for [u8; N] {
223    fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
224        let Some(chunk) = buf.first_chunk_mut::<N>() else {
225            unreachable!()
226        };
227
228        *chunk = *self;
229
230        chunk
231    }
232
233    fn packed_len(&self) -> usize {
234        N
235    }
236}
237
238impl EtherCrabWireWrite for &[u8] {
239    fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
240        let buf = &mut buf[0..self.len()];
241
242        buf.copy_from_slice(self);
243
244        buf
245    }
246
247    fn packed_len(&self) -> usize {
248        self.len()
249    }
250}
251
252// Blanket impl for references
253impl<T> EtherCrabWireWrite for &T
254where
255    T: EtherCrabWireWrite,
256{
257    fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
258        EtherCrabWireWrite::pack_to_slice_unchecked(*self, buf)
259    }
260
261    fn packed_len(&self) -> usize {
262        EtherCrabWireWrite::packed_len(*self)
263    }
264}
265
266// Blanket impl for arrays of known-sized types
267impl<const N: usize, T> EtherCrabWireRead for [T; N]
268where
269    T: EtherCrabWireReadSized,
270{
271    fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
272        buf.get(0..(T::PACKED_LEN * N))
273            .ok_or(WireError::ReadBufferTooShort)?
274            .chunks_exact(T::PACKED_LEN)
275            .take(N)
276            .map(T::unpack_from_slice)
277            .collect::<Result<heapless::Vec<_, N>, WireError>>()
278            .and_then(|res| res.into_array().map_err(|_e| WireError::ArrayLength))
279    }
280}
281
282// --- heapless::Vec ---
283
284impl<const N: usize, T> EtherCrabWireRead for heapless::Vec<T, N>
285where
286    T: EtherCrabWireReadSized,
287{
288    fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
289        buf.chunks_exact(T::PACKED_LEN)
290            .take(N)
291            .map(T::unpack_from_slice)
292            .collect::<Result<heapless::Vec<_, N>, WireError>>()
293    }
294}
295
296// MSRV: generic_const_exprs: When we can do `N * T::PACKED_LEN`, this specific impl for `u8` can be
297// replaced with `T: EtherCrabWireSized`.
298impl<const N: usize, T> EtherCrabWireSized for heapless::Vec<T, N>
299where
300    T: Into<u8>,
301{
302    const PACKED_LEN: usize = N;
303
304    type Buffer = [u8; N];
305
306    fn buffer() -> Self::Buffer {
307        [0u8; N]
308    }
309}
310
311// --- heapless::String ---
312
313impl<const N: usize> EtherCrabWireRead for heapless::String<N> {
314    fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
315        core::str::from_utf8(buf)
316            .map_err(|_| WireError::InvalidUtf8)
317            .and_then(|s| Self::try_from(s).map_err(|_| WireError::ArrayLength))
318    }
319}
320
321impl<const N: usize> EtherCrabWireSized for heapless::String<N> {
322    const PACKED_LEN: usize = N;
323
324    type Buffer = [u8; N];
325
326    fn buffer() -> Self::Buffer {
327        [0u8; N]
328    }
329}
330
331// --- std ---
332
333#[cfg(feature = "std")]
334impl EtherCrabWireRead for String {
335    fn unpack_from_slice(buf: &[u8]) -> Result<Self, WireError> {
336        core::str::from_utf8(buf)
337            .map_err(|_| WireError::InvalidUtf8)
338            .map(String::from)
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn bool_pack() {
348        assert_eq!(true.pack(), [0xff]);
349        assert_eq!(false.pack(), [0x00]);
350
351        let mut sl1 = [0u8; 8];
352        let mut sl2 = [0u8; 8];
353
354        assert_eq!(true.pack_to_slice_unchecked(&mut sl1), &[0xffu8]);
355        assert_eq!(false.pack_to_slice_unchecked(&mut sl2), &[0x00u8]);
356    }
357
358    #[test]
359    fn bool_unpack() {
360        assert_eq!(bool::unpack_from_slice(&[0xff]), Ok(true));
361        assert_eq!(bool::unpack_from_slice(&[0x00]), Ok(false));
362
363        // In case there are noncompliant subdevices
364        assert_eq!(bool::unpack_from_slice(&[0x01]), Ok(true));
365    }
366
367    #[test]
368    fn tuple_decode() {
369        let res = <(u32, u8)>::unpack_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd, 0x99]);
370
371        assert_eq!(
372            res,
373            Ok((u32::from_le_bytes([0xaa, 0xbb, 0xcc, 0xdd]), 0x99))
374        )
375    }
376
377    #[test]
378    fn tuple_encode() {
379        let mut buf = [0u8; 32];
380
381        let written = (0xaabbccddu32, 0x99u8, 0x1234u16).pack_to_slice_unchecked(&mut buf);
382
383        assert_eq!(written, &[0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x34, 0x12]);
384    }
385}