1use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned, little_endian};
2use zerocopy_derive::*;
3
4use crate::{
5 Pdu,
6 pdu::{CrcError, Response, ValidationError},
7};
8
9const MODBUS_CRC: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_MODBUS);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Unaligned, Immutable, FromBytes)]
13#[repr(C)]
14pub struct Frame<T> {
15 unit_id: u8,
16 pdu: T,
17 crc: little_endian::U16,
18}
19
20impl<T> Frame<T> {
21 pub fn new(unit_id: u8, pdu: T) -> Self
24 where
25 T: Pdu,
26 {
27 let mut frame = Self::without_crc(unit_id, pdu);
28 frame.update_crc();
29 frame
30 }
31
32 const fn without_crc(unit_id: u8, pdu: T) -> Self {
33 Frame {
34 unit_id,
35 pdu,
36 crc: little_endian::U16::ZERO,
37 }
38 }
39
40 pub const fn builder(unit_id: u8) -> FrameBuilder<T>
42 where
43 T: Pdu,
44 {
45 FrameBuilder::new(unit_id)
46 }
47
48 fn calculate_crc(&self) -> u16
49 where
50 T: IntoBytes + Unaligned + Immutable,
51 {
52 let bytes = self.as_bytes();
53 MODBUS_CRC.checksum(&bytes[..bytes.len() - 2])
55 }
56
57 fn update_crc(&mut self)
58 where
59 T: IntoBytes + Unaligned + Immutable,
60 {
61 self.crc = self.calculate_crc().into();
62 }
63
64 pub fn into_data<Request>(self, request: &Frame<Request>) -> Result<T::Data, ValidationError>
66 where
67 T: Response<Request>,
68 {
69 if self.calculate_crc() != self.crc.get() {
70 return Err(ValidationError::Crc(CrcError));
71 }
72
73 if self.unit_id != request.unit_id {
74 return Err(ValidationError::UnexpectedResponse);
75 }
76
77 if !self.pdu.matches_request(&request.pdu) {
78 return Err(ValidationError::UnexpectedResponse);
79 }
80
81 Ok(self.pdu.into_data())
82 }
83
84 pub fn view(&self) -> FrameView<'_>
86 where
87 T: IntoBytes + Unaligned + Immutable,
88 {
89 FrameView {
90 buf: self.as_bytes(),
91 }
92 }
93}
94
95#[derive(Debug)]
97pub struct FrameBuilder<T> {
98 inner: Frame<T>,
99}
100
101impl<T: Pdu> FrameBuilder<T> {
102 pub const fn with_pdu(unit_id: u8, pdu: T) -> Self {
107 Self {
108 inner: Frame::without_crc(unit_id, pdu),
110 }
111 }
112
113 pub const fn new(unit_id: u8) -> Self {
115 Self::with_pdu(unit_id, T::DEFAULT)
116 }
117
118 pub const fn set_unit_id(&mut self, unit_id: u8) {
120 self.inner.unit_id = unit_id;
121 }
122
123 pub fn build_ref(&mut self) -> &mut Frame<T> {
126 self.inner.update_crc();
127 &mut self.inner
128 }
129
130 pub fn build(mut self) -> Frame<T> {
132 self.inner.update_crc();
133 self.inner
134 }
135
136 pub fn pdu_mut(&mut self) -> &mut T {
138 &mut self.inner.pdu
139 }
140}
141
142impl<T: Pdu> Default for FrameBuilder<T> {
143 fn default() -> Self {
144 Self::new(0)
145 }
146}
147
148pub struct FrameView<'a> {
159 buf: &'a [u8],
160}
161
162impl<'a> FrameView<'a> {
163 pub fn try_from_bytes(buf: &'a [u8]) -> Option<Self> {
166 if (4..=256).contains(&buf.len()) {
167 Some(Self { buf })
168 } else {
169 None
170 }
171 }
172
173 pub const fn unit_id(&self) -> u8 {
175 self.buf[0]
176 }
177
178 pub const fn crc(&self) -> u16 {
181 u16::from_le_bytes([self.buf[self.buf.len() - 2], self.buf[self.buf.len() - 1]])
182 }
183
184 fn calculate_crc(&self) -> u16 {
185 MODBUS_CRC.checksum(&self.buf[..self.buf.len() - 2])
186 }
187
188 pub fn pdu(self) -> Result<&'a PduView, CrcError> {
190 self.validate_crc()?;
191 Ok(
192 PduView::ref_from_bytes(&self.buf[1..self.buf.len() - 2])
193 .expect("PduView is Unaligned"),
194 )
195 }
196
197 pub fn validate_crc(&self) -> Result<(), CrcError> {
203 if self.calculate_crc() == self.crc() {
204 Ok(())
205 } else {
206 Err(CrcError)
207 }
208 }
209}
210
211#[derive(Debug, FromBytes, KnownLayout, Immutable, IntoBytes, Unaligned)]
213#[repr(C)]
214pub struct PduView {
215 pub function_code: u8,
217 pub data: [u8],
219}
220
221impl PduView {
222 #[inline]
240 pub fn parse<T: Pdu>(&self) -> Option<&T> {
241 if self.function_code == T::FUNCTION_CODE {
242 T::try_ref_from_bytes(self.as_bytes()).ok()
243 } else {
244 None
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use hex_literal::hex;
252
253 use super::*;
254
255 #[test]
256 fn test_length_validation() {
257 assert!(FrameView::try_from_bytes(&hex!()).is_none());
258 assert!(FrameView::try_from_bytes(&hex!("00 00 00")).is_none());
259 assert!(FrameView::try_from_bytes(&hex!("00 00 00 00")).is_some());
260 assert!(FrameView::try_from_bytes(&[0; 256]).is_some());
261 assert!(FrameView::try_from_bytes(&[0; 257]).is_none());
262 }
263}