Skip to main content

packet_dissector_bgp/
lib.rs

1//! BGP-4 (Border Gateway Protocol version 4) dissector.
2//!
3//! ## References
4//! - RFC 4271 (BGP-4): <https://www.rfc-editor.org/rfc/rfc4271>
5//! - RFC 1997 (Communities): <https://www.rfc-editor.org/rfc/rfc1997>
6//! - RFC 2918 (Route Refresh): <https://www.rfc-editor.org/rfc/rfc2918>
7//! - RFC 4360 (Extended Communities): <https://www.rfc-editor.org/rfc/rfc4360>
8//! - RFC 4456 (Route Reflection): <https://www.rfc-editor.org/rfc/rfc4456>
9//! - RFC 4486 (Cease NOTIFICATION subcodes): <https://www.rfc-editor.org/rfc/rfc4486>
10//! - RFC 4760 (Multiprotocol Extensions): <https://www.rfc-editor.org/rfc/rfc4760>
11//! - RFC 6793 (4-octet AS Numbers): <https://www.rfc-editor.org/rfc/rfc6793>
12//! - RFC 7313 (Enhanced Route Refresh): <https://www.rfc-editor.org/rfc/rfc7313>
13//! - RFC 8092 (Large Communities): <https://www.rfc-editor.org/rfc/rfc8092>
14//! - RFC 8203 (Hard Reset Cease subcode): <https://www.rfc-editor.org/rfc/rfc8203>
15//! - RFC 8654 (Extended Message): <https://www.rfc-editor.org/rfc/rfc8654>
16//! - RFC 8669 (BGP Prefix-SID): <https://www.rfc-editor.org/rfc/rfc8669>
17//! - RFC 9012 (Tunnel Encapsulation / Color): <https://www.rfc-editor.org/rfc/rfc9012>
18//! - RFC 9072 (Extended Optional Parameters Length): <https://www.rfc-editor.org/rfc/rfc9072>
19//! - RFC 9252 (SRv6 BGP Services): <https://www.rfc-editor.org/rfc/rfc9252>
20//! - draft-ietf-bess-mup-safi-00 (MUP SAFI): <https://datatracker.ietf.org/doc/draft-ietf-bess-mup-safi/>
21//!
22//! # RFC 4271 (BGP-4) Coverage
23//!
24//! | RFC Section | Description | Test |
25//! |-------------|-------------|------|
26//! | 4.1 | Message Header | `parse_bgp_keepalive` |
27//! | 4.1 | Marker validation | `parse_bgp_invalid_marker` |
28//! | 4.1 | Truncated header | `parse_bgp_truncated_header` |
29//! | 4.2 | OPEN Message | `parse_bgp_open_basic` |
30//! | 4.2 | OPEN with Capabilities | `parse_bgp_open_with_capabilities` |
31//! | 4.2 | Truncated OPEN | `parse_bgp_truncated_open` |
32//! | 4.3 | UPDATE Withdrawn Routes | `parse_bgp_update_withdraw` |
33//! | 4.3 | UPDATE Path Attributes + NLRI | `parse_bgp_update_announce` |
34//! | 4.3 | NLRI prefix CIDR formatting | `format_nlri_ipv4_prefix_cidr` |
35//! | 4.5 | NOTIFICATION | `parse_bgp_notification` |
36//! | 5.1.1 | ORIGIN | `parse_bgp_update_origin` |
37//! | 5.1.2 | AS_PATH | `parse_bgp_update_as_path` |
38//! | 5.1.3 | NEXT_HOP | `parse_bgp_update_next_hop` |
39//! | 5.1.4 | MULTI_EXIT_DISC | `parse_bgp_update_multi_exit_disc` |
40//! | 5.1.5 | LOCAL_PREF | `parse_bgp_update_local_pref` |
41//! | 5.1.6 | ATOMIC_AGGREGATE | `parse_bgp_update_atomic_aggregate` |
42//! | 5.1.7 | AGGREGATOR (2-byte AS) | `parse_bgp_update_aggregator_2byte_as` |
43//! | 4.1 | Multiple messages per segment | `parse_bgp_multiple_messages` |
44//! | 4.2+4.4 | OPEN followed by KEEPALIVE | `parse_bgp_open_followed_by_keepalive` |
45//! | 4.3 | Unknown attribute (raw bytes) | `parse_bgp_update_unknown_attribute` |
46//!
47//! # RFC 6793 (4-octet AS Numbers) Coverage
48//!
49//! | RFC Section | Description | Test |
50//! |-------------|-------------|------|
51//! | 3 | AGGREGATOR (4-byte AS) | `parse_bgp_update_aggregator_4byte_as` |
52//! | 3 | AS4_PATH | `parse_bgp_update_as4_path` |
53//! | 3 | AS4_AGGREGATOR | `parse_bgp_update_as4_aggregator` |
54//!
55//! # RFC 1997 Coverage
56//!
57//! | RFC Section | Description | Test |
58//! |-------------|-------------|------|
59//! | 3 | COMMUNITIES | `parse_bgp_update_communities` |
60//!
61//! # RFC 2918 Coverage
62//!
63//! | RFC Section | Description | Test |
64//! |-------------|-------------|------|
65//! | 3 | ROUTE-REFRESH | `parse_bgp_route_refresh` |
66//!
67//! # RFC 7313 (Enhanced Route Refresh) Coverage
68//!
69//! | RFC Section | Description | Test |
70//! |-------------|-------------|------|
71//! | 4 | Message Subtype (BoRR) | `parse_bgp_route_refresh_subtype_borr` |
72//!
73//! # RFC 9072 (Extended Optional Parameters Length) Coverage
74//!
75//! | RFC Section | Description | Test |
76//! |-------------|-------------|------|
77//! | 2 | Extended OPEN encoding + 2-octet param length | `parse_bgp_open_extended_optional_parameters` |
78//!
79//! # RFC 4486 / RFC 8203 (Cease NOTIFICATION subcodes) Coverage
80//!
81//! | RFC Section | Description | Test |
82//! |-------------|-------------|------|
83//! | RFC 4486 §4 | Cease subcodes 1–8 + RFC 8203 §4 subcode 9 | `parse_bgp_notification_cease_subcode_name` |
84//!
85//! # RFC 4360 / RFC 9012 Coverage
86//!
87//! | RFC Section | Description | Test |
88//! |-------------|-------------|------|
89//! | 2 | Extended Communities + Color | `parse_bgp_update_extended_communities` |
90//! | 2 | IPv4 Address Specific Route Target | `parse_bgp_update_extended_communities_ipv4_route_target` |
91//! | 2 | Route Origin (Two-Octet AS) | `parse_bgp_update_extended_communities_route_origin` |
92//! | 2 | IPv4 Address Specific Route Origin | `parse_bgp_update_extended_communities_ipv4_route_origin` |
93//! | 2 | EVPN Extended Community | `parse_bgp_update_extended_communities_evpn` |
94//! | 2 | Unknown Extended Community | `parse_bgp_update_extended_communities_unknown` |
95//!
96//! # RFC 4456 (Route Reflection) Coverage
97//!
98//! | RFC Section | Description | Test |
99//! |-------------|-------------|------|
100//! | 8 | ORIGINATOR_ID | `parse_bgp_update_originator_id` |
101//! | 8 | CLUSTER_LIST | `parse_bgp_update_cluster_list` |
102//!
103//! # RFC 4760 Coverage
104//!
105//! | RFC Section | Description | Test |
106//! |-------------|-------------|------|
107//! | 3 | MP_REACH_NLRI (IPv6) | `parse_bgp_update_mp_reach_ipv6` |
108//! | 3 | MP_REACH_NLRI (IPv6 link-local NH) | `parse_bgp_update_mp_reach_ipv6_link_local` |
109//! | 4 | MP_UNREACH_NLRI (IPv6) | `parse_bgp_update_mp_unreach_ipv6` |
110//! | 4 | MP_UNREACH_NLRI (IPv4) | `parse_bgp_update_mp_unreach_ipv4` |
111//! | 3 | IPv6 NLRI prefix CIDR formatting | `format_nlri_ipv6_prefix_cidr` |
112//!
113//! # draft-ietf-bess-mup-safi-00 Coverage
114//!
115//! | Section | Description | Test |
116//! |---------|-------------|------|
117//! | 3 | MUP NLRI (Interwork Segment Discovery) | `parse_bgp_update_mup_interwork_segment_discovery` |
118//! | 3.3 | Type 1 ST (3GPP 5G) | `parse_bgp_update_mup_type1_st` |
119//!
120//! # RFC 8092 Coverage
121//!
122//! | RFC Section | Description | Test |
123//! |-------------|-------------|------|
124//! | 2 | LARGE_COMMUNITY | `parse_bgp_update_large_community` |
125//!
126//! # RFC 8669 (BGP Prefix-SID) Coverage
127//!
128//! | RFC Section | Description | Test |
129//! |-------------|-------------|------|
130//! | 3.1 | Label-Index TLV | `parse_bgp_prefix_sid_label_index` |
131//! | 3.2 | Originator SRGB TLV | `parse_bgp_prefix_sid_originator_srgb` |
132//! | 3 | Multiple TLVs | `parse_bgp_prefix_sid_multiple_tlvs` |
133//! | 3 | Unknown TLV | `parse_bgp_prefix_sid_unknown_tlv` |
134//! | 6 | Truncated TLV handling | `parse_bgp_prefix_sid_truncated` |
135//!
136//! # RFC 9252 (SRv6 BGP Services) Coverage
137//!
138//! | RFC Section | Description | Test |
139//! |-------------|-------------|------|
140//! | 2 | SRv6 L3 Service TLV | `parse_bgp_prefix_sid_srv6_l3_service` |
141//! | 2 | SRv6 L2 Service TLV | `parse_bgp_prefix_sid_srv6_l2_service` |
142//! | 3.1 | SRv6 SID Information Sub-TLV | `parse_bgp_prefix_sid_srv6_l3_service` |
143//! | 3.2.1 | SRv6 SID Structure Sub-Sub-TLV | `parse_bgp_prefix_sid_srv6_sid_structure` |
144
145#![deny(missing_docs)]
146
147use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
148use packet_dissector_core::error::PacketError;
149use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue, FormatContext};
150use packet_dissector_core::packet::DissectBuffer;
151use packet_dissector_core::util::{
152    read_be_u16, read_be_u24, read_be_u32, read_ipv4_addr, read_ipv6_addr,
153};
154
155/// BGP message header size in bytes (RFC 4271, Section 4.1).
156/// 16-byte marker + 2-byte length + 1-byte type.
157const HEADER_SIZE: usize = 19;
158
159/// BGP marker: 16 bytes of 0xFF (RFC 4271, Section 4.1).
160const MARKER: [u8; 16] = [0xFF; 16];
161
162/// Minimum OPEN message size: 19-byte header + 10-byte body (RFC 4271, Section 4.2).
163const MIN_OPEN_SIZE: usize = 29;
164
165/// Minimum NOTIFICATION message size: 19-byte header + 2-byte body (RFC 4271, Section 4.5).
166const MIN_NOTIFICATION_SIZE: usize = 21;
167
168/// Minimum UPDATE message size: 19-byte header + 4-byte body (RFC 4271, Section 4.3).
169const MIN_UPDATE_SIZE: usize = 23;
170
171/// ROUTE-REFRESH message size: 19-byte header + 4-byte body (RFC 2918).
172const ROUTE_REFRESH_SIZE: usize = 23;
173
174/// BGP message type: OPEN (RFC 4271, Section 4.1).
175const MSG_OPEN: u8 = 1;
176/// BGP message type: UPDATE (RFC 4271, Section 4.1).
177const MSG_UPDATE: u8 = 2;
178/// BGP message type: NOTIFICATION (RFC 4271, Section 4.1).
179const MSG_NOTIFICATION: u8 = 3;
180/// BGP message type: KEEPALIVE (RFC 4271, Section 4.1).
181const MSG_KEEPALIVE: u8 = 4;
182/// BGP message type: ROUTE-REFRESH (RFC 2918).
183const MSG_ROUTE_REFRESH: u8 = 5;
184
185/// Returns a human-readable name for BGP message types.
186///
187/// RFC 4271, Section 4.1 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.1>
188/// RFC 2918 — <https://www.rfc-editor.org/rfc/rfc2918>
189fn msg_type_name(v: u8) -> Option<&'static str> {
190    match v {
191        MSG_OPEN => Some("OPEN"),
192        MSG_UPDATE => Some("UPDATE"),
193        MSG_NOTIFICATION => Some("NOTIFICATION"),
194        MSG_KEEPALIVE => Some("KEEPALIVE"),
195        MSG_ROUTE_REFRESH => Some("ROUTE-REFRESH"),
196        _ => None,
197    }
198}
199
200/// Returns a human-readable name for AFI values.
201///
202/// IANA Address Family Numbers — <https://www.iana.org/assignments/address-family-numbers>
203fn afi_name(v: u16) -> Option<&'static str> {
204    match v {
205        1 => Some("IPv4"),
206        2 => Some("IPv6"),
207        25 => Some("L2VPN"),
208        _ => None,
209    }
210}
211
212/// Returns a human-readable name for SAFI values.
213///
214/// IANA SAFI Namespace — <https://www.iana.org/assignments/safi-namespace>
215fn safi_name(v: u8) -> Option<&'static str> {
216    match v {
217        1 => Some("Unicast"),
218        2 => Some("Multicast"),
219        4 => Some("MPLS Labels"),
220        65 => Some("VPLS"),
221        70 => Some("EVPN"),
222        71 => Some("BGP-LS"),
223        73 => Some("SR Policy"),
224        85 => Some("BGP-MUP"),
225        128 => Some("MPLS-labeled VPN"),
226        129 => Some("Multicast VPN"),
227        132 => Some("Route Target Constraints"),
228        133 => Some("FlowSpec"),
229        134 => Some("L3VPN FlowSpec"),
230        _ => None,
231    }
232}
233
234/// Parses OPEN message optional parameters, extracting capabilities.
235///
236/// `param_len_size` selects the parameter Length encoding width: 1 octet for the
237/// classic encoding (RFC 4271) or 2 octets for the extended encoding
238/// (RFC 9072, Section 3 — <https://www.rfc-editor.org/rfc/rfc9072#section-3>).
239///
240/// RFC 4271, Section 4.2 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.2>
241/// RFC 5492 — <https://www.rfc-editor.org/rfc/rfc5492>
242fn parse_optional_parameters<'pkt>(
243    buf: &mut DissectBuffer<'pkt>,
244    params_data: &'pkt [u8],
245    base_offset: usize,
246    param_len_size: usize,
247) {
248    let mut pos = 0;
249    let hdr_size = 1 + param_len_size;
250
251    while pos + hdr_size <= params_data.len() {
252        let param_type = params_data[pos];
253        let param_len = if param_len_size == 1 {
254            params_data[pos + 1] as usize
255        } else {
256            // RFC 9072 Figure 2 — 2-octet parameter length field.
257            ((params_data[pos + 1] as usize) << 8) | (params_data[pos + 2] as usize)
258        };
259        let param_start = base_offset + pos;
260
261        if pos + hdr_size + param_len > params_data.len() {
262            break;
263        }
264
265        // RFC 5492: Capability Optional Parameter (type=2)
266        if param_type == 2 {
267            let cap_data = &params_data[pos + hdr_size..pos + hdr_size + param_len];
268            let mut cap_pos = 0;
269            while cap_pos + 2 <= cap_data.len() {
270                let cap_code = cap_data[cap_pos];
271                let cap_len = cap_data[cap_pos + 1] as usize;
272                let cap_abs = base_offset + pos + hdr_size + cap_pos;
273
274                if cap_pos + 2 + cap_len > cap_data.len() {
275                    break;
276                }
277
278                let obj_idx = buf.begin_container(
279                    &OPT_PARAM_OBJECT_DESCRIPTOR,
280                    FieldValue::Object(0..0),
281                    cap_abs..cap_abs + 2 + cap_len,
282                );
283                buf.push_field(
284                    &OPT_PARAM_CHILDREN[FD_OPT_CODE],
285                    FieldValue::U8(cap_code),
286                    cap_abs..cap_abs + 1,
287                );
288                buf.push_field(
289                    &OPT_PARAM_CHILDREN[FD_OPT_LENGTH],
290                    FieldValue::U8(cap_data[cap_pos + 1]),
291                    cap_abs + 1..cap_abs + 2,
292                );
293
294                if cap_len > 0 {
295                    let cap_value = &cap_data[cap_pos + 2..cap_pos + 2 + cap_len];
296                    buf.push_field(
297                        &OPT_PARAM_CHILDREN[FD_OPT_VALUE],
298                        FieldValue::Bytes(cap_value),
299                        cap_abs + 2..cap_abs + 2 + cap_len,
300                    );
301                }
302
303                buf.end_container(obj_idx);
304                cap_pos += 2 + cap_len;
305            }
306        } else {
307            // Non-capability parameter: store raw
308            let val = &params_data[pos + hdr_size..pos + hdr_size + param_len];
309            let obj_idx = buf.begin_container(
310                &OPT_PARAM_OBJECT_DESCRIPTOR,
311                FieldValue::Object(0..0),
312                param_start..param_start + hdr_size + param_len,
313            );
314            buf.push_field(
315                &NON_CAP_PARAM_CHILDREN[FD_NCP_PARAM_TYPE],
316                FieldValue::U8(param_type),
317                param_start..param_start + 1,
318            );
319            buf.push_field(
320                &NON_CAP_PARAM_CHILDREN[FD_NCP_VALUE],
321                FieldValue::Bytes(val),
322                param_start + hdr_size..param_start + hdr_size + param_len,
323            );
324            buf.end_container(obj_idx);
325        }
326
327        pos += hdr_size + param_len;
328    }
329}
330
331/// Parses OPEN message body and appends fields.
332///
333/// RFC 4271, Section 4.2 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.2>
334/// RFC 9072 (Extended Optional Parameters Length) —
335/// <https://www.rfc-editor.org/rfc/rfc9072>
336fn parse_open<'pkt>(
337    buf: &mut DissectBuffer<'pkt>,
338    data: &'pkt [u8],
339    offset: usize,
340) -> Result<(), PacketError> {
341    if data.len() < MIN_OPEN_SIZE {
342        return Err(PacketError::Truncated {
343            expected: MIN_OPEN_SIZE,
344            actual: data.len(),
345        });
346    }
347
348    let version = data[19];
349    let my_as = read_be_u16(data, 20)?;
350    let hold_time = read_be_u16(data, 22)?;
351    let bgp_id = [data[24], data[25], data[26], data[27]];
352    let opt_params_len_byte = data[28];
353
354    buf.push_field(
355        &FIELD_DESCRIPTORS[FD_VERSION],
356        FieldValue::U8(version),
357        offset + 19..offset + 20,
358    );
359    buf.push_field(
360        &FIELD_DESCRIPTORS[FD_MY_AS],
361        FieldValue::U16(my_as),
362        offset + 20..offset + 22,
363    );
364    buf.push_field(
365        &FIELD_DESCRIPTORS[FD_HOLD_TIME],
366        FieldValue::U16(hold_time),
367        offset + 22..offset + 24,
368    );
369    buf.push_field(
370        &FIELD_DESCRIPTORS[FD_BGP_IDENTIFIER],
371        FieldValue::Ipv4Addr(bgp_id),
372        offset + 24..offset + 28,
373    );
374    buf.push_field(
375        &FIELD_DESCRIPTORS[FD_OPT_PARAMS_LENGTH],
376        FieldValue::U8(opt_params_len_byte),
377        offset + 28..offset + 29,
378    );
379
380    // RFC 9072, Section 2 — <https://www.rfc-editor.org/rfc/rfc9072#section-2>
381    // Extended encoding is signalled by the byte at offset 29 (Non-Ext OP Type)
382    // having value 255. The Extended Opt. Parm. Length is then encoded as a
383    // 2-octet unsigned integer at bytes 30..32 and each parameter uses a
384    // 2-octet length field.
385    let extended = data.len() >= 32 && data[29] == 255;
386
387    let (params_offset, params_len, param_len_size) = if extended {
388        let ext_len = read_be_u16(data, 30)? as usize;
389        buf.push_field(
390            &FIELD_DESCRIPTORS[FD_EXT_OPT_PARAMS_LENGTH],
391            FieldValue::U16(ext_len as u16),
392            offset + 30..offset + 32,
393        );
394        (32usize, ext_len, 2usize)
395    } else {
396        (29usize, opt_params_len_byte as usize, 1usize)
397    };
398
399    if params_len > 0 {
400        let params_end = params_offset + params_len;
401        if data.len() < params_end {
402            return Err(PacketError::Truncated {
403                expected: params_end,
404                actual: data.len(),
405            });
406        }
407
408        let params_data = &data[params_offset..params_end];
409        let array_idx = buf.begin_container(
410            &FIELD_DESCRIPTORS[FD_OPTIONAL_PARAMETERS],
411            FieldValue::Array(0..0),
412            offset + params_offset..offset + params_end,
413        );
414        parse_optional_parameters(buf, params_data, offset + params_offset, param_len_size);
415        buf.end_container(array_idx);
416    }
417
418    Ok(())
419}
420
421/// Returns a human-readable name for BGP error codes.
422///
423/// RFC 4271, Section 4.5 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.5>
424fn error_code_name(v: u8) -> Option<&'static str> {
425    match v {
426        1 => Some("Message Header Error"),
427        2 => Some("OPEN Message Error"),
428        3 => Some("UPDATE Message Error"),
429        4 => Some("Hold Timer Expired"),
430        5 => Some("Finite State Machine Error"),
431        6 => Some("Cease"),
432        7 => Some("ROUTE-REFRESH Message Error"),
433        8 => Some("Send Hold Timer Expired"),
434        _ => None,
435    }
436}
437
438/// Returns a human-readable name for Cease NOTIFICATION subcodes.
439///
440/// RFC 4486, Section 4 — <https://www.rfc-editor.org/rfc/rfc4486#section-4>
441/// RFC 8203, Section 4 — <https://www.rfc-editor.org/rfc/rfc8203#section-4>
442fn cease_subcode_name(v: u8) -> Option<&'static str> {
443    match v {
444        1 => Some("Maximum Number of Prefixes Reached"),
445        2 => Some("Administrative Shutdown"),
446        3 => Some("Peer De-configured"),
447        4 => Some("Administrative Reset"),
448        5 => Some("Connection Rejected"),
449        6 => Some("Other Configuration Change"),
450        7 => Some("Connection Collision Resolution"),
451        8 => Some("Out of Resources"),
452        9 => Some("Hard Reset"),
453        _ => None,
454    }
455}
456
457/// Parses NOTIFICATION message body and appends fields.
458///
459/// RFC 4271, Section 4.5 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.5>
460fn parse_notification<'pkt>(
461    buf: &mut DissectBuffer<'pkt>,
462    data: &'pkt [u8],
463    offset: usize,
464) -> Result<(), PacketError> {
465    if data.len() < MIN_NOTIFICATION_SIZE {
466        return Err(PacketError::Truncated {
467            expected: MIN_NOTIFICATION_SIZE,
468            actual: data.len(),
469        });
470    }
471
472    let error_code = data[19];
473    let error_subcode = data[20];
474
475    buf.push_field(
476        &FIELD_DESCRIPTORS[FD_ERROR_CODE],
477        FieldValue::U8(error_code),
478        offset + 19..offset + 20,
479    );
480    buf.push_field(
481        &FIELD_DESCRIPTORS[FD_ERROR_SUBCODE],
482        FieldValue::U8(error_subcode),
483        offset + 20..offset + 21,
484    );
485
486    if data.len() > MIN_NOTIFICATION_SIZE {
487        let data_bytes = &data[21..];
488        buf.push_field(
489            &FIELD_DESCRIPTORS[FD_DATA],
490            FieldValue::Bytes(data_bytes),
491            offset + 21..offset + data.len(),
492        );
493    }
494
495    Ok(())
496}
497
498/// Returns a human-readable name for ROUTE-REFRESH Message Subtypes.
499///
500/// RFC 7313, Section 4 — <https://www.rfc-editor.org/rfc/rfc7313#section-4>
501fn route_refresh_subtype_name(v: u8) -> Option<&'static str> {
502    match v {
503        0 => Some("Route Refresh"),
504        1 => Some("BoRR"),
505        2 => Some("EoRR"),
506        _ => None,
507    }
508}
509
510/// Parses ROUTE-REFRESH message body and appends fields.
511///
512/// RFC 2918 — <https://www.rfc-editor.org/rfc/rfc2918>
513/// RFC 7313 (Enhanced Route Refresh) — <https://www.rfc-editor.org/rfc/rfc7313>
514fn parse_route_refresh<'pkt>(
515    buf: &mut DissectBuffer<'pkt>,
516    data: &'pkt [u8],
517    offset: usize,
518) -> Result<(), PacketError> {
519    if data.len() < ROUTE_REFRESH_SIZE {
520        return Err(PacketError::Truncated {
521            expected: ROUTE_REFRESH_SIZE,
522            actual: data.len(),
523        });
524    }
525
526    let afi = read_be_u16(data, 19)?;
527    // RFC 7313, Section 4 — <https://www.rfc-editor.org/rfc/rfc7313#section-4>
528    // redefined byte 21 from "Reserved" to "Message Subtype".
529    let message_subtype = data[21];
530    let safi = data[22];
531
532    buf.push_field(
533        &FIELD_DESCRIPTORS[FD_AFI],
534        FieldValue::U16(afi),
535        offset + 19..offset + 21,
536    );
537    buf.push_field(
538        &FIELD_DESCRIPTORS[FD_MESSAGE_SUBTYPE],
539        FieldValue::U8(message_subtype),
540        offset + 21..offset + 22,
541    );
542    buf.push_field(
543        &FIELD_DESCRIPTORS[FD_SAFI],
544        FieldValue::U8(safi),
545        offset + 22..offset + 23,
546    );
547
548    Ok(())
549}
550
551/// Parses a sequence of BGP prefixes (prefix_len + prefix bytes).
552///
553/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
554/// Each prefix: 1-byte length (in bits) + ceil(length/8) bytes of prefix.
555/// When `ipv6` is true, formats as IPv6; otherwise as IPv4.
556fn parse_prefixes<'pkt>(
557    buf: &mut DissectBuffer<'pkt>,
558    data: &'pkt [u8],
559    base_offset: usize,
560    ipv6: bool,
561) {
562    let mut pos = 0;
563
564    let max_bits: usize = if ipv6 { 128 } else { 32 };
565    let descriptor = if ipv6 {
566        &PREFIX_ENTRY_IPV6_DESCRIPTOR
567    } else {
568        &PREFIX_ENTRY_IPV4_DESCRIPTOR
569    };
570
571    while pos < data.len() {
572        let prefix_bits = data[pos] as usize;
573
574        // Validate prefix length against address family maximum.
575        if prefix_bits > max_bits {
576            break;
577        }
578
579        let prefix_bytes = prefix_bits.div_ceil(8);
580
581        if pos + 1 + prefix_bytes > data.len() {
582            break;
583        }
584
585        let abs = base_offset + pos;
586        let entry_len = 1 + prefix_bytes;
587        buf.push_field(
588            descriptor,
589            FieldValue::Bytes(&data[pos..pos + entry_len]),
590            abs..abs + entry_len,
591        );
592
593        pos += entry_len;
594    }
595}
596
597/// Returns a human-readable name for path attribute type codes.
598///
599/// IANA BGP Path Attributes — <https://www.iana.org/assignments/bgp-parameters>
600fn path_attr_type_name(v: u8) -> Option<&'static str> {
601    match v {
602        1 => Some("ORIGIN"),
603        2 => Some("AS_PATH"),
604        3 => Some("NEXT_HOP"),
605        4 => Some("MULTI_EXIT_DISC"),
606        5 => Some("LOCAL_PREF"),
607        6 => Some("ATOMIC_AGGREGATE"),
608        7 => Some("AGGREGATOR"),
609        8 => Some("COMMUNITIES"),
610        9 => Some("ORIGINATOR_ID"),
611        10 => Some("CLUSTER_LIST"),
612        14 => Some("MP_REACH_NLRI"),
613        15 => Some("MP_UNREACH_NLRI"),
614        16 => Some("EXTENDED COMMUNITIES"),
615        17 => Some("AS4_PATH"),
616        18 => Some("AS4_AGGREGATOR"),
617        22 => Some("PMSI_TUNNEL"),
618        23 => Some("Tunnel Encapsulation"),
619        26 => Some("AIGP"),
620        29 => Some("BGP-LS Attribute"),
621        32 => Some("LARGE_COMMUNITY"),
622        33 => Some("BGPsec_Path"),
623        35 => Some("Only to Customer (OTC)"),
624        40 => Some("BGP Prefix-SID"),
625        _ => None,
626    }
627}
628
629/// Parses a single path attribute and pushes it as an Object into the buffer.
630///
631/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
632/// Returns the number of bytes consumed, or `None` if parsing fails.
633fn parse_path_attribute<'pkt>(
634    buf: &mut DissectBuffer<'pkt>,
635    data: &'pkt [u8],
636    base_offset: usize,
637) -> Option<usize> {
638    if data.len() < 3 {
639        return None;
640    }
641
642    let flags = data[0];
643    let type_code = data[1];
644    let extended_length = flags & 0x10 != 0;
645
646    let (attr_len, header_len) = if extended_length {
647        if data.len() < 4 {
648            return None;
649        }
650        (read_be_u16(data, 2).unwrap_or_default() as usize, 4usize)
651    } else {
652        (data[2] as usize, 3usize)
653    };
654
655    let total_len = header_len + attr_len;
656    if data.len() < total_len {
657        return None;
658    }
659
660    let value_data = &data[header_len..total_len];
661
662    let obj_idx = buf.begin_container(
663        &PATH_ATTR_OBJECT_DESCRIPTOR,
664        FieldValue::Object(0..0),
665        base_offset..base_offset + total_len,
666    );
667
668    buf.push_field(
669        &PATH_ATTR_CHILDREN[FD_PA_FLAGS],
670        FieldValue::U8(flags),
671        base_offset..base_offset + 1,
672    );
673    buf.push_field(
674        &PATH_ATTR_CHILDREN[FD_PA_TYPE_CODE],
675        FieldValue::U8(type_code),
676        base_offset + 1..base_offset + 2,
677    );
678    buf.push_field(
679        &PATH_ATTR_CHILDREN[FD_PA_ATTR_LENGTH],
680        FieldValue::U16(attr_len as u16),
681        base_offset + 2..base_offset + header_len,
682    );
683
684    let val_offset = base_offset + header_len;
685    parse_attr_value(buf, type_code, value_data, val_offset);
686
687    buf.end_container(obj_idx);
688
689    Some(total_len)
690}
691
692/// Returns a human-readable name for ORIGIN values.
693///
694/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
695fn origin_name(v: u8) -> Option<&'static str> {
696    match v {
697        0 => Some("IGP"),
698        1 => Some("EGP"),
699        2 => Some("INCOMPLETE"),
700        _ => None,
701    }
702}
703
704/// Formats a well-known community value using IANA registry names.
705///
706/// IANA BGP Well-known Communities —
707/// <https://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xhtml>
708fn well_known_community_name(v: u32) -> Option<&'static str> {
709    match v {
710        0xFFFF_0000 => Some("GRACEFUL_SHUTDOWN"),
711        0xFFFF_0001 => Some("ACCEPT_OWN"),
712        0xFFFF_0002 => Some("ROUTE_FILTER_TRANSLATED_v4"),
713        0xFFFF_0003 => Some("ROUTE_FILTER_v4"),
714        0xFFFF_0004 => Some("ROUTE_FILTER_TRANSLATED_v6"),
715        0xFFFF_0005 => Some("ROUTE_FILTER_v6"),
716        0xFFFF_0006 => Some("LLGR_STALE"),
717        0xFFFF_0007 => Some("NO_LLGR"),
718        0xFFFF_029A => Some("BLACKHOLE"),
719        0xFFFF_FF01 => Some("NO_EXPORT"),
720        0xFFFF_FF02 => Some("NO_ADVERTISE"),
721        0xFFFF_FF03 => Some("NO_EXPORT_SUBCONFED"),
722        0xFFFF_FF04 => Some("NOPEER"),
723        _ => None,
724    }
725}
726
727/// Returns a human-readable name for Extended Community types.
728///
729/// RFC 4360 — <https://www.rfc-editor.org/rfc/rfc4360>
730/// RFC 9012 — <https://www.rfc-editor.org/rfc/rfc9012>
731fn extended_community_type_name(type_high: u8, sub_type: u8) -> Option<&'static str> {
732    let base_type = type_high & 0x3F;
733    match (base_type, sub_type) {
734        (0x00, 0x02) | (0x02, 0x02) => Some("Route Target"),
735        (0x01, 0x02) => Some("Route Target (IPv4)"),
736        (0x00, 0x03) | (0x02, 0x03) => Some("Route Origin"),
737        (0x01, 0x03) => Some("Route Origin (IPv4)"),
738        (0x03, 0x0B) => Some("Color"),
739        (0x06, _) => Some("EVPN"),
740        (0x0C, 0x00) => Some("MUP Direct Segment Identifier"),
741        _ => None,
742    }
743}
744
745/// Returns a human-readable name for AS_PATH segment types.
746///
747/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
748/// RFC 5065 — <https://www.rfc-editor.org/rfc/rfc5065>
749fn as_path_segment_type_name(v: u8) -> Option<&'static str> {
750    match v {
751        1 => Some("AS_SET"),
752        2 => Some("AS_SEQUENCE"),
753        3 => Some("AS_CONFED_SEQUENCE"),
754        4 => Some("AS_CONFED_SET"),
755        _ => None,
756    }
757}
758
759/// Parses BGP Prefix-SID attribute value as a sequence of TLVs.
760///
761/// RFC 8669, Section 3 — <https://www.rfc-editor.org/rfc/rfc8669#section-3>
762///
763/// Each TLV: 1-byte Type + 2-byte Length + variable Value.
764fn parse_prefix_sid<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
765    let array_idx = buf.begin_container(
766        &PATH_ATTR_CHILDREN[FD_PA_VALUE],
767        FieldValue::Array(0..0),
768        offset..offset + data.len(),
769    );
770    let mut pos = 0;
771    let mut count = 0;
772
773    while pos + 3 <= data.len() {
774        let tlv_type = data[pos];
775        let tlv_len = read_be_u16(data, pos + 1).unwrap_or_default() as usize;
776        let abs = offset + pos;
777
778        if pos + 3 + tlv_len > data.len() {
779            break;
780        }
781
782        let total = 3 + tlv_len;
783        let obj_idx = buf.begin_container(
784            &PREFIX_SID_TLV_OBJECT_DESCRIPTOR,
785            FieldValue::Object(0..0),
786            abs..abs + total,
787        );
788
789        buf.push_field(
790            &PREFIX_SID_TLV_CHILDREN[FD_PSID_TYPE],
791            FieldValue::U8(tlv_type),
792            abs..abs + 1,
793        );
794        buf.push_field(
795            &PREFIX_SID_TLV_CHILDREN[FD_PSID_LENGTH],
796            FieldValue::U16(tlv_len as u16),
797            abs + 1..abs + 3,
798        );
799
800        let val_data = &data[pos + 3..pos + 3 + tlv_len];
801        let val_offset = abs + 3;
802
803        match tlv_type {
804            // Label-Index TLV (RFC 8669, Section 3.1)
805            1 => parse_label_index_tlv(buf, val_data, val_offset),
806            // Originator SRGB TLV (RFC 8669, Section 3.2)
807            3 => parse_originator_srgb_tlv(buf, val_data, val_offset),
808            // SRv6 L3 Service TLV (RFC 9252, Section 2) /
809            // SRv6 L2 Service TLV (RFC 9252, Section 2)
810            5 | 6 => parse_srv6_service_tlv(buf, val_data, val_offset),
811            _ => {
812                if !val_data.is_empty() {
813                    buf.push_field(
814                        &PREFIX_SID_TLV_CHILDREN[FD_PSID_VALUE],
815                        FieldValue::Bytes(val_data),
816                        val_offset..val_offset + val_data.len(),
817                    );
818                }
819            }
820        }
821
822        buf.end_container(obj_idx);
823        count += 1;
824        pos += total;
825    }
826
827    if count == 0 {
828        // Remove the empty array placeholder
829        buf.pop_field();
830    } else {
831        buf.end_container(array_idx);
832    }
833}
834
835/// Parses a Label-Index TLV value.
836///
837/// RFC 8669, Section 3.1 — <https://www.rfc-editor.org/rfc/rfc8669#section-3.1>
838///
839///   Reserved (1 byte) + Flags (2 bytes) + Label Index (4 bytes) = 7 bytes.
840fn parse_label_index_tlv<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
841    if data.len() < 7 {
842        if !data.is_empty() {
843            buf.push_field(
844                &PREFIX_SID_TLV_CHILDREN[FD_PSID_VALUE],
845                FieldValue::Bytes(data),
846                offset..offset + data.len(),
847            );
848        }
849        return;
850    }
851    // Skip Reserved (1 byte)
852    let flags = read_be_u16(data, 1).unwrap_or_default();
853    let label_index = read_be_u32(data, 3).unwrap_or_default();
854
855    buf.push_field(
856        &PREFIX_SID_TLV_CHILDREN[FD_PSID_FLAGS],
857        FieldValue::U16(flags),
858        offset + 1..offset + 3,
859    );
860    buf.push_field(
861        &PREFIX_SID_TLV_CHILDREN[FD_PSID_LABEL_INDEX],
862        FieldValue::U32(label_index),
863        offset + 3..offset + 7,
864    );
865}
866
867/// Parses an Originator SRGB TLV value.
868///
869/// RFC 8669, Section 3.2 — <https://www.rfc-editor.org/rfc/rfc8669#section-3.2>
870///
871///   Flags (2 bytes) + SRGB entries (6 bytes each: 3-byte base + 3-byte range).
872fn parse_originator_srgb_tlv<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
873    if data.len() < 2 {
874        if !data.is_empty() {
875            buf.push_field(
876                &PREFIX_SID_TLV_CHILDREN[FD_PSID_VALUE],
877                FieldValue::Bytes(data),
878                offset..offset + data.len(),
879            );
880        }
881        return;
882    }
883
884    let flags = read_be_u16(data, 0).unwrap_or_default();
885    buf.push_field(
886        &PREFIX_SID_TLV_CHILDREN[FD_PSID_FLAGS],
887        FieldValue::U16(flags),
888        offset..offset + 2,
889    );
890
891    let array_idx = buf.begin_container(
892        &PREFIX_SID_TLV_CHILDREN[FD_PSID_SRGB_ENTRIES],
893        FieldValue::Array(0..0),
894        offset + 2..offset + data.len(),
895    );
896    let mut count = 0;
897    let mut pos = 2;
898    while pos + 6 <= data.len() {
899        let abs = offset + pos;
900        let base = read_be_u24(data, pos).unwrap_or_default();
901        let range = read_be_u24(data, pos + 3).unwrap_or_default();
902
903        let obj_idx = buf.begin_container(
904            &SRGB_ENTRY_OBJECT_DESCRIPTOR,
905            FieldValue::Object(0..0),
906            abs..abs + 6,
907        );
908        buf.push_field(
909            &SRGB_ENTRY_CHILDREN[FD_SRGB_BASE],
910            FieldValue::U32(base),
911            abs..abs + 3,
912        );
913        buf.push_field(
914            &SRGB_ENTRY_CHILDREN[FD_SRGB_RANGE],
915            FieldValue::U32(range),
916            abs + 3..abs + 6,
917        );
918        buf.end_container(obj_idx);
919
920        count += 1;
921        pos += 6;
922    }
923
924    if count == 0 {
925        buf.pop_field();
926    } else {
927        buf.end_container(array_idx);
928    }
929}
930
931/// Parses an SRv6 L3/L2 Service TLV value.
932///
933/// RFC 9252, Section 2 — <https://www.rfc-editor.org/rfc/rfc9252#section-2>
934///
935///   Reserved (1 byte) + Sub-TLVs (each: 1-byte Type + 2-byte Length + variable Value).
936fn parse_srv6_service_tlv<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
937    if data.is_empty() {
938        return;
939    }
940
941    // Skip Reserved (1 byte)
942    let array_idx = buf.begin_container(
943        &PREFIX_SID_TLV_CHILDREN[FD_PSID_SUB_TLVS],
944        FieldValue::Array(0..0),
945        offset + 1..offset + data.len(),
946    );
947    let mut count = 0;
948    let mut pos = 1;
949
950    while pos + 3 <= data.len() {
951        let sub_type = data[pos];
952        let sub_len = read_be_u16(data, pos + 1).unwrap_or_default() as usize;
953        let abs = offset + pos;
954
955        if pos + 3 + sub_len > data.len() {
956            break;
957        }
958
959        let total = 3 + sub_len;
960        let obj_idx = buf.begin_container(
961            &SRV6_SID_INFO_OBJECT_DESCRIPTOR,
962            FieldValue::Object(0..0),
963            abs..abs + total,
964        );
965
966        buf.push_field(
967            &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_TYPE],
968            FieldValue::U8(sub_type),
969            abs..abs + 1,
970        );
971        buf.push_field(
972            &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_LENGTH],
973            FieldValue::U16(sub_len as u16),
974            abs + 1..abs + 3,
975        );
976
977        let val_data = &data[pos + 3..pos + 3 + sub_len];
978        let val_offset = abs + 3;
979
980        match sub_type {
981            // SRv6 SID Information Sub-TLV (RFC 9252, Section 3.1)
982            1 => parse_srv6_sid_info_sub_tlv(buf, val_data, val_offset),
983            _ => {
984                if !val_data.is_empty() {
985                    buf.push_field(
986                        &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_VALUE],
987                        FieldValue::Bytes(val_data),
988                        val_offset..val_offset + val_data.len(),
989                    );
990                }
991            }
992        }
993
994        buf.end_container(obj_idx);
995        count += 1;
996        pos += total;
997    }
998
999    if count == 0 {
1000        buf.pop_field();
1001    } else {
1002        buf.end_container(array_idx);
1003    }
1004}
1005
1006/// Parses an SRv6 SID Information Sub-TLV value.
1007///
1008/// RFC 9252, Section 3.1 — <https://www.rfc-editor.org/rfc/rfc9252#section-3.1>
1009///
1010///   Reserved1 (1) + SRv6 SID (16) + Service SID Flags (1) + Endpoint Behavior (2)
1011///   + Reserved2 (1) = 21 bytes minimum, followed by optional Sub-Sub-TLVs.
1012fn parse_srv6_sid_info_sub_tlv<'pkt>(
1013    buf: &mut DissectBuffer<'pkt>,
1014    data: &'pkt [u8],
1015    offset: usize,
1016) {
1017    if data.len() < 21 {
1018        if !data.is_empty() {
1019            buf.push_field(
1020                &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_VALUE],
1021                FieldValue::Bytes(data),
1022                offset..offset + data.len(),
1023            );
1024        }
1025        return;
1026    }
1027
1028    // Skip Reserved1 (1 byte)
1029    let sid = read_ipv6_addr(data, 1).unwrap_or_default();
1030    let sid_flags = data[17];
1031    let endpoint_behavior = read_be_u16(data, 18).unwrap_or_default();
1032    // Skip Reserved2 at data[20]
1033
1034    buf.push_field(
1035        &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_SID],
1036        FieldValue::Ipv6Addr(sid),
1037        offset + 1..offset + 17,
1038    );
1039    buf.push_field(
1040        &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_FLAGS],
1041        FieldValue::U8(sid_flags),
1042        offset + 17..offset + 18,
1043    );
1044    buf.push_field(
1045        &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_ENDPOINT_BEHAVIOR],
1046        FieldValue::U16(endpoint_behavior),
1047        offset + 18..offset + 20,
1048    );
1049
1050    // Parse Sub-Sub-TLVs (RFC 9252, Section 3.2)
1051    let mut pos = 21;
1052    while pos + 3 <= data.len() {
1053        let ss_type = data[pos];
1054        let ss_len = read_be_u16(data, pos + 1).unwrap_or_default() as usize;
1055
1056        if pos + 3 + ss_len > data.len() {
1057            break;
1058        }
1059
1060        // SRv6 SID Structure Sub-Sub-TLV (RFC 9252, Section 3.2.1)
1061        if ss_type == 1 && ss_len == 6 {
1062            let ss_val = &data[pos + 3..pos + 3 + ss_len];
1063            let abs = offset + pos + 3;
1064            let obj_idx = buf.begin_container(
1065                &SRV6_SID_INFO_CHILDREN[FD_SRV6_SI_SID_STRUCTURE],
1066                FieldValue::Object(0..0),
1067                offset + pos..offset + pos + 3 + ss_len,
1068            );
1069            buf.push_field(
1070                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_LBL],
1071                FieldValue::U8(ss_val[0]),
1072                abs..abs + 1,
1073            );
1074            buf.push_field(
1075                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_LNL],
1076                FieldValue::U8(ss_val[1]),
1077                abs + 1..abs + 2,
1078            );
1079            buf.push_field(
1080                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_FL],
1081                FieldValue::U8(ss_val[2]),
1082                abs + 2..abs + 3,
1083            );
1084            buf.push_field(
1085                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_AL],
1086                FieldValue::U8(ss_val[3]),
1087                abs + 3..abs + 4,
1088            );
1089            buf.push_field(
1090                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_TL],
1091                FieldValue::U8(ss_val[4]),
1092                abs + 4..abs + 5,
1093            );
1094            buf.push_field(
1095                &SRV6_SID_STRUCTURE_CHILDREN[FD_SRV6_SS_TO],
1096                FieldValue::U8(ss_val[5]),
1097                abs + 5..abs + 6,
1098            );
1099            buf.end_container(obj_idx);
1100        }
1101
1102        pos += 3 + ss_len;
1103    }
1104}
1105
1106/// Parses an AS_PATH or AS4_PATH attribute value into the buffer.
1107///
1108/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
1109/// RFC 6793 — <https://www.rfc-editor.org/rfc/rfc6793>
1110fn parse_as_path<'pkt>(
1111    buf: &mut DissectBuffer<'pkt>,
1112    data: &'pkt [u8],
1113    offset: usize,
1114    as_size: usize,
1115) {
1116    let mut pos = 0;
1117
1118    while pos + 2 <= data.len() {
1119        let seg_type = data[pos];
1120        let seg_len = data[pos + 1] as usize;
1121        let seg_abs = offset + pos;
1122
1123        let seg_data_len = seg_len * as_size;
1124        if pos + 2 + seg_data_len > data.len() {
1125            break;
1126        }
1127
1128        let seg_obj_idx = buf.begin_container(
1129            &AS_PATH_SEG_OBJECT_DESCRIPTOR,
1130            FieldValue::Object(0..0),
1131            seg_abs..seg_abs + 2 + seg_data_len,
1132        );
1133
1134        buf.push_field(
1135            &AS_PATH_SEG_CHILDREN[FD_APS_SEGMENT_TYPE],
1136            FieldValue::U8(seg_type),
1137            seg_abs..seg_abs + 1,
1138        );
1139
1140        let as_array_idx = buf.begin_container(
1141            &AS_PATH_SEG_CHILDREN[FD_APS_AS_NUMBERS],
1142            FieldValue::Array(0..0),
1143            seg_abs + 2..seg_abs + 2 + seg_data_len,
1144        );
1145        for i in 0..seg_len {
1146            let as_offset = pos + 2 + i * as_size;
1147            let as_abs = offset + as_offset;
1148            let asn = if as_size == 4 {
1149                read_be_u32(data, as_offset).unwrap_or_default()
1150            } else {
1151                read_be_u16(data, as_offset).unwrap_or_default() as u32
1152            };
1153            buf.push_field(
1154                &AS_NUMBER_DESCRIPTOR,
1155                FieldValue::U32(asn),
1156                as_abs..as_abs + as_size,
1157            );
1158        }
1159        buf.end_container(as_array_idx);
1160
1161        buf.end_container(seg_obj_idx);
1162
1163        pos += 2 + seg_data_len;
1164    }
1165}
1166
1167/// Parses the value of a path attribute based on its type code.
1168///
1169/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
1170fn parse_attr_value<'pkt>(
1171    buf: &mut DissectBuffer<'pkt>,
1172    type_code: u8,
1173    data: &'pkt [u8],
1174    offset: usize,
1175) {
1176    match type_code {
1177        // ORIGIN (RFC 4271, Section 5.1.1)
1178        1 if data.len() == 1 => {
1179            buf.push_field(
1180                &FD_ORIGIN_VALUE,
1181                FieldValue::U8(data[0]),
1182                offset..offset + 1,
1183            );
1184        }
1185        // AS_PATH (RFC 4271, Section 5.1.2) — 2-byte AS numbers
1186        2 => {
1187            let array_idx = buf.begin_container(
1188                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1189                FieldValue::Array(0..0),
1190                offset..offset + data.len(),
1191            );
1192            parse_as_path(buf, data, offset, 2);
1193            buf.end_container(array_idx);
1194        }
1195        // NEXT_HOP (RFC 4271, Section 5.1.3) — 4-byte IPv4 address
1196        3 if data.len() == 4 => {
1197            buf.push_field(
1198                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1199                FieldValue::Ipv4Addr(read_ipv4_addr(data, 0).unwrap_or_default()),
1200                offset..offset + 4,
1201            );
1202        }
1203        // MULTI_EXIT_DISC (RFC 4271, Section 5.1.4) — 4-byte unsigned integer
1204        4 if data.len() == 4 => {
1205            let med = read_be_u32(data, 0).unwrap_or_default();
1206            buf.push_field(
1207                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1208                FieldValue::U32(med),
1209                offset..offset + 4,
1210            );
1211        }
1212        // LOCAL_PREF (RFC 4271, Section 5.1.5) — 4-byte unsigned integer
1213        5 if data.len() == 4 => {
1214            let lp = read_be_u32(data, 0).unwrap_or_default();
1215            buf.push_field(
1216                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1217                FieldValue::U32(lp),
1218                offset..offset + 4,
1219            );
1220        }
1221        // ATOMIC_AGGREGATE (RFC 4271, Section 5.1.6) — 0 bytes
1222        6 => {}
1223        // AGGREGATOR (RFC 4271, Section 5.1.7) — 2-byte AS + 4-byte IP = 6 bytes
1224        // or 4-byte AS + 4-byte IP = 8 bytes (RFC 6793)
1225        7 if data.len() == 6 || data.len() == 8 => {
1226            buf.push_field(
1227                &FD_AGGREGATOR_VALUE,
1228                FieldValue::Bytes(data),
1229                offset..offset + data.len(),
1230            );
1231        }
1232        // COMMUNITIES (RFC 1997) — sequence of 4-byte values
1233        8 if data.len() % 4 == 0 => {
1234            let array_idx = buf.begin_container(
1235                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1236                FieldValue::Array(0..0),
1237                offset..offset + data.len(),
1238            );
1239            let mut pos = 0;
1240            while pos + 4 <= data.len() {
1241                let val = read_be_u32(data, pos).unwrap_or_default();
1242                buf.push_field(
1243                    &COMMUNITY_ENTRY_DESCRIPTOR,
1244                    FieldValue::U32(val),
1245                    offset + pos..offset + pos + 4,
1246                );
1247                pos += 4;
1248            }
1249            buf.end_container(array_idx);
1250        }
1251        // ORIGINATOR_ID (RFC 4456) — 4-byte IPv4 address
1252        9 if data.len() == 4 => {
1253            buf.push_field(
1254                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1255                FieldValue::Ipv4Addr(read_ipv4_addr(data, 0).unwrap_or_default()),
1256                offset..offset + 4,
1257            );
1258        }
1259        // CLUSTER_LIST (RFC 4456) — sequence of 4-byte cluster IDs
1260        10 if data.len() % 4 == 0 => {
1261            let array_idx = buf.begin_container(
1262                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1263                FieldValue::Array(0..0),
1264                offset..offset + data.len(),
1265            );
1266            let mut pos = 0;
1267            while pos + 4 <= data.len() {
1268                buf.push_field(
1269                    &CLUSTER_ID_DESCRIPTOR,
1270                    FieldValue::Ipv4Addr([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]),
1271                    offset + pos..offset + pos + 4,
1272                );
1273                pos += 4;
1274            }
1275            buf.end_container(array_idx);
1276        }
1277        // MP_REACH_NLRI (RFC 4760, Section 3)
1278        14 if data.len() >= 5 => {
1279            parse_mp_reach_nlri(buf, data, offset);
1280        }
1281        // MP_UNREACH_NLRI (RFC 4760, Section 4)
1282        15 if data.len() >= 3 => {
1283            parse_mp_unreach_nlri(buf, data, offset);
1284        }
1285        // EXTENDED_COMMUNITIES (RFC 4360) — sequence of 8-byte values
1286        16 if data.len() % 8 == 0 => {
1287            let array_idx = buf.begin_container(
1288                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1289                FieldValue::Array(0..0),
1290                offset..offset + data.len(),
1291            );
1292            let mut pos = 0;
1293            while pos + 8 <= data.len() {
1294                let abs = offset + pos;
1295                buf.push_field(
1296                    &EXT_COMMUNITY_ENTRY_DESCRIPTOR,
1297                    FieldValue::Bytes(&data[pos..pos + 8]),
1298                    abs..abs + 8,
1299                );
1300                pos += 8;
1301            }
1302            buf.end_container(array_idx);
1303        }
1304        // AS4_PATH (RFC 6793) — same format as AS_PATH but with 4-byte AS numbers
1305        17 => {
1306            let array_idx = buf.begin_container(
1307                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1308                FieldValue::Array(0..0),
1309                offset..offset + data.len(),
1310            );
1311            parse_as_path(buf, data, offset, 4);
1312            buf.end_container(array_idx);
1313        }
1314        // AS4_AGGREGATOR (RFC 6793) — 4-byte AS + 4-byte IP = 8 bytes
1315        18 if data.len() == 8 => {
1316            buf.push_field(
1317                &FD_AS4_AGGREGATOR_VALUE,
1318                FieldValue::Bytes(data),
1319                offset..offset + 8,
1320            );
1321        }
1322        // LARGE_COMMUNITY (RFC 8092) — sequence of 12-byte values
1323        32 if data.len() % 12 == 0 => {
1324            let array_idx = buf.begin_container(
1325                &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1326                FieldValue::Array(0..0),
1327                offset..offset + data.len(),
1328            );
1329            let mut pos = 0;
1330            while pos + 12 <= data.len() {
1331                buf.push_field(
1332                    &LARGE_COMMUNITY_ENTRY_DESCRIPTOR,
1333                    FieldValue::Bytes(&data[pos..pos + 12]),
1334                    offset + pos..offset + pos + 12,
1335                );
1336                pos += 12;
1337            }
1338            buf.end_container(array_idx);
1339        }
1340        // BGP Prefix-SID (RFC 8669, RFC 9252)
1341        40 => {
1342            parse_prefix_sid(buf, data, offset);
1343        }
1344        _ => {
1345            // Unknown/unhandled attribute: store raw bytes
1346            if !data.is_empty() {
1347                buf.push_field(
1348                    &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1349                    FieldValue::Bytes(data),
1350                    offset..offset + data.len(),
1351                );
1352            }
1353        }
1354    }
1355}
1356
1357/// Returns a human-readable name for MUP route types.
1358///
1359/// draft-ietf-bess-mup-safi-00, Section 3 —
1360/// <https://datatracker.ietf.org/doc/draft-ietf-bess-mup-safi/>
1361fn mup_route_type_name(v: u16) -> Option<&'static str> {
1362    match v {
1363        1 => Some("Interwork Segment Discovery"),
1364        2 => Some("Direct Segment Discovery"),
1365        3 => Some("Type 1 Session Transformed"),
1366        4 => Some("Type 2 Session Transformed"),
1367        _ => None,
1368    }
1369}
1370
1371/// Returns a human-readable name for MUP architecture types.
1372///
1373/// draft-ietf-bess-mup-safi-00, Section 3
1374fn mup_architecture_type_name(v: u8) -> Option<&'static str> {
1375    match v {
1376        1 => Some("3gpp-5g"),
1377        _ => None,
1378    }
1379}
1380
1381/// Parses a sequence of MUP NLRI entries into the buffer.
1382///
1383/// draft-ietf-bess-mup-safi-00, Section 3 —
1384/// <https://datatracker.ietf.org/doc/draft-ietf-bess-mup-safi/>
1385///
1386/// Each MUP NLRI: Architecture Type (1) + Route Type (2) + Length (1) + Route Type specific data.
1387fn parse_mup_nlri<'pkt>(
1388    buf: &mut DissectBuffer<'pkt>,
1389    data: &'pkt [u8],
1390    base_offset: usize,
1391    ipv6: bool,
1392) {
1393    let mut pos = 0;
1394
1395    while pos + 4 <= data.len() {
1396        let arch_type = data[pos];
1397        let route_type = read_be_u16(data, pos + 1).unwrap_or_default();
1398        let rt_len = data[pos + 3] as usize;
1399        let header_len = 4;
1400
1401        if pos + header_len + rt_len > data.len() {
1402            break;
1403        }
1404
1405        let abs = base_offset + pos;
1406        let rt_data = &data[pos + header_len..pos + header_len + rt_len];
1407        let total = header_len + rt_len;
1408
1409        let obj_idx = buf.begin_container(
1410            &MUP_NLRI_OBJECT_DESCRIPTOR,
1411            FieldValue::Object(0..0),
1412            abs..abs + total,
1413        );
1414
1415        buf.push_field(
1416            &MUP_NLRI_CHILDREN[FD_MUP_ARCH_TYPE],
1417            FieldValue::U8(arch_type),
1418            abs..abs + 1,
1419        );
1420        buf.push_field(
1421            &MUP_NLRI_CHILDREN[FD_MUP_ROUTE_TYPE],
1422            FieldValue::U16(route_type),
1423            abs + 1..abs + 3,
1424        );
1425
1426        let rt_offset = abs + header_len;
1427        parse_mup_route_type_data(buf, route_type, rt_data, rt_offset, ipv6);
1428
1429        buf.end_container(obj_idx);
1430
1431        pos += total;
1432    }
1433}
1434
1435/// Parses route-type-specific data for MUP NLRI entries.
1436///
1437/// draft-ietf-bess-mup-safi-00, Sections 3.1–3.4
1438fn parse_mup_route_type_data<'pkt>(
1439    buf: &mut DissectBuffer<'pkt>,
1440    route_type: u16,
1441    data: &'pkt [u8],
1442    offset: usize,
1443    ipv6: bool,
1444) {
1445    // All route types start with an 8-byte RD (RFC 4364).
1446    if data.len() < 8 {
1447        if !data.is_empty() {
1448            buf.push_field(
1449                &MUP_NLRI_CHILDREN[FD_MUP_VALUE],
1450                FieldValue::Bytes(data),
1451                offset..offset + data.len(),
1452            );
1453        }
1454        return;
1455    }
1456
1457    buf.push_field(
1458        &MUP_NLRI_CHILDREN[FD_MUP_RD],
1459        FieldValue::Bytes(&data[..8]),
1460        offset..offset + 8,
1461    );
1462
1463    let rest = &data[8..];
1464    let rest_offset = offset + 8;
1465
1466    match route_type {
1467        // Route Type 1: Interwork Segment Discovery
1468        1 => {
1469            if rest.is_empty() {
1470                return;
1471            }
1472            let prefix_len = rest[0] as usize;
1473            let prefix_bytes = prefix_len.div_ceil(8);
1474            if 1 + prefix_bytes > rest.len() {
1475                return;
1476            }
1477            let prefix_descriptor = if ipv6 {
1478                &PREFIX_ENTRY_IPV6_DESCRIPTOR
1479            } else {
1480                &PREFIX_ENTRY_IPV4_DESCRIPTOR
1481            };
1482            buf.push_field(
1483                prefix_descriptor,
1484                FieldValue::Bytes(&rest[..1 + prefix_bytes]),
1485                rest_offset..rest_offset + 1 + prefix_bytes,
1486            );
1487        }
1488        // Route Type 2: Direct Segment Discovery
1489        2 => {
1490            let addr_len = if ipv6 { 16 } else { 4 };
1491            if rest.len() < addr_len {
1492                return;
1493            }
1494            if ipv6 {
1495                let addr = read_ipv6_addr(rest, 0).unwrap_or_default();
1496                buf.push_field(
1497                    &MUP_NLRI_CHILDREN[FD_MUP_ADDRESS],
1498                    FieldValue::Ipv6Addr(addr),
1499                    rest_offset..rest_offset + 16,
1500                );
1501            } else {
1502                buf.push_field(
1503                    &MUP_NLRI_CHILDREN[FD_MUP_ADDRESS],
1504                    FieldValue::Ipv4Addr([rest[0], rest[1], rest[2], rest[3]]),
1505                    rest_offset..rest_offset + 4,
1506                );
1507            }
1508        }
1509        // Route Type 3: Type 1 Session Transformed (ST) — 3GPP 5G
1510        3 => {
1511            if rest.is_empty() {
1512                return;
1513            }
1514            let prefix_len = rest[0] as usize;
1515            let prefix_bytes = prefix_len.div_ceil(8);
1516            if 1 + prefix_bytes > rest.len() {
1517                return;
1518            }
1519            let prefix_descriptor = if ipv6 {
1520                &PREFIX_ENTRY_IPV6_DESCRIPTOR
1521            } else {
1522                &PREFIX_ENTRY_IPV4_DESCRIPTOR
1523            };
1524            buf.push_field(
1525                prefix_descriptor,
1526                FieldValue::Bytes(&rest[..1 + prefix_bytes]),
1527                rest_offset..rest_offset + 1 + prefix_bytes,
1528            );
1529
1530            // 3GPP 5G architecture-specific fields
1531            let arch_start = 1 + prefix_bytes;
1532            let arch_data = &rest[arch_start..];
1533            let arch_offset = rest_offset + arch_start;
1534            // TEID (4) + QFI (1) + Endpoint Address Length (1) = minimum 6
1535            if arch_data.len() >= 6 {
1536                buf.push_field(
1537                    &MUP_NLRI_CHILDREN[FD_MUP_TEID],
1538                    FieldValue::Bytes(&arch_data[..4]),
1539                    arch_offset..arch_offset + 4,
1540                );
1541                buf.push_field(
1542                    &MUP_NLRI_CHILDREN[FD_MUP_QFI],
1543                    FieldValue::U8(arch_data[4]),
1544                    arch_offset + 4..arch_offset + 5,
1545                );
1546
1547                let ep_addr_bits = arch_data[5] as usize;
1548                let ep_addr_bytes = ep_addr_bits / 8;
1549                let ep_start = 6;
1550                if ep_start + ep_addr_bytes <= arch_data.len() {
1551                    let ep_val = format_address(
1552                        &arch_data[ep_start..ep_start + ep_addr_bytes],
1553                        ep_addr_bits == 128,
1554                    );
1555                    buf.push_field(
1556                        &MUP_NLRI_CHILDREN[FD_MUP_ENDPOINT_ADDRESS],
1557                        ep_val,
1558                        arch_offset + ep_start..arch_offset + ep_start + ep_addr_bytes,
1559                    );
1560
1561                    // Optional Source Address
1562                    let src_start = ep_start + ep_addr_bytes;
1563                    if src_start < arch_data.len() {
1564                        let src_addr_bits = arch_data[src_start] as usize;
1565                        if src_addr_bits > 0 {
1566                            let src_addr_bytes = src_addr_bits / 8;
1567                            let src_data_start = src_start + 1;
1568                            if src_data_start + src_addr_bytes <= arch_data.len() {
1569                                let src_val = format_address(
1570                                    &arch_data[src_data_start..src_data_start + src_addr_bytes],
1571                                    src_addr_bits == 128,
1572                                );
1573                                buf.push_field(
1574                                    &MUP_NLRI_CHILDREN[FD_MUP_SOURCE_ADDRESS],
1575                                    src_val,
1576                                    arch_offset + src_data_start
1577                                        ..arch_offset + src_data_start + src_addr_bytes,
1578                                );
1579                            }
1580                        }
1581                    }
1582                }
1583            }
1584        }
1585        // Route Type 4: Type 2 Session Transformed (ST)
1586        4 => {
1587            if rest.is_empty() {
1588                return;
1589            }
1590            let ep_len_bits = rest[0] as usize;
1591            // Endpoint length includes TEID bits (32) + address bits
1592            let ep_total_bytes = ep_len_bits.div_ceil(8);
1593            if 1 + ep_total_bytes > rest.len() {
1594                return;
1595            }
1596            // Extract address portion (endpoint length minus TEID bits)
1597            let addr_bits = ep_len_bits.saturating_sub(32);
1598            let addr_bytes = addr_bits.div_ceil(8);
1599            if addr_bytes > 0 {
1600                let addr_val = format_address(&rest[1..1 + addr_bytes], addr_bits == 128);
1601                buf.push_field(
1602                    &MUP_NLRI_CHILDREN[FD_MUP_ENDPOINT_ADDRESS],
1603                    addr_val,
1604                    rest_offset + 1..rest_offset + 1 + addr_bytes,
1605                );
1606            }
1607            let teid_start = 1 + addr_bytes;
1608            if teid_start + 4 <= rest.len() {
1609                buf.push_field(
1610                    &MUP_NLRI_CHILDREN[FD_MUP_TEID],
1611                    FieldValue::Bytes(&rest[teid_start..teid_start + 4]),
1612                    rest_offset + teid_start..rest_offset + teid_start + 4,
1613                );
1614            }
1615        }
1616        _ => {
1617            if !rest.is_empty() {
1618                buf.push_field(
1619                    &MUP_NLRI_CHILDREN[FD_MUP_VALUE],
1620                    FieldValue::Bytes(rest),
1621                    rest_offset..rest_offset + rest.len(),
1622                );
1623            }
1624        }
1625    }
1626}
1627
1628/// Writes a BGP IPv4 NLRI prefix as a JSON-quoted CIDR string (e.g., `"192.168.1.0/24"`).
1629///
1630/// The raw bytes are `[prefix_len_bits, prefix_octets...]` per RFC 4271, Section 4.3.
1631/// Missing octets are zero-filled to produce a full dotted-quad address.
1632///
1633/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
1634fn format_nlri_ipv4_prefix(
1635    value: &FieldValue<'_>,
1636    _ctx: &FormatContext<'_>,
1637    w: &mut dyn std::io::Write,
1638) -> std::io::Result<()> {
1639    let bytes = match value {
1640        FieldValue::Bytes(b) => *b,
1641        _ => return w.write_all(b"\"\""),
1642    };
1643    if bytes.is_empty() {
1644        return w.write_all(b"\"\"");
1645    }
1646    let prefix_len = bytes[0];
1647    let mut octets = [0u8; 4];
1648    let available = (bytes.len() - 1).min(4);
1649    octets[..available].copy_from_slice(&bytes[1..1 + available]);
1650    write!(
1651        w,
1652        "\"{}.{}.{}.{}/{}\"",
1653        octets[0], octets[1], octets[2], octets[3], prefix_len
1654    )
1655}
1656
1657/// Writes a BGP IPv6 NLRI prefix as a JSON-quoted CIDR string (e.g., `"2001:db8::/32"`).
1658///
1659/// The raw bytes are `[prefix_len_bits, prefix_octets...]` per RFC 4760, Section 3.
1660/// Missing octets are zero-filled and the address is formatted per RFC 5952.
1661///
1662/// RFC 4760, Section 3 — <https://www.rfc-editor.org/rfc/rfc4760#section-3>
1663fn format_nlri_ipv6_prefix(
1664    value: &FieldValue<'_>,
1665    _ctx: &FormatContext<'_>,
1666    w: &mut dyn std::io::Write,
1667) -> std::io::Result<()> {
1668    let bytes = match value {
1669        FieldValue::Bytes(b) => *b,
1670        _ => return w.write_all(b"\"\""),
1671    };
1672    if bytes.is_empty() {
1673        return w.write_all(b"\"\"");
1674    }
1675    let prefix_len = bytes[0];
1676    let mut addr = [0u8; 16];
1677    let available = (bytes.len() - 1).min(16);
1678    addr[..available].copy_from_slice(&bytes[1..1 + available]);
1679    // Use FieldValue::Ipv6Addr Display which formats per RFC 5952.
1680    write!(w, "\"{}/{}\"", FieldValue::Ipv6Addr(addr), prefix_len)
1681}
1682
1683/// Formats an address as IPv4 or IPv6 FieldValue.
1684fn format_address(data: &[u8], ipv6: bool) -> FieldValue<'_> {
1685    if ipv6 && data.len() == 16 {
1686        FieldValue::Ipv6Addr(read_ipv6_addr(data, 0).unwrap_or_default())
1687    } else if !ipv6 && data.len() == 4 {
1688        FieldValue::Ipv4Addr(read_ipv4_addr(data, 0).unwrap_or_default())
1689    } else {
1690        FieldValue::Bytes(data)
1691    }
1692}
1693
1694/// Writes a BGP AGGREGATOR / AS4_AGGREGATOR value as `"<AS> <IPv4>"`.
1695///
1696/// Accepts 6 bytes (2-byte AS + 4-byte IPv4) or 8 bytes (4-byte AS + 4-byte IPv4).
1697///
1698/// RFC 4271, Section 5.1.7 — <https://www.rfc-editor.org/rfc/rfc4271#section-5.1.7>
1699/// RFC 6793, Section 7 — <https://www.rfc-editor.org/rfc/rfc6793#section-7>
1700fn format_aggregator(
1701    value: &FieldValue<'_>,
1702    _ctx: &FormatContext<'_>,
1703    w: &mut dyn std::io::Write,
1704) -> std::io::Result<()> {
1705    let bytes = match value {
1706        FieldValue::Bytes(b) => *b,
1707        _ => return w.write_all(b"\"\""),
1708    };
1709    match bytes.len() {
1710        6 => {
1711            let asn = u16::from_be_bytes([bytes[0], bytes[1]]) as u32;
1712            write!(
1713                w,
1714                "\"{} {}.{}.{}.{}\"",
1715                asn, bytes[2], bytes[3], bytes[4], bytes[5]
1716            )
1717        }
1718        8 => {
1719            let asn = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1720            write!(
1721                w,
1722                "\"{} {}.{}.{}.{}\"",
1723                asn, bytes[4], bytes[5], bytes[6], bytes[7]
1724            )
1725        }
1726        _ => w.write_all(b"\"\""),
1727    }
1728}
1729
1730/// Writes a BGP Extended Community as a human-readable string.
1731///
1732/// 8-byte value: Type (1) + Sub-Type (1) + Value (6).
1733/// - Type 0x00/0x40: 2-Octet AS — `"<AS>:<value>"`
1734/// - Type 0x01/0x41: IPv4 Address — `"<IPv4>:<value>"`
1735/// - Type 0x02/0x42: 4-Octet AS — `"<AS>:<value>"`
1736/// - Other types: hex representation.
1737///
1738/// RFC 4360, Section 3 — <https://www.rfc-editor.org/rfc/rfc4360#section-3>
1739fn format_ext_community(
1740    value: &FieldValue<'_>,
1741    _ctx: &FormatContext<'_>,
1742    w: &mut dyn std::io::Write,
1743) -> std::io::Result<()> {
1744    let bytes = match value {
1745        FieldValue::Bytes(b) if b.len() == 8 => *b,
1746        _ => return w.write_all(b"\"\""),
1747    };
1748    let type_high = bytes[0];
1749    match type_high {
1750        // 2-Octet AS Specific
1751        0x00 | 0x40 => {
1752            let asn = u16::from_be_bytes([bytes[2], bytes[3]]) as u32;
1753            let val = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1754            write!(w, "\"{}:{}\"", asn, val)
1755        }
1756        // IPv4 Address Specific
1757        0x01 | 0x41 => {
1758            let val = u16::from_be_bytes([bytes[6], bytes[7]]);
1759            write!(
1760                w,
1761                "\"{}.{}.{}.{}:{}\"",
1762                bytes[2], bytes[3], bytes[4], bytes[5], val
1763            )
1764        }
1765        // 4-Octet AS Specific
1766        0x02 | 0x42 => {
1767            let asn = u32::from_be_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]);
1768            let val = u16::from_be_bytes([bytes[6], bytes[7]]);
1769            write!(w, "\"{}:{}\"", asn, val)
1770        }
1771        _ => {
1772            write!(w, "\"0x")?;
1773            for b in bytes {
1774                write!(w, "{b:02x}")?;
1775            }
1776            write!(w, "\"")
1777        }
1778    }
1779}
1780
1781/// Writes a BGP Large Community as `"<global>:<local1>:<local2>"`.
1782///
1783/// 12-byte value: Global Administrator (u32) : Local Data 1 (u32) : Local Data 2 (u32).
1784///
1785/// RFC 8092, Section 2 — <https://www.rfc-editor.org/rfc/rfc8092#section-2>
1786fn format_large_community(
1787    value: &FieldValue<'_>,
1788    _ctx: &FormatContext<'_>,
1789    w: &mut dyn std::io::Write,
1790) -> std::io::Result<()> {
1791    let bytes = match value {
1792        FieldValue::Bytes(b) if b.len() == 12 => *b,
1793        _ => return w.write_all(b"\"\""),
1794    };
1795    let global = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1796    let local1 = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1797    let local2 = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
1798    write!(w, "\"{}:{}:{}\"", global, local1, local2)
1799}
1800
1801/// Writes a Route Distinguisher as `"<type>:<admin>:<assigned>"`.
1802///
1803/// 8-byte value: Type (u16 BE) + admin/assigned fields.
1804/// - Type 0: 2-byte ASN + 4-byte assigned → `"0:<ASN>:<assigned>"`
1805/// - Type 1: 4-byte IPv4 + 2-byte assigned → `"1:<IPv4>:<assigned>"`
1806/// - Type 2: 4-byte ASN + 2-byte assigned → `"2:<ASN>:<assigned>"`
1807///
1808/// RFC 4364, Section 4.2 — <https://www.rfc-editor.org/rfc/rfc4364#section-4.2>
1809fn format_route_distinguisher(
1810    value: &FieldValue<'_>,
1811    _ctx: &FormatContext<'_>,
1812    w: &mut dyn std::io::Write,
1813) -> std::io::Result<()> {
1814    let bytes = match value {
1815        FieldValue::Bytes(b) if b.len() == 8 => *b,
1816        _ => return w.write_all(b"\"\""),
1817    };
1818    let rd_type = u16::from_be_bytes([bytes[0], bytes[1]]);
1819    match rd_type {
1820        0 => {
1821            let asn = u16::from_be_bytes([bytes[2], bytes[3]]) as u32;
1822            let val = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
1823            write!(w, "\"0:{}:{}\"", asn, val)
1824        }
1825        1 => {
1826            let val = u16::from_be_bytes([bytes[6], bytes[7]]);
1827            write!(
1828                w,
1829                "\"1:{}.{}.{}.{}:{}\"",
1830                bytes[2], bytes[3], bytes[4], bytes[5], val
1831            )
1832        }
1833        2 => {
1834            let asn = u32::from_be_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]);
1835            let val = u16::from_be_bytes([bytes[6], bytes[7]]);
1836            write!(w, "\"2:{}:{}\"", asn, val)
1837        }
1838        _ => {
1839            write!(w, "\"{rd_type}:0x")?;
1840            for b in &bytes[2..] {
1841                write!(w, "{b:02x}")?;
1842            }
1843            write!(w, "\"")
1844        }
1845    }
1846}
1847
1848/// Writes a GTP TEID as a hex string (e.g., `"0x12345678"`).
1849///
1850/// 4-byte big-endian unsigned integer.
1851fn format_teid(
1852    value: &FieldValue<'_>,
1853    _ctx: &FormatContext<'_>,
1854    w: &mut dyn std::io::Write,
1855) -> std::io::Result<()> {
1856    let bytes = match value {
1857        FieldValue::Bytes(b) if b.len() == 4 => *b,
1858        _ => return w.write_all(b"\"\""),
1859    };
1860    let val = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
1861    write!(w, "\"0x{val:08x}\"")
1862}
1863
1864/// Parses MP_REACH_NLRI attribute value.
1865///
1866/// RFC 4760, Section 3 — <https://www.rfc-editor.org/rfc/rfc4760#section-3>
1867fn parse_mp_reach_nlri<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
1868    let afi = read_be_u16(data, 0).unwrap_or_default();
1869    let safi = data[2];
1870    let nh_len = data[3] as usize;
1871
1872    let obj_idx = buf.begin_container(
1873        &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1874        FieldValue::Object(0..0),
1875        offset..offset + data.len(),
1876    );
1877
1878    buf.push_field(
1879        &MP_CHILDREN[FD_MP_AFI],
1880        FieldValue::U16(afi),
1881        offset..offset + 2,
1882    );
1883    buf.push_field(
1884        &MP_CHILDREN[FD_MP_SAFI],
1885        FieldValue::U8(safi),
1886        offset + 2..offset + 3,
1887    );
1888
1889    let nh_start = 4;
1890    let nh_end = nh_start + nh_len;
1891    if nh_end > data.len() {
1892        buf.end_container(obj_idx);
1893        return;
1894    }
1895
1896    // Parse Next Hop based on AFI
1897    let nh_data = &data[nh_start..nh_end];
1898    if afi == 1 && nh_len == 4 {
1899        // IPv4 Next Hop
1900        buf.push_field(
1901            &MP_CHILDREN[FD_MP_NEXT_HOP],
1902            FieldValue::Ipv4Addr([nh_data[0], nh_data[1], nh_data[2], nh_data[3]]),
1903            offset + nh_start..offset + nh_end,
1904        );
1905    } else if afi == 2 && (nh_len == 16 || nh_len == 32) {
1906        // IPv6 Next Hop (16 bytes global, or 32 bytes global + link-local)
1907        let addr = read_ipv6_addr(nh_data, 0).unwrap_or_default();
1908        buf.push_field(
1909            &MP_CHILDREN[FD_MP_NEXT_HOP],
1910            FieldValue::Ipv6Addr(addr),
1911            offset + nh_start..offset + nh_start + 16,
1912        );
1913        if nh_len == 32 {
1914            let ll_addr = read_ipv6_addr(nh_data, 16).unwrap_or_default();
1915            buf.push_field(
1916                &MP_CHILDREN[FD_MP_NEXT_HOP_LINK_LOCAL],
1917                FieldValue::Ipv6Addr(ll_addr),
1918                offset + nh_start + 16..offset + nh_end,
1919            );
1920        }
1921    } else {
1922        buf.push_field(
1923            &MP_CHILDREN[FD_MP_NEXT_HOP],
1924            FieldValue::Bytes(nh_data),
1925            offset + nh_start..offset + nh_end,
1926        );
1927    }
1928
1929    // Skip Reserved byte
1930    let nlri_start = nh_end + 1;
1931    if nlri_start < data.len() {
1932        let nlri_data = &data[nlri_start..];
1933        let is_mup = safi == 85;
1934        let is_ip = afi == 1 || afi == 2;
1935        if is_mup || is_ip {
1936            let array_idx = buf.begin_container(
1937                &MP_CHILDREN[FD_MP_NLRI],
1938                FieldValue::Array(0..0),
1939                offset + nlri_start..offset + data.len(),
1940            );
1941            let before = buf.field_count();
1942            if is_mup {
1943                parse_mup_nlri(buf, nlri_data, offset + nlri_start, afi == 2);
1944            } else {
1945                parse_prefixes(buf, nlri_data, offset + nlri_start, afi == 2);
1946            }
1947            if buf.field_count() == before {
1948                buf.pop_field(); // remove empty array placeholder
1949            } else {
1950                buf.end_container(array_idx);
1951            }
1952        }
1953    }
1954
1955    buf.end_container(obj_idx);
1956}
1957
1958/// Parses MP_UNREACH_NLRI attribute value.
1959///
1960/// RFC 4760, Section 4 — <https://www.rfc-editor.org/rfc/rfc4760#section-4>
1961fn parse_mp_unreach_nlri<'pkt>(buf: &mut DissectBuffer<'pkt>, data: &'pkt [u8], offset: usize) {
1962    let afi = read_be_u16(data, 0).unwrap_or_default();
1963    let safi = data[2];
1964
1965    let obj_idx = buf.begin_container(
1966        &PATH_ATTR_CHILDREN[FD_PA_VALUE],
1967        FieldValue::Object(0..0),
1968        offset..offset + data.len(),
1969    );
1970
1971    buf.push_field(
1972        &MP_CHILDREN[FD_MP_AFI],
1973        FieldValue::U16(afi),
1974        offset..offset + 2,
1975    );
1976    buf.push_field(
1977        &MP_CHILDREN[FD_MP_SAFI],
1978        FieldValue::U8(safi),
1979        offset + 2..offset + 3,
1980    );
1981
1982    let wr_start = 3;
1983    if wr_start < data.len() {
1984        let wr_data = &data[wr_start..];
1985        let is_mup = safi == 85;
1986        let is_ip = afi == 1 || afi == 2;
1987        if is_mup || is_ip {
1988            let array_idx = buf.begin_container(
1989                &MP_CHILDREN[FD_MP_WITHDRAWN_ROUTES],
1990                FieldValue::Array(0..0),
1991                offset + wr_start..offset + data.len(),
1992            );
1993            let before = buf.field_count();
1994            if is_mup {
1995                parse_mup_nlri(buf, wr_data, offset + wr_start, afi == 2);
1996            } else {
1997                parse_prefixes(buf, wr_data, offset + wr_start, afi == 2);
1998            }
1999            if buf.field_count() == before {
2000                buf.pop_field(); // remove empty array placeholder
2001            } else {
2002                buf.end_container(array_idx);
2003            }
2004        }
2005    }
2006
2007    buf.end_container(obj_idx);
2008}
2009
2010/// Parses UPDATE message body and appends fields.
2011///
2012/// RFC 4271, Section 4.3 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.3>
2013fn parse_update<'pkt>(
2014    buf: &mut DissectBuffer<'pkt>,
2015    data: &'pkt [u8],
2016    offset: usize,
2017) -> Result<(), PacketError> {
2018    if data.len() < MIN_UPDATE_SIZE {
2019        return Err(PacketError::Truncated {
2020            expected: MIN_UPDATE_SIZE,
2021            actual: data.len(),
2022        });
2023    }
2024
2025    let withdrawn_len = read_be_u16(data, 19)? as usize;
2026    buf.push_field(
2027        &FIELD_DESCRIPTORS[FD_WITHDRAWN_ROUTES_LENGTH],
2028        FieldValue::U16(withdrawn_len as u16),
2029        offset + 19..offset + 21,
2030    );
2031
2032    let wr_start = 21;
2033    let wr_end = wr_start + withdrawn_len;
2034
2035    if data.len() < wr_end + 2 {
2036        return Err(PacketError::Truncated {
2037            expected: wr_end + 2,
2038            actual: data.len(),
2039        });
2040    }
2041
2042    // Parse withdrawn routes
2043    if withdrawn_len > 0 {
2044        let array_idx = buf.begin_container(
2045            &FIELD_DESCRIPTORS[FD_WITHDRAWN_ROUTES],
2046            FieldValue::Array(0..0),
2047            offset + wr_start..offset + wr_end,
2048        );
2049        let before = buf.field_count();
2050        parse_prefixes(buf, &data[wr_start..wr_end], offset + wr_start, false);
2051        if buf.field_count() == before {
2052            buf.pop_field();
2053        } else {
2054            buf.end_container(array_idx);
2055        }
2056    }
2057
2058    let path_attr_len = read_be_u16(data, wr_end)? as usize;
2059    buf.push_field(
2060        &FIELD_DESCRIPTORS[FD_TOTAL_PATH_ATTRIBUTE_LENGTH],
2061        FieldValue::U16(path_attr_len as u16),
2062        offset + wr_end..offset + wr_end + 2,
2063    );
2064
2065    let pa_start = wr_end + 2;
2066    let pa_end = pa_start + path_attr_len;
2067
2068    if data.len() < pa_end {
2069        return Err(PacketError::Truncated {
2070            expected: pa_end,
2071            actual: data.len(),
2072        });
2073    }
2074
2075    // Parse path attributes
2076    if path_attr_len > 0 {
2077        let array_idx = buf.begin_container(
2078            &FIELD_DESCRIPTORS[FD_PATH_ATTRIBUTES],
2079            FieldValue::Array(0..0),
2080            offset + pa_start..offset + pa_end,
2081        );
2082        let before = buf.field_count();
2083        let mut pos = 0;
2084        let attr_data = &data[pa_start..pa_end];
2085        while pos < attr_data.len() {
2086            if let Some(consumed) =
2087                parse_path_attribute(buf, &attr_data[pos..], offset + pa_start + pos)
2088            {
2089                pos += consumed;
2090            } else {
2091                break;
2092            }
2093        }
2094        if buf.field_count() == before {
2095            buf.pop_field();
2096        } else {
2097            buf.end_container(array_idx);
2098        }
2099    }
2100
2101    // Parse NLRI (remaining bytes after path attributes)
2102    let nlri_start = pa_end;
2103    let nlri_end = data.len();
2104    if nlri_start < nlri_end {
2105        let array_idx = buf.begin_container(
2106            &FIELD_DESCRIPTORS[FD_NLRI],
2107            FieldValue::Array(0..0),
2108            offset + nlri_start..offset + nlri_end,
2109        );
2110        let before = buf.field_count();
2111        parse_prefixes(buf, &data[nlri_start..nlri_end], offset + nlri_start, false);
2112        if buf.field_count() == before {
2113            buf.pop_field();
2114        } else {
2115            buf.end_container(array_idx);
2116        }
2117    }
2118
2119    Ok(())
2120}
2121
2122/// Object descriptor for capability entries inside `optional_parameters`.
2123static OPT_PARAM_OBJECT_DESCRIPTOR: FieldDescriptor =
2124    FieldDescriptor::new("capability", "Capability", FieldType::Object)
2125        .with_children(OPT_PARAM_CHILDREN);
2126
2127/// Object descriptor for path attribute entries inside `path_attributes`.
2128static PATH_ATTR_OBJECT_DESCRIPTOR: FieldDescriptor =
2129    FieldDescriptor::new("path_attribute", "Path Attribute", FieldType::Object)
2130        .with_children(PATH_ATTR_CHILDREN);
2131
2132/// Descriptor for IPv4 prefix entries with CIDR format (e.g., `"192.168.1.0/24"`).
2133///
2134/// Raw bytes: `[prefix_len_bits, prefix_octets...]` per RFC 4271, Section 4.3.
2135static PREFIX_ENTRY_IPV4_DESCRIPTOR: FieldDescriptor =
2136    FieldDescriptor::new("prefix", "Prefix", FieldType::Bytes)
2137        .with_format_fn(format_nlri_ipv4_prefix);
2138
2139/// Descriptor for IPv6 prefix entries with CIDR format (e.g., `"2001:db8::/32"`).
2140///
2141/// Raw bytes: `[prefix_len_bits, prefix_octets...]` per RFC 4760, Section 3.
2142static PREFIX_ENTRY_IPV6_DESCRIPTOR: FieldDescriptor =
2143    FieldDescriptor::new("prefix", "Prefix", FieldType::Bytes)
2144        .with_format_fn(format_nlri_ipv6_prefix);
2145
2146/// Object descriptor for AS_PATH segment entries.
2147static AS_PATH_SEG_OBJECT_DESCRIPTOR: FieldDescriptor =
2148    FieldDescriptor::new("segment", "Segment", FieldType::Object)
2149        .with_children(AS_PATH_SEG_CHILDREN);
2150
2151/// Descriptor for AS number entries inside AS_PATH segments.
2152static AS_NUMBER_DESCRIPTOR: FieldDescriptor =
2153    FieldDescriptor::new("asn", "AS Number", FieldType::U32);
2154
2155/// Descriptor for community entries (U32 raw value).
2156static COMMUNITY_ENTRY_DESCRIPTOR: FieldDescriptor =
2157    FieldDescriptor::new("community", "Community", FieldType::U32).with_display_fn(
2158        |v, _| match v {
2159            FieldValue::U32(c) => well_known_community_name(*c),
2160            _ => None,
2161        },
2162    );
2163
2164/// Descriptor for cluster ID entries.
2165static CLUSTER_ID_DESCRIPTOR: FieldDescriptor =
2166    FieldDescriptor::new("cluster_id", "Cluster ID", FieldType::Ipv4Addr);
2167
2168/// Descriptor for extended community entries (raw 8 bytes).
2169static EXT_COMMUNITY_ENTRY_DESCRIPTOR: FieldDescriptor =
2170    FieldDescriptor::new("ext_community", "Extended Community", FieldType::Bytes)
2171        .with_display_fn(|v, _| match v {
2172            FieldValue::Bytes(b) if b.len() >= 2 => extended_community_type_name(b[0], b[1]),
2173            _ => None,
2174        })
2175        .with_format_fn(format_ext_community);
2176
2177/// Descriptor for large community entries (raw 12 bytes).
2178static LARGE_COMMUNITY_ENTRY_DESCRIPTOR: FieldDescriptor =
2179    FieldDescriptor::new("large_community", "Large Community", FieldType::Bytes)
2180        .with_format_fn(format_large_community);
2181
2182/// Object descriptor for MUP NLRI entries.
2183static MUP_NLRI_OBJECT_DESCRIPTOR: FieldDescriptor =
2184    FieldDescriptor::new("mup_entry", "MUP Entry", FieldType::Object)
2185        .with_children(MUP_NLRI_CHILDREN);
2186
2187/// Object descriptor for Prefix-SID TLV entries.
2188static PREFIX_SID_TLV_OBJECT_DESCRIPTOR: FieldDescriptor =
2189    FieldDescriptor::new("tlv", "TLV", FieldType::Object).with_children(PREFIX_SID_TLV_CHILDREN);
2190
2191/// Object descriptor for SRGB entries.
2192static SRGB_ENTRY_OBJECT_DESCRIPTOR: FieldDescriptor =
2193    FieldDescriptor::new("srgb_entry", "SRGB Entry", FieldType::Object)
2194        .with_children(SRGB_ENTRY_CHILDREN);
2195
2196/// Object descriptor for SRv6 SID Information Sub-TLV entries.
2197static SRV6_SID_INFO_OBJECT_DESCRIPTOR: FieldDescriptor =
2198    FieldDescriptor::new("sub_tlv", "Sub-TLV", FieldType::Object)
2199        .with_children(SRV6_SID_INFO_CHILDREN);
2200
2201/// Field descriptor indices for [`NON_CAP_PARAM_CHILDREN`].
2202const FD_NCP_PARAM_TYPE: usize = 0;
2203const FD_NCP_VALUE: usize = 1;
2204
2205/// Child field descriptors for non-capability optional parameter objects.
2206static NON_CAP_PARAM_CHILDREN: &[FieldDescriptor] = &[
2207    FieldDescriptor::new("param_type", "Parameter Type", FieldType::U8),
2208    FieldDescriptor::new("value", "Value", FieldType::Bytes),
2209];
2210
2211/// Field descriptor indices for [`AS_PATH_SEG_CHILDREN`].
2212const FD_APS_SEGMENT_TYPE: usize = 0;
2213const FD_APS_AS_NUMBERS: usize = 1;
2214
2215/// Child field descriptors for AS_PATH segment objects.
2216static AS_PATH_SEG_CHILDREN: &[FieldDescriptor] = &[
2217    FieldDescriptor {
2218        name: "segment_type",
2219        display_name: "Segment Type",
2220        field_type: FieldType::U8,
2221        optional: false,
2222        children: None,
2223        display_fn: Some(|v, _siblings| match v {
2224            FieldValue::U8(t) => as_path_segment_type_name(*t),
2225            _ => None,
2226        }),
2227        format_fn: None,
2228    },
2229    FieldDescriptor::new("as_numbers", "AS Numbers", FieldType::Array),
2230];
2231
2232/// Field descriptor indices for [`MUP_NLRI_CHILDREN`].
2233const FD_MUP_ARCH_TYPE: usize = 0;
2234const FD_MUP_ROUTE_TYPE: usize = 1;
2235const FD_MUP_VALUE: usize = 2;
2236const FD_MUP_RD: usize = 3;
2237const FD_MUP_ADDRESS: usize = 5;
2238const FD_MUP_TEID: usize = 6;
2239const FD_MUP_QFI: usize = 7;
2240const FD_MUP_ENDPOINT_ADDRESS: usize = 8;
2241const FD_MUP_SOURCE_ADDRESS: usize = 9;
2242
2243/// Child field descriptors for MUP NLRI entry objects.
2244static MUP_NLRI_CHILDREN: &[FieldDescriptor] = &[
2245    FieldDescriptor {
2246        name: "architecture_type",
2247        display_name: "Architecture Type",
2248        field_type: FieldType::U8,
2249        optional: false,
2250        children: None,
2251        display_fn: Some(|v, _siblings| match v {
2252            FieldValue::U8(a) => mup_architecture_type_name(*a),
2253            _ => None,
2254        }),
2255        format_fn: None,
2256    },
2257    FieldDescriptor {
2258        name: "route_type",
2259        display_name: "Route Type",
2260        field_type: FieldType::U16,
2261        optional: false,
2262        children: None,
2263        display_fn: Some(|v, _siblings| match v {
2264            FieldValue::U16(r) => mup_route_type_name(*r),
2265            _ => None,
2266        }),
2267        format_fn: None,
2268    },
2269    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
2270    FieldDescriptor::new("rd", "Route Distinguisher", FieldType::Bytes)
2271        .optional()
2272        .with_format_fn(format_route_distinguisher),
2273    FieldDescriptor::new("prefix", "Prefix", FieldType::Bytes).optional(),
2274    FieldDescriptor::new("address", "Address", FieldType::Bytes).optional(),
2275    FieldDescriptor::new("teid", "TEID", FieldType::Bytes)
2276        .optional()
2277        .with_format_fn(format_teid),
2278    FieldDescriptor::new("qfi", "QFI", FieldType::U8).optional(),
2279    FieldDescriptor::new("endpoint_address", "Endpoint Address", FieldType::Bytes).optional(),
2280    FieldDescriptor::new("source_address", "Source Address", FieldType::Bytes).optional(),
2281];
2282
2283/// Field descriptor indices for [`PREFIX_SID_TLV_CHILDREN`].
2284const FD_PSID_TYPE: usize = 0;
2285const FD_PSID_LENGTH: usize = 1;
2286const FD_PSID_FLAGS: usize = 2;
2287const FD_PSID_LABEL_INDEX: usize = 3;
2288const FD_PSID_SRGB_ENTRIES: usize = 4;
2289const FD_PSID_SUB_TLVS: usize = 5;
2290const FD_PSID_VALUE: usize = 6;
2291
2292/// Child field descriptors for TLVs inside BGP Prefix-SID attribute.
2293///
2294/// RFC 8669 — <https://www.rfc-editor.org/rfc/rfc8669>
2295/// RFC 9252 — <https://www.rfc-editor.org/rfc/rfc9252>
2296static PREFIX_SID_TLV_CHILDREN: &[FieldDescriptor] = &[
2297    FieldDescriptor::new("type", "Type", FieldType::U8),
2298    FieldDescriptor::new("length", "Length", FieldType::U16),
2299    FieldDescriptor::new("flags", "Flags", FieldType::U16).optional(),
2300    FieldDescriptor::new("label_index", "Label Index", FieldType::U32).optional(),
2301    FieldDescriptor::new("srgb_entries", "SRGB Entries", FieldType::Array)
2302        .optional()
2303        .with_children(SRGB_ENTRY_CHILDREN),
2304    FieldDescriptor::new("sub_tlvs", "Sub-TLVs", FieldType::Array)
2305        .optional()
2306        .with_children(SRV6_SID_INFO_CHILDREN),
2307    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
2308];
2309
2310/// Field descriptor indices for [`SRGB_ENTRY_CHILDREN`].
2311const FD_SRGB_BASE: usize = 0;
2312const FD_SRGB_RANGE: usize = 1;
2313
2314/// Child field descriptors for SRGB entries in Originator SRGB TLV.
2315///
2316/// RFC 8669, Section 3.2 — <https://www.rfc-editor.org/rfc/rfc8669#section-3.2>
2317static SRGB_ENTRY_CHILDREN: &[FieldDescriptor] = &[
2318    FieldDescriptor::new("base", "SRGB Base", FieldType::U32),
2319    FieldDescriptor::new("range", "SRGB Range", FieldType::U32),
2320];
2321
2322/// Field descriptor indices for [`SRV6_SID_INFO_CHILDREN`].
2323const FD_SRV6_SI_TYPE: usize = 0;
2324const FD_SRV6_SI_LENGTH: usize = 1;
2325const FD_SRV6_SI_SID: usize = 2;
2326const FD_SRV6_SI_FLAGS: usize = 3;
2327const FD_SRV6_SI_ENDPOINT_BEHAVIOR: usize = 4;
2328const FD_SRV6_SI_SID_STRUCTURE: usize = 5;
2329const FD_SRV6_SI_VALUE: usize = 6;
2330
2331/// Child field descriptors for SRv6 SID Information Sub-TLV.
2332///
2333/// RFC 9252, Section 3.1 — <https://www.rfc-editor.org/rfc/rfc9252#section-3.1>
2334static SRV6_SID_INFO_CHILDREN: &[FieldDescriptor] = &[
2335    FieldDescriptor::new("type", "Type", FieldType::U8),
2336    FieldDescriptor::new("length", "Length", FieldType::U16),
2337    FieldDescriptor::new("srv6_sid", "SRv6 SID", FieldType::Ipv6Addr).optional(),
2338    FieldDescriptor::new("sid_flags", "Service SID Flags", FieldType::U8).optional(),
2339    FieldDescriptor::new("endpoint_behavior", "Endpoint Behavior", FieldType::U16).optional(),
2340    FieldDescriptor::new("sid_structure", "SID Structure", FieldType::Object)
2341        .optional()
2342        .with_children(SRV6_SID_STRUCTURE_CHILDREN),
2343    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
2344];
2345
2346/// Field descriptor indices for [`SRV6_SID_STRUCTURE_CHILDREN`].
2347const FD_SRV6_SS_LBL: usize = 0;
2348const FD_SRV6_SS_LNL: usize = 1;
2349const FD_SRV6_SS_FL: usize = 2;
2350const FD_SRV6_SS_AL: usize = 3;
2351const FD_SRV6_SS_TL: usize = 4;
2352const FD_SRV6_SS_TO: usize = 5;
2353
2354/// Child field descriptors for SRv6 SID Structure Sub-Sub-TLV.
2355///
2356/// RFC 9252, Section 3.2.1 — <https://www.rfc-editor.org/rfc/rfc9252#section-3.2.1>
2357static SRV6_SID_STRUCTURE_CHILDREN: &[FieldDescriptor] = &[
2358    FieldDescriptor::new(
2359        "locator_block_length",
2360        "Locator Block Length",
2361        FieldType::U8,
2362    ),
2363    FieldDescriptor::new("locator_node_length", "Locator Node Length", FieldType::U8),
2364    FieldDescriptor::new("function_length", "Function Length", FieldType::U8),
2365    FieldDescriptor::new("argument_length", "Argument Length", FieldType::U8),
2366    FieldDescriptor::new(
2367        "transposition_length",
2368        "Transposition Length",
2369        FieldType::U8,
2370    ),
2371    FieldDescriptor::new(
2372        "transposition_offset",
2373        "Transposition Offset",
2374        FieldType::U8,
2375    ),
2376];
2377
2378/// Field descriptor indices for [`MP_CHILDREN`].
2379const FD_MP_AFI: usize = 0;
2380const FD_MP_SAFI: usize = 1;
2381const FD_MP_NEXT_HOP: usize = 2;
2382const FD_MP_NEXT_HOP_LINK_LOCAL: usize = 3;
2383const FD_MP_NLRI: usize = 4;
2384const FD_MP_WITHDRAWN_ROUTES: usize = 5;
2385
2386/// Child field descriptors for MP_REACH_NLRI / MP_UNREACH_NLRI objects.
2387static MP_CHILDREN: &[FieldDescriptor] = &[
2388    FieldDescriptor::new("afi", "AFI", FieldType::U16),
2389    FieldDescriptor::new("safi", "SAFI", FieldType::U8),
2390    FieldDescriptor::new("next_hop", "Next Hop", FieldType::Bytes).optional(),
2391    FieldDescriptor::new(
2392        "next_hop_link_local",
2393        "Next Hop Link-Local",
2394        FieldType::Bytes,
2395    )
2396    .optional(),
2397    FieldDescriptor::new("nlri", "NLRI", FieldType::Array).optional(),
2398    FieldDescriptor::new("withdrawn_routes", "Withdrawn Routes", FieldType::Array).optional(),
2399];
2400
2401/// Field descriptor indices for [`OPT_PARAM_CHILDREN`].
2402const FD_OPT_CODE: usize = 0;
2403const FD_OPT_LENGTH: usize = 1;
2404const FD_OPT_VALUE: usize = 2;
2405
2406/// Child field descriptors for capability objects inside `optional_parameters`.
2407static OPT_PARAM_CHILDREN: &[FieldDescriptor] = &[
2408    FieldDescriptor::new("code", "Code", FieldType::U8),
2409    FieldDescriptor::new("length", "Length", FieldType::U8),
2410    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
2411];
2412
2413/// Field descriptor indices for [`PATH_ATTR_CHILDREN`].
2414const FD_PA_FLAGS: usize = 0;
2415const FD_PA_TYPE_CODE: usize = 1;
2416const FD_PA_ATTR_LENGTH: usize = 2;
2417const FD_PA_VALUE: usize = 3;
2418
2419/// ORIGIN "value" field descriptor with display_fn for IGP/EGP/INCOMPLETE.
2420static FD_ORIGIN_VALUE: FieldDescriptor = FieldDescriptor::new("value", "Value", FieldType::U8)
2421    .with_display_fn(|v, _| match v {
2422        FieldValue::U8(o) => origin_name(*o),
2423        _ => None,
2424    });
2425
2426/// AGGREGATOR "value" field descriptor with format_fn for `"<AS> <IPv4>"`.
2427///
2428/// RFC 4271, Section 5.1.7 — <https://www.rfc-editor.org/rfc/rfc4271#section-5.1.7>
2429static FD_AGGREGATOR_VALUE: FieldDescriptor =
2430    FieldDescriptor::new("value", "Value", FieldType::Bytes)
2431        .optional()
2432        .with_format_fn(format_aggregator);
2433
2434/// AS4_AGGREGATOR "value" field descriptor with format_fn for `"<AS> <IPv4>"`.
2435///
2436/// RFC 6793, Section 7 — <https://www.rfc-editor.org/rfc/rfc6793#section-7>
2437static FD_AS4_AGGREGATOR_VALUE: FieldDescriptor =
2438    FieldDescriptor::new("value", "Value", FieldType::Bytes)
2439        .optional()
2440        .with_format_fn(format_aggregator);
2441
2442/// Child field descriptors for objects inside `path_attributes`.
2443static PATH_ATTR_CHILDREN: &[FieldDescriptor] = &[
2444    FieldDescriptor::new("flags", "Flags", FieldType::U8),
2445    FieldDescriptor {
2446        name: "type_code",
2447        display_name: "Type Code",
2448        field_type: FieldType::U8,
2449        optional: false,
2450        children: None,
2451        display_fn: Some(|v, _siblings| match v {
2452            FieldValue::U8(t) => path_attr_type_name(*t),
2453            _ => None,
2454        }),
2455        format_fn: None,
2456    },
2457    FieldDescriptor::new("attr_length", "Attribute Length", FieldType::U16),
2458    FieldDescriptor::new("value", "Value", FieldType::Bytes).optional(),
2459];
2460
2461/// Field descriptor indices for [`FIELD_DESCRIPTORS`].
2462const FD_MARKER: usize = 0;
2463const FD_LENGTH: usize = 1;
2464const FD_TYPE: usize = 2;
2465// OPEN fields (RFC 4271, Section 4.2; RFC 9072)
2466const FD_VERSION: usize = 3;
2467const FD_MY_AS: usize = 4;
2468const FD_HOLD_TIME: usize = 5;
2469const FD_BGP_IDENTIFIER: usize = 6;
2470const FD_OPT_PARAMS_LENGTH: usize = 7;
2471const FD_EXT_OPT_PARAMS_LENGTH: usize = 8;
2472const FD_OPTIONAL_PARAMETERS: usize = 9;
2473// NOTIFICATION fields (RFC 4271, Section 4.5)
2474const FD_ERROR_CODE: usize = 10;
2475const FD_ERROR_SUBCODE: usize = 11;
2476const FD_DATA: usize = 12;
2477// ROUTE-REFRESH fields (RFC 2918, RFC 7313)
2478const FD_AFI: usize = 13;
2479const FD_SAFI: usize = 14;
2480const FD_MESSAGE_SUBTYPE: usize = 15;
2481// UPDATE fields (RFC 4271, Section 4.3)
2482const FD_WITHDRAWN_ROUTES_LENGTH: usize = 16;
2483const FD_WITHDRAWN_ROUTES: usize = 17;
2484const FD_TOTAL_PATH_ATTRIBUTE_LENGTH: usize = 18;
2485const FD_PATH_ATTRIBUTES: usize = 19;
2486const FD_NLRI: usize = 20;
2487
2488/// Field descriptors for the BGP dissector.
2489static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
2490    // Header fields (RFC 4271, Section 4.1)
2491    FieldDescriptor::new("marker", "Marker", FieldType::Bytes),
2492    FieldDescriptor::new("length", "Length", FieldType::U16),
2493    FieldDescriptor {
2494        name: "type",
2495        display_name: "Type",
2496        field_type: FieldType::U8,
2497        optional: false,
2498        children: None,
2499        display_fn: Some(|v, _siblings| match v {
2500            FieldValue::U8(t) => msg_type_name(*t),
2501            _ => None,
2502        }),
2503        format_fn: None,
2504    },
2505    // OPEN fields (RFC 4271, Section 4.2)
2506    FieldDescriptor::new("version", "Version", FieldType::U8).optional(),
2507    FieldDescriptor::new("my_as", "My AS", FieldType::U16).optional(),
2508    FieldDescriptor::new("hold_time", "Hold Time", FieldType::U16).optional(),
2509    FieldDescriptor::new("bgp_identifier", "BGP Identifier", FieldType::Ipv4Addr).optional(),
2510    FieldDescriptor::new(
2511        "opt_params_length",
2512        "Optional Parameters Length",
2513        FieldType::U8,
2514    )
2515    .optional(),
2516    // RFC 9072 — Extended Optional Parameters Length (2 octets, only when
2517    // byte 29 of the OPEN message equals the 0xFF sentinel).
2518    FieldDescriptor::new(
2519        "ext_opt_params_length",
2520        "Extended Optional Parameters Length",
2521        FieldType::U16,
2522    )
2523    .optional(),
2524    FieldDescriptor::new(
2525        "optional_parameters",
2526        "Optional Parameters",
2527        FieldType::Array,
2528    )
2529    .optional()
2530    .with_children(OPT_PARAM_CHILDREN),
2531    // NOTIFICATION fields (RFC 4271, Section 4.5)
2532    FieldDescriptor {
2533        name: "error_code",
2534        display_name: "Error Code",
2535        field_type: FieldType::U8,
2536        optional: true,
2537        children: None,
2538        display_fn: Some(|v, _siblings| match v {
2539            FieldValue::U8(c) => error_code_name(*c),
2540            _ => None,
2541        }),
2542        format_fn: None,
2543    },
2544    // RFC 4486 / RFC 8203 — Cease subcode names. The lookup is conditional on
2545    // the sibling `error_code` being 6 (Cease); other error codes have their
2546    // own subcode tables that are not currently decoded.
2547    FieldDescriptor {
2548        name: "error_subcode",
2549        display_name: "Error Subcode",
2550        field_type: FieldType::U8,
2551        optional: true,
2552        children: None,
2553        display_fn: Some(|v, siblings| {
2554            let FieldValue::U8(subcode) = v else {
2555                return None;
2556            };
2557            let error_code = siblings
2558                .iter()
2559                .find(|f| f.name() == "error_code")
2560                .and_then(|f| f.value.as_u8())?;
2561            if error_code == 6 {
2562                cease_subcode_name(*subcode)
2563            } else {
2564                None
2565            }
2566        }),
2567        format_fn: None,
2568    },
2569    FieldDescriptor::new("data", "Data", FieldType::Bytes).optional(),
2570    // ROUTE-REFRESH fields (RFC 2918)
2571    FieldDescriptor {
2572        name: "afi",
2573        display_name: "AFI",
2574        field_type: FieldType::U16,
2575        optional: true,
2576        children: None,
2577        display_fn: Some(|v, _siblings| match v {
2578            FieldValue::U16(a) => afi_name(*a),
2579            _ => None,
2580        }),
2581        format_fn: None,
2582    },
2583    FieldDescriptor {
2584        name: "safi",
2585        display_name: "SAFI",
2586        field_type: FieldType::U8,
2587        optional: true,
2588        children: None,
2589        display_fn: Some(|v, _siblings| match v {
2590            FieldValue::U8(s) => safi_name(*s),
2591            _ => None,
2592        }),
2593        format_fn: None,
2594    },
2595    // RFC 7313, Section 4 — Enhanced Route Refresh Message Subtype
2596    FieldDescriptor {
2597        name: "message_subtype",
2598        display_name: "Message Subtype",
2599        field_type: FieldType::U8,
2600        optional: true,
2601        children: None,
2602        display_fn: Some(|v, _siblings| match v {
2603            FieldValue::U8(s) => route_refresh_subtype_name(*s),
2604            _ => None,
2605        }),
2606        format_fn: None,
2607    },
2608    // UPDATE fields (RFC 4271, Section 4.3)
2609    FieldDescriptor::new(
2610        "withdrawn_routes_length",
2611        "Withdrawn Routes Length",
2612        FieldType::U16,
2613    )
2614    .optional(),
2615    FieldDescriptor::new("withdrawn_routes", "Withdrawn Routes", FieldType::Array).optional(),
2616    FieldDescriptor::new(
2617        "total_path_attribute_length",
2618        "Total Path Attribute Length",
2619        FieldType::U16,
2620    )
2621    .optional(),
2622    FieldDescriptor::new("path_attributes", "Path Attributes", FieldType::Array)
2623        .optional()
2624        .with_children(PATH_ATTR_CHILDREN),
2625    FieldDescriptor::new("nlri", "NLRI", FieldType::Array).optional(),
2626];
2627
2628/// Parses a single BGP message from the start of `data` and appends one layer.
2629/// Returns the number of bytes consumed.
2630///
2631/// RFC 4271, Section 4.1 — <https://www.rfc-editor.org/rfc/rfc4271#section-4.1>
2632fn dissect_one_message<'pkt>(
2633    data: &'pkt [u8],
2634    buf: &mut DissectBuffer<'pkt>,
2635    offset: usize,
2636) -> Result<usize, PacketError> {
2637    if data.len() < HEADER_SIZE {
2638        return Err(PacketError::Truncated {
2639            expected: HEADER_SIZE,
2640            actual: data.len(),
2641        });
2642    }
2643
2644    // Validate marker (RFC 4271, Section 4.1).
2645    if data[..16] != MARKER {
2646        return Err(PacketError::InvalidHeader("BGP marker must be all 0xFF"));
2647    }
2648
2649    let length = read_be_u16(data, 16)?;
2650
2651    // RFC 4271, Section 4.1: Length must be >= 19 (header size) and <= 4096
2652    // (or 65535 with Extended Message, RFC 8654).
2653    if (length as usize) < HEADER_SIZE {
2654        return Err(PacketError::InvalidFieldValue {
2655            field: "length",
2656            value: length as u32,
2657        });
2658    }
2659    if length as usize > data.len() {
2660        return Err(PacketError::Truncated {
2661            expected: length as usize,
2662            actual: data.len(),
2663        });
2664    }
2665
2666    let msg_type = data[18];
2667    let msg_len = length as usize;
2668    let msg_data = &data[..msg_len];
2669    let consumed = msg_data.len();
2670
2671    buf.begin_layer("BGP", None, FIELD_DESCRIPTORS, offset..offset + consumed);
2672
2673    buf.push_field(
2674        &FIELD_DESCRIPTORS[FD_MARKER],
2675        FieldValue::Bytes(&data[..16]),
2676        offset..offset + 16,
2677    );
2678    buf.push_field(
2679        &FIELD_DESCRIPTORS[FD_LENGTH],
2680        FieldValue::U16(length),
2681        offset + 16..offset + 18,
2682    );
2683    buf.push_field(
2684        &FIELD_DESCRIPTORS[FD_TYPE],
2685        FieldValue::U8(msg_type),
2686        offset + 18..offset + 19,
2687    );
2688
2689    match msg_type {
2690        MSG_OPEN => parse_open(buf, msg_data, offset)?,
2691        MSG_UPDATE => parse_update(buf, msg_data, offset)?,
2692        MSG_NOTIFICATION => parse_notification(buf, msg_data, offset)?,
2693        MSG_ROUTE_REFRESH => parse_route_refresh(buf, msg_data, offset)?,
2694        _ => {}
2695    }
2696
2697    buf.end_layer();
2698
2699    Ok(consumed)
2700}
2701
2702/// BGP-4 dissector.
2703pub struct BgpDissector;
2704
2705impl Dissector for BgpDissector {
2706    fn name(&self) -> &'static str {
2707        "Border Gateway Protocol"
2708    }
2709
2710    fn short_name(&self) -> &'static str {
2711        "BGP"
2712    }
2713
2714    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
2715        FIELD_DESCRIPTORS
2716    }
2717
2718    fn dissect<'pkt>(
2719        &self,
2720        data: &'pkt [u8],
2721        buf: &mut DissectBuffer<'pkt>,
2722        offset: usize,
2723    ) -> Result<DissectResult, PacketError> {
2724        let mut pos = 0;
2725
2726        // A single TCP segment may carry multiple BGP messages back-to-back.
2727        // Parse each one as a separate BGP layer.
2728        while pos + HEADER_SIZE <= data.len() {
2729            let consumed = dissect_one_message(&data[pos..], buf, offset + pos)?;
2730            pos += consumed;
2731        }
2732
2733        if pos == 0 {
2734            return Err(PacketError::Truncated {
2735                expected: HEADER_SIZE,
2736                actual: data.len(),
2737            });
2738        }
2739
2740        Ok(DissectResult::new(pos, DispatchHint::End))
2741    }
2742}
2743
2744#[cfg(test)]
2745mod tests {
2746    use super::*;
2747    use packet_dissector_core::field::Field;
2748
2749    fn nested_field_by_name<'a, 'pkt>(
2750        buf: &'a DissectBuffer<'pkt>,
2751        range: &core::ops::Range<u32>,
2752        name: &str,
2753    ) -> &'a Field<'pkt> {
2754        buf.nested_fields(range)
2755            .iter()
2756            .find(|f| f.name() == name)
2757            .unwrap_or_else(|| panic!("field '{}' not found", name))
2758    }
2759
2760    /// Build a minimal BGP KEEPALIVE message (19 bytes).
2761    fn build_keepalive() -> Vec<u8> {
2762        let mut raw = vec![0xFF; 16]; // Marker
2763        raw.extend_from_slice(&19u16.to_be_bytes()); // Length
2764        raw.push(4); // Type = KEEPALIVE
2765        raw
2766    }
2767
2768    #[test]
2769    fn parse_bgp_keepalive() {
2770        let data = build_keepalive();
2771        let mut buf = DissectBuffer::new();
2772        let result = BgpDissector.dissect(&data, &mut buf, 0).unwrap();
2773
2774        assert_eq!(result.bytes_consumed, 19);
2775        assert!(matches!(result.next, DispatchHint::End));
2776        assert_eq!(buf.layers().len(), 1);
2777
2778        let layer = &buf.layers()[0];
2779        assert_eq!(layer.name, "BGP");
2780        assert_eq!(
2781            buf.field_by_name(layer, "length").unwrap().value,
2782            FieldValue::U16(19)
2783        );
2784        assert_eq!(
2785            buf.field_by_name(layer, "type").unwrap().value,
2786            FieldValue::U8(4)
2787        );
2788        assert_eq!(
2789            buf.resolve_display_name(layer, "type_name"),
2790            Some("KEEPALIVE")
2791        );
2792    }
2793
2794    /// Build a BGP OPEN message with no optional parameters.
2795    fn build_open_basic() -> Vec<u8> {
2796        let mut raw = vec![0xFF; 16]; // Marker
2797        raw.extend_from_slice(&29u16.to_be_bytes()); // Length = 29 (minimum OPEN)
2798        raw.push(1); // Type = OPEN
2799        raw.push(4); // Version = 4
2800        raw.extend_from_slice(&65001u16.to_be_bytes()); // My AS = 65001
2801        raw.extend_from_slice(&180u16.to_be_bytes()); // Hold Time = 180
2802        raw.extend_from_slice(&[10, 0, 0, 1]); // BGP Identifier = 10.0.0.1
2803        raw.push(0); // Opt Params Len = 0
2804        raw
2805    }
2806
2807    /// Build a BGP OPEN message with capabilities.
2808    fn build_open_with_capabilities() -> Vec<u8> {
2809        // Capabilities: Multiprotocol Extensions (IPv4 Unicast) + 4-octet AS (65550)
2810        let cap_mp = [1, 4, 0, 1, 0, 1]; // code=1, len=4, AFI=1, res=0, SAFI=1
2811        let as4_bytes = 65582u32.to_be_bytes();
2812        let cap_as4 = [
2813            65,
2814            4,
2815            as4_bytes[0],
2816            as4_bytes[1],
2817            as4_bytes[2],
2818            as4_bytes[3],
2819        ];
2820
2821        // Capability parameter: type=2, length=sum of capabilities
2822        let cap_param_len = cap_mp.len() + cap_as4.len();
2823        let opt_params_len = 2 + cap_param_len; // type(1) + len(1) + caps
2824
2825        let total_len = 29 + opt_params_len;
2826        let mut raw = vec![0xFF; 16];
2827        raw.extend_from_slice(&(total_len as u16).to_be_bytes());
2828        raw.push(1); // Type = OPEN
2829        raw.push(4); // Version
2830        raw.extend_from_slice(&65001u16.to_be_bytes()); // My AS
2831        raw.extend_from_slice(&180u16.to_be_bytes()); // Hold Time
2832        raw.extend_from_slice(&[10, 0, 0, 1]); // BGP Identifier
2833        raw.push(opt_params_len as u8); // Opt Params Len
2834        raw.push(2); // Param Type = Capability
2835        raw.push(cap_param_len as u8);
2836        raw.extend_from_slice(&cap_mp);
2837        raw.extend_from_slice(&cap_as4);
2838        raw
2839    }
2840
2841    #[test]
2842    fn parse_bgp_open_basic() {
2843        let data = build_open_basic();
2844        let mut buf = DissectBuffer::new();
2845        let result = BgpDissector.dissect(&data, &mut buf, 0).unwrap();
2846
2847        assert_eq!(result.bytes_consumed, 29);
2848        let layer = &buf.layers()[0];
2849        assert_eq!(buf.resolve_display_name(layer, "type_name"), Some("OPEN"));
2850        assert_eq!(
2851            buf.field_by_name(layer, "version").unwrap().value,
2852            FieldValue::U8(4)
2853        );
2854        assert_eq!(
2855            buf.field_by_name(layer, "my_as").unwrap().value,
2856            FieldValue::U16(65001)
2857        );
2858        assert_eq!(
2859            buf.field_by_name(layer, "hold_time").unwrap().value,
2860            FieldValue::U16(180)
2861        );
2862        assert_eq!(
2863            buf.field_by_name(layer, "bgp_identifier").unwrap().value,
2864            FieldValue::Ipv4Addr([10, 0, 0, 1])
2865        );
2866        assert_eq!(
2867            buf.field_by_name(layer, "opt_params_length").unwrap().value,
2868            FieldValue::U8(0)
2869        );
2870        assert!(buf.field_by_name(layer, "optional_parameters").is_none());
2871    }
2872
2873    #[test]
2874    fn parse_bgp_open_with_capabilities() {
2875        let data = build_open_with_capabilities();
2876        let mut buf = DissectBuffer::new();
2877        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
2878
2879        let layer = &buf.layers()[0];
2880        let params = buf.field_by_name(layer, "optional_parameters").unwrap();
2881        let FieldValue::Array(ref arr_range) = params.value else {
2882            panic!("expected Array");
2883        };
2884        // Collect top-level Object children
2885        let objects: Vec<_> = buf
2886            .nested_fields(arr_range)
2887            .iter()
2888            .filter(|f| f.value.is_object())
2889            .collect();
2890        assert_eq!(objects.len(), 2);
2891        // First: Multiprotocol Extensions
2892        let cap1_range = objects[0].value.as_container_range().unwrap();
2893        assert_eq!(
2894            *nested_field_value(&buf, cap1_range, "code"),
2895            FieldValue::U8(1)
2896        );
2897        // value now stores raw capability bytes (AFI/SAFI)
2898        let val = nested_field_value(&buf, cap1_range, "value");
2899        assert_eq!(*val, FieldValue::Bytes(&[0, 1, 0, 1]));
2900        // Second: 4-octet AS
2901        let cap2_range = objects[1].value.as_container_range().unwrap();
2902        assert_eq!(
2903            *nested_field_value(&buf, cap2_range, "code"),
2904            FieldValue::U8(65)
2905        );
2906        // 4-byte AS number stored as raw bytes
2907        let as4_val = nested_field_value(&buf, cap2_range, "value");
2908        assert_eq!(*as4_val, FieldValue::Bytes(&65582u32.to_be_bytes()));
2909    }
2910
2911    #[test]
2912    fn parse_bgp_open_extended_optional_parameters() {
2913        // RFC 9072 Section 2 — Extended OPEN encoding.
2914        // - byte 28: Non-Ext OP Len  = 255
2915        // - byte 29: Non-Ext OP Type = 255 (sentinel)
2916        // - bytes 30-31: Extended Opt. Parm. Length (u16)
2917        // - bytes 32+: parameters with 2-octet length per parameter (RFC 9072 Figure 2)
2918        //
2919        // Build a single Capability parameter (type=2) containing one
2920        // Multiprotocol Extensions capability (code=1, len=4, AFI=1, res=0, SAFI=1).
2921        let cap_mp = [1u8, 4, 0, 1, 0, 1]; // capability code=1, len=4, AFI/res/SAFI
2922        let cap_param_value = cap_mp;
2923        let ext_param_hdr_len = 1 /* type */ + 2 /* len */;
2924        let ext_param_total_len = ext_param_hdr_len + cap_param_value.len();
2925        let ext_opt_params_len = ext_param_total_len; // single param
2926        let total_len = 29 /* OPEN body offsets up to byte 28 */
2927            + 1 /* Non-Ext OP Type */
2928            + 2 /* Extended Opt. Parm. Length */
2929            + ext_opt_params_len;
2930
2931        let mut raw = vec![0xFF; 16];
2932        raw.extend_from_slice(&(total_len as u16).to_be_bytes());
2933        raw.push(1); // Type = OPEN
2934        raw.push(4); // Version
2935        raw.extend_from_slice(&65001u16.to_be_bytes()); // My AS
2936        raw.extend_from_slice(&180u16.to_be_bytes()); // Hold Time
2937        raw.extend_from_slice(&[10, 0, 0, 1]); // BGP Identifier
2938        raw.push(255); // byte 28: Non-Ext OP Len = 255
2939        raw.push(255); // byte 29: Non-Ext OP Type = 255 (sentinel)
2940        raw.extend_from_slice(&(ext_opt_params_len as u16).to_be_bytes()); // bytes 30-31
2941        // Extended Optional Parameter (type=2 Capability, 2-byte length)
2942        raw.push(2); // Param Type = Capability
2943        raw.extend_from_slice(&(cap_param_value.len() as u16).to_be_bytes());
2944        raw.extend_from_slice(&cap_param_value);
2945
2946        let mut buf = DissectBuffer::new();
2947        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
2948
2949        let layer = &buf.layers()[0];
2950        assert_eq!(buf.resolve_display_name(layer, "type_name"), Some("OPEN"));
2951        // Original 1-byte length field reflects the 0xFF sentinel byte at offset 28.
2952        assert_eq!(
2953            buf.field_by_name(layer, "opt_params_length").unwrap().value,
2954            FieldValue::U8(255)
2955        );
2956        // Extended length field is present only when extended encoding is used.
2957        assert_eq!(
2958            buf.field_by_name(layer, "ext_opt_params_length")
2959                .unwrap()
2960                .value,
2961            FieldValue::U16(ext_opt_params_len as u16)
2962        );
2963        // Capability is parsed using the 2-byte parameter length.
2964        let params = buf.field_by_name(layer, "optional_parameters").unwrap();
2965        let FieldValue::Array(ref arr_range) = params.value else {
2966            panic!("expected Array");
2967        };
2968        let objects: Vec<_> = buf
2969            .nested_fields(arr_range)
2970            .iter()
2971            .filter(|f| f.value.is_object())
2972            .collect();
2973        assert_eq!(objects.len(), 1);
2974        let cap_range = objects[0].value.as_container_range().unwrap();
2975        assert_eq!(
2976            *nested_field_value(&buf, cap_range, "code"),
2977            FieldValue::U8(1)
2978        );
2979        assert_eq!(
2980            *nested_field_value(&buf, cap_range, "value"),
2981            FieldValue::Bytes(&[0, 1, 0, 1])
2982        );
2983    }
2984
2985    #[test]
2986    fn parse_bgp_notification() {
2987        let mut raw = vec![0xFF; 16];
2988        raw.extend_from_slice(&23u16.to_be_bytes()); // Length = 23
2989        raw.push(3); // Type = NOTIFICATION
2990        raw.push(6); // Error Code = Cease
2991        raw.push(2); // Error Subcode = Administrative Shutdown
2992        raw.extend_from_slice(&[0xDE, 0xAD]); // Data
2993
2994        let mut buf = DissectBuffer::new();
2995        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
2996
2997        let layer = &buf.layers()[0];
2998        assert_eq!(
2999            buf.resolve_display_name(layer, "type_name"),
3000            Some("NOTIFICATION")
3001        );
3002        assert_eq!(
3003            buf.field_by_name(layer, "error_code").unwrap().value,
3004            FieldValue::U8(6)
3005        );
3006        assert_eq!(
3007            buf.resolve_display_name(layer, "error_code_name"),
3008            Some("Cease")
3009        );
3010        assert_eq!(
3011            buf.field_by_name(layer, "error_subcode").unwrap().value,
3012            FieldValue::U8(2)
3013        );
3014        // error_subcode_name requires both code and subcode for lookup,
3015        // so it cannot be a simple display_fn on a single field.
3016        assert_eq!(
3017            buf.field_by_name(layer, "data").unwrap().value,
3018            FieldValue::Bytes(&[0xDE, 0xAD])
3019        );
3020    }
3021
3022    #[test]
3023    fn parse_bgp_notification_cease_subcode_name() {
3024        // RFC 4486, Section 4 — Cease NOTIFICATION subcode 2 = "Administrative Shutdown".
3025        let mut raw = vec![0xFF; 16];
3026        raw.extend_from_slice(&21u16.to_be_bytes()); // Length = 21 (no data)
3027        raw.push(3); // Type = NOTIFICATION
3028        raw.push(6); // Error Code = Cease
3029        raw.push(2); // Error Subcode = Administrative Shutdown
3030
3031        let mut buf = DissectBuffer::new();
3032        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3033        let layer = &buf.layers()[0];
3034        assert_eq!(
3035            buf.resolve_display_name(layer, "error_subcode_name"),
3036            Some("Administrative Shutdown")
3037        );
3038
3039        // RFC 8203, Section 4 — Cease subcode 9 = "Hard Reset".
3040        let mut raw = vec![0xFF; 16];
3041        raw.extend_from_slice(&21u16.to_be_bytes());
3042        raw.push(3); // Type = NOTIFICATION
3043        raw.push(6); // Cease
3044        raw.push(9); // Hard Reset (RFC 8203)
3045
3046        let mut buf = DissectBuffer::new();
3047        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3048        let layer = &buf.layers()[0];
3049        assert_eq!(
3050            buf.resolve_display_name(layer, "error_subcode_name"),
3051            Some("Hard Reset")
3052        );
3053
3054        // For non-Cease error codes, error_subcode_name must NOT decode as a
3055        // Cease subcode (the lookup table is error-code specific).
3056        let mut raw = vec![0xFF; 16];
3057        raw.extend_from_slice(&21u16.to_be_bytes());
3058        raw.push(3); // Type = NOTIFICATION
3059        raw.push(1); // Error Code = Message Header Error
3060        raw.push(2); // Subcode (not Cease subcode)
3061
3062        let mut buf = DissectBuffer::new();
3063        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3064        let layer = &buf.layers()[0];
3065        assert_eq!(buf.resolve_display_name(layer, "error_subcode_name"), None);
3066    }
3067
3068    #[test]
3069    fn parse_bgp_route_refresh() {
3070        let mut raw = vec![0xFF; 16];
3071        raw.extend_from_slice(&23u16.to_be_bytes()); // Length = 23
3072        raw.push(5); // Type = ROUTE-REFRESH
3073        raw.extend_from_slice(&1u16.to_be_bytes()); // AFI = IPv4
3074        raw.push(0); // Reserved
3075        raw.push(1); // SAFI = Unicast
3076
3077        let mut buf = DissectBuffer::new();
3078        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3079
3080        let layer = &buf.layers()[0];
3081        assert_eq!(
3082            buf.resolve_display_name(layer, "type_name"),
3083            Some("ROUTE-REFRESH")
3084        );
3085        assert_eq!(
3086            buf.field_by_name(layer, "afi").unwrap().value,
3087            FieldValue::U16(1)
3088        );
3089        assert_eq!(buf.resolve_display_name(layer, "afi_name"), Some("IPv4"));
3090        assert_eq!(
3091            buf.field_by_name(layer, "safi").unwrap().value,
3092            FieldValue::U8(1)
3093        );
3094        assert_eq!(
3095            buf.resolve_display_name(layer, "safi_name"),
3096            Some("Unicast")
3097        );
3098    }
3099
3100    #[test]
3101    fn parse_bgp_route_refresh_subtype_borr() {
3102        // RFC 7313 redefines byte 21 of ROUTE-REFRESH from Reserved to Message Subtype.
3103        // Subtype 1 = Beginning of RIB (BoRR).
3104        let mut raw = vec![0xFF; 16];
3105        raw.extend_from_slice(&23u16.to_be_bytes()); // Length = 23
3106        raw.push(5); // Type = ROUTE-REFRESH
3107        raw.extend_from_slice(&1u16.to_be_bytes()); // AFI = IPv4
3108        raw.push(1); // Message Subtype = BoRR (RFC 7313)
3109        raw.push(1); // SAFI = Unicast
3110
3111        let mut buf = DissectBuffer::new();
3112        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3113
3114        let layer = &buf.layers()[0];
3115        assert_eq!(
3116            buf.field_by_name(layer, "message_subtype").unwrap().value,
3117            FieldValue::U8(1)
3118        );
3119        assert_eq!(
3120            buf.resolve_display_name(layer, "message_subtype_name"),
3121            Some("BoRR")
3122        );
3123        assert_eq!(
3124            buf.field_by_name(layer, "safi").unwrap().value,
3125            FieldValue::U8(1)
3126        );
3127    }
3128
3129    #[test]
3130    fn parse_bgp_update_withdraw() {
3131        // UPDATE with 1 withdrawn route: 10.0.0.0/8
3132        let mut raw = vec![0xFF; 16];
3133        let withdrawn = [8, 10]; // prefix_len=8, prefix=10 (10.0.0.0/8)
3134        let total_len = 19 + 2 + withdrawn.len() + 2; // header + wr_len + wr + pa_len
3135        raw.extend_from_slice(&(total_len as u16).to_be_bytes());
3136        raw.push(2); // Type = UPDATE
3137        raw.extend_from_slice(&(withdrawn.len() as u16).to_be_bytes()); // Withdrawn Routes Length
3138        raw.extend_from_slice(&withdrawn);
3139        raw.extend_from_slice(&0u16.to_be_bytes()); // Total Path Attribute Length = 0
3140
3141        let mut buf = DissectBuffer::new();
3142        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3143
3144        let layer = &buf.layers()[0];
3145        assert_eq!(
3146            buf.field_by_name(layer, "withdrawn_routes_length")
3147                .unwrap()
3148                .value,
3149            FieldValue::U16(2)
3150        );
3151        let wr = buf.field_by_name(layer, "withdrawn_routes").unwrap();
3152        let FieldValue::Array(ref arr_range) = wr.value else {
3153            panic!("expected Array");
3154        };
3155        let arr = buf.nested_fields(arr_range);
3156        assert_eq!(arr.len(), 1);
3157        // Prefix stored as raw bytes: [prefix_len, prefix_bytes...]
3158        assert_eq!(arr[0].value, FieldValue::Bytes(&[8, 10]));
3159        assert!(buf.field_by_name(layer, "nlri").is_none());
3160    }
3161
3162    #[test]
3163    fn parse_bgp_update_announce() {
3164        // UPDATE with no withdrawn, a raw path attribute, and NLRI 192.168.1.0/24
3165        let mut raw = vec![0xFF; 16];
3166
3167        // Path attribute: ORIGIN = IGP (type=1, flags=0x40, len=1, value=0)
3168        let attr = [0x40, 0x01, 0x01, 0x00]; // well-known transitive, ORIGIN, len=1, IGP
3169
3170        // NLRI: 192.168.1.0/24
3171        let nlri = [24, 192, 168, 1]; // prefix_len=24, prefix=192.168.1
3172
3173        let total_len = 19 + 2 + 2 + attr.len() + nlri.len();
3174        raw.extend_from_slice(&(total_len as u16).to_be_bytes());
3175        raw.push(2); // Type = UPDATE
3176        raw.extend_from_slice(&0u16.to_be_bytes()); // Withdrawn Routes Length = 0
3177        raw.extend_from_slice(&(attr.len() as u16).to_be_bytes()); // Path Attr Length
3178        raw.extend_from_slice(&attr);
3179        raw.extend_from_slice(&nlri);
3180
3181        let mut buf = DissectBuffer::new();
3182        BgpDissector.dissect(&raw, &mut buf, 0).unwrap();
3183
3184        let layer = &buf.layers()[0];
3185        assert_eq!(
3186            buf.field_by_name(layer, "total_path_attribute_length")
3187                .unwrap()
3188                .value,
3189            FieldValue::U16(attr.len() as u16)
3190        );
3191
3192        // Check path attributes
3193        let pa = buf.field_by_name(layer, "path_attributes").unwrap();
3194        let FieldValue::Array(ref arr_range) = pa.value else {
3195            panic!("expected Array");
3196        };
3197        let pa_objects: Vec<_> = buf
3198            .nested_fields(arr_range)
3199            .iter()
3200            .filter(|f| f.value.is_object())
3201            .collect();
3202        assert_eq!(pa_objects.len(), 1);
3203
3204        // Check NLRI
3205        let nlri_field = buf.field_by_name(layer, "nlri").unwrap();
3206        let FieldValue::Array(ref nlri_range) = nlri_field.value else {
3207            panic!("expected Array");
3208        };
3209        let nlri_entries = buf.nested_fields(nlri_range);
3210        assert_eq!(nlri_entries.len(), 1);
3211        // Prefix stored as raw bytes: [prefix_len=24, 192, 168, 1]
3212        assert_eq!(nlri_entries[0].value, FieldValue::Bytes(&[24, 192, 168, 1]));
3213    }
3214
3215    /// Helper: extract the first path attribute's child range from a dissected UPDATE.
3216    fn first_pa_obj_range(buf: &DissectBuffer<'_>) -> core::ops::Range<u32> {
3217        let layer = &buf.layers()[0];
3218        let pa = buf.field_by_name(layer, "path_attributes").unwrap();
3219        let FieldValue::Array(ref arr_range) = pa.value else {
3220            panic!("expected Array for path_attributes")
3221        };
3222        let nested = buf.nested_fields(arr_range);
3223        let FieldValue::Object(ref obj_range) = nested[0].value else {
3224            panic!("expected Object for first path_attribute")
3225        };
3226        obj_range.clone()
3227    }
3228
3229    /// Helper: look up a named field's value from a range.
3230    fn nested_field_value<'a, 'pkt>(
3231        buf: &'a DissectBuffer<'pkt>,
3232        range: &core::ops::Range<u32>,
3233        name: &str,
3234    ) -> &'a FieldValue<'pkt> {
3235        &nested_field_by_name(buf, range, name).value
3236    }
3237
3238    /// Helper: build an UPDATE with specific path attributes and optional NLRI.
3239    fn build_update(attrs: &[u8], nlri: &[u8]) -> Vec<u8> {
3240        let total_len = 19 + 2 + 2 + attrs.len() + nlri.len();
3241        let mut raw = vec![0xFF; 16];
3242        raw.extend_from_slice(&(total_len as u16).to_be_bytes());
3243        raw.push(2); // Type = UPDATE
3244        raw.extend_from_slice(&0u16.to_be_bytes()); // Withdrawn Routes Length = 0
3245        raw.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
3246        raw.extend_from_slice(attrs);
3247        raw.extend_from_slice(nlri);
3248        raw
3249    }
3250
3251    /// Helper: build a path attribute header + value.
3252    fn build_attr(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
3253        let mut raw = vec![flags, type_code];
3254        if flags & 0x10 != 0 {
3255            raw.extend_from_slice(&(value.len() as u16).to_be_bytes());
3256        } else {
3257            raw.push(value.len() as u8);
3258        }
3259        raw.extend_from_slice(value);
3260        raw
3261    }
3262
3263    #[test]
3264    fn parse_bgp_update_origin() {
3265        let attr = build_attr(0x40, 1, &[0]); // ORIGIN = IGP
3266        let data = build_update(&attr, &[]);
3267        let mut buf = DissectBuffer::new();
3268        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3269
3270        let obj_range = first_pa_obj_range(&buf);
3271        // ORIGIN stored as raw U8 value; display deferred to format_fn
3272        assert_eq!(
3273            *nested_field_value(&buf, &obj_range, "value"),
3274            FieldValue::U8(0)
3275        );
3276    }
3277
3278    #[test]
3279    fn parse_bgp_update_as_path() {
3280        let mut as_path_value = vec![2, 2]; // AS_SEQUENCE, 2 ASNs
3281        as_path_value.extend_from_slice(&65001u16.to_be_bytes());
3282        as_path_value.extend_from_slice(&65002u16.to_be_bytes());
3283        let attr = build_attr(0x40, 2, &as_path_value);
3284        let data = build_update(&attr, &[]);
3285        let mut buf = DissectBuffer::new();
3286        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3287
3288        let obj_range = first_pa_obj_range(&buf);
3289        let FieldValue::Array(ref segs_range) = *nested_field_value(&buf, &obj_range, "value")
3290        else {
3291            panic!("expected Array for AS_PATH");
3292        };
3293        // First Object in the Array is a segment
3294        let segs: Vec<_> = buf
3295            .nested_fields(segs_range)
3296            .iter()
3297            .filter(|f| f.value.is_object())
3298            .collect();
3299        assert_eq!(segs.len(), 1);
3300        let seg_range = segs[0].value.as_container_range().unwrap();
3301        assert_eq!(
3302            *nested_field_value(&buf, seg_range, "segment_type"),
3303            FieldValue::U8(2)
3304        ); // AS_SEQUENCE
3305        let asns_field = nested_field_by_name(&buf, seg_range, "as_numbers");
3306        let asns_range = asns_field.value.as_container_range().unwrap();
3307        let asns = buf.nested_fields(asns_range);
3308        assert_eq!(asns.len(), 2);
3309        assert_eq!(asns[0].value, FieldValue::U32(65001));
3310        assert_eq!(asns[1].value, FieldValue::U32(65002));
3311    }
3312
3313    #[test]
3314    fn parse_bgp_update_next_hop() {
3315        let attr = build_attr(0x40, 3, &[10, 0, 0, 1]);
3316        let data = build_update(&attr, &[]);
3317        let mut buf = DissectBuffer::new();
3318        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3319
3320        let obj_range = first_pa_obj_range(&buf);
3321        assert_eq!(
3322            *nested_field_value(&buf, &obj_range, "value"),
3323            FieldValue::Ipv4Addr([10, 0, 0, 1])
3324        );
3325    }
3326
3327    #[test]
3328    fn parse_bgp_update_communities() {
3329        let mut val = Vec::new();
3330        val.extend_from_slice(&((65001u32 << 16) | 100).to_be_bytes());
3331        val.extend_from_slice(&0xFFFFFF01u32.to_be_bytes()); // NO_EXPORT
3332        let attr = build_attr(0xC0, 8, &val);
3333        let data = build_update(&attr, &[]);
3334        let mut buf = DissectBuffer::new();
3335        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3336
3337        let obj_range = first_pa_obj_range(&buf);
3338        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
3339        else {
3340            panic!("expected Array for communities");
3341        };
3342        let comms = buf.nested_fields(comms_range);
3343        assert_eq!(comms.len(), 2);
3344        assert_eq!(comms[0].value, FieldValue::U32((65001 << 16) | 100));
3345        assert_eq!(comms[1].value, FieldValue::U32(0xFFFFFF01));
3346    }
3347
3348    #[test]
3349    fn parse_bgp_update_extended_communities() {
3350        let mut val = vec![0x00, 0x02];
3351        val.extend_from_slice(&65001u16.to_be_bytes());
3352        val.extend_from_slice(&100u32.to_be_bytes());
3353        val.extend_from_slice(&[0x03, 0x0B, 0x00, 0x00]);
3354        val.extend_from_slice(&1000u32.to_be_bytes());
3355        let attr = build_attr(0xC0, 16, &val);
3356        let data = build_update(&attr, &[]);
3357        let mut buf = DissectBuffer::new();
3358        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3359
3360        let obj_range = first_pa_obj_range(&buf);
3361        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
3362        else {
3363            panic!("expected Array for extended communities");
3364        };
3365        let comms = buf.nested_fields(comms_range);
3366        assert_eq!(comms.len(), 2);
3367        // Extended communities stored as raw 8-byte slices
3368        assert_eq!(comms[0].value, FieldValue::Bytes(&val[0..8]));
3369        assert_eq!(comms[1].value, FieldValue::Bytes(&val[8..16]));
3370    }
3371
3372    #[test]
3373    fn parse_bgp_update_large_community() {
3374        let mut val = Vec::new();
3375        val.extend_from_slice(&64496u32.to_be_bytes());
3376        val.extend_from_slice(&100u32.to_be_bytes());
3377        val.extend_from_slice(&200u32.to_be_bytes());
3378        let attr = build_attr(0xC0, 32, &val);
3379        let data = build_update(&attr, &[]);
3380        let mut buf = DissectBuffer::new();
3381        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3382
3383        let obj_range = first_pa_obj_range(&buf);
3384        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
3385        else {
3386            panic!("expected Array for large communities");
3387        };
3388        let comms = buf.nested_fields(comms_range);
3389        assert_eq!(comms.len(), 1);
3390        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
3391    }
3392
3393    #[test]
3394    fn parse_bgp_update_mp_reach_ipv6() {
3395        let mut val = Vec::new();
3396        val.extend_from_slice(&2u16.to_be_bytes());
3397        val.push(1);
3398        val.push(16);
3399        val.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
3400        val.push(0);
3401        val.push(48);
3402        val.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
3403
3404        let attr = build_attr(0x80 | 0x10, 14, &val);
3405        let data = build_update(&attr, &[]);
3406        let mut buf = DissectBuffer::new();
3407        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3408
3409        let obj_range = first_pa_obj_range(&buf);
3410        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
3411        else {
3412            panic!("expected Object for MP_REACH");
3413        };
3414        assert_eq!(
3415            *nested_field_value(&buf, mp_range, "afi"),
3416            FieldValue::U16(2)
3417        );
3418        let expected_nh = [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
3419        assert_eq!(
3420            *nested_field_value(&buf, mp_range, "next_hop"),
3421            FieldValue::Ipv6Addr(expected_nh)
3422        );
3423        let FieldValue::Array(ref prefixes_range) = *nested_field_value(&buf, mp_range, "nlri")
3424        else {
3425            panic!("expected Array for NLRI");
3426        };
3427        let prefixes = buf.nested_fields(prefixes_range);
3428        assert_eq!(prefixes.len(), 1);
3429        assert_eq!(
3430            prefixes[0].value,
3431            FieldValue::Bytes(&[48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01])
3432        );
3433    }
3434
3435    #[test]
3436    fn parse_bgp_truncated_open() {
3437        let mut data = vec![0xFF; 16];
3438        data.extend_from_slice(&29u16.to_be_bytes());
3439        data.push(1);
3440        data.extend_from_slice(&[4, 0, 1]);
3441        let mut buf = DissectBuffer::new();
3442        let err = BgpDissector.dissect(&data, &mut buf, 0).unwrap_err();
3443        assert!(matches!(err, PacketError::Truncated { .. }));
3444    }
3445
3446    #[test]
3447    fn parse_bgp_truncated_header() {
3448        let data = vec![0xFF; 10];
3449        let mut buf = DissectBuffer::new();
3450        let err = BgpDissector.dissect(&data, &mut buf, 0).unwrap_err();
3451        assert!(matches!(
3452            err,
3453            PacketError::Truncated {
3454                expected: 19,
3455                actual: 10
3456            }
3457        ));
3458    }
3459
3460    #[test]
3461    fn parse_bgp_invalid_marker() {
3462        let mut data = build_keepalive();
3463        data[0] = 0x00;
3464        let mut buf = DissectBuffer::new();
3465        let err = BgpDissector.dissect(&data, &mut buf, 0).unwrap_err();
3466        assert!(matches!(err, PacketError::InvalidHeader(_)));
3467    }
3468
3469    #[test]
3470    fn parse_bgp_multiple_messages() {
3471        let mut data = build_keepalive();
3472        data.extend_from_slice(&build_keepalive());
3473
3474        let mut buf = DissectBuffer::new();
3475        let result = BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3476
3477        assert_eq!(result.bytes_consumed, 38);
3478        assert_eq!(buf.layers().len(), 2);
3479        assert_eq!(buf.layers()[0].name, "BGP");
3480        assert_eq!(buf.layers()[1].name, "BGP");
3481        assert_eq!(
3482            buf.resolve_display_name(&buf.layers()[0], "type_name"),
3483            Some("KEEPALIVE")
3484        );
3485        assert_eq!(
3486            buf.resolve_display_name(&buf.layers()[1], "type_name"),
3487            Some("KEEPALIVE")
3488        );
3489        assert_eq!(buf.layers()[0].range, 0..19);
3490        assert_eq!(buf.layers()[1].range, 19..38);
3491    }
3492
3493    #[test]
3494    fn parse_bgp_open_followed_by_keepalive() {
3495        let mut data = build_open_basic();
3496        data.extend_from_slice(&build_keepalive());
3497
3498        let mut buf = DissectBuffer::new();
3499        let result = BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3500
3501        assert_eq!(result.bytes_consumed, 48);
3502        assert_eq!(buf.layers().len(), 2);
3503        assert_eq!(
3504            buf.resolve_display_name(&buf.layers()[0], "type_name"),
3505            Some("OPEN")
3506        );
3507        assert_eq!(
3508            buf.resolve_display_name(&buf.layers()[1], "type_name"),
3509            Some("KEEPALIVE")
3510        );
3511    }
3512
3513    #[test]
3514    fn parse_bgp_update_mup_interwork_segment_discovery() {
3515        let mut val = Vec::new();
3516        val.extend_from_slice(&1u16.to_be_bytes());
3517        val.push(85);
3518        val.push(4);
3519        val.extend_from_slice(&[10, 0, 0, 1]);
3520        val.push(0);
3521        val.push(1);
3522        val.extend_from_slice(&1u16.to_be_bytes());
3523        val.push(12);
3524        val.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 1]);
3525        val.push(24);
3526        val.extend_from_slice(&[192, 168, 1]);
3527
3528        let attr = build_attr(0x80 | 0x10, 14, &val);
3529        let data = build_update(&attr, &[]);
3530        let mut buf = DissectBuffer::new();
3531        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3532
3533        let obj_range = first_pa_obj_range(&buf);
3534        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
3535        else {
3536            panic!("expected Object for MP_REACH");
3537        };
3538        assert_eq!(
3539            *nested_field_value(&buf, mp_range, "safi"),
3540            FieldValue::U8(85)
3541        );
3542        let FieldValue::Array(ref entries_range) = *nested_field_value(&buf, mp_range, "nlri")
3543        else {
3544            panic!("expected Array for MUP NLRI");
3545        };
3546        let entries: Vec<_> = buf
3547            .nested_fields(entries_range)
3548            .iter()
3549            .filter(|f| f.value.is_object())
3550            .collect();
3551        assert_eq!(entries.len(), 1);
3552        let entry_range = entries[0].value.as_container_range().unwrap();
3553        // Prefix stored as raw bytes [prefix_len, prefix_data...]
3554        assert_eq!(
3555            *nested_field_value(&buf, entry_range, "prefix"),
3556            FieldValue::Bytes(&[24, 192, 168, 1])
3557        );
3558    }
3559
3560    #[test]
3561    fn parse_bgp_update_mup_type1_st() {
3562        let mut val = Vec::new();
3563        val.extend_from_slice(&1u16.to_be_bytes());
3564        val.push(85);
3565        val.push(4);
3566        val.extend_from_slice(&[10, 0, 0, 1]);
3567        val.push(0);
3568        val.push(1);
3569        val.extend_from_slice(&3u16.to_be_bytes());
3570        let rt_len = 8 + 1 + 4 + 4 + 1 + 1 + 4;
3571        val.push(rt_len as u8);
3572        val.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 1]);
3573        val.push(32);
3574        val.extend_from_slice(&[10, 1, 1, 1]);
3575        val.extend_from_slice(&0x12345678u32.to_be_bytes());
3576        val.push(9);
3577        val.push(32);
3578        val.extend_from_slice(&[10, 0, 0, 2]);
3579
3580        let attr = build_attr(0x80 | 0x10, 14, &val);
3581        let data = build_update(&attr, &[]);
3582        let mut buf = DissectBuffer::new();
3583        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3584
3585        let obj_range = first_pa_obj_range(&buf);
3586        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
3587        else {
3588            panic!("expected Object for MP_REACH");
3589        };
3590        let FieldValue::Array(ref entries_range) = *nested_field_value(&buf, mp_range, "nlri")
3591        else {
3592            panic!("expected Array for MUP NLRI");
3593        };
3594        let entry_objs: Vec<_> = buf
3595            .nested_fields(entries_range)
3596            .iter()
3597            .filter(|f| f.value.is_object())
3598            .collect();
3599        let entry_range = entry_objs[0].value.as_container_range().unwrap();
3600        assert_eq!(
3601            *nested_field_value(&buf, entry_range, "prefix"),
3602            FieldValue::Bytes(&[32, 10, 1, 1, 1])
3603        );
3604        // TEID stored as raw 4 bytes
3605        assert_eq!(
3606            *nested_field_value(&buf, entry_range, "teid"),
3607            FieldValue::Bytes(&0x12345678u32.to_be_bytes())
3608        );
3609        assert_eq!(
3610            *nested_field_value(&buf, entry_range, "qfi"),
3611            FieldValue::U8(9)
3612        );
3613        assert_eq!(
3614            *nested_field_value(&buf, entry_range, "endpoint_address"),
3615            FieldValue::Ipv4Addr([10, 0, 0, 2])
3616        );
3617    }
3618
3619    #[test]
3620    fn parse_bgp_update_mup_extended_community() {
3621        let mut val = vec![0x0C, 0x00];
3622        val.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
3623        let attr = build_attr(0xC0, 16, &val);
3624        let data = build_update(&attr, &[]);
3625        let mut buf = DissectBuffer::new();
3626        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3627
3628        let obj_range = first_pa_obj_range(&buf);
3629        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
3630        else {
3631            panic!("expected Array for extended communities");
3632        };
3633        let comms = buf.nested_fields(comms_range);
3634        assert_eq!(comms.len(), 1);
3635        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
3636    }
3637
3638    // --- BGP Prefix-SID tests ---
3639
3640    fn build_psid_tlv(tlv_type: u8, value: &[u8]) -> Vec<u8> {
3641        let mut raw = vec![tlv_type];
3642        raw.extend_from_slice(&(value.len() as u16).to_be_bytes());
3643        raw.extend_from_slice(value);
3644        raw
3645    }
3646
3647    /// Helper: extract the first path attribute's "value" field.
3648    fn extract_pa_value<'a, 'pkt>(buf: &'a DissectBuffer<'pkt>) -> &'a FieldValue<'pkt> {
3649        let obj_range = first_pa_obj_range(buf);
3650        nested_field_value(buf, &obj_range, "value")
3651    }
3652
3653    #[test]
3654    fn parse_bgp_prefix_sid_label_index() {
3655        let mut val = vec![0x00];
3656        val.extend_from_slice(&0u16.to_be_bytes());
3657        val.extend_from_slice(&100u32.to_be_bytes());
3658        let tlv = build_psid_tlv(1, &val);
3659        let attr = build_attr(0xC0, 40, &tlv);
3660        let data = build_update(&attr, &[]);
3661        let mut buf = DissectBuffer::new();
3662        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3663
3664        let pa_value = extract_pa_value(&buf);
3665        let FieldValue::Array(tlvs_range) = pa_value else {
3666            panic!("expected Array");
3667        };
3668        let tlvs = buf.nested_fields(tlvs_range);
3669        assert!(tlvs[0].value.is_object());
3670        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3671            panic!("expected Object");
3672        };
3673        assert_eq!(
3674            *nested_field_value(&buf, obj_range, "type"),
3675            FieldValue::U8(1)
3676        );
3677        assert_eq!(
3678            *nested_field_value(&buf, obj_range, "label_index"),
3679            FieldValue::U32(100)
3680        );
3681        assert_eq!(
3682            *nested_field_value(&buf, obj_range, "flags"),
3683            FieldValue::U16(0)
3684        );
3685    }
3686
3687    #[test]
3688    fn parse_bgp_prefix_sid_originator_srgb() {
3689        let mut val = vec![0x00, 0x00];
3690        val.push(0x00);
3691        val.push(0x3E);
3692        val.push(0x80);
3693        val.push(0x00);
3694        val.push(0x1F);
3695        val.push(0x40);
3696        let tlv = build_psid_tlv(3, &val);
3697        let attr = build_attr(0xC0, 40, &tlv);
3698        let data = build_update(&attr, &[]);
3699        let mut buf = DissectBuffer::new();
3700        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3701
3702        let pa_value = extract_pa_value(&buf);
3703        let FieldValue::Array(tlvs_range) = pa_value else {
3704            panic!("expected Array");
3705        };
3706        let tlvs = buf.nested_fields(tlvs_range);
3707        assert!(tlvs[0].value.is_object());
3708        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3709            panic!("expected Object");
3710        };
3711        assert_eq!(
3712            *nested_field_value(&buf, obj_range, "type"),
3713            FieldValue::U8(3)
3714        );
3715        let entries_field = nested_field_by_name(&buf, obj_range, "srgb_entries");
3716        let FieldValue::Array(ref srgbs_range) = entries_field.value else {
3717            panic!("expected Array");
3718        };
3719        let srgbs = buf.nested_fields(srgbs_range);
3720        assert!(srgbs[0].value.is_object());
3721        let FieldValue::Object(ref entry_range) = srgbs[0].value else {
3722            panic!("expected Object");
3723        };
3724        assert_eq!(
3725            *nested_field_value(&buf, entry_range, "base"),
3726            FieldValue::U32(16000)
3727        );
3728        assert_eq!(
3729            *nested_field_value(&buf, entry_range, "range"),
3730            FieldValue::U32(8000)
3731        );
3732    }
3733
3734    #[test]
3735    fn parse_bgp_prefix_sid_srv6_l3_service() {
3736        let mut sid_info_val = vec![0x00];
3737        sid_info_val
3738            .extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
3739        sid_info_val.push(0x00);
3740        sid_info_val.extend_from_slice(&0x0029u16.to_be_bytes());
3741        sid_info_val.push(0x00);
3742        let sub_tlv = build_psid_tlv(1, &sid_info_val);
3743        let mut service_val = vec![0x00];
3744        service_val.extend_from_slice(&sub_tlv);
3745        let tlv = build_psid_tlv(5, &service_val);
3746        let attr = build_attr(0xC0 | 0x10, 40, &tlv);
3747        let data = build_update(&attr, &[]);
3748        let mut buf = DissectBuffer::new();
3749        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3750
3751        let pa_value = extract_pa_value(&buf);
3752        let FieldValue::Array(tlvs_range) = pa_value else {
3753            panic!("expected Array");
3754        };
3755        let tlvs = buf.nested_fields(tlvs_range);
3756        assert!(tlvs[0].value.is_object());
3757        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3758            panic!("expected Object");
3759        };
3760        let sub_tlvs_field = nested_field_by_name(&buf, obj_range, "sub_tlvs");
3761        let FieldValue::Array(ref subs_range) = sub_tlvs_field.value else {
3762            panic!("expected Array");
3763        };
3764        let subs = buf.nested_fields(subs_range);
3765        assert!(subs[0].value.is_object());
3766        let FieldValue::Object(ref si_range) = subs[0].value else {
3767            panic!("expected Object");
3768        };
3769        let expected_sid = [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
3770        assert_eq!(
3771            *nested_field_value(&buf, si_range, "srv6_sid"),
3772            FieldValue::Ipv6Addr(expected_sid)
3773        );
3774        assert_eq!(
3775            *nested_field_value(&buf, si_range, "endpoint_behavior"),
3776            FieldValue::U16(0x0029)
3777        );
3778    }
3779
3780    #[test]
3781    fn parse_bgp_prefix_sid_srv6_sid_structure() {
3782        let mut sid_info_val = vec![0x00];
3783        sid_info_val.extend_from_slice(&[
3784            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3785        ]);
3786        sid_info_val.push(0x00);
3787        sid_info_val.extend_from_slice(&0x003Eu16.to_be_bytes());
3788        sid_info_val.push(0x00);
3789        sid_info_val.push(0x01);
3790        sid_info_val.extend_from_slice(&6u16.to_be_bytes());
3791        sid_info_val.push(40);
3792        sid_info_val.push(24);
3793        sid_info_val.push(16);
3794        sid_info_val.push(0);
3795        sid_info_val.push(0);
3796        sid_info_val.push(0);
3797        let sub_tlv = build_psid_tlv(1, &sid_info_val);
3798        let mut service_val = vec![0x00];
3799        service_val.extend_from_slice(&sub_tlv);
3800        let tlv = build_psid_tlv(5, &service_val);
3801        let attr = build_attr(0xC0 | 0x10, 40, &tlv);
3802        let data = build_update(&attr, &[]);
3803        let mut buf = DissectBuffer::new();
3804        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3805
3806        let pa_value = extract_pa_value(&buf);
3807        let FieldValue::Array(tlvs_range) = pa_value else {
3808            panic!("expected Array");
3809        };
3810        let tlvs = buf.nested_fields(tlvs_range);
3811        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3812            panic!("expected Object");
3813        };
3814        let sub_tlvs_field = nested_field_by_name(&buf, obj_range, "sub_tlvs");
3815        let FieldValue::Array(ref subs_range) = sub_tlvs_field.value else {
3816            panic!("expected Array");
3817        };
3818        let subs = buf.nested_fields(subs_range);
3819        let FieldValue::Object(ref si_range) = subs[0].value else {
3820            panic!("expected Object");
3821        };
3822        let ss_field = nested_field_by_name(&buf, si_range, "sid_structure");
3823        let FieldValue::Object(ref ss_range) = ss_field.value else {
3824            panic!("expected Object");
3825        };
3826        assert_eq!(
3827            *nested_field_value(&buf, ss_range, "locator_block_length"),
3828            FieldValue::U8(40)
3829        );
3830        assert_eq!(
3831            *nested_field_value(&buf, ss_range, "locator_node_length"),
3832            FieldValue::U8(24)
3833        );
3834        assert_eq!(
3835            *nested_field_value(&buf, ss_range, "function_length"),
3836            FieldValue::U8(16)
3837        );
3838        assert_eq!(
3839            *nested_field_value(&buf, ss_range, "argument_length"),
3840            FieldValue::U8(0)
3841        );
3842        assert_eq!(
3843            *nested_field_value(&buf, ss_range, "transposition_length"),
3844            FieldValue::U8(0)
3845        );
3846        assert_eq!(
3847            *nested_field_value(&buf, ss_range, "transposition_offset"),
3848            FieldValue::U8(0)
3849        );
3850    }
3851
3852    #[test]
3853    fn parse_bgp_prefix_sid_multiple_tlvs() {
3854        let mut label_val = vec![0x00];
3855        label_val.extend_from_slice(&0u16.to_be_bytes());
3856        label_val.extend_from_slice(&200u32.to_be_bytes());
3857        let tlv1 = build_psid_tlv(1, &label_val);
3858        let mut srgb_val = vec![0x00, 0x00];
3859        srgb_val.extend_from_slice(&[0x00, 0x3E, 0x80]);
3860        srgb_val.extend_from_slice(&[0x00, 0x1F, 0x40]);
3861        let tlv2 = build_psid_tlv(3, &srgb_val);
3862        let mut attr_val = Vec::new();
3863        attr_val.extend_from_slice(&tlv1);
3864        attr_val.extend_from_slice(&tlv2);
3865        let attr = build_attr(0xC0, 40, &attr_val);
3866        let data = build_update(&attr, &[]);
3867        let mut buf = DissectBuffer::new();
3868        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3869
3870        let pa_value = extract_pa_value(&buf);
3871        let FieldValue::Array(tlvs_range) = pa_value else {
3872            panic!("expected Array");
3873        };
3874        let tlvs = buf.nested_fields(tlvs_range);
3875        assert!(tlvs[0].value.is_object());
3876        let FieldValue::Object(ref obj0) = tlvs[0].value else {
3877            panic!("expected Object");
3878        };
3879        assert_eq!(*nested_field_value(&buf, obj0, "type"), FieldValue::U8(1));
3880        assert_eq!(
3881            *nested_field_value(&buf, obj0, "label_index"),
3882            FieldValue::U32(200)
3883        );
3884        // Second TLV Object starts after the first Object's children
3885        let second_start = (obj0.end - tlvs_range.start) as usize;
3886        let FieldValue::Object(ref obj1) = tlvs[second_start].value else {
3887            panic!("expected Object");
3888        };
3889        assert_eq!(*nested_field_value(&buf, obj1, "type"), FieldValue::U8(3));
3890    }
3891
3892    #[test]
3893    fn parse_bgp_prefix_sid_unknown_tlv() {
3894        let payload = vec![0xDE, 0xAD, 0xBE, 0xEF];
3895        let tlv = build_psid_tlv(99, &payload);
3896        let attr = build_attr(0xC0, 40, &tlv);
3897        let data = build_update(&attr, &[]);
3898        let mut buf = DissectBuffer::new();
3899        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3900
3901        let pa_value = extract_pa_value(&buf);
3902        let FieldValue::Array(tlvs_range) = pa_value else {
3903            panic!("expected Array");
3904        };
3905        let tlvs = buf.nested_fields(tlvs_range);
3906        assert!(tlvs[0].value.is_object());
3907        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3908            panic!("expected Object");
3909        };
3910        assert_eq!(
3911            *nested_field_value(&buf, obj_range, "type"),
3912            FieldValue::U8(99)
3913        );
3914        assert_eq!(
3915            *nested_field_value(&buf, obj_range, "value"),
3916            FieldValue::Bytes(&[0xDE, 0xAD, 0xBE, 0xEF])
3917        );
3918    }
3919
3920    #[test]
3921    fn parse_bgp_prefix_sid_truncated() {
3922        let mut label_val = vec![0x00];
3923        label_val.extend_from_slice(&0u16.to_be_bytes());
3924        label_val.extend_from_slice(&50u32.to_be_bytes());
3925        let tlv1 = build_psid_tlv(1, &label_val);
3926        let mut truncated = vec![0x01];
3927        truncated.extend_from_slice(&10u16.to_be_bytes());
3928        truncated.extend_from_slice(&[0xAA, 0xBB]);
3929        let mut attr_val = Vec::new();
3930        attr_val.extend_from_slice(&tlv1);
3931        attr_val.extend_from_slice(&truncated);
3932        let attr = build_attr(0xC0, 40, &attr_val);
3933        let data = build_update(&attr, &[]);
3934        let mut buf = DissectBuffer::new();
3935        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3936
3937        let pa_value = extract_pa_value(&buf);
3938        let FieldValue::Array(tlvs_range) = pa_value else {
3939            panic!("expected Array");
3940        };
3941        let tlvs = buf.nested_fields(tlvs_range);
3942        assert!(tlvs[0].value.is_object());
3943        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
3944            panic!("expected Object");
3945        };
3946        assert_eq!(
3947            *nested_field_value(&buf, obj_range, "label_index"),
3948            FieldValue::U32(50)
3949        );
3950    }
3951
3952    #[test]
3953    fn parse_bgp_update_mp_unreach_ipv6() {
3954        let mut val = Vec::new();
3955        val.extend_from_slice(&2u16.to_be_bytes());
3956        val.push(1);
3957        val.push(32);
3958        val.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8]);
3959        let attr = build_attr(0x80 | 0x10, 15, &val);
3960        let data = build_update(&attr, &[]);
3961        let mut buf = DissectBuffer::new();
3962        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
3963
3964        let obj_range = first_pa_obj_range(&buf);
3965        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
3966        else {
3967            panic!("expected Object for MP_UNREACH");
3968        };
3969        assert_eq!(
3970            *nested_field_value(&buf, mp_range, "afi"),
3971            FieldValue::U16(2)
3972        );
3973        assert_eq!(
3974            *nested_field_value(&buf, mp_range, "safi"),
3975            FieldValue::U8(1)
3976        );
3977        let wr_field = nested_field_by_name(&buf, mp_range, "withdrawn_routes");
3978        let FieldValue::Array(ref wr_range) = wr_field.value else {
3979            panic!("expected Array");
3980        };
3981        let prefixes = buf.nested_fields(wr_range);
3982        assert_eq!(prefixes.len(), 1);
3983        assert_eq!(
3984            prefixes[0].value,
3985            FieldValue::Bytes(&[32, 0x20, 0x01, 0x0d, 0xb8])
3986        );
3987    }
3988
3989    #[test]
3990    fn parse_bgp_update_mp_unreach_ipv4() {
3991        let mut val = Vec::new();
3992        val.extend_from_slice(&1u16.to_be_bytes());
3993        val.push(1);
3994        val.push(8);
3995        val.push(10);
3996        let attr = build_attr(0x80 | 0x10, 15, &val);
3997        let data = build_update(&attr, &[]);
3998        let mut buf = DissectBuffer::new();
3999        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4000
4001        let obj_range = first_pa_obj_range(&buf);
4002        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
4003        else {
4004            panic!("expected Object for MP_UNREACH");
4005        };
4006        assert_eq!(
4007            *nested_field_value(&buf, mp_range, "afi"),
4008            FieldValue::U16(1)
4009        );
4010        let wr_field = nested_field_by_name(&buf, mp_range, "withdrawn_routes");
4011        let FieldValue::Array(ref wr_range) = wr_field.value else {
4012            panic!("expected Array");
4013        };
4014        let prefixes = buf.nested_fields(wr_range);
4015        assert_eq!(prefixes.len(), 1);
4016        assert_eq!(prefixes[0].value, FieldValue::Bytes(&[8, 10]));
4017    }
4018
4019    #[test]
4020    fn parse_bgp_update_aggregator_2byte_as() {
4021        let mut val = Vec::new();
4022        val.extend_from_slice(&65001u16.to_be_bytes());
4023        val.extend_from_slice(&[10, 0, 0, 1]);
4024        let attr = build_attr(0xC0, 7, &val);
4025        let data = build_update(&attr, &[]);
4026        let mut buf = DissectBuffer::new();
4027        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4028
4029        let obj_range = first_pa_obj_range(&buf);
4030        assert_eq!(
4031            *nested_field_value(&buf, &obj_range, "value"),
4032            FieldValue::Bytes(&val[..])
4033        );
4034    }
4035
4036    #[test]
4037    fn parse_bgp_update_aggregator_4byte_as() {
4038        let mut val = Vec::new();
4039        val.extend_from_slice(&131072u32.to_be_bytes());
4040        val.extend_from_slice(&[192, 168, 1, 1]);
4041        let attr = build_attr(0xC0 | 0x10, 7, &val);
4042        let data = build_update(&attr, &[]);
4043        let mut buf = DissectBuffer::new();
4044        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4045
4046        let obj_range = first_pa_obj_range(&buf);
4047        assert_eq!(
4048            *nested_field_value(&buf, &obj_range, "value"),
4049            FieldValue::Bytes(&val[..])
4050        );
4051    }
4052
4053    #[test]
4054    fn parse_bgp_update_as4_path() {
4055        let mut as4_path_value = vec![2, 2];
4056        as4_path_value.extend_from_slice(&200000u32.to_be_bytes());
4057        as4_path_value.extend_from_slice(&300000u32.to_be_bytes());
4058        let attr = build_attr(0xC0 | 0x10, 17, &as4_path_value);
4059        let data = build_update(&attr, &[]);
4060        let mut buf = DissectBuffer::new();
4061        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4062
4063        let obj_range = first_pa_obj_range(&buf);
4064        let FieldValue::Array(ref segs_range) = *nested_field_value(&buf, &obj_range, "value")
4065        else {
4066            panic!("expected Array for AS4_PATH");
4067        };
4068        let segs: Vec<_> = buf
4069            .nested_fields(segs_range)
4070            .iter()
4071            .filter(|f| f.value.is_object())
4072            .collect();
4073        assert_eq!(segs.len(), 1);
4074        let seg_range = segs[0].value.as_container_range().unwrap();
4075        assert_eq!(
4076            *nested_field_value(&buf, seg_range, "segment_type"),
4077            FieldValue::U8(2)
4078        );
4079        let asns_field = nested_field_by_name(&buf, seg_range, "as_numbers");
4080        let asns_range = asns_field.value.as_container_range().unwrap();
4081        let asns = buf.nested_fields(asns_range);
4082        assert_eq!(asns.len(), 2);
4083        assert_eq!(asns[0].value, FieldValue::U32(200000));
4084        assert_eq!(asns[1].value, FieldValue::U32(300000));
4085    }
4086
4087    #[test]
4088    fn parse_bgp_update_as4_aggregator() {
4089        let mut val = Vec::new();
4090        val.extend_from_slice(&200000u32.to_be_bytes());
4091        val.extend_from_slice(&[172, 16, 0, 1]);
4092        let attr = build_attr(0xC0 | 0x10, 18, &val);
4093        let data = build_update(&attr, &[]);
4094        let mut buf = DissectBuffer::new();
4095        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4096
4097        let obj_range = first_pa_obj_range(&buf);
4098        assert_eq!(
4099            *nested_field_value(&buf, &obj_range, "value"),
4100            FieldValue::Bytes(&val[..])
4101        );
4102    }
4103
4104    #[test]
4105    fn parse_bgp_update_cluster_list() {
4106        let mut val = Vec::new();
4107        val.extend_from_slice(&[10, 0, 0, 1]);
4108        val.extend_from_slice(&[10, 0, 0, 2]);
4109        let attr = build_attr(0x80, 10, &val);
4110        let data = build_update(&attr, &[]);
4111        let mut buf = DissectBuffer::new();
4112        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4113
4114        let obj_range = first_pa_obj_range(&buf);
4115        let FieldValue::Array(ref clusters_range) = *nested_field_value(&buf, &obj_range, "value")
4116        else {
4117            panic!("expected Array for CLUSTER_LIST");
4118        };
4119        let clusters = buf.nested_fields(clusters_range);
4120        assert_eq!(clusters.len(), 2);
4121        assert_eq!(clusters[0].value, FieldValue::Ipv4Addr([10, 0, 0, 1]));
4122        assert_eq!(clusters[1].value, FieldValue::Ipv4Addr([10, 0, 0, 2]));
4123    }
4124
4125    #[test]
4126    fn parse_bgp_update_originator_id() {
4127        let attr = build_attr(0x80, 9, &[10, 0, 0, 1]);
4128        let data = build_update(&attr, &[]);
4129        let mut buf = DissectBuffer::new();
4130        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4131
4132        let obj_range = first_pa_obj_range(&buf);
4133        assert_eq!(
4134            *nested_field_value(&buf, &obj_range, "value"),
4135            FieldValue::Ipv4Addr([10, 0, 0, 1])
4136        );
4137    }
4138
4139    #[test]
4140    fn parse_bgp_update_multi_exit_disc() {
4141        let attr = build_attr(0x80, 4, &100u32.to_be_bytes());
4142        let data = build_update(&attr, &[]);
4143        let mut buf = DissectBuffer::new();
4144        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4145
4146        let obj_range = first_pa_obj_range(&buf);
4147        assert_eq!(
4148            *nested_field_value(&buf, &obj_range, "value"),
4149            FieldValue::U32(100)
4150        );
4151    }
4152
4153    #[test]
4154    fn parse_bgp_update_local_pref() {
4155        let attr = build_attr(0x40, 5, &200u32.to_be_bytes());
4156        let data = build_update(&attr, &[]);
4157        let mut buf = DissectBuffer::new();
4158        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4159
4160        let obj_range = first_pa_obj_range(&buf);
4161        assert_eq!(
4162            *nested_field_value(&buf, &obj_range, "value"),
4163            FieldValue::U32(200)
4164        );
4165    }
4166
4167    #[test]
4168    fn parse_bgp_update_atomic_aggregate() {
4169        let attr = build_attr(0x40, 6, &[]);
4170        let data = build_update(&attr, &[]);
4171        let mut buf = DissectBuffer::new();
4172        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4173
4174        let obj_range = first_pa_obj_range(&buf);
4175        let fields = buf.nested_fields(&obj_range);
4176        assert!(!fields.iter().any(|f| f.name() == "value"));
4177    }
4178
4179    #[test]
4180    fn parse_bgp_update_extended_communities_ipv4_route_target() {
4181        let mut val = vec![0x01, 0x02];
4182        val.extend_from_slice(&[192, 168, 1, 1]);
4183        val.extend_from_slice(&100u16.to_be_bytes());
4184        let attr = build_attr(0xC0, 16, &val);
4185        let data = build_update(&attr, &[]);
4186        let mut buf = DissectBuffer::new();
4187        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4188
4189        let obj_range = first_pa_obj_range(&buf);
4190        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
4191        else {
4192            panic!("expected Array");
4193        };
4194        let comms = buf.nested_fields(comms_range);
4195        assert_eq!(comms.len(), 1);
4196        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
4197    }
4198
4199    #[test]
4200    fn parse_bgp_update_extended_communities_route_origin() {
4201        let mut val = vec![0x00, 0x03];
4202        val.extend_from_slice(&65001u16.to_be_bytes());
4203        val.extend_from_slice(&500u32.to_be_bytes());
4204        let attr = build_attr(0xC0, 16, &val);
4205        let data = build_update(&attr, &[]);
4206        let mut buf = DissectBuffer::new();
4207        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4208
4209        let obj_range = first_pa_obj_range(&buf);
4210        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
4211        else {
4212            panic!("expected Array");
4213        };
4214        let comms = buf.nested_fields(comms_range);
4215        assert_eq!(comms.len(), 1);
4216        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
4217    }
4218
4219    #[test]
4220    fn parse_bgp_update_extended_communities_evpn() {
4221        let mut val = vec![0x06, 0x00];
4222        val.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
4223        let attr = build_attr(0xC0, 16, &val);
4224        let data = build_update(&attr, &[]);
4225        let mut buf = DissectBuffer::new();
4226        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4227
4228        let obj_range = first_pa_obj_range(&buf);
4229        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
4230        else {
4231            panic!("expected Array");
4232        };
4233        let comms = buf.nested_fields(comms_range);
4234        assert_eq!(comms.len(), 1);
4235        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
4236    }
4237
4238    #[test]
4239    fn parse_bgp_update_extended_communities_unknown() {
4240        let val = vec![0x99, 0x99, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
4241        let attr = build_attr(0xC0, 16, &val);
4242        let data = build_update(&attr, &[]);
4243        let mut buf = DissectBuffer::new();
4244        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4245
4246        let obj_range = first_pa_obj_range(&buf);
4247        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
4248        else {
4249            panic!("expected Array");
4250        };
4251        let comms = buf.nested_fields(comms_range);
4252        assert_eq!(comms.len(), 1);
4253        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
4254    }
4255
4256    #[test]
4257    fn parse_bgp_update_unknown_attribute() {
4258        let attr = build_attr(0xC0, 99, &[0xDE, 0xAD]);
4259        let data = build_update(&attr, &[]);
4260        let mut buf = DissectBuffer::new();
4261        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4262
4263        let obj_range = first_pa_obj_range(&buf);
4264        let fields = buf.nested_fields(&obj_range);
4265        assert!(!fields.iter().any(|f| f.name() == "type_name"));
4266        assert_eq!(
4267            *nested_field_value(&buf, &obj_range, "value"),
4268            FieldValue::Bytes(&[0xDE, 0xAD])
4269        );
4270    }
4271
4272    #[test]
4273    fn parse_bgp_update_mp_reach_ipv6_link_local() {
4274        let mut val = Vec::new();
4275        val.extend_from_slice(&2u16.to_be_bytes());
4276        val.push(1);
4277        val.push(32);
4278        val.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
4279        val.extend_from_slice(&[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
4280        val.push(0);
4281        val.push(48);
4282        val.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
4283        let attr = build_attr(0x80 | 0x10, 14, &val);
4284        let data = build_update(&attr, &[]);
4285        let mut buf = DissectBuffer::new();
4286        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4287
4288        let obj_range = first_pa_obj_range(&buf);
4289        let FieldValue::Object(ref mp_range) = *nested_field_value(&buf, &obj_range, "value")
4290        else {
4291            panic!("expected Object for MP_REACH");
4292        };
4293        assert_eq!(
4294            *nested_field_value(&buf, mp_range, "next_hop"),
4295            FieldValue::Ipv6Addr([0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
4296        );
4297        assert_eq!(
4298            *nested_field_value(&buf, mp_range, "next_hop_link_local"),
4299            FieldValue::Ipv6Addr([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
4300        );
4301    }
4302
4303    #[test]
4304    fn parse_bgp_update_extended_communities_ipv4_route_origin() {
4305        let mut val = vec![0x01, 0x03];
4306        val.extend_from_slice(&[10, 0, 0, 1]);
4307        val.extend_from_slice(&200u16.to_be_bytes());
4308        let attr = build_attr(0xC0, 16, &val);
4309        let data = build_update(&attr, &[]);
4310        let mut buf = DissectBuffer::new();
4311        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4312
4313        let obj_range = first_pa_obj_range(&buf);
4314        let FieldValue::Array(ref comms_range) = *nested_field_value(&buf, &obj_range, "value")
4315        else {
4316            panic!("expected Array");
4317        };
4318        let comms = buf.nested_fields(comms_range);
4319        assert_eq!(comms.len(), 1);
4320        assert_eq!(comms[0].value, FieldValue::Bytes(&val[..]));
4321    }
4322
4323    #[test]
4324    fn parse_bgp_prefix_sid_srv6_l2_service() {
4325        let mut sid_info_val = vec![0x00];
4326        sid_info_val.extend_from_slice(&[0xfd, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
4327        sid_info_val.push(0x00);
4328        sid_info_val.extend_from_slice(&0x0014u16.to_be_bytes());
4329        sid_info_val.push(0x00);
4330        let sub_tlv = build_psid_tlv(1, &sid_info_val);
4331        let mut service_val = vec![0x00];
4332        service_val.extend_from_slice(&sub_tlv);
4333        let tlv = build_psid_tlv(6, &service_val);
4334        let attr = build_attr(0xC0 | 0x10, 40, &tlv);
4335        let data = build_update(&attr, &[]);
4336        let mut buf = DissectBuffer::new();
4337        BgpDissector.dissect(&data, &mut buf, 0).unwrap();
4338
4339        let pa_value = extract_pa_value(&buf);
4340        let FieldValue::Array(tlvs_range) = pa_value else {
4341            panic!("expected Array");
4342        };
4343        let tlvs = buf.nested_fields(tlvs_range);
4344        assert!(tlvs[0].value.is_object());
4345        let FieldValue::Object(ref obj_range) = tlvs[0].value else {
4346            panic!("expected Object");
4347        };
4348        assert_eq!(
4349            *nested_field_value(&buf, obj_range, "type"),
4350            FieldValue::U8(6)
4351        );
4352        let sub_tlvs_field = nested_field_by_name(&buf, obj_range, "sub_tlvs");
4353        let FieldValue::Array(ref subs_range) = sub_tlvs_field.value else {
4354            panic!("expected Array");
4355        };
4356        let subs = buf.nested_fields(subs_range);
4357        assert!(subs[0].value.is_object());
4358        let FieldValue::Object(ref si_range) = subs[0].value else {
4359            panic!("expected Object");
4360        };
4361        assert_eq!(
4362            *nested_field_value(&buf, si_range, "endpoint_behavior"),
4363            FieldValue::U16(0x0014)
4364        );
4365    }
4366
4367    /// Helper: invoke a format_fn and return the output bytes as a String.
4368    fn call_format_fn(
4369        f: fn(&FieldValue<'_>, &FormatContext<'_>, &mut dyn std::io::Write) -> std::io::Result<()>,
4370        value: &FieldValue<'_>,
4371    ) -> String {
4372        let ctx = FormatContext {
4373            packet_data: &[],
4374            scratch: &[],
4375            layer_range: 0..0,
4376            field_range: 0..0,
4377        };
4378        let mut out = Vec::new();
4379        f(value, &ctx, &mut out).unwrap();
4380        String::from_utf8(out).unwrap()
4381    }
4382
4383    #[test]
4384    fn format_nlri_ipv4_prefix_cidr() {
4385        // /24 prefix: 192.168.1.0/24
4386        assert_eq!(
4387            call_format_fn(
4388                format_nlri_ipv4_prefix,
4389                &FieldValue::Bytes(&[24, 192, 168, 1])
4390            ),
4391            "\"192.168.1.0/24\""
4392        );
4393        // /32 host route: 10.0.0.1/32
4394        assert_eq!(
4395            call_format_fn(
4396                format_nlri_ipv4_prefix,
4397                &FieldValue::Bytes(&[32, 10, 0, 0, 1])
4398            ),
4399            "\"10.0.0.1/32\""
4400        );
4401        // /0 default route: 0.0.0.0/0
4402        assert_eq!(
4403            call_format_fn(format_nlri_ipv4_prefix, &FieldValue::Bytes(&[0])),
4404            "\"0.0.0.0/0\""
4405        );
4406        // /8 prefix: 10.0.0.0/8
4407        assert_eq!(
4408            call_format_fn(format_nlri_ipv4_prefix, &FieldValue::Bytes(&[8, 10])),
4409            "\"10.0.0.0/8\""
4410        );
4411        // Empty bytes → empty string
4412        assert_eq!(
4413            call_format_fn(format_nlri_ipv4_prefix, &FieldValue::Bytes(&[])),
4414            "\"\""
4415        );
4416        // Non-Bytes variant → empty string
4417        assert_eq!(
4418            call_format_fn(format_nlri_ipv4_prefix, &FieldValue::U8(0)),
4419            "\"\""
4420        );
4421    }
4422
4423    #[test]
4424    fn format_nlri_ipv6_prefix_cidr() {
4425        // /48 prefix: 2001:db8:1::/48
4426        assert_eq!(
4427            call_format_fn(
4428                format_nlri_ipv6_prefix,
4429                &FieldValue::Bytes(&[48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01])
4430            ),
4431            "\"2001:db8:1::/48\""
4432        );
4433        // /128 host route: 2001:db8::1/128
4434        let mut full = vec![128];
4435        let mut addr = [0u8; 16];
4436        addr[0] = 0x20;
4437        addr[1] = 0x01;
4438        addr[2] = 0x0d;
4439        addr[3] = 0xb8;
4440        addr[15] = 0x01;
4441        full.extend_from_slice(&addr);
4442        assert_eq!(
4443            call_format_fn(format_nlri_ipv6_prefix, &FieldValue::Bytes(&full)),
4444            "\"2001:db8::1/128\""
4445        );
4446        // /0 default route: ::/0
4447        assert_eq!(
4448            call_format_fn(format_nlri_ipv6_prefix, &FieldValue::Bytes(&[0])),
4449            "\"::/0\""
4450        );
4451        // Empty bytes → empty string
4452        assert_eq!(
4453            call_format_fn(format_nlri_ipv6_prefix, &FieldValue::Bytes(&[])),
4454            "\"\""
4455        );
4456        // Non-Bytes variant → empty string
4457        assert_eq!(
4458            call_format_fn(format_nlri_ipv6_prefix, &FieldValue::U8(0)),
4459            "\"\""
4460        );
4461    }
4462
4463    #[test]
4464    fn format_aggregator_values() {
4465        // 6-byte: 2-byte AS 65001 + IPv4 10.0.0.1
4466        assert_eq!(
4467            call_format_fn(
4468                format_aggregator,
4469                &FieldValue::Bytes(&[0xFD, 0xE9, 10, 0, 0, 1])
4470            ),
4471            "\"65001 10.0.0.1\""
4472        );
4473        // 8-byte: 4-byte AS 65001 + IPv4 10.0.0.1
4474        assert_eq!(
4475            call_format_fn(
4476                format_aggregator,
4477                &FieldValue::Bytes(&[0, 0, 0xFD, 0xE9, 10, 0, 0, 1])
4478            ),
4479            "\"65001 10.0.0.1\""
4480        );
4481        // Empty bytes → empty string
4482        assert_eq!(
4483            call_format_fn(format_aggregator, &FieldValue::Bytes(&[])),
4484            "\"\""
4485        );
4486        // Wrong size (7 bytes) → empty string
4487        assert_eq!(
4488            call_format_fn(
4489                format_aggregator,
4490                &FieldValue::Bytes(&[0, 0, 0, 0, 0, 0, 0])
4491            ),
4492            "\"\""
4493        );
4494        // Non-Bytes variant → empty string
4495        assert_eq!(
4496            call_format_fn(format_aggregator, &FieldValue::U8(0)),
4497            "\"\""
4498        );
4499    }
4500
4501    #[test]
4502    fn format_ext_community_values() {
4503        // Type 0x00: 2-Octet AS — AS 65001, value 100
4504        assert_eq!(
4505            call_format_fn(
4506                format_ext_community,
4507                &FieldValue::Bytes(&[0x00, 0x02, 0xFD, 0xE9, 0, 0, 0, 100])
4508            ),
4509            "\"65001:100\""
4510        );
4511        // Type 0x40: transitive 2-Octet AS — same format
4512        assert_eq!(
4513            call_format_fn(
4514                format_ext_community,
4515                &FieldValue::Bytes(&[0x40, 0x02, 0xFD, 0xE9, 0, 0, 0, 100])
4516            ),
4517            "\"65001:100\""
4518        );
4519        // Type 0x01: IPv4 Address — 10.0.0.1:100
4520        assert_eq!(
4521            call_format_fn(
4522                format_ext_community,
4523                &FieldValue::Bytes(&[0x01, 0x02, 10, 0, 0, 1, 0, 100])
4524            ),
4525            "\"10.0.0.1:100\""
4526        );
4527        // Type 0x41: transitive IPv4 Address
4528        assert_eq!(
4529            call_format_fn(
4530                format_ext_community,
4531                &FieldValue::Bytes(&[0x41, 0x02, 10, 0, 0, 1, 0, 100])
4532            ),
4533            "\"10.0.0.1:100\""
4534        );
4535        // Type 0x02: 4-Octet AS — AS 65001, value 100
4536        assert_eq!(
4537            call_format_fn(
4538                format_ext_community,
4539                &FieldValue::Bytes(&[0x02, 0x02, 0, 0, 0xFD, 0xE9, 0, 100])
4540            ),
4541            "\"65001:100\""
4542        );
4543        // Type 0x42: transitive 4-Octet AS
4544        assert_eq!(
4545            call_format_fn(
4546                format_ext_community,
4547                &FieldValue::Bytes(&[0x42, 0x02, 0, 0, 0xFD, 0xE9, 0, 100])
4548            ),
4549            "\"65001:100\""
4550        );
4551        // Unknown type → hex fallback
4552        assert_eq!(
4553            call_format_fn(
4554                format_ext_community,
4555                &FieldValue::Bytes(&[0x03, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
4556            ),
4557            "\"0x0300010203040506\""
4558        );
4559        // Empty bytes → empty string
4560        assert_eq!(
4561            call_format_fn(format_ext_community, &FieldValue::Bytes(&[])),
4562            "\"\""
4563        );
4564        // Non-Bytes variant → empty string
4565        assert_eq!(
4566            call_format_fn(format_ext_community, &FieldValue::U8(0)),
4567            "\"\""
4568        );
4569    }
4570
4571    #[test]
4572    fn format_large_community_values() {
4573        // 65001:100:200
4574        assert_eq!(
4575            call_format_fn(
4576                format_large_community,
4577                &FieldValue::Bytes(&[0, 0, 0xFD, 0xE9, 0, 0, 0, 100, 0, 0, 0, 200])
4578            ),
4579            "\"65001:100:200\""
4580        );
4581        // 0:0:0
4582        assert_eq!(
4583            call_format_fn(
4584                format_large_community,
4585                &FieldValue::Bytes(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
4586            ),
4587            "\"0:0:0\""
4588        );
4589        // Empty bytes → empty string
4590        assert_eq!(
4591            call_format_fn(format_large_community, &FieldValue::Bytes(&[])),
4592            "\"\""
4593        );
4594        // Wrong size (8 bytes) → empty string
4595        assert_eq!(
4596            call_format_fn(
4597                format_large_community,
4598                &FieldValue::Bytes(&[0, 0, 0, 0, 0, 0, 0, 0])
4599            ),
4600            "\"\""
4601        );
4602        // Non-Bytes variant → empty string
4603        assert_eq!(
4604            call_format_fn(format_large_community, &FieldValue::U8(0)),
4605            "\"\""
4606        );
4607    }
4608
4609    #[test]
4610    fn format_route_distinguisher_values() {
4611        // Type 0: 2-byte ASN 65001 + 4-byte assigned 100
4612        assert_eq!(
4613            call_format_fn(
4614                format_route_distinguisher,
4615                &FieldValue::Bytes(&[0, 0, 0xFD, 0xE9, 0, 0, 0, 100])
4616            ),
4617            "\"0:65001:100\""
4618        );
4619        // Type 1: IPv4 10.0.0.1 + 2-byte assigned 100
4620        assert_eq!(
4621            call_format_fn(
4622                format_route_distinguisher,
4623                &FieldValue::Bytes(&[0, 1, 10, 0, 0, 1, 0, 100])
4624            ),
4625            "\"1:10.0.0.1:100\""
4626        );
4627        // Type 2: 4-byte ASN 65001 + 2-byte assigned 100
4628        assert_eq!(
4629            call_format_fn(
4630                format_route_distinguisher,
4631                &FieldValue::Bytes(&[0, 2, 0, 0, 0xFD, 0xE9, 0, 100])
4632            ),
4633            "\"2:65001:100\""
4634        );
4635        // Unknown type → hex fallback
4636        assert_eq!(
4637            call_format_fn(
4638                format_route_distinguisher,
4639                &FieldValue::Bytes(&[0, 3, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06])
4640            ),
4641            "\"3:0x010203040506\""
4642        );
4643        // Empty bytes → empty string
4644        assert_eq!(
4645            call_format_fn(format_route_distinguisher, &FieldValue::Bytes(&[])),
4646            "\"\""
4647        );
4648        // Non-Bytes variant → empty string
4649        assert_eq!(
4650            call_format_fn(format_route_distinguisher, &FieldValue::U8(0)),
4651            "\"\""
4652        );
4653    }
4654
4655    #[test]
4656    fn format_teid_values() {
4657        // 0x12345678
4658        assert_eq!(
4659            call_format_fn(format_teid, &FieldValue::Bytes(&[0x12, 0x34, 0x56, 0x78])),
4660            "\"0x12345678\""
4661        );
4662        // Zero
4663        assert_eq!(
4664            call_format_fn(format_teid, &FieldValue::Bytes(&[0, 0, 0, 0])),
4665            "\"0x00000000\""
4666        );
4667        // Empty bytes → empty string
4668        assert_eq!(call_format_fn(format_teid, &FieldValue::Bytes(&[])), "\"\"");
4669        // Wrong size (3 bytes) → empty string
4670        assert_eq!(
4671            call_format_fn(format_teid, &FieldValue::Bytes(&[1, 2, 3])),
4672            "\"\""
4673        );
4674        // Non-Bytes variant → empty string
4675        assert_eq!(call_format_fn(format_teid, &FieldValue::U8(0)), "\"\"");
4676    }
4677}