1use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20use crate::ie::Field;
21use crate::{DataSetId, FieldSpecifier};
22
23pub const IPFIX_VERSION: u16 = 10;
24
25pub(crate) const IPFIX_TEMPLATE_SET_ID: u16 = 2;
27
28pub(crate) const IPFIX_OPTIONS_TEMPLATE_SET_ID: u16 = 3;
30
31#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
33#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
34pub struct DecodingTemplate {
35 pub scope_fields_specs: Box<[FieldSpecifier]>,
36 pub fields_specs: Box<[FieldSpecifier]>,
37
38 pub processed_count: u64,
40}
41
42impl DecodingTemplate {
43 pub const fn new(
44 scope_fields_specs: Box<[FieldSpecifier]>,
45 fields_specs: Box<[FieldSpecifier]>,
46 ) -> Self {
47 Self {
48 scope_fields_specs,
49 fields_specs,
50 processed_count: 0,
51 }
52 }
53
54 pub const fn increment_processed_count(&mut self) {
56 self.processed_count = self.processed_count.wrapping_add(1);
57 }
58
59 pub const fn processed_count(&self) -> u64 {
61 self.processed_count
62 }
63
64 pub const fn reset_processed_count(&mut self) -> u64 {
66 let prev = self.processed_count;
67 self.processed_count = 0;
68 prev
69 }
70}
71
72pub type TemplatesMap = HashMap<u16, DecodingTemplate>;
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
101pub struct IpfixPacket {
102 version: u16,
103 #[cfg_attr(feature = "fuzz", arbitrary(with = crate::arbitrary_datetime))]
104 export_time: DateTime<Utc>,
105 sequence_number: u32,
106 observation_domain_id: u32,
107 sets: Box<[Set]>,
108}
109
110impl IpfixPacket {
111 pub const fn new(
112 export_time: DateTime<Utc>,
113 sequence_number: u32,
114 observation_domain_id: u32,
115 sets: Box<[Set]>,
116 ) -> Self {
117 Self {
118 version: IPFIX_VERSION,
119 export_time,
120 sequence_number,
121 observation_domain_id,
122 sets,
123 }
124 }
125
126 pub const fn version(&self) -> u16 {
128 self.version
129 }
130
131 pub const fn export_time(&self) -> DateTime<Utc> {
136 self.export_time
137 }
138
139 pub const fn sequence_number(&self) -> u32 {
146 self.sequence_number
147 }
148
149 pub const fn observation_domain_id(&self) -> u32 {
152 self.observation_domain_id
153 }
154
155 pub const fn sets(&self) -> &[Set] {
157 &self.sets
158 }
159
160 pub fn into_sets(self) -> Box<[Set]> {
162 self.sets
163 }
164
165 pub fn with_fields_added(self, add_fields: &[Field]) -> Self {
167 let sets = self
168 .sets
169 .into_vec()
170 .into_iter()
171 .map(|set| set.with_fields_added(add_fields))
172 .collect::<Box<[_]>>();
173
174 Self {
175 version: self.version,
176 export_time: self.export_time,
177 sequence_number: self.sequence_number,
178 observation_domain_id: self.observation_domain_id,
179 sets,
180 }
181 }
182
183 pub fn with_scope_fields_added(self, add_scope_fields: &[Field]) -> Self {
185 let sets = self
186 .sets
187 .into_vec()
188 .into_iter()
189 .map(|set| set.with_scope_fields_added(add_scope_fields))
190 .collect::<Box<[_]>>();
191
192 Self {
193 version: self.version,
194 export_time: self.export_time,
195 sequence_number: self.sequence_number,
196 observation_domain_id: self.observation_domain_id,
197 sets,
198 }
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
214pub enum Set {
215 Template(Box<[TemplateRecord]>),
216 OptionsTemplate(Box<[OptionsTemplateRecord]>),
217 Data {
218 id: DataSetId,
219 records: Box<[DataRecord]>,
220 },
221}
222
223impl Set {
224 pub const fn id(&self) -> u16 {
225 match self {
226 Self::Template(_) => IPFIX_TEMPLATE_SET_ID,
227 Self::OptionsTemplate(_) => IPFIX_OPTIONS_TEMPLATE_SET_ID,
228 Self::Data { id, records: _ } => id.0,
229 }
230 }
231
232 pub fn with_fields_added(self, add_fields: &[Field]) -> Self {
234 match self {
235 Set::Data { id, records } => {
236 let modified_records = records
237 .into_vec()
238 .into_iter()
239 .map(|record| record.with_fields_added(add_fields))
240 .collect::<Box<[_]>>();
241
242 Set::Data {
243 id,
244 records: modified_records,
245 }
246 }
247 other => other,
248 }
249 }
250
251 pub fn with_scope_fields_added(self, add_scope_fields: &[Field]) -> Self {
253 match self {
254 Set::Data { id, records } => {
255 let modified_records = records
256 .into_vec()
257 .into_iter()
258 .map(|record| record.with_scope_fields_added(add_scope_fields))
259 .collect::<Box<[_]>>();
260
261 Set::Data {
262 id,
263 records: modified_records,
264 }
265 }
266 other => other,
267 }
268 }
269}
270
271#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
299#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
300pub struct TemplateRecord {
301 id: u16,
302 field_specifiers: Box<[FieldSpecifier]>,
303}
304
305impl TemplateRecord {
306 pub const fn new(id: u16, field_specifiers: Box<[FieldSpecifier]>) -> Self {
307 Self {
308 id,
309 field_specifiers,
310 }
311 }
312
313 pub const fn id(&self) -> u16 {
317 self.id
318 }
319
320 pub const fn field_specifiers(&self) -> &[FieldSpecifier] {
322 &self.field_specifiers
323 }
324}
325
326#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
386#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
387pub struct OptionsTemplateRecord {
388 id: u16,
389 scope_field_specifiers: Box<[FieldSpecifier]>,
390 field_specifiers: Box<[FieldSpecifier]>,
391}
392
393impl OptionsTemplateRecord {
394 pub const fn new(
395 id: u16,
396 scope_field_specifiers: Box<[FieldSpecifier]>,
397 field_specifiers: Box<[FieldSpecifier]>,
398 ) -> Self {
399 Self {
400 id,
401 scope_field_specifiers,
402 field_specifiers,
403 }
404 }
405
406 pub const fn id(&self) -> u16 {
407 self.id
408 }
409
410 pub const fn scope_field_specifiers(&self) -> &[FieldSpecifier] {
411 &self.scope_field_specifiers
412 }
413
414 pub const fn field_specifiers(&self) -> &[FieldSpecifier] {
415 &self.field_specifiers
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
420#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
421pub struct DataRecord {
422 scope_fields: Box<[Field]>,
423 fields: Box<[Field]>,
424}
425
426impl DataRecord {
427 pub const fn new(scope_fields: Box<[Field]>, fields: Box<[Field]>) -> Self {
428 Self {
429 scope_fields,
430 fields,
431 }
432 }
433
434 pub const fn scope_fields(&self) -> &[Field] {
435 &self.scope_fields
436 }
437
438 pub const fn fields(&self) -> &[Field] {
439 &self.fields
440 }
441
442 pub fn into_parts(self) -> (Box<[Field]>, Box<[Field]>) {
444 (self.scope_fields, self.fields)
445 }
446
447 pub fn with_fields_added(self, add_fields: &[Field]) -> Self {
449 let mut fields = self.fields.into_vec();
450 fields.extend_from_slice(add_fields);
451
452 Self {
453 scope_fields: self.scope_fields,
454 fields: fields.into_boxed_slice(),
455 }
456 }
457
458 pub fn with_scope_fields_added(self, add_scope_fields: &[Field]) -> Self {
460 let mut scope_fields = self.scope_fields.into_vec();
461 scope_fields.extend_from_slice(add_scope_fields);
462
463 Self {
464 scope_fields: scope_fields.into_boxed_slice(),
465 fields: self.fields,
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::ie;
474 use chrono::TimeZone;
475
476 #[test]
477 fn test_ipfix_packet() {
478 let export_time = Utc.with_ymd_and_hms(2024, 6, 20, 14, 0, 0).unwrap();
479 let sequence_number = 0;
480 let observation_domain_id = 0;
481 let sets = [
482 Set::Template(Box::new([TemplateRecord::new(
483 256,
484 Box::new([
485 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
486 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
487 ]),
488 )])),
489 Set::OptionsTemplate(Box::new([OptionsTemplateRecord::new(
490 258,
491 Box::new([FieldSpecifier::new(ie::IE::egressVRFID, 4).unwrap()]),
492 Box::new([FieldSpecifier::new(ie::IE::interfaceName, 255).unwrap()]),
493 )])),
494 Set::Data {
495 id: DataSetId::new(256).unwrap(),
496 records: Box::new([DataRecord::new(
497 Box::new([Field::octetDeltaCount(189)]),
498 Box::new([Field::tcpDestinationPort(8080)]),
499 )]),
500 },
501 ];
502 let packet = IpfixPacket::new(
503 export_time,
504 sequence_number,
505 observation_domain_id,
506 Box::new(sets.clone()),
507 );
508 assert_eq!(packet.version(), IPFIX_VERSION);
509 assert_eq!(packet.export_time(), export_time);
510 assert_eq!(packet.sequence_number(), sequence_number);
511 assert_eq!(packet.observation_domain_id(), observation_domain_id);
512 assert_eq!(packet.sets(), &sets);
513 }
514
515 #[test]
516 fn test_template_record() {
517 let template = TemplateRecord::new(
518 256,
519 Box::new([
520 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
521 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
522 ]),
523 );
524 assert_eq!(template.id(), 256);
525 assert_eq!(
526 template.field_specifiers(),
527 &[
528 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
529 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
530 ]
531 );
532 }
533
534 #[test]
535 fn test_options_template_record() {
536 let template = OptionsTemplateRecord::new(
537 258,
538 Box::new([FieldSpecifier::new(ie::IE::egressVRFID, 4).unwrap()]),
539 Box::new([FieldSpecifier::new(ie::IE::interfaceName, 255).unwrap()]),
540 );
541 assert_eq!(template.id(), 258);
542 assert_eq!(
543 template.scope_field_specifiers(),
544 &[FieldSpecifier::new(ie::IE::egressVRFID, 4).unwrap()]
545 );
546 assert_eq!(
547 template.field_specifiers(),
548 &[FieldSpecifier::new(ie::IE::interfaceName, 255).unwrap()]
549 );
550 }
551
552 #[test]
553 fn test_data_record() {
554 let record = DataRecord::new(
555 Box::new([Field::octetDeltaCount(189)]),
556 Box::new([Field::tcpDestinationPort(8080)]),
557 );
558 assert_eq!(record.scope_fields(), &[Field::octetDeltaCount(189)]);
559 assert_eq!(record.fields(), &[Field::tcpDestinationPort(8080)]);
560 }
561
562 #[test]
563 fn test_set() {
564 let template = TemplateRecord::new(
565 256,
566 Box::new([
567 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
568 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
569 ]),
570 );
571 let options_template = OptionsTemplateRecord::new(
572 258,
573 Box::new([FieldSpecifier::new(ie::IE::egressVRFID, 4).unwrap()]),
574 Box::new([FieldSpecifier::new(ie::IE::interfaceName, 255).unwrap()]),
575 );
576 let data = DataRecord::new(
577 Box::new([Field::octetDeltaCount(189)]),
578 Box::new([Field::tcpDestinationPort(8080)]),
579 );
580 let sets = [
581 Set::Template(Box::new([template.clone()])),
582 Set::OptionsTemplate(Box::new([options_template.clone()])),
583 Set::Data {
584 id: DataSetId::new(256).unwrap(),
585 records: Box::new([data.clone()]),
586 },
587 ];
588 assert_eq!(sets[0].id(), IPFIX_TEMPLATE_SET_ID);
589 assert_eq!(sets[1].id(), IPFIX_OPTIONS_TEMPLATE_SET_ID);
590 assert_eq!(sets[2].id(), 256);
591 }
592
593 #[test]
594 fn test_data_record_with_fields_added() {
595 let original_record = DataRecord::new(
596 Box::new([Field::octetDeltaCount(189)]),
597 Box::new([Field::tcpDestinationPort(8080)]),
598 );
599
600 let modified_record = original_record.with_fields_added(&[
601 Field::packetDeltaCount(10),
602 Field::sourceIPv4Address([192, 168, 1, 1].into()),
603 ]);
604
605 let expected_record = DataRecord::new(
606 Box::new([Field::octetDeltaCount(189)]),
607 Box::new([
608 Field::tcpDestinationPort(8080),
609 Field::packetDeltaCount(10),
610 Field::sourceIPv4Address([192, 168, 1, 1].into()),
611 ]),
612 );
613
614 assert_eq!(modified_record, expected_record);
615 }
616
617 #[test]
618 fn test_data_record_with_scope_fields_added() {
619 let original_record = DataRecord::new(
620 Box::new([Field::octetDeltaCount(189)]),
621 Box::new([Field::tcpDestinationPort(8080)]),
622 );
623
624 let modified_record = original_record
625 .with_scope_fields_added(&[Field::egressVRFID(42), Field::ingressVRFID(24)]);
626
627 let expected_record = DataRecord::new(
628 Box::new([
629 Field::octetDeltaCount(189),
630 Field::egressVRFID(42),
631 Field::ingressVRFID(24),
632 ]),
633 Box::new([Field::tcpDestinationPort(8080)]),
634 );
635
636 assert_eq!(modified_record, expected_record);
637 }
638
639 #[test]
640 fn test_set_with_fields_added() {
641 let data_record1 = DataRecord::new(
642 Box::new([Field::octetDeltaCount(100)]),
643 Box::new([Field::tcpDestinationPort(80)]),
644 );
645 let data_record2 = DataRecord::new(
646 Box::new([Field::octetDeltaCount(200)]),
647 Box::new([Field::tcpDestinationPort(443)]),
648 );
649
650 let data_set = Set::Data {
651 id: DataSetId::new(256).unwrap(),
652 records: Box::new([data_record1, data_record2]),
653 };
654
655 let modified_set = data_set.with_fields_added(&[Field::packetDeltaCount(5)]);
656
657 let expected_set = Set::Data {
658 id: DataSetId::new(256).unwrap(),
659 records: Box::new([
660 DataRecord::new(
661 Box::new([Field::octetDeltaCount(100)]),
662 Box::new([Field::tcpDestinationPort(80), Field::packetDeltaCount(5)]),
663 ),
664 DataRecord::new(
665 Box::new([Field::octetDeltaCount(200)]),
666 Box::new([Field::tcpDestinationPort(443), Field::packetDeltaCount(5)]),
667 ),
668 ]),
669 };
670
671 assert_eq!(modified_set, expected_set);
672 }
673
674 #[test]
675 fn test_set_with_scope_fields_added() {
676 let data_record1 = DataRecord::new(
677 Box::new([Field::octetDeltaCount(100)]),
678 Box::new([Field::tcpDestinationPort(80)]),
679 );
680 let data_record2 = DataRecord::new(
681 Box::new([Field::octetDeltaCount(200)]),
682 Box::new([Field::tcpDestinationPort(443)]),
683 );
684
685 let data_set = Set::Data {
686 id: DataSetId::new(256).unwrap(),
687 records: Box::new([data_record1, data_record2]),
688 };
689
690 let modified_set = data_set.with_scope_fields_added(&[Field::egressVRFID(42)]);
691
692 let expected_set = Set::Data {
693 id: DataSetId::new(256).unwrap(),
694 records: Box::new([
695 DataRecord::new(
696 Box::new([Field::octetDeltaCount(100), Field::egressVRFID(42)]),
697 Box::new([Field::tcpDestinationPort(80)]),
698 ),
699 DataRecord::new(
700 Box::new([Field::octetDeltaCount(200), Field::egressVRFID(42)]),
701 Box::new([Field::tcpDestinationPort(443)]),
702 ),
703 ]),
704 };
705
706 assert_eq!(modified_set, expected_set);
707 }
708
709 #[test]
710 fn test_ipfix_packet_with_fields_added() {
711 let export_time = Utc.with_ymd_and_hms(2024, 6, 20, 14, 0, 0).unwrap();
712
713 let original_packet = IpfixPacket::new(
714 export_time,
715 0,
716 0,
717 Box::new([
718 Set::Data {
719 id: DataSetId::new(256).unwrap(),
720 records: Box::new([DataRecord::new(
721 Box::new([Field::octetDeltaCount(100)]),
722 Box::new([Field::tcpDestinationPort(80)]),
723 )]),
724 },
725 Set::Template(Box::new([TemplateRecord::new(
726 256,
727 Box::new([
728 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
729 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
730 ]),
731 )])),
732 ]),
733 );
734
735 let modified_packet = original_packet.with_fields_added(&[Field::packetDeltaCount(5)]);
736
737 let expected_packet = IpfixPacket::new(
738 export_time,
739 0,
740 0,
741 Box::new([
742 Set::Data {
743 id: DataSetId::new(256).unwrap(),
744 records: Box::new([DataRecord::new(
745 Box::new([Field::octetDeltaCount(100)]),
746 Box::new([Field::tcpDestinationPort(80), Field::packetDeltaCount(5)]),
747 )]),
748 },
749 Set::Template(Box::new([TemplateRecord::new(
750 256,
751 Box::new([
752 FieldSpecifier::new(ie::IE::octetDeltaCount, 4).unwrap(),
753 FieldSpecifier::new(ie::IE::tcpDestinationPort, 2).unwrap(),
754 ]),
755 )])),
756 ]),
757 );
758
759 assert_eq!(modified_packet, expected_packet);
760 }
761
762 #[test]
763 fn test_ipfix_packet_with_scope_fields_added() {
764 let export_time = Utc.with_ymd_and_hms(2024, 6, 20, 14, 0, 0).unwrap();
765 let data_record = DataRecord::new(
766 Box::new([Field::octetDeltaCount(100)]),
767 Box::new([Field::tcpDestinationPort(80)]),
768 );
769
770 let original_packet = IpfixPacket::new(
771 export_time,
772 0,
773 0,
774 Box::new([Set::Data {
775 id: DataSetId::new(256).unwrap(),
776 records: Box::new([data_record]),
777 }]),
778 );
779
780 let modified_packet = original_packet.with_scope_fields_added(&[Field::egressVRFID(42)]);
781
782 let expected_packet = IpfixPacket::new(
783 export_time,
784 0,
785 0,
786 Box::new([Set::Data {
787 id: DataSetId::new(256).unwrap(),
788 records: Box::new([DataRecord::new(
789 Box::new([Field::octetDeltaCount(100), Field::egressVRFID(42)]),
790 Box::new([Field::tcpDestinationPort(80)]),
791 )]),
792 }]),
793 );
794
795 assert_eq!(modified_packet, expected_packet);
796 }
797}