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(&self) -> &T {
138 &self.inner.pdu
139 }
140
141 pub fn pdu_mut(&mut self) -> &mut T {
143 &mut self.inner.pdu
144 }
145}
146
147impl<T: Pdu> Default for FrameBuilder<T> {
148 fn default() -> Self {
149 Self::new(0)
150 }
151}
152
153pub struct FrameView<'a> {
164 buf: &'a [u8],
165}
166
167impl<'a> FrameView<'a> {
168 pub fn try_from_bytes(buf: &'a [u8]) -> Option<Self> {
171 if (4..=256).contains(&buf.len()) {
172 Some(Self { buf })
173 } else {
174 None
175 }
176 }
177
178 pub const fn unit_id(&self) -> u8 {
180 self.buf[0]
181 }
182
183 pub const fn crc(&self) -> u16 {
186 u16::from_le_bytes([self.buf[self.buf.len() - 2], self.buf[self.buf.len() - 1]])
187 }
188
189 fn calculate_crc(&self) -> u16 {
190 MODBUS_CRC.checksum(&self.buf[..self.buf.len() - 2])
191 }
192
193 pub fn pdu(self) -> Result<&'a PduView, CrcError> {
195 self.validate_crc()?;
196 Ok(
197 PduView::ref_from_bytes(&self.buf[1..self.buf.len() - 2])
198 .expect("PduView is Unaligned"),
199 )
200 }
201
202 pub fn validate_crc(&self) -> Result<(), CrcError> {
208 if self.calculate_crc() == self.crc() {
209 Ok(())
210 } else {
211 Err(CrcError)
212 }
213 }
214}
215
216#[derive(Debug, FromBytes, KnownLayout, Immutable, IntoBytes, Unaligned)]
218#[repr(C)]
219pub struct PduView {
220 pub function_code: u8,
222 pub data: [u8],
224}
225
226impl PduView {
227 #[inline]
245 pub fn parse<T: Pdu>(&self) -> Option<&T> {
246 if self.function_code == T::FUNCTION_CODE {
247 T::try_ref_from_bytes(self.as_bytes()).ok()
248 } else {
249 None
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use hex_literal::hex;
257
258 use super::*;
259
260 #[test]
261 fn test_length_validation() {
262 assert!(FrameView::try_from_bytes(&hex!()).is_none());
263 assert!(FrameView::try_from_bytes(&hex!("00 00 00")).is_none());
264 assert!(FrameView::try_from_bytes(&hex!("00 00 00 00")).is_some());
265 assert!(FrameView::try_from_bytes(&[0; 256]).is_some());
266 assert!(FrameView::try_from_bytes(&[0; 257]).is_none());
267 }
268}