1use crate::models::Asn;
2use num_enum::{FromPrimitive, IntoPrimitive};
3use std::fmt::{Display, Formatter};
4use std::net::{Ipv4Addr, Ipv6Addr};
5
6#[derive(Debug, PartialEq, Copy, Clone, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(untagged))]
9pub enum MetaCommunity {
10 Plain(Community),
11 Extended(ExtendedCommunity),
12 Ipv6Extended(Ipv6AddrExtCommunity),
13 Large(LargeCommunity),
14}
15
16#[derive(Debug, PartialEq, Copy, Clone, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
19pub enum Community {
20 NoExport,
21 NoAdvertise,
22 NoExportSubConfed,
23 Custom(Asn, u16),
24}
25
26#[derive(Debug, PartialEq, Clone, Copy, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
34pub struct LargeCommunity {
35 pub global_admin: u32,
36 pub local_data: [u32; 2],
37}
38
39impl LargeCommunity {
40 pub fn new(global_admin: u32, local_data: [u32; 2]) -> LargeCommunity {
41 LargeCommunity {
42 global_admin,
43 local_data,
44 }
45 }
46}
47
48#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Copy, Clone)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
52#[repr(u8)]
53pub enum ExtendedCommunityType {
54 TransitiveTwoOctetAs = 0x00,
56 TransitiveIpv4Addr = 0x01,
57 TransitiveFourOctetAs = 0x02,
58 TransitiveOpaque = 0x03,
59
60 NonTransitiveTwoOctetAs = 0x40,
62 NonTransitiveIpv4Addr = 0x41,
63 NonTransitiveFourOctetAs = 0x42,
64 NonTransitiveOpaque = 0x43,
65 #[num_enum(catch_all)]
67 Unknown(u8),
68}
69
70#[derive(Debug, PartialEq, Clone, Copy, Eq)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
100pub enum ExtendedCommunity {
101 TransitiveTwoOctetAs(TwoOctetAsExtCommunity),
102 TransitiveIpv4Addr(Ipv4AddrExtCommunity),
103 TransitiveFourOctetAs(FourOctetAsExtCommunity),
104 TransitiveOpaque(OpaqueExtCommunity),
105 NonTransitiveTwoOctetAs(TwoOctetAsExtCommunity),
106 NonTransitiveIpv4Addr(Ipv4AddrExtCommunity),
107 NonTransitiveFourOctetAs(FourOctetAsExtCommunity),
108 NonTransitiveOpaque(OpaqueExtCommunity),
109 FlowSpecTrafficRate(FlowSpecTrafficRate),
111 FlowSpecTrafficAction(FlowSpecTrafficAction),
113 FlowSpecRedirect(TwoOctetAsExtCommunity),
115 FlowSpecTrafficMarking(FlowSpecTrafficMarking),
117 LinkBandwidth(LinkBandwidth),
119 Raw([u8; 8]),
120}
121
122impl ExtendedCommunity {
123 pub const fn community_type(&self) -> ExtendedCommunityType {
124 use ExtendedCommunityType::*;
125 match self {
126 ExtendedCommunity::TransitiveTwoOctetAs(_) => TransitiveTwoOctetAs,
127 ExtendedCommunity::TransitiveIpv4Addr(_) => TransitiveIpv4Addr,
128 ExtendedCommunity::TransitiveFourOctetAs(_) => TransitiveFourOctetAs,
129 ExtendedCommunity::TransitiveOpaque(_) => TransitiveOpaque,
130 ExtendedCommunity::NonTransitiveTwoOctetAs(_) => NonTransitiveTwoOctetAs,
131 ExtendedCommunity::NonTransitiveIpv4Addr(_) => NonTransitiveIpv4Addr,
132 ExtendedCommunity::NonTransitiveFourOctetAs(_) => NonTransitiveFourOctetAs,
133 ExtendedCommunity::NonTransitiveOpaque(_) => NonTransitiveOpaque,
134 ExtendedCommunity::FlowSpecTrafficRate(_) => NonTransitiveTwoOctetAs,
135 ExtendedCommunity::FlowSpecTrafficAction(_) => NonTransitiveTwoOctetAs,
136 ExtendedCommunity::FlowSpecRedirect(_) => NonTransitiveTwoOctetAs,
137 ExtendedCommunity::FlowSpecTrafficMarking(_) => NonTransitiveTwoOctetAs,
138 ExtendedCommunity::LinkBandwidth(link_bandwidth) => {
139 if link_bandwidth.transitive {
140 TransitiveTwoOctetAs
141 } else {
142 NonTransitiveTwoOctetAs
143 }
144 }
145 ExtendedCommunity::Raw(buffer) => Unknown(buffer[0]),
146 }
147 }
148}
149
150#[derive(Debug, PartialEq, Clone, Copy, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
153pub struct Ipv6AddrExtCommunity {
154 pub community_type: ExtendedCommunityType,
155 pub subtype: u8,
156 pub global_admin: Ipv6Addr,
158 pub local_admin: [u8; 2],
160}
161
162#[derive(Debug, PartialEq, Clone, Copy, Eq)]
166#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
167#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
168pub struct TwoOctetAsExtCommunity {
169 pub subtype: u8,
170 pub global_admin: Asn,
172 pub local_admin: [u8; 4],
174}
175
176#[derive(Debug, PartialEq, Clone, Copy, Eq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
182pub struct FourOctetAsExtCommunity {
183 pub subtype: u8,
184 pub global_admin: Asn,
186 pub local_admin: [u8; 2],
188}
189
190#[derive(Debug, PartialEq, Clone, Copy, Eq)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
196pub struct Ipv4AddrExtCommunity {
197 pub subtype: u8,
198 pub global_admin: Ipv4Addr,
200 pub local_admin: [u8; 2],
202}
203
204#[derive(Debug, PartialEq, Clone, Copy, Eq)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
210pub struct OpaqueExtCommunity {
211 pub subtype: u8,
212 pub value: [u8; 6],
214}
215
216#[derive(Debug, Clone, Copy)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
222pub struct LinkBandwidth {
223 pub global_admin: u16,
225 pub bandwidth: f32,
227 pub transitive: bool,
229}
230
231impl PartialEq for LinkBandwidth {
232 fn eq(&self, other: &Self) -> bool {
233 self.global_admin == other.global_admin
234 && self.bandwidth.to_bits() == other.bandwidth.to_bits()
235 && self.transitive == other.transitive
236 }
237}
238
239impl Eq for LinkBandwidth {}
240
241#[derive(Debug, Clone, Copy)]
245#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
246#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
247pub struct FlowSpecTrafficRate {
248 pub as_number: u16,
250 pub rate_bytes_per_sec: f32,
252}
253
254impl PartialEq for FlowSpecTrafficRate {
255 fn eq(&self, other: &Self) -> bool {
256 self.as_number == other.as_number
257 && self.rate_bytes_per_sec.to_bits() == other.rate_bytes_per_sec.to_bits()
258 }
259}
260
261impl Eq for FlowSpecTrafficRate {}
262
263#[derive(Debug, PartialEq, Clone, Copy, Eq)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
269pub struct FlowSpecTrafficAction {
270 pub as_number: u16,
272 pub terminal: bool,
274 pub sample: bool,
276}
277
278#[derive(Debug, PartialEq, Clone, Copy, Eq)]
282#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
283#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
284pub struct FlowSpecTrafficMarking {
285 pub as_number: u16,
287 pub dscp: u8,
289}
290
291impl FlowSpecTrafficRate {
292 pub fn new(as_number: u16, rate_bytes_per_sec: f32) -> Self {
294 Self {
295 as_number,
296 rate_bytes_per_sec,
297 }
298 }
299
300 pub fn discard(as_number: u16) -> Self {
302 Self {
303 as_number,
304 rate_bytes_per_sec: 0.0,
305 }
306 }
307}
308
309impl FlowSpecTrafficAction {
310 pub fn new(as_number: u16, terminal: bool, sample: bool) -> Self {
312 Self {
313 as_number,
314 terminal,
315 sample,
316 }
317 }
318}
319
320impl FlowSpecTrafficMarking {
321 pub fn new(as_number: u16, dscp: u8) -> Self {
323 Self {
324 as_number,
325 dscp: dscp & 0x3F,
326 } }
328}
329
330struct ToHexString<'a>(&'a [u8]);
335
336impl Display for ToHexString<'_> {
337 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
338 for byte in self.0 {
339 write!(f, "{byte:02X}")?;
340 }
341 Ok(())
342 }
343}
344
345impl Display for Community {
346 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
347 match self {
348 Community::NoExport => write!(f, "no-export"),
349 Community::NoAdvertise => write!(f, "no-advertise"),
350 Community::NoExportSubConfed => write!(f, "no-export-sub-confed"),
351 Community::Custom(asn, value) => write!(f, "{asn}:{value}"),
352 }
353 }
354}
355
356impl Display for LargeCommunity {
357 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
358 write!(
359 f,
360 "{}:{}:{}",
361 self.global_admin, self.local_data[0], self.local_data[1]
362 )
363 }
364}
365
366impl Display for ExtendedCommunity {
367 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
368 let ec_type = u8::from(self.community_type());
369 match self {
370 ExtendedCommunity::TransitiveTwoOctetAs(ec)
371 | ExtendedCommunity::NonTransitiveTwoOctetAs(ec) => {
372 write!(
373 f,
374 "{}:{}:{}:{}",
375 ec_type,
376 ec.subtype,
377 ec.global_admin,
378 ToHexString(&ec.local_admin)
379 )
380 }
381 ExtendedCommunity::TransitiveIpv4Addr(ec)
382 | ExtendedCommunity::NonTransitiveIpv4Addr(ec) => {
383 write!(
384 f,
385 "{}:{}:{}:{}",
386 ec_type,
387 ec.subtype,
388 ec.global_admin,
389 ToHexString(&ec.local_admin)
390 )
391 }
392 ExtendedCommunity::TransitiveFourOctetAs(ec)
393 | ExtendedCommunity::NonTransitiveFourOctetAs(ec) => {
394 write!(
395 f,
396 "{}:{}:{}:{}",
397 ec_type,
398 ec.subtype,
399 ec.global_admin,
400 ToHexString(&ec.local_admin)
401 )
402 }
403 ExtendedCommunity::TransitiveOpaque(ec)
404 | ExtendedCommunity::NonTransitiveOpaque(ec) => {
405 write!(f, "{}:{}:{}", ec_type, ec.subtype, ToHexString(&ec.value))
406 }
407 ExtendedCommunity::FlowSpecTrafficRate(rate) => {
408 write!(
409 f,
410 "rate:{} bytes/sec (AS {})",
411 rate.rate_bytes_per_sec, rate.as_number
412 )
413 }
414 ExtendedCommunity::FlowSpecTrafficAction(action) => {
415 let mut flags = Vec::new();
416 if action.terminal {
417 flags.push("terminal");
418 }
419 if action.sample {
420 flags.push("sample");
421 }
422 write!(f, "action:{} (AS {})", flags.join(","), action.as_number)
423 }
424 ExtendedCommunity::FlowSpecRedirect(redirect) => {
425 write!(
426 f,
427 "redirect:AS{}:{}",
428 redirect.global_admin,
429 ToHexString(&redirect.local_admin)
430 )
431 }
432 ExtendedCommunity::FlowSpecTrafficMarking(marking) => {
433 write!(f, "mark:DSCP{} (AS {})", marking.dscp, marking.as_number)
434 }
435 ExtendedCommunity::LinkBandwidth(link_bandwidth) => {
436 write!(
437 f,
438 "{}:{}:{}:{}",
439 ec_type, 0x04, link_bandwidth.global_admin, link_bandwidth.bandwidth
440 )
441 }
442 ExtendedCommunity::Raw(ec) => {
443 write!(f, "{}", ToHexString(ec))
444 }
445 }
446 }
447}
448
449impl Display for Ipv6AddrExtCommunity {
450 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
451 write!(
452 f,
453 "{}:{}:{}:{}",
454 u8::from(self.community_type),
455 self.subtype,
456 self.global_admin,
457 ToHexString(&self.local_admin)
458 )
459 }
460}
461
462impl Display for MetaCommunity {
463 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
464 match self {
465 MetaCommunity::Plain(c) => write!(f, "{c}"),
466 MetaCommunity::Extended(c) => write!(f, "{c}"),
467 MetaCommunity::Large(c) => write!(f, "{c}"),
468 MetaCommunity::Ipv6Extended(c) => write!(f, "{c}"),
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_large_community_new() {
479 let global_admin = 56;
480 let local_data = [3, 4];
481 let large_comm = LargeCommunity::new(global_admin, local_data);
482 assert_eq!(large_comm.global_admin, global_admin);
483 assert_eq!(large_comm.local_data, local_data);
484 }
485
486 #[test]
487 fn test_extended_community_community_type() {
488 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
489 subtype: 0,
490 global_admin: Asn::new_32bit(0),
491 local_admin: [0; 4],
492 };
493 let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
494 assert_eq!(
495 extended_community.community_type(),
496 ExtendedCommunityType::TransitiveTwoOctetAs
497 );
498 }
499
500 #[test]
501 fn test_display_community() {
502 assert_eq!(format!("{}", Community::NoExport), "no-export");
503 assert_eq!(format!("{}", Community::NoAdvertise), "no-advertise");
504 assert_eq!(
505 format!("{}", Community::NoExportSubConfed),
506 "no-export-sub-confed"
507 );
508 assert_eq!(
509 format!("{}", Community::Custom(Asn::new_32bit(64512), 100)),
510 "64512:100"
511 );
512 }
513
514 #[test]
515 fn test_display_large_community() {
516 let large_community = LargeCommunity::new(1, [2, 3]);
517 assert_eq!(format!("{large_community}"), "1:2:3");
518 }
519
520 #[test]
521 fn test_display_extended_community() {
522 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
523 subtype: 0,
524 global_admin: Asn::new_32bit(0),
525 local_admin: [0; 4],
526 };
527 let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
528 assert_eq!(format!("{extended_community}"), "0:0:0:00000000");
529
530 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
531 subtype: 0,
532 global_admin: Asn::new_32bit(0),
533 local_admin: [0; 4],
534 };
535 let extended_community = ExtendedCommunity::NonTransitiveTwoOctetAs(two_octet_as_ext_comm);
536 assert_eq!(format!("{extended_community}"), "64:0:0:00000000");
537
538 let ipv4_ext_comm = Ipv4AddrExtCommunity {
539 subtype: 1,
540 global_admin: "192.168.1.1".parse().unwrap(),
541 local_admin: [5, 6],
542 };
543 let extended_community = ExtendedCommunity::TransitiveIpv4Addr(ipv4_ext_comm);
544 assert_eq!(format!("{extended_community}"), "1:1:192.168.1.1:0506");
545
546 let ipv4_ext_comm = Ipv4AddrExtCommunity {
547 subtype: 1,
548 global_admin: "192.168.1.1".parse().unwrap(),
549 local_admin: [5, 6],
550 };
551 let extended_community = ExtendedCommunity::NonTransitiveIpv4Addr(ipv4_ext_comm);
552 assert_eq!(format!("{extended_community}"), "65:1:192.168.1.1:0506");
553
554 let four_octet_as_ext_comm = FourOctetAsExtCommunity {
555 subtype: 2,
556 global_admin: Asn::new_32bit(64512),
557 local_admin: [7, 8],
558 };
559 let extended_community = ExtendedCommunity::TransitiveFourOctetAs(four_octet_as_ext_comm);
560 assert_eq!(format!("{extended_community}"), "2:2:64512:0708");
561
562 let four_octet_as_ext_comm = FourOctetAsExtCommunity {
563 subtype: 2,
564 global_admin: Asn::new_32bit(64512),
565 local_admin: [7, 8],
566 };
567 let extended_community =
568 ExtendedCommunity::NonTransitiveFourOctetAs(four_octet_as_ext_comm);
569 assert_eq!(format!("{extended_community}"), "66:2:64512:0708");
570
571 let opaque_ext_comm = OpaqueExtCommunity {
572 subtype: 3,
573 value: [9, 10, 11, 12, 13, 14],
574 };
575 let extended_community = ExtendedCommunity::TransitiveOpaque(opaque_ext_comm);
576 assert_eq!(format!("{extended_community}"), "3:3:090A0B0C0D0E");
577
578 let opaque_ext_comm = OpaqueExtCommunity {
579 subtype: 3,
580 value: [9, 10, 11, 12, 13, 14],
581 };
582 let extended_community = ExtendedCommunity::NonTransitiveOpaque(opaque_ext_comm);
583 assert_eq!(format!("{extended_community}"), "67:3:090A0B0C0D0E");
584
585 let link_bandwidth = ExtendedCommunity::LinkBandwidth(LinkBandwidth {
586 global_admin: 1,
587 bandwidth: 1000.0,
588 transitive: true,
589 });
590 assert_eq!(format!("{link_bandwidth}"), "0:4:1:1000");
591
592 let link_bandwidth = ExtendedCommunity::LinkBandwidth(LinkBandwidth {
593 global_admin: 1,
594 bandwidth: 1000.0,
595 transitive: false,
596 });
597 assert_eq!(format!("{link_bandwidth}"), "64:4:1:1000");
598
599 let raw_ext_comm = [0, 1, 2, 3, 4, 5, 6, 7];
600 let extended_community = ExtendedCommunity::Raw(raw_ext_comm);
601 assert_eq!(format!("{extended_community}"), "0001020304050607");
602 }
603
604 #[test]
605 fn test_display_ipv6_addr_ext_community() {
606 let ipv6_addr_ext_comm = Ipv6AddrExtCommunity {
607 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
608 subtype: 0,
609 global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
610 local_admin: [0, 1],
611 };
612 assert_eq!(
613 format!("{ipv6_addr_ext_comm}"),
614 "0:0:2001:db8::8a2e:370:7334:0001"
615 );
616 }
617
618 #[test]
619 fn test_display_meta_community() {
620 let large_community = LargeCommunity::new(1, [2, 3]);
621 let meta_community = MetaCommunity::Large(large_community);
622 assert_eq!(format!("{meta_community}"), "1:2:3");
623 }
624
625 #[test]
626 fn test_to_hex_string() {
627 assert_eq!(format!("{}", ToHexString(&[])), "");
629
630 assert_eq!(format!("{}", ToHexString(&[0x0A])), "0A");
632
633 assert_eq!(format!("{}", ToHexString(&[0x0A, 0x0B, 0x0C])), "0A0B0C");
635
636 assert_eq!(format!("{}", ToHexString(&[0x00])), "00");
638
639 assert_eq!(format!("{}", ToHexString(&[0x10])), "10");
641
642 assert_eq!(
644 format!("{}", ToHexString(&[0x00, 0x0F, 0x10, 0xFF])),
645 "000F10FF"
646 );
647 }
648
649 #[test]
650 #[cfg(feature = "serde")]
651 fn test_serde() {
652 let meta_community = MetaCommunity::Large(LargeCommunity::new(1, [2, 3]));
653 let serialized = serde_json::to_string(&meta_community).unwrap();
654 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
655 assert_eq!(meta_community, deserialized);
656
657 let meta_community = MetaCommunity::Extended(ExtendedCommunity::TransitiveTwoOctetAs(
658 TwoOctetAsExtCommunity {
659 subtype: 0,
660 global_admin: Asn::new_32bit(0),
661 local_admin: [0; 4],
662 },
663 ));
664 let serialized = serde_json::to_string(&meta_community).unwrap();
665 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
666 assert_eq!(meta_community, deserialized);
667
668 let meta_community = MetaCommunity::Plain(Community::NoExport);
669 let serialized = serde_json::to_string(&meta_community).unwrap();
670 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
671 assert_eq!(meta_community, deserialized);
672
673 let meta_community = MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
674 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
675 subtype: 0,
676 global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
677 local_admin: [0, 1],
678 });
679 let serialized = serde_json::to_string(&meta_community).unwrap();
680 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
681 assert_eq!(meta_community, deserialized);
682 }
683
684 #[test]
685 fn test_flowspec_traffic_rate() {
686 let rate = FlowSpecTrafficRate::new(64512, 1000.0);
687 assert_eq!(rate.as_number, 64512);
688 assert_eq!(rate.rate_bytes_per_sec, 1000.0);
689
690 let discard = FlowSpecTrafficRate::discard(64512);
691 assert_eq!(discard.rate_bytes_per_sec, 0.0);
692 }
693
694 #[test]
695 fn test_flowspec_traffic_action() {
696 let action = FlowSpecTrafficAction::new(64512, true, false);
697 assert_eq!(action.as_number, 64512);
698 assert!(action.terminal);
699 assert!(!action.sample);
700 }
701
702 #[test]
703 fn test_flowspec_traffic_marking() {
704 let marking = FlowSpecTrafficMarking::new(64512, 46); assert_eq!(marking.as_number, 64512);
706 assert_eq!(marking.dscp, 46);
707
708 let masked = FlowSpecTrafficMarking::new(64512, 255);
710 assert_eq!(masked.dscp, 63); }
712
713 #[test]
714 fn test_flowspec_community_display() {
715 let rate = ExtendedCommunity::FlowSpecTrafficRate(FlowSpecTrafficRate::new(64512, 1000.0));
716 assert_eq!(format!("{}", rate), "rate:1000 bytes/sec (AS 64512)");
717
718 let action =
719 ExtendedCommunity::FlowSpecTrafficAction(FlowSpecTrafficAction::new(64512, true, true));
720 assert_eq!(format!("{}", action), "action:terminal,sample (AS 64512)");
721
722 let marking =
723 ExtendedCommunity::FlowSpecTrafficMarking(FlowSpecTrafficMarking::new(64512, 46));
724 assert_eq!(format!("{}", marking), "mark:DSCP46 (AS 64512)");
725 }
726}