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::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 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 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
628fn 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; 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
687fn 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; 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
745fn 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
775fn 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
792fn numeric_value_len_code(value: u64) -> u8 {
794 if value <= 0xFF {
795 0 } else if value <= 0xFFFF {
797 1 } else if value <= 0xFFFF_FFFF {
799 2 } else {
801 3 }
803}
804
805fn 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; }
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
848fn bitmask_value_len_code(value: u16) -> u8 {
850 u8::from(value > 0xFF)
851}
852
853fn 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; }
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
884fn 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#[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, }];
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, }]),
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, }])],
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 let mut buf = Vec::new();
1125 let len = 250; 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}