1use zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned, little_endian, try_transmute_ref};
2use zerocopy_derive::*;
3
4use crate::{
5 Pdu,
6 pdu::{CrcError, Response, ValidationError},
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Unaligned, Immutable, FromBytes)]
11#[repr(C)]
12pub struct Frame<T> {
13 unit_id: u8,
14 pdu: T,
15 crc: little_endian::U16,
16}
17
18impl<T> Frame<T> {
19 pub fn new(unit_id: u8, pdu: T) -> Self
22 where
23 T: Pdu,
24 {
25 let mut frame = Self::without_crc(unit_id, pdu);
26 frame.update_crc();
27 frame
28 }
29
30 const fn without_crc(unit_id: u8, pdu: T) -> Self {
31 Frame {
32 unit_id,
33 pdu,
34 crc: little_endian::U16::ZERO,
35 }
36 }
37
38 pub const fn builder(unit_id: u8) -> FrameBuilder<T>
40 where
41 T: Pdu,
42 {
43 FrameBuilder::new(unit_id)
44 }
45
46 fn calculate_crc(&self) -> u16
47 where
48 T: IntoBytes + Unaligned + Immutable,
49 {
50 let bytes = self.as_bytes();
51 crate::crc(&bytes[..bytes.len() - 2])
53 }
54
55 fn update_crc(&mut self)
56 where
57 T: IntoBytes + Unaligned + Immutable,
58 {
59 self.crc = self.calculate_crc().into();
60 }
61
62 pub fn into_data<Request>(self, request: &Frame<Request>) -> Result<T::Data, ValidationError>
64 where
65 T: Response<Request>,
66 {
67 if self.calculate_crc() != self.crc.get() {
68 return Err(ValidationError::Crc(CrcError));
69 }
70
71 if self.unit_id != request.unit_id {
72 return Err(ValidationError::UnexpectedResponse);
73 }
74
75 if !self.pdu.matches_request(&request.pdu) {
76 return Err(ValidationError::UnexpectedResponse);
77 }
78
79 Ok(self.pdu.into_data())
80 }
81
82 pub fn view(&self) -> FrameView<'_>
84 where
85 T: IntoBytes + Unaligned + Immutable,
86 {
87 FrameView {
88 buf: self.as_bytes(),
89 }
90 }
91}
92
93#[derive(Debug)]
95pub struct FrameBuilder<T> {
96 inner: Frame<T>,
97}
98
99impl<T: Pdu> FrameBuilder<T> {
100 pub const fn with_pdu(unit_id: u8, pdu: T) -> Self {
105 Self {
106 inner: Frame::without_crc(unit_id, pdu),
108 }
109 }
110
111 pub const fn new(unit_id: u8) -> Self {
113 Self::with_pdu(unit_id, T::DEFAULT)
114 }
115
116 pub const fn set_unit_id(&mut self, unit_id: u8) {
118 self.inner.unit_id = unit_id;
119 }
120
121 pub fn build_ref(&mut self) -> &mut Frame<T> {
124 self.inner.update_crc();
125 &mut self.inner
126 }
127
128 pub fn build(mut self) -> Frame<T> {
130 self.inner.update_crc();
131 self.inner
132 }
133
134 pub fn pdu_mut(&mut self) -> &mut T {
136 &mut self.inner.pdu
137 }
138}
139
140impl<T: Pdu> Default for FrameBuilder<T> {
141 fn default() -> Self {
142 Self::new(0)
143 }
144}
145
146pub struct FrameView<'a> {
157 buf: &'a [u8],
158}
159
160impl<'a> FrameView<'a> {
161 pub fn try_from_bytes(buf: &'a [u8]) -> Option<Self> {
164 if (4..=256).contains(&buf.len()) {
165 Some(Self { buf })
166 } else {
167 None
168 }
169 }
170
171 pub const fn unit_id(&self) -> u8 {
173 self.buf[0]
174 }
175
176 pub const fn crc(&self) -> u16 {
179 u16::from_le_bytes([self.buf[self.buf.len() - 2], self.buf[self.buf.len() - 1]])
180 }
181
182 fn calculate_crc(&self) -> u16 {
183 crate::crc(&self.buf[..self.buf.len() - 2])
184 }
185
186 pub fn pdu(self) -> Result<&'a PduView, CrcError> {
188 self.validate_crc()?;
189 Ok(
190 PduView::ref_from_bytes(&self.buf[1..self.buf.len() - 2])
191 .expect("PduView is Unaligned"),
192 )
193 }
194
195 pub fn validate_crc(&self) -> Result<(), CrcError> {
201 if self.calculate_crc() == self.crc() {
202 Ok(())
203 } else {
204 Err(CrcError)
205 }
206 }
207}
208
209#[derive(Debug, FromBytes, KnownLayout, Immutable, IntoBytes, Unaligned)]
211#[repr(C)]
212pub struct PduView {
213 pub function_code: u8,
215 pub data: [u8],
217}
218
219impl PduView {
220 #[inline]
223 pub fn parse<T: Pdu>(&self) -> Option<&T> {
224 if self.function_code == T::FUNCTION_CODE {
225 try_transmute_ref!(self).ok()
226 } else {
227 None
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use hex_literal::hex;
235
236 use super::*;
237
238 #[test]
239 fn test_length_validation() {
240 assert!(FrameView::try_from_bytes(&hex!()).is_none());
241 assert!(FrameView::try_from_bytes(&hex!("00 00 00")).is_none());
242 assert!(FrameView::try_from_bytes(&hex!("00 00 00 00")).is_some());
243 assert!(FrameView::try_from_bytes(&[0; 256]).is_some());
244 assert!(FrameView::try_from_bytes(&[0; 257]).is_none());
245 }
246}