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