1use crate::{E2EError, E2EProfile, E2EResult, E2EStatus};
21use crc::{Algorithm, Crc};
22
23const NIBBLE_MASK: u8 = 0x0F;
25const COUNTER_MAX: u8 = 14;
26const COUNTER_MODULO: u8 = 15;
27const MAX_DATA_LENGTH_BITS: u8 = 240;
28const BITS_PER_BYTE: u8 = 8;
29const BITS_PER_NIBBLE: u8 = 4;
30
31const CRC8_ALGO: Algorithm<u8> = Algorithm {
33 width: 8,
34 poly: 0x1d,
35 init: 0x00,
36 refin: false,
37 refout: false,
38 xorout: 0x00,
39 check: 0x4b,
40 residue: 0xc4,
41};
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum Profile11IdMode {
56 Both,
57 Nibble, }
59
60#[derive(Debug, Clone)]
62pub struct Profile11Config {
63 pub counter_offset: u8,
65 pub crc_offset: u8,
67 pub mode: Profile11IdMode,
69 pub data_id: u16,
71 pub nibble_offset: u8,
73 pub max_delta_counter: u8,
75 pub data_length: u8,
77}
78
79impl Default for Profile11Config {
80 fn default() -> Self {
81 Self {
82 counter_offset: 8, crc_offset: 0, mode: Profile11IdMode::Nibble,
85 data_id: 0x123,
86 nibble_offset: 12, max_delta_counter: 1,
88 data_length: 64, }
90 }
91}
92
93pub struct Profile11Check {
94 rx_counter: u8,
95 rx_crc: u8,
96 rx_nibble: u8,
97 calculated_crc: u8,
98}
99#[derive(Clone)]
104pub struct Profile11 {
105 config: Profile11Config,
106 counter: u8,
107 initialized: bool,
108}
109
110impl Profile11 {
111 fn validate_config(config: &Profile11Config) -> E2EResult<()> {
113 if config.data_length > MAX_DATA_LENGTH_BITS {
114 return Err(E2EError::InvalidConfiguration(format!(
115 "Maximum data length for Profile 11 is {} bits",
116 MAX_DATA_LENGTH_BITS
117 )));
118 }
119
120 if !config.data_length.is_multiple_of(BITS_PER_BYTE) {
121 return Err(E2EError::InvalidConfiguration(
122 "Data length shall be a multiple of 8".into(),
123 ));
124 }
125
126 if config.max_delta_counter == 0 || config.max_delta_counter > COUNTER_MAX {
127 return Err(E2EError::InvalidConfiguration(format!(
128 "Max delta counter must be between 1 and {}",
129 COUNTER_MAX
130 )));
131 }
132
133 if !config.counter_offset.is_multiple_of(BITS_PER_NIBBLE) {
134 return Err(E2EError::InvalidConfiguration(
135 "Counter offset shall be a multiple of 4".into(),
136 ));
137 }
138
139 if !config.crc_offset.is_multiple_of(BITS_PER_BYTE) {
140 return Err(E2EError::InvalidConfiguration(
141 "Crc offset shall be a multiple of 8".into(),
142 ));
143 }
144
145 if config.mode == Profile11IdMode::Nibble && !config.nibble_offset.is_multiple_of(4) {
146 return Err(E2EError::InvalidConfiguration(
147 "Nibble offset must be a multiple of 4 bits".into(),
148 ));
149 }
150
151 Ok(())
152 }
153 fn validate_length(&self, len: usize) -> E2EResult<()> {
155 let expected_bytes = (self.config.data_length / BITS_PER_BYTE) as usize;
156 if len != expected_bytes {
157 return Err(E2EError::InvalidDataFormat(format!(
158 "Expected {} bytes, got {} bytes",
159 expected_bytes, len
160 )));
161 }
162 Ok(())
163 }
164 fn write_nibble_data(&self, offset: u8, set_value: u8, data: &mut [u8]) {
165 let byte_idx = (offset >> 3) as usize;
166 let shift = offset & 0x07;
167
168 let mask = !(NIBBLE_MASK << shift);
169 let val = (set_value & NIBBLE_MASK) << shift;
170 data[byte_idx] = (data[byte_idx] & mask) | val;
171 }
172 fn read_nibble_data(&self, offset: u8, data: &[u8]) -> u8 {
173 let byte_idx = (offset >> 3) as usize;
174 let shift = offset & 0x07;
175
176 (data[byte_idx] >> shift) & NIBBLE_MASK
177 }
178 fn write_crc(&self, calculated_crc: u8, data: &mut [u8]) {
179 let byte_position = (self.config.crc_offset / BITS_PER_BYTE) as usize;
180 data[byte_position] = calculated_crc;
181 }
182 fn read_crc(&self, data: &[u8]) -> u8 {
183 let byte_position = (self.config.crc_offset / BITS_PER_BYTE) as usize;
184 data[byte_position]
185 }
186 fn update_crc_with_id(&self, digest: &mut crc::Digest<u8>) {
188 match self.config.mode {
189 Profile11IdMode::Both => {
190 digest.update(&self.config.data_id.to_le_bytes());
191 }
192 Profile11IdMode::Nibble => {
193 digest.update(&[self.config.data_id.to_le_bytes()[0], 0x00]);
194 }
195 }
196 }
197 fn update_crc_with_data(&self, digest: &mut crc::Digest<u8>, data: &[u8]) {
198 if self.config.crc_offset > 0 {
199 let offset_byte = (self.config.crc_offset / BITS_PER_BYTE) as usize;
200 digest.update(&data[0..offset_byte]);
201 digest.update(&data[(offset_byte + 1)..]);
202 } else {
203 digest.update(&data[1..]);
204 }
205 }
206 fn compute_crc(&self, data: &[u8]) -> u8 {
207 let crc: Crc<u8> = Crc::<u8>::new(&CRC8_ALGO);
208 let mut digest = crc.digest();
209 self.update_crc_with_id(&mut digest);
210 self.update_crc_with_data(&mut digest, data);
211 digest.finalize()
212 }
213 fn increment_counter(&mut self) {
214 self.counter = (self.counter + 1) % COUNTER_MODULO;
215 }
216 fn do_checks(&mut self, check_items: Profile11Check) -> E2EStatus {
217 if check_items.calculated_crc != check_items.rx_crc {
218 return E2EStatus::CrcError;
219 }
220 if (self.config.mode == Profile11IdMode::Nibble)
221 && ((self.config.data_id >> BITS_PER_BYTE) as u8 & NIBBLE_MASK) != check_items.rx_nibble
222 {
223 return E2EStatus::DataIdError;
224 }
225 let status = self.validate_counter(check_items.rx_counter);
226 self.counter = check_items.rx_counter;
227 status
228 }
229 fn check_counter_delta(&self, received_counter: u8) -> u8 {
231 if received_counter >= self.counter {
232 received_counter - self.counter
233 } else {
234 (COUNTER_MODULO + received_counter - self.counter) % COUNTER_MODULO
236 }
237 }
238 fn validate_counter(&self, rx_counter: u8) -> E2EStatus {
239 let delta = self.check_counter_delta(rx_counter);
240
241 if delta == 0 {
242 if self.initialized {
243 E2EStatus::Repeated
244 } else {
245 E2EStatus::Ok
246 }
247 } else if delta == 1 {
248 E2EStatus::Ok
249 } else if delta >= 2 && delta <= self.config.max_delta_counter {
250 E2EStatus::OkSomeLost
251 } else {
252 E2EStatus::WrongSequence
253 }
254 }
255}
256
257impl E2EProfile for Profile11 {
258 type Config = Profile11Config;
259
260 fn new(config: Self::Config) -> E2EResult<Self> {
261 Self::validate_config(&config)?;
263 Ok(Self {
264 config,
265 counter: 0,
266 initialized: false,
267 })
268 }
269
270 fn protect(&mut self, data: &mut [u8]) -> E2EResult<()> {
271 self.validate_length(data.len())?;
272 if self.config.mode == Profile11IdMode::Nibble {
273 self.write_nibble_data(
274 self.config.nibble_offset,
275 self.config.data_id.to_le_bytes()[1],
276 data,
277 );
278 }
279 self.write_nibble_data(self.config.counter_offset, self.counter, data);
280 let calculated_crc = self.compute_crc(data);
281 self.write_crc(calculated_crc, data);
282 self.increment_counter();
283 Ok(())
284 }
285
286 fn check(&mut self, data: &[u8]) -> E2EResult<E2EStatus> {
287 self.validate_length(data.len())?;
289 let check_items = Profile11Check {
290 rx_nibble: self.read_nibble_data(self.config.nibble_offset, data),
291 rx_counter: self.read_nibble_data(self.config.counter_offset, data),
292 rx_crc: self.read_crc(data),
293 calculated_crc: self.compute_crc(data),
294 };
295 let status = self.do_checks(check_items);
296 if !self.initialized && matches!(status, E2EStatus::Ok | E2EStatus::OkSomeLost) {
297 self.initialized = true;
298 }
299 Ok(status)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 #[test]
307 fn test_profile11_basic_both_example() {
308 let config = Profile11Config {
309 max_delta_counter: 1,
310 mode: Profile11IdMode::Both,
311 data_id: 0x123,
312 ..Default::default()
313 };
314
315 let mut profile_tx = Profile11::new(config.clone()).unwrap();
316 let mut profile_rx = Profile11::new(config).unwrap();
317
318 let mut data1 = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
319 profile_tx.protect(&mut data1).unwrap();
320 assert_eq!(data1[0], 0xcc);
321 assert_eq!(data1[1], 0x00);
322 assert_eq!(profile_rx.check(&data1).unwrap(), E2EStatus::Ok);
323
324 profile_tx.protect(&mut data1).unwrap();
325 assert_eq!(data1[0], 0x91);
326 assert_eq!(data1[1], 0x01);
327 assert_eq!(profile_rx.check(&data1).unwrap(), E2EStatus::Ok);
328 }
329
330 #[test]
331 fn test_profile11_basic_nibble_example() {
332 let config = Profile11Config {
333 max_delta_counter: 1,
334 mode: Profile11IdMode::Nibble,
335 data_id: 0x123,
336 ..Default::default()
337 };
338
339 let mut profile_tx = Profile11::new(config.clone()).unwrap();
340 let mut profile_rx = Profile11::new(config).unwrap();
341
342 let mut data1 = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
343 profile_tx.protect(&mut data1).unwrap();
344 assert_eq!(data1[0], 0x2a);
345 assert_eq!(data1[1], 0x10);
346 assert_eq!(profile_rx.check(&data1).unwrap(), E2EStatus::Ok);
347
348 profile_tx.protect(&mut data1).unwrap();
349 assert_eq!(data1[0], 0x77);
350 assert_eq!(data1[1], 0x11);
351 assert_eq!(profile_rx.check(&data1).unwrap(), E2EStatus::Ok);
352 }
353 #[test]
354 fn test_profile11_offset_nibble_example() {
355 let config = Profile11Config {
356 max_delta_counter: 1,
357 crc_offset: 64,
358 counter_offset: 72,
359 nibble_offset: 76,
360 data_length: 128,
361 mode: Profile11IdMode::Nibble,
362 data_id: 0x123,
363 };
364
365 let mut profile_tx = Profile11::new(config.clone()).unwrap();
366 let mut profile_rx = Profile11::new(config).unwrap();
367
368 let mut data1 = vec![
369 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
370 0x00, 0x00,
371 ];
372 profile_tx.protect(&mut data1).unwrap();
373 assert_eq!(data1[8], 0x7d);
374 assert_eq!(data1[9], 0x10);
375 assert_eq!(profile_rx.check(&data1).unwrap(), E2EStatus::Ok);
376 }
377 #[test]
378 fn test_profile11_crc_error() {
379 let mut profile = Profile11::new(Profile11Config::default()).unwrap();
380 let mut data = vec![0x00; 8];
381 profile.protect(&mut data).unwrap();
382 data[0] ^= 0xFF;
383 assert_eq!(profile.check(&data).unwrap(), E2EStatus::CrcError);
384 }
385 #[test]
386 fn test_profile11_counter_wraparound() {
387 let mut profile = Profile11::new(Profile11Config::default()).unwrap();
388 let mut data = vec![0x00; 8];
389 for _ in 0..=COUNTER_MAX + 1 {
390 profile.protect(&mut data).unwrap();
391 }
392 assert_eq!(
393 profile.read_nibble_data(profile.config.counter_offset, &data),
394 0x00
395 );
396 }
397 #[test]
398 fn test_profile11_some_lost_ok() {
399 let config = Profile11Config {
400 max_delta_counter: 3,
401 ..Default::default()
402 };
403 let mut tx = Profile11::new(config.clone()).unwrap();
404 let mut rx = Profile11::new(config).unwrap();
405
406 let mut data = vec![0x00; 8];
407 tx.protect(&mut data).unwrap();
408 rx.check(&data).unwrap();
409
410 tx.increment_counter();
412 tx.protect(&mut data).unwrap();
413 assert_eq!(rx.check(&data).unwrap(), E2EStatus::OkSomeLost);
414 }
415 #[test]
416 fn test_profile11_wrong_sequence() {
417 let config = Profile11Config {
418 max_delta_counter: 1,
419 ..Default::default()
420 };
421 let mut tx = Profile11::new(config.clone()).unwrap();
422 let mut rx = Profile11::new(config).unwrap();
423
424 let mut data = vec![0x00; 8];
425 tx.protect(&mut data).unwrap();
426 rx.check(&data).unwrap();
427
428 tx.counter = (tx.counter + 3) % COUNTER_MODULO;
430 tx.protect(&mut data).unwrap();
431 assert_eq!(rx.check(&data).unwrap(), E2EStatus::WrongSequence);
432 }
433 #[test]
434 fn test_profile11_repeated_frame() {
435 let mut profile = Profile11::new(Profile11Config::default()).unwrap();
436 let mut profile_rx = profile.clone();
437 let mut data = vec![0x00; 8];
438 profile.protect(&mut data).unwrap();
439 assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
440 assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Repeated);
441 }
442}