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
129    #[test]
130    fn test_parse_termination_message_truncated() {
131        // Test case where there's not enough bytes to read for a TLV
132
133        // Case 1: Not enough bytes for info_len
134        let mut data = Bytes::copy_from_slice(&[
135            0, 0, // info_type: String
136            0, // Only one byte for info_len, should be two
137        ]);
138
139        let result = parse_termination_message(&mut data);
140        assert!(result.is_ok());
141        assert_eq!(result.unwrap().tlvs.len(), 0);
142
143        // Case 2: Not enough bytes for info_value
144        let mut data = Bytes::copy_from_slice(&[
145            0, 0, // info_type: String
146            0, 5, // info_len: 5
147            67, 79, 68, // Only 3 bytes for info_value, should be 5
148        ]);
149
150        let result = parse_termination_message(&mut data);
151        // This should still succeed, but with an empty TLV list
152        // because the function breaks out of the loop when there's not enough bytes
153        assert!(result.is_ok());
154        assert_eq!(result.unwrap().tlvs.len(), 0);
155
156        // Case 3: Not enough bytes for the second TLV's info_value
157        let mut data = Bytes::copy_from_slice(&[
158            0, 0, // info_type: String
159            0, 5, // info_len: 5
160            67, 79, 68, 69, 83, // info: "CODES"
161            0, 1, // info_type: Reason
162            0, 2, // info_len: 2
163            0, // Only one byte for info_value, should be two
164        ]);
165
166        let result = parse_termination_message(&mut data);
167        // This should still succeed, but with only the first TLV
168        assert!(result.is_ok());
169        let termination_message = result.unwrap();
170        assert_eq!(termination_message.tlvs.len(), 1);
171        assert_eq!(
172            termination_message.tlvs[0].info_type,
173            TerminationTlvType::String
174        );
175        assert_eq!(
176            termination_message.tlvs[0].info_value,
177            TerminationTlvValue::String("CODES".to_string())
178        );
179    }
180}