Skip to main content

simple_someip/protocol/sd/
entry.rs

1use super::Error;
2use crate::{protocol::byte_order::WriteBytesExt, traits::WireFormat};
3
4pub const ENTRY_SIZE: usize = 16;
5
6/// The type of an SD entry.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum EntryType {
9    /// Find a service (0x00).
10    FindService,
11    /// Offer a service (0x01).
12    OfferService,
13    /// Stop offering a service (0x02).
14    StopOfferService,
15    /// Subscribe to an event group (0x06).
16    Subscribe,
17    /// Acknowledge an event group subscription (0x07).
18    SubscribeAck,
19}
20
21impl TryFrom<u8> for EntryType {
22    type Error = Error;
23    fn try_from(value: u8) -> Result<Self, Error> {
24        match value {
25            0x00 => Ok(EntryType::FindService),
26            0x01 => Ok(EntryType::OfferService),
27            0x02 => Ok(EntryType::StopOfferService),
28            0x06 => Ok(EntryType::Subscribe),
29            0x07 => Ok(EntryType::SubscribeAck),
30            _ => Err(Error::InvalidEntryType(value)),
31        }
32    }
33}
34
35impl From<EntryType> for u8 {
36    fn from(service_entry_type: EntryType) -> u8 {
37        match service_entry_type {
38            EntryType::FindService => 0x00,
39            EntryType::OfferService => 0x01,
40            EntryType::StopOfferService => 0x02,
41            EntryType::Subscribe => 0x06,
42            EntryType::SubscribeAck => 0x07,
43        }
44    }
45}
46
47/// Packed pair of 4-bit option run counts (first and second options run).
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub struct OptionsCount {
50    /// Number of options in the first options run.
51    pub first_options_count: u8,
52    /// Number of options in the second options run.
53    pub second_options_count: u8,
54}
55
56impl From<u8> for OptionsCount {
57    fn from(value: u8) -> Self {
58        let first_options_count = (value & 0xf0) >> 4;
59        let second_options_count = value & 0x0f;
60
61        Self {
62            first_options_count,
63            second_options_count,
64        }
65    }
66}
67
68impl From<OptionsCount> for u8 {
69    fn from(options_count: OptionsCount) -> u8 {
70        ((options_count.first_options_count << 4) & 0xf0)
71            | (options_count.second_options_count & 0x0f)
72    }
73}
74
75impl OptionsCount {
76    /// # Panics
77    /// Panics if either count is >= 16 (each count must fit in a 4-bit nibble).
78    #[must_use]
79    pub const fn new(first_options_count: u8, second_options_count: u8) -> Self {
80        assert!(first_options_count < 16);
81        assert!(second_options_count < 16);
82        OptionsCount {
83            first_options_count,
84            second_options_count,
85        }
86    }
87}
88
89/// An SD entry for event group operations (subscribe / subscribe-ack).
90#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct EventGroupEntry {
92    /// Index into the options array for the first options run.
93    pub index_first_options_run: u8,
94    /// Index into the options array for the second options run.
95    pub index_second_options_run: u8,
96    /// Number of options in each run.
97    pub options_count: OptionsCount,
98    /// The SOME/IP service ID.
99    pub service_id: u16,
100    /// The SOME/IP instance ID.
101    pub instance_id: u16,
102    /// The major version of the service interface.
103    pub major_version: u8,
104    /// Time-to-live in seconds (24-bit value).
105    pub ttl: u32,
106    /// Event group counter.
107    pub counter: u16,
108    /// The event group ID.
109    pub event_group_id: u16,
110}
111
112impl EventGroupEntry {
113    /// Creates a new event group entry with default option indices.
114    #[must_use]
115    pub const fn new(
116        service_id: u16,
117        instance_id: u16,
118        major_version: u8,
119        ttl: u32,
120        event_group_id: u16,
121    ) -> Self {
122        Self {
123            index_first_options_run: 0,
124            index_second_options_run: 0,
125            options_count: OptionsCount::new(1, 0),
126            service_id,
127            instance_id,
128            major_version,
129            ttl,
130            counter: 0,
131            event_group_id,
132        }
133    }
134}
135
136impl WireFormat for EventGroupEntry {
137    fn required_size(&self) -> usize {
138        16
139    }
140
141    fn encode<T: embedded_io::Write>(
142        &self,
143        writer: &mut T,
144    ) -> Result<usize, crate::protocol::Error> {
145        writer.write_u8(self.index_first_options_run)?;
146        writer.write_u8(self.index_second_options_run)?;
147        writer.write_u8(u8::from(self.options_count))?;
148        writer.write_u16_be(self.service_id)?;
149        writer.write_u16_be(self.instance_id)?;
150        writer.write_u8(self.major_version)?;
151        writer.write_u24_be(self.ttl)?;
152        writer.write_u16_be(self.counter)?;
153        writer.write_u16_be(self.event_group_id)?;
154        Ok(16)
155    }
156}
157
158/// An SD entry for service operations (find / offer / stop-offer).
159#[derive(Clone, Debug, Eq, PartialEq)]
160pub struct ServiceEntry {
161    /// Index into the options array for the first options run.
162    pub index_first_options_run: u8,
163    /// Index into the options array for the second options run.
164    pub index_second_options_run: u8,
165    /// Number of options in each run.
166    pub options_count: OptionsCount,
167    /// The SOME/IP service ID.
168    pub service_id: u16,
169    /// The SOME/IP instance ID.
170    pub instance_id: u16,
171    /// The major version of the service interface.
172    pub major_version: u8,
173    /// Time-to-live in seconds (24-bit value).
174    pub ttl: u32,
175    /// The minor version of the service interface.
176    pub minor_version: u32,
177}
178
179impl ServiceEntry {
180    /// Creates a `FindService` entry with wildcard instance/version fields.
181    #[must_use]
182    pub const fn find(service_id: u16) -> Self {
183        Self {
184            index_first_options_run: 0,
185            index_second_options_run: 0,
186            options_count: OptionsCount::new(1, 0),
187            service_id,
188            instance_id: 0xFFFF,
189            major_version: 0xFF,
190            ttl: 0x00FF_FFFF,
191            minor_version: 0xFFFF_FFFF,
192        }
193    }
194}
195
196impl WireFormat for ServiceEntry {
197    fn required_size(&self) -> usize {
198        16
199    }
200
201    fn encode<W: embedded_io::Write>(
202        &self,
203        writer: &mut W,
204    ) -> Result<usize, crate::protocol::Error> {
205        writer.write_u8(self.index_first_options_run)?;
206        writer.write_u8(self.index_second_options_run)?;
207        writer.write_u8(u8::from(self.options_count))?;
208        writer.write_u16_be(self.service_id)?;
209        writer.write_u16_be(self.instance_id)?;
210        writer.write_u8(self.major_version)?;
211        writer.write_u24_be(self.ttl)?;
212        writer.write_u32_be(self.minor_version)?;
213        Ok(16)
214    }
215}
216
217/// A decoded SD entry, wrapping a [`ServiceEntry`] or [`EventGroupEntry`].
218#[derive(Clone, Debug, Eq, PartialEq)]
219pub enum Entry {
220    /// Find a service.
221    FindService(ServiceEntry),
222    /// Offer a service.
223    OfferService(ServiceEntry),
224    /// Stop offering a service.
225    StopOfferService(ServiceEntry),
226    /// Subscribe to an event group.
227    SubscribeEventGroup(EventGroupEntry),
228    /// Acknowledge an event group subscription.
229    SubscribeAckEventGroup(EventGroupEntry),
230}
231
232impl Entry {
233    /// Returns the number of options in the first options run.
234    #[must_use]
235    pub fn first_options_count(&self) -> u8 {
236        match self {
237            Entry::FindService(service_entry)
238            | Entry::OfferService(service_entry)
239            | Entry::StopOfferService(service_entry) => {
240                service_entry.options_count.first_options_count
241            }
242            Entry::SubscribeEventGroup(event_group_entry)
243            | Entry::SubscribeAckEventGroup(event_group_entry) => {
244                event_group_entry.options_count.first_options_count
245            }
246        }
247    }
248
249    /// Returns the number of options in the second options run.
250    #[must_use]
251    pub fn second_options_count(&self) -> u8 {
252        match self {
253            Entry::FindService(service_entry)
254            | Entry::OfferService(service_entry)
255            | Entry::StopOfferService(service_entry) => {
256                service_entry.options_count.second_options_count
257            }
258            Entry::SubscribeEventGroup(event_group_entry)
259            | Entry::SubscribeAckEventGroup(event_group_entry) => {
260                event_group_entry.options_count.second_options_count
261            }
262        }
263    }
264
265    /// Returns the total number of options across both runs.
266    #[must_use]
267    pub fn total_options_count(&self) -> u8 {
268        self.first_options_count() + self.second_options_count()
269    }
270}
271
272impl WireFormat for Entry {
273    fn required_size(&self) -> usize {
274        1 + match self {
275            Entry::FindService(service_entry)
276            | Entry::OfferService(service_entry)
277            | Entry::StopOfferService(service_entry) => service_entry.required_size(),
278            Entry::SubscribeEventGroup(event_group_entry)
279            | Entry::SubscribeAckEventGroup(event_group_entry) => event_group_entry.required_size(),
280        }
281    }
282
283    fn encode<W: embedded_io::Write>(
284        &self,
285        writer: &mut W,
286    ) -> Result<usize, crate::protocol::Error> {
287        match self {
288            Entry::FindService(service_entry) => {
289                writer.write_u8(u8::from(EntryType::FindService))?;
290                service_entry.encode(writer)
291            }
292            Entry::OfferService(service_entry) => {
293                writer.write_u8(u8::from(EntryType::OfferService))?;
294                service_entry.encode(writer)
295            }
296            Entry::StopOfferService(service_entry) => {
297                writer.write_u8(u8::from(EntryType::StopOfferService))?;
298                service_entry.encode(writer)
299            }
300            Entry::SubscribeEventGroup(event_group_entry) => {
301                writer.write_u8(u8::from(EntryType::Subscribe))?;
302                event_group_entry.encode(writer)
303            }
304            Entry::SubscribeAckEventGroup(event_group_entry) => {
305                writer.write_u8(u8::from(EntryType::SubscribeAck))?;
306                event_group_entry.encode(writer)
307            }
308        }
309    }
310}
311
312// --- Zero-copy view types ---
313
314/// Zero-copy view into a 16-byte SD entry in a buffer.
315///
316/// Wire layout (16 bytes total):
317/// - `[0]`: entry type
318/// - `[1]`: `index_first_options_run`
319/// - `[2]`: `index_second_options_run`
320/// - `[3]`: `options_count` (packed nibbles)
321/// - `[4..6]`: `service_id` (BE)
322/// - `[6..8]`: `instance_id` (BE)
323/// - `[8]`: `major_version`
324/// - `[9..12]`: ttl (24-bit BE)
325/// - `[12..16]`: `minor_version` (BE) for service entries,
326///   OR `[12..14]` counter + `[14..16]` `event_group_id` for eventgroup entries
327#[derive(Clone, Copy, Debug)]
328pub struct EntryView<'a>(&'a [u8; ENTRY_SIZE]);
329
330impl EntryView<'_> {
331    /// Returns the entry type.
332    ///
333    /// # Errors
334    ///
335    /// Returns [`Error::InvalidEntryType`] if the type byte is not recognized.
336    pub fn entry_type(&self) -> Result<EntryType, Error> {
337        EntryType::try_from(self.0[0])
338    }
339
340    /// Returns the index of the first options run.
341    #[must_use]
342    pub fn index_first_options_run(&self) -> u8 {
343        self.0[1]
344    }
345
346    /// Returns the index of the second options run.
347    #[must_use]
348    pub fn index_second_options_run(&self) -> u8 {
349        self.0[2]
350    }
351
352    /// Returns the packed options count.
353    #[must_use]
354    pub fn options_count(&self) -> OptionsCount {
355        OptionsCount::from(self.0[3])
356    }
357
358    /// Returns the service ID.
359    #[must_use]
360    pub fn service_id(&self) -> u16 {
361        u16::from_be_bytes([self.0[4], self.0[5]])
362    }
363
364    /// Returns the instance ID.
365    #[must_use]
366    pub fn instance_id(&self) -> u16 {
367        u16::from_be_bytes([self.0[6], self.0[7]])
368    }
369
370    /// Returns the major version.
371    #[must_use]
372    pub fn major_version(&self) -> u8 {
373        self.0[8]
374    }
375
376    /// Returns the TTL (24-bit value).
377    #[must_use]
378    pub fn ttl(&self) -> u32 {
379        u32::from_be_bytes([0, self.0[9], self.0[10], self.0[11]])
380    }
381
382    /// Minor version (only valid for service entries).
383    #[must_use]
384    pub fn minor_version(&self) -> u32 {
385        u32::from_be_bytes([self.0[12], self.0[13], self.0[14], self.0[15]])
386    }
387
388    /// Counter field (only valid for eventgroup entries). Masked to lower 4 bits.
389    #[must_use]
390    pub fn counter(&self) -> u16 {
391        u16::from_be_bytes([self.0[12], self.0[13]]) & 0x000f
392    }
393
394    /// Event group ID (only valid for eventgroup entries).
395    #[must_use]
396    pub fn event_group_id(&self) -> u16 {
397        u16::from_be_bytes([self.0[14], self.0[15]])
398    }
399
400    /// Converts this view into an owned [`Entry`].
401    ///
402    /// # Errors
403    ///
404    /// Returns [`Error::InvalidEntryType`] if the entry type byte is not recognized.
405    pub fn to_owned(&self) -> Result<Entry, Error> {
406        let entry_type = self.entry_type()?;
407        match entry_type {
408            EntryType::FindService => Ok(Entry::FindService(self.to_service_entry())),
409            EntryType::OfferService => Ok(Entry::OfferService(self.to_service_entry())),
410            EntryType::StopOfferService => Ok(Entry::StopOfferService(self.to_service_entry())),
411            EntryType::Subscribe => Ok(Entry::SubscribeEventGroup(self.to_event_group_entry())),
412            EntryType::SubscribeAck => {
413                Ok(Entry::SubscribeAckEventGroup(self.to_event_group_entry()))
414            }
415        }
416    }
417
418    fn to_service_entry(self) -> ServiceEntry {
419        ServiceEntry {
420            index_first_options_run: self.index_first_options_run(),
421            index_second_options_run: self.index_second_options_run(),
422            options_count: self.options_count(),
423            service_id: self.service_id(),
424            instance_id: self.instance_id(),
425            major_version: self.major_version(),
426            ttl: self.ttl(),
427            minor_version: self.minor_version(),
428        }
429    }
430
431    fn to_event_group_entry(self) -> EventGroupEntry {
432        EventGroupEntry {
433            index_first_options_run: self.index_first_options_run(),
434            index_second_options_run: self.index_second_options_run(),
435            options_count: self.options_count(),
436            service_id: self.service_id(),
437            instance_id: self.instance_id(),
438            major_version: self.major_version(),
439            ttl: self.ttl(),
440            counter: self.counter(),
441            event_group_id: self.event_group_id(),
442        }
443    }
444}
445
446/// Iterator over 16-byte SD entries in a validated buffer.
447/// Entries are guaranteed valid (validated upfront in `SdHeaderView::parse`).
448pub struct EntryIter<'a> {
449    remaining: &'a [u8],
450}
451
452impl<'a> EntryIter<'a> {
453    pub(crate) fn new(buf: &'a [u8]) -> Self {
454        Self { remaining: buf }
455    }
456}
457
458impl<'a> Iterator for EntryIter<'a> {
459    type Item = EntryView<'a>;
460
461    fn next(&mut self) -> Option<Self::Item> {
462        if self.remaining.len() < ENTRY_SIZE {
463            return None;
464        }
465        let entry_bytes: &[u8; ENTRY_SIZE] = self.remaining[..ENTRY_SIZE]
466            .try_into()
467            .expect("length checked above");
468        self.remaining = &self.remaining[ENTRY_SIZE..];
469        Some(EntryView(entry_bytes))
470    }
471
472    fn size_hint(&self) -> (usize, Option<usize>) {
473        let n = self.remaining.len() / ENTRY_SIZE;
474        (n, Some(n))
475    }
476}
477
478impl ExactSizeIterator for EntryIter<'_> {}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    fn encode_entry(entry: &Entry) -> [u8; 17] {
485        let mut buf = [0u8; 17];
486        entry.encode(&mut buf.as_mut_slice()).unwrap();
487        buf
488    }
489
490    fn make_service_entry() -> ServiceEntry {
491        ServiceEntry {
492            index_first_options_run: 1,
493            index_second_options_run: 2,
494            options_count: OptionsCount::new(3, 4),
495            service_id: 0x1234,
496            instance_id: 0x5678,
497            major_version: 0x01,
498            ttl: 0x0000_00FF,
499            minor_version: 0x0000_0002,
500        }
501    }
502
503    fn make_event_group_entry() -> EventGroupEntry {
504        EventGroupEntry {
505            index_first_options_run: 1,
506            index_second_options_run: 2,
507            options_count: OptionsCount::new(3, 4),
508            service_id: 0xABCD,
509            instance_id: 0x0001,
510            major_version: 0x02,
511            ttl: 0x0000_0064,
512            counter: 0x0003,
513            event_group_id: 0x0010,
514        }
515    }
516
517    // --- EntryType ---
518
519    #[test]
520    fn entry_type_try_from_all_valid_values() {
521        assert_eq!(EntryType::try_from(0x00).unwrap(), EntryType::FindService);
522        assert_eq!(EntryType::try_from(0x01).unwrap(), EntryType::OfferService);
523        assert_eq!(
524            EntryType::try_from(0x02).unwrap(),
525            EntryType::StopOfferService
526        );
527        assert_eq!(EntryType::try_from(0x06).unwrap(), EntryType::Subscribe);
528        assert_eq!(EntryType::try_from(0x07).unwrap(), EntryType::SubscribeAck);
529    }
530
531    #[test]
532    fn entry_type_try_from_invalid_returns_error() {
533        assert!(matches!(
534            EntryType::try_from(0x03),
535            Err(Error::InvalidEntryType(0x03))
536        ));
537    }
538
539    #[test]
540    fn entry_type_into_u8_all_variants() {
541        assert_eq!(u8::from(EntryType::FindService), 0x00);
542        assert_eq!(u8::from(EntryType::OfferService), 0x01);
543        assert_eq!(u8::from(EntryType::StopOfferService), 0x02);
544        assert_eq!(u8::from(EntryType::Subscribe), 0x06);
545        assert_eq!(u8::from(EntryType::SubscribeAck), 0x07);
546    }
547
548    // --- OptionsCount ---
549
550    #[test]
551    fn options_count_round_trip() {
552        let oc = OptionsCount::new(3, 7);
553        let byte = u8::from(oc);
554        let decoded = OptionsCount::from(byte);
555        assert_eq!(decoded.first_options_count, 3);
556        assert_eq!(decoded.second_options_count, 7);
557    }
558
559    // --- required_size ---
560
561    #[test]
562    fn service_entry_required_size() {
563        assert_eq!(make_service_entry().required_size(), 16);
564    }
565
566    #[test]
567    fn event_group_entry_required_size() {
568        assert_eq!(make_event_group_entry().required_size(), 16);
569    }
570
571    #[test]
572    fn entry_required_size_all_variants() {
573        assert_eq!(Entry::FindService(make_service_entry()).required_size(), 17);
574        assert_eq!(
575            Entry::OfferService(make_service_entry()).required_size(),
576            17
577        );
578        assert_eq!(
579            Entry::StopOfferService(make_service_entry()).required_size(),
580            17
581        );
582        assert_eq!(
583            Entry::SubscribeEventGroup(make_event_group_entry()).required_size(),
584            17
585        );
586        assert_eq!(
587            Entry::SubscribeAckEventGroup(make_event_group_entry()).required_size(),
588            17
589        );
590    }
591
592    // --- first/second/total options count ---
593
594    #[test]
595    fn entry_options_count_service_variants() {
596        let se = make_service_entry(); // first=3, second=4
597        for entry in [
598            Entry::FindService(se),
599            Entry::OfferService(make_service_entry()),
600            Entry::StopOfferService(make_service_entry()),
601        ] {
602            assert_eq!(entry.first_options_count(), 3);
603            assert_eq!(entry.second_options_count(), 4);
604            assert_eq!(entry.total_options_count(), 7);
605        }
606    }
607
608    #[test]
609    fn entry_options_count_event_group_variants() {
610        let eg = make_event_group_entry(); // first=3, second=4
611        for entry in [
612            Entry::SubscribeEventGroup(eg.clone()),
613            Entry::SubscribeAckEventGroup(eg),
614        ] {
615            assert_eq!(entry.first_options_count(), 3);
616            assert_eq!(entry.second_options_count(), 4);
617            assert_eq!(entry.total_options_count(), 7);
618        }
619    }
620
621    // --- Entry encode / EntryView round-trips ---
622
623    #[test]
624    fn find_service_entry_round_trips() {
625        let entry = Entry::FindService(make_service_entry());
626        let buf = encode_entry(&entry);
627        // EntryView works on 16 bytes (type byte is first byte of the 16-byte entry)
628        // But Entry::encode writes type(1) + data(15) = 16 bytes out of the 17-byte buffer
629        let entry_bytes: &[u8; ENTRY_SIZE] = buf[..ENTRY_SIZE].try_into().unwrap();
630        let view = EntryView(entry_bytes);
631        assert_eq!(view.to_owned().unwrap(), entry);
632    }
633
634    #[test]
635    fn offer_service_entry_round_trips() {
636        let entry = Entry::OfferService(make_service_entry());
637        let buf = encode_entry(&entry);
638        let entry_bytes: &[u8; ENTRY_SIZE] = buf[..ENTRY_SIZE].try_into().unwrap();
639        let view = EntryView(entry_bytes);
640        assert_eq!(view.to_owned().unwrap(), entry);
641    }
642
643    #[test]
644    fn stop_offer_service_entry_round_trips() {
645        let entry = Entry::StopOfferService(make_service_entry());
646        let buf = encode_entry(&entry);
647        let entry_bytes: &[u8; ENTRY_SIZE] = buf[..ENTRY_SIZE].try_into().unwrap();
648        let view = EntryView(entry_bytes);
649        assert_eq!(view.to_owned().unwrap(), entry);
650    }
651
652    #[test]
653    fn subscribe_event_group_entry_round_trips() {
654        let entry = Entry::SubscribeEventGroup(make_event_group_entry());
655        let buf = encode_entry(&entry);
656        let entry_bytes: &[u8; ENTRY_SIZE] = buf[..ENTRY_SIZE].try_into().unwrap();
657        let view = EntryView(entry_bytes);
658        assert_eq!(view.to_owned().unwrap(), entry);
659    }
660
661    #[test]
662    fn subscribe_ack_event_group_entry_round_trips() {
663        let entry = Entry::SubscribeAckEventGroup(make_event_group_entry());
664        let buf = encode_entry(&entry);
665        let entry_bytes: &[u8; ENTRY_SIZE] = buf[..ENTRY_SIZE].try_into().unwrap();
666        let view = EntryView(entry_bytes);
667        assert_eq!(view.to_owned().unwrap(), entry);
668    }
669
670    #[test]
671    fn entry_view_invalid_type_returns_error() {
672        let buf: [u8; ENTRY_SIZE] = [0x03; ENTRY_SIZE]; // 0x03 is not a valid EntryType
673        let view = EntryView(&buf);
674        assert!(matches!(
675            view.to_owned(),
676            Err(Error::InvalidEntryType(0x03))
677        ));
678    }
679
680    // --- EntryIter ---
681
682    #[test]
683    fn entry_iter_empty() {
684        let iter = EntryIter::new(&[]);
685        assert_eq!(iter.len(), 0);
686    }
687
688    #[test]
689    fn entry_iter_two_entries() {
690        let e1 = Entry::FindService(make_service_entry());
691        let e2 = Entry::SubscribeEventGroup(make_event_group_entry());
692        let buf1 = encode_entry(&e1);
693        let buf2 = encode_entry(&e2);
694        // Concatenate the 16-byte entries (first 16 bytes of each 17-byte encode)
695        let mut combined = [0u8; 32];
696        combined[..16].copy_from_slice(&buf1[..16]);
697        combined[16..32].copy_from_slice(&buf2[..16]);
698
699        let mut iter = EntryIter::new(&combined);
700        assert_eq!(iter.len(), 2);
701        assert_eq!(iter.next().unwrap().to_owned().unwrap(), e1);
702        assert_eq!(iter.next().unwrap().to_owned().unwrap(), e2);
703        assert!(iter.next().is_none());
704    }
705}