1use std::fmt;
11use std::net::Ipv4Addr;
12
13use crate::capability::Afi;
14use crate::error::DecodeError;
15use crate::nlri::{Ipv4Prefix, Ipv6Prefix};
16
17#[expect(clippy::struct_excessive_bools)]
26#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct NumericMatch {
28 pub end_of_list: bool,
30 pub and_bit: bool,
32 pub lt: bool,
34 pub gt: bool,
36 pub eq: bool,
38 pub value: u64,
40}
41
42#[expect(clippy::struct_excessive_bools)]
44#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
45pub struct BitmaskMatch {
46 pub end_of_list: bool,
48 pub and_bit: bool,
50 pub not_bit: bool,
52 pub match_bit: bool,
54 pub value: u16,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
64pub struct Ipv6PrefixOffset {
65 pub prefix: Ipv6Prefix,
67 pub offset: u8,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
80pub enum FlowSpecComponent {
81 DestinationPrefix(FlowSpecPrefix),
83 SourcePrefix(FlowSpecPrefix),
85 IpProtocol(Vec<NumericMatch>),
87 Port(Vec<NumericMatch>),
89 DestinationPort(Vec<NumericMatch>),
91 SourcePort(Vec<NumericMatch>),
93 IcmpType(Vec<NumericMatch>),
95 IcmpCode(Vec<NumericMatch>),
97 TcpFlags(Vec<BitmaskMatch>),
99 PacketLength(Vec<NumericMatch>),
101 Dscp(Vec<NumericMatch>),
103 Fragment(Vec<BitmaskMatch>),
105 FlowLabel(Vec<NumericMatch>),
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
111pub enum FlowSpecPrefix {
112 V4(Ipv4Prefix),
114 V6(Ipv6PrefixOffset),
116}
117
118impl FlowSpecComponent {
119 #[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#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
149pub struct FlowSpecRule {
150 pub components: Vec<FlowSpecComponent>,
152}
153
154impl FlowSpecRule {
155 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 #[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 #[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#[derive(Debug, Clone, PartialEq)]
220pub enum FlowSpecAction {
221 TrafficRateBytes {
223 asn: u16,
225 rate: f32,
227 },
228 TrafficRatePackets {
230 asn: u16,
232 rate: f32,
234 },
235 TrafficAction {
237 sample: bool,
239 terminal: bool,
241 },
242 TrafficMarking {
244 dscp: u8,
246 },
247 Redirect2Octet {
249 asn: u16,
251 value: u32,
253 },
254 RedirectIpv4 {
256 addr: Ipv4Addr,
258 value: u16,
260 },
261 Redirect4Octet {
263 asn: u32,
265 value: u16,
267 },
268}
269
270impl crate::attribute::ExtendedCommunity {
271 #[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 (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 (0x80, 0x07) => {
288 let flags = bytes[7];
290 Some(FlowSpecAction::TrafficAction {
291 sample: flags & 0x02 != 0,
292 terminal: flags & 0x01 != 0,
293 })
294 }
295 (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 (0x80, 0x09) => Some(FlowSpecAction::TrafficMarking {
303 dscp: bytes[7] & 0x3F,
304 }),
305 (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 (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 (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 #[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
384pub 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 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
422pub 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
432fn 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 let real_len = len & 0x0FFF;
452 Ok((real_len, 2))
453 }
454}
455
456fn 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
468fn 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
479fn 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 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
554fn decode_prefix_component(buf: &[u8], afi: Afi) -> Result<(FlowSpecPrefix, usize), DecodeError> {
556 match afi {
557 Afi::Ipv4 => {
558 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 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
624fn 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; 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
683fn 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; 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
741fn 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
771fn 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
788fn numeric_value_len_code(value: u64) -> u8 {
790 if value <= 0xFF {
791 0 } else if value <= 0xFFFF {
793 1 } else if value <= 0xFFFF_FFFF {
795 2 } else {
797 3 }
799}
800
801fn 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; }
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
844fn bitmask_value_len_code(value: u16) -> u8 {
846 u8::from(value > 0xFF)
847}
848
849fn 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; }
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
880fn 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#[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, }];
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, }]),
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, }])],
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 let mut buf = Vec::new();
1121 let len = 250; 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}