bgpkit_parser/parser/bmp/messages/
stats_report.rs

1use crate::parser::bmp::error::ParserBmpError;
2use crate::parser::ReadUtils;
3use bytes::{Buf, Bytes};
4use num_enum::{FromPrimitive, IntoPrimitive};
5
6#[derive(Debug, PartialEq, Clone)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct StatsReport {
9    pub stats_count: u32,
10    pub counters: Vec<StatCounter>,
11}
12
13/// Statistics count values
14#[derive(Debug, PartialEq, Clone)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct StatCounter {
17    pub stat_type: StatType,
18    pub stat_len: u16,
19    pub stat_data: StatsData,
20}
21
22#[derive(Debug, PartialEq, Clone)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub enum StatsData {
25    Counter(u32),
26    Gauge(u64),
27    AfiSafiGauge(u16, u8, u64),
28    Unknown(Vec<u8>),
29}
30
31/// Stats counter types enum
32///
33/// Types of BMP statistics are listed here: <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#statistics-types>
34#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[repr(u16)]
37pub enum StatType {
38    PrefixesRejectedByInboundPolicy = 0,
39    DuplicatePrefixAdvertisements = 1,
40    DuplicateWithdrawnPrefixes = 2,
41    UpdatesInvalidatedDueToClusterListLoop = 3,
42    UpdatesInvalidatedDueToASPathLoop = 4,
43    UpdatesInvalidatedDueToOriginatorId = 5,
44    UpdatesInvalidatedDueToASConfedLoop = 6,
45    RoutesInAdjRibsIn = 7,
46    RoutesInLocRib = 8,
47    RoutesInPerAfiSafiAdjRibIn = 9,
48    RoutesInPerAfiSafiLocRib = 10,
49    UpdatesSubjectedToTreatAsWithdraw = 11,
50    PrefixesSubjectedToTreatAsWithdraw = 12,
51    DuplicateUpdateMessagesReceived = 13,
52    RoutesInPrePolicyAdjRibOut = 14,
53    RoutesInPostPolicyAdjRibOut = 15,
54    RoutesInPerAfiSafiPrePolicyAdjRibOut = 16,
55    RoutesInPerAfiSafiPostPolicyAdjRibOut = 17,
56    #[num_enum(catch_all)]
57    Other(u16) = 65535,
58}
59
60pub fn parse_stats_report(data: &mut Bytes) -> Result<StatsReport, ParserBmpError> {
61    let stats_count = data.read_u32()?;
62    let mut counters = vec![];
63    for _ in 0..stats_count {
64        let stat_type = StatType::from(data.read_u16()?);
65        let stat_len = data.read_u16()?;
66        data.has_n_remaining(stat_len as usize)?;
67        let stat_data = match stat_len {
68            4 => StatsData::Counter(data.read_u32()?),
69            8 => StatsData::Gauge(data.read_u64()?),
70            11 => {
71                let afi = data.read_u16()?;
72                let safi = data.read_u8()?;
73                let value = data.read_u64()?;
74                StatsData::AfiSafiGauge(afi, safi, value)
75            }
76            _ => {
77                let mut unknown = vec![0; stat_len as usize];
78                data.copy_to_slice(&mut unknown);
79                StatsData::Unknown(unknown)
80            }
81        };
82        counters.push(StatCounter {
83            stat_type,
84            stat_len,
85            stat_data,
86        })
87    }
88
89    Ok(StatsReport {
90        stats_count,
91        counters,
92    })
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use bytes::{BufMut, BytesMut};
99
100    #[test]
101    fn test_parse_stats_report_counter() {
102        let mut data = BytesMut::new();
103        data.put_u32(1);
104        data.put_u16(0);
105        data.put_u16(4);
106        data.put_u32(1234);
107
108        let result = parse_stats_report(&mut data.freeze());
109        match result {
110            Ok(report) => {
111                assert_eq!(report.stats_count, 1);
112                assert_eq!(
113                    report.counters[0].stat_type,
114                    StatType::PrefixesRejectedByInboundPolicy
115                );
116                assert_eq!(report.counters[0].stat_len, 4);
117                match report.counters[0].stat_data {
118                    StatsData::Counter(value) => assert_eq!(value, 1234),
119                    _ => panic!("Unexpected StatsData!"),
120                }
121            }
122            Err(_) => panic!("Error parsing stats!"),
123        }
124    }
125
126    #[test]
127    fn test_parse_stats_report_gauge() {
128        let mut data = BytesMut::new();
129        data.put_u32(1);
130        data.put_u16(0);
131        data.put_u16(8);
132        data.put_u64(1234);
133
134        let result = parse_stats_report(&mut data.freeze());
135        match result {
136            Ok(report) => {
137                assert_eq!(report.stats_count, 1);
138                assert_eq!(
139                    report.counters[0].stat_type,
140                    StatType::PrefixesRejectedByInboundPolicy
141                );
142                assert_eq!(report.counters[0].stat_len, 8);
143                match report.counters[0].stat_data {
144                    StatsData::Gauge(value) => assert_eq!(value, 1234),
145                    _ => panic!("Unexpected StatsData!"),
146                }
147            }
148            Err(_) => panic!("Error parsing stats!"),
149        }
150    }
151
152    #[test]
153    fn test_parse_stats_report_afi_safi_gauge() {
154        let mut data = BytesMut::new();
155        data.put_u32(1);
156        data.put_u16(9);
157        data.put_u16(11);
158        data.put_u16(1);
159        data.put_u8(2);
160        data.put_u64(1234);
161
162        let result = parse_stats_report(&mut data.freeze());
163        match result {
164            Ok(report) => {
165                assert_eq!(report.stats_count, 1);
166                assert_eq!(
167                    report.counters[0].stat_type,
168                    StatType::RoutesInPerAfiSafiAdjRibIn
169                );
170                assert_eq!(report.counters[0].stat_len, 11);
171                match report.counters[0].stat_data {
172                    StatsData::AfiSafiGauge(afi, safi, value) => {
173                        assert_eq!(afi, 1);
174                        assert_eq!(safi, 2);
175                        assert_eq!(value, 1234)
176                    }
177                    _ => panic!("Unexpected StatsData!"),
178                }
179            }
180            Err(_) => panic!("Error parsing stats!"),
181        }
182    }
183
184    #[test]
185    fn test_parse_stats_report_unknown() {
186        let mut data = BytesMut::new();
187        data.put_u32(1);
188        data.put_u16(100);
189        data.put_u16(1);
190        data.put_u8(3);
191
192        let result = parse_stats_report(&mut data.freeze());
193        match result {
194            Ok(report) => {
195                assert_eq!(report.stats_count, 1);
196                assert_eq!(report.counters[0].stat_type, StatType::Other(100));
197                assert_eq!(report.counters[0].stat_len, 1);
198                match &report.counters[0].stat_data {
199                    StatsData::Unknown(data_vec) => {
200                        assert_eq!(data_vec.len(), 1);
201                        assert_eq!(data_vec[0], 3);
202                    }
203                    _ => panic!("Unexpected StatsData!"),
204                }
205            }
206            Err(_) => panic!("Error parsing stats!"),
207        }
208    }
209
210    // Check parsing error
211    #[test]
212    fn test_parse_stats_report_error() {
213        let mut data = BytesMut::new();
214        data.put_u32(1);
215        data.put_u16(0);
216        data.put_u16(6);
217        data.put_u32(1234);
218
219        let result = parse_stats_report(&mut data.freeze());
220        assert!(result.is_err());
221    }
222}