Skip to main content

irontide_tracker/
compact.rs

1use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
2
3use crate::error::{Error, Result};
4
5/// Parse compact peer format (BEP 23): 6 bytes per peer (4 IP + 2 port).
6///
7/// # Errors
8///
9/// Returns an error if the data length is not a multiple of 6.
10pub fn parse_compact_peers(data: &[u8]) -> Result<Vec<SocketAddr>> {
11    if !data.len().is_multiple_of(6) {
12        return Err(Error::InvalidResponse(format!(
13            "compact peers length {} is not a multiple of 6",
14            data.len()
15        )));
16    }
17
18    let mut peers = Vec::with_capacity(data.len() / 6);
19    for chunk in data.chunks_exact(6) {
20        let ip = Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]);
21        let port = u16::from_be_bytes([chunk[4], chunk[5]]);
22        peers.push(SocketAddr::V4(SocketAddrV4::new(ip, port)));
23    }
24    Ok(peers)
25}
26
27/// Encode IPv4 peers to compact format (BEP 23): 6 bytes per peer (4 IP + 2 port).
28///
29/// IPv6 addresses are silently skipped.
30#[must_use]
31pub fn encode_compact_peers(peers: &[SocketAddr]) -> Vec<u8> {
32    let mut buf = Vec::with_capacity(peers.len() * 6);
33    for peer in peers {
34        if let SocketAddr::V4(v4) = peer {
35            buf.extend_from_slice(&v4.ip().octets());
36            buf.extend_from_slice(&v4.port().to_be_bytes());
37        }
38    }
39    buf
40}
41
42/// Parse compact IPv6 peer format (BEP 7): 18 bytes per peer (16 IP + 2 port).
43///
44/// # Errors
45///
46/// Returns an error if the data length is not a multiple of 18.
47pub fn parse_compact_peers6(data: &[u8]) -> Result<Vec<SocketAddr>> {
48    if !data.len().is_multiple_of(18) {
49        return Err(Error::InvalidResponse(format!(
50            "compact peers6 length {} is not a multiple of 18",
51            data.len()
52        )));
53    }
54
55    let mut peers = Vec::with_capacity(data.len() / 18);
56    for chunk in data.chunks_exact(18) {
57        let ip = Ipv6Addr::from(<[u8; 16]>::try_from(&chunk[..16]).unwrap());
58        let port = u16::from_be_bytes([chunk[16], chunk[17]]);
59        peers.push(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)));
60    }
61    Ok(peers)
62}
63
64/// Encode IPv6 peers to compact format (BEP 7): 18 bytes per peer (16 IP + 2 port).
65///
66/// IPv4 addresses are silently skipped.
67#[must_use]
68pub fn encode_compact_peers6(peers: &[SocketAddr]) -> Vec<u8> {
69    let mut buf = Vec::with_capacity(peers.len() * 18);
70    for peer in peers {
71        if let SocketAddr::V6(v6) = peer {
72            buf.extend_from_slice(&v6.ip().octets());
73            buf.extend_from_slice(&v6.port().to_be_bytes());
74        }
75    }
76    buf
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn parse_single_peer() {
85        // 192.168.1.1:6881
86        let data = [192, 168, 1, 1, 0x1A, 0xE1];
87        let peers = parse_compact_peers(&data).unwrap();
88        assert_eq!(peers.len(), 1);
89        assert_eq!(peers[0].to_string(), "192.168.1.1:6881");
90    }
91
92    #[test]
93    fn parse_multiple_peers() {
94        let mut data = Vec::new();
95        // 10.0.0.1:8080
96        data.extend_from_slice(&[10, 0, 0, 1, 0x1F, 0x90]);
97        // 172.16.0.1:6881
98        data.extend_from_slice(&[172, 16, 0, 1, 0x1A, 0xE1]);
99        let peers = parse_compact_peers(&data).unwrap();
100        assert_eq!(peers.len(), 2);
101        assert_eq!(peers[0].to_string(), "10.0.0.1:8080");
102        assert_eq!(peers[1].to_string(), "172.16.0.1:6881");
103    }
104
105    #[test]
106    fn parse_empty() {
107        let peers = parse_compact_peers(&[]).unwrap();
108        assert!(peers.is_empty());
109    }
110
111    #[test]
112    fn reject_invalid_length() {
113        assert!(parse_compact_peers(&[1, 2, 3, 4, 5]).is_err());
114    }
115
116    // --- encode_compact_peers (IPv4) ---
117
118    #[test]
119    fn encode_compact_peers_round_trip() {
120        let peers: Vec<SocketAddr> = vec![
121            "192.168.1.1:6881".parse().unwrap(),
122            "10.0.0.1:8080".parse().unwrap(),
123        ];
124        let encoded = encode_compact_peers(&peers);
125        assert_eq!(encoded.len(), 12);
126        let decoded = parse_compact_peers(&encoded).unwrap();
127        assert_eq!(peers, decoded);
128    }
129
130    #[test]
131    fn encode_compact_peers_skips_ipv6() {
132        let peers: Vec<SocketAddr> = vec![
133            "192.168.1.1:6881".parse().unwrap(),
134            "[::1]:6881".parse().unwrap(),
135        ];
136        let encoded = encode_compact_peers(&peers);
137        assert_eq!(encoded.len(), 6); // only the IPv4 peer
138    }
139
140    // --- parse_compact_peers6 ---
141
142    #[test]
143    fn parse_single_peer6() {
144        // [::1]:6881
145        let mut data = [0u8; 18];
146        data[15] = 1; // ::1
147        data[16] = 0x1A;
148        data[17] = 0xE1; // port 6881
149        let peers = parse_compact_peers6(&data).unwrap();
150        assert_eq!(peers.len(), 1);
151        assert_eq!(peers[0], "[::1]:6881".parse::<SocketAddr>().unwrap());
152    }
153
154    #[test]
155    fn parse_multiple_peers6() {
156        let mut data = Vec::new();
157        // [2001:db8::1]:8080
158        let ip1: Ipv6Addr = "2001:db8::1".parse().unwrap();
159        data.extend_from_slice(&ip1.octets());
160        data.extend_from_slice(&8080u16.to_be_bytes());
161        // [fe80::42]:6881
162        let ip2: Ipv6Addr = "fe80::42".parse().unwrap();
163        data.extend_from_slice(&ip2.octets());
164        data.extend_from_slice(&6881u16.to_be_bytes());
165
166        let peers = parse_compact_peers6(&data).unwrap();
167        assert_eq!(peers.len(), 2);
168        assert_eq!(
169            peers[0],
170            "[2001:db8::1]:8080".parse::<SocketAddr>().unwrap()
171        );
172        assert_eq!(peers[1], "[fe80::42]:6881".parse::<SocketAddr>().unwrap());
173    }
174
175    #[test]
176    fn parse_empty_peers6() {
177        let peers = parse_compact_peers6(&[]).unwrap();
178        assert!(peers.is_empty());
179    }
180
181    #[test]
182    fn reject_invalid_length_peers6() {
183        assert!(parse_compact_peers6(&[0u8; 17]).is_err());
184        assert!(parse_compact_peers6(&[0u8; 19]).is_err());
185    }
186
187    // --- encode_compact_peers6 ---
188
189    #[test]
190    fn encode_compact_peers6_round_trip() {
191        let peers: Vec<SocketAddr> = vec![
192            "[2001:db8::1]:8080".parse().unwrap(),
193            "[::1]:6881".parse().unwrap(),
194        ];
195        let encoded = encode_compact_peers6(&peers);
196        assert_eq!(encoded.len(), 36);
197        let decoded = parse_compact_peers6(&encoded).unwrap();
198        assert_eq!(peers, decoded);
199    }
200
201    #[test]
202    fn encode_compact_peers6_skips_ipv4() {
203        let peers: Vec<SocketAddr> = vec![
204            "[::1]:6881".parse().unwrap(),
205            "192.168.1.1:6881".parse().unwrap(),
206        ];
207        let encoded = encode_compact_peers6(&peers);
208        assert_eq!(encoded.len(), 18); // only the IPv6 peer
209    }
210}