use crate::protocol::byte_order::WriteBytesExt;
use automotive_wire_codec::{Decode, DecodeIter, DecodeIterator, Encode};
use super::{
Entry, EntryView, Flags, OptionView, Options,
entry::{ENTRY_SIZE, EntryIter},
options::OptionIter,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Header<'a> {
pub flags: Flags,
pub entries: &'a [Entry],
pub options: &'a [Options],
}
impl<'a> Header<'a> {
#[must_use]
pub const fn new(flags: Flags, entries: &'a [Entry], options: &'a [Options]) -> Self {
Self {
flags,
entries,
options,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct SdHeaderView<'a> {
flags: Flags,
entries_buf: &'a [u8],
options_buf: &'a [u8],
entry_count: usize,
option_count: usize,
}
impl<'a> SdHeaderView<'a> {
pub fn parse(buf: &'a [u8]) -> Result<Self, crate::protocol::Error> {
let (body, _rest) = SdBody::decode(buf)?;
let mut entry_count = 0usize;
for entry in body.entries() {
entry?.entry_type()?;
entry_count += 1;
}
let mut option_count = 0usize;
for option in body.options() {
option?.validate()?;
option_count += 1;
}
Ok(Self {
flags: body.flags,
entries_buf: body.entries_buf,
options_buf: body.options_buf,
entry_count,
option_count,
})
}
#[must_use]
pub fn flags(&self) -> Flags {
self.flags
}
#[must_use]
pub fn entries(&self) -> EntryIter<'a> {
EntryIter::new(self.entries_buf)
}
#[must_use]
pub fn options(&self) -> OptionIter<'a> {
OptionIter::new(self.options_buf)
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entry_count
}
#[must_use]
pub fn option_count(&self) -> usize {
self.option_count
}
}
#[derive(Clone, Copy, Debug)]
pub struct SdBody<'a> {
flags: Flags,
entries_buf: &'a [u8],
options_buf: &'a [u8],
}
impl<'a> SdBody<'a> {
#[must_use]
pub fn flags(&self) -> Flags {
self.flags
}
#[must_use]
pub fn entries(&self) -> DecodeIterator<'a, EntryView<'a>> {
EntryView::iter(self.entries_buf)
}
#[must_use]
pub fn options(&self) -> DecodeIterator<'a, OptionView<'a>> {
OptionView::iter(self.options_buf)
}
}
impl<'a> Decode<'a> for SdBody<'a> {
type Error = crate::protocol::Error;
fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> {
if buf.len() < 12 {
return Err(automotive_wire_codec::Incomplete {
needed: 12,
available: buf.len(),
}
.into());
}
let flags = Flags::from(buf[0]);
let entries_size = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
if !entries_size.is_multiple_of(ENTRY_SIZE) {
return Err(super::Error::IncorrectEntriesSize(entries_size).into());
}
let overflow = || automotive_wire_codec::Incomplete {
needed: usize::MAX,
available: buf.len(),
};
let entries_end = 8usize.checked_add(entries_size).ok_or_else(overflow)?;
let options_size_offset = entries_end;
let entries_section_end = entries_end.checked_add(4).ok_or_else(overflow)?;
if buf.len() < entries_section_end {
return Err(automotive_wire_codec::Incomplete {
needed: entries_section_end,
available: buf.len(),
}
.into());
}
let entries_buf = &buf[8..options_size_offset];
let options_size = u32::from_be_bytes([
buf[options_size_offset],
buf[options_size_offset + 1],
buf[options_size_offset + 2],
buf[options_size_offset + 3],
]) as usize;
let options_start = entries_section_end;
let options_end = options_start
.checked_add(options_size)
.ok_or_else(overflow)?;
if buf.len() < options_end {
return Err(automotive_wire_codec::Incomplete {
needed: options_end,
available: buf.len(),
}
.into());
}
let options_buf = &buf[options_start..options_end];
let rest = &buf[options_end..];
Ok((
Self {
flags,
entries_buf,
options_buf,
},
rest,
))
}
}
impl Encode for Header<'_> {
type Error = crate::protocol::Error;
fn encoded_size(&self) -> Result<usize, Self::Error> {
let mut size = 12 + self.entries.len() * ENTRY_SIZE;
for option in self.options {
size += option.size();
}
Ok(size)
}
fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, Self::Error> {
writer.write_u8(u8::from(self.flags))?;
let reserved: [u8; 3] = [0; 3];
writer.write_bytes(&reserved)?;
let entries_size = u32::try_from(self.entries.len() * 16).expect("entries size fits u32");
writer.write_u32_be(entries_size)?;
for entry in self.entries {
entry.encode(writer)?;
}
let mut options_size = 0;
for option in self.options {
options_size += option.size();
}
writer.write_u32_be(u32::try_from(options_size).expect("options size fits u32"))?;
for option in self.options {
option.encode(writer)?;
}
Ok(12 + entries_size as usize + options_size)
}
}
#[cfg(test)]
mod tests {
use core::net::Ipv4Addr;
use super::*;
use crate::protocol::sd::{
Error as SdError, EventGroupEntry, OptionType, OptionsCount, RebootFlag, ServiceEntry,
TransportProtocol,
options::{
IPV4_OPTION_IP_OFFSET, IPV4_OPTION_LENGTH_FIELD, IPV4_OPTION_PORT_OFFSET,
IPV4_OPTION_PROTOCOL_OFFSET, IPV4_OPTION_WIRE_SIZE,
},
};
use automotive_wire_codec::Encode;
fn ipv4_endpoint_bytes(ip: [u8; 4], protocol: u8, port: u16) -> [u8; IPV4_OPTION_WIRE_SIZE] {
let mut b = [0u8; IPV4_OPTION_WIRE_SIZE];
b[0..2].copy_from_slice(&IPV4_OPTION_LENGTH_FIELD.to_be_bytes());
b[2] = u8::from(OptionType::IpV4Endpoint);
b[IPV4_OPTION_IP_OFFSET..IPV4_OPTION_IP_OFFSET + 4].copy_from_slice(&ip);
b[IPV4_OPTION_PROTOCOL_OFFSET] = protocol;
b[IPV4_OPTION_PORT_OFFSET..IPV4_OPTION_PORT_OFFSET + 2]
.copy_from_slice(&port.to_be_bytes());
b
}
fn raw_header(entries_size: u32, options_size: u32) -> [u8; 12] {
let mut b = [0u8; 12];
b[4..8].copy_from_slice(&entries_size.to_be_bytes());
b[8..12].copy_from_slice(&options_size.to_be_bytes());
b
}
#[test]
fn header_new_stores_fields() {
let flags = Flags::new_sd(RebootFlag::RecentlyRebooted);
let entries: &[Entry] = &[];
let options: &[Options] = &[];
let h = Header::new(flags, entries, options);
assert_eq!(h.flags, flags);
assert!(h.entries.is_empty());
assert!(h.options.is_empty());
}
#[test]
fn service_offer_round_trips() {
let ip = Ipv4Addr::new(192, 168, 1, 10);
let entry = Entry::OfferService(ServiceEntry {
service_id: 0x1234,
instance_id: 0x0001,
major_version: 1,
ttl: 0xFF_FFFF,
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(1, 0),
minor_version: 0,
});
let endpoint = Options::IpV4Endpoint {
ip,
protocol: TransportProtocol::Udp,
port: 30509,
};
let entries = [entry];
let options = [endpoint];
let h = Header::new(
Flags::new_sd(RebootFlag::RecentlyRebooted),
&entries,
&options,
);
assert_eq!(h.encoded_size().unwrap(), 40);
let mut buf = [0u8; 64];
h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..h.encoded_size().unwrap()]).unwrap();
assert_eq!(view.entry_count(), 1);
let entry_view = view.entries().next().unwrap();
assert_eq!(entry_view.service_id(), 0x1234);
}
#[test]
fn subscribe_ack_round_trips() {
let entry = Entry::SubscribeAckEventGroup(EventGroupEntry::new(
0xAAAA, 0x0001, 1, 0xFF_FFFF, 0x0010,
));
let entries = [entry];
let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
assert_eq!(h.encoded_size().unwrap(), 28);
let mut buf = [0u8; 32];
h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..h.encoded_size().unwrap()]).unwrap();
assert_eq!(view.entry_count(), 1);
}
#[test]
fn parse_exact_size_slice_succeeds() {
let entry = Entry::OfferService(ServiceEntry {
service_id: 0x1234,
instance_id: 0x0001,
major_version: 1,
ttl: 0xFF_FFFF,
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(1, 0),
minor_version: 0,
});
let endpoint = Options::IpV4Endpoint {
ip: Ipv4Addr::new(192, 168, 1, 10),
protocol: TransportProtocol::Udp,
port: 30509,
};
let entries = [entry];
let options = [endpoint];
let h = Header::new(
Flags::new_sd(RebootFlag::RecentlyRebooted),
&entries,
&options,
);
let mut buf = [0u8; 64];
let n = h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..n]).unwrap();
assert_eq!(view.entry_count(), 1);
}
#[test]
fn parse_options_size_below_minimum_returns_error() {
let prefix = raw_header(0, 2);
let mut buf = [0u8; 14];
buf[..12].copy_from_slice(&prefix);
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Incomplete(
automotive_wire_codec::Incomplete {
needed: 4,
available: 2,
}
))
));
}
#[test]
fn parse_option_size_exceeds_declared_remaining_returns_error() {
let prefix = raw_header(0, 5);
let option = ipv4_endpoint_bytes([127, 0, 0, 1], 0x11, 1234);
let mut buf = [0u8; 24];
buf[..12].copy_from_slice(&prefix);
buf[12..24].copy_from_slice(&option);
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Incomplete(
automotive_wire_codec::Incomplete {
needed: 12,
available: 5,
}
))
));
}
#[test]
fn sd_header_view_entry_count() {
let entries = [
Entry::FindService(ServiceEntry::find(0x0001)),
Entry::FindService(ServiceEntry::find(0x0002)),
];
let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
let mut buf = [0u8; 64];
h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..h.encoded_size().unwrap()]).unwrap();
assert_eq!(view.entry_count(), 2);
}
#[test]
fn sd_header_view_accessors_yield_cached_counts() {
let ip = Ipv4Addr::new(192, 168, 1, 10);
let entries = [
Entry::FindService(ServiceEntry::find(0x0001)),
Entry::FindService(ServiceEntry::find(0x0002)),
];
let options = [
Options::IpV4Endpoint {
ip,
protocol: TransportProtocol::Udp,
port: 30509,
},
Options::IpV4Endpoint {
ip,
protocol: TransportProtocol::Tcp,
port: 30510,
},
];
let h = Header::new(
Flags::new_sd(RebootFlag::RecentlyRebooted),
&entries,
&options,
);
let mut buf = [0u8; 128];
let n = h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..n]).unwrap();
assert_eq!(view.entry_count(), 2);
assert_eq!(view.option_count(), 2);
assert_eq!(view.entries().count(), view.entry_count());
assert_eq!(view.options().count(), view.option_count());
assert_eq!(view.entries().len(), view.entry_count());
}
#[test]
fn parse_rejects_trailing_partial_option() {
let prefix = raw_header(0, 12);
let mut option = ipv4_endpoint_bytes([10, 0, 0, 1], 0x11, 30490);
option[0..2].copy_from_slice(&13u16.to_be_bytes());
let mut buf = [0u8; 24];
buf[..12].copy_from_slice(&prefix);
buf[12..24].copy_from_slice(&option);
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Incomplete(
automotive_wire_codec::Incomplete {
needed: 16,
available: 12,
}
))
));
}
#[test]
fn sd_header_view_flags() {
let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &[], &[]);
let mut buf = [0u8; 16];
h.encode(&mut buf.as_mut_slice()).unwrap();
let view = SdHeaderView::parse(&buf[..h.encoded_size().unwrap()]).unwrap();
assert_eq!(view.flags(), h.flags);
}
#[test]
fn parse_incorrect_entries_size_returns_error() {
let mut buf = [0u8; 12];
buf[4..8].copy_from_slice(&5u32.to_be_bytes());
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Sd(SdError::IncorrectEntriesSize(5)))
));
}
#[test]
fn parse_rejects_ipv4_option_with_invalid_transport_protocol() {
const SD_HEADER_PREFIX_SIZE: usize = 12;
let options_size = u32::try_from(IPV4_OPTION_WIRE_SIZE).expect("wire size fits u32");
let prefix = raw_header(0, options_size);
let option = ipv4_endpoint_bytes([10, 0, 0, 1], 0xAB, 30490);
let mut buf = [0u8; SD_HEADER_PREFIX_SIZE + IPV4_OPTION_WIRE_SIZE];
buf[..SD_HEADER_PREFIX_SIZE].copy_from_slice(&prefix);
buf[SD_HEADER_PREFIX_SIZE..].copy_from_slice(&option);
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Sd(
SdError::InvalidOptionTransportProtocol(0xAB)
))
));
}
#[test]
fn sd_body_decode_slices_sections() {
let ip = Ipv4Addr::new(192, 168, 1, 10);
let entry = Entry::OfferService(ServiceEntry {
service_id: 0x1234,
instance_id: 0x0001,
major_version: 1,
ttl: 0xFF_FFFF,
index_first_options_run: 0,
index_second_options_run: 0,
options_count: OptionsCount::new(1, 0),
minor_version: 0,
});
let endpoint = Options::IpV4Endpoint {
ip,
protocol: TransportProtocol::Udp,
port: 30509,
};
let entries = [entry];
let options = [endpoint];
let h = Header::new(
Flags::new_sd(RebootFlag::RecentlyRebooted),
&entries,
&options,
);
let mut buf = [0u8; 64];
let n = h.encode(&mut buf.as_mut_slice()).unwrap();
let (body, rest) = SdBody::decode(&buf[..n]).unwrap();
assert!(rest.is_empty());
assert_eq!(body.flags(), h.flags);
let entry_view = body.entries().next().unwrap().unwrap();
assert_eq!(entry_view.service_id(), 0x1234);
let opt_view = body.options().next().unwrap().unwrap();
assert_eq!(opt_view.as_ipv4().unwrap().0, ip);
}
#[test]
fn sd_body_decode_returns_trailing_remainder() {
let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &[], &[]);
let mut buf = [0u8; 32];
let n = h.encode(&mut buf.as_mut_slice()).unwrap();
buf[n] = 0xDE;
buf[n + 1] = 0xAD;
buf[n + 2] = 0xBE;
let (_body, rest) = SdBody::decode(&buf[..n + 3]).unwrap();
assert_eq!(rest, &[0xDE, 0xAD, 0xBE]);
}
#[test]
fn sd_body_decode_defers_entry_type_validation() {
let mut buf = [0u8; 28];
buf[4..8].copy_from_slice(&16u32.to_be_bytes());
buf[8] = 0x03; let (body, rest) = SdBody::decode(&buf).unwrap();
assert!(rest.is_empty());
let entry_view = body.entries().next().unwrap().unwrap();
assert!(matches!(
entry_view.to_owned(),
Err(SdError::InvalidEntryType(0x03))
));
assert!(matches!(
SdHeaderView::parse(&buf),
Err(crate::protocol::Error::Sd(SdError::InvalidEntryType(0x03)))
));
}
#[test]
fn sd_body_decode_defers_option_validation() {
const PREFIX: usize = 12;
let options_size = u32::try_from(IPV4_OPTION_WIRE_SIZE).unwrap();
let prefix = raw_header(0, options_size);
let option = ipv4_endpoint_bytes([10, 0, 0, 1], 0xAB, 30490);
let mut buf = [0u8; PREFIX + IPV4_OPTION_WIRE_SIZE];
buf[..PREFIX].copy_from_slice(&prefix);
buf[PREFIX..].copy_from_slice(&option);
let (body, rest) = SdBody::decode(&buf).unwrap();
assert!(rest.is_empty());
let opt_view = body.options().next().unwrap().unwrap();
assert!(matches!(
opt_view.as_ipv4(),
Err(SdError::InvalidOptionTransportProtocol(0xAB))
));
}
#[test]
fn sd_body_decode_too_short_is_incomplete() {
let buf = [0u8; 8];
assert!(matches!(
SdBody::decode(&buf),
Err(crate::protocol::Error::Incomplete(
automotive_wire_codec::Incomplete {
needed: 12,
available: 8,
}
))
));
}
#[test]
fn sd_body_decode_rejects_non_multiple_entries_size() {
let mut buf = [0u8; 12];
buf[4..8].copy_from_slice(&5u32.to_be_bytes());
assert!(matches!(
SdBody::decode(&buf),
Err(crate::protocol::Error::Sd(SdError::IncorrectEntriesSize(5)))
));
}
#[test]
fn sd_body_entries_remaining_len_reports_count() {
let entries = [
Entry::FindService(ServiceEntry::find(0x0001)),
Entry::FindService(ServiceEntry::find(0x0002)),
];
let h = Header::new(Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
let mut buf = [0u8; 64];
let n = h.encode(&mut buf.as_mut_slice()).unwrap();
let (body, _rest) = SdBody::decode(&buf[..n]).unwrap();
assert_eq!(body.entries().remaining_len(), Some(2));
}
}