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