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