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