simple-someip 0.8.0

A lightweight SOME/IP serialization and communication library
Documentation
use crate::protocol::sd;
use crate::traits::{PayloadWireFormat, WireFormat};

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct TestSdHeader {
    pub flags: sd::Flags,
    pub entries: heapless::Vec<sd::Entry, 4>,
    pub options: heapless::Vec<sd::Options, 4>,
}

impl WireFormat for TestSdHeader {
    fn required_size(&self) -> usize {
        sd::Header::new(self.flags, &self.entries, &self.options).required_size()
    }
    fn encode<T: embedded_io::Write>(
        &self,
        writer: &mut T,
    ) -> Result<usize, crate::protocol::Error> {
        sd::Header::new(self.flags, &self.entries, &self.options).encode(writer)
    }
}

// NOTE: `tools/size_probe`'s `ProbePayload` mirrors this type
// field-for-field for thumbv7em layout capture (it can't reach this
// `pub(crate)` item). If you change `TestPayload`/`TestSdHeader`,
// update the probe or its measured layouts silently drift.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct TestPayload {
    pub header: TestSdHeader,
}

impl PayloadWireFormat for TestPayload {
    type SdHeader = TestSdHeader;
    fn message_id(&self) -> crate::protocol::MessageId {
        crate::protocol::MessageId::SD
    }
    fn as_sd_header(&self) -> Option<&TestSdHeader> {
        Some(&self.header)
    }
    fn from_payload_bytes(
        message_id: crate::protocol::MessageId,
        payload: &[u8],
    ) -> Result<Self, crate::protocol::Error> {
        match message_id {
            crate::protocol::MessageId::SD => {
                let view = sd::SdHeaderView::parse(payload)?;
                let mut entries = heapless::Vec::new();
                for ev in view.entries() {
                    entries.push(ev.to_owned().unwrap()).ok();
                }
                let mut options = heapless::Vec::new();
                for ov in view.options() {
                    options.push(ov.to_owned().unwrap()).ok();
                }
                Ok(Self {
                    header: TestSdHeader {
                        flags: view.flags(),
                        entries,
                        options,
                    },
                })
            }
            _ => Err(crate::protocol::Error::UnsupportedMessageID(message_id)),
        }
    }
    fn new_sd_payload(header: &TestSdHeader) -> Self {
        Self {
            header: header.clone(),
        }
    }
    fn sd_flags(&self) -> Option<sd::Flags> {
        Some(self.header.flags)
    }
    fn required_size(&self) -> usize {
        self.header.required_size()
    }
    fn encode<T: embedded_io::Write>(
        &self,
        writer: &mut T,
    ) -> Result<usize, crate::protocol::Error> {
        self.header.encode(writer)
    }
    fn new_subscription_sd_header(
        service_id: u16,
        instance_id: u16,
        major_version: u8,
        ttl: u32,
        event_group_id: u16,
        client_ip: core::net::Ipv4Addr,
        protocol: sd::TransportProtocol,
        client_port: u16,
        reboot_flag: sd::RebootFlag,
    ) -> TestSdHeader {
        let entry = sd::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 mut entries = heapless::Vec::new();
        entries.push(entry).unwrap();
        let mut options = heapless::Vec::new();
        options.push(endpoint).unwrap();
        TestSdHeader {
            flags: sd::Flags::new_sd(reboot_flag),
            entries,
            options,
        }
    }
    fn set_reboot_flag(header: &mut TestSdHeader, reboot: sd::RebootFlag) {
        header.flags = sd::Flags::new(bool::from(reboot), header.flags.unicast());
    }
}

pub(crate) fn empty_sd_header() -> TestSdHeader {
    TestSdHeader {
        flags: sd::Flags::new_sd(sd::RebootFlag::RecentlyRebooted),
        entries: heapless::Vec::new(),
        options: heapless::Vec::new(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(feature = "std")]
    #[test]
    fn set_reboot_flag_flips_reboot_and_preserves_unicast() {
        let mut header = empty_sd_header();
        assert_eq!(header.flags.reboot(), sd::RebootFlag::RecentlyRebooted);
        assert!(header.flags.unicast());

        TestPayload::set_reboot_flag(&mut header, sd::RebootFlag::Continuous);
        assert_eq!(header.flags.reboot(), sd::RebootFlag::Continuous);
        assert!(header.flags.unicast());

        TestPayload::set_reboot_flag(&mut header, sd::RebootFlag::RecentlyRebooted);
        assert_eq!(header.flags.reboot(), sd::RebootFlag::RecentlyRebooted);
        assert!(header.flags.unicast());
    }

    #[test]
    fn empty_sd_header_has_no_entries() {
        let h = empty_sd_header();
        assert!(h.entries.is_empty());
        assert!(h.options.is_empty());
        assert!(h.flags.unicast());
    }

    #[test]
    fn from_payload_bytes_non_sd_returns_error() {
        let mid = crate::protocol::MessageId::new_from_service_and_method(0x1234, 0x0001);
        let result = TestPayload::from_payload_bytes(mid, &[1, 2, 3]);
        assert!(result.is_err());
    }

    #[test]
    fn from_payload_bytes_sd_parses_correctly() {
        let header = sd::Header::new(
            sd::Flags::new_sd(sd::RebootFlag::RecentlyRebooted),
            &[],
            &[],
        );
        let mut buf = [0u8; 64];
        let n = header.encode(&mut buf.as_mut_slice()).unwrap();
        let payload =
            TestPayload::from_payload_bytes(crate::protocol::MessageId::SD, &buf[..n]).unwrap();
        assert!(payload.header.entries.is_empty());
    }

    #[cfg(feature = "std")]
    #[test]
    fn new_subscription_sd_header_creates_valid_structure() {
        let header = TestPayload::new_subscription_sd_header(
            0x5B,
            1,
            1,
            3,
            0x01,
            std::net::Ipv4Addr::LOCALHOST,
            sd::TransportProtocol::Udp,
            12345,
            sd::RebootFlag::Continuous,
        );
        assert_eq!(header.entries.len(), 1);
        assert_eq!(header.options.len(), 1);
        assert_eq!(header.flags.reboot(), sd::RebootFlag::Continuous);

        let header_reboot = TestPayload::new_subscription_sd_header(
            0x5B,
            1,
            1,
            3,
            0x01,
            std::net::Ipv4Addr::LOCALHOST,
            sd::TransportProtocol::Udp,
            12345,
            sd::RebootFlag::RecentlyRebooted,
        );
        assert_eq!(
            header_reboot.flags.reboot(),
            sd::RebootFlag::RecentlyRebooted
        );
    }

    #[cfg(feature = "std")]
    #[test]
    fn test_payload_offered_endpoints_default_empty() {
        let p = TestPayload {
            header: empty_sd_header(),
        };
        assert!(p.offered_endpoints().is_empty());
    }
}