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