Skip to main content

sails_idl_meta/
header.rs

1use crate::InterfaceId;
2use alloc::vec::Vec;
3use core::num::NonZeroU8;
4use parity_scale_codec::{Decode, Encode, Error, Input, Output};
5
6/// Sails protocol highest supported version.
7pub const HIGHEST_SUPPORTED_VERSION: u8 = 1;
8
9/// Sails protocol magic bytes.
10/// Bytes stand for the UTF-8 string `GM`.
11pub const MAGIC_BYTES: [u8; 2] = [0x47, 0x4D];
12
13/// Minimal Sails message header length in bytes.
14pub const MINIMAL_HLEN: u8 = 16;
15
16/// Sails message header.
17///
18/// The header is a feature of an IDLv2. It gives opportunity for on top of blockchain
19/// services to trace sails messages and programs.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct SailsMessageHeader {
22    version: Version,
23    hlen: HeaderLength,
24    interface_id: InterfaceId,
25    route_id: u8,
26    entry_id: u16,
27}
28
29impl SailsMessageHeader {
30    /// Creates a new Sails message header.
31    pub const fn new(
32        version: Version,
33        hlen: HeaderLength,
34        interface_id: InterfaceId,
35        route_id: u8,
36        entry_id: u16,
37    ) -> Self {
38        Self {
39            version,
40            hlen,
41            interface_id,
42            route_id,
43            entry_id,
44        }
45    }
46
47    pub const fn v1(interface_id: InterfaceId, entry_id: u16, route_id: u8) -> Self {
48        Self {
49            version: Version::v1(),
50            hlen: HeaderLength(MINIMAL_HLEN),
51            interface_id,
52            route_id,
53            entry_id,
54        }
55    }
56
57    /// Gets the version of the header.
58    pub const fn version(&self) -> Version {
59        self.version
60    }
61
62    /// Gets the header length.
63    pub const fn hlen(&self) -> HeaderLength {
64        self.hlen
65    }
66
67    /// Gets the interface ID.
68    pub const fn interface_id(&self) -> InterfaceId {
69        self.interface_id
70    }
71
72    /// Gets the route ID.
73    pub const fn route_id(&self) -> u8 {
74        self.route_id
75    }
76
77    /// Gets the entry ID.
78    pub const fn entry_id(&self) -> u16 {
79        self.entry_id
80    }
81}
82
83// Serialization and deserialization
84impl SailsMessageHeader {
85    /// Serialize header to bytes.
86    pub fn to_bytes(&self) -> Vec<u8> {
87        assert!(
88            self.hlen.inner() == MINIMAL_HLEN,
89            "encoding headers with extensions is not supported yet"
90        );
91        let mut bytes = Vec::with_capacity(self.hlen.inner() as usize);
92        bytes.extend_from_slice(Magic::new().as_bytes());
93        bytes.push(self.version.inner());
94        bytes.push(self.hlen.inner());
95        bytes.extend_from_slice(self.interface_id.as_bytes());
96        bytes.extend_from_slice(&self.entry_id.to_le_bytes());
97        bytes.push(self.route_id);
98        // Reserved byte
99        bytes.push(0);
100
101        bytes
102    }
103
104    /// Deserialize header from bytes advancing the slice.
105    pub fn try_read_bytes(bytes: &mut &[u8]) -> Result<Self, &'static str> {
106        let input_len = bytes.len();
107        if input_len < MINIMAL_HLEN as usize {
108            return Err("Insufficient bytes for header");
109        }
110
111        // Validate and consume magic bytes.
112        Magic::try_read_bytes(bytes)?;
113
114        let version = Version::try_read_bytes(bytes)?;
115        let hlen = HeaderLength::try_read_bytes(bytes)?;
116        if version == Version::v1() && hlen.0 > MINIMAL_HLEN {
117            return Err("Header len must be 16 in version 1");
118        }
119        let interface_id = InterfaceId::try_read_bytes(bytes)?;
120
121        let entry_id = u16::from_le_bytes([bytes[0], bytes[1]]);
122        let route_id = bytes[2];
123        let reserved = bytes[3];
124
125        if version == Version::v1() && reserved != 0 {
126            return Err("Reserved byte must be zero in version 1");
127        }
128
129        // Read 4 bytes for entry_id, route_id and reserved.
130        *bytes = &bytes[4..];
131
132        Ok(Self {
133            version,
134            hlen,
135            interface_id,
136            route_id,
137            entry_id,
138        })
139    }
140
141    /// Deserialize header from bytes (expects magic bytes at the start) without mutating the input.
142    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
143        let mut slice = bytes;
144        Self::try_read_bytes(&mut slice)
145    }
146
147    /// Tries to match the header's interface ID and route ID against a list of known interfaces in the program.
148    ///
149    /// If the header has route_id == 0 and exactly one interface ID match exists, the returned
150    /// MatchedInterface uses the program's route_id for that interface.
151    pub fn try_match_interfaces(
152        self,
153        interfaces: &[(InterfaceId, u8)],
154    ) -> Result<MatchedInterface, &'static str> {
155        let Self {
156            interface_id,
157            route_id,
158            entry_id,
159            ..
160        } = self;
161
162        let message_route = NonZeroU8::new(route_id);
163        let mut resolved_route = None;
164
165        for (_, program_route_id) in interfaces.iter().filter(|(id, _)| *id == interface_id) {
166            let program_route_id = match NonZeroU8::new(*program_route_id) {
167                Some(route) => route,
168                None => return Err("Interfaces must not contain route ID `0`"),
169            };
170            // match by `interface_id` and `route_id`
171            if message_route.is_some_and(|id| id == program_route_id) {
172                resolved_route = Some(program_route_id);
173                break;
174            }
175            // match by `interface_id` and set first `route_id`
176            if message_route.is_none() && resolved_route.replace(program_route_id).is_some() {
177                return Err("Can't infer the interface by route ID `0`, many instances");
178            }
179        }
180        match resolved_route {
181            Some(route) => Ok(MatchedInterface {
182                interface_id,
183                entry_id,
184                route_id: route.get(),
185            }),
186            None => Err("No matching interface and route ID found"),
187        }
188    }
189}
190
191impl Encode for SailsMessageHeader {
192    fn encode_to<O: Output + ?Sized>(&self, dest: &mut O) {
193        assert!(
194            self.hlen.inner() == MINIMAL_HLEN,
195            "encoding headers with extensions is not supported yet"
196        );
197        // Copy-paste for the optimization purpose, as `to_bytes` allocates a new Vec.
198        dest.write(Magic::new().as_bytes());
199        dest.push_byte(self.version.inner());
200        dest.push_byte(self.hlen.inner());
201        dest.write(self.interface_id.as_bytes());
202        dest.write(&self.entry_id.to_le_bytes());
203        dest.push_byte(self.route_id);
204        // Reserved byte
205        dest.push_byte(0);
206    }
207}
208
209impl Decode for SailsMessageHeader {
210    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
211        let mut header_bytes = [0u8; MINIMAL_HLEN as usize]; // Include magic bytes
212        input.read(&mut header_bytes)?;
213
214        let mut slice = header_bytes.as_slice();
215        Self::try_read_bytes(&mut slice).map_err(Error::from)
216    }
217}
218
219/// Sails message header's protocol magic bytes.
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
221pub struct Magic([u8; 2]);
222
223impl Magic {
224    #[allow(clippy::new_without_default)]
225    pub fn new() -> Self {
226        Self(MAGIC_BYTES)
227    }
228
229    /// Get magic bytes as a byte slice.
230    pub fn as_bytes(&self) -> &[u8] {
231        &self.0
232    }
233
234    /// Deserialize from bytes, advancing the slice.
235    pub fn try_read_bytes(bytes: &mut &[u8]) -> Result<Self, &'static str> {
236        if bytes.len() < MAGIC_BYTES.len() {
237            return Err("Insufficient bytes for magic");
238        }
239
240        let magic = [bytes[0], bytes[1]];
241        if magic != MAGIC_BYTES {
242            return Err("Invalid Sails magic bytes");
243        }
244
245        *bytes = &bytes[2..];
246        Ok(Self(magic))
247    }
248
249    /// Deserialize from bytes without mutating the input.
250    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
251        let mut slice = bytes;
252        Self::try_read_bytes(&mut slice)
253    }
254}
255
256impl Decode for Magic {
257    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
258        let mut magic = [0u8; 2];
259        input.read(&mut magic)?;
260
261        let mut slice = magic.as_slice();
262        Self::try_read_bytes(&mut slice).map_err(Error::from)
263    }
264}
265
266/// Sails message header's protocol version.
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
268pub struct Version(u8);
269
270impl Version {
271    /// Instantiates the type with version 1.
272    pub const fn v1() -> Self {
273        Self(1)
274    }
275
276    /// Instantiates the type with the latest supported version.
277    pub const fn latest() -> Self {
278        Self(HIGHEST_SUPPORTED_VERSION)
279    }
280
281    /// Creates a new version instance if the version is supported.
282    ///
283    /// Returns error if the version is unsupported, i.e. if:
284    /// - version is 0
285    /// - version is greater than highest supported version
286    pub fn new(version: u8) -> Result<Self, &'static str> {
287        if version == 0 || version > HIGHEST_SUPPORTED_VERSION {
288            Err("Unsupported Sails version")
289        } else {
290            Ok(Self(version))
291        }
292    }
293
294    /// Get inner version type.
295    pub fn inner(&self) -> u8 {
296        self.0
297    }
298
299    /// Deserialize from bytes, advancing the slice.
300    pub fn try_read_bytes(bytes: &mut &[u8]) -> Result<Self, &'static str> {
301        if bytes.is_empty() {
302            return Err("Insufficient bytes for version");
303        }
304
305        let version = bytes[0];
306        *bytes = &bytes[1..];
307        Self::new(version)
308    }
309
310    /// Deserialize from bytes without mutating the input.
311    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
312        let mut slice = bytes;
313        Self::try_read_bytes(&mut slice)
314    }
315}
316
317impl Decode for Version {
318    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
319        let version = input.read_byte()?;
320        let version_array = [version];
321
322        Self::try_read_bytes(&mut version_array.as_slice()).map_err(Error::from)
323    }
324}
325
326/// Sails message header length.
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
328pub struct HeaderLength(u8);
329
330impl HeaderLength {
331    pub fn new(hlen: u8) -> Result<Self, &'static str> {
332        if hlen < MINIMAL_HLEN {
333            Err("Header length is less than minimal Sails header length")
334        } else {
335            Ok(Self(hlen))
336        }
337    }
338
339    /// Get the header length as a u8.
340    pub fn inner(&self) -> u8 {
341        self.0
342    }
343
344    /// Deserialize from bytes, advancing the slice.
345    pub fn try_read_bytes(bytes: &mut &[u8]) -> Result<Self, &'static str> {
346        if bytes.is_empty() {
347            return Err("Insufficient bytes for header length");
348        }
349
350        let hlen = bytes[0];
351        *bytes = &bytes[1..];
352        Self::new(hlen)
353    }
354
355    /// Deserialize from bytes without mutating the input.
356    pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
357        let mut slice = bytes;
358        Self::try_read_bytes(&mut slice)
359    }
360}
361
362impl Decode for HeaderLength {
363    fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
364        let hlen = input.read_byte()?;
365
366        let hlen_array = [hlen];
367        Self::try_read_bytes(&mut hlen_array.as_slice()).map_err(Error::from)
368    }
369}
370
371/// The outcome of matching a message header against known interfaces.
372///
373/// Contains the matched interface ID, route ID, and entry ID to be executed.
374///
375/// The type is only instantiated upon successful matching. This guarantees, that
376/// the contained values are against known interfaces map.
377#[derive(Debug)]
378pub struct MatchedInterface {
379    interface_id: InterfaceId,
380    route_id: u8,
381    entry_id: u16,
382}
383
384impl MatchedInterface {
385    /// Consumes the matched interface and returns its components.
386    pub fn into_inner(self) -> (InterfaceId, u8, u16) {
387        (self.interface_id, self.route_id, self.entry_id)
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394    use alloc::vec;
395
396    #[test]
397    fn try_from_bytes_does_not_move_offset() {
398        let magic = Magic::new();
399        let bytes = magic.as_bytes();
400
401        let _ = Magic::try_from_bytes(bytes).expect("same bytes");
402        assert_eq!(bytes, [0x47, 0x4D]);
403    }
404
405    #[test]
406    fn magic_codec() {
407        let magic = Magic::new();
408
409        let encoded = magic.encode();
410        assert_eq!(MAGIC_BYTES.encode(), encoded);
411        let decoded = Magic::decode(&mut &encoded[..]).unwrap();
412
413        assert_eq!(magic, decoded);
414    }
415
416    #[test]
417    fn magic_serde() {
418        let magic = Magic::new();
419
420        let mut serialized = magic.as_bytes();
421        assert_eq!(MAGIC_BYTES, serialized);
422        let deserialized = Magic::try_read_bytes(&mut serialized).unwrap();
423
424        assert_eq!(magic, deserialized);
425    }
426
427    #[test]
428    fn magic_try_read_fails() {
429        // Invalid bytes
430        let invalid_bytes = [0x00, 0x00];
431        let mut slice = invalid_bytes.as_slice();
432        let result = Magic::try_read_bytes(&mut slice);
433
434        assert!(result.is_err());
435        assert_eq!(result.unwrap_err(), "Invalid Sails magic bytes");
436
437        // Insufficient bytes
438        let short_bytes = [0x47];
439        let mut slice = short_bytes.as_slice();
440        let result = Magic::try_read_bytes(&mut slice);
441
442        assert!(result.is_err());
443        assert_eq!(result.unwrap_err(), "Insufficient bytes for magic");
444    }
445
446    #[test]
447    fn version_serde() {
448        let version = Version::new(1).unwrap();
449
450        let serialized = version.inner();
451        assert_eq!(1u8, serialized);
452        let deserialized = Version::try_read_bytes(&mut [serialized].as_slice()).unwrap();
453
454        assert_eq!(version, deserialized);
455    }
456
457    #[test]
458    fn version_codec() {
459        let version = Version::new(1).unwrap();
460
461        let encoded = version.encode();
462        assert_eq!(1u8.encode(), encoded);
463        let decoded = Version::decode(&mut &encoded[..]).unwrap();
464
465        assert_eq!(version, decoded);
466    }
467
468    #[test]
469    fn version_try_read_fails() {
470        // Unsupported version
471        let bytes1 = [255];
472        let bytes2 = [0];
473        let mut slice1 = bytes1.as_slice();
474        let mut slice2 = bytes2.as_slice();
475
476        let result1 = Version::try_read_bytes(&mut slice1);
477        let result2 = Version::try_read_bytes(&mut slice2);
478        assert!(result1.is_err());
479        assert!(result2.is_err());
480
481        assert_eq!(result1.unwrap_err(), "Unsupported Sails version");
482        assert_eq!(result2.unwrap_err(), "Unsupported Sails version");
483
484        // Insufficient bytes
485        let bytes: [u8; 0] = [];
486        let mut slice = bytes.as_slice();
487        let result = Version::try_read_bytes(&mut slice);
488
489        assert!(result.is_err());
490        assert_eq!(result.unwrap_err(), "Insufficient bytes for version");
491    }
492
493    #[test]
494    fn version_latest() {
495        let version = Version::latest();
496        assert_eq!(version.inner(), HIGHEST_SUPPORTED_VERSION);
497    }
498
499    #[test]
500    fn header_length_serde() {
501        let hlen = HeaderLength::new(20).unwrap();
502
503        let serialized = hlen.inner();
504        assert_eq!(20u8, serialized);
505        let deserialized = HeaderLength::try_read_bytes(&mut [serialized].as_slice()).unwrap();
506
507        assert_eq!(hlen, deserialized);
508    }
509
510    #[test]
511    fn header_length_codec() {
512        let hlen = HeaderLength::new(20).unwrap();
513
514        let encoded = hlen.encode();
515        assert_eq!(20u8.encode(), encoded);
516        let decoded = HeaderLength::decode(&mut &encoded[..]).unwrap();
517
518        assert_eq!(hlen, decoded);
519    }
520
521    #[test]
522    fn header_try_read_fails() {
523        // Header length less than minimal
524        let bytes1 = [MINIMAL_HLEN - 1];
525        let mut slice1 = bytes1.as_slice();
526        let result1 = HeaderLength::try_read_bytes(&mut slice1);
527
528        assert!(result1.is_err());
529        assert_eq!(
530            result1.unwrap_err(),
531            "Header length is less than minimal Sails header length"
532        );
533
534        // Insufficient bytes
535        let bytes: [u8; 0] = [];
536        let mut slice = bytes.as_slice();
537        let result2 = HeaderLength::try_read_bytes(&mut slice);
538
539        assert!(result2.is_err());
540        assert_eq!(result2.unwrap_err(), "Insufficient bytes for header length");
541    }
542
543    #[test]
544    fn message_header_serde() {
545        let header = SailsMessageHeader {
546            version: Version::new(1).unwrap(),
547            hlen: HeaderLength::new(MINIMAL_HLEN).unwrap(),
548            interface_id: InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]),
549            route_id: 42,
550            entry_id: 1234,
551        };
552
553        let bytes = header.to_bytes();
554        assert_eq!(bytes.len(), MINIMAL_HLEN as usize);
555        assert_eq!(
556            bytes,
557            vec![
558                0x47, 0x4D, // magic ("GM")
559                1,    // version
560                16,   // hlen
561                1, 2, 3, 4, 5, 6, 7, 8, // interface_id
562                210, 4,  // entry_id (1234 in little-endian)
563                42, // route_id
564                0,  // reserved
565            ]
566        );
567
568        let mut slice = bytes.as_slice();
569        let deserialized = SailsMessageHeader::try_read_bytes(&mut slice).unwrap();
570
571        assert_eq!(header, deserialized);
572        assert_eq!(slice.len(), 0); // all bytes consumed
573    }
574
575    #[test]
576    fn message_header_try_read_fails_invalid_magic() {
577        // Insufficient bytes (no route id)
578        let bytes = [0x47, 0x4D, 1, 15, 1, 2, 3, 4, 5, 6, 7, 8, 210, 4];
579
580        let mut slice = bytes.as_slice();
581        let result = SailsMessageHeader::try_read_bytes(&mut slice);
582
583        assert!(result.is_err());
584        assert_eq!(result.unwrap_err(), "Insufficient bytes for header");
585    }
586
587    #[test]
588    fn message_header_serde_with_surplus() {
589        let header_bytes = vec![
590            0x47, 0x4D, // magic ("GM")
591            1,    // version
592            16,   // hlen
593            1, 2, 3, 4, 5, 6, 7, 8, // interface_id
594            210, 4,  // entry_id (1234 in little-endian)
595            42, // route_id
596            0,  // reserved
597            // Surplus bytes (payload)
598            99, 100, 101,
599        ];
600        let mut slice = header_bytes.as_slice();
601        let deserialized = SailsMessageHeader::try_read_bytes(&mut slice).unwrap();
602        assert_eq!(deserialized.version, Version::new(1).unwrap());
603        assert_eq!(deserialized.hlen, HeaderLength::new(MINIMAL_HLEN).unwrap());
604        assert_eq!(
605            deserialized.interface_id,
606            InterfaceId([1, 2, 3, 4, 5, 6, 7, 8])
607        );
608        assert_eq!(deserialized.entry_id, 1234);
609        assert_eq!(deserialized.route_id, 42);
610        assert_eq!(slice, &[99, 100, 101]); // Surplus bytes
611    }
612
613    #[test]
614    fn message_header_with_non_zero_reserved_fails() {
615        // Reserved byte is non-zero when version is 1
616        let header_bytes = vec![
617            0x47, 0x4D, // magic ("GM")
618            1,    // version
619            16,   // hlen
620            1, 2, 3, 4, 5, 6, 7, 8, // interface_id
621            210, 4,  // entry_id (1234 in little-endian)
622            42, // route_id
623            1,  // reserved (non-zero)
624        ];
625        let mut slice = header_bytes.as_slice();
626        let result = SailsMessageHeader::try_read_bytes(&mut slice);
627
628        assert!(result.is_err());
629        assert_eq!(
630            result.unwrap_err(),
631            "Reserved byte must be zero in version 1"
632        );
633
634        // Reserved byte is non-zero when version is 2 (unsupported currently version)
635        let header_bytes = vec![
636            0x47, 0x4D, // magic ("GM")
637            2,    // version
638            16,   // hlen
639            1, 2, 3, 4, 5, 6, 7, 8, // interface_id
640            210, 4,  // entry_id (1234 in little-endian)
641            42, // route_id
642            1,  // reserved (non-zero)
643        ];
644        let mut slice = header_bytes.as_slice();
645        let result = SailsMessageHeader::try_read_bytes(&mut slice);
646        assert!(result.is_err());
647        assert_eq!(result.unwrap_err(), "Unsupported Sails version");
648    }
649
650    #[test]
651    fn match_interfaces_works() {
652        // Simple test case
653        let header = SailsMessageHeader {
654            version: Version::new(1).unwrap(),
655            hlen: HeaderLength::new(16).unwrap(),
656            interface_id: InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]),
657            route_id: 1,
658            entry_id: 100,
659        };
660
661        let interfaces = [(InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 1)];
662        let result = header.try_match_interfaces(&interfaces).unwrap();
663        let (iid, rid, eid) = result.into_inner();
664
665        assert_eq!(iid, InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]));
666        assert_eq!(rid, 1);
667        assert_eq!(eid, 100);
668
669        // Route id zero with single matching interface
670        let header = SailsMessageHeader {
671            version: Version::new(1).unwrap(),
672            hlen: HeaderLength::new(16).unwrap(),
673            interface_id: InterfaceId([9, 8, 7, 6, 5, 4, 3, 2]),
674            route_id: 0,
675            entry_id: 200,
676        };
677
678        let interfaces = [
679            (InterfaceId([9, 8, 7, 6, 5, 4, 3, 2]), 42),
680            (InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 42),
681        ];
682
683        let result = header.try_match_interfaces(&interfaces).unwrap();
684        let (iid, rid, eid) = result.into_inner();
685        assert_eq!(iid, InterfaceId([9, 8, 7, 6, 5, 4, 3, 2]));
686        assert_eq!(rid, 42);
687        assert_eq!(eid, 200);
688    }
689
690    #[test]
691    fn match_interfaces_route_zero_resolves_to_program_route() {
692        let header = SailsMessageHeader {
693            version: Version::new(1).unwrap(),
694            hlen: HeaderLength::new(16).unwrap(),
695            interface_id: InterfaceId([7, 7, 7, 7, 7, 7, 7, 7]),
696            route_id: 0,
697            entry_id: 300,
698        };
699
700        let interfaces = [
701            (InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 10),
702            (InterfaceId([7, 7, 7, 7, 7, 7, 7, 7]), 99),
703        ];
704
705        let result = header.try_match_interfaces(&interfaces).unwrap();
706        let (iid, rid, eid) = result.into_inner();
707        assert_eq!(iid, InterfaceId([7, 7, 7, 7, 7, 7, 7, 7]));
708        assert_eq!(rid, 99);
709        assert_eq!(eid, 300);
710    }
711
712    #[test]
713    fn match_interfaces_no_match() {
714        let header = SailsMessageHeader {
715            version: Version::new(1).unwrap(),
716            hlen: HeaderLength::new(16).unwrap(),
717            interface_id: InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]),
718            route_id: 1,
719            entry_id: 100,
720        };
721
722        let interfaces = [(InterfaceId([9, 9, 9, 9, 9, 9, 9, 9]), 1)];
723        let result = header.try_match_interfaces(&interfaces);
724
725        assert!(result.is_err());
726        assert_eq!(
727            result.unwrap_err(),
728            "No matching interface and route ID found"
729        );
730    }
731
732    #[test]
733    fn match_interfaces_multiple_same_interface_with_route_zero() {
734        let header = SailsMessageHeader {
735            version: Version::new(1).unwrap(),
736            hlen: HeaderLength::new(16).unwrap(),
737            interface_id: InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]),
738            route_id: 0,
739            entry_id: 100,
740        };
741
742        let interfaces = [
743            (InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 1),
744            (InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 2),
745        ];
746        let result = header.try_match_interfaces(&interfaces);
747
748        assert!(result.is_err());
749        assert_eq!(
750            result.unwrap_err(),
751            "Can't infer the interface by route ID `0`, many instances"
752        );
753    }
754
755    #[test]
756    fn match_interfaces_route_mismatch() {
757        let header = SailsMessageHeader {
758            version: Version::new(1).unwrap(),
759            hlen: HeaderLength::new(16).unwrap(),
760            interface_id: InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]),
761            route_id: 5,
762            entry_id: 100,
763        };
764
765        let interfaces = [(InterfaceId([1, 2, 3, 4, 5, 6, 7, 8]), 1)];
766        let result = header.try_match_interfaces(&interfaces);
767
768        assert!(result.is_err());
769        assert_eq!(
770            result.unwrap_err(),
771            "No matching interface and route ID found"
772        );
773    }
774}