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 IpAddr::V4(ip) => (ip.to_bits() as u128) << 95,
104 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 (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 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 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 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 let network = s.parse::<IpNetwork>()?;
248 Ok(PeerFilter::IpNetwork(network))
249 } else if let Ok(addr) = s.parse::<PeerAddr>() {
250 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 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 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 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 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 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 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 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 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 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 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}