#![cfg(all(feature = "server", feature = "bare_metal"))]
use core::future::Future;
use core::net::{Ipv4Addr, SocketAddrV4};
use core::pin::Pin;
use core::task::{Context, Poll};
use core::time::Duration;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::vec::Vec;
use simple_someip::e2e::E2ERegistry;
use simple_someip::server::NonSdRequestCallback;
use simple_someip::server::ServerConfig;
use simple_someip::server::{SubscribeError, Subscriber, SubscriptionHandle};
use simple_someip::transport::{
ReceivedDatagram, SocketOptions, Timer, TransportError, TransportFactory, TransportSocket,
};
use simple_someip::{Server, ServerDeps};
#[derive(Default)]
struct MockPipe {
sent: Mutex<VecDeque<(Vec<u8>, SocketAddrV4)>>,
inbound: Mutex<VecDeque<(Vec<u8>, SocketAddrV4)>>,
inbound_waker: Mutex<Option<core::task::Waker>>,
}
#[derive(Clone)]
struct MockFactory {
unicast_pipe: Arc<MockPipe>,
sd_pipe: Arc<MockPipe>,
next_port: Arc<Mutex<u16>>,
}
impl TransportFactory for MockFactory {
type Socket = MockSocket;
type BindFuture<'a> =
core::pin::Pin<Box<dyn Future<Output = Result<Self::Socket, TransportError>> + Send + 'a>>;
fn bind<'a>(&'a self, addr: SocketAddrV4, options: &'a SocketOptions) -> Self::BindFuture<'a> {
let pipe = if options.multicast_if_v4.is_some() {
Arc::clone(&self.sd_pipe)
} else {
Arc::clone(&self.unicast_pipe)
};
let port = if addr.port() == 0 {
let mut p = self.next_port.lock().unwrap();
let next = *p + 1;
*p = next;
40000 + next
} else {
addr.port()
};
let local = SocketAddrV4::new(*addr.ip(), port);
Box::pin(async move { Ok(MockSocket { pipe, local }) })
}
}
struct MockSocket {
pipe: Arc<MockPipe>,
local: SocketAddrV4,
}
struct MockSendFut {
pipe: Arc<MockPipe>,
bytes: Option<Vec<u8>>,
target: SocketAddrV4,
}
impl Future for MockSendFut {
type Output = Result<(), TransportError>;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = self.get_mut();
if let Some(bytes) = me.bytes.take() {
me.pipe.sent.lock().unwrap().push_back((bytes, me.target));
}
Poll::Ready(Ok(()))
}
}
struct MockRecvFut<'a> {
pipe: Arc<MockPipe>,
buf: &'a mut [u8],
}
impl Future for MockRecvFut<'_> {
type Output = Result<ReceivedDatagram, TransportError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = self.get_mut();
let entry = me.pipe.inbound.lock().unwrap().pop_front();
match entry {
Some((bytes, source)) => {
let n = bytes.len().min(me.buf.len());
me.buf[..n].copy_from_slice(&bytes[..n]);
Poll::Ready(Ok(ReceivedDatagram {
bytes_received: n,
source,
truncated: n < bytes.len(),
}))
}
None => {
*me.pipe.inbound_waker.lock().unwrap() = Some(cx.waker().clone());
if let Some((bytes, source)) = me.pipe.inbound.lock().unwrap().pop_front() {
let n = bytes.len().min(me.buf.len());
me.buf[..n].copy_from_slice(&bytes[..n]);
return Poll::Ready(Ok(ReceivedDatagram {
bytes_received: n,
source,
truncated: n < bytes.len(),
}));
}
Poll::Pending
}
}
}
}
impl TransportSocket for MockSocket {
type SendFuture<'a> = MockSendFut;
type RecvFuture<'a> = MockRecvFut<'a>;
fn send_to<'a>(&'a self, buf: &'a [u8], target: SocketAddrV4) -> Self::SendFuture<'a> {
MockSendFut {
pipe: Arc::clone(&self.pipe),
bytes: Some(buf.to_vec()),
target,
}
}
fn recv_from<'a>(&'a self, buf: &'a mut [u8]) -> Self::RecvFuture<'a> {
MockRecvFut {
pipe: Arc::clone(&self.pipe),
buf,
}
}
fn local_addr(&self) -> Result<SocketAddrV4, TransportError> {
Ok(self.local)
}
fn join_multicast_v4(&self, _group: Ipv4Addr, _iface: Ipv4Addr) -> Result<(), TransportError> {
Ok(())
}
fn leave_multicast_v4(&self, _group: Ipv4Addr, _iface: Ipv4Addr) -> Result<(), TransportError> {
Ok(())
}
}
#[derive(Clone)]
struct MockTimer;
impl Timer for MockTimer {
type SleepFuture<'a> = core::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
fn sleep(&self, duration: Duration) -> Self::SleepFuture<'_> {
Box::pin(async move {
tokio::time::sleep(duration).await;
})
}
}
type SubKey = (u16, u16, u16, SocketAddrV4);
#[derive(Clone, Default)]
#[allow(clippy::type_complexity)]
struct MockSubscriptions(Arc<Mutex<Vec<SubKey>>>);
impl SubscriptionHandle for MockSubscriptions {
type SubscribeFuture<'a> =
core::pin::Pin<Box<dyn Future<Output = Result<(), SubscribeError>> + Send + 'a>>;
type UnsubscribeFuture<'a> = core::pin::Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
type ForEachFuture<'a> = core::pin::Pin<Box<dyn Future<Output = usize> + Send + 'a>>;
fn subscribe(
&self,
service_id: u16,
instance_id: u16,
event_group_id: u16,
subscriber_addr: SocketAddrV4,
) -> Self::SubscribeFuture<'_> {
let this = self.0.clone();
Box::pin(async move {
let mut guard = this.lock().unwrap();
let key = (service_id, instance_id, event_group_id, subscriber_addr);
if !guard.contains(&key) {
guard.push(key);
}
Ok(())
})
}
fn unsubscribe(
&self,
service_id: u16,
instance_id: u16,
event_group_id: u16,
subscriber_addr: SocketAddrV4,
) -> Self::UnsubscribeFuture<'_> {
let this = self.0.clone();
Box::pin(async move {
let mut guard = this.lock().unwrap();
guard.retain(|e| *e != (service_id, instance_id, event_group_id, subscriber_addr));
})
}
fn for_each_subscriber<'a>(
&'a self,
service_id: u16,
instance_id: u16,
event_group_id: u16,
f: &'a mut (dyn FnMut(&Subscriber) + Send),
) -> Self::ForEachFuture<'a> {
let this = self.0.clone();
Box::pin(async move {
let guard = this.lock().unwrap();
let mut count = 0;
for (s, i, e, addr) in guard.iter() {
if *s == service_id && *i == instance_id && *e == event_group_id {
let sub = Subscriber::new(*addr, *s, *i, *e);
f(&sub);
count += 1;
}
}
count
})
}
}
#[tokio::test]
async fn server_constructible_without_server_tokio_feature() {
let factory = MockFactory {
unicast_pipe: Arc::new(MockPipe::default()),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let subs = MockSubscriptions::default();
let config = ServerConfig::new(0x5B, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30490);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: subs,
non_sd_observer: None,
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed with no-tokio mocks");
let handle = tokio::spawn(run);
tokio::task::yield_now().await;
tokio::task::yield_now().await;
handle.abort();
let _ = handle.await;
}
#[tokio::test]
async fn passive_server_constructible_without_server_tokio_feature() {
let factory = MockFactory {
unicast_pipe: Arc::new(MockPipe::default()),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let subs = MockSubscriptions::default();
let config = ServerConfig::new(0x5C, 2)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(0);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: subs,
non_sd_observer: None,
};
let (_server, _handles, _run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_passive_with_deps(deps, config)
.await
.expect("Server::new_passive_with_deps must succeed with no-tokio mocks");
}
use std::sync::OnceLock;
static OBSERVED_SOME: OnceLock<Mutex<Option<(usize, SocketAddrV4, u16, u16, Vec<u8>, u8)>>> =
OnceLock::new();
fn record_some(
ctx: usize,
source: SocketAddrV4,
service_id: u16,
method_id: u16,
payload: &[u8],
e2e_status: u8,
_response_out: &mut [u8],
) -> i32 {
let slot = OBSERVED_SOME.get_or_init(|| Mutex::new(None));
*slot.lock().unwrap() = Some((
ctx,
source,
service_id,
method_id,
payload.to_vec(),
e2e_status,
));
-1 }
static OBSERVED_SD_UNICAST: OnceLock<Mutex<Option<(usize, SocketAddrV4, u16, u16, Vec<u8>, u8)>>> =
OnceLock::new();
static OBSERVED_MULTICAST: OnceLock<Mutex<Option<(usize, SocketAddrV4, u16, u16, Vec<u8>, u8)>>> =
OnceLock::new();
fn record_sd_unicast(
ctx: usize,
source: SocketAddrV4,
service_id: u16,
method_id: u16,
payload: &[u8],
e2e_status: u8,
_response_out: &mut [u8],
) -> i32 {
let slot = OBSERVED_SD_UNICAST.get_or_init(|| Mutex::new(None));
*slot.lock().unwrap() = Some((
ctx,
source,
service_id,
method_id,
payload.to_vec(),
e2e_status,
));
-1 }
fn record_multicast(
ctx: usize,
source: SocketAddrV4,
service_id: u16,
method_id: u16,
payload: &[u8],
e2e_status: u8,
_response_out: &mut [u8],
) -> i32 {
let slot = OBSERVED_MULTICAST.get_or_init(|| Mutex::new(None));
*slot.lock().unwrap() = Some((
ctx,
source,
service_id,
method_id,
payload.to_vec(),
e2e_status,
));
-1 }
fn build_method_request(service_id: u16, method_id: u16, payload: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(16 + payload.len());
buf.extend_from_slice(&service_id.to_be_bytes()); buf.extend_from_slice(&method_id.to_be_bytes()); buf.extend_from_slice(&(8u32 + payload.len() as u32).to_be_bytes()); buf.extend_from_slice(&0u32.to_be_bytes()); buf.push(1); buf.push(1); buf.push(0); buf.push(0); buf.extend_from_slice(payload);
buf
}
fn build_sd_message() -> Vec<u8> {
let mut buf = Vec::with_capacity(28);
buf.extend_from_slice(&0xFFFFu16.to_be_bytes()); buf.extend_from_slice(&0x8100u16.to_be_bytes()); buf.extend_from_slice(&20u32.to_be_bytes()); buf.extend_from_slice(&0u32.to_be_bytes()); buf.push(1); buf.push(1); buf.push(2); buf.push(0); buf.push(0x80); buf.extend_from_slice(&[0, 0, 0]); buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&0u32.to_be_bytes()); buf
}
async fn drive_until<F: FnMut() -> bool>(mut check: F) {
for _ in 0..200 {
if check() {
return;
}
tokio::task::yield_now().await;
}
panic!("timed out waiting for condition (callback never fired or assertion never held)");
}
#[tokio::test]
async fn non_sd_observer_some_receives_unicast_method_request() {
let unicast_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let subs = MockSubscriptions::default();
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30700);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: subs,
non_sd_observer: Some((record_some as NonSdRequestCallback, 0xC0FF_EE00)),
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let datagram = build_method_request(0x1234, 0x0001, &[0xDE, 0xAD, 0xBE, 0xEF]);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 100), 40000);
unicast_pipe
.inbound
.lock()
.unwrap()
.push_back((datagram.clone(), src));
if let Some(w) = unicast_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| {
OBSERVED_SOME
.get()
.and_then(|m| m.lock().unwrap().clone())
.is_some()
})
.await;
let (got_ctx, got_src, got_service, got_method, got_payload, got_e2e) = OBSERVED_SOME
.get()
.unwrap()
.lock()
.unwrap()
.clone()
.expect("callback fired");
assert_eq!(
got_ctx, 0xC0FF_EE00,
"callback must receive the registered ctx word verbatim"
);
assert_eq!(got_src, src, "callback must receive the original source");
assert_eq!(got_service, 0x1234, "decoded service id");
assert_eq!(got_method, 0x0001, "decoded method id");
assert_eq!(
got_payload,
[0xDE, 0xAD, 0xBE, 0xEF],
"payload must be the bytes after the 16-byte header"
);
assert_eq!(got_e2e, 0, "server-side requests are not E2E-checked today");
handle.abort();
let _ = handle.await;
}
#[tokio::test]
async fn non_sd_observer_ignores_sd_message_on_unicast_socket() {
let unicast_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30702);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: MockSubscriptions::default(),
non_sd_observer: Some((record_sd_unicast as NonSdRequestCallback, 7)),
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 102), 40002);
unicast_pipe
.inbound
.lock()
.unwrap()
.push_back((build_sd_message(), src));
if let Some(w) = unicast_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| unicast_pipe.inbound.lock().unwrap().is_empty()).await;
tokio::task::yield_now().await;
assert!(
!handle.is_finished(),
"run-future must still be alive after processing the datagram"
);
let observed = OBSERVED_SD_UNICAST
.get()
.and_then(|m| m.lock().unwrap().clone());
assert!(
observed.is_none(),
"observer must NOT fire for SD messages; got {observed:?}"
);
handle.abort();
let _ = handle.await;
}
#[tokio::test]
async fn non_sd_observer_ignores_non_sd_on_multicast_socket() {
let sd_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::new(MockPipe::default()),
sd_pipe: Arc::clone(&sd_pipe),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30703);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: MockSubscriptions::default(),
non_sd_observer: Some((record_multicast as NonSdRequestCallback, 9)),
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 103), 40003);
sd_pipe
.inbound
.lock()
.unwrap()
.push_back((build_method_request(0x1234, 0x0001, &[]), src));
if let Some(w) = sd_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| sd_pipe.inbound.lock().unwrap().is_empty()).await;
tokio::task::yield_now().await;
assert!(
!handle.is_finished(),
"run-future must still be alive after processing the datagram"
);
let observed = OBSERVED_MULTICAST
.get()
.and_then(|m| m.lock().unwrap().clone());
assert!(
observed.is_none(),
"observer must NOT fire for non-unicast datagrams; got {observed:?}"
);
handle.abort();
let _ = handle.await;
}
#[tokio::test]
async fn non_sd_observer_none_preserves_ignore_behavior() {
let unicast_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30701);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: MockSubscriptions::default(),
non_sd_observer: None,
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 101), 40001);
unicast_pipe
.inbound
.lock()
.unwrap()
.push_back((build_method_request(0x1234, 0x0001, &[]), src));
if let Some(w) = unicast_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| unicast_pipe.inbound.lock().unwrap().is_empty()).await;
tokio::task::yield_now().await;
assert!(
!handle.is_finished(),
"run-future must keep running (no panic / no error) after \
ignoring a non-SD datagram with no observer registered"
);
handle.abort();
let _ = handle.await;
}
fn build_method_request_with_id(
service_id: u16,
method_id: u16,
request_id: u32,
payload: &[u8],
) -> Vec<u8> {
let mut buf = Vec::with_capacity(16 + payload.len());
buf.extend_from_slice(&service_id.to_be_bytes());
buf.extend_from_slice(&method_id.to_be_bytes());
buf.extend_from_slice(&(8u32 + payload.len() as u32).to_be_bytes());
buf.extend_from_slice(&request_id.to_be_bytes());
buf.push(1); buf.push(1); buf.push(0); buf.push(0); buf.extend_from_slice(payload);
buf
}
fn respond_with_body(
_ctx: usize,
_source: SocketAddrV4,
_service_id: u16,
_method_id: u16,
_payload: &[u8],
_e2e_status: u8,
response_out: &mut [u8],
) -> i32 {
let body = [0x11u8, 0x22, 0x33];
response_out[..body.len()].copy_from_slice(&body);
body.len() as i32
}
fn respond_oversized(
_ctx: usize,
_source: SocketAddrV4,
_service_id: u16,
_method_id: u16,
_payload: &[u8],
_e2e_status: u8,
response_out: &mut [u8],
) -> i32 {
(response_out.len() + 100) as i32
}
#[tokio::test]
async fn non_sd_responder_frames_and_sends_response() {
let unicast_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30702);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: MockSubscriptions::default(),
non_sd_observer: Some((respond_with_body as NonSdRequestCallback, 0)),
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 110), 40010);
let request_id = 0xABCD_1234u32;
unicast_pipe.inbound.lock().unwrap().push_back((
build_method_request_with_id(0x1234, 0x0001, request_id, &[0xDE, 0xAD]),
src,
));
if let Some(w) = unicast_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| !unicast_pipe.sent.lock().unwrap().is_empty()).await;
let (resp, target) = unicast_pipe
.sent
.lock()
.unwrap()
.pop_front()
.expect("a RESPONSE datagram must be sent");
assert_eq!(target, src, "response goes back to the requester");
assert!(resp.len() >= 16, "response has a full SOME/IP header");
assert_eq!(&resp[0..2], &0x1234u16.to_be_bytes(), "service id");
assert_eq!(&resp[2..4], &0x0001u16.to_be_bytes(), "method id");
assert_eq!(
&resp[4..8],
&(8u32 + 3).to_be_bytes(),
"length = 8 (upper header) + 3-byte body"
);
assert_eq!(
&resp[8..12],
&request_id.to_be_bytes(),
"request id echoed verbatim"
);
assert_eq!(resp[14], 0x80, "message type = Response");
assert_eq!(&resp[16..], &[0x11, 0x22, 0x33], "body the callback wrote");
handle.abort();
let _ = handle.await;
}
#[tokio::test]
async fn non_sd_responder_oversized_length_is_rejected_not_panicked() {
let unicast_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::new(MockPipe::default()),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30703);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: MockSubscriptions::default(),
non_sd_observer: Some((respond_oversized as NonSdRequestCallback, 0)),
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 111), 40011);
unicast_pipe
.inbound
.lock()
.unwrap()
.push_back((build_method_request(0x1234, 0x0001, &[]), src));
if let Some(w) = unicast_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| unicast_pipe.inbound.lock().unwrap().is_empty()).await;
tokio::task::yield_now().await;
assert!(
unicast_pipe.sent.lock().unwrap().is_empty(),
"an over-range response length must be dropped, not framed/sent"
);
assert!(
!handle.is_finished(),
"run-future must survive a contract-violating response length \
(no out-of-bounds slice panic)"
);
handle.abort();
let _ = handle.await;
}
async fn drive_co_offer_subscribe(local_port: u16, subscribe_major: u8) -> Vec<SubKey> {
use simple_someip::protocol::sd::RebootFlag;
use simple_someip::sd_codec::{
SubscribeEventgroupRequest, build_subscribe_eventgroup_datagram,
};
let unicast_pipe = Arc::new(MockPipe::default());
let sd_pipe = Arc::new(MockPipe::default());
let factory = MockFactory {
unicast_pipe: Arc::clone(&unicast_pipe),
sd_pipe: Arc::clone(&sd_pipe),
next_port: Arc::new(Mutex::new(0)),
};
let e2e_handle: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let subs_log: Arc<Mutex<Vec<SubKey>>> = Arc::new(Mutex::new(Vec::new()));
let subs = MockSubscriptions(subs_log.clone());
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(local_port)
.with_accepted_offer(0x5678, 1, 2, 0x0001);
let deps: ServerDeps<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions> =
ServerDeps {
factory,
timer: MockTimer,
e2e_registry: e2e_handle,
subscriptions: subs,
non_sd_observer: None,
};
let (_server, _handles, run): (
Server<MockFactory, MockTimer, Arc<Mutex<E2ERegistry>>, MockSubscriptions>,
_,
_,
) = Server::new_with_deps(deps, config, false)
.await
.expect("Server::new_with_deps must succeed");
let handle = tokio::spawn(run);
let req = SubscribeEventgroupRequest {
service_id: 0x5678,
instance_id: 1,
major_version: subscribe_major,
event_group_id: 0x0001,
ttl: 0x00FF_FFFF,
local_ip: Ipv4Addr::new(192, 0, 2, 200),
local_rx_port: 45_000,
};
let mut buf = [0u8; 256];
let n = build_subscribe_eventgroup_datagram(&mut buf, &req, 1, RebootFlag::RecentlyRebooted)
.expect("encode subscribe");
let src = SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 200), 45_000);
sd_pipe
.inbound
.lock()
.unwrap()
.push_back((buf[..n].to_vec(), src));
if let Some(w) = sd_pipe.inbound_waker.lock().unwrap().take() {
w.wake();
}
drive_until(|| sd_pipe.inbound.lock().unwrap().is_empty()).await;
for _ in 0..50 {
tokio::task::yield_now().await;
}
handle.abort();
let _ = handle.await;
let recorded = subs_log.lock().unwrap().clone();
recorded
}
#[tokio::test]
async fn co_offered_subscribe_with_matching_major_version_is_accepted() {
let recorded = drive_co_offer_subscribe(30710, 2).await;
assert_eq!(
recorded.len(),
1,
"co-offered subscribe with the registered major version must be accepted"
);
assert_eq!(
recorded[0].0, 0x5678,
"subscription recorded for the co-offered service"
);
}
#[tokio::test]
async fn co_offered_subscribe_with_wrong_major_version_is_rejected() {
let recorded = drive_co_offer_subscribe(30711, 99).await;
assert!(
recorded.is_empty(),
"co-offered subscribe with a mismatched major version must be rejected, \
not silently accepted by skipping the version guard"
);
}