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