use core::net::SocketAddrV4;
use futures_util::{FutureExt, future::Either, pin_mut, select_biased};
use crate::Timer;
use crate::protocol::sd::{self, Entry, Flags, OptionsCount, ServiceEntry, TransportProtocol};
use crate::transport::{E2ERegistryHandle, SharedHandle, TransportSocket};
use super::sd_state::SdStateManager;
use super::subscription_manager::{SubscribeError, SubscriptionHandle};
use super::{Error, ServerConfig};
pub(super) async fn send_unicast_offer<T>(
buf: &mut [u8],
config: &ServerConfig,
sd_socket: &T,
sd_state: &SdStateManager,
target: core::net::SocketAddr,
) -> Result<(), Error>
where
T: TransportSocket,
{
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 (sid, reboot_flag) = sd_state.next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &options);
if buf.len() < 16 {
return Err(Error::Capacity("udp_buffer"));
}
let sd_data_len = sd_payload
.encode_to_slice(&mut buf[16..])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let total_len = 16 + sd_data_len;
debug_assert!(total_len <= buf.len());
let someip_header = SomeIpHeader::new_sd(sid, sd_data_len);
someip_header
.encode_to_slice(&mut buf[..16])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let target_v4 = socket_addr_v4(target)?;
sd_socket.send_to(&buf[..total_len], target_v4).await?;
crate::log::debug!(
"Sent unicast OfferService to {} for service 0x{:04X}",
target,
config.service_id
);
Ok(())
}
pub(super) async fn send_subscribe_ack_from_view<T>(
buf: &mut [u8],
config: &ServerConfig,
sd_socket: &T,
sd_state: &SdStateManager,
entry_view: &sd::EntryView<'_>,
subscriber: core::net::SocketAddr,
) -> Result<(), Error>
where
T: TransportSocket,
{
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: config.ttl,
counter: entry_view.counter(),
event_group_id: entry_view.event_group_id(),
});
let entries = [ack_entry];
let (sid, reboot_flag) = sd_state.next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &[]);
if buf.len() < 16 {
return Err(Error::Capacity("udp_buffer"));
}
let sd_data_len = sd_payload
.encode_to_slice(&mut buf[16..])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let total_len = 16 + sd_data_len;
debug_assert!(total_len <= buf.len());
let someip_header = SomeIpHeader::new_sd(sid, sd_data_len);
someip_header
.encode_to_slice(&mut buf[..16])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let subscriber_v4 = socket_addr_v4(subscriber)?;
sd_socket.send_to(&buf[..total_len], subscriber_v4).await?;
crate::log::debug!(
"Sent SubscribeAck to {} for service 0x{:04X}, eventgroup 0x{:04X}",
subscriber,
entry_view.service_id(),
entry_view.event_group_id()
);
Ok(())
}
pub(super) async fn send_subscribe_nack_from_view<T>(
buf: &mut [u8],
_config: &ServerConfig,
sd_socket: &T,
sd_state: &SdStateManager,
entry_view: &sd::EntryView<'_>,
subscriber: core::net::SocketAddr,
reason: &str,
) -> Result<(), Error>
where
T: TransportSocket,
{
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 (sid, reboot_flag) = sd_state.next_session_id_with_reboot_flag();
let sd_payload = sd::Header::new(Flags::new_sd(reboot_flag), &entries, &[]);
if buf.len() < 16 {
return Err(Error::Capacity("udp_buffer"));
}
let sd_data_len = sd_payload
.encode_to_slice(&mut buf[16..])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let total_len = 16 + sd_data_len;
debug_assert!(total_len <= buf.len());
let someip_header = SomeIpHeader::new_sd(sid, sd_data_len);
someip_header
.encode_to_slice(&mut buf[..16])
.map_err(|_| Error::Capacity("udp_buffer"))?;
let subscriber_v4 = socket_addr_v4(subscriber)?;
sd_socket.send_to(&buf[..total_len], subscriber_v4).await?;
crate::log::warn!(
"Sent SubscribeNack to {} for service 0x{:04X}, eventgroup 0x{:04X} (reason: {})",
subscriber,
entry_view.service_id(),
entry_view.event_group_id(),
reason
);
Ok(())
}
#[allow(clippy::too_many_lines)]
pub(super) async fn handle_sd_message<T, Sub>(
config: &ServerConfig,
sd_socket: &T,
sd_state: &SdStateManager,
subscriptions: &Sub,
sd_view: &sd::SdHeaderView<'_>,
sender: core::net::SocketAddr,
send_buf: &mut [u8],
) -> Result<(), Error>
where
T: TransportSocket,
Sub: SubscriptionHandle,
{
crate::log::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 => {
crate::log::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()
);
let co_offered = config.accepts_offer(
entry_view.service_id(),
entry_view.instance_id(),
entry_view.major_version(),
entry_view.event_group_id(),
);
if !co_offered && entry_view.service_id() != config.service_id {
crate::log::warn!(
"Subscribe for wrong service: expected 0x{:04X}, got 0x{:04X}",
config.service_id,
entry_view.service_id()
);
send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
"wrong_service_id",
)
.await?;
} else if !co_offered && entry_view.instance_id() != config.instance_id {
crate::log::warn!(
"Subscribe for wrong instance: expected {}, got {}",
config.instance_id,
entry_view.instance_id()
);
send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
"wrong_instance_id",
)
.await?;
} else if !co_offered && entry_view.major_version() != config.major_version {
crate::log::warn!(
"Subscribe for wrong major_version: expected {}, got {}",
config.major_version,
entry_view.major_version()
);
if let Err(e) = send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
"wrong_major_version",
)
.await
{
crate::log::warn!("SubscribeNack send failed: {e}");
}
} else if !co_offered && !config.accepts_event_group(entry_view.event_group_id()) {
crate::log::warn!(
"Subscribe for unknown event_group_id 0x{:04X} (service 0x{:04X})",
entry_view.event_group_id(),
entry_view.service_id()
);
if let Err(e) = send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
"unknown_event_group",
)
.await
{
crate::log::warn!("SubscribeNack send failed: {e}");
}
} 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) = extract_subscriber_endpoint(
&sd_view.options(),
first_index,
first_count,
second_index,
second_count,
) {
let subscribe_result = subscriptions
.subscribe(
entry_view.service_id(),
entry_view.instance_id(),
entry_view.event_group_id(),
endpoint_addr,
)
.await;
match subscribe_result {
Ok(()) => {
if let Err(e) = send_subscribe_ack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
)
.await
{
crate::log::warn!(
"SubscribeAck send failed; rolling back subscription \
(service_id=0x{:04X}, instance_id={}, \
event_group_id=0x{:04X}, error={e})",
entry_view.service_id(),
entry_view.instance_id(),
entry_view.event_group_id(),
);
subscriptions
.unsubscribe(
entry_view.service_id(),
entry_view.instance_id(),
entry_view.event_group_id(),
endpoint_addr,
)
.await;
}
}
Err(e) => {
let reason: &'static str = match e {
SubscribeError::SubscribersPerGroupFull => {
"subscribers_per_group_full"
}
SubscribeError::EventGroupsFull => "event_groups_full",
};
crate::log::debug!("Subscription rejected: {reason}");
if let Err(e) = send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
reason,
)
.await
{
crate::log::warn!("SubscribeNack send failed: {e}");
}
}
}
} else {
crate::log::warn!("No endpoint found in Subscribe message options");
if let Err(e) = send_subscribe_nack_from_view(
send_buf,
config,
sd_socket,
sd_state,
&entry_view,
sender,
"no_endpoint_in_options",
)
.await
{
crate::log::warn!("SubscribeNack send failed: {e}");
}
}
}
}
sd::EntryType::FindService => {
let find_service_id = entry_view.service_id();
if find_service_id == config.service_id || find_service_id == 0xFFFF {
crate::log::debug!(
"Received FindService from {} for service 0x{:04X} (ours: 0x{:04X}), sending unicast offer",
sender,
find_service_id,
config.service_id
);
if let Err(e) =
send_unicast_offer(send_buf, config, sd_socket, sd_state, sender).await
{
crate::log::warn!("Unicast OfferService send failed: {e}");
}
} else {
crate::log::trace!(
"Ignoring FindService for service 0x{:04X} (not ours)",
find_service_id
);
}
}
_ => {
crate::log::trace!("Ignoring SD entry type: {:?}", entry_type);
}
}
}
Ok(())
}
pub(super) async fn announce_loop<T, Tm>(
config: &ServerConfig,
sd_socket: &T,
sd_state: &SdStateManager,
timer: &Tm,
announce_send_buf: &mut [u8],
) where
T: TransportSocket,
Tm: Timer,
{
let mut announcement_count = 0u32;
loop {
match sd_state
.send_offer_service(announce_send_buf, config, sd_socket)
.await
{
Ok(()) => {
announcement_count += 1;
if announcement_count == 1 {
crate::log::info!(
"Sent first SD announcement for service 0x{:04X}",
config.service_id
);
} else {
crate::log::debug!(
"Sent {} SD announcements for service 0x{:04X}",
announcement_count,
config.service_id
);
}
}
Err(e) => {
crate::log::error!("Failed to send OfferService: {:?}", e);
}
}
timer.sleep(core::time::Duration::from_secs(1)).await;
}
}
async fn dispatch_non_sd_request<T: TransportSocket, R: E2ERegistryHandle>(
unicast_socket: &T,
observer: (super::NonSdRequestCallback, usize),
e2e: &R,
view: &crate::protocol::MessageView<'_>,
source: core::net::SocketAddrV4,
send_buf: &mut [u8],
) {
let (cb, ctx) = observer;
let hdr = view.header();
let id = hdr.message_id();
let (service_id, method_id) = (id.service_id(), id.method_id());
let parsed = crate::sd_codec::ParsedDatagram {
service_id,
method_id,
upper_header: hdr.upper_header_bytes(),
payload: view.payload_bytes(),
};
let (status, body) =
crate::sd_codec::check_parsed_e2e(e2e, core::net::IpAddr::V4(*source.ip()), &parsed);
let resp_len = cb(
ctx,
source,
service_id,
method_id,
body,
crate::sd_codec::e2e_status_code(status),
&mut send_buf[crate::sd_codec::SOMEIP_HEADER_LEN..],
);
let Ok(payload_len) = usize::try_from(resp_len) else {
return;
};
let usable = send_buf.len() - crate::sd_codec::SOMEIP_HEADER_LEN;
if payload_len > usable {
crate::log::warn!(
"non-SD response length {} exceeds {}-byte response buffer; dropped",
payload_len,
usable
);
return;
}
if crate::sd_codec::encode_response_header(
send_buf,
service_id,
method_id,
hdr.request_id(),
hdr.protocol_version(),
hdr.interface_version(),
payload_len,
)
.is_ok()
{
let total = crate::sd_codec::SOMEIP_HEADER_LEN + payload_len;
if let Err(e) = unicast_socket.send_to(&send_buf[..total], source).await {
crate::log::warn!("non-SD response send failed: {:?}", e);
}
}
}
#[allow(clippy::too_many_arguments)]
async fn recv_loop<T, Sub, R>(
config: &ServerConfig,
unicast_socket: &T,
sd_socket: &T,
sd_state: &SdStateManager,
subscriptions: &Sub,
e2e: &R,
unicast_buf: &mut [u8],
sd_buf: &mut [u8],
send_buf: &mut [u8],
non_sd_observer: Option<(super::NonSdRequestCallback, usize)>,
) -> Result<(), Error>
where
T: TransportSocket,
Sub: SubscriptionHandle,
R: E2ERegistryHandle,
{
use crate::protocol::MessageView;
let mut prefer_sd_first = false;
loop {
let (datagram, from_unicast) = {
let unicast_fut = unicast_socket.recv_from(&mut *unicast_buf).fuse();
let sd_fut = sd_socket.recv_from(&mut *sd_buf).fuse();
pin_mut!(unicast_fut, sd_fut);
if prefer_sd_first {
select_biased! {
result = sd_fut => (result?, false),
result = unicast_fut => (result?, true),
}
} else {
select_biased! {
result = unicast_fut => (result?, true),
result = sd_fut => (result?, false),
}
}
};
prefer_sd_first = !prefer_sd_first;
let len = datagram.bytes_received;
let addr = core::net::SocketAddr::V4(datagram.source);
let source = if from_unicast {
"unicast"
} else {
"sd-multicast"
};
let data = if from_unicast {
&unicast_buf[..len]
} else {
&sd_buf[..len]
};
crate::log::trace!("Received {} bytes from {} on {} socket", len, addr, source);
crate::log::trace!("Raw data: {:02X?}", &data[..len.min(64_usize)]);
match MessageView::parse(data) {
Ok(view) => {
crate::log::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() {
crate::log::trace!("This is an SD message");
match view.sd_header() {
Ok(sd_view) => {
crate::log::trace!("SD message has {} entries", sd_view.entry_count());
handle_sd_message(
config,
sd_socket,
sd_state,
subscriptions,
&sd_view,
addr,
send_buf,
)
.await?;
}
Err(e) => {
crate::log::warn!("Failed to parse SD message: {:?}", e);
}
}
} else if from_unicast {
if let Some(observer) = non_sd_observer {
if let core::net::SocketAddr::V4(src_v4) = addr {
dispatch_non_sd_request(
unicast_socket,
observer,
e2e,
&view,
src_v4,
send_buf,
)
.await;
}
} else {
crate::log::trace!(
"Non-SD unicast SOME/IP message, no observer registered — ignoring"
);
}
} else {
crate::log::trace!("Non-SD multicast SOME/IP message, ignoring");
}
}
Err(e) => {
crate::log::warn!("Failed to parse SOME/IP header from {}: {:?}", addr, e);
crate::log::trace!("Data: {:02X?}", &data[..len.min(32)]);
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn run_combined<H, T, Sub, Hsd, Tm, R>(
config: ServerConfig,
unicast_socket: H,
sd_socket: H,
subscriptions: Sub,
sd_state: Hsd,
e2e: R,
timer: Tm,
is_passive: bool,
unicast_buf: &mut [u8],
sd_buf: &mut [u8],
recv_send_buf: &mut [u8],
announce_send_buf: &mut [u8],
non_sd_observer: Option<(super::NonSdRequestCallback, usize)>,
) -> Result<(), Error>
where
H: SharedHandle<T>,
T: TransportSocket + 'static,
Sub: SubscriptionHandle,
Hsd: SharedHandle<SdStateManager>,
Tm: Timer,
R: E2ERegistryHandle,
{
if is_passive {
crate::log::warn!(
"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`)",
config.service_id
);
return Err(Error::InvalidUsage("passive_server_run"));
}
let unicast = unicast_socket.get();
let sd = sd_socket.get();
let sd_state_ref = sd_state.get();
let recv_fut = recv_loop(
&config,
unicast,
sd,
sd_state_ref,
&subscriptions,
&e2e,
unicast_buf,
sd_buf,
recv_send_buf,
non_sd_observer,
);
if config.announce {
let announce_fut = announce_loop(&config, sd, sd_state_ref, &timer, announce_send_buf);
pin_mut!(recv_fut, announce_fut);
match futures_util::future::select(recv_fut, announce_fut).await {
Either::Left((recv_result, _)) => recv_result,
Either::Right(((), recv_pending)) => recv_pending.await,
}
} else {
recv_fut.await
}
}
fn socket_addr_v4(addr: core::net::SocketAddr) -> Result<SocketAddrV4, Error> {
match addr {
core::net::SocketAddr::V4(v4) => Ok(v4),
core::net::SocketAddr::V6(_) => Err(Error::Transport(
crate::transport::TransportError::Unsupported,
)),
}
}
pub(super) 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 => {
crate::log::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");
crate::log::trace!("Found IPv4 endpoint {}", ep);
Some(ep)
}
n => {
let ep = first_endpoint.expect("endpoint_count>=1 implies first_endpoint is Some");
crate::log::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)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::net::{Ipv4Addr, SocketAddrV4};
use crate::transport::{ReceivedDatagram, TransportError, TransportSocket};
struct NullSocket;
struct NullSend;
impl core::future::Future for NullSend {
type Output = Result<(), TransportError>;
fn poll(
self: core::pin::Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
core::task::Poll::Ready(Ok(()))
}
}
struct NullRecv;
impl core::future::Future for NullRecv {
type Output = Result<ReceivedDatagram, TransportError>;
fn poll(
self: core::pin::Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
core::task::Poll::Pending
}
}
impl TransportSocket for NullSocket {
type SendFuture<'a> = NullSend;
type RecvFuture<'a> = NullRecv;
fn send_to<'a>(&'a self, _buf: &'a [u8], _target: SocketAddrV4) -> Self::SendFuture<'a> {
NullSend
}
fn recv_from<'a>(&'a self, _buf: &'a mut [u8]) -> Self::RecvFuture<'a> {
NullRecv
}
fn local_addr(&self) -> Result<SocketAddrV4, TransportError> {
Ok(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))
}
fn join_multicast_v4(
&self,
_group: Ipv4Addr,
_iface: Ipv4Addr,
) -> Result<(), TransportError> {
Ok(())
}
fn leave_multicast_v4(
&self,
_group: Ipv4Addr,
_iface: Ipv4Addr,
) -> Result<(), TransportError> {
Ok(())
}
}
fn make_config() -> ServerConfig {
ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30500)
}
fn make_sd_state() -> SdStateManager {
SdStateManager::new()
}
fn subscriber_addr() -> core::net::SocketAddr {
core::net::SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 40000))
}
#[tokio::test]
async fn send_unicast_offer_undersized_buf_returns_capacity() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let target = subscriber_addr();
let result = send_unicast_offer(&mut [0u8; 24], &config, &socket, &sd_state, target).await;
assert!(
matches!(result, Err(Error::Capacity("udp_buffer"))),
"expected Capacity(\"udp_buffer\"), got {result:?}"
);
}
#[tokio::test]
async fn send_unicast_offer_buf_shorter_than_header_returns_capacity() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let target = subscriber_addr();
let result = send_unicast_offer(&mut [0u8; 8], &config, &socket, &sd_state, target).await;
assert!(
matches!(result, Err(Error::Capacity("udp_buffer"))),
"expected Capacity(\"udp_buffer\"), got {result:?}"
);
}
#[tokio::test]
async fn send_unicast_offer_full_size_buf_succeeds() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let target = subscriber_addr();
let result = send_unicast_offer(
&mut [0u8; crate::UDP_BUFFER_SIZE],
&config,
&socket,
&sd_state,
target,
)
.await;
assert!(result.is_ok(), "full-size buf must succeed, got {result:?}");
}
fn subscribe_wire_bytes() -> ([u8; 512], usize) {
use crate::traits::WireFormat;
let entry = sd::Entry::SubscribeEventGroup(sd::EventGroupEntry {
index_first_options_run: 0,
index_second_options_run: 0,
options_count: sd::OptionsCount::new(1, 0),
service_id: 0x1234,
instance_id: 1,
major_version: 1,
ttl: 3,
counter: 0,
event_group_id: 0x0001,
});
let option = sd::Options::IpV4Endpoint {
ip: Ipv4Addr::LOCALHOST,
port: 40000,
protocol: sd::TransportProtocol::Udp,
};
let entries = [entry];
let options = [option];
let sd_payload = sd::Header::new(
sd::Flags::new_sd(sd::RebootFlag::RecentlyRebooted),
&entries,
&options,
);
let mut wire = [0u8; 512];
let sd_len = sd_payload.encode_to_slice(&mut wire).expect("encode");
(wire, sd_len)
}
#[tokio::test]
async fn send_subscribe_ack_undersized_buf_returns_capacity_not_panic() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let subscriber = subscriber_addr();
let (wire, sd_len) = subscribe_wire_bytes();
let sd_view = sd::SdHeaderView::parse(&wire[..sd_len]).expect("parse");
let entry_view = sd_view.entries().next().expect("one entry");
let result = send_subscribe_ack_from_view(
&mut [0u8; 24],
&config,
&socket,
&sd_state,
&entry_view,
subscriber,
)
.await;
assert!(
matches!(result, Err(Error::Capacity("udp_buffer"))),
"expected Capacity(\"udp_buffer\"), got {result:?}"
);
}
#[tokio::test]
async fn send_subscribe_ack_full_size_buf_succeeds() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let subscriber = subscriber_addr();
let (wire, sd_len) = subscribe_wire_bytes();
let sd_view = sd::SdHeaderView::parse(&wire[..sd_len]).expect("parse");
let entry_view = sd_view.entries().next().expect("one entry");
let result = send_subscribe_ack_from_view(
&mut [0u8; crate::UDP_BUFFER_SIZE],
&config,
&socket,
&sd_state,
&entry_view,
subscriber,
)
.await;
assert!(result.is_ok(), "full-size buf must succeed, got {result:?}");
}
#[tokio::test]
async fn send_subscribe_nack_undersized_buf_returns_capacity_not_panic() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let subscriber = subscriber_addr();
let (wire, sd_len) = subscribe_wire_bytes();
let sd_view = sd::SdHeaderView::parse(&wire[..sd_len]).expect("parse");
let entry_view = sd_view.entries().next().expect("one entry");
let result = send_subscribe_nack_from_view(
&mut [0u8; 24],
&config,
&socket,
&sd_state,
&entry_view,
subscriber,
"test_reason",
)
.await;
assert!(
matches!(result, Err(Error::Capacity("udp_buffer"))),
"expected Capacity(\"udp_buffer\"), got {result:?}"
);
}
#[tokio::test]
async fn send_subscribe_nack_full_size_buf_succeeds() {
let config = make_config();
let sd_state = make_sd_state();
let socket = NullSocket;
let subscriber = subscriber_addr();
let (wire, sd_len) = subscribe_wire_bytes();
let sd_view = sd::SdHeaderView::parse(&wire[..sd_len]).expect("parse");
let entry_view = sd_view.entries().next().expect("one entry");
let result = send_subscribe_nack_from_view(
&mut [0u8; crate::UDP_BUFFER_SIZE],
&config,
&socket,
&sd_state,
&entry_view,
subscriber,
"test_reason",
)
.await;
assert!(result.is_ok(), "full-size buf must succeed, got {result:?}");
}
}