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::L2Vpn => Err(DecodeError::MalformedField {
558            message_type: "UPDATE",
559            detail: "FlowSpec prefix component not valid for L2VPN family".to_string(),
560        }),
561        Afi::Ipv4 => {
562            // IPv4: prefix-length (1 byte) + prefix bytes (ceil(len/8))
563            if buf.is_empty() {
564                return Err(DecodeError::MalformedField {
565                    message_type: "UPDATE",
566                    detail: "FlowSpec IPv4 prefix: missing length byte".to_string(),
567                });
568            }
569            let prefix_len = buf[0];
570            if prefix_len > 32 {
571                return Err(DecodeError::MalformedField {
572                    message_type: "UPDATE",
573                    detail: format!("FlowSpec IPv4 prefix length {prefix_len} > 32"),
574                });
575            }
576            let byte_count = (prefix_len as usize).div_ceil(8);
577            if buf.len() < 1 + byte_count {
578                return Err(DecodeError::MalformedField {
579                    message_type: "UPDATE",
580                    detail: "FlowSpec IPv4 prefix truncated".to_string(),
581                });
582            }
583            let mut octets = [0u8; 4];
584            octets[..byte_count].copy_from_slice(&buf[1..=byte_count]);
585            let addr = Ipv4Addr::from(octets);
586            Ok((
587                FlowSpecPrefix::V4(Ipv4Prefix::new(addr, prefix_len)),
588                1 + byte_count,
589            ))
590        }
591        Afi::Ipv6 => {
592            // IPv6: prefix-length (1) + offset (1) + prefix bytes
593            if buf.len() < 2 {
594                return Err(DecodeError::MalformedField {
595                    message_type: "UPDATE",
596                    detail: "FlowSpec IPv6 prefix: need length+offset bytes".to_string(),
597                });
598            }
599            let prefix_len = buf[0];
600            let offset = buf[1];
601            if prefix_len > 128 {
602                return Err(DecodeError::MalformedField {
603                    message_type: "UPDATE",
604                    detail: format!("FlowSpec IPv6 prefix length {prefix_len} > 128"),
605                });
606            }
607            let byte_count = (prefix_len as usize).div_ceil(8);
608            if buf.len() < 2 + byte_count {
609                return Err(DecodeError::MalformedField {
610                    message_type: "UPDATE",
611                    detail: "FlowSpec IPv6 prefix truncated".to_string(),
612                });
613            }
614            let mut octets = [0u8; 16];
615            octets[..byte_count].copy_from_slice(&buf[2..2 + byte_count]);
616            let addr = std::net::Ipv6Addr::from(octets);
617            Ok((
618                FlowSpecPrefix::V6(Ipv6PrefixOffset {
619                    prefix: Ipv6Prefix::new(addr, prefix_len),
620                    offset,
621                }),
622                2 + byte_count,
623            ))
624        }
625    }
626}
627
628/// Decode numeric operator+value pairs until end-of-list.
629fn decode_numeric_ops(mut buf: &[u8]) -> Result<(Vec<NumericMatch>, usize), DecodeError> {
630    let mut ops = Vec::new();
631    let start_len = buf.len();
632    loop {
633        if buf.is_empty() {
634            return Err(DecodeError::MalformedField {
635                message_type: "UPDATE",
636                detail: "FlowSpec numeric operators: unexpected end of data".to_string(),
637            });
638        }
639        let op_byte = buf[0];
640        buf = &buf[1..];
641
642        let end_of_list = op_byte & 0x80 != 0;
643        let and_bit = op_byte & 0x40 != 0;
644        let value_len_code = (op_byte >> 4) & 0x03;
645        let value_len = 1usize << value_len_code; // 1, 2, 4, or 8
646        let lt = op_byte & 0x04 != 0;
647        let gt = op_byte & 0x02 != 0;
648        let eq = op_byte & 0x01 != 0;
649
650        if buf.len() < value_len {
651            return Err(DecodeError::MalformedField {
652                message_type: "UPDATE",
653                detail: format!(
654                    "FlowSpec numeric value truncated: need {value_len}, have {}",
655                    buf.len()
656                ),
657            });
658        }
659
660        let value = match value_len {
661            1 => u64::from(buf[0]),
662            2 => u64::from(u16::from_be_bytes([buf[0], buf[1]])),
663            4 => u64::from(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]])),
664            8 => u64::from_be_bytes([
665                buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
666            ]),
667            _ => unreachable!(),
668        };
669        buf = &buf[value_len..];
670
671        ops.push(NumericMatch {
672            end_of_list,
673            and_bit,
674            lt,
675            gt,
676            eq,
677            value,
678        });
679
680        if end_of_list {
681            break;
682        }
683    }
684    Ok((ops, start_len - buf.len()))
685}
686
687/// Decode bitmask operator+value pairs until end-of-list.
688fn decode_bitmask_ops(mut buf: &[u8]) -> Result<(Vec<BitmaskMatch>, usize), DecodeError> {
689    let mut ops = Vec::new();
690    let start_len = buf.len();
691    loop {
692        if buf.is_empty() {
693            return Err(DecodeError::MalformedField {
694                message_type: "UPDATE",
695                detail: "FlowSpec bitmask operators: unexpected end of data".to_string(),
696            });
697        }
698        let op_byte = buf[0];
699        buf = &buf[1..];
700
701        let end_of_list = op_byte & 0x80 != 0;
702        let and_bit = op_byte & 0x40 != 0;
703        let value_len_code = (op_byte >> 4) & 0x03;
704        let value_len = 1usize << value_len_code; // 1 or 2 for bitmask
705        let not_bit = op_byte & 0x02 != 0;
706        let match_bit = op_byte & 0x01 != 0;
707
708        if buf.len() < value_len {
709            return Err(DecodeError::MalformedField {
710                message_type: "UPDATE",
711                detail: format!(
712                    "FlowSpec bitmask value truncated: need {value_len}, have {}",
713                    buf.len()
714                ),
715            });
716        }
717
718        let value = match value_len {
719            1 => u16::from(buf[0]),
720            2 => u16::from_be_bytes([buf[0], buf[1]]),
721            _ => {
722                return Err(DecodeError::MalformedField {
723                    message_type: "UPDATE",
724                    detail: format!("FlowSpec bitmask value length {value_len} unsupported"),
725                });
726            }
727        };
728        buf = &buf[value_len..];
729
730        ops.push(BitmaskMatch {
731            end_of_list,
732            and_bit,
733            not_bit,
734            match_bit,
735            value,
736        });
737
738        if end_of_list {
739            break;
740        }
741    }
742    Ok((ops, start_len - buf.len()))
743}
744
745// ---------------------------------------------------------------------------
746// Wire encode
747// ---------------------------------------------------------------------------
748
749/// Encode a single `FlowSpec` rule to wire bytes (without the length prefix).
750fn encode_flowspec_rule(rule: &FlowSpecRule, buf: &mut Vec<u8>, afi: Afi) {
751    for component in &rule.components {
752        buf.push(component.type_code());
753        match component {
754            FlowSpecComponent::DestinationPrefix(p) | FlowSpecComponent::SourcePrefix(p) => {
755                encode_prefix_component(p, buf, afi);
756            }
757            FlowSpecComponent::IpProtocol(ops)
758            | FlowSpecComponent::Port(ops)
759            | FlowSpecComponent::DestinationPort(ops)
760            | FlowSpecComponent::SourcePort(ops)
761            | FlowSpecComponent::IcmpType(ops)
762            | FlowSpecComponent::IcmpCode(ops)
763            | FlowSpecComponent::PacketLength(ops)
764            | FlowSpecComponent::Dscp(ops)
765            | FlowSpecComponent::FlowLabel(ops) => {
766                encode_numeric_ops(ops, buf);
767            }
768            FlowSpecComponent::TcpFlags(ops) | FlowSpecComponent::Fragment(ops) => {
769                encode_bitmask_ops(ops, buf);
770            }
771        }
772    }
773}
774
775/// Encode a prefix component.
776fn encode_prefix_component(prefix: &FlowSpecPrefix, buf: &mut Vec<u8>, _afi: Afi) {
777    match prefix {
778        FlowSpecPrefix::V4(p) => {
779            buf.push(p.len);
780            let byte_count = (p.len as usize).div_ceil(8);
781            buf.extend_from_slice(&p.addr.octets()[..byte_count]);
782        }
783        FlowSpecPrefix::V6(p) => {
784            buf.push(p.prefix.len);
785            buf.push(p.offset);
786            let byte_count = (p.prefix.len as usize).div_ceil(8);
787            buf.extend_from_slice(&p.prefix.addr.octets()[..byte_count]);
788        }
789    }
790}
791
792/// Determine the minimum value-length code for a numeric value.
793fn numeric_value_len_code(value: u64) -> u8 {
794    if value <= 0xFF {
795        0 // 1 byte
796    } else if value <= 0xFFFF {
797        1 // 2 bytes
798    } else if value <= 0xFFFF_FFFF {
799        2 // 4 bytes
800    } else {
801        3 // 8 bytes
802    }
803}
804
805/// Encode numeric operator+value pairs.
806fn encode_numeric_ops(ops: &[NumericMatch], buf: &mut Vec<u8>) {
807    for (i, op) in ops.iter().enumerate() {
808        let is_last = i == ops.len() - 1;
809        let len_code = numeric_value_len_code(op.value);
810        let mut op_byte: u8 = 0;
811        if is_last {
812            op_byte |= 0x80; // end-of-list
813        }
814        if op.and_bit {
815            op_byte |= 0x40;
816        }
817        op_byte |= len_code << 4;
818        if op.lt {
819            op_byte |= 0x04;
820        }
821        if op.gt {
822            op_byte |= 0x02;
823        }
824        if op.eq {
825            op_byte |= 0x01;
826        }
827        buf.push(op_byte);
828        let value_len = 1usize << len_code;
829        match value_len {
830            1 => {
831                #[expect(clippy::cast_possible_truncation)]
832                buf.push(op.value as u8);
833            }
834            2 => {
835                #[expect(clippy::cast_possible_truncation)]
836                buf.extend_from_slice(&(op.value as u16).to_be_bytes());
837            }
838            4 => {
839                #[expect(clippy::cast_possible_truncation)]
840                buf.extend_from_slice(&(op.value as u32).to_be_bytes());
841            }
842            8 => buf.extend_from_slice(&op.value.to_be_bytes()),
843            _ => unreachable!(),
844        }
845    }
846}
847
848/// Determine the minimum value-length code for a bitmask value.
849fn bitmask_value_len_code(value: u16) -> u8 {
850    u8::from(value > 0xFF)
851}
852
853/// Encode bitmask operator+value pairs.
854fn encode_bitmask_ops(ops: &[BitmaskMatch], buf: &mut Vec<u8>) {
855    for (i, op) in ops.iter().enumerate() {
856        let is_last = i == ops.len() - 1;
857        let len_code = bitmask_value_len_code(op.value);
858        let mut op_byte: u8 = 0;
859        if is_last {
860            op_byte |= 0x80; // end-of-list
861        }
862        if op.and_bit {
863            op_byte |= 0x40;
864        }
865        op_byte |= len_code << 4;
866        if op.not_bit {
867            op_byte |= 0x02;
868        }
869        if op.match_bit {
870            op_byte |= 0x01;
871        }
872        buf.push(op_byte);
873        match 1usize << len_code {
874            1 => {
875                #[expect(clippy::cast_possible_truncation)]
876                buf.push(op.value as u8);
877            }
878            2 => buf.extend_from_slice(&op.value.to_be_bytes()),
879            _ => unreachable!(),
880        }
881    }
882}
883
884// ---------------------------------------------------------------------------
885// Display helpers
886// ---------------------------------------------------------------------------
887
888fn format_component(c: &FlowSpecComponent) -> String {
889    match c {
890        FlowSpecComponent::DestinationPrefix(p) => format!("dst {}", format_prefix(p)),
891        FlowSpecComponent::SourcePrefix(p) => format!("src {}", format_prefix(p)),
892        FlowSpecComponent::IpProtocol(ops) => format!("proto {}", format_numeric(ops)),
893        FlowSpecComponent::Port(ops) => format!("port {}", format_numeric(ops)),
894        FlowSpecComponent::DestinationPort(ops) => format!("dport {}", format_numeric(ops)),
895        FlowSpecComponent::SourcePort(ops) => format!("sport {}", format_numeric(ops)),
896        FlowSpecComponent::IcmpType(ops) => format!("icmp-type {}", format_numeric(ops)),
897        FlowSpecComponent::IcmpCode(ops) => format!("icmp-code {}", format_numeric(ops)),
898        FlowSpecComponent::TcpFlags(ops) => format!("tcp-flags {}", format_bitmask(ops)),
899        FlowSpecComponent::PacketLength(ops) => format!("pkt-len {}", format_numeric(ops)),
900        FlowSpecComponent::Dscp(ops) => format!("dscp {}", format_numeric(ops)),
901        FlowSpecComponent::Fragment(ops) => format!("fragment {}", format_bitmask(ops)),
902        FlowSpecComponent::FlowLabel(ops) => format!("flow-label {}", format_numeric(ops)),
903    }
904}
905
906fn format_prefix(p: &FlowSpecPrefix) -> String {
907    match p {
908        FlowSpecPrefix::V4(v4) => format!("{}/{}", v4.addr, v4.len),
909        FlowSpecPrefix::V6(v6) => {
910            if v6.offset == 0 {
911                format!("{}/{}", v6.prefix.addr, v6.prefix.len)
912            } else {
913                format!("{}/{} offset {}", v6.prefix.addr, v6.prefix.len, v6.offset)
914            }
915        }
916    }
917}
918
919fn format_numeric(ops: &[NumericMatch]) -> String {
920    let mut parts = Vec::new();
921    for op in ops {
922        let cmp = match (op.lt, op.gt, op.eq) {
923            (false, false, true) => "==",
924            (true, false, false) => "<",
925            (false, true, false) => ">",
926            (true, false, true) => "<=",
927            (false, true, true) => ">=",
928            (true, true, false) => "!=",
929            _ => "?",
930        };
931        parts.push(format!("{cmp}{}", op.value));
932    }
933    parts.join(",")
934}
935
936fn format_bitmask(ops: &[BitmaskMatch]) -> String {
937    let mut parts = Vec::new();
938    for op in ops {
939        let prefix = if op.not_bit { "!" } else { "" };
940        let suffix = if op.match_bit { "/match" } else { "" };
941        parts.push(format!("{prefix}0x{:x}{suffix}", op.value));
942    }
943    parts.join(",")
944}
945
946// ---------------------------------------------------------------------------
947// Tests
948// ---------------------------------------------------------------------------
949
950#[cfg(test)]
951mod tests {
952    use super::*;
953    use std::net::Ipv4Addr;
954
955    #[test]
956    fn numeric_ops_roundtrip() {
957        let ops = vec![
958            NumericMatch {
959                end_of_list: false,
960                and_bit: false,
961                lt: false,
962                gt: false,
963                eq: true,
964                value: 6,
965            },
966            NumericMatch {
967                end_of_list: true,
968                and_bit: false,
969                lt: false,
970                gt: false,
971                eq: true,
972                value: 17,
973            },
974        ];
975        let mut buf = Vec::new();
976        encode_numeric_ops(&ops, &mut buf);
977        let (decoded, consumed) = decode_numeric_ops(&buf).unwrap();
978        assert_eq!(consumed, buf.len());
979        assert_eq!(decoded.len(), 2);
980        assert_eq!(decoded[0].value, 6);
981        assert!(decoded[0].eq);
982        assert_eq!(decoded[1].value, 17);
983        assert!(decoded[1].end_of_list);
984    }
985
986    #[test]
987    fn bitmask_ops_roundtrip() {
988        let ops = vec![BitmaskMatch {
989            end_of_list: true,
990            and_bit: false,
991            not_bit: false,
992            match_bit: true,
993            value: 0x02, // SYN
994        }];
995        let mut buf = Vec::new();
996        encode_bitmask_ops(&ops, &mut buf);
997        let (decoded, consumed) = decode_bitmask_ops(&buf).unwrap();
998        assert_eq!(consumed, buf.len());
999        assert_eq!(decoded.len(), 1);
1000        assert_eq!(decoded[0].value, 0x02);
1001        assert!(decoded[0].match_bit);
1002    }
1003
1004    #[test]
1005    fn ipv4_prefix_component_roundtrip() {
1006        let prefix = FlowSpecPrefix::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8));
1007        let mut buf = Vec::new();
1008        encode_prefix_component(&prefix, &mut buf, Afi::Ipv4);
1009        let (decoded, consumed) = decode_prefix_component(&buf, Afi::Ipv4).unwrap();
1010        assert_eq!(consumed, buf.len());
1011        assert_eq!(decoded, prefix);
1012    }
1013
1014    #[test]
1015    fn ipv6_prefix_component_roundtrip() {
1016        let prefix = FlowSpecPrefix::V6(Ipv6PrefixOffset {
1017            prefix: Ipv6Prefix::new(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32),
1018            offset: 0,
1019        });
1020        let mut buf = Vec::new();
1021        encode_prefix_component(&prefix, &mut buf, Afi::Ipv6);
1022        let (decoded, consumed) = decode_prefix_component(&buf, Afi::Ipv6).unwrap();
1023        assert_eq!(consumed, buf.len());
1024        assert_eq!(decoded, prefix);
1025    }
1026
1027    #[test]
1028    fn simple_ipv4_rule_roundtrip() {
1029        let rule = FlowSpecRule {
1030            components: vec![
1031                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1032                    Ipv4Addr::new(10, 0, 0, 0),
1033                    24,
1034                ))),
1035                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1036                    end_of_list: true,
1037                    and_bit: false,
1038                    lt: false,
1039                    gt: false,
1040                    eq: true,
1041                    value: 6, // TCP
1042                }]),
1043                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1044                    end_of_list: true,
1045                    and_bit: false,
1046                    lt: false,
1047                    gt: false,
1048                    eq: true,
1049                    value: 80,
1050                }]),
1051            ],
1052        };
1053        let mut buf = Vec::new();
1054        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv4);
1055        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1056        assert_eq!(decoded.len(), 1);
1057        assert_eq!(decoded[0], rule);
1058    }
1059
1060    #[test]
1061    fn multi_rule_roundtrip() {
1062        let rule1 = FlowSpecRule {
1063            components: vec![FlowSpecComponent::IpProtocol(vec![NumericMatch {
1064                end_of_list: true,
1065                and_bit: false,
1066                lt: false,
1067                gt: false,
1068                eq: true,
1069                value: 17, // UDP
1070            }])],
1071        };
1072        let rule2 = FlowSpecRule {
1073            components: vec![FlowSpecComponent::DestinationPort(vec![NumericMatch {
1074                end_of_list: true,
1075                and_bit: false,
1076                lt: false,
1077                gt: false,
1078                eq: true,
1079                value: 53,
1080            }])],
1081        };
1082        let mut buf = Vec::new();
1083        encode_flowspec_nlri(&[rule1.clone(), rule2.clone()], &mut buf, Afi::Ipv4);
1084        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1085        assert_eq!(decoded.len(), 2);
1086        assert_eq!(decoded[0], rule1);
1087        assert_eq!(decoded[1], rule2);
1088    }
1089
1090    #[test]
1091    fn component_type_ordering_validated() {
1092        let rule = FlowSpecRule {
1093            components: vec![
1094                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1095                    end_of_list: true,
1096                    and_bit: false,
1097                    lt: false,
1098                    gt: false,
1099                    eq: true,
1100                    value: 80,
1101                }]),
1102                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1103                    end_of_list: true,
1104                    and_bit: false,
1105                    lt: false,
1106                    gt: false,
1107                    eq: true,
1108                    value: 6,
1109                }]),
1110            ],
1111        };
1112        assert!(rule.validate().is_err());
1113    }
1114
1115    #[test]
1116    fn empty_rule_rejected() {
1117        let rule = FlowSpecRule { components: vec![] };
1118        assert!(rule.validate().is_err());
1119    }
1120
1121    #[test]
1122    fn two_byte_length_prefix() {
1123        // Construct a rule large enough to need 2-byte length
1124        let mut buf = Vec::new();
1125        let len = 250; // > 0xF0
1126        encode_flowspec_length(len, &mut buf);
1127        assert_eq!(buf.len(), 2);
1128        let (decoded_len, consumed) = decode_flowspec_length(&buf).unwrap();
1129        assert_eq!(decoded_len, len);
1130        assert_eq!(consumed, 2);
1131    }
1132
1133    #[test]
1134    fn one_byte_length_prefix() {
1135        let mut buf = Vec::new();
1136        let len = 100;
1137        encode_flowspec_length(len, &mut buf);
1138        assert_eq!(buf.len(), 1);
1139        let (decoded_len, consumed) = decode_flowspec_length(&buf).unwrap();
1140        assert_eq!(decoded_len, len);
1141        assert_eq!(consumed, 1);
1142    }
1143
1144    #[test]
1145    fn numeric_2byte_value() {
1146        let ops = vec![NumericMatch {
1147            end_of_list: true,
1148            and_bit: false,
1149            lt: false,
1150            gt: false,
1151            eq: true,
1152            value: 8080,
1153        }];
1154        let mut buf = Vec::new();
1155        encode_numeric_ops(&ops, &mut buf);
1156        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1157        assert_eq!(decoded[0].value, 8080);
1158    }
1159
1160    #[test]
1161    fn numeric_4byte_value() {
1162        let ops = vec![NumericMatch {
1163            end_of_list: true,
1164            and_bit: false,
1165            lt: false,
1166            gt: false,
1167            eq: true,
1168            value: 100_000,
1169        }];
1170        let mut buf = Vec::new();
1171        encode_numeric_ops(&ops, &mut buf);
1172        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1173        assert_eq!(decoded[0].value, 100_000);
1174    }
1175
1176    #[test]
1177    fn bitmask_2byte_value() {
1178        let ops = vec![BitmaskMatch {
1179            end_of_list: true,
1180            and_bit: false,
1181            not_bit: false,
1182            match_bit: true,
1183            value: 0x0FFF,
1184        }];
1185        let mut buf = Vec::new();
1186        encode_bitmask_ops(&ops, &mut buf);
1187        let (decoded, _) = decode_bitmask_ops(&buf).unwrap();
1188        assert_eq!(decoded[0].value, 0x0FFF);
1189    }
1190
1191    #[test]
1192    fn display_string_formatting() {
1193        let rule = FlowSpecRule {
1194            components: vec![
1195                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1196                    Ipv4Addr::new(192, 168, 1, 0),
1197                    24,
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        let s = rule.display_string();
1210        assert!(s.contains("dst 192.168.1.0/24"));
1211        assert!(s.contains("proto ==6"));
1212    }
1213
1214    #[test]
1215    fn traffic_rate_bytes_action_roundtrip() {
1216        let action = FlowSpecAction::TrafficRateBytes {
1217            asn: 65000,
1218            rate: 0.0,
1219        };
1220        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1221        let decoded = ec.as_flowspec_action().unwrap();
1222        match decoded {
1223            FlowSpecAction::TrafficRateBytes { asn, rate } => {
1224                assert_eq!(asn, 65000);
1225                assert!((rate - 0.0).abs() < f32::EPSILON);
1226            }
1227            _ => panic!("wrong action type"),
1228        }
1229    }
1230
1231    #[test]
1232    fn traffic_action_roundtrip() {
1233        let action = FlowSpecAction::TrafficAction {
1234            sample: true,
1235            terminal: false,
1236        };
1237        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1238        let decoded = ec.as_flowspec_action().unwrap();
1239        match decoded {
1240            FlowSpecAction::TrafficAction { sample, terminal } => {
1241                assert!(sample);
1242                assert!(!terminal);
1243            }
1244            _ => panic!("wrong action type"),
1245        }
1246    }
1247
1248    #[test]
1249    fn traffic_marking_roundtrip() {
1250        let action = FlowSpecAction::TrafficMarking { dscp: 46 };
1251        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1252        let decoded = ec.as_flowspec_action().unwrap();
1253        assert!(matches!(
1254            decoded,
1255            FlowSpecAction::TrafficMarking { dscp: 46 }
1256        ));
1257    }
1258
1259    #[test]
1260    fn redirect_2octet_roundtrip() {
1261        let action = FlowSpecAction::Redirect2Octet {
1262            asn: 65001,
1263            value: 100,
1264        };
1265        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1266        let decoded = ec.as_flowspec_action().unwrap();
1267        assert!(matches!(
1268            decoded,
1269            FlowSpecAction::Redirect2Octet {
1270                asn: 65001,
1271                value: 100
1272            }
1273        ));
1274    }
1275
1276    #[test]
1277    fn redirect_ipv4_roundtrip() {
1278        let action = FlowSpecAction::RedirectIpv4 {
1279            addr: Ipv4Addr::new(10, 0, 0, 1),
1280            value: 200,
1281        };
1282        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1283        let decoded = ec.as_flowspec_action().unwrap();
1284        match decoded {
1285            FlowSpecAction::RedirectIpv4 { addr, value } => {
1286                assert_eq!(addr, Ipv4Addr::new(10, 0, 0, 1));
1287                assert_eq!(value, 200);
1288            }
1289            _ => panic!("wrong action type"),
1290        }
1291    }
1292
1293    #[test]
1294    fn redirect_4octet_roundtrip() {
1295        let action = FlowSpecAction::Redirect4Octet {
1296            asn: 400_000,
1297            value: 300,
1298        };
1299        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1300        let decoded = ec.as_flowspec_action().unwrap();
1301        match decoded {
1302            FlowSpecAction::Redirect4Octet { asn, value } => {
1303                assert_eq!(asn, 400_000);
1304                assert_eq!(value, 300);
1305            }
1306            _ => panic!("wrong action type"),
1307        }
1308    }
1309
1310    #[test]
1311    fn traffic_rate_packets_roundtrip() {
1312        let action = FlowSpecAction::TrafficRatePackets {
1313            asn: 0,
1314            rate: 1000.0,
1315        };
1316        let ec = crate::attribute::ExtendedCommunity::from_flowspec_action(&action);
1317        let decoded = ec.as_flowspec_action().unwrap();
1318        match decoded {
1319            FlowSpecAction::TrafficRatePackets { asn, rate } => {
1320                assert_eq!(asn, 0);
1321                assert!((rate - 1000.0).abs() < f32::EPSILON);
1322            }
1323            _ => panic!("wrong action type"),
1324        }
1325    }
1326
1327    #[test]
1328    fn flow_label_ipv6_roundtrip() {
1329        let rule = FlowSpecRule {
1330            components: vec![FlowSpecComponent::FlowLabel(vec![NumericMatch {
1331                end_of_list: true,
1332                and_bit: false,
1333                lt: false,
1334                gt: false,
1335                eq: true,
1336                value: 12345,
1337            }])],
1338        };
1339        let mut buf = Vec::new();
1340        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv6);
1341        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv6).unwrap();
1342        assert_eq!(decoded[0], rule);
1343    }
1344
1345    #[test]
1346    fn ipv6_rule_with_prefix_and_flow_label() {
1347        let rule = FlowSpecRule {
1348            components: vec![
1349                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V6(Ipv6PrefixOffset {
1350                    prefix: Ipv6Prefix::new(
1351                        std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0),
1352                        32,
1353                    ),
1354                    offset: 0,
1355                })),
1356                FlowSpecComponent::FlowLabel(vec![NumericMatch {
1357                    end_of_list: true,
1358                    and_bit: false,
1359                    lt: false,
1360                    gt: false,
1361                    eq: true,
1362                    value: 42,
1363                }]),
1364            ],
1365        };
1366        let mut buf = Vec::new();
1367        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv6);
1368        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv6).unwrap();
1369        assert_eq!(decoded[0], rule);
1370    }
1371
1372    #[test]
1373    #[expect(clippy::too_many_lines)]
1374    fn all_13_component_types_in_order() {
1375        let rule = FlowSpecRule {
1376            components: vec![
1377                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1378                    Ipv4Addr::new(10, 0, 0, 0),
1379                    8,
1380                ))),
1381                FlowSpecComponent::SourcePrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1382                    Ipv4Addr::new(172, 16, 0, 0),
1383                    12,
1384                ))),
1385                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1386                    end_of_list: true,
1387                    and_bit: false,
1388                    lt: false,
1389                    gt: false,
1390                    eq: true,
1391                    value: 6,
1392                }]),
1393                FlowSpecComponent::Port(vec![NumericMatch {
1394                    end_of_list: true,
1395                    and_bit: false,
1396                    lt: false,
1397                    gt: false,
1398                    eq: true,
1399                    value: 80,
1400                }]),
1401                FlowSpecComponent::DestinationPort(vec![NumericMatch {
1402                    end_of_list: true,
1403                    and_bit: false,
1404                    lt: false,
1405                    gt: false,
1406                    eq: true,
1407                    value: 443,
1408                }]),
1409                FlowSpecComponent::SourcePort(vec![NumericMatch {
1410                    end_of_list: true,
1411                    and_bit: false,
1412                    lt: false,
1413                    gt: true,
1414                    eq: false,
1415                    value: 1024,
1416                }]),
1417                FlowSpecComponent::IcmpType(vec![NumericMatch {
1418                    end_of_list: true,
1419                    and_bit: false,
1420                    lt: false,
1421                    gt: false,
1422                    eq: true,
1423                    value: 8,
1424                }]),
1425                FlowSpecComponent::IcmpCode(vec![NumericMatch {
1426                    end_of_list: true,
1427                    and_bit: false,
1428                    lt: false,
1429                    gt: false,
1430                    eq: true,
1431                    value: 0,
1432                }]),
1433                FlowSpecComponent::TcpFlags(vec![BitmaskMatch {
1434                    end_of_list: true,
1435                    and_bit: false,
1436                    not_bit: false,
1437                    match_bit: true,
1438                    value: 0x02,
1439                }]),
1440                FlowSpecComponent::PacketLength(vec![NumericMatch {
1441                    end_of_list: true,
1442                    and_bit: false,
1443                    lt: true,
1444                    gt: false,
1445                    eq: true,
1446                    value: 1500,
1447                }]),
1448                FlowSpecComponent::Dscp(vec![NumericMatch {
1449                    end_of_list: true,
1450                    and_bit: false,
1451                    lt: false,
1452                    gt: false,
1453                    eq: true,
1454                    value: 46,
1455                }]),
1456                FlowSpecComponent::Fragment(vec![BitmaskMatch {
1457                    end_of_list: true,
1458                    and_bit: false,
1459                    not_bit: false,
1460                    match_bit: true,
1461                    value: 0x01,
1462                }]),
1463                FlowSpecComponent::FlowLabel(vec![NumericMatch {
1464                    end_of_list: true,
1465                    and_bit: false,
1466                    lt: false,
1467                    gt: false,
1468                    eq: true,
1469                    value: 99999,
1470                }]),
1471            ],
1472        };
1473        assert!(rule.validate().is_ok());
1474        let mut buf = Vec::new();
1475        encode_flowspec_nlri(std::slice::from_ref(&rule), &mut buf, Afi::Ipv4);
1476        let decoded = decode_flowspec_nlri(&buf, Afi::Ipv4).unwrap();
1477        assert_eq!(decoded[0], rule);
1478    }
1479
1480    #[test]
1481    fn destination_prefix_extraction() {
1482        let rule = FlowSpecRule {
1483            components: vec![
1484                FlowSpecComponent::DestinationPrefix(FlowSpecPrefix::V4(Ipv4Prefix::new(
1485                    Ipv4Addr::new(10, 0, 0, 0),
1486                    24,
1487                ))),
1488                FlowSpecComponent::IpProtocol(vec![NumericMatch {
1489                    end_of_list: true,
1490                    and_bit: false,
1491                    lt: false,
1492                    gt: false,
1493                    eq: true,
1494                    value: 6,
1495                }]),
1496            ],
1497        };
1498        let prefix = rule.destination_prefix().unwrap();
1499        match prefix {
1500            crate::nlri::Prefix::V4(p) => {
1501                assert_eq!(p.addr, Ipv4Addr::new(10, 0, 0, 0));
1502                assert_eq!(p.len, 24);
1503            }
1504            crate::nlri::Prefix::V6(_) => panic!("expected V4 prefix"),
1505        }
1506    }
1507
1508    #[test]
1509    fn rule_without_destination_prefix() {
1510        let rule = FlowSpecRule {
1511            components: vec![FlowSpecComponent::IpProtocol(vec![NumericMatch {
1512                end_of_list: true,
1513                and_bit: false,
1514                lt: false,
1515                gt: false,
1516                eq: true,
1517                value: 17,
1518            }])],
1519        };
1520        assert!(rule.destination_prefix().is_none());
1521    }
1522
1523    #[test]
1524    fn and_bit_numeric_ops() {
1525        let ops = vec![
1526            NumericMatch {
1527                end_of_list: false,
1528                and_bit: false,
1529                lt: false,
1530                gt: true,
1531                eq: true,
1532                value: 100,
1533            },
1534            NumericMatch {
1535                end_of_list: true,
1536                and_bit: true,
1537                lt: true,
1538                gt: false,
1539                eq: true,
1540                value: 200,
1541            },
1542        ];
1543        let mut buf = Vec::new();
1544        encode_numeric_ops(&ops, &mut buf);
1545        let (decoded, _) = decode_numeric_ops(&buf).unwrap();
1546        assert_eq!(decoded.len(), 2);
1547        assert!(!decoded[0].and_bit);
1548        assert!(decoded[0].gt);
1549        assert!(decoded[0].eq);
1550        assert!(decoded[1].and_bit);
1551        assert!(decoded[1].lt);
1552        assert!(decoded[1].eq);
1553    }
1554}