Skip to main content

bgpkit_parser/parser/bmp/messages/
initiation_message.rs

1use crate::parser::bmp::error::ParserBmpError;
2use crate::parser::ReadUtils;
3use bytes::{Buf, Bytes};
4use num_enum::{FromPrimitive, IntoPrimitive};
5
6#[derive(Debug, Clone, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct InitiationMessage {
9    pub tlvs: Vec<InitiationTlv>,
10}
11
12#[derive(Debug, PartialEq, Clone)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct InitiationTlv {
15    pub info_type: InitiationTlvType,
16    pub info_len: u16,
17    pub info: String,
18}
19
20/// BMP Initiation Information TLV Type
21///
22/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#initiation-information-tlvs>
23///
24/// Per RFC 9736, the "BMP Initiation and Peer Up Information TLVs" registry was renamed
25/// to "BMP Initiation Information TLVs", and types 3, 4, and 65535 are reserved in this
26/// namespace. The VrTableName and AdminLabel variants are retained for backward compatibility
27/// with pre-RFC 9736 implementations.
28#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[repr(u16)]
31pub enum InitiationTlvType {
32    String = 0,
33    SysDescr = 1,
34    SysName = 2,
35    /// Reserved per RFC 9736, retained for backward compatibility.
36    VrTableName = 3,
37    /// Reserved per RFC 9736, retained for backward compatibility.
38    AdminLabel = 4,
39    #[num_enum(catch_all)]
40    Unknown(u16) = 65535,
41}
42
43/// Parse BMP initiation message
44///
45/// <https://www.rfc-editor.org/rfc/rfc7854#section-4.3>
46pub fn parse_initiation_message(data: &mut Bytes) -> Result<InitiationMessage, ParserBmpError> {
47    let mut tlvs = vec![];
48
49    while data.remaining() > 4 {
50        let info_type = InitiationTlvType::from(data.read_u16()?);
51        let info_len = data.read_u16()?;
52        if data.remaining() < info_len as usize {
53            // not enough bytes to read
54            break;
55        }
56        let info = data.read_n_bytes_to_string(info_len as usize)?;
57        tlvs.push(InitiationTlv {
58            info_type,
59            info_len,
60            info,
61        });
62    }
63
64    Ok(InitiationMessage { tlvs })
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use bytes::{BufMut, BytesMut};
71
72    #[test]
73    fn test_parse_initiation_message() {
74        let mut buffer = BytesMut::new();
75        buffer.put_u16(1); // InitiationTlvType::SysDescr
76        buffer.put_u16(5); // Length of following info
77        buffer.put_slice(b"Test1"); // Info
78
79        let mut bytes = buffer.freeze();
80
81        match parse_initiation_message(&mut bytes) {
82            Ok(initiation_message) => {
83                for tlv in initiation_message.tlvs {
84                    assert_eq!(tlv.info_type, InitiationTlvType::SysDescr);
85                    assert_eq!(tlv.info_len, 5);
86                    assert_eq!(tlv.info, "Test1".to_string());
87                }
88            }
89            Err(_) => panic!("Failed to parse initiation message"),
90        }
91    }
92
93    #[test]
94    fn test_parse_unknown_initiation_tlv() {
95        let mut buffer = BytesMut::new();
96        // Known TLV (SysDescr)
97        buffer.put_u16(1); // InitiationTlvType::SysDescr
98        buffer.put_u16(4);
99        buffer.put_slice(b"Test");
100        // Unknown TLV (unassigned type 255)
101        buffer.put_u16(255);
102        buffer.put_u16(3);
103        buffer.put_slice(b"Foo");
104
105        let mut bytes = buffer.freeze();
106        let result = parse_initiation_message(&mut bytes).unwrap();
107
108        assert_eq!(result.tlvs.len(), 2);
109        assert_eq!(result.tlvs[0].info_type, InitiationTlvType::SysDescr);
110        assert_eq!(result.tlvs[0].info, "Test");
111        assert_eq!(result.tlvs[1].info_type, InitiationTlvType::Unknown(255));
112        assert_eq!(result.tlvs[1].info_len, 3);
113        assert_eq!(result.tlvs[1].info, "Foo");
114    }
115
116    #[test]
117    fn test_debug() {
118        let initiation_message = InitiationMessage {
119            tlvs: vec![InitiationTlv {
120                info_type: InitiationTlvType::SysDescr,
121                info_len: 5,
122                info: "Test1".to_string(),
123            }],
124        };
125        assert_eq!(
126            format!("{initiation_message:?}"),
127            "InitiationMessage { tlvs: [InitiationTlv { info_type: SysDescr, info_len: 5, info: \"Test1\" }] }"
128        );
129    }
130}