bgpkit_parser/models/network/
prefix.rs

1use crate::models::BgpModelsError;
2#[cfg(feature = "parser")]
3use bytes::{BufMut, Bytes, BytesMut};
4use ipnet::IpNet;
5use std::fmt::{Debug, Display, Formatter};
6use std::str::FromStr;
7
8/// A representation of a network prefix with an optional path ID.
9#[derive(PartialEq, Eq, Clone, Copy, Hash)]
10pub struct NetworkPrefix {
11    pub prefix: IpNet,
12    pub path_id: Option<u32>,
13}
14
15// Attempt to reduce the size of the debug output
16impl Debug for NetworkPrefix {
17    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
18        match self.path_id {
19            Some(path_id) => write!(f, "{}#{}", self.prefix, path_id),
20            None => write!(f, "{}", self.prefix),
21        }
22    }
23}
24
25impl FromStr for NetworkPrefix {
26    type Err = BgpModelsError;
27
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        let prefix = IpNet::from_str(s)?;
30        Ok(NetworkPrefix {
31            prefix,
32            path_id: None,
33        })
34    }
35}
36
37impl NetworkPrefix {
38    pub fn new(prefix: IpNet, path_id: Option<u32>) -> NetworkPrefix {
39        NetworkPrefix { prefix, path_id }
40    }
41
42    #[cfg(feature = "parser")]
43    /// Encodes the IPNet prefix into a byte slice.
44    ///
45    /// # Arguments
46    ///
47    /// * `add_path` - A bool indicating whether to include the path identifier in the encoded bytes.
48    ///
49    /// # Returns
50    ///
51    /// A `Bytes` slice containing the encoded IPNet prefix.
52    ///
53    /// # Example
54    ///
55    /// ```rust
56    /// use std::str::FromStr;
57    /// use bytes::Bytes;
58    /// use ipnet::{IpNet, Ipv4Net};
59    /// use bgpkit_parser::models::NetworkPrefix;
60    ///
61    /// let prefix = NetworkPrefix::from_str("192.168.0.0/24").unwrap();
62    /// let encoded_bytes = prefix.encode();
63    ///
64    /// assert_eq!(encoded_bytes.iter().as_slice(), &[24, 192, 168, 0]);
65    /// ```
66    pub fn encode(&self) -> Bytes {
67        let mut bytes = BytesMut::new();
68
69        // encode path identifier if it exists
70        if let Some(path_id) = self.path_id {
71            // encode path identifier
72            bytes.put_u32(path_id);
73        }
74
75        // encode prefix
76
77        let bit_len = self.prefix.prefix_len();
78        let byte_len = bit_len.div_ceil(8) as usize;
79        bytes.put_u8(bit_len);
80
81        match self.prefix {
82            IpNet::V4(prefix) => {
83                bytes.put_slice(&prefix.addr().octets()[0..byte_len]);
84            }
85            IpNet::V6(prefix) => {
86                bytes.put_slice(&prefix.addr().octets()[0..byte_len]);
87            }
88        };
89        bytes.freeze()
90    }
91}
92
93impl Display for NetworkPrefix {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{}", self.prefix)
96    }
97}
98
99#[cfg(feature = "serde")]
100mod serde_impl {
101    use super::*;
102    use serde::{Deserialize, Deserializer, Serialize, Serializer};
103
104    #[derive(Serialize, Deserialize)]
105    #[serde(untagged, deny_unknown_fields)]
106    enum SerdeNetworkPrefixRepr {
107        PlainPrefix(IpNet),
108        WithPathId { prefix: IpNet, path_id: u32 },
109    }
110
111    impl Serialize for NetworkPrefix {
112        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
113        where
114            S: Serializer,
115        {
116            match self.path_id {
117                Some(path_id) => SerdeNetworkPrefixRepr::WithPathId {
118                    prefix: self.prefix,
119                    path_id,
120                }
121                .serialize(serializer),
122                None => self.prefix.serialize(serializer),
123            }
124        }
125    }
126
127    impl<'de> Deserialize<'de> for NetworkPrefix {
128        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129        where
130            D: Deserializer<'de>,
131        {
132            match SerdeNetworkPrefixRepr::deserialize(deserializer)? {
133                SerdeNetworkPrefixRepr::PlainPrefix(prefix) => Ok(NetworkPrefix {
134                    prefix,
135                    path_id: None,
136                }),
137                SerdeNetworkPrefixRepr::WithPathId { prefix, path_id } => Ok(NetworkPrefix {
138                    prefix,
139                    path_id: Some(path_id),
140                }),
141            }
142        }
143    }
144}
145
146// Here's the test code appended at the end of your source code
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_fromstr() {
153        let prefix_str = "192.168.0.0/24";
154        let network_prefix = NetworkPrefix::from_str(prefix_str).unwrap();
155        assert_eq!(
156            network_prefix.prefix,
157            IpNet::from_str("192.168.0.0/24").unwrap()
158        );
159        assert_eq!(network_prefix.path_id, None);
160    }
161
162    #[test]
163    #[cfg(feature = "parser")]
164    fn test_encode() {
165        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
166        let network_prefix = NetworkPrefix::new(prefix, Some(1));
167        let _encoded = network_prefix.encode();
168    }
169
170    #[test]
171    fn test_display() {
172        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
173        let network_prefix = NetworkPrefix::new(prefix, Some(1));
174        assert_eq!(network_prefix.to_string(), "192.168.0.0/24");
175    }
176
177    #[test]
178    #[cfg(feature = "serde")]
179    fn test_serialization() {
180        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
181        let network_prefix = NetworkPrefix::new(prefix, Some(1));
182        let serialized = serde_json::to_string(&network_prefix).unwrap();
183        assert_eq!(serialized, "{\"prefix\":\"192.168.0.0/24\",\"path_id\":1}");
184    }
185
186    #[test]
187    #[cfg(feature = "serde")]
188    fn test_deserialization() {
189        let serialized = "{\"prefix\":\"192.168.0.0/24\",\"path_id\":1}";
190        let deserialized: NetworkPrefix = serde_json::from_str(serialized).unwrap();
191        assert_eq!(
192            deserialized.prefix,
193            IpNet::from_str("192.168.0.0/24").unwrap()
194        );
195        assert_eq!(deserialized.path_id, Some(1));
196    }
197
198    #[test]
199    #[cfg(feature = "serde")]
200    fn test_binary_serialization_with_path_id() {
201        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
202        let network_prefix = NetworkPrefix::new(prefix, Some(42));
203        // Test non-human readable serialization (binary-like)
204        let serialized = serde_json::to_vec(&network_prefix).unwrap();
205        let deserialized: NetworkPrefix = serde_json::from_slice(&serialized).unwrap();
206        assert_eq!(deserialized.prefix, prefix);
207        assert_eq!(deserialized.path_id, Some(42));
208    }
209
210    #[test]
211    fn test_debug() {
212        let prefix = IpNet::from_str("192.168.0.0/24").unwrap();
213        let network_prefix = NetworkPrefix::new(prefix, Some(1));
214        assert_eq!(format!("{network_prefix:?}"), "192.168.0.0/24#1");
215    }
216}