Skip to main content

rustbgpd_wire/
capability.rs

1use bytes::{Buf, BufMut, Bytes};
2
3use crate::constants::{capability_code, param_type};
4use crate::error::{DecodeError, EncodeError};
5
6/// Address Family Identifier (IANA).
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[repr(u16)]
9pub enum Afi {
10    /// IPv4 (AFI 1).
11    Ipv4 = 1,
12    /// IPv6 (AFI 2).
13    Ipv6 = 2,
14    /// Layer-2 VPN (AFI 25) — carrier family for EVPN (RFC 7432).
15    L2Vpn = 25,
16}
17
18impl Afi {
19    /// Create from a raw 16-bit AFI value.
20    #[must_use]
21    pub fn from_u16(value: u16) -> Option<Self> {
22        match value {
23            1 => Some(Self::Ipv4),
24            2 => Some(Self::Ipv6),
25            25 => Some(Self::L2Vpn),
26            _ => None,
27        }
28    }
29}
30
31/// Subsequent Address Family Identifier (IANA).
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33#[repr(u8)]
34pub enum Safi {
35    /// Unicast forwarding (SAFI 1).
36    Unicast = 1,
37    /// Multicast forwarding (SAFI 2).
38    Multicast = 2,
39    /// RFC 7432 EVPN — only valid with [`Afi::L2Vpn`].
40    Evpn = 70,
41    /// RFC 8955 `FlowSpec`.
42    FlowSpec = 133,
43}
44
45impl Safi {
46    /// Create from a raw 8-bit SAFI value.
47    #[must_use]
48    pub fn from_u8(value: u8) -> Option<Self> {
49        match value {
50            1 => Some(Self::Unicast),
51            2 => Some(Self::Multicast),
52            70 => Some(Self::Evpn),
53            133 => Some(Self::FlowSpec),
54            _ => None,
55        }
56    }
57}
58
59/// Per-AFI/SAFI entry in the Graceful Restart capability (RFC 4724 §3).
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct GracefulRestartFamily {
62    /// Address family.
63    pub afi: Afi,
64    /// Sub-address family.
65    pub safi: Safi,
66    /// Whether the peer preserved forwarding state for this family.
67    pub forwarding_preserved: bool,
68}
69
70/// Add-Path send/receive mode per AFI/SAFI (RFC 7911 §4).
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72#[repr(u8)]
73pub enum AddPathMode {
74    /// Peer can receive multiple paths.
75    Receive = 1,
76    /// Peer can send multiple paths.
77    Send = 2,
78    /// Peer can both send and receive multiple paths.
79    Both = 3,
80}
81
82impl AddPathMode {
83    /// Create from a raw 8-bit mode value.
84    #[must_use]
85    pub fn from_u8(value: u8) -> Option<Self> {
86        match value {
87            1 => Some(Self::Receive),
88            2 => Some(Self::Send),
89            3 => Some(Self::Both),
90            _ => None,
91        }
92    }
93}
94
95/// Per-AFI/SAFI entry in the Add-Path capability (RFC 7911 §4).
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub struct AddPathFamily {
98    /// Address family.
99    pub afi: Afi,
100    /// Sub-address family.
101    pub safi: Safi,
102    /// Send/receive mode for this family.
103    pub send_receive: AddPathMode,
104}
105
106/// Per-family entry in the Extended Next Hop Encoding capability (RFC 8950).
107///
108/// Each tuple advertises that NLRI for `(nlri_afi, nlri_safi)` may use the
109/// specified `next_hop_afi` in `MP_REACH_NLRI`.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
111pub struct ExtendedNextHopFamily {
112    /// AFI of the NLRI.
113    pub nlri_afi: Afi,
114    /// SAFI of the NLRI.
115    pub nlri_safi: Safi,
116    /// AFI of the next-hop encoding.
117    pub next_hop_afi: Afi,
118}
119
120/// Per-AFI/SAFI entry in the Long-Lived Graceful Restart capability (RFC 9494).
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct LlgrFamily {
123    /// Address family.
124    pub afi: Afi,
125    /// Sub-address family.
126    pub safi: Safi,
127    /// Whether the peer preserved forwarding state for this family during LLGR.
128    pub forwarding_preserved: bool,
129    /// Long-lived stale time in seconds (24-bit, max `16_777_215` ≈ 194 days).
130    pub stale_time: u32,
131}
132
133/// BGP capability as negotiated in OPEN optional parameters.
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum Capability {
136    /// RFC 4760: Multi-Protocol Extensions.
137    MultiProtocol {
138        /// Address family.
139        afi: Afi,
140        /// Sub-address family.
141        safi: Safi,
142    },
143    /// RFC 8950: Extended Next Hop Encoding.
144    ExtendedNextHop(Vec<ExtendedNextHopFamily>),
145    /// RFC 4724: Graceful Restart.
146    GracefulRestart {
147        /// R-bit: the sender has restarted and its forwarding state
148        /// may have been preserved.
149        restart_state: bool,
150        /// N-bit (RFC 8538): the sender supports Notification GR — NOTIFICATIONs
151        /// trigger GR unless Cease/Hard Reset (subcode 9) is used.
152        notification: bool,
153        /// Time in seconds the sender will retain stale routes (12-bit, max 4095).
154        restart_time: u16,
155        /// Per-AFI/SAFI forwarding state flags.
156        families: Vec<GracefulRestartFamily>,
157    },
158    /// RFC 2918: Route Refresh.
159    RouteRefresh,
160    /// RFC 7313: Enhanced Route Refresh.
161    EnhancedRouteRefresh,
162    /// RFC 8654: Extended Messages (raise max message length to 65535).
163    ExtendedMessage,
164    /// RFC 9494: Long-Lived Graceful Restart.
165    LongLivedGracefulRestart(Vec<LlgrFamily>),
166    /// RFC 7911: Add-Path — advertise/receive multiple paths per prefix.
167    AddPath(Vec<AddPathFamily>),
168    /// RFC 6793: 4-Byte AS Number.
169    FourOctetAs {
170        /// The 4-byte autonomous system number.
171        asn: u32,
172    },
173    /// Unknown or unrecognized capability, preserved for re-emission.
174    Unknown {
175        /// Capability code.
176        code: u8,
177        /// Raw capability value bytes.
178        data: Bytes,
179    },
180}
181
182impl Capability {
183    /// Decode a single capability TLV from a buffer.
184    ///
185    /// # Errors
186    ///
187    /// Returns [`DecodeError::MalformedOptionalParameter`] if the TLV is
188    /// truncated or the claimed length exceeds the remaining bytes.
189    #[expect(clippy::too_many_lines)]
190    pub fn decode(buf: &mut impl Buf) -> Result<Self, DecodeError> {
191        if buf.remaining() < 2 {
192            return Err(DecodeError::MalformedOptionalParameter {
193                offset: 0,
194                detail: "capability TLV too short".into(),
195            });
196        }
197
198        let code = buf.get_u8();
199        let length = buf.get_u8();
200
201        if buf.remaining() < usize::from(length) {
202            return Err(DecodeError::MalformedOptionalParameter {
203                offset: 0,
204                detail: format!(
205                    "capability code {code} claims length {length}, \
206                     but only {} bytes remain",
207                    buf.remaining()
208                ),
209            });
210        }
211
212        match code {
213            capability_code::MULTI_PROTOCOL => {
214                if length != 4 {
215                    // Store as unknown if length is wrong
216                    let data = buf.copy_to_bytes(usize::from(length));
217                    return Ok(Capability::Unknown { code, data });
218                }
219                let afi_raw = buf.get_u16();
220                let _reserved = buf.get_u8();
221                let safi_raw = buf.get_u8();
222
223                if let (Some(afi), Some(safi)) = (Afi::from_u16(afi_raw), Safi::from_u8(safi_raw)) {
224                    Ok(Capability::MultiProtocol { afi, safi })
225                } else {
226                    // Unrecognized AFI/SAFI — store as unknown
227                    let mut data = bytes::BytesMut::with_capacity(4);
228                    data.put_u16(afi_raw);
229                    data.put_u8(0); // reserved
230                    data.put_u8(safi_raw);
231                    Ok(Capability::Unknown {
232                        code,
233                        data: data.freeze(),
234                    })
235                }
236            }
237            capability_code::ROUTE_REFRESH => {
238                if length != 0 {
239                    let data = buf.copy_to_bytes(usize::from(length));
240                    return Ok(Capability::Unknown { code, data });
241                }
242                Ok(Capability::RouteRefresh)
243            }
244            capability_code::ENHANCED_ROUTE_REFRESH => {
245                if length != 0 {
246                    let data = buf.copy_to_bytes(usize::from(length));
247                    return Ok(Capability::Unknown { code, data });
248                }
249                Ok(Capability::EnhancedRouteRefresh)
250            }
251            capability_code::EXTENDED_NEXT_HOP => {
252                // RFC 8950: repeated tuples of
253                // NLRI AFI (2) | NLRI SAFI (2) | Next Hop AFI (2)
254                if !usize::from(length).is_multiple_of(6) {
255                    let data = buf.copy_to_bytes(usize::from(length));
256                    return Ok(Capability::Unknown { code, data });
257                }
258                let entry_count = usize::from(length) / 6;
259                let raw_data = buf.copy_to_bytes(usize::from(length));
260                let mut cursor = raw_data.clone();
261                let mut families = Vec::with_capacity(entry_count);
262                let mut all_valid = true;
263                for _ in 0..entry_count {
264                    let nlri_afi_raw = cursor.get_u16();
265                    let nlri_safi_field = cursor.get_u16();
266                    let next_hop_afi_raw = cursor.get_u16();
267                    let nlri_safi = u8::try_from(nlri_safi_field).ok().and_then(Safi::from_u8);
268                    if let (Some(nlri_afi), Some(nlri_safi), Some(next_hop_afi)) = (
269                        Afi::from_u16(nlri_afi_raw),
270                        nlri_safi,
271                        Afi::from_u16(next_hop_afi_raw),
272                    ) {
273                        families.push(ExtendedNextHopFamily {
274                            nlri_afi,
275                            nlri_safi,
276                            next_hop_afi,
277                        });
278                    } else {
279                        all_valid = false;
280                    }
281                }
282                if all_valid {
283                    Ok(Capability::ExtendedNextHop(families))
284                } else {
285                    Ok(Capability::Unknown {
286                        code,
287                        data: raw_data,
288                    })
289                }
290            }
291            capability_code::EXTENDED_MESSAGE => {
292                if length != 0 {
293                    let data = buf.copy_to_bytes(usize::from(length));
294                    return Ok(Capability::Unknown { code, data });
295                }
296                Ok(Capability::ExtendedMessage)
297            }
298            capability_code::GRACEFUL_RESTART => {
299                // Minimum 2 bytes (restart flags/time). Each family is 4 bytes.
300                if length < 2 || !(length - 2).is_multiple_of(4) {
301                    let data = buf.copy_to_bytes(usize::from(length));
302                    return Ok(Capability::Unknown { code, data });
303                }
304                let flags_and_time = buf.get_u16();
305                let restart_state = (flags_and_time & 0x8000) != 0;
306                let notification = (flags_and_time & 0x4000) != 0;
307                let restart_time = flags_and_time & 0x0FFF;
308                let family_count = (length - 2) / 4;
309                let mut families = Vec::with_capacity(usize::from(family_count));
310                for _ in 0..family_count {
311                    let afi_raw = buf.get_u16();
312                    let safi_raw = buf.get_u8();
313                    let flags = buf.get_u8();
314                    if let (Some(afi), Some(safi)) =
315                        (Afi::from_u16(afi_raw), Safi::from_u8(safi_raw))
316                    {
317                        families.push(GracefulRestartFamily {
318                            afi,
319                            safi,
320                            forwarding_preserved: (flags & 0x80) != 0,
321                        });
322                    }
323                    // Skip unrecognized AFI/SAFI entries silently
324                }
325                Ok(Capability::GracefulRestart {
326                    restart_state,
327                    notification,
328                    restart_time,
329                    families,
330                })
331            }
332            capability_code::LONG_LIVED_GRACEFUL_RESTART => {
333                // RFC 9494: repeated entries of AFI(2) + SAFI(1) + flags(1) + stale_time(3) = 7 bytes each
334                if !usize::from(length).is_multiple_of(7) {
335                    let data = buf.copy_to_bytes(usize::from(length));
336                    return Ok(Capability::Unknown { code, data });
337                }
338                let entry_count = usize::from(length) / 7;
339                let raw_data = buf.copy_to_bytes(usize::from(length));
340                let mut cursor = raw_data.clone();
341                let mut families = Vec::with_capacity(entry_count);
342                let mut all_valid = true;
343                for _ in 0..entry_count {
344                    let afi_raw = cursor.get_u16();
345                    let safi_raw = cursor.get_u8();
346                    let flags = cursor.get_u8();
347                    // stale_time is 24-bit (3 bytes, big-endian)
348                    let st_hi = cursor.get_u8();
349                    let st_lo = cursor.get_u16();
350                    let stale_time = (u32::from(st_hi) << 16) | u32::from(st_lo);
351                    if let (Some(afi), Some(safi)) =
352                        (Afi::from_u16(afi_raw), Safi::from_u8(safi_raw))
353                    {
354                        families.push(LlgrFamily {
355                            afi,
356                            safi,
357                            forwarding_preserved: (flags & 0x80) != 0,
358                            stale_time,
359                        });
360                    } else {
361                        all_valid = false;
362                    }
363                }
364                if all_valid {
365                    Ok(Capability::LongLivedGracefulRestart(families))
366                } else {
367                    Ok(Capability::Unknown {
368                        code,
369                        data: raw_data,
370                    })
371                }
372            }
373            capability_code::ADD_PATH => {
374                // RFC 7911 §4: value is N entries of (AFI:2 + SAFI:1 + mode:1) = 4 bytes each
375                if length == 0 || !usize::from(length).is_multiple_of(4) {
376                    let data = buf.copy_to_bytes(usize::from(length));
377                    return Ok(Capability::Unknown { code, data });
378                }
379                let entry_count = usize::from(length) / 4;
380                // Snapshot the raw bytes before parsing so we can fall back
381                // to Unknown if any entry would be discarded (lossless roundtrip).
382                let raw_data = buf.copy_to_bytes(usize::from(length));
383                let mut cursor = raw_data.clone();
384                let mut families = Vec::with_capacity(entry_count);
385                let mut all_valid = true;
386                for _ in 0..entry_count {
387                    let afi_raw = cursor.get_u16();
388                    let safi_raw = cursor.get_u8();
389                    let mode_raw = cursor.get_u8();
390                    if let (Some(afi), Some(safi), Some(mode)) = (
391                        Afi::from_u16(afi_raw),
392                        Safi::from_u8(safi_raw),
393                        AddPathMode::from_u8(mode_raw),
394                    ) {
395                        families.push(AddPathFamily {
396                            afi,
397                            safi,
398                            send_receive: mode,
399                        });
400                    } else {
401                        all_valid = false;
402                    }
403                }
404                // Preserve as Unknown if any entry was unrecognized, to avoid
405                // silently rewriting malformed capability data on re-encode.
406                if all_valid {
407                    Ok(Capability::AddPath(families))
408                } else {
409                    Ok(Capability::Unknown {
410                        code,
411                        data: raw_data,
412                    })
413                }
414            }
415            capability_code::FOUR_OCTET_AS => {
416                if length != 4 {
417                    let data = buf.copy_to_bytes(usize::from(length));
418                    return Ok(Capability::Unknown { code, data });
419                }
420                let asn = buf.get_u32();
421                Ok(Capability::FourOctetAs { asn })
422            }
423            _ => {
424                let data = buf.copy_to_bytes(usize::from(length));
425                Ok(Capability::Unknown { code, data })
426            }
427        }
428    }
429
430    /// Encode a single capability TLV into a buffer.
431    ///
432    /// # Errors
433    ///
434    /// Returns [`EncodeError::ValueOutOfRange`] if the capability value
435    /// exceeds the 255-byte limit of the single-octet length field.
436    #[expect(
437        clippy::too_many_lines,
438        reason = "Capability encode keeps all TLV variants in one exhaustive wire encoder"
439    )]
440    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError> {
441        match self {
442            Capability::MultiProtocol { afi, safi } => {
443                buf.put_u8(capability_code::MULTI_PROTOCOL);
444                buf.put_u8(4); // length
445                buf.put_u16(*afi as u16);
446                buf.put_u8(0); // reserved
447                buf.put_u8(*safi as u8);
448            }
449            Capability::RouteRefresh => {
450                buf.put_u8(capability_code::ROUTE_REFRESH);
451                buf.put_u8(0); // zero-length value
452            }
453            Capability::EnhancedRouteRefresh => {
454                buf.put_u8(capability_code::ENHANCED_ROUTE_REFRESH);
455                buf.put_u8(0); // zero-length value
456            }
457            Capability::ExtendedNextHop(families) => {
458                let value_len = families.len() * 6;
459                if value_len > 255 {
460                    return Err(EncodeError::ValueOutOfRange {
461                        field: "extended_next_hop_capability_length",
462                        value: value_len.to_string(),
463                    });
464                }
465                buf.put_u8(capability_code::EXTENDED_NEXT_HOP);
466                #[expect(clippy::cast_possible_truncation)]
467                buf.put_u8(value_len as u8);
468                for fam in families {
469                    buf.put_u16(fam.nlri_afi as u16);
470                    buf.put_u16(u16::from(fam.nlri_safi as u8));
471                    buf.put_u16(fam.next_hop_afi as u16);
472                }
473            }
474            Capability::ExtendedMessage => {
475                buf.put_u8(capability_code::EXTENDED_MESSAGE);
476                buf.put_u8(0); // zero-length value
477            }
478            Capability::GracefulRestart {
479                restart_state,
480                notification,
481                restart_time,
482                families,
483            } => {
484                let value_len = 2 + families.len() * 4;
485                if value_len > 255 {
486                    return Err(EncodeError::ValueOutOfRange {
487                        field: "graceful_restart_capability_length",
488                        value: value_len.to_string(),
489                    });
490                }
491                if *restart_time > 4095 {
492                    return Err(EncodeError::ValueOutOfRange {
493                        field: "graceful_restart_time",
494                        value: restart_time.to_string(),
495                    });
496                }
497                buf.put_u8(capability_code::GRACEFUL_RESTART);
498                #[expect(clippy::cast_possible_truncation)]
499                buf.put_u8(value_len as u8);
500                let mut flags_and_time = *restart_time;
501                if *restart_state {
502                    flags_and_time |= 0x8000;
503                }
504                if *notification {
505                    flags_and_time |= 0x4000;
506                }
507                buf.put_u16(flags_and_time);
508                for fam in families {
509                    buf.put_u16(fam.afi as u16);
510                    buf.put_u8(fam.safi as u8);
511                    buf.put_u8(if fam.forwarding_preserved { 0x80 } else { 0 });
512                }
513            }
514            Capability::LongLivedGracefulRestart(families) => {
515                let value_len = families.len() * 7;
516                if value_len > 255 {
517                    return Err(EncodeError::ValueOutOfRange {
518                        field: "llgr_capability_length",
519                        value: value_len.to_string(),
520                    });
521                }
522                buf.put_u8(capability_code::LONG_LIVED_GRACEFUL_RESTART);
523                #[expect(clippy::cast_possible_truncation)]
524                buf.put_u8(value_len as u8);
525                for fam in families {
526                    buf.put_u16(fam.afi as u16);
527                    buf.put_u8(fam.safi as u8);
528                    buf.put_u8(if fam.forwarding_preserved { 0x80 } else { 0 });
529                    // stale_time is 24-bit (3 bytes, big-endian)
530                    #[expect(clippy::cast_possible_truncation)]
531                    buf.put_u8((fam.stale_time >> 16) as u8);
532                    buf.put_u16((fam.stale_time & 0xFFFF) as u16);
533                }
534            }
535            Capability::AddPath(families) => {
536                let value_len = families.len() * 4;
537                if value_len > 255 {
538                    return Err(EncodeError::ValueOutOfRange {
539                        field: "add_path_capability_length",
540                        value: value_len.to_string(),
541                    });
542                }
543                buf.put_u8(capability_code::ADD_PATH);
544                #[expect(clippy::cast_possible_truncation)]
545                buf.put_u8(value_len as u8);
546                for fam in families {
547                    buf.put_u16(fam.afi as u16);
548                    buf.put_u8(fam.safi as u8);
549                    buf.put_u8(fam.send_receive as u8);
550                }
551            }
552            Capability::FourOctetAs { asn } => {
553                buf.put_u8(capability_code::FOUR_OCTET_AS);
554                buf.put_u8(4); // length
555                buf.put_u32(*asn);
556            }
557            Capability::Unknown { code, data } => {
558                if data.len() > 255 {
559                    return Err(EncodeError::ValueOutOfRange {
560                        field: "unknown_capability_length",
561                        value: data.len().to_string(),
562                    });
563                }
564                buf.put_u8(*code);
565                #[expect(clippy::cast_possible_truncation)]
566                buf.put_u8(data.len() as u8);
567                buf.put_slice(data);
568            }
569        }
570        Ok(())
571    }
572
573    /// Returns the capability code byte.
574    #[must_use]
575    pub fn code(&self) -> u8 {
576        match self {
577            Self::MultiProtocol { .. } => capability_code::MULTI_PROTOCOL,
578            Self::RouteRefresh => capability_code::ROUTE_REFRESH,
579            Self::EnhancedRouteRefresh => capability_code::ENHANCED_ROUTE_REFRESH,
580            Self::ExtendedNextHop(_) => capability_code::EXTENDED_NEXT_HOP,
581            Self::ExtendedMessage => capability_code::EXTENDED_MESSAGE,
582            Self::LongLivedGracefulRestart(_) => capability_code::LONG_LIVED_GRACEFUL_RESTART,
583            Self::AddPath(_) => capability_code::ADD_PATH,
584            Self::GracefulRestart { .. } => capability_code::GRACEFUL_RESTART,
585            Self::FourOctetAs { .. } => capability_code::FOUR_OCTET_AS,
586            Self::Unknown { code, .. } => *code,
587        }
588    }
589
590    /// Encoded size of this capability TLV (code + length + value).
591    #[must_use]
592    pub fn encoded_len(&self) -> usize {
593        2 + match self {
594            Self::MultiProtocol { .. } | Self::FourOctetAs { .. } => 4,
595            Self::RouteRefresh | Self::EnhancedRouteRefresh | Self::ExtendedMessage => 0,
596            Self::ExtendedNextHop(families) => families.len() * 6,
597            Self::LongLivedGracefulRestart(families) => families.len() * 7,
598            Self::AddPath(families) => families.len() * 4,
599            Self::GracefulRestart { families, .. } => 2 + families.len() * 4,
600            Self::Unknown { data, .. } => data.len(),
601        }
602    }
603}
604
605/// Decode all optional parameters from an OPEN message body.
606/// Returns capabilities found in parameter type 2 TLVs.
607///
608/// # Errors
609///
610/// Returns [`DecodeError::MalformedOptionalParameter`] if any parameter TLV
611/// is truncated or contains an invalid capability.
612pub fn decode_optional_parameters(
613    buf: &mut impl Buf,
614    opt_params_len: u8,
615) -> Result<Vec<Capability>, DecodeError> {
616    let mut capabilities = Vec::new();
617    let mut remaining = usize::from(opt_params_len);
618
619    while remaining > 0 {
620        if buf.remaining() < 2 {
621            return Err(DecodeError::MalformedOptionalParameter {
622                offset: usize::from(opt_params_len) - remaining,
623                detail: "optional parameter TLV too short".into(),
624            });
625        }
626
627        let param_type = buf.get_u8();
628        let param_len = buf.get_u8();
629        remaining = remaining.saturating_sub(2);
630
631        if usize::from(param_len) > remaining || buf.remaining() < usize::from(param_len) {
632            return Err(DecodeError::MalformedOptionalParameter {
633                offset: usize::from(opt_params_len) - remaining,
634                detail: format!(
635                    "parameter type {param_type} claims length {param_len}, \
636                     but only {remaining} bytes remain"
637                ),
638            });
639        }
640
641        if param_type == param_type::CAPABILITIES {
642            // Parse capabilities from a bounded sub-buffer so a malformed
643            // capability length cannot consume into the next parameter or
644            // beyond the OPEN body.
645            let param_bytes = buf.copy_to_bytes(usize::from(param_len));
646            let mut cap_buf = param_bytes;
647            while cap_buf.has_remaining() {
648                let cap = Capability::decode(&mut cap_buf)?;
649                capabilities.push(cap);
650            }
651        } else {
652            // Skip unknown parameter types
653            buf.advance(usize::from(param_len));
654        }
655
656        remaining = remaining.saturating_sub(usize::from(param_len));
657    }
658
659    Ok(capabilities)
660}
661
662/// Encode capabilities as OPEN optional parameters (parameter type 2).
663///
664/// # Errors
665///
666/// Returns [`EncodeError::ValueOutOfRange`] if the total capabilities size
667/// exceeds 255 bytes or any individual capability is too large.
668///
669/// # Note
670///
671/// On error, partial bytes may have been written to `buf`. Callers should
672/// encode into a staging buffer (as `OpenMessage::encode` does) to ensure
673/// atomicity.
674pub fn encode_optional_parameters(
675    capabilities: &[Capability],
676    buf: &mut impl BufMut,
677) -> Result<(), EncodeError> {
678    if capabilities.is_empty() {
679        return Ok(());
680    }
681
682    // Calculate total capability TLV size
683    let cap_total: usize = capabilities.iter().map(Capability::encoded_len).sum();
684
685    if cap_total > 255 {
686        return Err(EncodeError::ValueOutOfRange {
687            field: "capabilities_parameter_length",
688            value: cap_total.to_string(),
689        });
690    }
691
692    // Parameter type 2 header
693    buf.put_u8(param_type::CAPABILITIES);
694    #[expect(clippy::cast_possible_truncation)]
695    buf.put_u8(cap_total as u8);
696
697    for cap in capabilities {
698        cap.encode(buf)?;
699    }
700    Ok(())
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706
707    #[test]
708    fn decode_multi_protocol_ipv4_unicast() {
709        let data: &[u8] = &[1, 4, 0, 1, 0, 1]; // code=1, len=4, AFI=1, res=0, SAFI=1
710        let mut buf = Bytes::copy_from_slice(data);
711        let cap = Capability::decode(&mut buf).unwrap();
712        assert_eq!(
713            cap,
714            Capability::MultiProtocol {
715                afi: Afi::Ipv4,
716                safi: Safi::Unicast
717            }
718        );
719    }
720
721    #[test]
722    fn decode_four_octet_as() {
723        let data: &[u8] = &[65, 4, 0, 0, 0xFD, 0xE8]; // code=65, len=4, ASN=65000
724        let mut buf = Bytes::copy_from_slice(data);
725        let cap = Capability::decode(&mut buf).unwrap();
726        assert_eq!(cap, Capability::FourOctetAs { asn: 65000 });
727    }
728
729    #[test]
730    fn decode_unknown_capability_preserved() {
731        let data: &[u8] = &[99, 3, 0xAA, 0xBB, 0xCC]; // code=99, len=3
732        let mut buf = Bytes::copy_from_slice(data);
733        let cap = Capability::decode(&mut buf).unwrap();
734        match cap {
735            Capability::Unknown { code, data } => {
736                assert_eq!(code, 99);
737                assert_eq!(data.as_ref(), &[0xAA, 0xBB, 0xCC]);
738            }
739            _ => panic!("expected Unknown"),
740        }
741    }
742
743    #[test]
744    fn unrecognized_afi_safi_stored_as_unknown() {
745        let data: &[u8] = &[1, 4, 0, 99, 0, 1]; // code=1, len=4, AFI=99 (unknown)
746        let mut buf = Bytes::copy_from_slice(data);
747        let cap = Capability::decode(&mut buf).unwrap();
748        assert!(matches!(cap, Capability::Unknown { code: 1, .. }));
749    }
750
751    #[test]
752    fn roundtrip_multi_protocol() {
753        let original = Capability::MultiProtocol {
754            afi: Afi::Ipv6,
755            safi: Safi::Unicast,
756        };
757        let mut encoded = bytes::BytesMut::with_capacity(6);
758        original.encode(&mut encoded).unwrap();
759        let mut buf = encoded.freeze();
760        let decoded = Capability::decode(&mut buf).unwrap();
761        assert_eq!(original, decoded);
762    }
763
764    #[test]
765    fn roundtrip_four_octet_as() {
766        let original = Capability::FourOctetAs { asn: 4_200_000_000 };
767        let mut encoded = bytes::BytesMut::with_capacity(6);
768        original.encode(&mut encoded).unwrap();
769        let mut buf = encoded.freeze();
770        let decoded = Capability::decode(&mut buf).unwrap();
771        assert_eq!(original, decoded);
772    }
773
774    #[test]
775    fn roundtrip_unknown() {
776        let original = Capability::Unknown {
777            code: 42,
778            data: Bytes::from_static(&[1, 2, 3]),
779        };
780        let mut encoded = bytes::BytesMut::with_capacity(5);
781        original.encode(&mut encoded).unwrap();
782        let mut buf = encoded.freeze();
783        let decoded = Capability::decode(&mut buf).unwrap();
784        assert_eq!(original, decoded);
785    }
786
787    #[test]
788    fn decode_optional_params_multiple_caps() {
789        // Parameter type=2, length=12, containing two capabilities
790        let mut data = bytes::BytesMut::new();
791        data.put_u8(2); // param type = capabilities
792        data.put_u8(12); // param length
793        // Cap 1: MultiProtocol IPv4 Unicast
794        data.put_u8(1);
795        data.put_u8(4);
796        data.put_u16(1); // AFI IPv4
797        data.put_u8(0);
798        data.put_u8(1); // SAFI Unicast
799        // Cap 2: FourOctetAs 65001
800        data.put_u8(65);
801        data.put_u8(4);
802        data.put_u32(65001);
803
804        let mut buf = data.freeze();
805        let caps = decode_optional_parameters(&mut buf, 14).unwrap();
806        assert_eq!(caps.len(), 2);
807        assert_eq!(
808            caps[0],
809            Capability::MultiProtocol {
810                afi: Afi::Ipv4,
811                safi: Safi::Unicast
812            }
813        );
814        assert_eq!(caps[1], Capability::FourOctetAs { asn: 65001 });
815    }
816
817    #[test]
818    fn decode_empty_optional_params() {
819        let mut buf = Bytes::new();
820        let caps = decode_optional_parameters(&mut buf, 0).unwrap();
821        assert!(caps.is_empty());
822    }
823
824    #[test]
825    fn reject_truncated_capability() {
826        let data: &[u8] = &[65, 4, 0, 0]; // FourOctetAs but only 2 bytes of value
827        let mut buf = Bytes::copy_from_slice(data);
828        assert!(Capability::decode(&mut buf).is_err());
829    }
830
831    #[test]
832    fn decode_graceful_restart_with_families() {
833        // code=64, len=10 (2 + 2*4), flags=0x80 (R-bit) | time=120
834        // Family 1: IPv4/Unicast, forwarding preserved
835        // Family 2: IPv6/Unicast, forwarding not preserved
836        let mut data = bytes::BytesMut::new();
837        data.put_u8(64); // code
838        data.put_u8(10); // length: 2 + 2*4
839        data.put_u16(0x8078); // R-bit set, restart_time=120
840        data.put_u16(1); // AFI IPv4
841        data.put_u8(1); // SAFI Unicast
842        data.put_u8(0x80); // forwarding preserved
843        data.put_u16(2); // AFI IPv6
844        data.put_u8(1); // SAFI Unicast
845        data.put_u8(0x00); // forwarding not preserved
846
847        let mut buf = data.freeze();
848        let cap = Capability::decode(&mut buf).unwrap();
849        assert_eq!(
850            cap,
851            Capability::GracefulRestart {
852                restart_state: true,
853                notification: false,
854                restart_time: 120,
855                families: vec![
856                    GracefulRestartFamily {
857                        afi: Afi::Ipv4,
858                        safi: Safi::Unicast,
859                        forwarding_preserved: true,
860                    },
861                    GracefulRestartFamily {
862                        afi: Afi::Ipv6,
863                        safi: Safi::Unicast,
864                        forwarding_preserved: false,
865                    },
866                ],
867            }
868        );
869    }
870
871    #[test]
872    fn decode_graceful_restart_no_r_bit() {
873        let mut data = bytes::BytesMut::new();
874        data.put_u8(64);
875        data.put_u8(6); // 2 + 1*4
876        data.put_u16(0x005A); // R-bit clear, restart_time=90
877        data.put_u16(1); // AFI IPv4
878        data.put_u8(1); // SAFI Unicast
879        data.put_u8(0x00); // forwarding not preserved
880
881        let mut buf = data.freeze();
882        let cap = Capability::decode(&mut buf).unwrap();
883        assert_eq!(
884            cap,
885            Capability::GracefulRestart {
886                restart_state: false,
887                notification: false,
888                restart_time: 90,
889                families: vec![GracefulRestartFamily {
890                    afi: Afi::Ipv4,
891                    safi: Safi::Unicast,
892                    forwarding_preserved: false,
893                }],
894            }
895        );
896    }
897
898    #[test]
899    fn decode_graceful_restart_empty_families() {
900        let mut data = bytes::BytesMut::new();
901        data.put_u8(64);
902        data.put_u8(2); // just the flags/time, no families
903        data.put_u16(0x003C); // time=60
904
905        let mut buf = data.freeze();
906        let cap = Capability::decode(&mut buf).unwrap();
907        assert_eq!(
908            cap,
909            Capability::GracefulRestart {
910                restart_state: false,
911                notification: false,
912                restart_time: 60,
913                families: vec![],
914            }
915        );
916    }
917
918    #[test]
919    fn roundtrip_graceful_restart() {
920        let original = Capability::GracefulRestart {
921            restart_state: true,
922            notification: false,
923            restart_time: 120,
924            families: vec![
925                GracefulRestartFamily {
926                    afi: Afi::Ipv4,
927                    safi: Safi::Unicast,
928                    forwarding_preserved: true,
929                },
930                GracefulRestartFamily {
931                    afi: Afi::Ipv6,
932                    safi: Safi::Unicast,
933                    forwarding_preserved: false,
934                },
935            ],
936        };
937        let mut encoded = bytes::BytesMut::with_capacity(12);
938        original.encode(&mut encoded).unwrap();
939        let mut buf = encoded.freeze();
940        let decoded = Capability::decode(&mut buf).unwrap();
941        assert_eq!(original, decoded);
942    }
943
944    #[test]
945    fn graceful_restart_encoded_len() {
946        let cap = Capability::GracefulRestart {
947            restart_state: false,
948            notification: false,
949            restart_time: 120,
950            families: vec![GracefulRestartFamily {
951                afi: Afi::Ipv4,
952                safi: Safi::Unicast,
953                forwarding_preserved: true,
954            }],
955        };
956        // code(1) + length(1) + flags_time(2) + 1 family(4) = 8
957        assert_eq!(cap.encoded_len(), 8);
958    }
959
960    #[test]
961    fn graceful_restart_code() {
962        let cap = Capability::GracefulRestart {
963            restart_state: false,
964            notification: false,
965            restart_time: 0,
966            families: vec![],
967        };
968        assert_eq!(cap.code(), 64);
969    }
970
971    #[test]
972    fn graceful_restart_bad_length_stored_as_unknown() {
973        // Length 3 is invalid (not 2 + N*4)
974        let data: &[u8] = &[64, 3, 0x00, 0x3C, 0xFF];
975        let mut buf = Bytes::copy_from_slice(data);
976        let cap = Capability::decode(&mut buf).unwrap();
977        assert!(matches!(cap, Capability::Unknown { code: 64, .. }));
978    }
979
980    #[test]
981    fn encode_rejects_oversized_gr_families() {
982        // 64 families → value_len = 2 + 64*4 = 258, exceeds u8
983        let families: Vec<GracefulRestartFamily> = (0..64)
984            .map(|_| GracefulRestartFamily {
985                afi: Afi::Ipv4,
986                safi: Safi::Unicast,
987                forwarding_preserved: false,
988            })
989            .collect();
990        let cap = Capability::GracefulRestart {
991            restart_state: false,
992            notification: false,
993            restart_time: 120,
994            families,
995        };
996        let mut buf = bytes::BytesMut::new();
997        assert!(cap.encode(&mut buf).is_err());
998    }
999
1000    #[test]
1001    fn encode_rejects_oversized_unknown_data() {
1002        let cap = Capability::Unknown {
1003            code: 99,
1004            data: Bytes::from(vec![0u8; 256]),
1005        };
1006        let mut buf = bytes::BytesMut::new();
1007        assert!(cap.encode(&mut buf).is_err());
1008    }
1009
1010    #[test]
1011    fn encode_optional_params_rejects_overflow() {
1012        // Total capabilities exceeding 255 bytes
1013        let caps: Vec<Capability> = (0..50)
1014            .map(|_| Capability::Unknown {
1015                code: 99,
1016                data: Bytes::from(vec![0u8; 5]),
1017            })
1018            .collect();
1019        // 50 caps * 7 bytes each = 350 > 255
1020        let mut buf = bytes::BytesMut::new();
1021        assert!(encode_optional_parameters(&caps, &mut buf).is_err());
1022    }
1023
1024    #[test]
1025    fn encode_rejects_restart_time_over_4095() {
1026        let cap = Capability::GracefulRestart {
1027            restart_state: false,
1028            notification: false,
1029            restart_time: 4096,
1030            families: vec![],
1031        };
1032        let mut buf = bytes::BytesMut::new();
1033        assert!(cap.encode(&mut buf).is_err());
1034    }
1035
1036    #[test]
1037    fn encode_accepts_restart_time_at_4095() {
1038        let cap = Capability::GracefulRestart {
1039            restart_state: false,
1040            notification: false,
1041            restart_time: 4095,
1042            families: vec![],
1043        };
1044        let mut buf = bytes::BytesMut::new();
1045        assert!(cap.encode(&mut buf).is_ok());
1046    }
1047
1048    #[test]
1049    fn decode_graceful_restart_n_bit() {
1050        let mut data = bytes::BytesMut::new();
1051        data.put_u8(64);
1052        data.put_u8(6); // 2 + 1*4
1053        data.put_u16(0xC078); // R-bit + N-bit set, restart_time=120
1054        data.put_u16(1); // AFI IPv4
1055        data.put_u8(1); // SAFI Unicast
1056        data.put_u8(0x80); // forwarding preserved
1057
1058        let mut buf = data.freeze();
1059        let cap = Capability::decode(&mut buf).unwrap();
1060        assert_eq!(
1061            cap,
1062            Capability::GracefulRestart {
1063                restart_state: true,
1064                notification: true,
1065                restart_time: 120,
1066                families: vec![GracefulRestartFamily {
1067                    afi: Afi::Ipv4,
1068                    safi: Safi::Unicast,
1069                    forwarding_preserved: true,
1070                }],
1071            }
1072        );
1073    }
1074
1075    #[test]
1076    fn roundtrip_graceful_restart_with_n_bit() {
1077        let original = Capability::GracefulRestart {
1078            restart_state: true,
1079            notification: true,
1080            restart_time: 120,
1081            families: vec![GracefulRestartFamily {
1082                afi: Afi::Ipv4,
1083                safi: Safi::Unicast,
1084                forwarding_preserved: true,
1085            }],
1086        };
1087        let mut encoded = bytes::BytesMut::with_capacity(12);
1088        original.encode(&mut encoded).unwrap();
1089        let mut buf = encoded.freeze();
1090        let decoded = Capability::decode(&mut buf).unwrap();
1091        assert_eq!(original, decoded);
1092    }
1093
1094    #[test]
1095    fn decode_capability_bounded_to_parameter_slice() {
1096        // Build optional parameters where the capability inside claims a
1097        // length that would overrun the parameter boundary.
1098        // Parameter: type=2, len=4 (only 4 bytes of capability data)
1099        // Capability inside: code=65 (FourOctetAs), len=8 (claims 8 but only 2 available)
1100        // Followed by: a valid second parameter that should not be consumed.
1101        let mut data = bytes::BytesMut::new();
1102        // Parameter 1: capabilities, len=4
1103        data.put_u8(2); // param type = capabilities
1104        data.put_u8(4); // param len = 4 bytes
1105        // Capability: code=65, len=8 (overflows the 4-byte parameter)
1106        data.put_u8(65);
1107        data.put_u8(8); // claims 8 bytes but only 2 remain in parameter
1108        data.put_u16(0xBEEF); // 2 bytes of data
1109        // Parameter 2: unknown type, should be untouched
1110        data.put_u8(99); // param type = unknown
1111        data.put_u8(2); // param len = 2
1112        data.put_u16(0xCAFE);
1113
1114        let mut buf = data.freeze();
1115        // Should fail because the capability overflows the parameter slice
1116        // Total is 8 bytes: param1(2+4) + param2(2+2) but we pass the full
1117        // length so the outer parser sees both parameters.
1118        let result = decode_optional_parameters(&mut buf, 8);
1119        assert!(result.is_err());
1120    }
1121
1122    #[test]
1123    fn decode_extended_message() {
1124        let data: &[u8] = &[6, 0]; // code=6, len=0
1125        let mut buf = Bytes::copy_from_slice(data);
1126        let cap = Capability::decode(&mut buf).unwrap();
1127        assert_eq!(cap, Capability::ExtendedMessage);
1128    }
1129
1130    #[test]
1131    fn roundtrip_extended_message() {
1132        let original = Capability::ExtendedMessage;
1133        let mut encoded = bytes::BytesMut::with_capacity(2);
1134        original.encode(&mut encoded).unwrap();
1135        let mut buf = encoded.freeze();
1136        let decoded = Capability::decode(&mut buf).unwrap();
1137        assert_eq!(original, decoded);
1138    }
1139
1140    #[test]
1141    fn extended_message_code_and_len() {
1142        let cap = Capability::ExtendedMessage;
1143        assert_eq!(cap.code(), 6);
1144        assert_eq!(cap.encoded_len(), 2);
1145    }
1146
1147    #[test]
1148    fn extended_message_bad_length_stored_as_unknown() {
1149        let data: &[u8] = &[6, 1, 0xFF]; // code=6, len=1 (should be 0)
1150        let mut buf = Bytes::copy_from_slice(data);
1151        let cap = Capability::decode(&mut buf).unwrap();
1152        assert!(matches!(cap, Capability::Unknown { code: 6, .. }));
1153    }
1154
1155    // --- Extended Next Hop capability tests ---
1156
1157    #[test]
1158    fn decode_extended_nexthop_single_family() {
1159        // code=5, len=6,
1160        // NLRI AFI=1 (IPv4), NLRI SAFI=1 (Unicast), NH AFI=2 (IPv6)
1161        let data: &[u8] = &[5, 6, 0, 1, 0, 1, 0, 2];
1162        let mut buf = Bytes::copy_from_slice(data);
1163        let cap = Capability::decode(&mut buf).unwrap();
1164        assert_eq!(
1165            cap,
1166            Capability::ExtendedNextHop(vec![ExtendedNextHopFamily {
1167                nlri_afi: Afi::Ipv4,
1168                nlri_safi: Safi::Unicast,
1169                next_hop_afi: Afi::Ipv6,
1170            }])
1171        );
1172    }
1173
1174    #[test]
1175    fn roundtrip_extended_nexthop() {
1176        let original = Capability::ExtendedNextHop(vec![ExtendedNextHopFamily {
1177            nlri_afi: Afi::Ipv4,
1178            nlri_safi: Safi::Unicast,
1179            next_hop_afi: Afi::Ipv6,
1180        }]);
1181        let mut encoded = bytes::BytesMut::with_capacity(8);
1182        original.encode(&mut encoded).unwrap();
1183        let mut buf = encoded.freeze();
1184        let decoded = Capability::decode(&mut buf).unwrap();
1185        assert_eq!(original, decoded);
1186    }
1187
1188    #[test]
1189    fn extended_nexthop_bad_length_stored_as_unknown() {
1190        // code=5, len=4 (must be multiple of 6)
1191        let data: &[u8] = &[5, 4, 0, 1, 0, 1];
1192        let mut buf = Bytes::copy_from_slice(data);
1193        let cap = Capability::decode(&mut buf).unwrap();
1194        assert!(matches!(cap, Capability::Unknown { code: 5, .. }));
1195    }
1196
1197    // --- Add-Path capability tests ---
1198
1199    #[test]
1200    fn decode_add_path_single_family() {
1201        // code=69, len=4, AFI=1(IPv4), SAFI=1(Unicast), mode=3(Both)
1202        let data: &[u8] = &[69, 4, 0, 1, 1, 3];
1203        let mut buf = Bytes::copy_from_slice(data);
1204        let cap = Capability::decode(&mut buf).unwrap();
1205        assert_eq!(
1206            cap,
1207            Capability::AddPath(vec![AddPathFamily {
1208                afi: Afi::Ipv4,
1209                safi: Safi::Unicast,
1210                send_receive: AddPathMode::Both,
1211            }])
1212        );
1213    }
1214
1215    #[test]
1216    fn decode_add_path_multiple_families() {
1217        let mut data = bytes::BytesMut::new();
1218        data.put_u8(69); // code
1219        data.put_u8(8); // len = 2 * 4
1220        data.put_u16(1); // AFI IPv4
1221        data.put_u8(1); // SAFI Unicast
1222        data.put_u8(1); // Receive
1223        data.put_u16(2); // AFI IPv6
1224        data.put_u8(1); // SAFI Unicast
1225        data.put_u8(2); // Send
1226
1227        let mut buf = data.freeze();
1228        let cap = Capability::decode(&mut buf).unwrap();
1229        assert_eq!(
1230            cap,
1231            Capability::AddPath(vec![
1232                AddPathFamily {
1233                    afi: Afi::Ipv4,
1234                    safi: Safi::Unicast,
1235                    send_receive: AddPathMode::Receive,
1236                },
1237                AddPathFamily {
1238                    afi: Afi::Ipv6,
1239                    safi: Safi::Unicast,
1240                    send_receive: AddPathMode::Send,
1241                },
1242            ])
1243        );
1244    }
1245
1246    #[test]
1247    fn roundtrip_add_path() {
1248        let original = Capability::AddPath(vec![
1249            AddPathFamily {
1250                afi: Afi::Ipv4,
1251                safi: Safi::Unicast,
1252                send_receive: AddPathMode::Both,
1253            },
1254            AddPathFamily {
1255                afi: Afi::Ipv6,
1256                safi: Safi::Unicast,
1257                send_receive: AddPathMode::Receive,
1258            },
1259        ]);
1260        let mut encoded = bytes::BytesMut::with_capacity(10);
1261        original.encode(&mut encoded).unwrap();
1262        let mut buf = encoded.freeze();
1263        let decoded = Capability::decode(&mut buf).unwrap();
1264        assert_eq!(original, decoded);
1265    }
1266
1267    #[test]
1268    fn add_path_code_and_len() {
1269        let cap = Capability::AddPath(vec![AddPathFamily {
1270            afi: Afi::Ipv4,
1271            safi: Safi::Unicast,
1272            send_receive: AddPathMode::Receive,
1273        }]);
1274        assert_eq!(cap.code(), 69);
1275        // code(1) + length(1) + 1 family(4) = 6
1276        assert_eq!(cap.encoded_len(), 6);
1277    }
1278
1279    #[test]
1280    fn add_path_bad_length_stored_as_unknown() {
1281        // code=69, len=3 (not multiple of 4)
1282        let data: &[u8] = &[69, 3, 0, 1, 1];
1283        let mut buf = Bytes::copy_from_slice(data);
1284        let cap = Capability::decode(&mut buf).unwrap();
1285        assert!(matches!(cap, Capability::Unknown { code: 69, .. }));
1286    }
1287
1288    #[test]
1289    fn add_path_zero_length_stored_as_unknown() {
1290        // code=69, len=0
1291        let data: &[u8] = &[69, 0];
1292        let mut buf = Bytes::copy_from_slice(data);
1293        let cap = Capability::decode(&mut buf).unwrap();
1294        assert!(matches!(cap, Capability::Unknown { code: 69, .. }));
1295    }
1296
1297    #[test]
1298    fn add_path_unknown_afi_preserved_as_unknown() {
1299        // code=69, len=4, AFI=99(unknown), SAFI=1, mode=3
1300        let data: &[u8] = &[69, 4, 0, 99, 1, 3];
1301        let mut buf = Bytes::copy_from_slice(data);
1302        let cap = Capability::decode(&mut buf).unwrap();
1303        // Unrecognized entry → preserve as Unknown for lossless roundtrip
1304        assert!(matches!(cap, Capability::Unknown { code: 69, .. }));
1305    }
1306
1307    #[test]
1308    fn add_path_invalid_mode_preserved_as_unknown() {
1309        // code=69, len=4, AFI=1, SAFI=1, mode=0 (invalid)
1310        let data: &[u8] = &[69, 4, 0, 1, 1, 0];
1311        let mut buf = Bytes::copy_from_slice(data);
1312        let cap = Capability::decode(&mut buf).unwrap();
1313        // Invalid mode → preserve as Unknown for lossless roundtrip
1314        assert!(matches!(cap, Capability::Unknown { code: 69, .. }));
1315    }
1316
1317    #[test]
1318    fn add_path_mixed_valid_and_invalid_preserved_as_unknown() {
1319        // Two entries: valid IPv4/Unicast/Both + invalid AFI=99
1320        let mut data = bytes::BytesMut::new();
1321        data.put_u8(69); // code
1322        data.put_u8(8); // len = 2 * 4
1323        data.put_u16(1); // AFI IPv4
1324        data.put_u8(1); // SAFI Unicast
1325        data.put_u8(3); // Both (valid)
1326        data.put_u16(99); // AFI unknown
1327        data.put_u8(1); // SAFI Unicast
1328        data.put_u8(3); // Both
1329        let mut buf = data.freeze();
1330        let cap = Capability::decode(&mut buf).unwrap();
1331        // One invalid entry → entire capability preserved as Unknown
1332        assert!(matches!(cap, Capability::Unknown { code: 69, .. }));
1333    }
1334
1335    #[test]
1336    fn llgr_capability_roundtrip() {
1337        let families = vec![
1338            LlgrFamily {
1339                afi: Afi::Ipv4,
1340                safi: Safi::Unicast,
1341                forwarding_preserved: true,
1342                stale_time: 86400,
1343            },
1344            LlgrFamily {
1345                afi: Afi::Ipv6,
1346                safi: Safi::Unicast,
1347                forwarding_preserved: false,
1348                stale_time: 3600,
1349            },
1350        ];
1351        let cap = Capability::LongLivedGracefulRestart(families);
1352
1353        let mut buf = bytes::BytesMut::new();
1354        cap.encode(&mut buf).unwrap();
1355        let mut frozen = buf.freeze();
1356        let decoded = Capability::decode(&mut frozen).unwrap();
1357
1358        match decoded {
1359            Capability::LongLivedGracefulRestart(fams) => {
1360                assert_eq!(fams.len(), 2);
1361                assert_eq!(fams[0].afi, Afi::Ipv4);
1362                assert_eq!(fams[0].safi, Safi::Unicast);
1363                assert!(fams[0].forwarding_preserved);
1364                assert_eq!(fams[0].stale_time, 86400);
1365                assert_eq!(fams[1].afi, Afi::Ipv6);
1366                assert_eq!(fams[1].safi, Safi::Unicast);
1367                assert!(!fams[1].forwarding_preserved);
1368                assert_eq!(fams[1].stale_time, 3600);
1369            }
1370            other => panic!("expected LongLivedGracefulRestart, got {other:?}"),
1371        }
1372    }
1373
1374    #[test]
1375    fn llgr_capability_max_stale_time() {
1376        let cap = Capability::LongLivedGracefulRestart(vec![LlgrFamily {
1377            afi: Afi::Ipv4,
1378            safi: Safi::Unicast,
1379            forwarding_preserved: false,
1380            stale_time: 0x00FF_FFFF, // 24-bit max
1381        }]);
1382
1383        let mut buf = bytes::BytesMut::new();
1384        cap.encode(&mut buf).unwrap();
1385        let mut frozen = buf.freeze();
1386        let decoded = Capability::decode(&mut frozen).unwrap();
1387
1388        match decoded {
1389            Capability::LongLivedGracefulRestart(fams) => {
1390                assert_eq!(fams[0].stale_time, 0x00FF_FFFF);
1391            }
1392            other => panic!("expected LongLivedGracefulRestart, got {other:?}"),
1393        }
1394    }
1395
1396    #[test]
1397    fn llgr_capability_empty() {
1398        let cap = Capability::LongLivedGracefulRestart(vec![]);
1399        let mut buf = bytes::BytesMut::new();
1400        cap.encode(&mut buf).unwrap();
1401        let mut frozen = buf.freeze();
1402        let decoded = Capability::decode(&mut frozen).unwrap();
1403        assert!(matches!(
1404            decoded,
1405            Capability::LongLivedGracefulRestart(fams) if fams.is_empty()
1406        ));
1407    }
1408}