Skip to main content

bgpkit_parser/parser/mrt/messages/table_dump_v2/
geo_peer_table.rs

1//! RFC 6397: GEO_PEER_TABLE parsing for MRT TABLE_DUMP_V2 format
2
3use crate::encoder::sink::put_u16_len_slice;
4use crate::error::{check_max, EncodingError, ParserError};
5use crate::models::*;
6use crate::parser::ReadUtils;
7use bytes::{Buf, BufMut, Bytes, BytesMut};
8use std::net::IpAddr;
9
10/// Parse GEO_PEER_TABLE message according to RFC 6397
11///
12/// ```text
13/// The GEO_PEER_TABLE is encoded as follows:
14///
15///  0                   1                   2                   3
16///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
17/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18/// |                      Collector BGP ID                        |
19/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20/// |       View Name Length        |     View Name (variable)      |
21/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22/// |                    Collector Latitude                         |
23/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24/// |                    Collector Longitude                        |
25/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26/// |          Peer Count           |   Peer Entries (variable)     |
27/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28/// ```
29///
30/// Each Peer Entry is encoded as:
31///
32/// ```text
33///  0                   1                   2                   3
34///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
35/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36/// |   Peer Type   |
37/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38/// |                         Peer BGP ID                          |
39/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40/// |                    Peer IP Address (variable)                |
41/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42/// |                        Peer AS (variable)                    |
43/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44/// |                        Peer Latitude                         |
45/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
46/// |                        Peer Longitude                        |
47/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48/// ```
49pub fn parse_geo_peer_table(data: &mut Bytes) -> Result<GeoPeerTable, ParserError> {
50    // Read collector BGP ID (4 bytes)
51    let collector_bgp_id = data.read_ipv4_address()?;
52
53    // Read view name length and view name
54    let view_name_len = data.read_u16()? as usize;
55    let view_name = data.read_n_bytes_to_string(view_name_len)?;
56
57    // Read collector coordinates (4 bytes each, 32-bit float)
58    data.has_n_remaining(4)?;
59    let collector_latitude = data.get_f32();
60    data.has_n_remaining(4)?;
61    let collector_longitude = data.get_f32();
62
63    let mut geo_table = GeoPeerTable::new(
64        collector_bgp_id,
65        view_name,
66        collector_latitude,
67        collector_longitude,
68    );
69
70    // Read peer count
71    let peer_count = data.read_u16()?;
72
73    // Parse each peer entry
74    for _ in 0..peer_count {
75        // Read peer type (1 byte)
76        let peer_type_raw = data.read_u8()?;
77        let peer_type = PeerType::from_bits_retain(peer_type_raw);
78
79        // Read peer BGP ID (4 bytes)
80        let peer_bgp_id = data.read_ipv4_address()?;
81
82        // Read peer IP address (4 or 16 bytes depending on address family)
83        let peer_ip: IpAddr = if peer_type.contains(PeerType::ADDRESS_FAMILY_IPV6) {
84            data.read_ipv6_address()?.into()
85        } else {
86            data.read_ipv4_address()?.into()
87        };
88
89        // Read peer AS number (2 or 4 bytes depending on AS size)
90        let peer_asn = if peer_type.contains(PeerType::AS_SIZE_32BIT) {
91            Asn::new_32bit(data.read_u32()?)
92        } else {
93            Asn::new_16bit(data.read_u16()?)
94        };
95
96        // Create the peer structure
97        let peer = Peer {
98            peer_type,
99            peer_bgp_id,
100            peer_ip,
101            peer_asn,
102        };
103
104        // Read peer coordinates (4 bytes each, 32-bit float)
105        data.has_n_remaining(4)?;
106        let peer_latitude = data.get_f32();
107        data.has_n_remaining(4)?;
108        let peer_longitude = data.get_f32();
109
110        let geo_peer = GeoPeer::new(peer, peer_latitude, peer_longitude);
111        geo_table.add_geo_peer(geo_peer);
112    }
113
114    Ok(geo_table)
115}
116
117impl GeoPeerTable {
118    /// Encode the GEO_PEER_TABLE into bytes according to RFC 6397
119    ///
120    /// # Returns
121    ///
122    /// A `Bytes` object containing the encoded GEO_PEER_TABLE data.
123    ///
124    /// # Example
125    ///
126    /// ```
127    /// use std::net::Ipv4Addr;
128    /// use std::str::FromStr;
129    /// use bgpkit_parser::models::{GeoPeerTable, GeoPeer, Peer, Asn};
130    ///
131    /// let mut geo_table = GeoPeerTable::new(
132    ///     Ipv4Addr::from_str("10.0.0.1").unwrap(),
133    ///     "test-view".to_string(),
134    ///     51.5074,  // London latitude
135    ///     -0.1278,  // London longitude
136    /// );
137    ///
138    /// let encoded = geo_table.encode().unwrap();
139    /// ```
140    pub fn encode(&self) -> Result<Bytes, EncodingError> {
141        let mut buf = BytesMut::new();
142
143        // Encode collector BGP ID (4 bytes)
144        buf.put_u32(self.collector_bgp_id.into());
145
146        // Encode view name length and view name
147        put_u16_len_slice(
148            &mut buf,
149            "GeoPeerTable view name length",
150            self.view_name.as_bytes(),
151        )?;
152
153        // Encode collector coordinates (4 bytes each, 32-bit float)
154        buf.put_f32(self.collector_latitude);
155        buf.put_f32(self.collector_longitude);
156
157        // Encode peer count
158        check_max(
159            "GeoPeerTable peer count",
160            self.geo_peers.len(),
161            u16::MAX as usize,
162        )?;
163        buf.put_u16(self.geo_peers.len() as u16);
164
165        // Encode each peer entry
166        for geo_peer in &self.geo_peers {
167            // Encode peer type (1 byte)
168            buf.put_u8(geo_peer.peer.peer_type.bits());
169
170            // Encode peer BGP ID (4 bytes)
171            buf.put_u32(geo_peer.peer.peer_bgp_id.into());
172
173            // Encode peer IP address (4 or 16 bytes depending on address family)
174            match geo_peer.peer.peer_ip {
175                std::net::IpAddr::V4(ipv4) => {
176                    buf.put_u32(ipv4.into());
177                }
178                std::net::IpAddr::V6(ipv6) => {
179                    buf.extend_from_slice(&ipv6.octets());
180                }
181            }
182
183            // Encode peer AS number (2 or 4 bytes depending on AS size)
184            if geo_peer
185                .peer
186                .peer_type
187                .contains(crate::models::PeerType::AS_SIZE_32BIT)
188            {
189                buf.put_u32(u32::from(geo_peer.peer.peer_asn));
190            } else {
191                buf.put_u16(u16::from(geo_peer.peer.peer_asn));
192            }
193
194            // Encode peer coordinates (4 bytes each, 32-bit float)
195            buf.put_f32(geo_peer.peer_latitude);
196            buf.put_f32(geo_peer.peer_longitude);
197        }
198
199        Ok(buf.freeze())
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use bytes::BufMut;
207    use bytes::BytesMut;
208    use std::net::{Ipv4Addr, Ipv6Addr};
209    use std::str::FromStr;
210
211    #[test]
212    fn test_parse_geo_peer_table() {
213        let mut data = BytesMut::new();
214
215        // Collector BGP ID
216        data.put_u32(0x0A000001); // 10.0.0.1
217
218        // View name length and name
219        let view_name = "test-view";
220        data.put_u16(view_name.len() as u16);
221        data.extend_from_slice(view_name.as_bytes());
222
223        // Collector coordinates (London: 51.5074, -0.1278)
224        data.put_f32(51.5074);
225        data.put_f32(-0.1278);
226
227        // Peer count
228        data.put_u16(2);
229
230        // First peer: IPv4, 2-byte AS
231        data.put_u8(0x00); // Peer type: IPv4, 2-byte AS
232        data.put_u32(0x01010101); // BGP ID: 1.1.1.1
233        data.put_u32(0x02020202); // Peer IP: 2.2.2.2
234        data.put_u16(65001); // AS number
235        data.put_f32(40.7128); // New York latitude
236        data.put_f32(-74.0060); // New York longitude
237
238        // Second peer: IPv6, 4-byte AS
239        data.put_u8(0x03); // Peer type: IPv6, 4-byte AS
240        data.put_u32(0x03030303); // BGP ID: 3.3.3.3
241                                  // IPv6 address: 2001:db8::1
242        data.extend_from_slice(&[
243            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
244            0x00, 0x01,
245        ]);
246        data.put_u32(65002); // AS number
247        data.put_f32(f32::NAN); // Private latitude
248        data.put_f32(f32::NAN); // Private longitude
249
250        let mut bytes = data.freeze();
251        let result = parse_geo_peer_table(&mut bytes).unwrap();
252
253        // Check collector info
254        assert_eq!(
255            result.collector_bgp_id,
256            Ipv4Addr::from_str("10.0.0.1").unwrap()
257        );
258        assert_eq!(result.view_name, "test-view");
259        assert_eq!(result.collector_latitude, 51.5074);
260        assert_eq!(result.collector_longitude, -0.1278);
261
262        // Check peer count
263        assert_eq!(result.geo_peers.len(), 2);
264
265        // Check first peer
266        let peer1 = &result.geo_peers[0];
267        assert_eq!(
268            peer1.peer.peer_bgp_id,
269            Ipv4Addr::from_str("1.1.1.1").unwrap()
270        );
271        assert_eq!(
272            peer1.peer.peer_ip,
273            IpAddr::V4(Ipv4Addr::from_str("2.2.2.2").unwrap())
274        );
275        assert_eq!(peer1.peer.peer_asn, Asn::new_16bit(65001));
276        assert!(!peer1.peer.peer_type.contains(PeerType::ADDRESS_FAMILY_IPV6));
277        assert!(!peer1.peer.peer_type.contains(PeerType::AS_SIZE_32BIT));
278        assert_eq!(peer1.peer_latitude, 40.7128);
279        assert_eq!(peer1.peer_longitude, -74.0060);
280
281        // Check second peer
282        let peer2 = &result.geo_peers[1];
283        assert_eq!(
284            peer2.peer.peer_bgp_id,
285            Ipv4Addr::from_str("3.3.3.3").unwrap()
286        );
287        assert_eq!(
288            peer2.peer.peer_ip,
289            IpAddr::V6(Ipv6Addr::from_str("2001:db8::1").unwrap())
290        );
291        assert_eq!(peer2.peer.peer_asn, Asn::new_32bit(65002));
292        assert!(peer2.peer.peer_type.contains(PeerType::ADDRESS_FAMILY_IPV6));
293        assert!(peer2.peer.peer_type.contains(PeerType::AS_SIZE_32BIT));
294        assert!(peer2.peer_latitude.is_nan());
295        assert!(peer2.peer_longitude.is_nan());
296    }
297
298    #[test]
299    fn test_parse_geo_peer_table_private_collector() {
300        let mut data = BytesMut::new();
301
302        // Collector BGP ID
303        data.put_u32(0x0A000001); // 10.0.0.1
304
305        // View name length and name
306        let view_name = "private-view";
307        data.put_u16(view_name.len() as u16);
308        data.extend_from_slice(view_name.as_bytes());
309
310        // Private collector coordinates (NaN)
311        data.put_f32(f32::NAN);
312        data.put_f32(f32::NAN);
313
314        // No peers
315        data.put_u16(0);
316
317        let mut bytes = data.freeze();
318        let result = parse_geo_peer_table(&mut bytes).unwrap();
319
320        assert!(result.collector_latitude.is_nan());
321        assert!(result.collector_longitude.is_nan());
322        assert_eq!(result.geo_peers.len(), 0);
323    }
324
325    #[test]
326    fn test_geo_peer_table_round_trip() {
327        let collector_bgp_id = Ipv4Addr::from_str("192.168.1.1").unwrap();
328        let mut original_table = GeoPeerTable::new(
329            collector_bgp_id,
330            "round-trip-test".to_string(),
331            37.7749,   // San Francisco latitude
332            -122.4194, // San Francisco longitude
333        );
334
335        // Add peers with different configurations
336        let peer1 = Peer::new(
337            Ipv4Addr::from_str("203.0.113.1").unwrap(),
338            Ipv4Addr::from_str("203.0.113.2").unwrap().into(),
339            Asn::new_16bit(64512),
340        );
341        let geo_peer1 = GeoPeer::new(peer1, 35.6762, 139.6503); // Tokyo
342        original_table.add_geo_peer(geo_peer1);
343
344        let peer2 = Peer::new(
345            Ipv4Addr::from_str("198.51.100.1").unwrap(),
346            std::net::Ipv6Addr::from_str("2001:db8:85a3::8a2e:370:7334")
347                .unwrap()
348                .into(),
349            Asn::new_32bit(4200000000),
350        );
351        let geo_peer2 = GeoPeer::new(peer2, -33.8688, 151.2093); // Sydney
352        original_table.add_geo_peer(geo_peer2);
353
354        // Encode and then parse back
355        let encoded = original_table.encode().unwrap();
356        let mut encoded_bytes = encoded;
357        let parsed_table = parse_geo_peer_table(&mut encoded_bytes).unwrap();
358
359        // Verify all fields match
360        assert_eq!(
361            parsed_table.collector_bgp_id,
362            original_table.collector_bgp_id
363        );
364        assert_eq!(parsed_table.view_name, original_table.view_name);
365        assert_eq!(
366            parsed_table.collector_latitude,
367            original_table.collector_latitude
368        );
369        assert_eq!(
370            parsed_table.collector_longitude,
371            original_table.collector_longitude
372        );
373        assert_eq!(parsed_table.geo_peers.len(), original_table.geo_peers.len());
374
375        // Check first peer
376        let parsed_peer1 = &parsed_table.geo_peers[0];
377        let original_peer1 = &original_table.geo_peers[0];
378        assert_eq!(
379            parsed_peer1.peer.peer_bgp_id,
380            original_peer1.peer.peer_bgp_id
381        );
382        assert_eq!(parsed_peer1.peer.peer_ip, original_peer1.peer.peer_ip);
383        assert_eq!(parsed_peer1.peer.peer_asn, original_peer1.peer.peer_asn);
384        assert_eq!(parsed_peer1.peer.peer_type, original_peer1.peer.peer_type);
385        assert_eq!(parsed_peer1.peer_latitude, original_peer1.peer_latitude);
386        assert_eq!(parsed_peer1.peer_longitude, original_peer1.peer_longitude);
387
388        // Check second peer
389        let parsed_peer2 = &parsed_table.geo_peers[1];
390        let original_peer2 = &original_table.geo_peers[1];
391        assert_eq!(
392            parsed_peer2.peer.peer_bgp_id,
393            original_peer2.peer.peer_bgp_id
394        );
395        assert_eq!(parsed_peer2.peer.peer_ip, original_peer2.peer.peer_ip);
396        assert_eq!(parsed_peer2.peer.peer_asn, original_peer2.peer.peer_asn);
397        assert_eq!(parsed_peer2.peer.peer_type, original_peer2.peer.peer_type);
398        assert_eq!(parsed_peer2.peer_latitude, original_peer2.peer_latitude);
399        assert_eq!(parsed_peer2.peer_longitude, original_peer2.peer_longitude);
400
401        // Test overall equality
402        assert_eq!(parsed_table, original_table);
403    }
404
405    #[test]
406    fn test_geo_peer_table_encoding() {
407        let collector_bgp_id = Ipv4Addr::from_str("10.0.0.1").unwrap();
408        let mut geo_table = GeoPeerTable::new(
409            collector_bgp_id,
410            "test-view".to_string(),
411            51.5074, // London latitude
412            -0.1278, // London longitude
413        );
414
415        // Add a peer with IPv4 address and 2-byte AS
416        let peer1 = Peer::new(
417            Ipv4Addr::from_str("1.1.1.1").unwrap(),
418            Ipv4Addr::from_str("2.2.2.2").unwrap().into(),
419            Asn::new_16bit(65001),
420        );
421        let geo_peer1 = GeoPeer::new(peer1, 40.7128, -74.0060); // New York
422        geo_table.add_geo_peer(geo_peer1);
423
424        // Add a peer with IPv6 address and 4-byte AS
425        let peer2 = Peer::new(
426            Ipv4Addr::from_str("3.3.3.3").unwrap(),
427            std::net::Ipv6Addr::from_str("2001:db8::1").unwrap().into(),
428            Asn::new_32bit(65002),
429        );
430        let geo_peer2 = GeoPeer::new(peer2, f32::NAN, f32::NAN); // Private coordinates
431        geo_table.add_geo_peer(geo_peer2);
432
433        // Encode the geo table
434        let encoded = geo_table.encode().unwrap();
435
436        // Create expected bytes manually for comparison
437        let mut expected = BytesMut::new();
438
439        // Collector BGP ID
440        expected.put_u32(0x0A000001); // 10.0.0.1
441
442        // View name length and name
443        let view_name = "test-view";
444        expected.put_u16(view_name.len() as u16);
445        expected.extend_from_slice(view_name.as_bytes());
446
447        // Collector coordinates
448        expected.put_f32(51.5074);
449        expected.put_f32(-0.1278);
450
451        // Peer count
452        expected.put_u16(2);
453
454        // First peer: IPv4, 2-byte AS
455        expected.put_u8(0x00); // Peer type: IPv4, 2-byte AS
456        expected.put_u32(0x01010101); // BGP ID: 1.1.1.1
457        expected.put_u32(0x02020202); // Peer IP: 2.2.2.2
458        expected.put_u16(65001); // AS number
459        expected.put_f32(40.7128); // New York latitude
460        expected.put_f32(-74.0060); // New York longitude
461
462        // Second peer: IPv6, 4-byte AS
463        expected.put_u8(0x03); // Peer type: IPv6, 4-byte AS
464        expected.put_u32(0x03030303); // BGP ID: 3.3.3.3
465        expected.extend_from_slice(&[
466            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
467            0x00, 0x01,
468        ]); // IPv6: 2001:db8::1
469        expected.put_u32(65002); // AS number
470        expected.put_f32(f32::NAN); // Private latitude
471        expected.put_f32(f32::NAN); // Private longitude
472
473        let expected_bytes = expected.freeze();
474
475        // Compare the encoded bytes with expected
476        assert_eq!(encoded.len(), expected_bytes.len());
477        assert_eq!(encoded, expected_bytes);
478    }
479}