autosar_e2e/profiles/
profile5.rs

1//! # E2E Profile 5 Implementation
2//!
3//! Profile 5 is designed for protecting data packets
4//! with low overhead. It uses:
5//! - 16-bit CRC for data integrity
6//! - 8-bit counter for sequence checking
7//! - 8-bit Data ID for masquerade prevention
8//!
9//! # Data layout
10//! [DATA ... | CRC(2B) | COUNTER(1B) | DATA ...]
11use crate::{E2EError, E2EProfile, E2EResult, E2EStatus};
12use crc::{Crc, CRC_16_IBM_3740};
13
14// Constants
15const COUNTER_MAX: u8 = 0xFF;
16const BITS_PER_BYTE: u16 = 8;
17const COUNTER_MODULO: u16 = 0x100;
18
19/// Configuration for E2E Profile 5
20#[derive(Debug, Clone)]
21pub struct Profile5Config {
22    /// Length of Data, in bits. The value shall be a multiple of 8.
23    pub data_length: u16,
24    /// An array of appropriately chosen Data IDs for protection against masquerading.
25    pub data_id: u16,
26    /// Maximum allowed delta between consecutive counters
27    pub max_delta_counter: u8,
28    /// Bit offset of E2E header in the Data[] array in bits.
29    pub offset: u16,
30}
31
32/// Check Item for E2E Profile 5
33#[derive(Debug, Clone)]
34pub struct Profile5Check {
35    rx_counter: u8,
36    rx_crc: u16,
37    calculated_crc: u16,
38}
39
40impl Default for Profile5Config {
41    fn default() -> Self {
42        Self {
43            data_id: 0x1234,
44            offset: 0x0000,
45            data_length: 24, // 3bytes
46            max_delta_counter: 1,
47        }
48    }
49}
50
51/// E2E Profile 5 Implementation
52///
53/// Implements AUTOSAR E2E Profile 5 protection mechanism
54#[derive(Clone)]
55pub struct Profile5 {
56    config: Profile5Config,
57    counter: u8,
58    initialized: bool,
59}
60
61impl Profile5 {
62    /// Validate configuration parameters
63    fn validate_config(config: &Profile5Config) -> E2EResult<()> {
64        if config.data_length < 3 * BITS_PER_BYTE || 4096 * BITS_PER_BYTE < config.data_length {
65            return Err(E2EError::InvalidConfiguration(
66                "Minimum Data length shall be between 3B and 4096B".into(),
67            ));
68        }
69        if config.data_length - 3 * BITS_PER_BYTE < config.offset {
70            return Err(E2EError::InvalidConfiguration(
71                "Offset shall be between 0 and data length - 3B".into(),
72            ));
73        }
74        if config.max_delta_counter == 0 || config.max_delta_counter == COUNTER_MAX {
75            return Err(E2EError::InvalidConfiguration(format!(
76                "Max delta counter must be between 1 and {}",
77                COUNTER_MAX
78            )));
79        }
80        Ok(())
81    }
82    /// Validate data length against min/max constraints
83    fn validate_length(&self, len: u16) -> E2EResult<()> {
84        let expected_bytes = self.config.data_length / BITS_PER_BYTE;
85        if len != expected_bytes {
86            return Err(E2EError::InvalidDataFormat(format!(
87                "Expected {} bytes, got {} bytes",
88                expected_bytes, len
89            )));
90        }
91        Ok(())
92    }
93    fn write_counter(&self, data: &mut [u8]) {
94        let offset = (self.config.offset / BITS_PER_BYTE) as usize;
95        data[offset + 2] = self.counter;
96    }
97    fn compute_crc(&self, data: &[u8]) -> u16 {
98        let crc: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
99        let mut digest = crc.digest();
100        let offset = (self.config.offset / BITS_PER_BYTE) as usize;
101        digest.update(&data[0..offset]); // crc calculation data before offset
102        digest.update(&data[(offset + 2)..]); // crc calculation data after offset
103        digest.update(&self.config.data_id.to_le_bytes());
104        digest.finalize()
105    }
106    fn write_crc(&self, calculated_crc: u16, data: &mut [u8]) {
107        let offset = (self.config.offset / BITS_PER_BYTE) as usize;
108        data[offset..=offset + 1].copy_from_slice(&calculated_crc.to_le_bytes());
109    }
110    fn increment_counter(&mut self) {
111        self.counter = (self.counter as u16 + 1) as u8 & COUNTER_MAX;
112    }
113    fn read_counter(&self, data: &[u8]) -> u8 {
114        let offset = (self.config.offset / BITS_PER_BYTE) as usize;
115        data[offset + 2]
116    }
117    fn read_crc(&self, data: &[u8]) -> u16 {
118        let offset = (self.config.offset / BITS_PER_BYTE) as usize;
119        u16::from_le_bytes([data[offset], data[offset + 1]])
120    }
121
122    fn do_checks(&mut self, check_items: Profile5Check) -> E2EStatus {
123        if check_items.calculated_crc != check_items.rx_crc {
124            return E2EStatus::CrcError;
125        }
126        let status = self.validate_counter(check_items.rx_counter);
127        self.counter = check_items.rx_counter;
128        status
129    }
130    /// Check if counter delta is within acceptable range
131    fn check_counter_delta(&self, received_counter: u8) -> u8 {
132        if received_counter >= self.counter {
133            received_counter - self.counter
134        } else {
135            // Handle wrap-around
136            ((COUNTER_MODULO + received_counter as u16 - self.counter as u16) % COUNTER_MODULO)
137                as u8
138        }
139    }
140    fn validate_counter(&self, rx_counter: u8) -> E2EStatus {
141        let delta = self.check_counter_delta(rx_counter);
142
143        if delta == 0 {
144            if self.initialized {
145                E2EStatus::Repeated
146            } else {
147                E2EStatus::Ok
148            }
149        } else if delta == 1 {
150            E2EStatus::Ok
151        } else if delta >= 2 && delta <= self.config.max_delta_counter {
152            E2EStatus::OkSomeLost
153        } else {
154            E2EStatus::WrongSequence
155        }
156    }
157}
158
159impl E2EProfile for Profile5 {
160    type Config = Profile5Config;
161
162    fn new(config: Self::Config) -> E2EResult<Self> {
163        // Validate config
164        Self::validate_config(&config)?;
165        Ok(Self {
166            config,
167            counter: 0,
168            initialized: false,
169        })
170    }
171
172    fn protect(&mut self, data: &mut [u8]) -> E2EResult<()> {
173        self.validate_length(data.len() as u16)?;
174        self.write_counter(data);
175        let calculated_crc = self.compute_crc(data);
176        self.write_crc(calculated_crc, data);
177        self.increment_counter();
178        Ok(())
179    }
180
181    fn check(&mut self, data: &[u8]) -> E2EResult<E2EStatus> {
182        // Check data length
183        self.validate_length(data.len() as u16)?;
184        let check_items = Profile5Check {
185            rx_counter: self.read_counter(data),
186            rx_crc: self.read_crc(data),
187            calculated_crc: self.compute_crc(data),
188        };
189        let status = self.do_checks(check_items);
190        if !self.initialized && matches!(status, E2EStatus::Ok | E2EStatus::OkSomeLost) {
191            self.initialized = true;
192        }
193        Ok(status)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    #[test]
201    fn test_profile5_basic_example() {
202        let config = Profile5Config {
203            data_length: 8 * BITS_PER_BYTE,
204            ..Default::default()
205        };
206
207        let mut profile_tx = Profile5::new(config.clone()).unwrap();
208        let mut profile_rx = Profile5::new(config).unwrap();
209
210        let mut data = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
211        profile_tx.protect(&mut data).unwrap();
212        // crc check
213        assert_eq!(data[0], 0x1c);
214        assert_eq!(data[1], 0xca);
215        // counter check
216        assert_eq!(data[2], 0x00);
217        assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
218    }
219    #[test]
220    fn test_profile5_offset_example() {
221        let config = Profile5Config {
222            offset: 8 * BITS_PER_BYTE,
223            data_length: 16 * BITS_PER_BYTE,
224            ..Default::default()
225        };
226
227        let mut profile_tx = Profile5::new(config.clone()).unwrap();
228        let mut profile_rx = Profile5::new(config).unwrap();
229
230        let mut data = vec![
231            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
232            0x00, 0x00,
233        ];
234        profile_tx.protect(&mut data).unwrap();
235        // crc check
236        assert_eq!(data[8], 0x28);
237        assert_eq!(data[9], 0x91);
238        // counter check
239        assert_eq!(data[10], 0x00);
240        assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
241    }
242    #[test]
243    fn test_profile5_counter_wraparound() {
244        let config = Profile5Config {
245            offset: 8 * BITS_PER_BYTE,
246            data_length: 16 * BITS_PER_BYTE,
247            ..Default::default()
248        };
249
250        let mut profile_tx = Profile5::new(config.clone()).unwrap();
251        let mut profile_rx = Profile5::new(config).unwrap();
252
253        let mut data = vec![
254            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
255            0x00, 0x00,
256        ];
257        profile_tx.protect(&mut data).unwrap();
258        // crc check
259        assert_eq!(data[8], 0x28);
260        assert_eq!(data[9], 0x91);
261        // counter check
262        assert_eq!(data[10], 0x00);
263        assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
264        for i in 1u8..=0xFF {
265            profile_tx.protect(&mut data).unwrap();
266            // counter check
267            assert_eq!(data[10], i);
268            assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
269        }
270        profile_tx.protect(&mut data).unwrap();
271        // counter check
272        assert_eq!(data[10], 0x00);
273        assert_eq!(profile_rx.check(&data).unwrap(), E2EStatus::Ok);
274    }
275}