bgpkit_parser/parser/bmp/messages/
termination_message.rs

1use crate::parser::bmp::error::ParserBmpError;
2use crate::parser::ReadUtils;
3use bytes::{Buf, Bytes};
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5use std::convert::TryFrom;
6
7#[derive(Debug, PartialEq, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct TerminationMessage {
10    pub tlvs: Vec<TerminationTlv>,
11}
12
13#[derive(Debug, PartialEq, Clone)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct TerminationTlv {
16    pub info_type: TerminationTlvType,
17    pub info_len: u16,
18    pub info_value: TerminationTlvValue,
19}
20
21#[derive(Debug, PartialEq, Clone)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum TerminationTlvValue {
24    String(String),
25    Reason(TerminationReason),
26}
27
28#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[repr(u16)]
31pub enum TerminationReason {
32    AdministrativelyClosed = 0,
33    UnspecifiedReason = 1,
34    OutOfResources = 2,
35    RedundantConnection = 3,
36    PermanentlyAdministrativelyClosed = 4,
37}
38
39///Type-Length-Value Type
40///
41/// For more, see: https://datatracker.ietf.org/doc/html/rfc1213
42#[derive(Debug, TryFromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[repr(u16)]
45pub enum TerminationTlvType {
46    String = 0,
47    Reason = 1,
48}
49
50pub fn parse_termination_message(data: &mut Bytes) -> Result<TerminationMessage, ParserBmpError> {
51    let mut tlvs = vec![];
52
53    while data.remaining() > 4 {
54        let info_type: TerminationTlvType = TerminationTlvType::try_from(data.read_u16()?)?;
55        let info_len = data.read_u16()?;
56        if data.remaining() < info_len as usize {
57            // not enough bytes to read
58            break;
59        }
60        let info_value = match info_type {
61            TerminationTlvType::String => {
62                let info = data.read_n_bytes_to_string(info_len as usize)?;
63                TerminationTlvValue::String(info)
64            }
65            TerminationTlvType::Reason => {
66                let reason = TerminationReason::try_from(data.read_u16()?)?;
67                TerminationTlvValue::Reason(reason)
68            }
69        };
70        tlvs.push(TerminationTlv {
71            info_type,
72            info_len,
73            info_value,
74        })
75    }
76
77    Ok(TerminationMessage { tlvs })
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use bytes::Bytes;
84
85    #[test]
86    fn test_parse_termination_message() {
87        // Create a Bytes object to simulate the incoming data
88        let mut data = Bytes::copy_from_slice(&[
89            0, 0, // info_type: String
90            0, 5, // info_len: 5
91            67, 79, 68, 69, 83, // info: "CODES"
92            0, 1, // info_type: Reason
93            0, 2, // info_len: 2
94            0, 1, // info: UnspecifiedReason
95        ]);
96
97        // Check if parse_termination_message correctly reads the data
98        let result = parse_termination_message(&mut data);
99        match result {
100            Ok(termination_message) => {
101                assert_eq!(termination_message.tlvs.len(), 2);
102
103                // tlvs[0] assertions
104                assert_eq!(
105                    termination_message.tlvs[0].info_type,
106                    TerminationTlvType::String
107                );
108                assert_eq!(termination_message.tlvs[0].info_len, 5);
109                assert_eq!(
110                    termination_message.tlvs[0].info_value,
111                    TerminationTlvValue::String("CODES".to_string())
112                );
113
114                // tlvs[1] assertions
115                assert_eq!(
116                    termination_message.tlvs[1].info_type,
117                    TerminationTlvType::Reason
118                );
119                assert_eq!(termination_message.tlvs[1].info_len, 2);
120                assert_eq!(
121                    termination_message.tlvs[1].info_value,
122                    TerminationTlvValue::Reason(TerminationReason::UnspecifiedReason)
123                );
124            }
125            Err(e) => panic!("Failed to parse: {}", e),
126        }
127    }
128}