diff --git a/src/bare_metal_tasks.rs b/src/bare_metal_tasks.rs index 5095cac6..7cf62174 100644 --- a/src/bare_metal_tasks.rs +++ b/src/bare_metal_tasks.rs @@ -131,7 +131,7 @@ pub async fn event_rx_dispatch_future<'a, S, R>( continue; }; let (status, body) = if e2e_enabled { - check_parsed_e2e(e2e, &parsed) + check_parsed_e2e(e2e, core::net::IpAddr::V4(*source.ip()), &parsed) } else { (E2ECheckStatus::Unchecked, parsed.payload) }; diff --git a/src/client/inner.rs b/src/client/inner.rs index 405a0e40..a6b9e3e6 100644 --- a/src/client/inner.rs +++ b/src/client/inner.rs @@ -1057,6 +1057,7 @@ where request_queue, session_tracker, service_registry, + e2e_registry, run, timer, .. @@ -1194,6 +1195,11 @@ where }); if rebooted { + // A rebooted sender restarts its E2E counter at + // zero, so drop our stored per-source receive + // state for it; otherwise its first post-reboot + // frame would read as out-of-sequence. + e2e_registry.reset_source(source.ip()); let _ = update_sender.send_now(ClientUpdate::SenderRebooted(source)); } @@ -1214,7 +1220,7 @@ where trace!("Received unicast message: {:?}", unicast); match unicast { Ok(received) => { - let ReceivedMessage { message: received_message, e2e_status, .. } = received; + let ReceivedMessage { message: received_message, e2e_status, source } = received; // Check if this matches a pending request-response by request_id let request_id = received_message.header().request_id(); if let Some(sender) = pending_responses.remove(&request_id) { @@ -1222,7 +1228,7 @@ where continue; } // Not a response — forward as ClientUpdate::Unicast - let _ = update_sender.send_now(ClientUpdate::Unicast { message: received_message, e2e_status }); + let _ = update_sender.send_now(ClientUpdate::Unicast { message: received_message, e2e_status, source }); } Err(err) => { let _ = update_sender.send_now(ClientUpdate::Error(err)); diff --git a/src/client/mod.rs b/src/client/mod.rs index 0283b4d9..bd9d1e13 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -213,6 +213,10 @@ pub enum ClientUpdate { message: Message

, /// E2E check status, if E2E was configured for this message. e2e_status: Option, + /// The sender's source address. On a shared subnet this is the only + /// way to attribute a unicast event to a specific device, since the + /// SOME/IP header carries no instance id. + source: SocketAddr, }, /// The client encountered an error. Error(Error), @@ -226,10 +230,12 @@ impl core::fmt::Debug for ClientUpdate

