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 Raw([u8; 8]),
114}
115
116impl ExtendedCommunity {
117 pub const fn community_type(&self) -> ExtendedCommunityType {
118 use ExtendedCommunityType::*;
119 match self {
120 ExtendedCommunity::TransitiveTwoOctetAs(_) => TransitiveTwoOctetAs,
121 ExtendedCommunity::TransitiveIpv4Addr(_) => TransitiveIpv4Addr,
122 ExtendedCommunity::TransitiveFourOctetAs(_) => TransitiveFourOctetAs,
123 ExtendedCommunity::TransitiveOpaque(_) => TransitiveOpaque,
124 ExtendedCommunity::NonTransitiveTwoOctetAs(_) => NonTransitiveTwoOctetAs,
125 ExtendedCommunity::NonTransitiveIpv4Addr(_) => NonTransitiveIpv4Addr,
126 ExtendedCommunity::NonTransitiveFourOctetAs(_) => NonTransitiveFourOctetAs,
127 ExtendedCommunity::NonTransitiveOpaque(_) => NonTransitiveOpaque,
128 ExtendedCommunity::FlowSpecTrafficRate(_) => NonTransitiveTwoOctetAs,
129 ExtendedCommunity::FlowSpecTrafficAction(_) => NonTransitiveTwoOctetAs,
130 ExtendedCommunity::FlowSpecRedirect(_) => NonTransitiveTwoOctetAs,
131 ExtendedCommunity::FlowSpecTrafficMarking(_) => NonTransitiveTwoOctetAs,
132 ExtendedCommunity::Raw(buffer) => Unknown(buffer[0]),
133 }
134 }
135}
136
137#[derive(Debug, PartialEq, Clone, Copy, Eq)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
139pub struct Ipv6AddrExtCommunity {
140 pub community_type: ExtendedCommunityType,
141 pub subtype: u8,
142 pub global_admin: Ipv6Addr,
144 pub local_admin: [u8; 2],
146}
147
148#[derive(Debug, PartialEq, Clone, Copy, Eq)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
153pub struct TwoOctetAsExtCommunity {
154 pub subtype: u8,
155 pub global_admin: Asn,
157 pub local_admin: [u8; 4],
159}
160
161#[derive(Debug, PartialEq, Clone, Copy, Eq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct FourOctetAsExtCommunity {
167 pub subtype: u8,
168 pub global_admin: Asn,
170 pub local_admin: [u8; 2],
172}
173
174#[derive(Debug, PartialEq, Clone, Copy, Eq)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179pub struct Ipv4AddrExtCommunity {
180 pub subtype: u8,
181 pub global_admin: Ipv4Addr,
183 pub local_admin: [u8; 2],
185}
186
187#[derive(Debug, PartialEq, Clone, Copy, Eq)]
191#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
192pub struct OpaqueExtCommunity {
193 pub subtype: u8,
194 pub value: [u8; 6],
196}
197
198#[derive(Debug, Clone, Copy)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct FlowSpecTrafficRate {
204 pub as_number: u16,
206 pub rate_bytes_per_sec: f32,
208}
209
210impl PartialEq for FlowSpecTrafficRate {
211 fn eq(&self, other: &Self) -> bool {
212 self.as_number == other.as_number
213 && self.rate_bytes_per_sec.to_bits() == other.rate_bytes_per_sec.to_bits()
214 }
215}
216
217impl Eq for FlowSpecTrafficRate {}
218
219#[derive(Debug, PartialEq, Clone, Copy, Eq)]
223#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
224pub struct FlowSpecTrafficAction {
225 pub as_number: u16,
227 pub terminal: bool,
229 pub sample: bool,
231}
232
233#[derive(Debug, PartialEq, Clone, Copy, Eq)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct FlowSpecTrafficMarking {
239 pub as_number: u16,
241 pub dscp: u8,
243}
244
245impl FlowSpecTrafficRate {
246 pub fn new(as_number: u16, rate_bytes_per_sec: f32) -> Self {
248 Self {
249 as_number,
250 rate_bytes_per_sec,
251 }
252 }
253
254 pub fn discard(as_number: u16) -> Self {
256 Self {
257 as_number,
258 rate_bytes_per_sec: 0.0,
259 }
260 }
261}
262
263impl FlowSpecTrafficAction {
264 pub fn new(as_number: u16, terminal: bool, sample: bool) -> Self {
266 Self {
267 as_number,
268 terminal,
269 sample,
270 }
271 }
272}
273
274impl FlowSpecTrafficMarking {
275 pub fn new(as_number: u16, dscp: u8) -> Self {
277 Self {
278 as_number,
279 dscp: dscp & 0x3F,
280 } }
282}
283
284struct ToHexString<'a>(&'a [u8]);
289
290impl Display for ToHexString<'_> {
291 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
292 for byte in self.0 {
293 write!(f, "{byte:02X}")?;
294 }
295 Ok(())
296 }
297}
298
299impl Display for Community {
300 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
301 match self {
302 Community::NoExport => write!(f, "no-export"),
303 Community::NoAdvertise => write!(f, "no-advertise"),
304 Community::NoExportSubConfed => write!(f, "no-export-sub-confed"),
305 Community::Custom(asn, value) => write!(f, "{asn}:{value}"),
306 }
307 }
308}
309
310impl Display for LargeCommunity {
311 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
312 write!(
313 f,
314 "{}:{}:{}",
315 self.global_admin, self.local_data[0], self.local_data[1]
316 )
317 }
318}
319
320impl Display for ExtendedCommunity {
321 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
322 let ec_type = u8::from(self.community_type());
323 match self {
324 ExtendedCommunity::TransitiveTwoOctetAs(ec)
325 | ExtendedCommunity::NonTransitiveTwoOctetAs(ec) => {
326 write!(
327 f,
328 "{}:{}:{}:{}",
329 ec_type,
330 ec.subtype,
331 ec.global_admin,
332 ToHexString(&ec.local_admin)
333 )
334 }
335 ExtendedCommunity::TransitiveIpv4Addr(ec)
336 | ExtendedCommunity::NonTransitiveIpv4Addr(ec) => {
337 write!(
338 f,
339 "{}:{}:{}:{}",
340 ec_type,
341 ec.subtype,
342 ec.global_admin,
343 ToHexString(&ec.local_admin)
344 )
345 }
346 ExtendedCommunity::TransitiveFourOctetAs(ec)
347 | ExtendedCommunity::NonTransitiveFourOctetAs(ec) => {
348 write!(
349 f,
350 "{}:{}:{}:{}",
351 ec_type,
352 ec.subtype,
353 ec.global_admin,
354 ToHexString(&ec.local_admin)
355 )
356 }
357 ExtendedCommunity::TransitiveOpaque(ec)
358 | ExtendedCommunity::NonTransitiveOpaque(ec) => {
359 write!(f, "{}:{}:{}", ec_type, ec.subtype, ToHexString(&ec.value))
360 }
361 ExtendedCommunity::FlowSpecTrafficRate(rate) => {
362 write!(
363 f,
364 "rate:{} bytes/sec (AS {})",
365 rate.rate_bytes_per_sec, rate.as_number
366 )
367 }
368 ExtendedCommunity::FlowSpecTrafficAction(action) => {
369 let mut flags = Vec::new();
370 if action.terminal {
371 flags.push("terminal");
372 }
373 if action.sample {
374 flags.push("sample");
375 }
376 write!(f, "action:{} (AS {})", flags.join(","), action.as_number)
377 }
378 ExtendedCommunity::FlowSpecRedirect(redirect) => {
379 write!(
380 f,
381 "redirect:AS{}:{}",
382 redirect.global_admin,
383 ToHexString(&redirect.local_admin)
384 )
385 }
386 ExtendedCommunity::FlowSpecTrafficMarking(marking) => {
387 write!(f, "mark:DSCP{} (AS {})", marking.dscp, marking.as_number)
388 }
389 ExtendedCommunity::Raw(ec) => {
390 write!(f, "{}", ToHexString(ec))
391 }
392 }
393 }
394}
395
396impl Display for Ipv6AddrExtCommunity {
397 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
398 write!(
399 f,
400 "{}:{}:{}:{}",
401 u8::from(self.community_type),
402 self.subtype,
403 self.global_admin,
404 ToHexString(&self.local_admin)
405 )
406 }
407}
408
409impl Display for MetaCommunity {
410 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
411 match self {
412 MetaCommunity::Plain(c) => write!(f, "{c}"),
413 MetaCommunity::Extended(c) => write!(f, "{c}"),
414 MetaCommunity::Large(c) => write!(f, "{c}"),
415 MetaCommunity::Ipv6Extended(c) => write!(f, "{c}"),
416 }
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_large_community_new() {
426 let global_admin = 56;
427 let local_data = [3, 4];
428 let large_comm = LargeCommunity::new(global_admin, local_data);
429 assert_eq!(large_comm.global_admin, global_admin);
430 assert_eq!(large_comm.local_data, local_data);
431 }
432
433 #[test]
434 fn test_extended_community_community_type() {
435 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
436 subtype: 0,
437 global_admin: Asn::new_32bit(0),
438 local_admin: [0; 4],
439 };
440 let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
441 assert_eq!(
442 extended_community.community_type(),
443 ExtendedCommunityType::TransitiveTwoOctetAs
444 );
445 }
446
447 #[test]
448 fn test_display_community() {
449 assert_eq!(format!("{}", Community::NoExport), "no-export");
450 assert_eq!(format!("{}", Community::NoAdvertise), "no-advertise");
451 assert_eq!(
452 format!("{}", Community::NoExportSubConfed),
453 "no-export-sub-confed"
454 );
455 assert_eq!(
456 format!("{}", Community::Custom(Asn::new_32bit(64512), 100)),
457 "64512:100"
458 );
459 }
460
461 #[test]
462 fn test_display_large_community() {
463 let large_community = LargeCommunity::new(1, [2, 3]);
464 assert_eq!(format!("{large_community}"), "1:2:3");
465 }
466
467 #[test]
468 fn test_display_extended_community() {
469 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
470 subtype: 0,
471 global_admin: Asn::new_32bit(0),
472 local_admin: [0; 4],
473 };
474 let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
475 assert_eq!(format!("{extended_community}"), "0:0:0:00000000");
476
477 let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
478 subtype: 0,
479 global_admin: Asn::new_32bit(0),
480 local_admin: [0; 4],
481 };
482 let extended_community = ExtendedCommunity::NonTransitiveTwoOctetAs(two_octet_as_ext_comm);
483 assert_eq!(format!("{extended_community}"), "64:0:0:00000000");
484
485 let ipv4_ext_comm = Ipv4AddrExtCommunity {
486 subtype: 1,
487 global_admin: "192.168.1.1".parse().unwrap(),
488 local_admin: [5, 6],
489 };
490 let extended_community = ExtendedCommunity::TransitiveIpv4Addr(ipv4_ext_comm);
491 assert_eq!(format!("{extended_community}"), "1:1:192.168.1.1:0506");
492
493 let ipv4_ext_comm = Ipv4AddrExtCommunity {
494 subtype: 1,
495 global_admin: "192.168.1.1".parse().unwrap(),
496 local_admin: [5, 6],
497 };
498 let extended_community = ExtendedCommunity::NonTransitiveIpv4Addr(ipv4_ext_comm);
499 assert_eq!(format!("{extended_community}"), "65:1:192.168.1.1:0506");
500
501 let four_octet_as_ext_comm = FourOctetAsExtCommunity {
502 subtype: 2,
503 global_admin: Asn::new_32bit(64512),
504 local_admin: [7, 8],
505 };
506 let extended_community = ExtendedCommunity::TransitiveFourOctetAs(four_octet_as_ext_comm);
507 assert_eq!(format!("{extended_community}"), "2:2:64512:0708");
508
509 let four_octet_as_ext_comm = FourOctetAsExtCommunity {
510 subtype: 2,
511 global_admin: Asn::new_32bit(64512),
512 local_admin: [7, 8],
513 };
514 let extended_community =
515 ExtendedCommunity::NonTransitiveFourOctetAs(four_octet_as_ext_comm);
516 assert_eq!(format!("{extended_community}"), "66:2:64512:0708");
517
518 let opaque_ext_comm = OpaqueExtCommunity {
519 subtype: 3,
520 value: [9, 10, 11, 12, 13, 14],
521 };
522 let extended_community = ExtendedCommunity::TransitiveOpaque(opaque_ext_comm);
523 assert_eq!(format!("{extended_community}"), "3:3:090A0B0C0D0E");
524
525 let opaque_ext_comm = OpaqueExtCommunity {
526 subtype: 3,
527 value: [9, 10, 11, 12, 13, 14],
528 };
529 let extended_community = ExtendedCommunity::NonTransitiveOpaque(opaque_ext_comm);
530 assert_eq!(format!("{extended_community}"), "67:3:090A0B0C0D0E");
531
532 let raw_ext_comm = [0, 1, 2, 3, 4, 5, 6, 7];
533 let extended_community = ExtendedCommunity::Raw(raw_ext_comm);
534 assert_eq!(format!("{extended_community}"), "0001020304050607");
535 }
536
537 #[test]
538 fn test_display_ipv6_addr_ext_community() {
539 let ipv6_addr_ext_comm = Ipv6AddrExtCommunity {
540 community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
541 subtype: 0,
542 global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
543 local_admin: [0, 1],
544 };
545 assert_eq!(
546 format!("{ipv6_addr_ext_comm}"),
547 "0:0:2001:db8::8a2e:370:7334:0001"
548 );
549 }
550
551 #[test]
552 fn test_display_meta_community() {
553 let large_community = LargeCommunity::new(1, [2, 3]);
554 let meta_community = MetaCommunity::Large(large_community);
555 assert_eq!(format!("{meta_community}"), "1:2:3");
556 }
557
558 #[test]
559 fn test_to_hex_string() {
560 assert_eq!(format!("{}", ToHexString(&[])), "");
562
563 assert_eq!(format!("{}", ToHexString(&[0x0A])), "0A");
565
566 assert_eq!(format!("{}", ToHexString(&[0x0A, 0x0B, 0x0C])), "0A0B0C");
568
569 assert_eq!(format!("{}", ToHexString(&[0x00])), "00");
571
572 assert_eq!(format!("{}", ToHexString(&[0x10])), "10");
574
575 assert_eq!(
577 format!("{}", ToHexString(&[0x00, 0x0F, 0x10, 0xFF])),
578 "000F10FF"
579 );
580 }
581
582 #[test]
583 #[cfg(feature = "serde")]
584 fn test_serde() {
585 let meta_community = MetaCommunity::Large(LargeCommunity::new(1, [2, 3]));
586 let serialized = serde_json::to_string(&meta_community).unwrap();
587 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
588 assert_eq!(meta_community, deserialized);
589
590 let meta_community = MetaCommunity::Extended(ExtendedCommunity::TransitiveTwoOctetAs(
591 TwoOctetAsExtCommunity {
592 subtype: 0,
593 global_admin: Asn::new_32bit(0),
594 local_admin: [0; 4],
595 },
596 ));
597 let serialized = serde_json::to_string(&meta_community).unwrap();
598 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
599 assert_eq!(meta_community, deserialized);
600
601 let meta_community = MetaCommunity::Plain(Community::NoExport);
602 let serialized = serde_json::to_string(&meta_community).unwrap();
603 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
604 assert_eq!(meta_community, deserialized);
605
606 let meta_community = MetaCommunity::Ipv6Extended(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 let serialized = serde_json::to_string(&meta_community).unwrap();
613 let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
614 assert_eq!(meta_community, deserialized);
615 }
616
617 #[test]
618 fn test_flowspec_traffic_rate() {
619 let rate = FlowSpecTrafficRate::new(64512, 1000.0);
620 assert_eq!(rate.as_number, 64512);
621 assert_eq!(rate.rate_bytes_per_sec, 1000.0);
622
623 let discard = FlowSpecTrafficRate::discard(64512);
624 assert_eq!(discard.rate_bytes_per_sec, 0.0);
625 }
626
627 #[test]
628 fn test_flowspec_traffic_action() {
629 let action = FlowSpecTrafficAction::new(64512, true, false);
630 assert_eq!(action.as_number, 64512);
631 assert!(action.terminal);
632 assert!(!action.sample);
633 }
634
635 #[test]
636 fn test_flowspec_traffic_marking() {
637 let marking = FlowSpecTrafficMarking::new(64512, 46); assert_eq!(marking.as_number, 64512);
639 assert_eq!(marking.dscp, 46);
640
641 let masked = FlowSpecTrafficMarking::new(64512, 255);
643 assert_eq!(masked.dscp, 63); }
645
646 #[test]
647 fn test_flowspec_community_display() {
648 let rate = ExtendedCommunity::FlowSpecTrafficRate(FlowSpecTrafficRate::new(64512, 1000.0));
649 assert_eq!(format!("{}", rate), "rate:1000 bytes/sec (AS 64512)");
650
651 let action =
652 ExtendedCommunity::FlowSpecTrafficAction(FlowSpecTrafficAction::new(64512, true, true));
653 assert_eq!(format!("{}", action), "action:terminal,sample (AS 64512)");
654
655 let marking =
656 ExtendedCommunity::FlowSpecTrafficMarking(FlowSpecTrafficMarking::new(64512, 46));
657 assert_eq!(format!("{}", marking), "mark:DSCP46 (AS 64512)");
658 }
659}