Skip to main content

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    RoutesInPrePolicyAdjRibIn = 18,
57    RoutesInPerAfiSafiPrePolicyAdjRibIn = 19,
58    RoutesInPostPolicyAdjRibIn = 20,
59    RoutesInPerAfiSafiPostPolicyAdjRibIn = 21,
60    RoutesInPerAfiSafiPrePolicyAdjRibInRejectedByPolicy = 22,
61    RoutesInPerAfiSafiPostPolicyAdjRibInAcceptedByPolicy = 23,
62    RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibSuppressed = 26,
63    RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibStaleByGracefulRestart = 27,
64    RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibStaleByLongLivedGracefulRestart = 28,
65    RoutesInPostPolicyAdjRibInLeftBeforeThreshold = 29,
66    RoutesInPerAfiSafiPostPolicyAdjRibInLeftBeforeThreshold = 30,
67    RoutesInPostPolicyAdjRibInOrLocRibLeftBeforeLicenseThreshold = 31,
68    RoutesInPerAfiSafiPostPolicyAdjRibInOrLocRibLeftBeforeLicenseThreshold = 32,
69    RoutesInPrePolicyAdjRibInRejectedDueToMaxAsPathLength = 33,
70    RoutesInPerAfiSafiPrePolicyAdjRibInRejectedDueToMaxAsPathLength = 34,
71    RoutesInPerAfiSafiPostPolicyAdjRibInInvalidatedByRoa = 35,
72    RoutesInPerAfiSafiPostPolicyAdjRibInValidatedByRoa = 36,
73    RoutesInPerAfiSafiPostPolicyAdjRibInRpkiStateNotFound = 37,
74    RoutesInPerAfiSafiPrePolicyAdjRibOutRejectedByPolicy = 38,
75    RoutesInPrePolicyAdjRibOutFilteredDueToMaxAsPathLength = 39,
76    RoutesInPerAfiSafiPrePolicyAdjRibOutFilteredDueToMaxAsPathLength = 40,
77    RoutesInPerAfiSafiPostPolicyAdjRibOutInvalidatedByRoa = 41,
78    RoutesInPerAfiSafiPostPolicyAdjRibOutValidatedByRoa = 42,
79    RoutesInPerAfiSafiPostPolicyAdjRibOutRpkiStateNotFound = 43,
80    #[num_enum(catch_all)]
81    Other(u16) = 65535,
82}
83
84pub fn parse_stats_report(data: &mut Bytes) -> Result<StatsReport, ParserBmpError> {
85    let stats_count = data.read_u32()?;
86    let mut counters = vec![];
87    for _ in 0..stats_count {
88        let stat_type = StatType::from(data.read_u16()?);
89        let stat_len = data.read_u16()?;
90        data.has_n_remaining(stat_len as usize)?;
91        let stat_data = match stat_len {
92            4 => StatsData::Counter(data.read_u32()?),
93            8 => StatsData::Gauge(data.read_u64()?),
94            11 => {
95                let afi = data.read_u16()?;
96                let safi = data.read_u8()?;
97                let value = data.read_u64()?;
98                StatsData::AfiSafiGauge(afi, safi, value)
99            }
100            _ => {
101                let mut unknown = vec![0; stat_len as usize];
102                data.copy_to_slice(&mut unknown);
103                StatsData::Unknown(unknown)
104            }
105        };
106        counters.push(StatCounter {
107            stat_type,
108            stat_len,
109            stat_data,
110        })
111    }
112
113    Ok(StatsReport {
114        stats_count,
115        counters,
116    })
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use bytes::{BufMut, BytesMut};
123
124    #[test]
125    fn test_parse_stats_report_counter() {
126        let mut data = BytesMut::new();
127        data.put_u32(1);
128        data.put_u16(0);
129        data.put_u16(4);
130        data.put_u32(1234);
131
132        let result = parse_stats_report(&mut data.freeze());
133        match result {
134            Ok(report) => {
135                assert_eq!(report.stats_count, 1);
136                assert_eq!(
137                    report.counters[0].stat_type,
138                    StatType::PrefixesRejectedByInboundPolicy
139                );
140                assert_eq!(report.counters[0].stat_len, 4);
141                match report.counters[0].stat_data {
142                    StatsData::Counter(value) => assert_eq!(value, 1234),
143                    _ => panic!("Unexpected StatsData!"),
144                }
145            }
146            Err(_) => panic!("Error parsing stats!"),
147        }
148    }
149
150    #[test]
151    fn test_parse_stats_report_gauge() {
152        let mut data = BytesMut::new();
153        data.put_u32(1);
154        data.put_u16(0);
155        data.put_u16(8);
156        data.put_u64(1234);
157
158        let result = parse_stats_report(&mut data.freeze());
159        match result {
160            Ok(report) => {
161                assert_eq!(report.stats_count, 1);
162                assert_eq!(
163                    report.counters[0].stat_type,
164                    StatType::PrefixesRejectedByInboundPolicy
165                );
166                assert_eq!(report.counters[0].stat_len, 8);
167                match report.counters[0].stat_data {
168                    StatsData::Gauge(value) => assert_eq!(value, 1234),
169                    _ => panic!("Unexpected StatsData!"),
170                }
171            }
172            Err(_) => panic!("Error parsing stats!"),
173        }
174    }
175
176    #[test]
177    fn test_parse_stats_report_afi_safi_gauge() {
178        let mut data = BytesMut::new();
179        data.put_u32(1);
180        data.put_u16(9);
181        data.put_u16(11);
182        data.put_u16(1);
183        data.put_u8(2);
184        data.put_u64(1234);
185
186        let result = parse_stats_report(&mut data.freeze());
187        match result {
188            Ok(report) => {
189                assert_eq!(report.stats_count, 1);
190                assert_eq!(
191                    report.counters[0].stat_type,
192                    StatType::RoutesInPerAfiSafiAdjRibIn
193                );
194                assert_eq!(report.counters[0].stat_len, 11);
195                match report.counters[0].stat_data {
196                    StatsData::AfiSafiGauge(afi, safi, value) => {
197                        assert_eq!(afi, 1);
198                        assert_eq!(safi, 2);
199                        assert_eq!(value, 1234)
200                    }
201                    _ => panic!("Unexpected StatsData!"),
202                }
203            }
204            Err(_) => panic!("Error parsing stats!"),
205        }
206    }
207
208    #[test]
209    fn test_parse_stats_report_unknown() {
210        let mut data = BytesMut::new();
211        data.put_u32(1);
212        data.put_u16(100);
213        data.put_u16(1);
214        data.put_u8(3);
215
216        let result = parse_stats_report(&mut data.freeze());
217        match result {
218            Ok(report) => {
219                assert_eq!(report.stats_count, 1);
220                assert_eq!(report.counters[0].stat_type, StatType::Other(100));
221                assert_eq!(report.counters[0].stat_len, 1);
222                match &report.counters[0].stat_data {
223                    StatsData::Unknown(data_vec) => {
224                        assert_eq!(data_vec.len(), 1);
225                        assert_eq!(data_vec[0], 3);
226                    }
227                    _ => panic!("Unexpected StatsData!"),
228                }
229            }
230            Err(_) => panic!("Error parsing stats!"),
231        }
232    }
233
234    // Check parsing error
235    #[test]
236    fn test_parse_stats_report_error() {
237        let mut data = BytesMut::new();
238        data.put_u32(1);
239        data.put_u16(0);
240        data.put_u16(6);
241        data.put_u32(1234);
242
243        let result = parse_stats_report(&mut data.freeze());
244        assert!(result.is_err());
245    }
246}