Skip to main content

rustbgpd_wire/
flowspec.rs

1//! RFC 8955 / RFC 8956 `FlowSpec` NLRI codec and types.
2//!
3//! `FlowSpec` rules consist of ordered match components (type-value pairs) that
4//! describe traffic to filter. Each component uses either numeric operators
5//! (ports, protocol, length, DSCP) or bitmask operators (TCP flags, fragment).
6//!
7//! The wire format uses a length-prefixed TLV structure with operator bytes
8//! that encode comparison semantics and value sizes.
9
10use std::fmt;
11use std::net::Ipv4Addr;
12
13use crate::capability::Afi;
14use crate::error::{DecodeError, EncodeError};
15use crate::nlri::{Ipv4Prefix, Ipv6Prefix};
16
17/// Maximum encoded byte length of one `FlowSpec` rule NLRI.
18///
19/// RFC 8955 uses a 12-bit extended length form for a single `FlowSpec`
20/// rule, so values above 4095 cannot be represented without corrupting
21/// the length field.
22pub const MAX_FLOWSPEC_NLRI_RULE_LEN: usize = 0x0FFF;
23
24// ---------------------------------------------------------------------------
25// Numeric operator — RFC 8955 §3.1 Figure 2
26// ---------------------------------------------------------------------------
27
28/// A single numeric comparison term with operator flags and a value.
29///
30/// Multiple terms are combined with AND/OR logic per the `and_bit` flag;
31/// the `end_of_list` flag terminates the operator list for a component.
32#[expect(clippy::struct_excessive_bools)]
33#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct NumericMatch {
35    /// Last term in this operator list.
36    pub end_of_list: bool,
37    /// If true, AND with the previous term; otherwise OR.
38    pub and_bit: bool,
39    /// Less-than comparison flag.
40    pub lt: bool,
41    /// Greater-than comparison flag.
42    pub gt: bool,
43    /// Equal comparison flag.
44    pub eq: bool,
45    /// Numeric value to compare against.
46    pub value: u64,
47}
48
49/// A single bitmask comparison term.
50#[expect(clippy::struct_excessive_bools)]
51#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
52pub struct BitmaskMatch {
53    /// Last term in this operator list.
54    pub end_of_list: bool,
55    /// If true, AND with the previous term; otherwise OR.
56    pub and_bit: bool,
57    /// Negate the match result.
58    pub not_bit: bool,
59    /// If true, all specified bits must match; otherwise any bit suffices.
60    pub match_bit: bool,
61    /// Bitmask value to compare against.
62    pub value: u16,
63}
64
65// ---------------------------------------------------------------------------
66// IPv6 prefix with offset — RFC 8956 §3.1
67// ---------------------------------------------------------------------------
68
69/// IPv6 prefix with an additional bit offset for `FlowSpec` source/destination.
70#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
71pub struct Ipv6PrefixOffset {
72    /// The IPv6 prefix.
73    pub prefix: Ipv6Prefix,
74    /// Bit offset within the prefix (RFC 8956 §3.1).
75    pub offset: u8,
76}
77
78// ---------------------------------------------------------------------------
79// FlowSpec component — RFC 8955 §4
80// ---------------------------------------------------------------------------
81
82/// A single `FlowSpec` match component.
83///
84/// Components are identified by type code (1–13) and must be stored in
85/// ascending type order within a [`FlowSpecRule`].
86#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
87pub enum FlowSpecComponent {
88    /// Type 1: Destination IP prefix (IPv4 or IPv6 with offset).
89    DestinationPrefix(FlowSpecPrefix),
90    /// Type 2: Source IP prefix (IPv4 or IPv6 with offset).
91    SourcePrefix(FlowSpecPrefix),
92    /// Type 3: IP protocol (e.g., TCP=6, UDP=17).
93    IpProtocol(Vec<NumericMatch>),
94    /// Type 4: Port (source or destination).
95    Port(Vec<NumericMatch>),
96    /// Type 5: Destination port.
97    DestinationPort(Vec<NumericMatch>),
98    /// Type 6: Source port.
99    SourcePort(Vec<NumericMatch>),
100    /// Type 7: ICMP type.
101    IcmpType(Vec<NumericMatch>),
102    /// Type 8: ICMP code.
103    IcmpCode(Vec<NumericMatch>),
104    /// Type 9: TCP flags.
105    TcpFlags(Vec<BitmaskMatch>),
106    /// Type 10: Packet length.
107    PacketLength(Vec<NumericMatch>),
108    /// Type 11: DSCP value.
109    Dscp(Vec<NumericMatch>),
110    /// Type 12: Fragment flags.
111    Fragment(Vec<BitmaskMatch>),
112    /// Type 13: Flow label (IPv6 only, RFC 8956).
113    FlowLabel(Vec<NumericMatch>),
114}
115
116/// Prefix value for `FlowSpec` destination/source components.
117#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
118pub enum FlowSpecPrefix {
119    /// IPv4 prefix.
120    V4(Ipv4Prefix),
121    /// IPv6 prefix with bit offset.
122    V6(Ipv6PrefixOffset),
123}
124
125impl FlowSpecComponent {
126    /// Return the wire type code for this component.
127    #[must_use]
128    pub fn type_code(&self) -> u8 {
129        match self {
130            Self::DestinationPrefix(_) => 1,
131            Self::SourcePrefix(_) => 2,
132            Self::IpProtocol(_) => 3,
133            Self::Port(_) => 4,
134            Self::DestinationPort(_) => 5,
135            Self::SourcePort(_) => 6,
136            Self::IcmpType(_) => 7,
137            Self::IcmpCode(_) => 8,
138            Self::TcpFlags(_) => 9,
139            Self::PacketLength(_) => 10,
140            Self::Dscp(_) => 11,
141            Self::Fragment(_) => 12,
142            Self::FlowLabel(_) => 13,
143        }
144    }
145}
146
147// ---------------------------------------------------------------------------
148// FlowSpecRule — ordered set of components
149// ---------------------------------------------------------------------------
150
151/// A complete `FlowSpec` NLRI rule — an ordered set of match components.
152///
153/// Components must be sorted by ascending type code. A rule may contain at
154/// most one component of each type.
155#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
156pub struct FlowSpecRule {
157    /// Ordered match components (ascending type code).
158    pub components: Vec<FlowSpecComponent>,
159}
160
161impl FlowSpecRule {
162    /// Validate that components are in ascending type-code order.
163    ///
164    /// # Errors
165    ///
166    /// Returns `DecodeError` if the rule has no components or if components
167    /// are not in strictly ascending type-code order.
168    pub fn validate(&self) -> Result<(), DecodeError> {
169        if self.components.is_empty() {
170            return Err(DecodeError::MalformedField {
171                message_type: "UPDATE",
172                detail: "FlowSpec rule has no components".to_string(),
173            });
174        }
175        for window in self.components.windows(2) {
176            if window[0].type_code() >= window[1].type_code() {
177                return Err(DecodeError::MalformedField {
178                    message_type: "UPDATE",
179                    detail: format!(
180                        "FlowSpec components out of order: type {} >= {}",
181                        window[0].type_code(),
182                        window[1].type_code()
183                    ),
184                });
185            }
186        }
187        Ok(())
188    }
189
190    /// Return this rule's encoded `FlowSpec` NLRI payload length, excluding
191    /// the one- or two-byte `FlowSpec` rule length prefix.
192    #[must_use]
193    pub fn encoded_len(&self, afi: Afi) -> usize {
194        let mut rule_bytes = Vec::new();
195        encode_flowspec_rule(self, &mut rule_bytes, afi);
196        rule_bytes.len()
197    }
198
199    /// Validate that this rule can be represented by `FlowSpec`'s 12-bit rule
200    /// length field.
201    ///
202    /// # Errors
203    ///
204    /// Returns `EncodeError` if the encoded rule exceeds 4095 bytes.
205    pub fn validate_encoded_len(&self, afi: Afi) -> Result<(), EncodeError> {
206        let len = self.encoded_len(afi);
207        if len > MAX_FLOWSPEC_NLRI_RULE_LEN {
208            return Err(EncodeError::ValueOutOfRange {
209                field: "FlowSpec NLRI rule length",
210                value: len.to_string(),
211            });
212        }
213        Ok(())
214    }
215
216    /// Return a human-readable display string for logging / gRPC.
217    #[must_use]
218    pub fn display_string(&self) -> String {
219        let mut parts = Vec::new();
220        for c in &self.components {
221            parts.push(format_component(c));
222        }
223        parts.join(" && ")
224    }
225
226    /// Extract the destination prefix from the rule, if present.
227    #[must_use]
228    pub fn destination_prefix(&self) -> Option<crate::nlri::Prefix> {
229        self.components.iter().find_map(|c| match c {
230            FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(p)) => {
231                Some(crate::nlri::Prefix::V4(*p))
232            }
233            FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V6(p)) => {
234                Some(crate::nlri::Prefix::V6(p.prefix))
235            }
236            _ => None,
237        })
238    }
239}
240
241impl fmt::Display for FlowSpecRule {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        write!(f, "{}", self.display_string())
244    }
245}
246
247// ---------------------------------------------------------------------------
248// FlowSpec actions — RFC 8955 §7, RFC 7674
249// ---------------------------------------------------------------------------
250
251/// A traffic action decoded from a `FlowSpec` extended community.
252#[derive(Debug, Clone, PartialEq)]
253pub enum FlowSpecAction {
254    /// Traffic-rate (type 0x8006): rate=0.0 means drop.
255    TrafficRateBytes {
256        /// Informational ASN.
257        asn: u16,
258        /// Rate limit in bytes per second.
259        rate: f32,
260    },
261    /// Traffic-rate-packets (type 0x800c, RFC 8955 Appendix A).
262    TrafficRatePackets {
263        /// Informational ASN.
264        asn: u16,
265        /// Rate limit in packets per second.
266        rate: f32,
267    },
268    /// Traffic-action (type 0x8007): sample and/or terminal bits.
269    TrafficAction {
270        /// Sample matching traffic.
271        sample: bool,
272        /// Terminal action — do not evaluate further `FlowSpec` rules.
273        terminal: bool,
274    },
275    /// Traffic-marking (type 0x8009): set DSCP value.
276    TrafficMarking {
277        /// DSCP value to mark on matching packets.
278        dscp: u8,
279    },
280    /// Redirect 2-octet AS (type 0x8008).
281    Redirect2Octet {
282        /// Target 2-octet ASN.
283        asn: u16,
284        /// Local administrator value.
285        value: u32,
286    },
287    /// Redirect IPv4 (type 0x8108, RFC 7674).
288    RedirectIpv4 {
289        /// Target IPv4 address.
290        addr: Ipv4Addr,
291        /// Local administrator value.
292        value: u16,
293    },
294    /// Redirect 4-octet AS (type 0x8208, RFC 7674).
295    Redirect4Octet {
296        /// Target 4-octet ASN.
297        asn: u32,
298        /// Local administrator value.
299        value: u16,
300    },
301}
302
303impl crate::attribute::ExtendedCommunity {
304    /// Try to decode this extended community as a `FlowSpec` action.
305    #[must_use]
306    pub fn as_flowspec_action(&self) -> Option<FlowSpecAction> {
307        let raw = self.as_u64();
308        let bytes = raw.to_be_bytes();
309        let type_high = bytes[0];
310        let subtype = bytes[1];
311
312        match (type_high, subtype) {
313            // Traffic-rate bytes (transitive): 0x80, 0x06
314            (0x80, 0x06) => {
315                let asn = u16::from_be_bytes([bytes[2], bytes[3]]);
316                let rate = f32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
317                Some(FlowSpecAction::TrafficRateBytes { asn, rate })
318            }
319            // Traffic-action: 0x80, 0x07
320            (0x80, 0x07) => {
321                // bits in byte 7: bit 0 = terminal, bit 1 = sample
322                let flags = bytes[7];
323                Some(FlowSpecAction::TrafficAction {
324                    sample: flags & 0x02 != 0,
325                    terminal: flags & 0x01 != 0,
326                })
327            }
328            // Redirect 2-octet AS: 0x80, 0x08
329            (0x80, 0x08) => {
330                let asn = u16::from_be_bytes([bytes[2], bytes[3]]);
331                let value = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
332                Some(FlowSpecAction::Redirect2Octet { asn, value })
333            }
334            // Traffic-marking: 0x80, 0x09
335            (0x80, 0x09) => Some(FlowSpecAction::TrafficMarking {
336                dscp: bytes[7] & 0x3F,
337            }),
338            // Traffic-rate packets: 0x80, 0x0c
339            (0x80, 0x0c) => {
340                let asn = u16::from_be_bytes([bytes[2], bytes[3]]);
341                let rate = f32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
342                Some(FlowSpecAction::TrafficRatePackets { asn, rate })
343            }
344            // Redirect IPv4: 0x81, 0x08
345            (0x81, 0x08) => {
346                let addr = Ipv4Addr::new(bytes[2], bytes[3], bytes[4], bytes[5]);
347                let value = u16::from_be_bytes([bytes[6], bytes[7]]);
348                Some(FlowSpecAction::RedirectIpv4 { addr, value })
349            }
350            // Redirect 4-octet AS: 0x82, 0x08
351            (0x82, 0x08) => {
352                let asn = u32::from_be_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]);
353                let value = u16::from_be_bytes([bytes[6], bytes[7]]);
354                Some(FlowSpecAction::Redirect4Octet { asn, value })
355            }
356            _ => None,
357        }
358    }
359
360    /// Create an extended community from a `FlowSpec` action.
361    #[must_use]
362    pub fn from_flowspec_action(action: &FlowSpecAction) -> Self {
363        let mut bytes = [0u8; 8];
364        match action {
365            FlowSpecAction::TrafficRateBytes { asn, rate } => {
366                bytes[0] = 0x80;
367                bytes[1] = 0x06;
368                bytes[2..4].copy_from_slice(&asn.to_be_bytes());
369                bytes[4..8].copy_from_slice(&rate.to_be_bytes());
370            }
371            FlowSpecAction::TrafficRatePackets { asn, rate } => {
372                bytes[0] = 0x80;
373                bytes[1] = 0x0c;
374                bytes[2..4].copy_from_slice(&asn.to_be_bytes());
375                bytes[4..8].copy_from_slice(&rate.to_be_bytes());
376            }
377            FlowSpecAction::TrafficAction { sample, terminal } => {
378                bytes[0] = 0x80;
379                bytes[1] = 0x07;
380                let mut flags = 0u8;
381                if *sample {
382                    flags |= 0x02;
383                }
384                if *terminal {
385                    flags |= 0x01;
386                }
387                bytes[7] = flags;
388            }
389            FlowSpecAction::TrafficMarking { dscp } => {
390                bytes[0] = 0x80;
391                bytes[1] = 0x09;
392                bytes[7] = *dscp & 0x3F;
393            }
394            FlowSpecAction::Redirect2Octet { asn, value } => {
395                bytes[0] = 0x80;
396                bytes[1] = 0x08;
397                bytes[2..4].copy_from_slice(&asn.to_be_bytes());
398                bytes[4..8].copy_from_slice(&value.to_be_bytes());
399            }
400            FlowSpecAction::RedirectIpv4 { addr, value } => {
401                bytes[0] = 0x81;
402                bytes[1] = 0x08;
403                bytes[2..6].copy_from_slice(&addr.octets());
404                bytes[6..8].copy_from_slice(&value.to_be_bytes());
405            }
406            FlowSpecAction::Redirect4Octet { asn, value } => {
407                bytes[0] = 0x82;
408                bytes[1] = 0x08;
409                bytes[2..6].copy_from_slice(&asn.to_be_bytes());
410                bytes[6..8].copy_from_slice(&value.to_be_bytes());
411            }
412        }
413        Self::new(u64::from_be_bytes(bytes))
414    }
415}
416
417// ---------------------------------------------------------------------------
418// Wire decode
419// ---------------------------------------------------------------------------
420
421/// Decode one or more `FlowSpec` NLRI rules from wire bytes.
422///
423/// Each rule is length-prefixed: 1-byte length if < 0xF0 (240),
424/// otherwise 2-byte big-endian length with first byte ≥ 0xF0.
425///
426/// # Errors
427///
428/// Returns `DecodeError` if the wire data is truncated, malformed, or
429/// contains components in invalid order.
430pub fn decode_flowspec_nlri(mut buf: &[u8], afi: Afi) -> Result<Vec<FlowSpecRule>, DecodeError> {
431    let mut rules = Vec::new();
432    while !buf.is_empty() {
433        // Length prefix
434        let (rule_len, consumed) = decode_flowspec_length(buf)?;
435        buf = &buf[consumed..];
436        if buf.len() < rule_len {
437            return Err(DecodeError::MalformedField {
438                message_type: "UPDATE",
439                detail: format!(
440                    "FlowSpec NLRI truncated: need {rule_len} bytes, have {}",
441                    buf.len()
442                ),
443            });
444        }
445        let rule_bytes = &buf[..rule_len];
446        buf = &buf[rule_len..];
447
448        let rule = decode_flowspec_rule(rule_bytes, afi)?;
449        rule.validate()?;
450        rules.push(rule);
451    }
452    Ok(rules)
453}
454
455/// Encode `FlowSpec` NLRI rules to wire bytes.
456///
457/// # Errors
458///
459/// Returns [`EncodeError`] if any rule exceeds the 4095-byte `FlowSpec` NLRI
460/// rule payload limit. On error, `buf` is restored to its original length.
461pub fn try_encode_flowspec_nlri(
462    rules: &[FlowSpecRule],
463    buf: &mut Vec<u8>,
464    afi: Afi,
465) -> Result<(), EncodeError> {
466    let start_len = buf.len();
467    for rule in rules {
468        let mut rule_bytes = Vec::new();
469        encode_flowspec_rule(rule, &mut rule_bytes, afi);
470        if rule_bytes.len() > MAX_FLOWSPEC_NLRI_RULE_LEN {
471            buf.truncate(start_len);
472            return Err(EncodeError::ValueOutOfRange {
473                field: "FlowSpec NLRI rule length",
474                value: rule_bytes.len().to_string(),
475            });
476        }
477        encode_flowspec_length(rule_bytes.len(), buf);
478        buf.extend_from_slice(&rule_bytes);
479    }
480    Ok(())
481}
482
483/// Encode `FlowSpec` NLRI rules to wire bytes.
484///
485/// Locally constructed rules must be checked with
486/// [`FlowSpecRule::validate_encoded_len`] before they reach this encoder.
487/// Rules decoded from a peer are already bounded by the on-wire 12-bit
488/// `FlowSpec` length prefix.
489pub(crate) fn encode_flowspec_nlri(rules: &[FlowSpecRule], buf: &mut Vec<u8>, afi: Afi) {
490    try_encode_flowspec_nlri(rules, buf, afi)
491        .expect("FlowSpec NLRI encoder received an oversized rule");
492}
493
494/// Decode `FlowSpec` NLRI length prefix.
495fn decode_flowspec_length(buf: &[u8]) -> Result<(usize, usize), DecodeError> {
496    if buf.is_empty() {
497        return Err(DecodeError::MalformedField {
498            message_type: "UPDATE",
499            detail: "FlowSpec NLRI length: empty buffer".to_string(),
500        });
501    }
502    if buf[0] < 0xF0 {
503        Ok((buf[0] as usize, 1))
504    } else {
505        if buf.len() < 2 {
506            return Err(DecodeError::MalformedField {
507                message_type: "UPDATE",
508                detail: "FlowSpec NLRI 2-byte length truncated".to_string(),
509            });
510        }
511        let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
512        // The top 4 bits are 0xF, so subtract 0xF000 to get real length
513        let real_len = len & 0x0FFF;
514        Ok((real_len, 2))
515    }
516}
517
518/// Encode `FlowSpec` NLRI length prefix.
519fn encode_flowspec_length(len: usize, buf: &mut Vec<u8>) {
520    if len < 0xF0 {
521        #[expect(clippy::cast_possible_truncation)]
522        buf.push(len as u8);
523    } else {
524        #[expect(clippy::cast_possible_truncation)]
525        let val = (0xF000 | (len & 0x0FFF)) as u16;
526        buf.extend_from_slice(&val.to_be_bytes());
527    }
528}
529
530/// Decode a single `FlowSpec` rule from its component bytes (after length prefix).
531fn decode_flowspec_rule(mut buf: &[u8], afi: Afi) -> Result<FlowSpecRule, DecodeError> {
532    let mut components = Vec::new();
533    while !buf.is_empty() {
534        let (component, consumed) = decode_component(buf, afi)?;
535        components.push(component);
536        buf = &buf[consumed..];
537    }
538    Ok(FlowSpecRule { components })
539}
540
541/// Decode a single `FlowSpec` component from the start of `buf`.
542/// Returns the component and the number of bytes consumed.
543fn decode_component(buf: &[u8], afi: Afi) -> Result<(FlowSpecComponent, usize), DecodeError> {
544    if buf.is_empty() {
545        return Err(DecodeError::MalformedField {
546            message_type: "UPDATE",
547            detail: "FlowSpec component: empty buffer".to_string(),
548        });
549    }
550
551    let type_code = buf[0];
552    let rest = &buf[1..];
553
554    match type_code {
555        1 | 2 => {
556            // Prefix component — encoding differs by AFI
557            let (prefix, consumed) = decode_prefix_component(rest, afi)?;
558            let component = if type_code == 1 {
559                FlowSpecComponent::DestinationPrefix(prefix)
560            } else {
561                FlowSpecComponent::SourcePrefix(prefix)
562            };
563            Ok((component, 1 + consumed))
564        }
565        3 => {
566            let (ops, consumed) = decode_numeric_ops(rest)?;
567            Ok((FlowSpecComponent::IpProtocol(ops), 1 + consumed))
568        }
569        4 => {
570            let (ops, consumed) = decode_numeric_ops(rest)?;
571            Ok((FlowSpecComponent::Port(ops), 1 + consumed))
572        }
573        5 => {
574            let (ops, consumed) = decode_numeric_ops(rest)?;
575            Ok((FlowSpecComponent::DestinationPort(ops), 1 + consumed))
576        }
577        6 => {
578            let (ops, consumed) = decode_numeric_ops(rest)?;
579            Ok((FlowSpecComponent::SourcePort(ops), 1 + consumed))
580        }
581        7 => {
582            let (ops, consumed) = decode_numeric_ops(rest)?;
583            Ok((FlowSpecComponent::IcmpType(ops), 1 + consumed))
584        }
585        8 => {
586            let (ops, consumed) = decode_numeric_ops(rest)?;
587            Ok((FlowSpecComponent::IcmpCode(ops), 1 + consumed))
588        }
589        9 => {
590            let (ops, consumed) = decode_bitmask_ops(rest)?;
591            Ok((FlowSpecComponent::TcpFlags(ops), 1 + consumed))
592        }
593        10 => {
594            let (ops, consumed) = decode_numeric_ops(rest)?;
595            Ok((FlowSpecComponent::PacketLength(ops), 1 + consumed))
596        }
597        11 => {
598            let (ops, consumed) = decode_numeric_ops(rest)?;
599            Ok((FlowSpecComponent::Dscp(ops), 1 + consumed))
600        }
601        12 => {
602            let (ops, consumed) = decode_bitmask_ops(rest)?;
603            Ok((FlowSpecComponent::Fragment(ops), 1 + consumed))
604        }
605        13 => {
606            let (ops, consumed) = decode_numeric_ops(rest)?;
607            Ok((FlowSpecComponent::FlowLabel(ops), 1 + consumed))
608        }
609        _ => Err(DecodeError::MalformedField {
610            message_type: "UPDATE",
611            detail: format!("unknown FlowSpec component type {type_code}"),
612        }),
613    }
614}
615
616/// Decode a prefix component (types 1 and 2).
617fn decode_prefix_component(buf: &[u8], afi: Afi) -> Result<(FlowSpecPrefix, usize), DecodeError> {
618    match afi {
619        Afi::L2Vpn => Err(DecodeError::MalformedField {
620            message_type: "UPDATE",
621            detail: "FlowSpec prefix component not valid for L2VPN family".to_string(),
622        }),
623        Afi::Ipv4 => {
624            // IPv4: prefix-length (1 byte) + prefix bytes (ceil(len/8))
625            if buf.is_empty() {
626                return Err(DecodeError::MalformedField {
627                    message_type: "UPDATE",
628                    detail: "FlowSpec IPv4 prefix: missing length byte".to_string(),
629                });
630            }
631            let prefix_len = buf[0];
632            if prefix_len > 32 {
633                return Err(DecodeError::MalformedField {
634                    message_type: "UPDATE",
635                    detail: format!("FlowSpec IPv4 prefix length {prefix_len} > 32"),
636                });
637            }
638            let byte_count = (prefix_len as usize).div_ceil(8);
639            if buf.len() < 1 + byte_count {
640                return Err(DecodeError::MalformedField {
641                    message_type: "UPDATE",
642                    detail: "FlowSpec IPv4 prefix truncated".to_string(),
643                });
644            }
645            let mut octets = [0u8; 4];
646            octets[..byte_count].copy_from_slice(&buf[1..=byte_count]);
647            let addr = Ipv4Addr::from(octets);
648            Ok((
649                FlowSpecPrefix::V4(Ipv4Prefix::new(addr, prefix_len)),
650                1 + byte_count,
651            ))
652        }
653        Afi::Ipv6 => {
654            // IPv6: prefix-length (1) + offset (1) + prefix bytes
655            if buf.len() < 2 {
656                return Err(DecodeError::MalformedField {
657                    message_type: "UPDATE",
658                    detail: "FlowSpec IPv6 prefix: need length+offset bytes".to_string(),
659                });
660            }
661            let prefix_len = buf[0];
662            let offset = buf[1];
663            if prefix_len > 128 {
664                return Err(DecodeError::MalformedField {
665                    message_type: "UPDATE",
666                    detail: format!("FlowSpec IPv6 prefix length {prefix_len} > 128"),
667                });
668            }
669            let byte_count = (prefix_len as usize).div_ceil(8);
670            if buf.len() < 2 + byte_count {
671                return Err(DecodeError::MalformedField {
672                    message_type: "UPDATE",
673                    detail: "FlowSpec IPv6 prefix truncated".to_string(),
674                });
675            }
676            let mut octets = [0u8; 16];
677            octets[..byte_count].copy_from_slice(&buf[2..2 + byte_count]);
678            let addr = std::net::Ipv6Addr::from(octets);
679            Ok((
680                FlowSpecPrefix::V6(Ipv6PrefixOffset {
681                    prefix: Ipv6Prefix::new(addr, prefix_len),
682                    offset,
683                }),
684                2 + byte_count,
685            ))
686        }
687    }
688}
689
690/// Decode numeric operator+value pairs until end-of-list.
691fn decode_numeric_ops(mut buf: &[u8]) -> Result<(Vec<NumericMatch>, usize), DecodeError> {
692    let mut ops = Vec::new();
693    let start_len = buf.len();
694    loop {
695        if buf.is_empty() {
696            return Err(DecodeError::MalformedField {
697                message_type: "UPDATE",
698                detail: "FlowSpec numeric operators: unexpected end of data".to_string(),
699            });
700        }
701        let op_byte = buf[0];
702        buf = &buf[1..];
703
704        let end_of_list = op_byte & 0x80 != 0;
705        let and_bit = op_byte & 0x40 != 0;
706        let value_len_code = (op_byte >> 4) & 0x03;
707        let value_len = 1usize << value_len_code; // 1, 2, 4, or 8
708        let lt = op_byte & 0x04 != 0;
709        let gt = op_byte & 0x02 != 0;
710        let eq = op_byte & 0x01 != 0;
711
712        if buf.len() < value_len {
713            return Err(DecodeError::MalformedField {
714                message_type: "UPDATE",
715                detail: format!(
716                    "FlowSpec numeric value truncated: need {value_len}, have {}",
717                    buf.len()
718                ),
719            });
720        }
721
722        let value = match value_len {
723            1 => u64::from(buf[0]),
724            2 => u64::from(u16::from_be_bytes([buf[0], buf[1]])),
725            4 => u64::from(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]])),
726            8 => u64::from_be_bytes([
727                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
728            ]),
729            _ => unreachable!(),
730        };
731        buf = &buf[value_len..];
732
733        ops.push(NumericMatch {
734            end_of_list,
735            and_bit,
736            lt,
737            gt,
738            eq,
739            value,
740        });
741
742        if end_of_list {
743            break;
744        }
745    }
746    Ok((ops, start_len - buf.len()))
747}
748
749/// Decode bitmask operator+value pairs until end-of-list.
750fn decode_bitmask_ops(mut buf: &[u8]) -> Result<(Vec<BitmaskMatch>, usize), DecodeError> {
751    let mut ops = Vec::new();
752    let start_len = buf.len();
753    loop {
754        if buf.is_empty() {
755            return Err(DecodeError::MalformedField {
756                message_type: "UPDATE",
757                detail: "FlowSpec bitmask operators: unexpected end of data".to_string(),
758            });
759        }
760        let op_byte = buf[0];
761        buf = &buf[1..];
762
763        let end_of_list = op_byte & 0x80 != 0;
764        let and_bit = op_byte & 0x40 != 0;
765        let value_len_code = (op_byte >> 4) & 0x03;
766        let value_len = 1usize << value_len_code; // 1 or 2 for bitmask
767        let not_bit = op_byte & 0x02 != 0;
768        let match_bit = op_byte & 0x01 != 0;
769
770        if buf.len() < value_len {
771            return Err(DecodeError::MalformedField {
772                message_type: "UPDATE",
773                detail: format!(
774                    "FlowSpec bitmask value truncated: need {value_len}, have {}",
775                    buf.len()
776                ),
777            });
778        }
779
780        let value = match value_len {
781            1 => u16::from(buf[0]),
782            2 => u16::from_be_bytes([buf[0], buf[1]]),
783            _ => {
784                return Err(DecodeError::MalformedField {
785                    message_type: "UPDATE",
786                    detail: format!("FlowSpec bitmask value length {value_len} unsupported"),
787                });
788            }
789        };
790        buf = &buf[value_len..];
791
792        ops.push(BitmaskMatch {
793            end_of_list,
794            and_bit,
795            not_bit,
796            match_bit,
797            value,
798        });
799
800        if end_of_list {
801            break;
802        }
803    }
804    Ok((ops, start_len - buf.len()))
805}
806
807// ---------------------------------------------------------------------------
808// Wire encode
809// ---------------------------------------------------------------------------
810
811/// Encode a single `FlowSpec` rule to wire bytes (without the length prefix).
812fn encode_flowspec_rule(rule: &FlowSpecRule, buf: &mut Vec<u8>, afi: Afi) {
813    for component in &rule.components {
814        buf.push(component.type_code());
815        match component {
816            FlowSpecComponent::DestinationPrefix(p) | FlowSpecComponent::SourcePrefix(p) => {
817                encode_prefix_component(p, buf, afi);
818            }
819            FlowSpecComponent::IpProtocol(ops)
820            | FlowSpecComponent::Port(ops)
821            | FlowSpecComponent::DestinationPort(ops)
822            | FlowSpecComponent::SourcePort(ops)
823            | FlowSpecComponent::IcmpType(ops)
824            | FlowSpecComponent::IcmpCode(ops)
825            | FlowSpecComponent::PacketLength(ops)
826            | FlowSpecComponent::Dscp(ops)
827            | FlowSpecComponent::FlowLabel(ops) => {
828                encode_numeric_ops(ops, buf);
829            }
830            FlowSpecComponent::TcpFlags(ops) | FlowSpecComponent::Fragment(ops) => {
831                encode_bitmask_ops(ops, buf);
832            }
833        }
834    }
835}
836
837/// Encode a prefix component.
838fn encode_prefix_component(prefix: &FlowSpecPrefix, buf: &mut Vec<u8>, _afi: Afi) {
839    match prefix {
840        FlowSpecPrefix::V4(p) => {
841            buf.push(p.len);
842            let byte_count = (p.len as usize).div_ceil(8);
843            buf.extend_from_slice(&p.addr.octets()[..byte_count]);
844        }
845        FlowSpecPrefix::V6(p) => {
846            buf.push(p.prefix.len);
847            buf.push(p.offset);
848            let byte_count = (p.prefix.len as usize).div_ceil(8);
849            buf.extend_from_slice(&p.prefix.addr.octets()[..byte_count]);
850        }
851    }
852}
853
854/// Determine the minimum value-length code for a numeric value.
855fn numeric_value_len_code(value: u64) -> u8 {
856    if value <= 0xFF {
857        0 // 1 byte
858    } else if value <= 0xFFFF {
859        1 // 2 bytes
860    } else if value <= 0xFFFF_FFFF {
861        2 // 4 bytes
862    } else {
863        3 // 8 bytes
864    }
865}
866
867/// Encode numeric operator+value pairs.
868fn encode_numeric_ops(ops: &[NumericMatch], buf: &mut Vec<u8>) {
869    for (i, op) in ops.iter().enumerate() {
870        let is_last = i == ops.len() - 1;
871        let len_code = numeric_value_len_code(op.value);
872        let mut op_byte: u8 = 0;
873        if is_last {
874            op_byte |= 0x80; // end-of-list
875        }
876        if op.and_bit {
877            op_byte |= 0x40;
878        }
879        op_byte |= len_code << 4;
880        if op.lt {
881            op_byte |= 0x04;
882        }
883        if op.gt {
884            op_byte |= 0x02;
885        }
886        if op.eq {
887            op_byte |= 0x01;
888        }
889        buf.push(op_byte);
890        let value_len = 1usize << len_code;
891        match value_len {
892            1 => {
893                #[expect(clippy::cast_possible_truncation)]
894                buf.push(op.value as u8);
895            }
896            2 => {
897                #[expect(clippy::cast_possible_truncation)]
898                buf.extend_from_slice(&(op.value as u16).to_be_bytes());
899            }
900            4 => {
901                #[expect(clippy::cast_possible_truncation)]
902                buf.extend_from_slice(&(op.value as u32).to_be_bytes());
903            }
904            8 => buf.extend_from_slice(&op.value.to_be_bytes()),
905            _ => unreachable!(),
906        }
907    }
908}
909
910/// Determine the minimum value-length code for a bitmask value.
911fn bitmask_value_len_code(value: u16) -> u8 {
912    u8::from(value > 0xFF)
913}
914
915/// Encode bitmask operator+value pairs.
916fn encode_bitmask_ops(ops: &[BitmaskMatch], buf: &mut Vec<u8>) {
917    for (i, op) in ops.iter().enumerate() {
918        let is_last = i == ops.len() - 1;
919        let len_code = bitmask_value_len_code(op.value);
920        let mut op_byte: u8 = 0;
921        if is_last {
922            op_byte |= 0x80; // end-of-list
923        }
924        if op.and_bit {
925            op_byte |= 0x40;
926        }
927        op_byte |= len_code << 4;
928        if op.not_bit {
929            op_byte |= 0x02;
930        }
931        if op.match_bit {
932            op_byte |= 0x01;
933        }
934        buf.push(op_byte);
935        match 1usize << len_code {
936            1 => {
937                #[expect(clippy::cast_possible_truncation)]
938                buf.push(op.value as u8);
939            }
940            2 => buf.extend_from_slice(&op.value.to_be_bytes()),
941            _ => unreachable!(),
942        }
943    }
944}
945
946// ---------------------------------------------------------------------------
947// Display helpers
948// ---------------------------------------------------------------------------
949
950fn format_component(c: &FlowSpecComponent) -> String {
951    match c {
952        FlowSpecComponent::DestinationPrefix(p) => format!("dst {}", format_prefix(p)),
953        FlowSpecComponent::SourcePrefix(p) => format!("src {}", format_prefix(p)),
954        FlowSpecComponent::IpProtocol(ops) => format!("proto {}", format_numeric(ops)),
955        FlowSpecComponent::Port(ops) => format!("port {}", format_numeric(ops)),
956        FlowSpecComponent::DestinationPort(ops) => format!("dport {}", format_numeric(ops)),
957        FlowSpecComponent::SourcePort(ops) => format!("sport {}", format_numeric(ops)),
958        FlowSpecComponent::IcmpType(ops) => format!("icmp-type {}", format_numeric(ops)),
959        FlowSpecComponent::IcmpCode(ops) => format!("icmp-code {}", format_numeric(ops)),
960        FlowSpecComponent::TcpFlags(ops) => format!("tcp-flags {}", format_bitmask(ops)),
961        FlowSpecComponent::PacketLength(ops) => format!("pkt-len {}", format_numeric(ops)),
962        FlowSpecComponent::Dscp(ops) => format!("dscp {}", format_numeric(ops)),
963        FlowSpecComponent::Fragment(ops) => format!("fragment {}", format_bitmask(ops)),
964        FlowSpecComponent::FlowLabel(ops) => format!("flow-label {}", format_numeric(ops)),
965    }
966}
967
968fn format_prefix(p: &FlowSpecPrefix) -> String {
969    match p {
970        FlowSpecPrefix::V4(v4) => format!("{}/{}", v4.addr, v4.len),
971        FlowSpecPrefix::V6(v6) => {
972            if v6.offset == 0 {
973                format!("{}/{}", v6.prefix.addr, v6.prefix.len)
974            } else {
975                format!("{}/{} offset {}", v6.prefix.addr, v6.prefix.len, v6.offset)
976            }
977        }
978    }
979}
980
981fn format_numeric(ops: &[NumericMatch]) -> String {
982    let mut parts = Vec::new();
983    for op in ops {
984        let cmp = match (op.lt, op.gt, op.eq) {
985            (false, false, true) => "==",
986            (true, false, false) => "<",
987            (false, true, false) => ">",
988            (true, false, true) => "<=",
989            (false, true, true) => ">=",
990            (true, true, false) => "!=",
991            _ => "?",
992        };
993        parts.push(format!("{cmp}{}", op.value));
994    }
995    parts.join(",")
996}
997
998fn format_bitmask(ops: &[BitmaskMatch]) -> String {
999    let mut parts = Vec::new();
1000    for op in ops {
1001        let prefix = if op.not_bit { "!" } else { "" };
1002        let suffix = if op.match_bit { "/match" } else { "" };
1003        parts.push(format!("{prefix}0x{:x}{suffix}", op.value));
1004    }
1005    parts.join(",")
1006}
1007
1008// ---------------------------------------------------------------------------
1009// Tests
1010// ---------------------------------------------------------------------------
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use std::net::Ipv4Addr;
1016
1017    fn oversized_rule() -> FlowSpecRule {
1018        let mut ops: Vec<NumericMatch> = (0..2_200)
1019            .map(|i| NumericMatch {
1020                end_of_list: false,
1021                and_bit: i != 0,
1022                lt: false,
1023                gt: false,
1024                eq: true,
1025                value: i,
1026            })
1027            .collect();
1028        ops.last_mut().expect("non-empty test rule").end_of_list = true;
1029        FlowSpecRule {
1030            components: vec![FlowSpecComponent::Port(ops)],
1031        }
1032    }
1033
1034    fn exact_max_len_rule() -> FlowSpecRule {
1035        // 1 byte component type + 2047 two-byte numeric operators = 4095 bytes.
1036        let mut ops: Vec<NumericMatch> = (0..2047)
1037            .map(|i| NumericMatch {
1038                end_of_list: false,
1039                and_bit: i != 0,
1040                lt: false,
1041                gt: false,
1042                eq: true,
1043                value: 1,
1044            })
1045            .collect();
1046        ops.last_mut().expect("non-empty test rule").end_of_list = true;
1047        FlowSpecRule {
1048            components: vec![FlowSpecComponent::Port(ops)],
1049        }
1050    }
1051
1052    #[test]
1053    fn numeric_ops_roundtrip() {
1054        let ops = vec![
1055            NumericMatch {
1056                end_of_list: false,
1057                and_bit: false,
1058                lt: false,
1059                gt: false,
1060                eq: true,
1061                value: 6,
1062            },
1063            NumericMatch {
1064                end_of_list: true,
1065                and_bit: false,
1066                lt: false,
1067                gt: false,
1068                eq: true,
1069                value: 17,
1070            },
1071        ];
1072        let mut buf = Vec::new();
1073        encode_numeric_ops(&ops, &mut buf);
1074        let (decoded, consumed) = decode_numeric_ops(&buf).unwrap();
1075        assert_eq!(consumed, buf.len());
1076        assert_eq!(decoded.len(), 2);
1077        assert_eq!(decoded[0].value, 6);
1078        assert!(decoded[0].eq);
1079        assert_eq!(decoded[1].value, 17);
1080        assert!(decoded[1].end_of_list);
1081    }
1082
1083    #[test]
1084    fn bitmask_ops_roundtrip() {
1085        let ops = vec![BitmaskMatch {
1086            end_of_list: true,
1087            and_bit: false,
1088            not_bit: false,
1089            match_bit: true,
1090            value: 0x02, // SYN
1091        }];
1092        let mut buf = Vec::new();
1093        encode_bitmask_ops(&ops, &mut buf);
1094        let (decoded, consumed) = decode_bitmask_ops(&buf).unwrap();
1095        assert_eq!(consumed, buf.len());
1096        assert_eq!(decoded.len(), 1);
1097        assert_eq!(decoded[0].value, 0x02);
1098        assert!(decoded[0].match_bit);
1099    }
1100
1101    #[test]
1102    fn ipv4_prefix_component_roundtrip() {
1103        let prefix = FlowSpecPrefix::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8));
1104        let mut buf = Vec::new();
1105        encode_prefix_component(&prefix, &mut buf, Afi::Ipv4);
1106        let (decoded, consumed) = decode_prefix_component(&buf, Afi::Ipv4).unwrap();
1107        assert_eq!(consumed, buf.len());
1108        assert_eq!(decoded, prefix);
1109    }
1110
1111    #[test]
1112    fn ipv6_prefix_component_roundtrip() {
1113        let prefix = FlowSpecPrefix::V6(Ipv6PrefixOffset {
1114            prefix: Ipv6Prefix::new(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32),
1115            offset: 0,
1116        });
1117        let mut buf = Vec::new();
1118        encode_prefix_component(&prefix, &mut buf, Afi::Ipv6);
1119        let (decoded, consumed) = decode_prefix_component(&buf, Afi::Ipv6).unwrap();
1120        assert_eq!(consumed, buf.len());
1121        assert_eq!(decoded, prefix);
1122    }
1123
1124    #[test]
1125    fn simple_ipv4_rule_roundtrip() {
1126        let rule = FlowSpecRule {
1127            components: vec![
1128                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1129                    Ipv4Addr::new(10, 0, 0, 0),
1130                    24,
1131                ))),
1132                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1133                    end_of_list: true,
1134                    and_bit: false,
1135                    lt: false,
1136                    gt: false,
1137                    eq: true,
1138                    value: 6, // TCP
1139                }]),
1140                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1141                    end_of_list: true,
1142                    and_bit: false,
1143                    lt: false,
1144                    gt: false,
1145                    eq: true,
1146                    value: 80,
1147                }]),
1148            ],
1149        };
1150        let mut buf = Vec::new();
1151        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv4);
1152        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1153        assert_eq!(decoded.len(), 1);
1154        assert_eq!(decoded[0], rule);
1155    }
1156
1157    #[test]
1158    fn multi_rule_roundtrip() {
1159        let rule1 = FlowSpecRule {
1160            components: vec![FlowSpecComponent::IpProtocol(vec![NumericMatch {
1161                end_of_list: true,
1162                and_bit: false,
1163                lt: false,
1164                gt: false,
1165                eq: true,
1166                value: 17, // UDP
1167            }])],
1168        };
1169        let rule2 = FlowSpecRule {
1170            components: vec![FlowSpecComponent::DestinationPort(vec![NumericMatch {
1171                end_of_list: true,
1172                and_bit: false,
1173                lt: false,
1174                gt: false,
1175                eq: true,
1176                value: 53,
1177            }])],
1178        };
1179        let mut buf = Vec::new();
1180        encode_flowspec_nlri(&[rule1.clone(), rule2.clone()], &mut buf, Afi::Ipv4);
1181        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1182        assert_eq!(decoded.len(), 2);
1183        assert_eq!(decoded[0], rule1);
1184        assert_eq!(decoded[1], rule2);
1185    }
1186
1187    #[test]
1188    fn component_type_ordering_validated() {
1189        let rule = FlowSpecRule {
1190            components: vec![
1191                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1192                    end_of_list: true,
1193                    and_bit: false,
1194                    lt: false,
1195                    gt: false,
1196                    eq: true,
1197                    value: 80,
1198                }]),
1199                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1200                    end_of_list: true,
1201                    and_bit: false,
1202                    lt: false,
1203                    gt: false,
1204                    eq: true,
1205                    value: 6,
1206                }]),
1207            ],
1208        };
1209        assert!(rule.validate().is_err());
1210    }
1211
1212    #[test]
1213    fn empty_rule_rejected() {
1214        let rule = FlowSpecRule { components: vec![] };
1215        assert!(rule.validate().is_err());
1216    }
1217
1218    #[test]
1219    fn two_byte_length_prefix() {
1220        // Construct a rule large enough to need 2-byte length
1221        let mut buf = Vec::new();
1222        let len = 250; // > 0xF0
1223        encode_flowspec_length(len, &mut buf);
1224        assert_eq!(buf.len(), 2);
1225        let (decoded_len, consumed) = decode_flowspec_length(&buf).unwrap();
1226        assert_eq!(decoded_len, len);
1227        assert_eq!(consumed, 2);
1228    }
1229
1230    #[test]
1231    fn encoded_len_rejects_over_4095_byte_rule() {
1232        let rule = oversized_rule();
1233        assert!(rule.encoded_len(Afi::Ipv4) > MAX_FLOWSPEC_NLRI_RULE_LEN);
1234
1235        let err = rule.validate_encoded_len(Afi::Ipv4).unwrap_err();
1236        let EncodeError::ValueOutOfRange { field, value } = err else {
1237            panic!("expected ValueOutOfRange");
1238        };
1239        assert_eq!(field, "FlowSpec NLRI rule length");
1240        assert_eq!(value, rule.encoded_len(Afi::Ipv4).to_string());
1241    }
1242
1243    #[test]
1244    fn encoded_len_accepts_exact_4095_byte_rule() {
1245        let rule = exact_max_len_rule();
1246        assert_eq!(rule.encoded_len(Afi::Ipv4), MAX_FLOWSPEC_NLRI_RULE_LEN);
1247        rule.validate_encoded_len(Afi::Ipv4).unwrap();
1248    }
1249
1250    #[test]
1251    fn try_encode_flowspec_nlri_rejects_oversized_rule_without_mutating_buffer() {
1252        let mut buf = vec![0xaa, 0xbb];
1253        let rule = oversized_rule();
1254
1255        let err =
1256            try_encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv4).unwrap_err();
1257
1258        let EncodeError::ValueOutOfRange { field, value } = err else {
1259            panic!("expected ValueOutOfRange");
1260        };
1261        assert_eq!(field, "FlowSpec NLRI rule length");
1262        assert_eq!(value, rule.encoded_len(Afi::Ipv4).to_string());
1263        assert_eq!(buf, vec![0xaa, 0xbb]);
1264    }
1265
1266    #[test]
1267    fn one_byte_length_prefix() {
1268        let mut buf = Vec::new();
1269        let len = 100;
1270        encode_flowspec_length(len, &mut buf);
1271        assert_eq!(buf.len(), 1);
1272        let (decoded_len, consumed) = decode_flowspec_length(&buf).unwrap();
1273        assert_eq!(decoded_len, len);
1274        assert_eq!(consumed, 1);
1275    }
1276
1277    #[test]
1278    fn numeric_2byte_value() {
1279        let ops = vec![NumericMatch {
1280            end_of_list: true,
1281            and_bit: false,
1282            lt: false,
1283            gt: false,
1284            eq: true,
1285            value: 8080,
1286        }];
1287        let mut buf = Vec::new();
1288        encode_numeric_ops(&ops, &mut buf);
1289        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1290        assert_eq!(decoded[0].value, 8080);
1291    }
1292
1293    #[test]
1294    fn numeric_4byte_value() {
1295        let ops = vec![NumericMatch {
1296            end_of_list: true,
1297            and_bit: false,
1298            lt: false,
1299            gt: false,
1300            eq: true,
1301            value: 100_000,
1302        }];
1303        let mut buf = Vec::new();
1304        encode_numeric_ops(&ops, &mut buf);
1305        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1306        assert_eq!(decoded[0].value, 100_000);
1307    }
1308
1309    #[test]
1310    fn bitmask_2byte_value() {
1311        let ops = vec![BitmaskMatch {
1312            end_of_list: true,
1313            and_bit: false,
1314            not_bit: false,
1315            match_bit: true,
1316            value: 0x0FFF,
1317        }];
1318        let mut buf = Vec::new();
1319        encode_bitmask_ops(&ops, &mut buf);
1320        let (decoded, _) = decode_bitmask_ops(&buf).unwrap();
1321        assert_eq!(decoded[0].value, 0x0FFF);
1322    }
1323
1324    #[test]
1325    fn display_string_formatting() {
1326        let rule = FlowSpecRule {
1327            components: vec![
1328                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1329                    Ipv4Addr::new(192, 168, 1, 0),
1330                    24,
1331                ))),
1332                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1333                    end_of_list: true,
1334                    and_bit: false,
1335                    lt: false,
1336                    gt: false,
1337                    eq: true,
1338                    value: 6,
1339                }]),
1340            ],
1341        };
1342        let s = rule.display_string();
1343        assert!(s.contains("dst 192.168.1.0/24"));
1344        assert!(s.contains("proto ==6"));
1345    }
1346
1347    #[test]
1348    fn traffic_rate_bytes_action_roundtrip() {
1349        let action = FlowSpecAction::TrafficRateBytes {
1350            asn: 65000,
1351            rate: 0.0,
1352        };
1353        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1354        let decoded = ec.as_flowspec_action().unwrap();
1355        match decoded {
1356            FlowSpecAction::TrafficRateBytes { asn, rate } => {
1357                assert_eq!(asn, 65000);
1358                assert!((rate - 0.0).abs() < f32::EPSILON);
1359            }
1360            _ => panic!("wrong action type"),
1361        }
1362    }
1363
1364    #[test]
1365    fn traffic_action_roundtrip() {
1366        let action = FlowSpecAction::TrafficAction {
1367            sample: true,
1368            terminal: false,
1369        };
1370        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1371        let decoded = ec.as_flowspec_action().unwrap();
1372        match decoded {
1373            FlowSpecAction::TrafficAction { sample, terminal } => {
1374                assert!(sample);
1375                assert!(!terminal);
1376            }
1377            _ => panic!("wrong action type"),
1378        }
1379    }
1380
1381    #[test]
1382    fn traffic_marking_roundtrip() {
1383        let action = FlowSpecAction::TrafficMarking { dscp: 46 };
1384        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1385        let decoded = ec.as_flowspec_action().unwrap();
1386        assert!(matches!(
1387            decoded,
1388            FlowSpecAction::TrafficMarking { dscp: 46 }
1389        ));
1390    }
1391
1392    #[test]
1393    fn redirect_2octet_roundtrip() {
1394        let action = FlowSpecAction::Redirect2Octet {
1395            asn: 65001,
1396            value: 100,
1397        };
1398        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1399        let decoded = ec.as_flowspec_action().unwrap();
1400        assert!(matches!(
1401            decoded,
1402            FlowSpecAction::Redirect2Octet {
1403                asn: 65001,
1404                value: 100
1405            }
1406        ));
1407    }
1408
1409    #[test]
1410    fn redirect_ipv4_roundtrip() {
1411        let action = FlowSpecAction::RedirectIpv4 {
1412            addr: Ipv4Addr::new(10, 0, 0, 1),
1413            value: 200,
1414        };
1415        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1416        let decoded = ec.as_flowspec_action().unwrap();
1417        match decoded {
1418            FlowSpecAction::RedirectIpv4 { addr, value } => {
1419                assert_eq!(addr, Ipv4Addr::new(10, 0, 0, 1));
1420                assert_eq!(value, 200);
1421            }
1422            _ => panic!("wrong action type"),
1423        }
1424    }
1425
1426    #[test]
1427    fn redirect_4octet_roundtrip() {
1428        let action = FlowSpecAction::Redirect4Octet {
1429            asn: 400_000,
1430            value: 300,
1431        };
1432        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1433        let decoded = ec.as_flowspec_action().unwrap();
1434        match decoded {
1435            FlowSpecAction::Redirect4Octet { asn, value } => {
1436                assert_eq!(asn, 400_000);
1437                assert_eq!(value, 300);
1438            }
1439            _ => panic!("wrong action type"),
1440        }
1441    }
1442
1443    #[test]
1444    fn traffic_rate_packets_roundtrip() {
1445        let action = FlowSpecAction::TrafficRatePackets {
1446            asn: 0,
1447            rate: 1000.0,
1448        };
1449        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1450        let decoded = ec.as_flowspec_action().unwrap();
1451        match decoded {
1452            FlowSpecAction::TrafficRatePackets { asn, rate } => {
1453                assert_eq!(asn, 0);
1454                assert!((rate - 1000.0).abs() < f32::EPSILON);
1455            }
1456            _ => panic!("wrong action type"),
1457        }
1458    }
1459
1460    #[test]
1461    fn flow_label_ipv6_roundtrip() {
1462        let rule = FlowSpecRule {
1463            components: vec![FlowSpecComponent::FlowLabel(vec![NumericMatch {
1464                end_of_list: true,
1465                and_bit: false,
1466                lt: false,
1467                gt: false,
1468                eq: true,
1469                value: 12345,
1470            }])],
1471        };
1472        let mut buf = Vec::new();
1473        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv6);
1474        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv6).unwrap();
1475        assert_eq!(decoded[0], rule);
1476    }
1477
1478    #[test]
1479    fn ipv6_rule_with_prefix_and_flow_label() {
1480        let rule = FlowSpecRule {
1481            components: vec![
1482                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V6(Ipv6PrefixOffset {
1483                    prefix: Ipv6Prefix::new(
1484                        std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
1485                        32,
1486                    ),
1487                    offset: 0,
1488                })),
1489                FlowSpecComponent::FlowLabel(vec![NumericMatch {
1490                    end_of_list: true,
1491                    and_bit: false,
1492                    lt: false,
1493                    gt: false,
1494                    eq: true,
1495                    value: 42,
1496                }]),
1497            ],
1498        };
1499        let mut buf = Vec::new();
1500        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv6);
1501        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv6).unwrap();
1502        assert_eq!(decoded[0], rule);
1503    }
1504
1505    #[test]
1506    #[expect(clippy::too_many_lines)]
1507    fn all_13_component_types_in_order() {
1508        let rule = FlowSpecRule {
1509            components: vec![
1510                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1511                    Ipv4Addr::new(10, 0, 0, 0),
1512                    8,
1513                ))),
1514                FlowSpecComponent::SourcePrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1515                    Ipv4Addr::new(172, 16, 0, 0),
1516                    12,
1517                ))),
1518                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1519                    end_of_list: true,
1520                    and_bit: false,
1521                    lt: false,
1522                    gt: false,
1523                    eq: true,
1524                    value: 6,
1525                }]),
1526                FlowSpecComponent::Port(vec![NumericMatch {
1527                    end_of_list: true,
1528                    and_bit: false,
1529                    lt: false,
1530                    gt: false,
1531                    eq: true,
1532                    value: 80,
1533                }]),
1534                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1535                    end_of_list: true,
1536                    and_bit: false,
1537                    lt: false,
1538                    gt: false,
1539                    eq: true,
1540                    value: 443,
1541                }]),
1542                FlowSpecComponent::SourcePort(vec![NumericMatch {
1543                    end_of_list: true,
1544                    and_bit: false,
1545                    lt: false,
1546                    gt: true,
1547                    eq: false,
1548                    value: 1024,
1549                }]),
1550                FlowSpecComponent::IcmpType(vec![NumericMatch {
1551                    end_of_list: true,
1552                    and_bit: false,
1553                    lt: false,
1554                    gt: false,
1555                    eq: true,
1556                    value: 8,
1557                }]),
1558                FlowSpecComponent::IcmpCode(vec![NumericMatch {
1559                    end_of_list: true,
1560                    and_bit: false,
1561                    lt: false,
1562                    gt: false,
1563                    eq: true,
1564                    value: 0,
1565                }]),
1566                FlowSpecComponent::TcpFlags(vec![BitmaskMatch {
1567                    end_of_list: true,
1568                    and_bit: false,
1569                    not_bit: false,
1570                    match_bit: true,
1571                    value: 0x02,
1572                }]),
1573                FlowSpecComponent::PacketLength(vec![NumericMatch {
1574                    end_of_list: true,
1575                    and_bit: false,
1576                    lt: true,
1577                    gt: false,
1578                    eq: true,
1579                    value: 1500,
1580                }]),
1581                FlowSpecComponent::Dscp(vec![NumericMatch {
1582                    end_of_list: true,
1583                    and_bit: false,
1584                    lt: false,
1585                    gt: false,
1586                    eq: true,
1587                    value: 46,
1588                }]),
1589                FlowSpecComponent::Fragment(vec![BitmaskMatch {
1590                    end_of_list: true,
1591                    and_bit: false,
1592                    not_bit: false,
1593                    match_bit: true,
1594                    value: 0x01,
1595                }]),
1596                FlowSpecComponent::FlowLabel(vec![NumericMatch {
1597                    end_of_list: true,
1598                    and_bit: false,
1599                    lt: false,
1600                    gt: false,
1601                    eq: true,
1602                    value: 99999,
1603                }]),
1604            ],
1605        };
1606        assert!(rule.validate().is_ok());
1607        let mut buf = Vec::new();
1608        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv4);
1609        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1610        assert_eq!(decoded[0], rule);
1611    }
1612
1613    #[test]
1614    fn destination_prefix_extraction() {
1615        let rule = FlowSpecRule {
1616            components: vec![
1617                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1618                    Ipv4Addr::new(10, 0, 0, 0),
1619                    24,
1620                ))),
1621                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1622                    end_of_list: true,
1623                    and_bit: false,
1624                    lt: false,
1625                    gt: false,
1626                    eq: true,
1627                    value: 6,
1628                }]),
1629            ],
1630        };
1631        let prefix = rule.destination_prefix().unwrap();
1632        match prefix {
1633            crate::nlri::Prefix::V4(p) => {
1634                assert_eq!(p.addr, Ipv4Addr::new(10, 0, 0, 0));
1635                assert_eq!(p.len, 24);
1636            }
1637            crate::nlri::Prefix::V6(_) => panic!("expected V4 prefix"),
1638        }
1639    }
1640
1641    #[test]
1642    fn rule_without_destination_prefix() {
1643        let rule = FlowSpecRule {
1644            components: vec![FlowSpecComponent::IpProtocol(vec![NumericMatch {
1645                end_of_list: true,
1646                and_bit: false,
1647                lt: false,
1648                gt: false,
1649                eq: true,
1650                value: 17,
1651            }])],
1652        };
1653        assert!(rule.destination_prefix().is_none());
1654    }
1655
1656    #[test]
1657    fn and_bit_numeric_ops() {
1658        let ops = vec![
1659            NumericMatch {
1660                end_of_list: false,
1661                and_bit: false,
1662                lt: false,
1663                gt: true,
1664                eq: true,
1665                value: 100,
1666            },
1667            NumericMatch {
1668                end_of_list: true,
1669                and_bit: true,
1670                lt: true,
1671                gt: false,
1672                eq: true,
1673                value: 200,
1674            },
1675        ];
1676        let mut buf = Vec::new();
1677        encode_numeric_ops(&ops, &mut buf);
1678        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1679        assert_eq!(decoded.len(), 2);
1680        assert!(!decoded[0].and_bit);
1681        assert!(decoded[0].gt);
1682        assert!(decoded[0].eq);
1683        assert!(decoded[1].and_bit);
1684        assert!(decoded[1].lt);
1685        assert!(decoded[1].eq);
1686    }
1687}