mod error;
mod event_publisher;
mod service_info;
mod subscription_manager;
pub use error::Error;
pub use event_publisher::EventPublisher;
pub use service_info::{EventGroupInfo, ServiceInfo};
pub use subscription_manager::SubscriptionManager;
use crate::e2e::{E2EKey, E2EProfile, E2ERegistry};
use crate::protocol::sd::{self, Entry, Flags, OptionsCount, ServiceEntry, TransportProtocol};
use core::sync::atomic::Ordering;
use std::{
format,
net::{IpAddr, Ipv4Addr, SocketAddrV4},
sync::{Arc, Mutex, atomic::AtomicU16},
vec,
vec::Vec,
};
use tokio::{net::UdpSocket, sync::RwLock};
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub interface: Ipv4Addr,
pub local_port: u16,
pub service_id: u16,
pub instance_id: u16,
pub major_version: u8,
pub minor_version: u32,
pub ttl: u32,
}
impl ServerConfig {
#[must_use]
pub fn new(interface: Ipv4Addr, local_port: u16, service_id: u16, instance_id: u16) -> Self {
Self {
interface,
local_port,
service_id,
instance_id,
major_version: 1,
minor_version: 0,
ttl: 3, }
}
}
pub struct Server {
config: ServerConfig,
unicast_socket: Arc<UdpSocket>,
sd_socket: Arc<UdpSocket>,
subscriptions: Arc<RwLock<SubscriptionManager>>,
publisher: Arc<EventPublisher>,
sd_session_id: Arc<AtomicU16>,
e2e_registry: Arc<Mutex<E2ERegistry>>,
is_passive: bool,
}
impl Server {
pub async fn new(config: ServerConfig) -> Result<Self, Error> {
let unicast_addr = SocketAddrV4::new(config.interface, config.local_port);
let unicast_socket = Arc::new(UdpSocket::bind(unicast_addr).await?);
tracing::info!(
"Server bound to {} for service 0x{:04X}",
unicast_addr,
config.service_id
);
let expected_sd_port = sd::MULTICAST_PORT;
let sd_bind_addr =
std::net::SocketAddr::new(IpAddr::V4(config.interface), expected_sd_port);
let sd_raw_socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::DGRAM,
Some(socket2::Protocol::UDP),
)?;
sd_raw_socket.set_reuse_address(true)?;
#[cfg(unix)]
sd_raw_socket.set_reuse_port(true)?;
sd_raw_socket.set_multicast_if_v4(&config.interface)?;
sd_raw_socket.set_multicast_loop_v4(false)?;
sd_raw_socket.bind(&sd_bind_addr.into())?;
sd_raw_socket.set_nonblocking(true)?;
let sd_std_socket: std::net::UdpSocket = sd_raw_socket.into();
let sd_socket = UdpSocket::from_std(sd_std_socket)?;
sd_socket.join_multicast_v4(sd::MULTICAST_IP, config.interface)?;
let actual_sd_addr = sd_socket.local_addr()?;
tracing::info!(
"Server SD socket bound to {} (expected port {}), joined multicast {}",
actual_sd_addr,
expected_sd_port,
sd::MULTICAST_IP
);
if let std::net::SocketAddr::V4(v4) = actual_sd_addr
&& v4.port() != expected_sd_port
{
tracing::error!(
"SD socket port mismatch! Expected {}, got {}. Offers will use wrong source port.",
expected_sd_port,
v4.port()
);
}
let subscriptions = Arc::new(RwLock::new(SubscriptionManager::new()));
let e2e_registry = Arc::new(Mutex::new(E2ERegistry::new()));
let publisher = Arc::new(EventPublisher::new(
Arc::clone(&subscriptions),
Arc::clone(&unicast_socket),
Arc::clone(&e2e_registry),
));
Ok(Self {
config,
unicast_socket,
sd_socket: Arc::new(sd_socket),
subscriptions,
publisher,
sd_session_id: Arc::new(AtomicU16::new(1)),
e2e_registry,
is_passive: false,
})
}
pub async fn new_passive(config: ServerConfig) -> Result<Self, Error> {
let unicast_addr = SocketAddrV4::new(config.interface, config.local_port);
let unicast_socket = Arc::new(UdpSocket::bind(unicast_addr).await?);
tracing::info!(
"Passive server bound to {} for service 0x{:04X}",
unicast_addr,
config.service_id
);
let sd_placeholder_addr = std::net::SocketAddr::new(IpAddr::V4(config.interface), 0);
let sd_socket = UdpSocket::bind(sd_placeholder_addr).await?;
tracing::info!(
"Passive server SD placeholder socket bound to {:?} (not in SD reuseport group)",
sd_socket.local_addr()
);
let subscriptions = Arc::new(RwLock::new(SubscriptionManager::new()));
let e2e_registry = Arc::new(Mutex::new(E2ERegistry::new()));
let publisher = Arc::new(EventPublisher::new(
Arc::clone(&subscriptions),
Arc::clone(&unicast_socket),
Arc::clone(&e2e_registry),
));
Ok(Self {
config,
unicast_socket,
sd_socket: Arc::new(sd_socket),
subscriptions,
publisher,
sd_session_id: Arc::new(AtomicU16::new(1)),
e2e_registry,
is_passive: true,
})
}
pub fn start_announcing(&self) -> Result<(), Error> {
if self.is_passive {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"start_announcing called on passive Server for service 0x{:04X}; \
announcements must be driven externally (e.g. via \
`simple_someip::Client::start_sd_announcements`)",
self.config.service_id
),
)));
}
let config = self.config.clone();
let sd_socket = Arc::clone(&self.sd_socket);
let sd_session_id = Arc::clone(&self.sd_session_id);
tokio::spawn(async move {
let mut announcement_count = 0u32;
loop {
match Self::send_offer_service(&config, &sd_socket, &sd_session_id).await {
Ok(()) => {
announcement_count += 1;
if announcement_count == 1 {
tracing::info!(
"Sent first SD announcement for service 0x{:04X}",
config.service_id
);
} else {
tracing::debug!(
"Sent {} SD announcements for service 0x{:04X}",
announcement_count,
config.service_id
);
}
}
Err(e) => {
tracing::error!("Failed to send OfferService: {:?}", e);
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
});
Ok(())
}
async fn send_offer_service(
config: &ServerConfig,
socket: &UdpSocket,
session_id: &AtomicU16,
) -> Result<(), Error> {
use crate::protocol::Header as SomeIpHeader;
use crate::traits::WireFormat;
let entry = Entry::OfferService(ServiceEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(1, 0),
service_id: config.service_id,
instance_id: config.instance_id,
major_version: config.major_version,
ttl: config.ttl,
minor_version: config.minor_version,
});
let option = sd::Options::IpV4Endpoint {
ip: config.interface,
port: config.local_port,
protocol: TransportProtocol::Udp,
};
let entries = [entry];
let options = [option];
let sd_payload = sd::Header::new(Flags::new(true, true), &entries, &options);
let mut sd_data = Vec::new();
sd_payload.encode(&mut sd_data)?;
let prev = session_id
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
let next = v.wrapping_add(1);
Some(if next == 0 { 1 } else { next })
})
.unwrap();
let next = prev.wrapping_add(1);
let sid = u32::from(if next == 0 { 1 } else { next });
let someip_header = SomeIpHeader::new_sd(sid, sd_data.len());
let mut buffer = Vec::new();
someip_header.encode(&mut buffer)?;
buffer.extend_from_slice(&sd_data);
let multicast_addr = SocketAddrV4::new(sd::MULTICAST_IP, sd::MULTICAST_PORT);
tracing::trace!(
"Sending OfferService: service=0x{:04X}, instance={}, port={}, size={} bytes",
config.service_id,
config.instance_id,
config.local_port,
buffer.len()
);
tracing::trace!(
"OfferService data: {:02X?}",
&buffer[..buffer.len().min(64)]
);
socket.send_to(&buffer, multicast_addr).await?;
tracing::trace!("Sent to {}", multicast_addr);
Ok(())
}
async fn send_unicast_offer(&self, target: std::net::SocketAddr) -> Result<(), Error> {
use crate::protocol::Header as SomeIpHeader;
use crate::traits::WireFormat;
let entry = Entry::OfferService(ServiceEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(1, 0),
service_id: self.config.service_id,
instance_id: self.config.instance_id,
major_version: self.config.major_version,
ttl: self.config.ttl,
minor_version: self.config.minor_version,
});
let option = sd::Options::IpV4Endpoint {
ip: self.config.interface,
port: self.config.local_port,
protocol: TransportProtocol::Udp,
};
let entries = [entry];
let options = [option];
let sd_payload = sd::Header::new(Flags::new(true, true), &entries, &options);
let mut sd_data = Vec::new();
sd_payload.encode(&mut sd_data)?;
let sid = self.next_sd_session_id();
let someip_header = SomeIpHeader::new_sd(sid, sd_data.len());
let mut buffer = Vec::new();
someip_header.encode(&mut buffer)?;
buffer.extend_from_slice(&sd_data);
self.sd_socket.send_to(&buffer, target).await?;
tracing::debug!(
"Sent unicast OfferService to {} for service 0x{:04X}",
target,
self.config.service_id
);
Ok(())
}
fn next_sd_session_id(&self) -> u32 {
let prev = self
.sd_session_id
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
let next = v.wrapping_add(1);
Some(if next == 0 { 1 } else { next })
})
.unwrap();
let next = prev.wrapping_add(1);
u32::from(if next == 0 { 1 } else { next })
}
#[must_use]
pub fn publisher(&self) -> Arc<EventPublisher> {
Arc::clone(&self.publisher)
}
pub fn unicast_local_addr(&self) -> Result<std::net::SocketAddr, std::io::Error> {
self.unicast_socket.local_addr()
}
pub fn set_local_port(&mut self, port: u16) {
self.config.local_port = port;
}
pub fn register_e2e(&self, key: E2EKey, profile: E2EProfile) {
self.e2e_registry
.lock()
.expect("e2e registry lock poisoned")
.register(key, profile);
}
pub fn unregister_e2e(&self, key: &E2EKey) {
self.e2e_registry
.lock()
.expect("e2e registry lock poisoned")
.unregister(key);
}
pub async fn run(&mut self) -> Result<(), Error> {
use crate::protocol::MessageView;
if self.is_passive {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"run called on passive Server for service 0x{:04X}; \
SD receive must be driven externally (e.g. via the \
Client's discovery socket, routing Subscribes to \
`EventPublisher::register_subscriber`)",
self.config.service_id
),
)));
}
let mut unicast_buf = vec![0u8; 65535];
let mut sd_buf = vec![0u8; 65535];
loop {
let (data, len, addr, source) = tokio::select! {
result = self.unicast_socket.recv_from(&mut unicast_buf) => {
let (len, addr) = result?;
(&unicast_buf[..], len, addr, "unicast")
}
result = self.sd_socket.recv_from(&mut sd_buf) => {
let (len, addr) = result?;
(&sd_buf[..], len, addr, "sd-multicast")
}
};
let data = &data[..len];
tracing::trace!("Received {} bytes from {} on {} socket", len, addr, source);
tracing::trace!("Raw data: {:02X?}", &data[..len.min(64_usize)]);
match MessageView::parse(data) {
Ok(view) => {
tracing::trace!(
"SOME/IP Header: service=0x{:04X}, method=0x{:04X}, type={:?}",
view.header().message_id().service_id(),
view.header().message_id().method_id(),
view.header().message_type().message_type()
);
if view.is_sd() {
tracing::trace!("This is an SD message");
match view.sd_header() {
Ok(sd_view) => {
tracing::trace!("SD message has {} entries", sd_view.entry_count(),);
self.handle_sd_message(&sd_view, addr).await?;
}
Err(e) => {
tracing::warn!("Failed to parse SD message: {:?}", e);
}
}
} else {
tracing::trace!("Non-SD SOME/IP message, ignoring");
}
}
Err(e) => {
tracing::warn!("Failed to parse SOME/IP header from {}: {:?}", addr, e);
tracing::trace!("Data: {:02X?}", &data[..len.min(32)]);
}
}
}
}
async fn handle_sd_message(
&mut self,
sd_view: &sd::SdHeaderView<'_>,
sender: std::net::SocketAddr,
) -> Result<(), Error> {
tracing::trace!("Handling SD message from {}", sender);
for entry_view in sd_view.entries() {
let entry_type = entry_view.entry_type()?;
match entry_type {
sd::EntryType::Subscribe => {
tracing::debug!(
"Received Subscribe from {}: service=0x{:04X}, instance={}, eventgroup=0x{:04X}",
sender,
entry_view.service_id(),
entry_view.instance_id(),
entry_view.event_group_id()
);
if entry_view.service_id() != self.config.service_id {
tracing::warn!(
"Subscribe for wrong service: expected 0x{:04X}, got 0x{:04X}",
self.config.service_id,
entry_view.service_id()
);
self.send_subscribe_nack_from_view(&entry_view, sender, "Wrong service ID")
.await?;
} else if entry_view.instance_id() != self.config.instance_id {
tracing::warn!(
"Subscribe for wrong instance: expected {}, got {}",
self.config.instance_id,
entry_view.instance_id()
);
self.send_subscribe_nack_from_view(
&entry_view,
sender,
"Wrong instance ID",
)
.await?;
} else {
let first_index = entry_view.index_first_options_run() as usize;
let first_count = entry_view.options_count().first_options_count as usize;
let second_index = entry_view.index_second_options_run() as usize;
let second_count = entry_view.options_count().second_options_count as usize;
if let Some(endpoint_addr) = Self::extract_subscriber_endpoint(
&sd_view.options(),
first_index,
first_count,
second_index,
second_count,
) {
let mut subs = self.subscriptions.write().await;
subs.subscribe(
entry_view.service_id(),
entry_view.instance_id(),
entry_view.event_group_id(),
endpoint_addr,
);
self.send_subscribe_ack_from_view(&entry_view, sender)
.await?;
} else {
tracing::warn!("No endpoint found in Subscribe message options");
self.send_subscribe_nack_from_view(
&entry_view,
sender,
"No endpoint in options",
)
.await?;
}
}
}
sd::EntryType::FindService => {
let find_service_id = entry_view.service_id();
if find_service_id == self.config.service_id || find_service_id == 0xFFFF {
tracing::debug!(
"Received FindService from {} for service 0x{:04X} (ours: 0x{:04X}), sending unicast offer",
sender,
find_service_id,
self.config.service_id
);
self.send_unicast_offer(sender).await?;
} else {
tracing::trace!(
"Ignoring FindService for service 0x{:04X} (not ours)",
find_service_id
);
}
}
_ => {
tracing::trace!("Ignoring SD entry type: {:?}", entry_type);
}
}
}
Ok(())
}
fn extract_subscriber_endpoint(
options: &sd::OptionIter<'_>,
first_index: usize,
first_count: usize,
second_index: usize,
second_count: usize,
) -> Option<SocketAddrV4> {
let mut first_endpoint: Option<SocketAddrV4> = None;
let mut endpoint_count: usize = 0;
let mut ignored_other: usize = 0;
let mut walk_run = |index: usize, count: usize| {
if count == 0 {
return;
}
for option_view in options.clone().skip(index).take(count) {
match option_view.option_type() {
Ok(sd::OptionType::IpV4Endpoint) => {
if let Ok((ip, _, port)) = option_view.as_ipv4() {
endpoint_count += 1;
if first_endpoint.is_none() {
first_endpoint = Some(SocketAddrV4::new(ip, port));
}
}
}
Ok(_) | Err(_) => ignored_other += 1,
}
}
};
walk_run(first_index, first_count);
walk_run(second_index, second_count);
match endpoint_count {
0 => {
tracing::warn!(
"No IPv4 endpoint in options runs \
(first: idx={first_index}, count={first_count}; \
second: idx={second_index}, count={second_count}; \
ignored={ignored_other})"
);
None
}
1 => {
let ep = first_endpoint.expect("endpoint_count=1 implies first_endpoint is Some");
tracing::trace!("Found IPv4 endpoint {}", ep);
Some(ep)
}
n => {
let ep = first_endpoint.expect("endpoint_count>=1 implies first_endpoint is Some");
tracing::warn!(
"{} IPv4 endpoints found in subscribe options runs; \
using first ({}) and ignoring {} additional. \
Multi-endpoint (e.g. TCP+UDP) subscribers are not yet supported.",
n,
ep,
n - 1
);
Some(ep)
}
}
}
async fn send_subscribe_ack_from_view(
&self,
entry_view: &sd::EntryView<'_>,
subscriber: std::net::SocketAddr,
) -> Result<(), Error> {
use crate::protocol::Header as SomeIpHeader;
use crate::traits::WireFormat;
let ack_entry = Entry::SubscribeAckEventGroup(sd::EventGroupEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(0, 0),
service_id: entry_view.service_id(),
instance_id: entry_view.instance_id(),
major_version: entry_view.major_version(),
ttl: self.config.ttl,
counter: entry_view.counter(),
event_group_id: entry_view.event_group_id(),
});
let entries = [ack_entry];
let sd_payload = sd::Header::new(Flags::new(true, true), &entries, &[]);
let mut sd_data = Vec::new();
sd_payload.encode(&mut sd_data)?;
let sid = self.next_sd_session_id();
let someip_header = SomeIpHeader::new_sd(sid, sd_data.len());
let mut buffer = Vec::new();
someip_header.encode(&mut buffer)?;
buffer.extend_from_slice(&sd_data);
self.sd_socket.send_to(&buffer, subscriber).await?;
tracing::debug!(
"Sent SubscribeAck to {} for service 0x{:04X}, eventgroup 0x{:04X}",
subscriber,
entry_view.service_id(),
entry_view.event_group_id()
);
Ok(())
}
async fn send_subscribe_nack_from_view(
&self,
entry_view: &sd::EntryView<'_>,
subscriber: std::net::SocketAddr,
reason: &str,
) -> Result<(), Error> {
use crate::protocol::Header as SomeIpHeader;
use crate::traits::WireFormat;
let nack_entry = Entry::SubscribeAckEventGroup(sd::EventGroupEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(0, 0),
service_id: entry_view.service_id(),
instance_id: entry_view.instance_id(),
major_version: entry_view.major_version(),
ttl: 0, counter: entry_view.counter(),
event_group_id: entry_view.event_group_id(),
});
let entries = [nack_entry];
let sd_payload = sd::Header::new(Flags::new(true, true), &entries, &[]);
let mut sd_data = Vec::new();
sd_payload.encode(&mut sd_data)?;
let sid = self.next_sd_session_id();
let someip_header = SomeIpHeader::new_sd(sid, sd_data.len());
let mut buffer = Vec::new();
someip_header.encode(&mut buffer)?;
buffer.extend_from_slice(&sd_data);
self.sd_socket.send_to(&buffer, subscriber).await?;
tracing::warn!(
"Sent SubscribeNack to {} for service 0x{:04X}, eventgroup 0x{:04X} (reason: {})",
subscriber,
entry_view.service_id(),
entry_view.event_group_id(),
reason
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::{
Header as SomeIpHeader, MessageType, MessageTypeField, MessageView, ReturnCode,
};
use crate::traits::WireFormat;
use std::format;
#[tokio::test]
async fn test_server_creation() {
let config = ServerConfig::new(Ipv4Addr::new(127, 0, 0, 1), 30682, 0x5B, 1);
let server: Result<Server, _> = Server::new(config).await;
assert!(server.is_ok());
}
fn build_sd_message(sd_header: &sd::Header<'_>) -> Vec<u8> {
let mut sd_data = Vec::new();
sd_header.encode(&mut sd_data).unwrap();
let someip_header = SomeIpHeader::new_sd(0x0001, sd_data.len());
let mut buffer = Vec::new();
someip_header.encode(&mut buffer).unwrap();
buffer.extend_from_slice(&sd_data);
buffer
}
fn parse_subscribe_ack_ttl(data: &[u8]) -> u32 {
let view = MessageView::parse(data).expect("Failed to parse SOME/IP message");
let sd_view = view.sd_header().expect("Failed to parse SD header");
let mut entries = sd_view.entries();
let entry = entries.next().expect("Expected at least 1 entry");
assert_eq!(
entry.entry_type().unwrap(),
sd::EntryType::SubscribeAck,
"Expected SubscribeAckEventGroup entry"
);
entry.ttl()
}
async fn create_test_server(service_id: u16, instance_id: u16) -> (Server, u16) {
let config = ServerConfig::new(Ipv4Addr::new(127, 0, 0, 1), 0, service_id, instance_id);
let mut server = Server::new(config).await.expect("Failed to create server");
let port = match server.unicast_local_addr().unwrap() {
std::net::SocketAddr::V4(addr) => addr.port(),
_ => panic!("Expected IPv4 address"),
};
server.set_local_port(port);
(server, port)
}
fn make_subscription_header(
service_id: u16,
instance_id: u16,
major_version: u8,
ttl: u32,
event_group_id: u16,
client_ip: Ipv4Addr,
protocol: sd::TransportProtocol,
client_port: u16,
) -> Vec<u8> {
let entry = Entry::SubscribeEventGroup(sd::EventGroupEntry::new(
service_id,
instance_id,
major_version,
ttl,
event_group_id,
));
let endpoint = sd::Options::IpV4Endpoint {
ip: client_ip,
protocol,
port: client_port,
};
let entries = [entry];
let options = [endpoint];
let sd_header = sd::Header::new(Flags::new_sd(false), &entries, &options);
build_sd_message(&sd_header)
}
#[tokio::test]
async fn test_subscribe_ack_success() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let message = make_subscription_header(
0x5B,
1,
1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
server_port,
);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
let subs = server.subscriptions.read().await;
assert_eq!(subs.subscription_count(), 1);
let subscribers = subs.get_subscribers(0x5B, 1, 0x01);
assert_eq!(subscribers.len(), 1);
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeAck")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert!(ttl > 0, "Expected ACK (TTL > 0), got TTL={}", ttl);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_subscribe_nack_wrong_service() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let message = make_subscription_header(
0x99, 1,
1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
server_port,
);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
let subs = server.subscriptions.read().await;
assert_eq!(subs.subscription_count(), 0);
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeNack")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert_eq!(ttl, 0, "Expected NACK (TTL=0), got TTL={}", ttl);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_subscribe_nack_wrong_instance() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let message = make_subscription_header(
0x5B,
99, 1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
server_port,
);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
let subs = server.subscriptions.read().await;
assert_eq!(subs.subscription_count(), 0);
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeNack")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert_eq!(ttl, 0, "Expected NACK (TTL=0), got TTL={}", ttl);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_find_service_sends_unicast_offer() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let find_entry = Entry::FindService(ServiceEntry::find(0x5B));
let find_entries = [find_entry];
let sd_header = sd::Header::new(Flags::new_sd(false), &find_entries, &[]);
let message = build_sd_message(&sd_header);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for unicast OfferService")
.unwrap();
let view = MessageView::parse(&resp_buf[..resp_len]).unwrap();
assert_eq!(view.header().message_id().service_id(), 0xFFFF);
let sd_view = view.sd_header().unwrap();
let mut entries = sd_view.entries();
let entry = entries.next().unwrap();
assert_eq!(entry.entry_type().unwrap(), sd::EntryType::OfferService);
assert_eq!(entry.service_id(), 0x5B);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_find_service_wildcard() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let find_entry = Entry::FindService(ServiceEntry::find(0xFFFF));
let find_entries = [find_entry];
let sd_header = sd::Header::new(Flags::new_sd(false), &find_entries, &[]);
let message = build_sd_message(&sd_header);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for unicast OfferService")
.unwrap();
let view = MessageView::parse(&resp_buf[..resp_len]).unwrap();
let sd_view = view.sd_header().unwrap();
let mut entries = sd_view.entries();
let entry = entries.next().unwrap();
assert_eq!(entry.entry_type().unwrap(), sd::EntryType::OfferService);
assert_eq!(entry.service_id(), 0x5B);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_find_service_wrong_service_ignored() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let find_entry = Entry::FindService(ServiceEntry::find(0x99));
let find_entries = [find_entry];
let sd_header = sd::Header::new(Flags::new_sd(false), &find_entries, &[]);
let message = build_sd_message(&sd_header);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
});
let mut resp_buf = vec![0u8; 65535];
let result = tokio::time::timeout(
std::time::Duration::from_millis(200),
client_socket.recv_from(&mut resp_buf),
)
.await;
assert!(
result.is_err(),
"Expected timeout (no response for wrong service)"
);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_subscribe_nack_no_endpoint() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let entry = sd::Entry::SubscribeEventGroup(sd::EventGroupEntry::new(0x5B, 1, 1, 3, 0x01));
let sub_entries = [entry];
let sd_header = sd::Header::new(Flags::new(true, true), &sub_entries, &[]);
let message = build_sd_message(&sd_header);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
let subs = server.subscriptions.read().await;
assert_eq!(subs.subscription_count(), 0);
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeNack")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert_eq!(ttl, 0, "Expected NACK (TTL=0), got TTL={}", ttl);
server_handle.await.unwrap();
}
#[tokio::test]
async fn test_send_offer_service() {
let receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let recv_addr = receiver.local_addr().unwrap();
let (server, _) = create_test_server(0x5B, 1).await;
server
.send_unicast_offer(recv_addr)
.await
.expect("send_unicast_offer failed");
let mut buf = vec![0u8; 65535];
let (len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
receiver.recv_from(&mut buf),
)
.await
.expect("Timeout waiting for OfferService")
.unwrap();
let view = MessageView::parse(&buf[..len]).unwrap();
assert_eq!(view.header().message_id(), crate::protocol::MessageId::SD);
let sd_view = view.sd_header().unwrap();
let mut entries = sd_view.entries();
let entry = entries.next().unwrap();
assert_eq!(entry.entry_type().unwrap(), sd::EntryType::OfferService);
assert_eq!(entry.service_id(), 0x5B);
assert_eq!(entry.instance_id(), 1);
drop(server);
let (server2, _) = create_test_server(0x5B, 1).await;
assert!(server2.start_announcing().is_ok());
}
#[tokio::test]
async fn test_run_non_sd_message() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let client_port = match client_socket.local_addr().unwrap() {
std::net::SocketAddr::V4(a) => a.port(),
_ => panic!("expected v4"),
};
let subscriptions = Arc::clone(&server.subscriptions);
let server_handle = tokio::spawn(async move {
server.run().await.ok();
});
let non_sd_header = SomeIpHeader::new(
crate::protocol::MessageId::new_from_service_and_method(0x1234, 0x0001),
0x0001,
0x01,
0x01,
MessageTypeField::new(MessageType::Request, false),
ReturnCode::Ok,
0,
);
let mut non_sd_buf = Vec::new();
non_sd_header.encode(&mut non_sd_buf).unwrap();
client_socket
.send_to(&non_sd_buf, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let message = make_subscription_header(
0x5B,
1,
1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
client_port,
);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeAck")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert!(ttl > 0, "Expected ACK (TTL > 0), got TTL={}", ttl);
let subs = subscriptions.read().await;
assert_eq!(subs.subscription_count(), 1);
server_handle.abort();
}
#[tokio::test]
async fn test_run_malformed_data() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let client_port = match client_socket.local_addr().unwrap() {
std::net::SocketAddr::V4(a) => a.port(),
_ => panic!("expected v4"),
};
let subscriptions = Arc::clone(&server.subscriptions);
let server_handle = tokio::spawn(async move {
server.run().await.ok();
});
client_socket
.send_to(&[0xFF, 0xFE, 0xFD], format!("127.0.0.1:{}", server_port))
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let message = make_subscription_header(
0x5B,
1,
1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
client_port,
);
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeAck")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert!(ttl > 0, "Expected ACK (TTL > 0), got TTL={}", ttl);
let subs = subscriptions.read().await;
assert_eq!(subs.subscription_count(), 1);
server_handle.abort();
}
#[tokio::test]
async fn test_next_sd_session_id_wraps() {
let (server, _) = create_test_server(0x5B, 1).await;
server.sd_session_id.store(0xFFFE, Ordering::Relaxed);
let sid1 = server.next_sd_session_id();
assert_eq!(sid1, 0xFFFF);
let sid2 = server.next_sd_session_id();
assert_eq!(sid2, 0x0001);
}
#[tokio::test]
async fn test_handle_sd_other_entry_type() {
let (mut server, _) = create_test_server(0x5B, 1).await;
let entry = sd::Entry::StopOfferService(sd::ServiceEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: sd::OptionsCount::new(0, 0),
service_id: 0x5B,
instance_id: 1,
major_version: 1,
ttl: 0,
minor_version: 0,
});
let stop_entries = [entry];
let sd_msg = sd::Header::new(Flags::new(true, true), &stop_entries, &[]);
let mut buf = [0u8; 64];
let n = sd_msg.encode(&mut buf.as_mut_slice()).unwrap();
let sd_view = sd::SdHeaderView::parse(&buf[..n]).unwrap();
let result = server
.handle_sd_message(&sd_view, "127.0.0.1:12345".parse().unwrap())
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_subscribe_ack_different_endpoint_port() {
let (mut server, server_port) = create_test_server(0x5B, 1).await;
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let message = make_subscription_header(
0x5B,
1,
1,
3,
0x01,
Ipv4Addr::new(127, 0, 0, 1),
sd::TransportProtocol::Udp,
server_port.wrapping_add(1), );
client_socket
.send_to(&message, format!("127.0.0.1:{}", server_port))
.await
.unwrap();
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
let (len, addr) = server.unicast_socket.recv_from(&mut buf).await.unwrap();
let data = &buf[..len];
let view = MessageView::parse(data).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, addr).await.unwrap();
let subs = server.subscriptions.read().await;
assert_eq!(subs.subscription_count(), 1);
});
let mut resp_buf = vec![0u8; 65535];
let (resp_len, _) = tokio::time::timeout(
std::time::Duration::from_secs(2),
client_socket.recv_from(&mut resp_buf),
)
.await
.expect("Timeout waiting for SubscribeAck")
.unwrap();
let ttl = parse_subscribe_ack_ttl(&resp_buf[..resp_len]);
assert!(ttl > 0, "Expected ACK (TTL > 0), got TTL={}", ttl);
server_handle.await.unwrap();
}
fn write_ipv4_endpoint_option(
buf: &mut [u8],
ip: Ipv4Addr,
port: u16,
protocol: sd::TransportProtocol,
) -> usize {
let opt = sd::Options::IpV4Endpoint { ip, protocol, port };
let mut slot = buf;
opt.write(&mut slot).unwrap()
}
fn write_load_balancing_option(buf: &mut [u8], priority: u16, weight: u16) -> usize {
let opt = sd::Options::LoadBalancing { priority, weight };
let mut slot = buf;
opt.write(&mut slot).unwrap()
}
fn fill_ipv4_endpoints(buf: &mut [u8], count: usize, base_port: u16) -> usize {
let mut offset = 0;
for i in 0..count {
let port_offset = u16::try_from(i).expect("test fixture count fits in u16");
let n = write_ipv4_endpoint_option(
&mut buf[offset..],
Ipv4Addr::new(10, 0, 0, 1),
base_port + port_offset,
sd::TransportProtocol::Udp,
);
offset += n;
}
offset
}
#[test]
fn extract_endpoint_single_option_first_run() {
let mut buf = [0u8; 64];
let total = fill_ipv4_endpoints(&mut buf, 1, 30000);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 0, 1, 0, 0);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30000))
);
}
#[test]
fn extract_endpoint_zero_options_in_both_runs_returns_none() {
let iter = sd::OptionIter::new(&[]);
assert_eq!(Server::extract_subscriber_endpoint(&iter, 0, 0, 0, 0), None);
}
#[test]
fn extract_endpoint_count_zero_with_nonzero_index_returns_none() {
let mut buf = [0u8; 64];
let total = fill_ipv4_endpoints(&mut buf, 2, 30100);
let iter = sd::OptionIter::new(&buf[..total]);
assert_eq!(Server::extract_subscriber_endpoint(&iter, 1, 0, 0, 0), None);
}
#[test]
fn extract_endpoint_multi_option_first_run_returns_first() {
let mut buf = [0u8; 64];
let total = fill_ipv4_endpoints(&mut buf, 2, 30200);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 0, 2, 0, 0);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30200))
);
}
#[test]
fn extract_endpoint_split_across_first_and_second_runs() {
let mut buf = [0u8; 96];
let total = fill_ipv4_endpoints(&mut buf, 3, 30300);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 0, 1, 2, 1);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30300))
);
}
#[test]
fn extract_endpoint_honors_first_index_offset() {
let mut buf = [0u8; 128];
let total = fill_ipv4_endpoints(&mut buf, 4, 30400);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 2, 1, 0, 0);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30402))
);
}
#[test]
fn extract_endpoint_respects_first_count_cap() {
let mut buf = [0u8; 128];
let total = fill_ipv4_endpoints(&mut buf, 4, 30500);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 1, 1, 0, 0);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30501))
);
}
#[test]
fn extract_endpoint_skips_non_ipv4_options() {
let mut buf = [0u8; 64];
let mut offset = 0;
offset += write_load_balancing_option(&mut buf[offset..], 1, 2);
offset += write_ipv4_endpoint_option(
&mut buf[offset..],
Ipv4Addr::new(10, 0, 0, 1),
30600,
sd::TransportProtocol::Udp,
);
offset += write_load_balancing_option(&mut buf[offset..], 3, 4);
let iter = sd::OptionIter::new(&buf[..offset]);
let got = Server::extract_subscriber_endpoint(&iter, 0, 3, 0, 0);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30600))
);
}
#[test]
fn extract_endpoint_all_non_ipv4_returns_none() {
let mut buf = [0u8; 32];
let mut offset = 0;
offset += write_load_balancing_option(&mut buf[offset..], 1, 2);
offset += write_load_balancing_option(&mut buf[offset..], 3, 4);
let iter = sd::OptionIter::new(&buf[..offset]);
assert_eq!(Server::extract_subscriber_endpoint(&iter, 0, 2, 0, 0), None);
}
#[test]
fn extract_endpoint_second_run_only() {
let mut buf = [0u8; 64];
let total = fill_ipv4_endpoints(&mut buf, 2, 30700);
let iter = sd::OptionIter::new(&buf[..total]);
let got = Server::extract_subscriber_endpoint(&iter, 0, 0, 1, 1);
assert_eq!(
got,
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 30701))
);
}
#[tokio::test]
async fn combined_sd_subscribe_uses_its_own_options_run() {
let (mut server, _port) = create_test_server(0x5B, 1).await;
let offer_endpoint_port: u16 = 40_111;
let subscribe_endpoint_port: u16 = 40_222;
let offer_entry = Entry::OfferService(sd::ServiceEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: sd::OptionsCount::new(1, 0),
service_id: 0x5B,
instance_id: 1,
major_version: 1,
ttl: 3,
minor_version: 0,
});
let subscribe_entry = Entry::SubscribeEventGroup(sd::EventGroupEntry {
index_first_options_run: 1,
index_second_options_run: 0,
options_count: sd::OptionsCount::new(1, 0),
service_id: 0x5B,
instance_id: 1,
major_version: 1,
ttl: 3,
counter: 0,
event_group_id: 0x0001,
});
let entries = [offer_entry, subscribe_entry];
let options = [
sd::Options::IpV4Endpoint {
ip: Ipv4Addr::LOCALHOST,
protocol: sd::TransportProtocol::Udp,
port: offer_endpoint_port,
},
sd::Options::IpV4Endpoint {
ip: Ipv4Addr::LOCALHOST,
protocol: sd::TransportProtocol::Udp,
port: subscribe_endpoint_port,
},
];
let sd_header = sd::Header::new(sd::Flags::new_sd(false), &entries, &options);
let message = build_sd_message(&sd_header);
let client_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let sd_addr = match server.sd_socket.local_addr().unwrap() {
std::net::SocketAddr::V4(v4) => v4,
std::net::SocketAddr::V6(_) => panic!("expected v4 sd socket"),
};
client_socket.send_to(&message, sd_addr).await.unwrap();
let mut buf = vec![0u8; 65_535];
let (len, sender) = tokio::time::timeout(
std::time::Duration::from_secs(2),
server.sd_socket.recv_from(&mut buf),
)
.await
.expect("timeout receiving combined SD packet")
.unwrap();
let view = MessageView::parse(&buf[..len]).unwrap();
let sd_view = view.sd_header().unwrap();
server.handle_sd_message(&sd_view, sender).await.unwrap();
let subs = server.subscriptions.read().await;
let subscribers = subs.get_subscribers(0x5B, 1, 0x0001);
assert_eq!(
subscribers.len(),
1,
"combined SD packet must yield exactly one subscriber"
);
assert_eq!(
subscribers[0].address.port(),
subscribe_endpoint_port,
"subscription endpoint must come from the Subscribe entry's own \
options run (options[1]={subscribe_endpoint_port}), not from \
the Offer entry's options[0]={offer_endpoint_port}"
);
assert_ne!(
subscribers[0].address.port(),
offer_endpoint_port,
"regression: subscription picked up the OfferService endpoint \
instead of its own SubscribeEventGroup endpoint"
);
}
async fn make_passive_server(service_id: u16, instance_id: u16) -> Server {
let config = ServerConfig::new(Ipv4Addr::LOCALHOST, 0, service_id, instance_id);
Server::new_passive(config)
.await
.expect("new_passive should succeed")
}
#[tokio::test]
async fn new_passive_unicast_bound_to_requested_port() {
let server = make_passive_server(0x005C, 0x0001).await;
let local = server.unicast_local_addr().unwrap();
match local {
std::net::SocketAddr::V4(v4) => {
assert_ne!(
v4.port(),
0,
"kernel should assign an ephemeral port when local_port=0"
);
}
std::net::SocketAddr::V6(_) => panic!("expected IPv4 unicast address"),
}
}
#[tokio::test]
async fn new_passive_sd_socket_is_not_bound_to_30490() {
let server = make_passive_server(0x005C, 0x0001).await;
let sd_addr = server.sd_socket.local_addr().unwrap();
match sd_addr {
std::net::SocketAddr::V4(v4) => {
assert_ne!(
v4.port(),
30490,
"passive SD socket must not bind the SOME/IP SD port"
);
}
std::net::SocketAddr::V6(_) => panic!("expected IPv4 SD address"),
}
}
#[tokio::test]
async fn new_passive_publisher_accepts_register_subscriber() {
let server = make_passive_server(0x005C, 0x0001).await;
let publisher = server.publisher();
assert!(!publisher.has_subscribers(0x005C, 0x0001, 0x0001).await);
let subscriber = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 2), 40_000);
publisher
.register_subscriber(0x005C, 0x0001, 0x0001, subscriber)
.await;
assert!(publisher.has_subscribers(0x005C, 0x0001, 0x0001).await);
assert_eq!(publisher.subscriber_count(0x005C, 0x0001, 0x0001).await, 1);
publisher
.remove_subscriber(0x005C, 0x0001, 0x0001, subscriber)
.await;
assert!(!publisher.has_subscribers(0x005C, 0x0001, 0x0001).await);
}
#[tokio::test]
async fn start_announcing_on_passive_returns_invalid_input() {
let server = make_passive_server(0x005C, 0x0001).await;
let err = server
.start_announcing()
.expect_err("start_announcing on a passive server must fail");
match err {
Error::Io(io_err) => {
assert_eq!(io_err.kind(), std::io::ErrorKind::InvalidInput);
let msg = format!("{io_err}");
assert!(
msg.contains("passive"),
"error message should mention 'passive': {msg}"
);
assert!(
msg.contains("0x005C"),
"error message should include the service_id: {msg}"
);
}
other => panic!("expected Error::Io(InvalidInput), got {other:?}"),
}
}
#[tokio::test]
async fn run_on_passive_returns_invalid_input() {
let mut server = make_passive_server(0x005C, 0x0001).await;
let err = server
.run()
.await
.expect_err("run on a passive server must fail");
match err {
Error::Io(io_err) => {
assert_eq!(io_err.kind(), std::io::ErrorKind::InvalidInput);
let msg = format!("{io_err}");
assert!(
msg.contains("passive"),
"error message should mention 'passive': {msg}"
);
assert!(
msg.contains("0x005C"),
"error message should include the service_id: {msg}"
);
}
other => panic!("expected Error::Io(InvalidInput), got {other:?}"),
}
}
#[tokio::test]
async fn start_announcing_on_regular_server_still_succeeds() {
let (server, _port) = create_test_server(0x005C, 0x0001).await;
server
.start_announcing()
.expect("start_announcing on a regular server must still succeed");
}
#[tokio::test]
async fn new_passive_two_instances_do_not_fight_over_sd_port() {
let a = make_passive_server(0x005B, 0x0002).await;
let b = make_passive_server(0x005C, 0x0001).await;
let addr_a = a.sd_socket.local_addr().unwrap();
let addr_b = b.sd_socket.local_addr().unwrap();
assert_ne!(addr_a, addr_b);
if let std::net::SocketAddr::V4(v4) = addr_a {
assert_ne!(v4.port(), 30490);
}
if let std::net::SocketAddr::V4(v4) = addr_b {
assert_ne!(v4.port(), 30490);
}
}
#[tokio::test]
async fn new_passive_returns_error_when_unicast_bind_fails() {
let blocker = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))
.await
.expect("blocker bind should succeed");
let blocker_port = match blocker.local_addr().unwrap() {
std::net::SocketAddr::V4(v4) => v4.port(),
std::net::SocketAddr::V6(_) => panic!("expected IPv4"),
};
let config = ServerConfig::new(Ipv4Addr::LOCALHOST, blocker_port, 0x005C, 0x0001);
let result = Server::new_passive(config).await;
let Err(err) = result else {
panic!("new_passive must fail when the unicast port is taken");
};
match err {
Error::Io(io_err) => {
assert!(
matches!(
io_err.kind(),
std::io::ErrorKind::AddrInUse | std::io::ErrorKind::PermissionDenied
),
"expected AddrInUse or PermissionDenied, got {:?}",
io_err.kind()
);
}
other => panic!("expected Error::Io, got {other:?}"),
}
drop(blocker);
}
#[tokio::test]
async fn new_passive_with_tracing_subscriber_evaluates_format_args() {
use tracing::subscriber::with_default;
use tracing_subscriber::fmt;
let subscriber = fmt()
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.finish();
let fut = async {
let _server = make_passive_server(0x00AA, 0x00BB).await;
};
let handle = std::thread::spawn(move || {
with_default(subscriber, || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(fut);
});
});
handle.join().expect("subscriber thread panicked");
}
#[test]
fn extract_subscriber_endpoint_with_tracing_evaluates_log_args() {
use tracing::subscriber::with_default;
use tracing_subscriber::fmt;
let subscriber = fmt()
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.finish();
with_default(subscriber, || {
let iter_empty = sd::OptionIter::new(&[]);
assert_eq!(
Server::extract_subscriber_endpoint(&iter_empty, 0, 0, 0, 0),
None
);
let mut buf_one = [0u8; 32];
let len_one = fill_ipv4_endpoints(&mut buf_one, 1, 31000);
let iter_one = sd::OptionIter::new(&buf_one[..len_one]);
assert_eq!(
Server::extract_subscriber_endpoint(&iter_one, 0, 1, 0, 0),
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 31000))
);
let mut buf_many = [0u8; 64];
let len_many = fill_ipv4_endpoints(&mut buf_many, 3, 31100);
let iter_many = sd::OptionIter::new(&buf_many[..len_many]);
assert_eq!(
Server::extract_subscriber_endpoint(&iter_many, 0, 3, 0, 0),
Some(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 31100))
);
});
}
}