apt_swarm/p2p/
proto.rs

1use crate::errors::*;
2use ipnetwork::IpNetwork;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::net::{IpAddr, SocketAddr};
6use std::str::FromStr;
7
8#[derive(Debug, PartialEq)]
9pub struct SyncRequest {
10    pub hint: Option<SyncHint>,
11    pub addrs: Vec<PeerAddr>,
12}
13
14#[derive(Debug, PartialEq)]
15pub struct SyncHint {
16    pub fp: sequoia_openpgp::Fingerprint,
17    pub idx: String,
18}
19
20impl From<PeerGossip> for SyncRequest {
21    fn from(gossip: PeerGossip) -> Self {
22        Self {
23            hint: Some(SyncHint {
24                fp: gossip.fp,
25                idx: gossip.idx,
26            }),
27            addrs: gossip.addrs,
28        }
29    }
30}
31
32#[derive(Debug, PartialEq)]
33pub struct PeerGossip {
34    pub fp: sequoia_openpgp::Fingerprint,
35    pub idx: String,
36    pub count: u64,
37    pub addrs: Vec<PeerAddr>,
38}
39
40impl FromStr for PeerGossip {
41    type Err = Error;
42
43    fn from_str(s: &str) -> Result<Self> {
44        let s = s
45            .strip_prefix("[sync] ")
46            .context("Message is missing the [sync] tag")?;
47
48        let mut split = s.split(' ');
49        let fp = split
50            .next()
51            .context("Missing mandatory attribute: fingerprint")?;
52        let fp = fp
53            .strip_prefix("fp=")
54            .with_context(|| anyhow!("First attribute is expected to be fingerprint: {fp:?}"))?;
55        let fp = fp
56            .parse()
57            .with_context(|| anyhow!("Failed to parse as fingerprint: {fp:?}"))?;
58
59        let idx = split.next().context("Missing mandatory attribute: index")?;
60        let idx = idx
61            .strip_prefix("idx=")
62            .with_context(|| anyhow!("First attribute is expected to be index: {idx:?}"))?
63            .to_string();
64
65        let count = split.next().context("Missing mandatory attribute: count")?;
66        let count = count
67            .strip_prefix("count=")
68            .with_context(|| anyhow!("First attribute is expected to be count: {count:?}"))?;
69        let count = count
70            .parse()
71            .with_context(|| anyhow!("Failed to parse as count: {count:?}"))?;
72
73        let mut addrs = Vec::new();
74
75        for extra in split {
76            if let Some(addr) = extra.strip_prefix("addr=") {
77                let addr = addr
78                    .parse()
79                    .with_context(|| anyhow!("Failed to parse as address: {addr:?}"))?;
80                addrs.push(addr);
81            }
82        }
83
84        Ok(PeerGossip {
85            fp,
86            idx,
87            count,
88            addrs,
89        })
90    }
91}
92
93#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
94pub enum PeerAddr {
95    Inet(SocketAddr),
96    Onion((String, u16)),
97}
98
99impl PeerAddr {
100    fn inet_to_u128(addr: IpAddr) -> u128 {
101        match addr {
102            // IPv4 has most significant bit set to 0
103            IpAddr::V4(ip) => (ip.to_bits() as u128) << 95,
104            // IPv6 has most significant bit set to 1
105            IpAddr::V6(ip) => (ip.to_bits() >> 1) | (1 << 127),
106        }
107    }
108
109    pub fn xor_distance(&self, other: &PeerAddr) -> u128 {
110        match (self, other) {
111            (PeerAddr::Inet(value), PeerAddr::Inet(other)) => {
112                let value = Self::inet_to_u128(value.ip());
113                let other = Self::inet_to_u128(other.ip());
114                value ^ other
115            }
116            // key distance doesn't make sense here
117            (PeerAddr::Onion(_), PeerAddr::Onion(_)) => 1,
118            _ => u128::MAX,
119        }
120    }
121}
122
123impl fmt::Debug for PeerAddr {
124    fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            PeerAddr::Inet(addr) => fmt::Debug::fmt(addr, w),
127            PeerAddr::Onion((host, port)) => {
128                write!(w, "\"{}:{}\"", host.escape_debug(), port)?;
129                Ok(())
130            }
131        }
132    }
133}
134
135impl fmt::Display for PeerAddr {
136    fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            PeerAddr::Inet(addr) => fmt::Display::fmt(addr, w),
139            PeerAddr::Onion((host, port)) => {
140                write!(w, "{host}:{port}")?;
141                Ok(())
142            }
143        }
144    }
145}
146
147impl FromStr for PeerAddr {
148    type Err = Error;
149
150    fn from_str(addr: &str) -> Result<Self> {
151        if addr.starts_with('[') {
152            // IPv6 address
153            let addr = addr.parse()?;
154            Ok(PeerAddr::Inet(addr))
155        } else {
156            let Some((host, port)) = addr.rsplit_once(':') else {
157                bail!("Missing port in peer address: {addr:?}");
158            };
159            let port = port
160                .parse()
161                .with_context(|| anyhow!("Failed to parse port: {addr:?}"))?;
162
163            if host.ends_with(".onion") {
164                // .onion address
165                if !host.chars().all(|c| c.is_alphanumeric() || c == '.') {
166                    bail!("Onion address contains invalid characters");
167                }
168                Ok(PeerAddr::Onion((host.to_string(), port)))
169            } else {
170                // IPv4 address
171                let host = host
172                    .parse()
173                    .with_context(|| anyhow!("Failed to parse ip address: {addr:?}"))?;
174                Ok(PeerAddr::Inet(SocketAddr::new(host, port)))
175            }
176        }
177    }
178}
179
180impl Serialize for PeerAddr {
181    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
182    where
183        S: serde::Serializer,
184    {
185        match self {
186            PeerAddr::Inet(addr) => {
187                let addr = addr.to_string();
188                addr.serialize(serializer)
189            }
190            PeerAddr::Onion((host, port)) => format!("{host}:{port}").serialize(serializer),
191        }
192    }
193}
194
195impl<'de> Deserialize<'de> for PeerAddr {
196    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197    where
198        D: serde::Deserializer<'de>,
199    {
200        let value: String = Deserialize::deserialize(deserializer)?;
201        value
202            .parse()
203            .map_err(|err| serde::de::Error::custom(format!("{err:#}")))
204    }
205}
206
207#[derive(Debug, Clone, PartialEq)]
208pub enum PeerFilter {
209    IpNetwork(IpNetwork),
210    ExactIp { addr: IpAddr, port: Option<u16> },
211    ExactOnion { onion: String, port: Option<u16> },
212}
213
214impl PeerFilter {
215    pub fn matches(&self, addr: &PeerAddr) -> bool {
216        match (self, addr) {
217            (PeerFilter::IpNetwork(net), PeerAddr::Inet(peer)) => net.contains(peer.ip()),
218            (PeerFilter::IpNetwork(_), PeerAddr::Onion(_)) => false,
219            (PeerFilter::ExactIp { addr, port }, PeerAddr::Inet(peer)) => {
220                if let Some(port) = port {
221                    if *port != peer.port() {
222                        return false;
223                    }
224                }
225                peer.ip() == *addr
226            }
227            (PeerFilter::ExactIp { .. }, PeerAddr::Onion(_)) => false,
228            (PeerFilter::ExactOnion { .. }, PeerAddr::Inet(_)) => false,
229            (PeerFilter::ExactOnion { onion, port }, PeerAddr::Onion((peer_host, peer_port))) => {
230                if let Some(port) = port {
231                    if *port != *peer_port {
232                        return false;
233                    }
234                }
235                peer_host == onion
236            }
237        }
238    }
239}
240
241impl FromStr for PeerFilter {
242    type Err = Error;
243
244    fn from_str(s: &str) -> Result<Self> {
245        if s.contains("/") {
246            // this is an ip network without port
247            let network = s.parse::<IpNetwork>()?;
248            Ok(PeerFilter::IpNetwork(network))
249        } else if let Ok(addr) = s.parse::<PeerAddr>() {
250            // this is a full PeerAddr (with port)
251            match addr {
252                PeerAddr::Inet(addr) => Ok(PeerFilter::ExactIp {
253                    addr: addr.ip(),
254                    port: Some(addr.port()),
255                }),
256                PeerAddr::Onion((host, port)) => Ok(PeerFilter::ExactOnion {
257                    onion: host,
258                    port: Some(port),
259                }),
260            }
261        } else {
262            // this is a host without port
263            if s.ends_with(".onion") {
264                Ok(PeerFilter::ExactOnion {
265                    onion: s.to_string(),
266                    port: None,
267                })
268            } else {
269                let addr = s.parse::<IpAddr>()?;
270                Ok(PeerFilter::ExactIp { addr, port: None })
271            }
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_parse_irc_no_addrs() -> Result<()> {
282        let s = "[sync] fp=ED541312A33F1128F10B1C6C54404762BBB6E853 idx=sha256:1994bea786a499ec72ce94a45e2830ce31746a5ef4fb7a2b73ba0934e4a046ac count=180";
283        let gi = s.parse::<PeerGossip>()?;
284        assert_eq!(
285            gi,
286            PeerGossip {
287                fp: "ED541312A33F1128F10B1C6C54404762BBB6E853".parse()?,
288                idx: "sha256:1994bea786a499ec72ce94a45e2830ce31746a5ef4fb7a2b73ba0934e4a046ac"
289                    .to_string(),
290                count: 180,
291                addrs: Vec::new(),
292            }
293        );
294        Ok(())
295    }
296
297    #[test]
298    fn test_parse_irc_multiple_addrs() -> Result<()> {
299        let s = "[sync] fp=2265EB4CB2BF88D900AE8D1B74A941BA219EC810 idx=sha256:55a00753512036f55ccc421217e008e4922c66592e6281b09de2fcba4dbd59ce count=12 addr=192.0.2.146:16169 addr=[2001:db8:c010:8f3a::1]:16169";
300        let gi = s.parse::<PeerGossip>()?;
301        assert_eq!(
302            gi,
303            PeerGossip {
304                fp: "2265EB4CB2BF88D900AE8D1B74A941BA219EC810".parse()?,
305                idx: "sha256:55a00753512036f55ccc421217e008e4922c66592e6281b09de2fcba4dbd59ce"
306                    .to_string(),
307                count: 12,
308                addrs: vec![
309                    "192.0.2.146:16169".parse()?,
310                    "[2001:db8:c010:8f3a::1]:16169".parse()?,
311                ],
312            }
313        );
314        Ok(())
315    }
316
317    #[test]
318    fn test_parse_irc_with_onion() -> Result<()> {
319        let s = "[sync] fp=2265EB4CB2BF88D900AE8D1B74A941BA219EC810 idx=sha256:55a00753512036f55ccc421217e008e4922c66592e6281b09de2fcba4dbd59ce count=12 addr=192.0.2.146:16169 addr=3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169";
320        let gi = s.parse::<PeerGossip>()?;
321        assert_eq!(
322            gi,
323            PeerGossip {
324                fp: "2265EB4CB2BF88D900AE8D1B74A941BA219EC810".parse()?,
325                idx: "sha256:55a00753512036f55ccc421217e008e4922c66592e6281b09de2fcba4dbd59ce"
326                    .to_string(),
327                count: 12,
328                addrs: vec![
329                    PeerAddr::Inet("192.0.2.146:16169".parse()?),
330                    PeerAddr::Onion((
331                        "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion"
332                            .to_string(),
333                        16169
334                    )),
335                ],
336            }
337        );
338        Ok(())
339    }
340
341    #[test]
342    fn test_ipv4_xor_distance() {
343        let base = "192.168.1.2:16169".parse::<PeerAddr>().unwrap();
344        assert_eq!(
345            base.xor_distance(&"192.168.1.2:16169".parse::<PeerAddr>().unwrap()),
346            0
347        );
348        assert_eq!(
349            base.xor_distance(&"192.168.1.2:443".parse::<PeerAddr>().unwrap()),
350            0
351        );
352        assert_eq!(
353            base.xor_distance(&"192.168.1.1:16169".parse::<PeerAddr>().unwrap()),
354            3 << 95
355        );
356        assert_eq!(
357            base.xor_distance(&"192.168.1.3:16169".parse::<PeerAddr>().unwrap()),
358            1 << 95
359        );
360        assert_eq!(
361            base.xor_distance(&"192.168.2.0:16169".parse::<PeerAddr>().unwrap()),
362            770 << 95
363        );
364        assert_eq!(
365            base.xor_distance(&"1.0.0.1:16169".parse::<PeerAddr>().unwrap()),
366            3_249_012_995 << 95
367        );
368        assert_eq!(
369            base.xor_distance(&"255.255.255.255:16169".parse::<PeerAddr>().unwrap()),
370            1_062_731_517 << 95
371        );
372        assert_eq!(
373            base.xor_distance(
374                &"[2001:db8:3333:4444:5555:6666:7777:8888]:16169"
375                    .parse::<PeerAddr>()
376                    .unwrap()
377            ),
378            319_453_597_143_525_594_717_699_116_388_956_488_772,
379        );
380    }
381
382    #[test]
383    fn test_ipv6_xor_distance() {
384        let base = "[2001:db8:3333:4444:5555:6666:7777:8888]:16169"
385            .parse::<PeerAddr>()
386            .unwrap();
387        assert_eq!(
388            base.xor_distance(
389                &"[2001:db8:3333:4444:5555:6666:7777:8888]:16169"
390                    .parse::<PeerAddr>()
391                    .unwrap()
392            ),
393            0
394        );
395        assert_eq!(
396            base.xor_distance(
397                &"[2001:db8:3333:4444:5555:6666:7777:8888]:443"
398                    .parse::<PeerAddr>()
399                    .unwrap()
400            ),
401            0
402        );
403        assert_eq!(
404            base.xor_distance(&"[2001:db8::]:16169".parse::<PeerAddr>().unwrap()),
405            7_922_856_549_568_655_098_759_595_076
406        );
407        assert_eq!(
408            base.xor_distance(&"[fe80::1a2b:3c4d:5e6f]:16169".parse::<PeerAddr>().unwrap()),
409            147_879_349_812_077_389_872_108_282_106_859_055_987
410        );
411        assert_eq!(
412            base.xor_distance(&"192.168.1.3:16169".parse::<PeerAddr>().unwrap()),
413            319_453_597_183_139_675_974_831_285_185_728_463_940
414        );
415    }
416
417    #[test]
418    fn test_peer_addr_serialize() {
419        let addr =
420            serde_json::to_string(&PeerAddr::Inet("[2001:db8::]:16169".parse().unwrap())).unwrap();
421        assert_eq!(addr, "\"[2001:db8::]:16169\"");
422
423        let addr = serde_json::to_string(&PeerAddr::Onion((
424            "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion".to_string(),
425            16169,
426        )))
427        .unwrap();
428        assert_eq!(
429            addr,
430            "\"3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169\""
431        );
432    }
433
434    #[test]
435    fn test_peer_addr_deserialize() {
436        let addr = serde_json::from_str::<PeerAddr>("\"[2001:db8::]:16169\"").unwrap();
437        assert_eq!(addr, PeerAddr::Inet("[2001:db8::]:16169".parse().unwrap()));
438
439        let addr = serde_json::from_str::<PeerAddr>(
440            "\"3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169\"",
441        )
442        .unwrap();
443        assert_eq!(
444            addr,
445            PeerAddr::Onion((
446                "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion".to_string(),
447                16169
448            ))
449        );
450    }
451
452    #[test]
453    fn test_peer_addr_debug_inet() {
454        let addr = PeerAddr::Inet("[2001:db8::]:16169".parse().unwrap());
455        assert_eq!(format!("{addr:?}"), "[2001:db8::]:16169");
456    }
457
458    #[test]
459    fn test_peer_addr_debug_onion() {
460        let addr = PeerAddr::Onion((
461            "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion".to_string(),
462            16169,
463        ));
464        assert_eq!(
465            format!("{addr:?}"),
466            "\"3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169\""
467        );
468    }
469
470    #[test]
471    fn test_detect_invalid_onion_address() {
472        let addr = "3wisi2b\nfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169";
473        assert!(addr.parse::<PeerAddr>().is_err());
474    }
475
476    #[test]
477    fn parse_peerfilter() {
478        fn test_matches(filter: PeerFilter) -> Vec<(&'static str, bool)> {
479            [
480                "192.168.1.2:16169",
481                "1.1.1.1:16169",
482                "1.1.1.1:1337",
483                "[2001:db8::]:16169",
484                "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
485                "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
486                "[fe80::1]:16169",
487                "[fe80::1]:1337",
488            ]
489            .into_iter()
490            .map(|addr| (addr, addr.parse::<PeerAddr>().unwrap()))
491            .map(|(addr, peer)| {
492                let matches = filter.matches(&peer);
493                (addr, matches)
494            })
495            .collect()
496        }
497
498        // test
499        let filter = "1.1.1.1".parse::<PeerFilter>().unwrap();
500        assert_eq!(
501            filter,
502            PeerFilter::ExactIp {
503                addr: "1.1.1.1".parse().unwrap(),
504                port: None,
505            }
506        );
507        assert_eq!(
508            test_matches(filter),
509            &[
510                ("192.168.1.2:16169", false),
511                ("1.1.1.1:16169", true),
512                ("1.1.1.1:1337", true),
513                ("[2001:db8::]:16169", false),
514                (
515                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
516                    false
517                ),
518                (
519                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
520                    false
521                ),
522                ("[fe80::1]:16169", false),
523                ("[fe80::1]:1337", false)
524            ]
525        );
526
527        // test
528        let filter = "::/0".parse::<PeerFilter>().unwrap();
529        assert_eq!(filter, PeerFilter::IpNetwork("::/0".parse().unwrap()),);
530        assert_eq!(
531            test_matches(filter),
532            &[
533                ("192.168.1.2:16169", false),
534                ("1.1.1.1:16169", false),
535                ("1.1.1.1:1337", false),
536                ("[2001:db8::]:16169", true),
537                (
538                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
539                    false
540                ),
541                (
542                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
543                    false
544                ),
545                ("[fe80::1]:16169", true),
546                ("[fe80::1]:1337", true)
547            ]
548        );
549
550        // test
551        let filter = "0.0.0.0/0".parse::<PeerFilter>().unwrap();
552        assert_eq!(filter, PeerFilter::IpNetwork("0.0.0.0/0".parse().unwrap()),);
553        assert_eq!(
554            test_matches(filter),
555            &[
556                ("192.168.1.2:16169", true),
557                ("1.1.1.1:16169", true),
558                ("1.1.1.1:1337", true),
559                ("[2001:db8::]:16169", false),
560                (
561                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
562                    false
563                ),
564                (
565                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
566                    false
567                ),
568                ("[fe80::1]:16169", false),
569                ("[fe80::1]:1337", false)
570            ]
571        );
572
573        // test
574        let filter = "1.1.1.1/8".parse::<PeerFilter>().unwrap();
575        assert_eq!(filter, PeerFilter::IpNetwork("1.1.1.1/8".parse().unwrap()),);
576        assert_eq!(
577            test_matches(filter),
578            &[
579                ("192.168.1.2:16169", false),
580                ("1.1.1.1:16169", true),
581                ("1.1.1.1:1337", true),
582                ("[2001:db8::]:16169", false),
583                (
584                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
585                    false
586                ),
587                (
588                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
589                    false
590                ),
591                ("[fe80::1]:16169", false),
592                ("[fe80::1]:1337", false)
593            ]
594        );
595
596        // test
597        let filter = "fe80::1".parse::<PeerFilter>().unwrap();
598        assert_eq!(
599            filter,
600            PeerFilter::ExactIp {
601                addr: "fe80::1".parse().unwrap(),
602                port: None,
603            }
604        );
605        assert_eq!(
606            test_matches(filter),
607            &[
608                ("192.168.1.2:16169", false),
609                ("1.1.1.1:16169", false),
610                ("1.1.1.1:1337", false),
611                ("[2001:db8::]:16169", false),
612                (
613                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
614                    false
615                ),
616                (
617                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
618                    false
619                ),
620                ("[fe80::1]:16169", true),
621                ("[fe80::1]:1337", true)
622            ]
623        );
624
625        // test
626        let filter = "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion"
627            .parse::<PeerFilter>()
628            .unwrap();
629        assert_eq!(
630            filter,
631            PeerFilter::ExactOnion {
632                onion: "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion".to_string(),
633                port: None,
634            }
635        );
636        assert_eq!(
637            test_matches(filter),
638            &[
639                ("192.168.1.2:16169", false),
640                ("1.1.1.1:16169", false),
641                ("1.1.1.1:1337", false),
642                ("[2001:db8::]:16169", false),
643                (
644                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
645                    true
646                ),
647                (
648                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
649                    true
650                ),
651                ("[fe80::1]:16169", false),
652                ("[fe80::1]:1337", false)
653            ]
654        );
655
656        // test
657        let filter = "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169"
658            .parse::<PeerFilter>()
659            .unwrap();
660        assert_eq!(
661            filter,
662            PeerFilter::ExactOnion {
663                onion: "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion".to_string(),
664                port: Some(16169),
665            }
666        );
667        assert_eq!(
668            test_matches(filter),
669            &[
670                ("192.168.1.2:16169", false),
671                ("1.1.1.1:16169", false),
672                ("1.1.1.1:1337", false),
673                ("[2001:db8::]:16169", false),
674                (
675                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
676                    true
677                ),
678                (
679                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
680                    false
681                ),
682                ("[fe80::1]:16169", false),
683                ("[fe80::1]:1337", false)
684            ]
685        );
686
687        // test
688        let filter = "1.1.1.1:16169".parse::<PeerFilter>().unwrap();
689        assert_eq!(
690            filter,
691            PeerFilter::ExactIp {
692                addr: "1.1.1.1".parse().unwrap(),
693                port: Some(16169),
694            }
695        );
696        assert_eq!(
697            test_matches(filter),
698            &[
699                ("192.168.1.2:16169", false),
700                ("1.1.1.1:16169", true),
701                ("1.1.1.1:1337", false),
702                ("[2001:db8::]:16169", false),
703                (
704                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
705                    false
706                ),
707                (
708                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
709                    false
710                ),
711                ("[fe80::1]:16169", false),
712                ("[fe80::1]:1337", false)
713            ]
714        );
715
716        // test
717        let filter = "[fe80::1]:16169".parse::<PeerFilter>().unwrap();
718        assert_eq!(
719            filter,
720            PeerFilter::ExactIp {
721                addr: "fe80::1".parse().unwrap(),
722                port: Some(16169),
723            }
724        );
725        assert_eq!(
726            test_matches(filter),
727            &[
728                ("192.168.1.2:16169", false),
729                ("1.1.1.1:16169", false),
730                ("1.1.1.1:1337", false),
731                ("[2001:db8::]:16169", false),
732                (
733                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:16169",
734                    false
735                ),
736                (
737                    "3wisi2bfpxplne5wlwz4l5ucvsbaozbteaqnm62oxzmgwhb2qqxvsuyd.onion:1337",
738                    false
739                ),
740                ("[fe80::1]:16169", true),
741                ("[fe80::1]:1337", false)
742            ]
743        );
744    }
745}