{ Self::Unicast { message, e2e_status, + source, } => f .debug_struct("Unicast") .field("message", message) .field("e2e_status", e2e_status) + .field("source", source) .finish(), Self::Error(err) => f.debug_tuple("Error").field(err).finish(), } @@ -1484,6 +1490,7 @@ mod tests { let update: ClientUpdate = ClientUpdate::Unicast { message: msg, e2e_status: None, + source: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 30640), }; let debug_str = format!("{update:?}"); assert!(debug_str.contains("Unicast")); @@ -1494,6 +1501,21 @@ mod tests { assert!(debug_str.contains("Error")); } + #[test] + fn unicast_update_carries_source() { + let src = SocketAddr::new(Ipv4Addr::new(192, 168, 11, 101).into(), 30640); + let msg = crate::protocol::Message::new_sd(1, &empty_sd_header()); + let update: ClientUpdate = ClientUpdate::Unicast { + message: msg, + e2e_status: None, + source: src, + }; + match update { + ClientUpdate::Unicast { source, .. } => assert_eq!(source, src), + _ => panic!("expected Unicast"), + } + } + #[tokio::test] async fn test_subscribe_unknown_service_returns_error() { let (client, _updates, run_fut) = TestClient::new(Ipv4Addr::LOCALHOST); diff --git a/src/client/socket_manager.rs b/src/client/socket_manager.rs index a0b52193..5c7ff1db 100644 --- a/src/client/socket_manager.rs +++ b/src/client/socket_manager.rs @@ -794,9 +794,16 @@ where let key = E2EKey::from_message_id(header.message_id()); let payload_bytes = view.payload_bytes(); - // Apply E2E check if configured + // Apply E2E check if configured. The source IP keys + // the receive counter state so interleaved senders + // on a shared subnet don't collide (see `E2ERegistry`). let (e2e_status, effective_payload) = - match e2e_registry.check(key, payload_bytes, upper_header) { + match e2e_registry.check( + source_address.ip(), + key, + payload_bytes, + upper_header, + ) { Some((status, stripped)) => (Some(status), stripped), None => (None, payload_bytes), }; diff --git a/src/e2e/registry.rs b/src/e2e/registry.rs index 30c7dfe6..2c99601c 100644 --- a/src/e2e/registry.rs +++ b/src/e2e/registry.rs @@ -7,7 +7,9 @@ //! rather than silently dropping or growing — see [`E2ERegistry::register`] //! and [`E2ERegistryFull`]. -use heapless::index_map::FnvIndexMap; +use core::net::IpAddr; + +use heapless::index_map::{Entry, FnvIndexMap}; use super::{E2ECheckStatus, E2EKey, E2EProfile, E2EState, Error, e2e_check, e2e_protect}; @@ -24,6 +26,27 @@ const _: () = assert!( "E2E_REGISTRY_CAP must be a power of two for heapless::FnvIndexMap" ); +/// Maximum number of distinct `(source, key)` **receive** counter slots +/// the registry can hold at once. +/// +/// On a shared subnet the receive state is keyed per source (see +/// [`E2ERegistry`]), so this bounds *sources × keys*, not just keys — +/// size it for the high-water mark of distinct senders the node expects +/// to demux concurrently. Once full, [`E2ERegistry::check`] still runs +/// (CRC is always validated) but a brand-new source falls back to a +/// transient per-call counter, so its *sequence* continuity is not +/// tracked until a slot frees via [`E2ERegistry::reset_source`] / +/// [`E2ERegistry::unregister`]. A one-shot `warn!` fires the first time +/// this happens. +/// +/// Must be a power of two for [`FnvIndexMap`]. +pub const E2E_RX_STATE_CAP: usize = 64; + +const _: () = assert!( + E2E_RX_STATE_CAP.is_power_of_two(), + "E2E_RX_STATE_CAP must be a power of two for heapless::FnvIndexMap" +); + /// Returned by [`E2ERegistry::register`] when the registry is at /// capacity. /// @@ -34,16 +57,36 @@ const _: () = assert!( #[error("e2e registry at capacity ({0})")] pub struct E2ERegistryFull(pub usize); -/// Registry mapping message keys to E2E profile configurations and -/// the per-key counter / sequence state. +/// Registry mapping message keys to E2E profile configurations and the +/// per-source / per-key counter state. +/// +/// On a shared subnet several devices send the same `(service, method)` under +/// the same fixed instance id. The profile *configuration* is endpoint-agnostic +/// (one per [`E2EKey`]), but the **receive** counter state must be independent +/// per device — otherwise two senders' interleaved counters collide into +/// spurious `WrongSequence` results. Receive state is therefore keyed by +/// `(source, key)` and created lazily the first time a source is seen. +/// +/// Transmit (protect) counter state stays per-key: a fan-out publish sends the +/// same protected bytes (one counter) to every subscriber, and per-recipient +/// transmit counters are handled a layer up (e.g. `iris_someip_client`). /// -/// `no_std`-friendly: backed by a fixed-capacity -/// [`FnvIndexMap`] so construction and the entire lifetime of the -/// registry are heap-free. Construction is `const`, so a `static` -/// instance can be declared in firmware boot code. +/// `no_std`-friendly: every map is a fixed-capacity [`FnvIndexMap`], so +/// construction and the entire lifetime of the registry are heap-free. +/// Construction is `const`, so a `static` instance can be declared in +/// firmware boot code. Profile/transmit slots are bounded by +/// [`E2E_REGISTRY_CAP`]; receive slots by [`E2E_RX_STATE_CAP`]. #[derive(Debug)] pub struct E2ERegistry { - map: FnvIndexMap, + /// Endpoint-agnostic profile configuration, keyed by data element. + configs: FnvIndexMap, + /// Receive counter state, per `(source, key)`. + rx_states: FnvIndexMap<(IpAddr, E2EKey), E2EState, E2E_RX_STATE_CAP>, + /// Transmit counter state, per key. + tx_states: FnvIndexMap, + /// Latches the one-shot `warn!` emitted when `rx_states` first + /// saturates, so an over-capacity subnet doesn't flood the logs. + rx_saturation_warned: bool, } impl E2ERegistry { @@ -52,14 +95,18 @@ impl E2ERegistry { #[must_use] pub const fn new() -> Self { Self { - map: FnvIndexMap::new(), + configs: FnvIndexMap::new(), + rx_states: FnvIndexMap::new(), + tx_states: FnvIndexMap::new(), + rx_saturation_warned: false, } } - /// Register an E2E profile for the given key, creating fresh state. + /// Register an E2E profile for the given key, creating fresh transmit + /// state and clearing any prior per-source receive state for the key. /// /// Replacing the profile of an already-registered key always - /// succeeds (the existing slot is reused). Adding a new key when + /// succeeds (the existing slots are reused). Adding a new key when /// the registry already holds [`E2E_REGISTRY_CAP`] entries returns /// [`Err(E2ERegistryFull)`](E2ERegistryFull); the caller is /// responsible for sizing the cap to its workload's high-water @@ -71,39 +118,75 @@ impl E2ERegistry { /// already present. pub fn register(&mut self, key: E2EKey, profile: E2EProfile) -> Result<(), E2ERegistryFull> { let state = E2EState::from_profile(&profile); - // `FnvIndexMap::insert` returns `Err((K, V))` only when the - // map is full AND `key` is not already present (replacing an - // existing entry never overflows). - match self.map.insert(key, (profile, state)) { - Ok(_) => Ok(()), - Err(_) => Err(E2ERegistryFull(E2E_REGISTRY_CAP)), + // `FnvIndexMap::insert` returns `Err((K, V))` only when the map is + // full AND `key` is not already present (replacing an existing + // entry never overflows). `configs` and `tx_states` share both the + // key set and `E2E_REGISTRY_CAP`, so we gate on `configs` first and + // the `tx_states` insert below can only ever replace-in-place. + if self.configs.insert(key, profile).is_err() { + return Err(E2ERegistryFull(E2E_REGISTRY_CAP)); } + let _ = self.tx_states.insert(key, state); + // A re-register restarts the counter, so drop stale per-source + // receive state for this key. + self.rx_states.retain(|(_, k), _| *k != key); + Ok(()) } - /// Remove E2E configuration for the given key. No-op if absent. + /// Remove E2E configuration (and all state) for the given key. pub fn unregister(&mut self, key: &E2EKey) { - self.map.remove(key); + self.configs.remove(key); + self.tx_states.remove(key); + self.rx_states.retain(|(_, k), _| k != key); } /// Returns `true` if a profile is registered for `key`. #[must_use] pub fn contains_key(&self, key: &E2EKey) -> bool { - self.map.contains_key(key) + self.configs.contains_key(key) } - /// Run E2E check for `key` if configured. + /// Run E2E check for `key` against `source`'s receive counter state, if + /// configured. /// - /// Returns `None` if no profile is registered for `key`. - /// Otherwise returns the check status and the best available payload - /// (stripped E2E header on success, original bytes on check failure). + /// Returns `None` if no profile is registered for `key`. Otherwise returns + /// the check status and the best available payload (stripped E2E header on + /// success, original bytes on check failure). pub fn check<'a>( &mut self, + source: IpAddr, key: E2EKey, payload: &'a [u8], upper_header: [u8; 8], ) -> Option<(E2ECheckStatus, &'a [u8])> { - let (profile, state) = self.map.get_mut(&key)?; - Some(e2e_check(profile, state, payload, upper_header)) + let profile = self.configs.get(&key)?; + // Per-source receive state, created lazily the first time a + // `(source, key)` pair is seen. When `rx_states` is at + // [`E2E_RX_STATE_CAP`] a brand-new source can't claim a slot; fall + // back to a transient counter so the CRC is still validated (only + // sequence continuity is lost) and warn once. + match self.rx_states.entry((source, key)) { + Entry::Occupied(occupied) => { + let state = occupied.into_mut(); + Some(e2e_check(profile, state, payload, upper_header)) + } + Entry::Vacant(vacant) => match vacant.insert(E2EState::from_profile(profile)) { + Ok(state) => Some(e2e_check(profile, state, payload, upper_header)), + Err(_full) => { + if !self.rx_saturation_warned { + self.rx_saturation_warned = true; + crate::log::warn!( + "E2E rx_states at capacity ({}); source {} falls back to a \ + transient counter — sequence continuity untracked until a slot frees", + E2E_RX_STATE_CAP, + source + ); + } + let mut transient = E2EState::from_profile(profile); + Some(e2e_check(profile, &mut transient, payload, upper_header)) + } + }, + } } /// Run E2E protect for `key` if configured. @@ -116,9 +199,17 @@ impl E2ERegistry { upper_header: [u8; 8], output: &mut [u8], ) -> Option> { - let (profile, state) = self.map.get_mut(&key)?; + let profile = self.configs.get(&key)?; + let state = self.tx_states.get_mut(&key)?; Some(e2e_protect(profile, state, payload, upper_header, output)) } + + /// Drop all per-source receive state for `source` (e.g. on its reboot), so + /// its next frame starts a fresh counter sequence. Configuration and + /// transmit state are untouched. + pub fn reset_source(&mut self, source: IpAddr) { + self.rx_states.retain(|(s, _), _| *s != source); + } } impl Default for E2ERegistry { @@ -131,11 +222,29 @@ impl Default for E2ERegistry { mod tests { use super::*; use crate::e2e::{Profile4Config, Profile5Config}; + use core::net::Ipv4Addr; fn make_key() -> E2EKey { E2EKey::new(0x1234, 0x5678) } + fn src() -> IpAddr { + IpAddr::V4(Ipv4Addr::LOCALHOST) + } + + fn make_profile5() -> E2EProfile { + E2EProfile::Profile5(Profile5Config::new(0x1234, 20, 15)) + } + + /// Protect a 20-byte "Hello" frame with `sender`'s next transmit counter, + /// writing into `out` and returning the protected length. Avoids `Vec` + /// because the crate's prelude is `core` (no_std-compatible). + fn protect_next(sender: &mut E2ERegistry, key: E2EKey, out: &mut [u8; 64]) -> usize { + let mut payload = [0u8; 20]; + payload[..5].copy_from_slice(b"Hello"); + sender.protect(key, &payload, [0; 8], out).unwrap().unwrap() + } + #[test] fn register_and_check_profile4() { let mut reg = E2ERegistry::new(); @@ -154,7 +263,7 @@ mod tests { .unwrap(); // Check it - let (status, stripped) = reg.check(key, &out[..len], [0; 8]).unwrap(); + let (status, stripped) = reg.check(src(), key, &out[..len], [0; 8]).unwrap(); assert_eq!(status, E2ECheckStatus::Ok); assert_eq!(stripped, payload); } @@ -163,8 +272,7 @@ mod tests { fn register_and_check_profile5() { let mut reg = E2ERegistry::new(); let key = make_key(); - let config = Profile5Config::new(0x1234, 20, 15); - reg.register(key, E2EProfile::Profile5(config)) + reg.register(key, make_profile5()) .expect("register fits within E2E_REGISTRY_CAP"); let mut payload = [0u8; 20]; @@ -175,17 +283,88 @@ mod tests { .unwrap() .unwrap(); - let (status, stripped) = reg.check(key, &out[..len], [0; 8]).unwrap(); + let (status, stripped) = reg.check(src(), key, &out[..len], [0; 8]).unwrap(); assert_eq!(status, E2ECheckStatus::Ok); assert_eq!(stripped, &payload); } + #[test] + fn distinct_sources_have_independent_e2e_state() { + let a = IpAddr::V4(Ipv4Addr::new(192, 168, 11, 101)); + let b = IpAddr::V4(Ipv4Addr::new(192, 168, 11, 102)); + let key = make_key(); + + // A sender produces two frames carrying counters 0 then 1. + let mut sender = E2ERegistry::new(); + sender.register(key, make_profile5()) + .expect("register fits within E2E_REGISTRY_CAP"); + let mut b0 = [0u8; 64]; + let l0 = protect_next(&mut sender, key, &mut b0); + let mut b1 = [0u8; 64]; + let l1 = protect_next(&mut sender, key, &mut b1); + + let mut recv = E2ERegistry::new(); + recv.register(key, make_profile5()) + .expect("register fits within E2E_REGISTRY_CAP"); + + // Source A consumes counters 0 then 1. + assert_eq!( + recv.check(a, key, &b0[..l0], [0; 8]).unwrap().0, + E2ECheckStatus::Ok + ); + assert_eq!( + recv.check(a, key, &b1[..l1], [0; 8]).unwrap().0, + E2ECheckStatus::Ok + ); + // Source B, interleaved AFTER A, starts its own counter sequence at 0. + // With shared (per-key) receive state this would flag b0 as + // out-of-sequence because A already advanced the single counter past 0. + assert_eq!( + recv.check(b, key, &b0[..l0], [0; 8]).unwrap().0, + E2ECheckStatus::Ok, + "source B's receive counter must be independent of source A's" + ); + assert_eq!( + recv.check(b, key, &b1[..l1], [0; 8]).unwrap().0, + E2ECheckStatus::Ok + ); + } + + #[test] + fn reset_source_clears_only_that_source() { + let a = IpAddr::V4(Ipv4Addr::new(192, 168, 11, 101)); + let key = make_key(); + + let mut sender = E2ERegistry::new(); + sender.register(key, make_profile5()) + .expect("register fits within E2E_REGISTRY_CAP"); + let mut b0 = [0u8; 64]; + let l0 = protect_next(&mut sender, key, &mut b0); + let mut b1 = [0u8; 64]; + let l1 = protect_next(&mut sender, key, &mut b1); + + let mut recv = E2ERegistry::new(); + recv.register(key, make_profile5()) + .expect("register fits within E2E_REGISTRY_CAP"); + recv.check(a, key, &b0[..l0], [0; 8]); + recv.check(a, key, &b1[..l1], [0; 8]); + + // After a reboot, source A starts fresh — its counter-0 frame is Ok + // again. + recv.reset_source(a); + assert_eq!( + recv.check(a, key, &b0[..l0], [0; 8]).unwrap().0, + E2ECheckStatus::Ok, + "reset_source(a) restarts A's receive counter sequence" + ); + } + #[test] fn unregistered_key_returns_none() { let mut reg = E2ERegistry::new(); let key = make_key(); assert!(!reg.contains_key(&key)); - assert!(reg.check(key, b"test", [0; 8]).is_none()); + assert!(reg.check(src(), key, b"test", [0; 8]).is_none()); assert!(reg.protect(key, b"test", [0; 8], &mut [0; 64]).is_none()); } diff --git a/src/lib.rs b/src/lib.rs index 408be122..80cb825c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ //! while let Some(update) = updates.recv().await { //! match update { //! ClientUpdate::DiscoveryUpdated(msg) => { /* SD message received */ } -//! ClientUpdate::Unicast { message, e2e_status } => { /* unicast reply */ } +//! ClientUpdate::Unicast { message, e2e_status, source } => { /* unicast reply */ } //! ClientUpdate::SenderRebooted(addr) => { /* remote reboot */ } //! ClientUpdate::Error(err) => { /* error */ } //! } diff --git a/src/sd_codec.rs b/src/sd_codec.rs index 95629148..71cbab50 100644 --- a/src/sd_codec.rs +++ b/src/sd_codec.rs @@ -8,7 +8,7 @@ //! [`crate::bare_metal_tasks`] and the firmware's publish/deinit FFI, so //! that no SOME/IP byte-encoding or header-parsing lives in the firmware. -use core::net::Ipv4Addr; +use core::net::{IpAddr, Ipv4Addr}; use core::sync::atomic::{AtomicU16, Ordering}; use crate::WireFormat; @@ -402,20 +402,26 @@ pub fn parse_someip_sd_datagram(data: &[u8]) -> Option> { SdHeaderView::parse(sd_payload).ok() } -/// Run an E2E check for `parsed` against `e2e`. Returns +/// Run an E2E check for `parsed` against `e2e`, keyed by `source`. Returns /// `(Unchecked, parsed.payload)` when no profile is registered for the /// `(service_id, method_id)` pair. Generic over [`E2ERegistryHandle`] so /// it works with any handle (the bare-metal `StaticE2EHandle` included). +/// +/// `source` is the sender's IP: receive E2E counter state is tracked per +/// source so several devices sending the same `(service, method)` on a +/// shared subnet don't collide into spurious sequence errors. See +/// [`crate::e2e::E2ERegistry`]. #[must_use] pub fn check_parsed_e2e<'a, R: E2ERegistryHandle>( e2e: &R, + source: IpAddr, parsed: &ParsedDatagram<'a>, ) -> (E2ECheckStatus, &'a [u8]) { let key = E2EKey::from_message_id(MessageId::new_from_service_and_method( parsed.service_id, parsed.method_id, )); - match e2e.check(key, parsed.payload, parsed.upper_header) { + match e2e.check(source, key, parsed.payload, parsed.upper_header) { Some((status, body)) => (status, body), None => (E2ECheckStatus::Unchecked, parsed.payload), } diff --git a/src/server/runtime.rs b/src/server/runtime.rs index 8beb09e2..030d69ff 100644 --- a/src/server/runtime.rs +++ b/src/server/runtime.rs @@ -536,7 +536,8 @@ async fn dispatch_non_sd_request( upper_header: hdr.upper_header_bytes(), payload: view.payload_bytes(), }; - let (status, body) = crate::sd_codec::check_parsed_e2e(e2e, &parsed); + let (status, body) = + crate::sd_codec::check_parsed_e2e(e2e, core::net::IpAddr::V4(*source.ip()), &parsed); let resp_len = cb( ctx, source, diff --git a/src/transport.rs b/src/transport.rs index 8529046e..f2e7ea68 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -227,7 +227,7 @@ //! outside this trait. use core::future::Future; -use core::net::{Ipv4Addr, SocketAddrV4}; +use core::net::{IpAddr, Ipv4Addr, SocketAddrV4}; use core::time::Duration; use crate::e2e::Error as E2EError; @@ -805,20 +805,33 @@ pub trait E2ERegistryHandle: Clone + Send + Sync + 'static { output: &mut [u8], ) -> Option>; - /// Run E2E check for `key` if configured. + /// Run E2E check for `key` against `source`'s receive counter state, + /// if configured. /// /// Returns `None` if no profile is registered for `key`. Otherwise /// returns the check status and the effective payload slice — the /// E2E header is stripped on success; the original bytes are returned /// on check failure so the caller can decide how to handle it. /// + /// `source` keys the receive counter state: on a shared subnet several + /// devices send the same `(service, method)` under one instance id, so + /// each sender's sequence counter must be tracked independently. See + /// [`crate::e2e::E2ERegistry`]. + /// /// The returned slice borrows from `payload`, not from this handle. fn check<'a>( &self, + source: IpAddr, key: E2EKey, payload: &'a [u8], upper_header: [u8; 8], ) -> Option<(E2ECheckStatus, &'a [u8])>; + + /// Drop all per-source receive counter state for `source` (e.g. when + /// its reboot is detected via Service Discovery), so its next frame + /// starts a fresh sequence. Configuration and transmit state are + /// untouched. + fn reset_source(&self, source: IpAddr); } /// Shared handle to the local interface address. @@ -937,7 +950,7 @@ mod std_handle_impls { use super::{E2ERegistryHandle, InterfaceHandle}; use crate::e2e::Error as E2EError; use crate::e2e::{E2ECheckStatus, E2EKey, E2EProfile, E2ERegistry, E2ERegistryFull}; - use core::net::Ipv4Addr; + use core::net::{IpAddr, Ipv4Addr}; use std::sync::{Arc, Mutex, RwLock}; impl E2ERegistryHandle for Arc> { @@ -976,13 +989,20 @@ mod std_handle_impls { fn check<'a>( &self, + source: IpAddr, key: E2EKey, payload: &'a [u8], upper_header: [u8; 8], ) -> Option<(E2ECheckStatus, &'a [u8])> { self.lock() .expect("e2e registry lock poisoned") - .check(key, payload, upper_header) + .check(source, key, payload, upper_header) + } + + fn reset_source(&self, source: IpAddr) { + self.lock() + .expect("e2e registry lock poisoned") + .reset_source(source); } } @@ -1108,6 +1128,7 @@ pub mod bare_metal_e2e_impl { E2ECheckStatus, E2EKey, E2EProfile, E2ERegistry, E2ERegistryFull, Error as E2EError, }; use core::cell::RefCell; + use core::net::IpAddr; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; @@ -1159,12 +1180,17 @@ pub mod bare_metal_e2e_impl { fn check<'a>( &self, + source: IpAddr, key: E2EKey, payload: &'a [u8], upper_header: [u8; 8], ) -> Option<(E2ECheckStatus, &'a [u8])> { self.0 - .lock(|cell| cell.borrow_mut().check(key, payload, upper_header)) + .lock(|cell| cell.borrow_mut().check(source, key, payload, upper_header)) + } + + fn reset_source(&self, source: IpAddr) { + self.0.lock(|cell| cell.borrow_mut().reset_source(source)); } } } @@ -1429,7 +1455,7 @@ pub mod probe { }; use crate::e2e::{E2ECheckStatus, E2EKey, E2EProfile, Error as E2EError}; use core::future::Future; - use core::net::{Ipv4Addr, SocketAddrV4}; + use core::net::{IpAddr, Ipv4Addr, SocketAddrV4}; use core::time::Duration; /// Socket whose I/O futures resolve immediately with @@ -1532,12 +1558,14 @@ pub mod probe { } fn check<'a>( &self, + _source: IpAddr, _key: E2EKey, _payload: &'a [u8], _upper_header: [u8; 8], ) -> Option<(E2ECheckStatus, &'a [u8])> { None } + fn reset_source(&self, _source: IpAddr) {} } /// Interface handle pinned to a fixed address. @@ -1689,7 +1717,11 @@ mod tests { ) .expect("NullE2ERegistry::register is infallible"); assert!(!r.contains_key(&key)); - assert!(r.check(key, b"hello", [0; 8]).is_none()); + assert!( + r.check(Ipv4Addr::LOCALHOST.into(), key, b"hello", [0; 8]) + .is_none() + ); + r.reset_source(Ipv4Addr::LOCALHOST.into()); // no-op in null impl } #[test] diff --git a/tests/no_alloc_witness.rs b/tests/no_alloc_witness.rs index 0466ffdb..2aa05ec9 100644 --- a/tests/no_alloc_witness.rs +++ b/tests/no_alloc_witness.rs @@ -201,7 +201,12 @@ fn witness_static_e2e_handle_reads() { assert_no_alloc("StaticE2EHandle::check (absent key → None)", || { assert!( handle - .check(E2EKey::new(0xFFFF, 0x0000), b"payload", [0u8; 8]) + .check( + Ipv4Addr::LOCALHOST.into(), + E2EKey::new(0xFFFF, 0x0000), + b"payload", + [0u8; 8] + ) .is_none() ); }); @@ -245,7 +250,7 @@ fn witness_static_e2e_handle_protect_check() { .expect("profile registered") .expect("protect succeeded"); let (status, stripped) = handle - .check(key, &protected[..len], [0u8; 8]) + .check(Ipv4Addr::LOCALHOST.into(), key, &protected[..len], [0u8; 8]) .expect("profile registered"); assert_eq!(status, simple_someip::E2ECheckStatus::Ok); assert_eq!(stripped, payload); @@ -262,7 +267,7 @@ fn witness_static_e2e_handle_protect_check() { .expect("profile registered") .expect("protect succeeded"); let (status, stripped) = handle - .check(key5, &protected5[..len], [0u8; 8]) + .check(Ipv4Addr::LOCALHOST.into(), key5, &protected5[..len], [0u8; 8]) .expect("profile registered"); assert_eq!(status, simple_someip::E2ECheckStatus::Ok); assert_eq!(stripped, payload);