Skip to main content

simple_someip/e2e/
mod.rs

1//! E2E (End-to-End) protection for SOME/IP payloads.
2//!
3//! This module implements E2E Profile 4 and Profile 5 protection as specified
4//! in the [Open SOME/IP Specification](https://github.com/some-ip-com/open-someip-spec).
5//!
6//! # Example
7//!
8//! ```
9//! use simple_someip::e2e::{
10//!     Profile4Config, Profile4State,
11//!     protect_profile4, check_profile4,
12//!     E2ECheckStatus,
13//! };
14//!
15//! let config = Profile4Config::new(0x1234_5678, 15);
16//! let mut protect_state = Profile4State::new();
17//! let mut check_state = Profile4State::new();
18//!
19//! let payload = b"Hello, SOME/IP!";
20//! let mut buf = [0u8; 128];
21//! let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
22//!
23//! let result = check_profile4(&config, &mut check_state, &buf[..len]);
24//! assert!(matches!(result.status, E2ECheckStatus::Ok));
25//! ```
26
27mod config;
28mod crc;
29mod e2e_checker;
30mod e2e_protector;
31mod error;
32mod registry;
33mod state;
34
35pub use config::{Profile4Config, Profile5Config};
36pub use e2e_checker::{check_profile4, check_profile5, check_profile5_with_header};
37pub use e2e_protector::{
38    PROFILE4_HEADER_SIZE, PROFILE5_HEADER_SIZE, protect_profile4, protect_profile5,
39    protect_profile5_with_header,
40};
41pub use error::Error;
42pub use registry::{E2E_REGISTRY_CAP, E2E_RX_STATE_CAP, E2ERegistry, E2ERegistryFull};
43pub use state::{Profile4State, Profile5State};
44
45/// Status result from E2E check operations.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum E2ECheckStatus {
48    /// Initial state, no check performed yet.
49    Unchecked,
50    /// Check passed successfully.
51    Ok,
52    /// CRC verification failed.
53    CrcError,
54    /// Counter value is repeated (same as last received).
55    Repeated,
56    /// Check passed but some messages were lost (counter gap within tolerance).
57    OkSomeLost,
58    /// Counter sequence error (gap exceeds `max_delta_counter`).
59    WrongSequence,
60    /// Invalid input arguments (e.g., message too short).
61    BadArgument,
62}
63
64impl E2ECheckStatus {
65    /// Convert to a numeric return code compatible with E2E.
66    #[must_use]
67    pub fn to_return_code(self) -> u8 {
68        match self {
69            E2ECheckStatus::Unchecked => 0,
70            E2ECheckStatus::Ok => 1,
71            E2ECheckStatus::CrcError => 2,
72            E2ECheckStatus::Repeated => 3,
73            E2ECheckStatus::OkSomeLost => 4,
74            E2ECheckStatus::WrongSequence => 5,
75            E2ECheckStatus::BadArgument => 6,
76        }
77    }
78}
79
80/// Result from an E2E check operation.
81#[derive(Debug, Clone)]
82pub struct E2ECheckResult<'a> {
83    /// Status of the E2E check.
84    pub status: E2ECheckStatus,
85    /// Counter value extracted from the header (if parsing succeeded).
86    pub counter: Option<u32>,
87    /// Extracted payload without E2E header (if check succeeded).
88    ///
89    /// This is a borrowed subslice of the input `protected` buffer and is only
90    /// valid as long as that buffer is kept alive.
91    pub payload: Option<&'a [u8]>,
92}
93
94impl<'a> E2ECheckResult<'a> {
95    pub(crate) fn error(status: E2ECheckStatus) -> Self {
96        Self {
97            status,
98            counter: None,
99            payload: None,
100        }
101    }
102
103    pub(crate) fn success(status: E2ECheckStatus, counter: u32, payload: &'a [u8]) -> Self {
104        Self {
105            status,
106            counter: Some(counter),
107            payload: Some(payload),
108        }
109    }
110
111    /// Copy the extracted payload into an owned `Vec<u8>`.
112    ///
113    /// Returns `None` if the check did not produce a payload (e.g. on error).
114    #[cfg(feature = "std")]
115    #[must_use]
116    pub fn to_owned_payload(&self) -> Option<std::vec::Vec<u8>> {
117        self.payload.map(<[u8]>::to_vec)
118    }
119}
120
121/// Describes which E2E profile to apply for a given data element.
122#[derive(Debug, Clone)]
123pub enum E2EProfile {
124    /// E2E Profile 4 (CRC-32, 12-byte header).
125    Profile4(Profile4Config),
126    /// E2E Profile 5 (CRC-16, 3-byte header, no upper-header in CRC).
127    Profile5(Profile5Config),
128    /// E2E Profile 5 with SOME/IP upper-header included in the CRC.
129    Profile5WithHeader(Profile5Config),
130}
131
132/// Identifies a data element for E2E protection lookup.
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
134pub struct E2EKey {
135    /// SOME/IP service ID.
136    pub service_id: u16,
137    /// SOME/IP method or event ID.
138    pub method_or_event_id: u16,
139}
140
141impl E2EKey {
142    /// Create a new key from explicit service and method/event IDs.
143    #[must_use]
144    pub const fn new(service_id: u16, method_or_event_id: u16) -> Self {
145        Self {
146            service_id,
147            method_or_event_id,
148        }
149    }
150
151    /// Derive a key from a [`MessageId`](crate::protocol::MessageId).
152    #[must_use]
153    pub fn from_message_id(message_id: crate::protocol::MessageId) -> Self {
154        Self {
155            service_id: message_id.service_id(),
156            method_or_event_id: message_id.method_id(),
157        }
158    }
159}
160
161/// Internal E2E state, one per registered key.
162#[derive(Debug, Clone)]
163pub(crate) enum E2EState {
164    /// State for Profile 4.
165    Profile4(Profile4State),
166    /// State for Profile 5 (used by both `Profile5` and `Profile5WithHeader`).
167    Profile5(Profile5State),
168}
169
170impl E2EState {
171    pub(crate) fn from_profile(profile: &E2EProfile) -> Self {
172        match profile {
173            E2EProfile::Profile4(_) => Self::Profile4(Profile4State::new()),
174            E2EProfile::Profile5(_) | E2EProfile::Profile5WithHeader(_) => {
175                Self::Profile5(Profile5State::new())
176            }
177        }
178    }
179}
180
181/// Run the appropriate E2E check for the given profile, returning the status
182/// and the best available payload slice (stripped on success, original on error).
183pub(crate) fn e2e_check<'a>(
184    profile: &E2EProfile,
185    state: &mut E2EState,
186    payload: &'a [u8],
187    upper_header: [u8; 8],
188) -> (E2ECheckStatus, &'a [u8]) {
189    let result = match (profile, state) {
190        (E2EProfile::Profile4(config), E2EState::Profile4(st)) => {
191            check_profile4(config, st, payload)
192        }
193        (E2EProfile::Profile5(config), E2EState::Profile5(st)) => {
194            check_profile5(config, st, payload)
195        }
196        (E2EProfile::Profile5WithHeader(config), E2EState::Profile5(st)) => {
197            check_profile5_with_header(config, st, payload, upper_header)
198        }
199        _ => return (E2ECheckStatus::BadArgument, payload),
200    };
201    let stripped = result.payload.unwrap_or(payload);
202    (result.status, stripped)
203}
204
205/// Run the appropriate E2E protect for the given profile.
206///
207/// # Errors
208///
209/// Returns [`Error::BufferTooSmall`] if `output` cannot hold the protected payload.
210pub(crate) fn e2e_protect(
211    profile: &E2EProfile,
212    state: &mut E2EState,
213    payload: &[u8],
214    upper_header: [u8; 8],
215    output: &mut [u8],
216) -> Result<usize, Error> {
217    match (profile, state) {
218        (E2EProfile::Profile4(config), E2EState::Profile4(st)) => {
219            protect_profile4(config, st, payload, output)
220        }
221        (E2EProfile::Profile5(config), E2EState::Profile5(st)) => {
222            protect_profile5(config, st, payload, output)
223        }
224        (E2EProfile::Profile5WithHeader(config), E2EState::Profile5(st)) => {
225            protect_profile5_with_header(config, st, payload, upper_header, output)
226        }
227        _ => unreachable!("E2EState is always created from E2EProfile"),
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_status_return_codes() {
237        assert_eq!(E2ECheckStatus::Unchecked.to_return_code(), 0);
238        assert_eq!(E2ECheckStatus::Ok.to_return_code(), 1);
239        assert_eq!(E2ECheckStatus::CrcError.to_return_code(), 2);
240        assert_eq!(E2ECheckStatus::Repeated.to_return_code(), 3);
241        assert_eq!(E2ECheckStatus::OkSomeLost.to_return_code(), 4);
242        assert_eq!(E2ECheckStatus::WrongSequence.to_return_code(), 5);
243        assert_eq!(E2ECheckStatus::BadArgument.to_return_code(), 6);
244    }
245
246    #[test]
247    fn test_profile4_roundtrip() {
248        let config = Profile4Config::new(0x1234_5678, 15);
249        let mut protect_state = Profile4State::new();
250        let mut check_state = Profile4State::new();
251
252        let payload = b"Test payload data";
253        let mut buf = [0u8; 256];
254        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
255        let protected = &buf[..len];
256
257        assert_eq!(len, payload.len() + 12); // 12-byte header
258
259        let result = check_profile4(&config, &mut check_state, protected);
260        assert_eq!(result.status, E2ECheckStatus::Ok);
261        assert_eq!(result.counter, Some(0));
262        assert_eq!(result.payload, Some(payload.as_slice()));
263    }
264
265    #[test]
266    fn test_profile5_roundtrip() {
267        let config = Profile5Config::new(0x1234, 20, 15);
268        let mut protect_state = Profile5State::new();
269        let mut check_state = Profile5State::new();
270
271        // Payload must be padded to data_length (20 bytes) for check_profile5
272        let mut payload = [0u8; 20];
273        payload[..17].copy_from_slice(b"Test payload data");
274        let mut buf = [0u8; 256];
275        let len = protect_profile5(&config, &mut protect_state, &payload, &mut buf).unwrap();
276        let protected = &buf[..len];
277
278        assert_eq!(len, payload.len() + 3); // 3-byte header
279
280        let result = check_profile5(&config, &mut check_state, protected);
281        assert_eq!(result.status, E2ECheckStatus::Ok);
282        assert_eq!(result.counter, Some(0));
283        assert_eq!(result.payload, Some(payload.as_slice()));
284    }
285
286    #[test]
287    fn test_profile4_sequence_detection() {
288        let config = Profile4Config::new(0x1234_5678, 5);
289        let mut protect_state = Profile4State::new();
290        let mut check_state = Profile4State::new();
291
292        let payload = b"Test";
293        let mut buf1 = [0u8; 256];
294        let mut buf2 = [0u8; 256];
295
296        // First message - should be Ok
297        let len1 = protect_profile4(&config, &mut protect_state, payload, &mut buf1).unwrap();
298        let result1 = check_profile4(&config, &mut check_state, &buf1[..len1]);
299        assert_eq!(result1.status, E2ECheckStatus::Ok);
300
301        // Second message - should be Ok
302        let len2 = protect_profile4(&config, &mut protect_state, payload, &mut buf2).unwrap();
303        let result2 = check_profile4(&config, &mut check_state, &buf2[..len2]);
304        assert_eq!(result2.status, E2ECheckStatus::Ok);
305
306        // Replay first message - should be Repeated or WrongSequence
307        let result3 = check_profile4(&config, &mut check_state, &buf1[..len1]);
308        assert!(matches!(
309            result3.status,
310            E2ECheckStatus::Repeated | E2ECheckStatus::WrongSequence
311        ));
312    }
313
314    #[test]
315    fn test_profile4_some_lost_detection() {
316        let config = Profile4Config::new(0x1234_5678, 5);
317        let mut protect_state = Profile4State::new();
318        let mut check_state = Profile4State::new();
319
320        let payload = b"Test";
321        let mut buf = [0u8; 256];
322
323        // First message
324        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
325        let result1 = check_profile4(&config, &mut check_state, &buf[..len]);
326        assert_eq!(result1.status, E2ECheckStatus::Ok);
327
328        // Skip a few messages by advancing protector counter
329        protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
330        protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
331        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
332
333        // Check skipped message - should be OkSomeLost (delta=3, within max_delta=5)
334        let result4 = check_profile4(&config, &mut check_state, &buf[..len]);
335        assert_eq!(result4.status, E2ECheckStatus::OkSomeLost);
336    }
337
338    #[test]
339    fn test_profile4_wrong_sequence_detection() {
340        let config = Profile4Config::new(0x1234_5678, 2);
341        let mut protect_state = Profile4State::new();
342        let mut check_state = Profile4State::new();
343
344        let payload = b"Test";
345        let mut buf = [0u8; 256];
346
347        // First message
348        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
349        let result1 = check_profile4(&config, &mut check_state, &buf[..len]);
350        assert_eq!(result1.status, E2ECheckStatus::Ok);
351
352        // Skip many messages (exceed max_delta)
353        for _ in 0..5 {
354            protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
355        }
356        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
357
358        // Check - should be WrongSequence (delta=6, exceeds max_delta=2)
359        let result = check_profile4(&config, &mut check_state, &buf[..len]);
360        assert_eq!(result.status, E2ECheckStatus::WrongSequence);
361    }
362
363    #[test]
364    fn test_profile4_crc_error() {
365        let config = Profile4Config::new(0x1234_5678, 15);
366        let mut protect_state = Profile4State::new();
367        let mut check_state = Profile4State::new();
368
369        let payload = b"Test";
370        let mut buf = [0u8; 256];
371        let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
372
373        // Corrupt the CRC (last 4 bytes of header)
374        buf[8] ^= 0xFF;
375
376        let result = check_profile4(&config, &mut check_state, &buf[..len]);
377        assert_eq!(result.status, E2ECheckStatus::CrcError);
378    }
379
380    #[test]
381    fn test_profile5_crc_error() {
382        let config = Profile5Config::new(0x1234, 20, 15);
383        let mut protect_state = Profile5State::new();
384        let mut check_state = Profile5State::new();
385
386        let mut payload = [0u8; 20];
387        payload[..4].copy_from_slice(b"Test");
388        let mut buf = [0u8; 256];
389        let len = protect_profile5(&config, &mut protect_state, &payload, &mut buf).unwrap();
390
391        // Corrupt the CRC (bytes 1-2 of header)
392        buf[1] ^= 0xFF;
393
394        let result = check_profile5(&config, &mut check_state, &buf[..len]);
395        assert_eq!(result.status, E2ECheckStatus::CrcError);
396    }
397
398    #[test]
399    fn test_profile4_bad_argument_short_message() {
400        let config = Profile4Config::new(0x1234_5678, 15);
401        let mut check_state = Profile4State::new();
402
403        // Message too short (less than 12-byte header)
404        let short_message = [0u8; 8];
405        let result = check_profile4(&config, &mut check_state, &short_message);
406        assert_eq!(result.status, E2ECheckStatus::BadArgument);
407    }
408
409    #[test]
410    fn test_profile5_bad_argument_short_message() {
411        let config = Profile5Config::new(0x1234, 20, 15);
412        let mut check_state = Profile5State::new();
413
414        // Message too short (less than 3-byte header)
415        let short_message = [0u8; 2];
416        let result = check_profile5(&config, &mut check_state, &short_message);
417        assert_eq!(result.status, E2ECheckStatus::BadArgument);
418    }
419
420    #[cfg(feature = "std")]
421    #[test]
422    fn test_check_result_to_owned_payload() {
423        let data = b"hello";
424        let result = E2ECheckResult::success(E2ECheckStatus::Ok, 0, data);
425        let owned = result.to_owned_payload();
426        assert_eq!(owned, Some(b"hello".to_vec()));
427
428        let err_result = E2ECheckResult::error(E2ECheckStatus::CrcError);
429        assert_eq!(err_result.to_owned_payload(), None);
430    }
431
432    #[test]
433    fn test_e2e_key_from_message_id() {
434        let mid = crate::protocol::MessageId::new_from_service_and_method(0x1234, 0x0001);
435        let key = E2EKey::from_message_id(mid);
436        assert_eq!(key.service_id, 0x1234);
437        assert_eq!(key.method_or_event_id, 0x0001);
438    }
439}