Skip to main content

simple_someip/protocol/
header.rs

1use crate::protocol::{Error, MessageId, MessageTypeField, ReturnCode, byte_order::WriteBytesExt};
2use automotive_wire_codec::Decode;
3
4/// SOME/IP header
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct Header {
7    /// Message ID, encoding service ID and method ID
8    message_id: MessageId,
9    /// Length of the message in bytes, starting at the request Id
10    /// Total length of the message is therefore length + 8
11    length: u32,
12    /// SOME/IP Request ID (4 bytes): Client ID [31:16] + Session ID [15:0].
13    request_id: u32,
14    protocol_version: u8,
15    interface_version: u8,
16    message_type: MessageTypeField,
17    return_code: ReturnCode,
18}
19
20impl Header {
21    /// Returns the message ID (service ID + method ID).
22    #[must_use]
23    pub const fn message_id(&self) -> MessageId {
24        self.message_id
25    }
26
27    /// Returns the length field (payload size + 8).
28    #[must_use]
29    pub const fn length(&self) -> u32 {
30        self.length
31    }
32
33    /// Returns the request ID (client ID + session ID).
34    #[must_use]
35    pub const fn request_id(&self) -> u32 {
36        self.request_id
37    }
38
39    /// Returns the protocol version.
40    #[must_use]
41    pub const fn protocol_version(&self) -> u8 {
42        self.protocol_version
43    }
44
45    /// Returns the interface version.
46    #[must_use]
47    pub const fn interface_version(&self) -> u8 {
48        self.interface_version
49    }
50
51    /// Returns the message type field.
52    #[must_use]
53    pub const fn message_type(&self) -> MessageTypeField {
54        self.message_type
55    }
56
57    /// Returns the return code.
58    #[must_use]
59    pub const fn return_code(&self) -> ReturnCode {
60        self.return_code
61    }
62
63    /// Return the 8-byte "upper header" used by E2E UPPER-HEADER-BITS-TO-SHIFT.
64    ///
65    /// Layout (big-endian): `request_id(4)` + `protocol_version(1)` + `interface_version(1)`
66    ///                      + `message_type(1)` + `return_code(1)`
67    ///
68    /// Note: `request_id` is the full 4-byte SOME/IP Request ID field
69    /// (Client ID \[31:16\] + Session ID \[15:0\]), not just the 2-byte Session ID.
70    #[must_use]
71    pub const fn upper_header_bytes(&self) -> [u8; 8] {
72        let rid = self.request_id.to_be_bytes();
73        [
74            rid[0],
75            rid[1],
76            rid[2],
77            rid[3],
78            self.protocol_version,
79            self.interface_version,
80            self.message_type.as_u8(),
81            self.return_code.as_u8(),
82        ]
83    }
84
85    /// Creates a header from raw field values.
86    ///
87    /// Unlike [`new`](Self::new), the `length` field is taken directly rather
88    /// than being computed from a payload size.  This is the inverse of the
89    /// accessor methods and is useful for FFI or any context where the caller
90    /// already has the raw on-wire field values.
91    #[must_use]
92    pub const fn from_fields(
93        message_id: MessageId,
94        length: u32,
95        request_id: u32,
96        protocol_version: u8,
97        interface_version: u8,
98        message_type: MessageTypeField,
99        return_code: ReturnCode,
100    ) -> Self {
101        Self {
102            message_id,
103            length,
104            request_id,
105            protocol_version,
106            interface_version,
107            message_type,
108            return_code,
109        }
110    }
111
112    /// Creates a new header with the given fields.
113    ///
114    /// # Panics
115    ///
116    /// Panics if `payload_len` exceeds `u32::MAX - 8`.
117    #[must_use]
118    #[allow(clippy::cast_possible_truncation)]
119    pub const fn new(
120        message_id: MessageId,
121        request_id: u32,
122        protocol_version: u8,
123        interface_version: u8,
124        message_type: MessageTypeField,
125        return_code: ReturnCode,
126        payload_len: usize,
127    ) -> Self {
128        assert!(payload_len <= u32::MAX as usize - 8);
129        Self {
130            message_id,
131            length: 8 + payload_len as u32,
132            request_id,
133            protocol_version,
134            interface_version,
135            message_type,
136            return_code,
137        }
138    }
139
140    /// Creates a new SOME/IP-SD header with standard SD field values.
141    ///
142    /// # Panics
143    ///
144    /// Panics if `sd_header_size` exceeds `u32::MAX - 8`.
145    #[must_use]
146    #[allow(clippy::cast_possible_truncation)]
147    pub const fn new_sd(request_id: u32, sd_header_size: usize) -> Self {
148        assert!(sd_header_size <= u32::MAX as usize - 8);
149        Self {
150            message_id: MessageId::SD,
151            length: 8 + sd_header_size as u32,
152            request_id,
153            protocol_version: 0x01,
154            interface_version: 0x01,
155            message_type: MessageTypeField::new_sd(),
156            return_code: ReturnCode::Ok,
157        }
158    }
159
160    /// Creates a new header for a SOME/IP event notification.
161    ///
162    /// # Panics
163    ///
164    /// Panics if `payload_len` exceeds `u32::MAX - 8`.
165    #[must_use]
166    #[allow(clippy::cast_possible_truncation)]
167    pub const fn new_event(
168        service_id: u16,
169        event_id: u16,
170        request_id: u32,
171        protocol_version: u8,
172        interface_version: u8,
173        payload_len: usize,
174    ) -> Self {
175        assert!(payload_len <= u32::MAX as usize - 8);
176        Self {
177            message_id: MessageId::new_from_service_and_method(service_id, event_id),
178            length: 8 + payload_len as u32,
179            request_id,
180            protocol_version,
181            interface_version,
182            message_type: MessageTypeField::new(crate::protocol::MessageType::Notification, false),
183            return_code: ReturnCode::Ok,
184        }
185    }
186
187    /// Returns `true` if this is a SOME/IP-SD message.
188    #[must_use]
189    pub const fn is_sd(&self) -> bool {
190        self.message_id.is_sd()
191    }
192
193    /// Returns the payload size in bytes (`length - 8`).
194    #[must_use]
195    pub const fn payload_size(&self) -> usize {
196        (self.length as usize).saturating_sub(8)
197    }
198
199    /// Sets the request ID field.
200    pub const fn set_request_id(&mut self, request_id: u32) {
201        self.request_id = request_id;
202    }
203}
204
205/// Zero-copy view into a 16-byte SOME/IP header in a buffer.
206#[derive(Clone, Copy, Debug)]
207pub struct HeaderView<'a>(&'a [u8; 16]);
208
209impl<'a> HeaderView<'a> {
210    /// Parse and validate a SOME/IP header from the beginning of `buf`.
211    /// Returns `(view, remaining_bytes)` on success.
212    ///
213    /// # Errors
214    ///
215    /// Returns an error if `buf` is shorter than 16 bytes, the protocol version is
216    /// not `0x01`, the message type byte is unrecognized, or the return code is invalid.
217    ///
218    /// # Panics
219    ///
220    /// Cannot panic — the `expect` is guarded by a length check above it.
221    ///
222    /// This is a thin wrapper over the [`Decode`] impl, which is the single
223    /// source of decode logic for this type.
224    pub fn parse(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> {
225        Self::decode(buf)
226    }
227
228    /// Returns the message ID (service ID + method ID).
229    #[must_use]
230    pub fn message_id(&self) -> MessageId {
231        MessageId::from(u32::from_be_bytes([
232            self.0[0], self.0[1], self.0[2], self.0[3],
233        ]))
234    }
235
236    /// Returns the length field (payload size + 8).
237    #[must_use]
238    pub fn length(&self) -> u32 {
239        u32::from_be_bytes([self.0[4], self.0[5], self.0[6], self.0[7]])
240    }
241
242    /// Returns the request ID (client ID + session ID).
243    #[must_use]
244    pub fn request_id(&self) -> u32 {
245        u32::from_be_bytes([self.0[8], self.0[9], self.0[10], self.0[11]])
246    }
247
248    /// Returns the payload size in bytes (`length - 8`).
249    #[must_use]
250    pub fn payload_size(&self) -> usize {
251        (self.length() as usize).saturating_sub(8)
252    }
253
254    /// Returns header bytes 8..16: the request ID, protocol and interface
255    /// versions, message type, and return code. E2E Profile 5 with-header
256    /// CRC covers these bytes.
257    #[must_use]
258    pub const fn upper_header_bytes(&self) -> [u8; 8] {
259        [
260            self.0[8], self.0[9], self.0[10], self.0[11], self.0[12], self.0[13], self.0[14],
261            self.0[15],
262        ]
263    }
264
265    /// Returns the protocol version.
266    #[must_use]
267    pub fn protocol_version(&self) -> u8 {
268        self.0[12]
269    }
270
271    /// Returns the interface version.
272    #[must_use]
273    pub fn interface_version(&self) -> u8 {
274        self.0[13]
275    }
276
277    /// Returns the message type field.
278    ///
279    /// # Panics
280    ///
281    /// Cannot panic — the value is validated during [`Self::parse`].
282    #[must_use]
283    pub fn message_type(&self) -> MessageTypeField {
284        // Safe: validated in parse()
285        MessageTypeField::try_from(self.0[14]).expect("validated in parse")
286    }
287
288    /// Returns the return code.
289    ///
290    /// # Panics
291    ///
292    /// Cannot panic — the value is validated during [`Self::parse`].
293    #[must_use]
294    pub fn return_code(&self) -> ReturnCode {
295        // Safe: validated in parse()
296        ReturnCode::try_from(self.0[15]).expect("validated in parse")
297    }
298
299    /// Returns `true` if this is a SOME/IP-SD message.
300    #[must_use]
301    pub fn is_sd(&self) -> bool {
302        self.message_id().is_sd()
303    }
304
305    /// Copies the view into an owned [`Header`].
306    #[must_use]
307    pub fn to_owned(&self) -> Header {
308        Header {
309            message_id: self.message_id(),
310            length: self.length(),
311            request_id: self.request_id(),
312            protocol_version: self.protocol_version(),
313            interface_version: self.interface_version(),
314            message_type: self.message_type(),
315            return_code: self.return_code(),
316        }
317    }
318}
319
320impl<'a> Decode<'a> for HeaderView<'a> {
321    type Error = Error;
322
323    /// Decode and validate a SOME/IP header from the front of `buf`.
324    ///
325    /// Returns `(view, remaining_bytes)` on success.
326    ///
327    /// # Errors
328    ///
329    /// Returns an error if `buf` is shorter than 16 bytes, the protocol version is
330    /// not `0x01`, the message type byte is unrecognized, or the return code is invalid.
331    ///
332    /// # Panics
333    ///
334    /// Cannot panic — the `expect` is guarded by a length check above it.
335    fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> {
336        if buf.len() < 16 {
337            return Err(automotive_wire_codec::Incomplete {
338                needed: 16,
339                available: buf.len(),
340            }
341            .into());
342        }
343        let header_bytes: &[u8; 16] = buf[..16].try_into().expect("length checked above");
344        let view = Self(header_bytes);
345
346        // Validate protocol version
347        let pv = view.protocol_version();
348        if pv != 0x01 {
349            return Err(Error::InvalidProtocolVersion(pv));
350        }
351        // Validate message type
352        MessageTypeField::try_from(header_bytes[14])?;
353        // Validate return code
354        ReturnCode::try_from(header_bytes[15])?;
355
356        Ok((view, &buf[16..]))
357    }
358}
359
360impl automotive_wire_codec::Encode for Header {
361    type Error = Error;
362
363    fn encoded_size(&self) -> Result<usize, Self::Error> {
364        Ok(16)
365    }
366
367    fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, Error> {
368        writer.write_u32_be(self.message_id.message_id())?;
369        writer.write_u32_be(self.length)?;
370        writer.write_u32_be(self.request_id)?;
371        writer.write_u8(self.protocol_version)?;
372        writer.write_u8(self.interface_version)?;
373        writer.write_u8(u8::from(self.message_type))?;
374        writer.write_u8(u8::from(self.return_code))?;
375        Ok(16)
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use crate::protocol::{Error, MessageId, MessageTypeField, ReturnCode};
383    use crate::traits::EncodeExt;
384    use automotive_wire_codec::Encode;
385
386    fn make_header() -> Header {
387        Header {
388            message_id: MessageId::new_from_service_and_method(0x1234, 0x0001),
389            length: 16,
390            request_id: 0xABCD_0042,
391            protocol_version: 0x01,
392            interface_version: 0x03,
393            message_type: MessageTypeField::try_from(0x00).unwrap(), // Request
394            return_code: ReturnCode::Ok,
395        }
396    }
397
398    fn encode_header(h: &Header) -> [u8; 16] {
399        let mut buf = [0u8; 16];
400        h.encode(&mut buf.as_mut_slice()).unwrap();
401        buf
402    }
403
404    // --- upper_header_bytes ---
405
406    #[test]
407    fn upper_header_bytes_layout() {
408        let h = make_header();
409        let ub = h.upper_header_bytes();
410        let rid = h.request_id().to_be_bytes();
411        assert_eq!(ub[0..4], rid);
412        assert_eq!(ub[4], h.protocol_version());
413        assert_eq!(ub[5], h.interface_version());
414        assert_eq!(ub[6], u8::from(h.message_type()));
415        assert_eq!(ub[7], u8::from(h.return_code()));
416    }
417
418    // --- new_sd ---
419
420    #[test]
421    fn new_sd_fields() {
422        let h = Header::new_sd(0x0000_0001, 28);
423        assert_eq!(h.message_id(), MessageId::SD);
424        assert_eq!(h.length(), 8 + 28);
425        assert_eq!(h.request_id(), 0x0000_0001);
426        assert_eq!(h.protocol_version(), 0x01);
427        assert_eq!(h.interface_version(), 0x01);
428        assert_eq!(h.return_code(), ReturnCode::Ok);
429    }
430
431    // --- is_sd ---
432
433    #[test]
434    fn is_sd_true_for_sd_header() {
435        let h = Header::new_sd(0, 12);
436        assert!(h.is_sd());
437    }
438
439    #[test]
440    fn is_sd_false_for_non_sd_header() {
441        let h = make_header();
442        assert!(!h.is_sd());
443    }
444
445    // --- payload_size ---
446
447    #[test]
448    fn payload_size_returns_length_minus_8() {
449        let h = Header {
450            length: 24,
451            ..make_header()
452        };
453        assert_eq!(h.payload_size(), 16);
454    }
455
456    // --- set_request_id ---
457
458    #[test]
459    fn set_request_id_updates_value() {
460        let mut h = make_header();
461        h.set_request_id(0xDEAD_BEEF);
462        assert_eq!(h.request_id(), 0xDEAD_BEEF);
463    }
464
465    // --- required_size ---
466
467    #[test]
468    fn required_size_is_16() {
469        assert_eq!(make_header().encoded_size().unwrap(), 16);
470    }
471
472    // --- encode / parse round-trip ---
473
474    #[test]
475    fn encode_parse_round_trip() {
476        let h = make_header();
477        let buf = encode_header(&h);
478        let (view, remaining) = HeaderView::parse(&buf[..]).unwrap();
479        assert_eq!(view.to_owned(), h);
480        assert!(remaining.is_empty());
481    }
482
483    #[test]
484    fn encode_returns_16() {
485        let h = make_header();
486        let mut buf = [0u8; 16];
487        let n = h.encode(&mut buf.as_mut_slice()).unwrap();
488        assert_eq!(n, 16);
489    }
490
491    #[test]
492    fn sd_header_round_trips() {
493        let h = Header::new_sd(0x0000_0042, 28);
494        let buf = encode_header(&h);
495        let (view, _) = HeaderView::parse(&buf[..]).unwrap();
496        assert_eq!(view.to_owned(), h);
497    }
498
499    // --- parse with exactly-sized slice ---
500
501    #[test]
502    fn parse_exact_size_slice_returns_empty_remainder() {
503        let h = make_header();
504        let buf = encode_header(&h);
505        // buf is exactly 16 bytes — no extra data
506        let (view, remaining) = HeaderView::parse(&buf).unwrap();
507        assert_eq!(view.to_owned(), h);
508        assert!(remaining.is_empty());
509    }
510
511    // --- parse error paths ---
512
513    #[test]
514    fn parse_invalid_protocol_version_returns_error() {
515        let mut h = make_header();
516        h.protocol_version = 0x02;
517        // Manually encode with wrong protocol version
518        let mid = h.message_id.message_id().to_be_bytes();
519        let len = h.length.to_be_bytes();
520        let rid = h.request_id.to_be_bytes();
521        let buf: [u8; 16] = [
522            mid[0], mid[1], mid[2], mid[3], len[0], len[1], len[2], len[3], rid[0], rid[1], rid[2],
523            rid[3], 0x02, // bad protocol version
524            0x03, 0x00, 0x00,
525        ];
526        assert!(matches!(
527            HeaderView::parse(&buf[..]),
528            Err(Error::InvalidProtocolVersion(0x02))
529        ));
530    }
531
532    #[test]
533    fn parse_invalid_message_type_returns_error() {
534        let h = make_header();
535        let mut buf = encode_header(&h);
536        buf[14] = 0xFF; // invalid message type
537        assert!(matches!(
538            HeaderView::parse(&buf[..]),
539            Err(Error::InvalidMessageTypeField(0xFF))
540        ));
541    }
542
543    #[test]
544    fn parse_invalid_return_code_returns_error() {
545        let h = make_header();
546        let mut buf = encode_header(&h);
547        buf[15] = 0x5F; // invalid return code
548        assert!(matches!(
549            HeaderView::parse(&buf[..]),
550            Err(Error::InvalidReturnCode(0x5F))
551        ));
552    }
553
554    #[test]
555    fn parse_truncated_input_returns_eof() {
556        let buf: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
557        assert!(matches!(
558            HeaderView::parse(&buf[..]),
559            Err(Error::Incomplete(automotive_wire_codec::Incomplete {
560                needed: 16,
561                available: 4,
562            }))
563        ));
564    }
565
566    // --- Decode trait (Phase 3) ---
567
568    #[test]
569    fn decode_returns_header_and_remainder() {
570        use automotive_wire_codec::Decode;
571        let h = make_header();
572        let mut buf = [0u8; 20];
573        buf[..16].copy_from_slice(&encode_header(&h));
574        buf[16..].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
575        let (view, rest) = HeaderView::decode(&buf).unwrap();
576        assert_eq!(view.to_owned(), h);
577        assert_eq!(rest, &[0xAA, 0xBB, 0xCC, 0xDD]);
578    }
579
580    #[test]
581    fn decode_exact_rejects_trailing() {
582        use automotive_wire_codec::Decode;
583        let h = make_header();
584        let mut buf = [0u8; 17];
585        buf[..16].copy_from_slice(&encode_header(&h));
586        assert!(matches!(
587            HeaderView::decode_exact(&buf),
588            Err(Error::Trailing(_))
589        ));
590        assert!(HeaderView::decode_exact(&buf[..16]).is_ok());
591    }
592
593    // --- from_fields ---
594
595    #[test]
596    fn from_fields_round_trip() {
597        let h = make_header();
598        let h2 = Header::from_fields(
599            h.message_id(),
600            h.length(),
601            h.request_id(),
602            h.protocol_version(),
603            h.interface_version(),
604            h.message_type(),
605            h.return_code(),
606        );
607        assert_eq!(h, h2);
608    }
609
610    // --- new_event ---
611
612    #[test]
613    fn new_event_fields() {
614        let h = Header::new_event(0x5B, 0x8001, 0x0001, 0x01, 0x03, 10);
615        assert_eq!(h.message_id().service_id(), 0x5B);
616        assert_eq!(h.message_id().method_id(), 0x8001);
617        assert_eq!(h.request_id(), 0x0001);
618        assert_eq!(h.protocol_version(), 0x01);
619        assert_eq!(h.interface_version(), 0x03);
620        assert_eq!(h.length(), 18); // 8 + 10
621        assert_eq!(h.return_code(), ReturnCode::Ok);
622    }
623
624    // --- new constructor ---
625
626    #[test]
627    fn new_constructor_sets_length() {
628        let h = Header::new(
629            MessageId::new_from_service_and_method(0x1234, 0x0001),
630            0x0001,
631            0x01,
632            0x01,
633            MessageTypeField::try_from(0x00).unwrap(),
634            ReturnCode::Ok,
635            100,
636        );
637        assert_eq!(h.length(), 108); // 8 + 100
638        assert_eq!(h.payload_size(), 100);
639    }
640
641    // --- HeaderView accessors ---
642
643    #[test]
644    fn header_view_accessors() {
645        let h = make_header();
646        let buf = encode_header(&h);
647        let (view, _) = HeaderView::parse(&buf[..]).unwrap();
648        assert_eq!(view.message_id(), h.message_id());
649        assert_eq!(view.length(), h.length());
650        assert_eq!(view.request_id(), h.request_id());
651        assert_eq!(view.payload_size(), h.payload_size());
652        assert_eq!(view.protocol_version(), h.protocol_version());
653        assert_eq!(view.interface_version(), h.interface_version());
654        assert_eq!(view.message_type(), h.message_type());
655        assert_eq!(view.return_code(), h.return_code());
656        assert_eq!(view.is_sd(), h.is_sd());
657    }
658
659    // --- Encode/EncodeExt default methods (encode_to_slice / encode_to_vec) ---
660
661    #[test]
662    fn encode_to_slice_works() {
663        let h = make_header();
664        let mut buf = [0u8; 16];
665        let n = h.encode_to_slice(&mut buf).unwrap();
666        assert_eq!(n, 16);
667        let (view, _) = HeaderView::parse(&buf).unwrap();
668        assert_eq!(view.to_owned(), h);
669    }
670
671    #[cfg(feature = "std")]
672    #[test]
673    fn encode_to_vec_works() {
674        let h = make_header();
675        let buf = h.encode_to_vec().unwrap();
676        assert_eq!(buf.len(), 16);
677        let (view, _) = HeaderView::parse(&buf).unwrap();
678        assert_eq!(view.to_owned(), h);
679    }
680
681    // --- Encode size-exactness invariant ---
682
683    #[test]
684    fn encoded_size_matches_bytes_written() {
685        use automotive_wire_codec::CountingSink;
686        let h = make_header();
687        let mut sink = CountingSink::new();
688        let written = h.encode(&mut sink).unwrap();
689        assert_eq!(written, h.encoded_size().unwrap());
690        assert_eq!(written, sink.count());
691    }
692
693    #[test]
694    fn encode_to_slice_too_small_yields_insufficient_buffer() {
695        use automotive_wire_codec::{EncodeToSliceError, InsufficientBuffer};
696        let h = make_header();
697        let mut buf = [0u8; 4];
698        let err = h.encode_to_slice(&mut buf).unwrap_err();
699        assert!(matches!(
700            err,
701            EncodeToSliceError::InsufficientBuffer(InsufficientBuffer {
702                needed: 16,
703                available: 4,
704            })
705        ));
706    }
707}