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