autosar_e2e/profiles/
profile11.rs

1//! # E2E Profile 11 Implementation
2//!
3//! Profile 11 is designed for protecting small data packets (up to MAX_DATA_LENGTH_BITS bytes)
4//! with low overhead. It uses:
5//! - 8-bit CRC for data integrity
6//! - 4-bit counter for sequence checking (0-14)
7//! - 4-bit Data ID nibble for addressing verification
8//!
9//! # Data layout
10//! [DATA ... | CRC(1B) | HDR(1B) | DATA ...]
11//! - HDR (bits 7..4) : DI_hi_nibble(nibble mode) OR data(both mode)
12//! - HDR (bits 3..0) : counter
13//!
14//! # Modes
15//!
16//! Profile 11 supports two main modes:
17//! - **Both(11A)**: full 16-bit Data-ID is implicit (only used in CRC).
18//! - **Nibble(11C)**: high 4-bit is explicit in the header (1..=0xE recommended), low 8-bit is implicit (in CRC).
19
20use crate::{E2EError, E2EProfile, E2EResult, E2EStatus};
21use crc::{Algorithm, Crc};
22
23// Constants
24const 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
31// Profile 11 uses CRC-8-SAE-J1850 with custom parameters
32const 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/// Data-ID mode for Profile 11.
44///
45/// # Variants
46///
47/// * `Both` - Profile 11A: The complete 16-bit Data-ID is only used
48///   implicitly for CRC calculation. The header preserves the original
49///   upper nibble of the data.
50///
51/// * `Nibble` - Profile 11C: The upper 4 bits of the Data-ID are
52///   stored explicitly in the header, while the lower 8 bits are used
53///   implicitly for CRC calculation. Recommended range: 0x100-0xE00.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum Profile11IdMode {
56    Both,
57    Nibble, // Only lower 12 bits used: 0x000..=0xFFF
58}
59
60/// Configuration for E2E Profile 11
61#[derive(Debug, Clone)]
62pub struct Profile11Config {
63    /// Bit offset of Counter in MSB first order
64    pub counter_offset: u8,
65    /// Bit offset of CRC in MSB first order
66    pub crc_offset: u8,
67    /// Profile mode(11A or 11C)
68    pub mode: Profile11IdMode,
69    /// A unique identifier
70    pub data_id: u16,
71    /// Bit offset of the low nibble of the high byte of Data ID
72    pub nibble_offset: u8,
73    /// Maximum allowed delta between consecutive counters
74    pub max_delta_counter: u8,
75    /// data length (up to DEFAULT_MAX_DATA_LENGTH bytes)
76    pub data_length: u8,
77}
78
79impl Default for Profile11Config {
80    fn default() -> Self {
81        Self {
82            counter_offset: 8, // bits
83            crc_offset: 0,     // bits
84            mode: Profile11IdMode::Nibble,
85            data_id: 0x123,
86            nibble_offset: 12, // bits
87            max_delta_counter: 1,
88            data_length: 64, // bits
89        }
90    }
91}
92
93pub struct Profile11Check {
94    rx_counter: u8,
95    rx_crc: u8,
96    rx_nibble: u8,
97    calculated_crc: u8,
98}
99/// E2E Profile 11 Implementation
100///
101/// Implements AUTOSAR E2E Profile 11 protection mechanism with support
102/// for both 11A and 11C variants.
103#[derive(Clone)]
104pub struct Profile11 {
105    config: Profile11Config,
106    counter: u8,
107    initialized: bool,
108}
109
110impl Profile11 {
111    /// Validate configuration parameters
112    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    /// Validate data length against min/max constraints
154    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    /// Update Crc with ID
187    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    /// Check if counter delta is within acceptable range
230    fn check_counter_delta(&self, received_counter: u8) -> u8 {
231        if received_counter >= self.counter {
232            received_counter - self.counter
233        } else {
234            // Handle wrap-around
235            (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        // Validate config
262        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        // Check data length
288        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        // Counter jump
411        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        // Counter jump a lot
429        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}