modbus_core/frame/
data.rs

1// SPDX-FileCopyrightText: Copyright (c) 2018-2025 slowtec GmbH <post@slowtec.de>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::*;
5use crate::error::*;
6
7/// Modbus data (u16 values)
8#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Data<'d> {
11    pub(crate) data: RawData<'d>,
12    pub(crate) quantity: usize,
13}
14
15impl<'d> Data<'d> {
16    /// Pack words (u16 values) into a byte buffer.
17    pub fn from_words(words: &[u16], target: &'d mut [u8]) -> Result<Self, Error> {
18        if (words.len() * 2 > target.len()) || words.is_empty() {
19            return Err(Error::BufferSize);
20        }
21        for (i, w) in words.iter().enumerate() {
22            BigEndian::write_u16(&mut target[i * 2..], *w);
23        }
24        Ok(Data {
25            data: target,
26            quantity: words.len(),
27        })
28    }
29    //TODO: add tests
30    pub(crate) fn copy_to(&self, buf: &mut [u8]) {
31        let cnt = self.quantity * 2;
32        debug_assert!(buf.len() >= cnt);
33        (0..cnt).for_each(|idx| {
34            buf[idx] = self.data[idx];
35        });
36    }
37    /// Quantity of words (u16 values)
38    #[must_use]
39    pub const fn len(&self) -> usize {
40        self.quantity
41    }
42    ///  Returns `true` if the container has no items.
43    #[must_use]
44    pub const fn is_empty(&self) -> bool {
45        self.quantity == 0
46    }
47    /// Get a specific word.
48    #[must_use]
49    pub fn get(&self, idx: usize) -> Option<Word> {
50        if idx + 1 > self.quantity {
51            return None;
52        }
53        let idx = idx * 2;
54        Some(BigEndian::read_u16(&self.data[idx..idx + 2]))
55    }
56
57    #[must_use]
58    pub const fn payload(&self) -> &[u8] {
59        self.data
60    }
61}
62
63/// The buffer has an invalid size (must be a non null multiple of 2).
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct DataFromBufferError;
66
67impl<'buffer> TryFrom<&'buffer [u8]> for Data<'buffer> {
68    type Error = DataFromBufferError;
69
70    fn try_from(value: &'buffer [u8]) -> Result<Self, Self::Error> {
71        if value.is_empty() || value.len() % 2 != 0 {
72            Err(DataFromBufferError)
73        } else {
74            Ok(Self {
75                data: value,
76                quantity: value.len() / 2,
77            })
78        }
79    }
80}
81
82macro_rules! derive_from_for_data {
83    ($($buffer_length: literal)+) => {
84       $(
85            impl<'buffer> From<&'buffer [u8; $buffer_length]> for Data<'buffer> {
86                fn from(value: &'buffer [u8; $buffer_length]) -> Self {
87                    Self {
88                        data: value,
89                        quantity: $buffer_length / 2,
90                    }
91                }
92            }
93       )+
94    };
95}
96derive_from_for_data!(2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32);
97
98/// Data iterator
99// TODO: crate a generic iterator
100#[cfg_attr(all(feature = "defmt", target_os = "none"), derive(defmt::Format))]
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct DataIter<'d> {
103    cnt: usize,
104    data: Data<'d>,
105}
106
107impl Iterator for DataIter<'_> {
108    type Item = Word;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        let result = self.data.get(self.cnt);
112        self.cnt += 1;
113        result
114    }
115}
116
117impl<'d> IntoIterator for Data<'d> {
118    type Item = Word;
119    type IntoIter = DataIter<'d>;
120
121    fn into_iter(self) -> Self::IntoIter {
122        DataIter { cnt: 0, data: self }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128
129    use super::*;
130
131    #[test]
132    fn from_word_slice() {
133        let words: &[u16] = &[0xABCD, 0xEF00, 0x1234];
134        let buff: &mut [u8] = &mut [0; 5];
135        assert!(Data::from_words(words, buff).is_err());
136        let buff: &mut [u8] = &mut [0; 6];
137        let data = Data::from_words(words, buff).unwrap();
138        assert_eq!(data.len(), 3);
139        let mut iter = data.into_iter();
140        assert_eq!(iter.next(), Some(0xABCD));
141        assert_eq!(iter.next(), Some(0xEF00));
142        assert_eq!(iter.next(), Some(0x1234));
143        assert_eq!(iter.next(), None);
144    }
145
146    #[test]
147    fn data_len() {
148        let data = Data {
149            data: &[0, 1, 2],
150            quantity: 5,
151        };
152        assert_eq!(data.len(), 5);
153    }
154
155    #[test]
156    fn data_empty() {
157        let data = Data {
158            data: &[0, 1, 2],
159            quantity: 0,
160        };
161        assert!(data.is_empty());
162    }
163
164    #[test]
165    fn data_get() {
166        let data = Data {
167            data: &[0xAB, 0xBC, 0x12],
168            quantity: 1,
169        };
170        assert_eq!(data.get(0), Some(0xABBC));
171        assert_eq!(data.get(1), None);
172
173        let data = Data {
174            data: &[0xFF, 0xAB, 0xCD, 0xEF, 0x33],
175            quantity: 2,
176        };
177        assert_eq!(data.get(0), Some(0xFFAB));
178        assert_eq!(data.get(1), Some(0xCDEF));
179        assert_eq!(data.get(2), None);
180    }
181
182    #[test]
183    fn data_iter() {
184        let data = Data {
185            data: &[0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB],
186            quantity: 3,
187        };
188        let mut data_iter = DataIter { cnt: 0, data };
189        assert_eq!(data_iter.next(), Some(0x0102));
190        assert_eq!(data_iter.next(), Some(0x0304));
191        assert_eq!(data_iter.next(), Some(0xAABB));
192        assert_eq!(data_iter.next(), None);
193    }
194
195    #[test]
196    fn data_into_iter() {
197        let data = Data {
198            data: &[0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB],
199            quantity: 3,
200        };
201        let mut data_iter = data.into_iter();
202        assert!(data_iter.next().is_some());
203        assert!(data_iter.next().is_some());
204        assert!(data_iter.next().is_some());
205        assert!(data_iter.next().is_none());
206    }
207
208    #[test]
209    fn data_try_from() {
210        assert_eq!(
211            Data::try_from(&[] as &[u8]),
212            Err(DataFromBufferError),
213            "Data from empty buffer is not allowed"
214        );
215        assert_eq!(
216            Data::try_from(&[0u8] as &[u8]),
217            Err(DataFromBufferError),
218            "Data from buffer with length that is not a multiple of 2 is not allowed"
219        );
220        assert_eq!(
221            Data::try_from(&[0u8, 1, 2] as &[u8]),
222            Err(DataFromBufferError),
223            "Data from buffer with length that is not a multiple of 2 is not allowed"
224        );
225        assert_eq!(
226            Data::try_from(&[0u8, 1] as &[u8]),
227            Ok(Data {
228                data: &[0, 1],
229                quantity: 1
230            }),
231            "Data from buffer with even length must succeed"
232        );
233        assert_eq!(
234            Data::try_from(&0x1234_5678_u64.to_be_bytes() as &[u8]),
235            Ok(Data {
236                data: &0x1234_5678_u64.to_be_bytes(),
237                quantity: 4
238            }),
239            "Data from buffer with even length must succeed"
240        );
241    }
242
243    #[test]
244    fn data_from() {
245        assert_eq!(
246            Data::from(&[0u8, 1]),
247            Data {
248                data: &[0, 1],
249                quantity: 1
250            },
251            "Data from buffer with even length must succeed"
252        );
253        assert_eq!(
254            Data::from(&0x1234_5678_u64.to_be_bytes()),
255            Data {
256                data: &0x1234_5678_u64.to_be_bytes(),
257                quantity: 4
258            },
259            "Data from buffer with even length must succeed"
260        );
261    }
262}