pcapsql_core/protocol/
rtnetlink.rs

1//! RTNetlink protocol parser.
2//!
3//! Parses NETLINK_ROUTE (family 0) messages from Linux Netlink captures.
4//! Handles link, address, and route messages using netlink-packet-route.
5
6use compact_str::CompactString;
7use smallvec::SmallVec;
8
9use super::netlink::family as netlink_family;
10use super::{FieldValue, ParseContext, ParseResult, Protocol};
11use crate::schema::{DataKind, FieldDescriptor};
12
13/// RTNetlink message type constants.
14pub mod msg_type {
15    pub const RTM_NEWLINK: u16 = 16;
16    pub const RTM_DELLINK: u16 = 17;
17    pub const RTM_GETLINK: u16 = 18;
18    pub const RTM_SETLINK: u16 = 19;
19    pub const RTM_NEWADDR: u16 = 20;
20    pub const RTM_DELADDR: u16 = 21;
21    pub const RTM_GETADDR: u16 = 22;
22    pub const RTM_NEWROUTE: u16 = 24;
23    pub const RTM_DELROUTE: u16 = 25;
24    pub const RTM_GETROUTE: u16 = 26;
25    pub const RTM_NEWNEIGH: u16 = 28;
26    pub const RTM_DELNEIGH: u16 = 29;
27    pub const RTM_GETNEIGH: u16 = 30;
28}
29
30/// Get the name of an RTNetlink message type.
31fn msg_type_name(msg_type: u16) -> &'static str {
32    match msg_type {
33        msg_type::RTM_NEWLINK => "RTM_NEWLINK",
34        msg_type::RTM_DELLINK => "RTM_DELLINK",
35        msg_type::RTM_GETLINK => "RTM_GETLINK",
36        msg_type::RTM_SETLINK => "RTM_SETLINK",
37        msg_type::RTM_NEWADDR => "RTM_NEWADDR",
38        msg_type::RTM_DELADDR => "RTM_DELADDR",
39        msg_type::RTM_GETADDR => "RTM_GETADDR",
40        msg_type::RTM_NEWROUTE => "RTM_NEWROUTE",
41        msg_type::RTM_DELROUTE => "RTM_DELROUTE",
42        msg_type::RTM_GETROUTE => "RTM_GETROUTE",
43        msg_type::RTM_NEWNEIGH => "RTM_NEWNEIGH",
44        msg_type::RTM_DELNEIGH => "RTM_DELNEIGH",
45        msg_type::RTM_GETNEIGH => "RTM_GETNEIGH",
46        _ => "UNKNOWN",
47    }
48}
49
50/// RTNetlink protocol parser.
51///
52/// Parses routing netlink messages including link, address, and route operations.
53#[derive(Debug, Clone, Copy)]
54pub struct RtnetlinkProtocol;
55
56impl Protocol for RtnetlinkProtocol {
57    fn name(&self) -> &'static str {
58        "rtnetlink"
59    }
60
61    fn display_name(&self) -> &'static str {
62        "RTNetlink"
63    }
64
65    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
66        // Only parse when parent is netlink and family is ROUTE (0)
67        if context.parent_protocol == Some("netlink") {
68            if let Some(family) = context.hint("netlink_family") {
69                if family == netlink_family::ROUTE as u64 {
70                    return Some(100);
71                }
72            }
73        }
74        None
75    }
76
77    fn parse<'a>(&self, data: &'a [u8], context: &ParseContext) -> ParseResult<'a> {
78        let mut fields = SmallVec::new();
79
80        // Get the message type from parent netlink hints
81        let nl_msg_type = context
82            .hint("netlink_msg_type")
83            .map(|t| t as u16)
84            .unwrap_or(0);
85
86        fields.push(("msg_type", FieldValue::UInt16(nl_msg_type)));
87        fields.push(("msg_type_name", FieldValue::Str(msg_type_name(nl_msg_type))));
88
89        // Parse based on message type range
90        match nl_msg_type {
91            // Link messages (RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK, RTM_SETLINK)
92            16..=19 => {
93                parse_link_header(data, &mut fields);
94            }
95            // Address messages (RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR)
96            20..=22 => {
97                parse_addr_header(data, &mut fields);
98            }
99            // Route messages (RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE)
100            24..=26 => {
101                parse_route_header(data, &mut fields);
102            }
103            _ => {
104                // Unknown message type - just record the type
105            }
106        }
107
108        // RTNetlink is a terminal protocol (no child protocols)
109        ParseResult::success(fields, &[], SmallVec::new())
110    }
111
112    fn schema_fields(&self) -> Vec<FieldDescriptor> {
113        vec![
114            // Common fields
115            FieldDescriptor::new("rtnetlink.msg_type", DataKind::UInt16).set_nullable(true),
116            FieldDescriptor::new("rtnetlink.msg_type_name", DataKind::String).set_nullable(true),
117            // Link message fields
118            FieldDescriptor::new("rtnetlink.link_index", DataKind::UInt32).set_nullable(true),
119            FieldDescriptor::new("rtnetlink.link_type", DataKind::UInt16).set_nullable(true),
120            FieldDescriptor::new("rtnetlink.link_flags", DataKind::UInt32).set_nullable(true),
121            FieldDescriptor::new("rtnetlink.if_name", DataKind::String).set_nullable(true),
122            FieldDescriptor::new("rtnetlink.mtu", DataKind::UInt32).set_nullable(true),
123            FieldDescriptor::mac_field("rtnetlink.hw_addr").set_nullable(true),
124            // Address message fields
125            FieldDescriptor::new("rtnetlink.addr_family", DataKind::UInt8).set_nullable(true),
126            FieldDescriptor::new("rtnetlink.addr_family_name", DataKind::String).set_nullable(true),
127            FieldDescriptor::new("rtnetlink.prefix_len", DataKind::UInt8).set_nullable(true),
128            FieldDescriptor::new("rtnetlink.addr_index", DataKind::UInt32).set_nullable(true),
129            FieldDescriptor::new("rtnetlink.address", DataKind::String).set_nullable(true),
130            FieldDescriptor::new("rtnetlink.local_addr", DataKind::String).set_nullable(true),
131            // Route message fields
132            FieldDescriptor::new("rtnetlink.route_family", DataKind::UInt8).set_nullable(true),
133            FieldDescriptor::new("rtnetlink.dst_prefix_len", DataKind::UInt8).set_nullable(true),
134            FieldDescriptor::new("rtnetlink.src_prefix_len", DataKind::UInt8).set_nullable(true),
135            FieldDescriptor::new("rtnetlink.route_table", DataKind::UInt8).set_nullable(true),
136            FieldDescriptor::new("rtnetlink.route_protocol", DataKind::UInt8).set_nullable(true),
137            FieldDescriptor::new("rtnetlink.route_scope", DataKind::UInt8).set_nullable(true),
138            FieldDescriptor::new("rtnetlink.route_type", DataKind::UInt8).set_nullable(true),
139            FieldDescriptor::new("rtnetlink.destination", DataKind::String).set_nullable(true),
140            FieldDescriptor::new("rtnetlink.gateway", DataKind::String).set_nullable(true),
141            FieldDescriptor::new("rtnetlink.oif_index", DataKind::UInt32).set_nullable(true),
142        ]
143    }
144
145    fn dependencies(&self) -> &'static [&'static str] {
146        &["netlink"]
147    }
148}
149
150/// Link message header offsets (struct ifinfomsg).
151#[allow(dead_code)]
152mod link_header {
153    pub const FAMILY_OFFSET: usize = 0;
154    pub const TYPE_OFFSET: usize = 2;
155    pub const INDEX_OFFSET: usize = 4;
156    pub const FLAGS_OFFSET: usize = 8;
157    pub const CHANGE_OFFSET: usize = 12;
158    pub const MIN_LEN: usize = 16;
159}
160
161/// Address message header offsets (struct ifaddrmsg).
162#[allow(dead_code)]
163mod addr_header {
164    pub const FAMILY_OFFSET: usize = 0;
165    pub const PREFIXLEN_OFFSET: usize = 1;
166    pub const FLAGS_OFFSET: usize = 2;
167    pub const SCOPE_OFFSET: usize = 3;
168    pub const INDEX_OFFSET: usize = 4;
169    pub const MIN_LEN: usize = 8;
170}
171
172/// Route message header offsets (struct rtmsg).
173#[allow(dead_code)]
174mod route_header {
175    pub const FAMILY_OFFSET: usize = 0;
176    pub const DST_LEN_OFFSET: usize = 1;
177    pub const SRC_LEN_OFFSET: usize = 2;
178    pub const TOS_OFFSET: usize = 3;
179    pub const TABLE_OFFSET: usize = 4;
180    pub const PROTOCOL_OFFSET: usize = 5;
181    pub const SCOPE_OFFSET: usize = 6;
182    pub const TYPE_OFFSET: usize = 7;
183    pub const MIN_LEN: usize = 12;
184}
185
186/// Parse link message header fields.
187fn parse_link_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
188    if data.len() < link_header::MIN_LEN {
189        return;
190    }
191
192    let link_type = u16::from_le_bytes([
193        data[link_header::TYPE_OFFSET],
194        data[link_header::TYPE_OFFSET + 1],
195    ]);
196    let index = u32::from_le_bytes([
197        data[link_header::INDEX_OFFSET],
198        data[link_header::INDEX_OFFSET + 1],
199        data[link_header::INDEX_OFFSET + 2],
200        data[link_header::INDEX_OFFSET + 3],
201    ]);
202    let flags = u32::from_le_bytes([
203        data[link_header::FLAGS_OFFSET],
204        data[link_header::FLAGS_OFFSET + 1],
205        data[link_header::FLAGS_OFFSET + 2],
206        data[link_header::FLAGS_OFFSET + 3],
207    ]);
208
209    fields.push(("link_index", FieldValue::UInt32(index)));
210    fields.push(("link_type", FieldValue::UInt16(link_type)));
211    fields.push(("link_flags", FieldValue::UInt32(flags)));
212
213    // Parse attributes (TLV format after header)
214    parse_link_attributes(&data[link_header::MIN_LEN..], fields);
215}
216
217/// Parse link attributes in TLV format.
218fn parse_link_attributes(mut data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
219    // Netlink TLV: 2 bytes len, 2 bytes type, then value (padded to 4 bytes)
220    while data.len() >= 4 {
221        let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
222        let attr_type = u16::from_le_bytes([data[2], data[3]]);
223
224        if attr_len < 4 || attr_len > data.len() {
225            break;
226        }
227
228        let value = &data[4..attr_len];
229
230        match attr_type {
231            // IFLA_IFNAME = 3
232            3 => {
233                if let Ok(name) = std::str::from_utf8(value.strip_suffix(&[0]).unwrap_or(value)) {
234                    fields.push(("if_name", FieldValue::OwnedString(CompactString::new(name))));
235                }
236            }
237            // IFLA_MTU = 4
238            4 if value.len() >= 4 => {
239                let mtu = u32::from_le_bytes([value[0], value[1], value[2], value[3]]);
240                fields.push(("mtu", FieldValue::UInt32(mtu)));
241            }
242            // IFLA_ADDRESS = 1 (hardware address)
243            1 if value.len() == 6 => {
244                let mut mac = [0u8; 6];
245                mac.copy_from_slice(value);
246                fields.push(("hw_addr", FieldValue::MacAddr(mac)));
247            }
248            _ => {}
249        }
250
251        // Move to next attribute (4-byte aligned)
252        let padded_len = (attr_len + 3) & !3;
253        if padded_len > data.len() {
254            break;
255        }
256        data = &data[padded_len..];
257    }
258}
259
260/// Parse address message header fields.
261fn parse_addr_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
262    if data.len() < addr_header::MIN_LEN {
263        return;
264    }
265
266    let family = data[addr_header::FAMILY_OFFSET];
267    let prefix_len = data[addr_header::PREFIXLEN_OFFSET];
268    let index = u32::from_le_bytes([
269        data[addr_header::INDEX_OFFSET],
270        data[addr_header::INDEX_OFFSET + 1],
271        data[addr_header::INDEX_OFFSET + 2],
272        data[addr_header::INDEX_OFFSET + 3],
273    ]);
274
275    fields.push(("addr_family", FieldValue::UInt8(family)));
276    fields.push((
277        "addr_family_name",
278        FieldValue::Str(match family {
279            2 => "IPv4",  // AF_INET
280            10 => "IPv6", // AF_INET6
281            _ => "Unknown",
282        }),
283    ));
284    fields.push(("prefix_len", FieldValue::UInt8(prefix_len)));
285    fields.push(("addr_index", FieldValue::UInt32(index)));
286
287    // Parse attributes
288    parse_addr_attributes(&data[addr_header::MIN_LEN..], family, fields);
289}
290
291/// Parse address attributes in TLV format.
292fn parse_addr_attributes(
293    mut data: &[u8],
294    family: u8,
295    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
296) {
297    while data.len() >= 4 {
298        let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
299        let attr_type = u16::from_le_bytes([data[2], data[3]]);
300
301        if attr_len < 4 || attr_len > data.len() {
302            break;
303        }
304
305        let value = &data[4..attr_len];
306
307        match attr_type {
308            // IFA_ADDRESS = 1
309            1 => {
310                if let Some(addr_str) = format_ip_address(value, family) {
311                    fields.push((
312                        "address",
313                        FieldValue::OwnedString(CompactString::new(&addr_str)),
314                    ));
315                }
316            }
317            // IFA_LOCAL = 2
318            2 => {
319                if let Some(addr_str) = format_ip_address(value, family) {
320                    fields.push((
321                        "local_addr",
322                        FieldValue::OwnedString(CompactString::new(&addr_str)),
323                    ));
324                }
325            }
326            _ => {}
327        }
328
329        let padded_len = (attr_len + 3) & !3;
330        if padded_len > data.len() {
331            break;
332        }
333        data = &data[padded_len..];
334    }
335}
336
337/// Format an IP address from bytes.
338fn format_ip_address(value: &[u8], family: u8) -> Option<String> {
339    match family {
340        2 if value.len() == 4 => {
341            // AF_INET (IPv4)
342            Some(format!(
343                "{}.{}.{}.{}",
344                value[0], value[1], value[2], value[3]
345            ))
346        }
347        10 if value.len() == 16 => {
348            // AF_INET6 (IPv6)
349            use std::net::Ipv6Addr;
350            let addr = Ipv6Addr::new(
351                u16::from_be_bytes([value[0], value[1]]),
352                u16::from_be_bytes([value[2], value[3]]),
353                u16::from_be_bytes([value[4], value[5]]),
354                u16::from_be_bytes([value[6], value[7]]),
355                u16::from_be_bytes([value[8], value[9]]),
356                u16::from_be_bytes([value[10], value[11]]),
357                u16::from_be_bytes([value[12], value[13]]),
358                u16::from_be_bytes([value[14], value[15]]),
359            );
360            Some(addr.to_string())
361        }
362        _ => None,
363    }
364}
365
366/// Parse route message header fields.
367fn parse_route_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
368    if data.len() < route_header::MIN_LEN {
369        return;
370    }
371
372    let family = data[route_header::FAMILY_OFFSET];
373    let dst_len = data[route_header::DST_LEN_OFFSET];
374    let src_len = data[route_header::SRC_LEN_OFFSET];
375    let table = data[route_header::TABLE_OFFSET];
376    let protocol = data[route_header::PROTOCOL_OFFSET];
377    let scope = data[route_header::SCOPE_OFFSET];
378    let route_type = data[route_header::TYPE_OFFSET];
379
380    fields.push(("route_family", FieldValue::UInt8(family)));
381    fields.push(("dst_prefix_len", FieldValue::UInt8(dst_len)));
382    fields.push(("src_prefix_len", FieldValue::UInt8(src_len)));
383    fields.push(("route_table", FieldValue::UInt8(table)));
384    fields.push(("route_protocol", FieldValue::UInt8(protocol)));
385    fields.push(("route_scope", FieldValue::UInt8(scope)));
386    fields.push(("route_type", FieldValue::UInt8(route_type)));
387
388    // Parse attributes
389    parse_route_attributes(&data[route_header::MIN_LEN..], family, fields);
390}
391
392/// Parse route attributes in TLV format.
393fn parse_route_attributes(
394    mut data: &[u8],
395    family: u8,
396    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
397) {
398    while data.len() >= 4 {
399        let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
400        let attr_type = u16::from_le_bytes([data[2], data[3]]);
401
402        if attr_len < 4 || attr_len > data.len() {
403            break;
404        }
405
406        let value = &data[4..attr_len];
407
408        match attr_type {
409            // RTA_DST = 1
410            1 => {
411                if let Some(addr_str) = format_ip_address(value, family) {
412                    fields.push((
413                        "destination",
414                        FieldValue::OwnedString(CompactString::new(&addr_str)),
415                    ));
416                }
417            }
418            // RTA_GATEWAY = 5
419            5 => {
420                if let Some(addr_str) = format_ip_address(value, family) {
421                    fields.push((
422                        "gateway",
423                        FieldValue::OwnedString(CompactString::new(&addr_str)),
424                    ));
425                }
426            }
427            // RTA_OIF = 4
428            4 if value.len() >= 4 => {
429                let oif = u32::from_le_bytes([value[0], value[1], value[2], value[3]]);
430                fields.push(("oif_index", FieldValue::UInt32(oif)));
431            }
432            _ => {}
433        }
434
435        let padded_len = (attr_len + 3) & !3;
436        if padded_len > data.len() {
437            break;
438        }
439        data = &data[padded_len..];
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::protocol::netlink::LINKTYPE_NETLINK;
447
448    /// Helper to create rtnetlink context.
449    fn make_rtnetlink_context(msg_type: u16) -> ParseContext {
450        let mut ctx = ParseContext::new(LINKTYPE_NETLINK);
451        ctx.parent_protocol = Some("netlink");
452        ctx.insert_hint("netlink_family", netlink_family::ROUTE as u64);
453        ctx.insert_hint("netlink_msg_type", msg_type as u64);
454        ctx
455    }
456
457    // ==========================================================================
458    // Test 1: can_parse with netlink_family=0 hint
459    // ==========================================================================
460    #[test]
461    fn test_can_parse_rtnetlink() {
462        let parser = RtnetlinkProtocol;
463        let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
464
465        assert!(parser.can_parse(&ctx).is_some());
466        assert_eq!(parser.can_parse(&ctx), Some(100));
467    }
468
469    // ==========================================================================
470    // Test 2: cannot parse without proper hints
471    // ==========================================================================
472    #[test]
473    fn test_cannot_parse_without_family_hint() {
474        let parser = RtnetlinkProtocol;
475        let ctx = ParseContext::new(LINKTYPE_NETLINK);
476
477        assert!(parser.can_parse(&ctx).is_none());
478    }
479
480    // ==========================================================================
481    // Test 3: cannot parse with wrong family
482    // ==========================================================================
483    #[test]
484    fn test_cannot_parse_wrong_family() {
485        let parser = RtnetlinkProtocol;
486        let mut ctx = ParseContext::new(LINKTYPE_NETLINK);
487        ctx.parent_protocol = Some("netlink");
488        ctx.insert_hint("netlink_family", netlink_family::NETFILTER as u64);
489
490        assert!(parser.can_parse(&ctx).is_none());
491    }
492
493    // ==========================================================================
494    // Test 4: Parse RTM_NEWLINK message type extraction
495    // ==========================================================================
496    #[test]
497    fn test_parse_rtm_newlink_type() {
498        let parser = RtnetlinkProtocol;
499        let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
500
501        // Empty data - just tests message type extraction from hints
502        let result = parser.parse(&[], &ctx);
503
504        assert!(result.is_ok());
505        assert_eq!(
506            result.get("msg_type"),
507            Some(&FieldValue::UInt16(msg_type::RTM_NEWLINK))
508        );
509        assert_eq!(
510            result.get("msg_type_name"),
511            Some(&FieldValue::Str("RTM_NEWLINK"))
512        );
513    }
514
515    // ==========================================================================
516    // Test 5: Parse RTM_NEWADDR message type extraction
517    // ==========================================================================
518    #[test]
519    fn test_parse_rtm_newaddr_type() {
520        let parser = RtnetlinkProtocol;
521        let ctx = make_rtnetlink_context(msg_type::RTM_NEWADDR);
522
523        let result = parser.parse(&[], &ctx);
524
525        assert!(result.is_ok());
526        assert_eq!(
527            result.get("msg_type_name"),
528            Some(&FieldValue::Str("RTM_NEWADDR"))
529        );
530    }
531
532    // ==========================================================================
533    // Test 6: Parse RTM_NEWROUTE message type extraction
534    // ==========================================================================
535    #[test]
536    fn test_parse_rtm_newroute_type() {
537        let parser = RtnetlinkProtocol;
538        let ctx = make_rtnetlink_context(msg_type::RTM_NEWROUTE);
539
540        let result = parser.parse(&[], &ctx);
541
542        assert!(result.is_ok());
543        assert_eq!(
544            result.get("msg_type_name"),
545            Some(&FieldValue::Str("RTM_NEWROUTE"))
546        );
547    }
548
549    // ==========================================================================
550    // Test 7: Schema fields complete
551    // ==========================================================================
552    #[test]
553    fn test_rtnetlink_schema_fields() {
554        let parser = RtnetlinkProtocol;
555        let fields = parser.schema_fields();
556
557        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
558
559        // Common fields
560        assert!(field_names.contains(&"rtnetlink.msg_type"));
561        assert!(field_names.contains(&"rtnetlink.msg_type_name"));
562
563        // Link fields
564        assert!(field_names.contains(&"rtnetlink.link_index"));
565        assert!(field_names.contains(&"rtnetlink.link_type"));
566        assert!(field_names.contains(&"rtnetlink.link_flags"));
567        assert!(field_names.contains(&"rtnetlink.if_name"));
568        assert!(field_names.contains(&"rtnetlink.mtu"));
569        assert!(field_names.contains(&"rtnetlink.hw_addr"));
570
571        // Address fields
572        assert!(field_names.contains(&"rtnetlink.addr_family"));
573        assert!(field_names.contains(&"rtnetlink.prefix_len"));
574        assert!(field_names.contains(&"rtnetlink.address"));
575
576        // Route fields
577        assert!(field_names.contains(&"rtnetlink.route_family"));
578        assert!(field_names.contains(&"rtnetlink.dst_prefix_len"));
579        assert!(field_names.contains(&"rtnetlink.destination"));
580        assert!(field_names.contains(&"rtnetlink.gateway"));
581        assert!(field_names.contains(&"rtnetlink.oif_index"));
582    }
583
584    // ==========================================================================
585    // Test 8: Dependencies declaration
586    // ==========================================================================
587    #[test]
588    fn test_rtnetlink_dependencies() {
589        let parser = RtnetlinkProtocol;
590        let deps = parser.dependencies();
591
592        assert!(deps.contains(&"netlink"));
593    }
594
595    // ==========================================================================
596    // Test 9: Unknown message type handling
597    // ==========================================================================
598    #[test]
599    fn test_unknown_message_type() {
600        let parser = RtnetlinkProtocol;
601        let ctx = make_rtnetlink_context(999);
602
603        let result = parser.parse(&[], &ctx);
604
605        assert!(result.is_ok());
606        assert_eq!(
607            result.get("msg_type_name"),
608            Some(&FieldValue::Str("UNKNOWN"))
609        );
610    }
611
612    // ==========================================================================
613    // Test 10: RTNetlink is terminal protocol (no remaining data)
614    // ==========================================================================
615    #[test]
616    fn test_rtnetlink_is_terminal() {
617        let parser = RtnetlinkProtocol;
618        let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
619
620        let result = parser.parse(&[0u8; 32], &ctx);
621
622        assert!(result.is_ok());
623        assert!(result.remaining.is_empty());
624        assert!(result.child_hints.is_empty());
625    }
626}