1use core::borrow::Borrow;
6use core::convert::TryFrom;
7use core::str::FromStr;
8use core::fmt;
9#[cfg(feature = "std")]
10use std::io;
11#[cfg(feature = "std")]
12use std::vec::Vec;
13use crate::NodeId;
14
15#[cfg(rust_v_1_77)]
16use core::net;
17#[cfg(not(rust_v_1_77))]
18use std::net;
19
20#[cfg(feature = "alloc")]
21use alloc::{boxed::Box, string::String, borrow::ToOwned, string::ToString};
22
23const LN_DEFAULT_PORT: u16 = 9735;
24
25#[cfg(feature = "alloc")]
29trait StringOps: AsRef<str> + Into<String> {
30 fn into_substring(self, start: usize, end: usize) -> String;
32}
33
34#[cfg(not(feature = "alloc"))]
35trait StringOps: AsRef<str> { }
36
37#[cfg(feature = "alloc")]
39impl StringOps for String {
40 fn into_substring(mut self, start: usize, end: usize) -> String {
41 self.replace_range(0..start, "");
42 self.truncate(end - start);
43 self
44 }
45}
46
47impl<'a> StringOps for &'a str {
48 #[cfg(feature = "alloc")]
49 fn into_substring(self, start: usize, end: usize) -> String {
50 self[start..end].to_owned()
51 }
52}
53
54#[cfg(feature = "alloc")]
56impl StringOps for Box<str> {
57 fn into_substring(self, start: usize, end: usize) -> String {
58 String::from(self).into_substring(start, end)
59 }
60}
61
62#[derive(Clone)]
66enum HostInner {
67 Ip(net::IpAddr),
68 #[cfg(feature = "alloc")]
69 Hostname(String),
70 }
72
73#[derive(Clone)]
79pub struct Host(HostInner);
80
81impl Host {
82 pub fn is_onion(&self) -> bool {
84 match &self.0 {
85 #[cfg(feature = "alloc")]
86 HostInner::Hostname(hostname) => hostname.ends_with(".onion"),
87 HostInner::Ip(_) => false,
88 }
89 }
90
91 pub fn is_ip_addr(&self) -> bool {
93 match &self.0 {
94 #[cfg(feature = "alloc")]
95 HostInner::Hostname(_) => false,
96 HostInner::Ip(_) => true,
97 }
98 }
99}
100
101impl fmt::Display for Host {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 match &self.0 {
104 HostInner::Ip(addr) => fmt::Display::fmt(&addr, f),
105 #[cfg(feature = "alloc")]
106 HostInner::Hostname(addr) => fmt::Display::fmt(&addr, f),
107 }
108 }
109}
110
111pub struct HostPort<H: Borrow<Host>>(
116 pub H,
120
121 pub u16,
123);
124
125impl<H: Borrow<Host>> fmt::Display for HostPort<H> {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 match &self.0.borrow().0 {
129 HostInner::Ip(net::IpAddr::V6(addr)) => write!(f, "[{}]:{}", addr, self.1),
130 _ => write!(f, "{}:{}", self.0.borrow(), self.1),
131 }
132 }
133}
134
135#[cfg(feature = "alloc")]
136impl From<Host> for String {
137 fn from(value: Host) -> Self {
138 match value.0 {
139 HostInner::Ip(ip_addr) => ip_addr.to_string(),
140 #[cfg(feature = "alloc")]
141 HostInner::Hostname(hostname) => hostname,
142 }
143 }
144}
145
146impl TryFrom<Host> for net::IpAddr {
148 type Error = NotIpAddr;
149
150 fn try_from(value: Host) -> Result<Self, Self::Error> {
151 match value.0 {
152 HostInner::Ip(ip_addr) => Ok(ip_addr),
153 #[cfg(feature = "alloc")]
154 HostInner::Hostname(hostname) => Err(NotIpAddr(hostname)),
155 }
156 }
157}
158
159#[cfg(feature = "alloc")]
163#[derive(Debug)]
164pub struct NotIpAddr(String);
165
166#[derive(Debug)]
170#[cfg(not(feature = "alloc"))]
171#[non_exhaustive]
172pub struct NotIpAddr;
173
174impl fmt::Display for NotIpAddr {
175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176 #[cfg(feature = "alloc")]
177 {
178 write!(f, "the hostname '{}' is not an IP address", self.0)
179 }
180 #[cfg(not(feature = "alloc"))]
181 {
182 write!(f, "the hostname is not an IP address")
183 }
184 }
185}
186
187#[cfg(feature = "std")]
188impl std::error::Error for NotIpAddr {}
189
190#[derive(Clone)]
215pub struct P2PAddress {
216 pub node_id: NodeId,
218 pub host: Host,
220 pub port: u16,
222}
223
224enum IpOrHostnamePos {
229 Ip(net::IpAddr),
230 #[cfg(feature = "alloc")]
231 Hostname(usize, usize),
232 #[cfg(not(feature = "alloc"))]
233 Hostname((), ()),
234}
235
236impl P2PAddress {
237 pub fn as_host_port(&self) -> HostPort<&Host> {
242 HostPort(&self.host, self.port)
243 }
244
245 fn parse_raw(s: &str) -> Result<(NodeId, IpOrHostnamePos, u16), ParseErrorInner> {
249 let at_pos = s.find('@').ok_or(ParseErrorInner::MissingAtSymbol)?;
250 let (node_id, host_port) = s.split_at(at_pos);
251 let host_port = &host_port[1..];
252 let node_id = node_id.parse().map_err(ParseErrorInner::InvalidNodeId)?;
253 let (host_end, port) = match (host_port.starts_with('[') && host_port.ends_with(']'), host_port.rfind(':')) {
254 (true, _) => (host_port.len(), LN_DEFAULT_PORT),
256 (false, Some(pos)) => (pos, host_port[(pos + 1)..].parse().map_err(ParseErrorInner::InvalidPortNumber)?),
257 (false, None) => (host_port.len(), LN_DEFAULT_PORT),
258 };
259 let host = &host_port[..host_end];
260 let host = match host.parse::<net::Ipv4Addr>() {
261 Ok(ip) => IpOrHostnamePos::Ip(ip.into()),
262 Err(_) if host.starts_with('[') && host.ends_with(']') => {
264 let ip = host_port[1..(host.len() - 1)]
265 .parse::<net::Ipv6Addr>()
266 .map_err(ParseErrorInner::InvalidIpv6)?;
267
268 IpOrHostnamePos::Ip(ip.into())
269 },
270 #[cfg(feature = "alloc")]
271 Err(_) => {
272 IpOrHostnamePos::Hostname(at_pos + 1, at_pos + 1 + host_end)
273 },
274 #[cfg(not(feature = "alloc"))]
275 Err(_) => {
276 IpOrHostnamePos::Hostname((), ())
277 },
278 };
279
280 Ok((node_id, host, port))
281 }
282
283 fn internal_parse<S: StringOps>(s: S) -> Result<Self, ParseError> {
285 let (node_id, host, port) = match Self::parse_raw(s.as_ref()) {
286 Ok(result) => result,
287 Err(error) => return Err(ParseError {
288 #[cfg(feature = "alloc")]
289 input: s.into(),
290 reason: error,
291 }),
292 };
293 let host = match host {
294 #[cfg(feature = "alloc")]
295 IpOrHostnamePos::Hostname(begin, end) => HostInner::Hostname(s.into_substring(begin, end)),
296 #[cfg(not(feature = "alloc"))]
297 IpOrHostnamePos::Hostname(_, _) => return Err(ParseError { reason: ParseErrorInner::UnsupportedHostname }),
298 IpOrHostnamePos::Ip(ip) => HostInner::Ip(ip),
299 };
300
301 Ok(P2PAddress {
302 node_id,
303 host: Host(host),
304 port,
305 })
306 }
307}
308
309impl fmt::Display for P2PAddress {
311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312 if f.alternate() {
313 write!(f, "{:X}@{}", self.node_id, HostPort(&self.host, self.port))
314 } else {
315 write!(f, "{:x}@{}", self.node_id, HostPort(&self.host, self.port))
316 }
317 }
318}
319
320impl fmt::Debug for P2PAddress {
322 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323 fmt::Display::fmt(self, f)
324 }
325}
326
327impl FromStr for P2PAddress {
328 type Err = ParseError;
329
330 #[inline]
331 fn from_str(s: &str) -> Result<Self, Self::Err> {
332 Self::internal_parse(s)
333 }
334}
335
336impl<'a> TryFrom<&'a str> for P2PAddress {
337 type Error = ParseError;
338
339 #[inline]
340 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
341 Self::internal_parse(s)
342 }
343}
344
345#[cfg(feature = "alloc")]
346impl TryFrom<String> for P2PAddress {
347 type Error = ParseError;
348
349 #[inline]
350 fn try_from(s: String) -> Result<Self, Self::Error> {
351 Self::internal_parse(s)
352 }
353}
354
355#[cfg(feature = "alloc")]
356impl TryFrom<Box<str>> for P2PAddress {
357 type Error = ParseError;
358
359 #[inline]
360 fn try_from(s: Box<str>) -> Result<Self, Self::Error> {
361 Self::internal_parse(s)
362 }
363}
364
365#[derive(Debug, Clone)]
369pub struct ParseError {
370 #[cfg(feature = "alloc")]
371 input: String,
372 reason: ParseErrorInner,
373}
374
375impl fmt::Display for ParseError {
376 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
377 #[cfg(feature = "alloc")]
378 {
379 write_err!(f, "failed to parse '{}' as Lightning Network P2P address", self.input; &self.reason)
380 }
381 #[cfg(not(feature = "alloc"))]
382 {
383 write_err!(f, "failed to parse Lightning Network P2P address"; &self.reason)
384 }
385 }
386}
387
388#[cfg(feature = "std")]
389impl std::error::Error for ParseError {
390 #[inline]
391 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
392 if let ParseErrorInner::InvalidNodeId(error) = &self.reason {
393 Some(error)
394 } else {
395 Some(&self.reason)
396 }
397 }
398}
399
400#[derive(Debug, Clone)]
401enum ParseErrorInner {
402 MissingAtSymbol,
403 InvalidNodeId(crate::node_id::ParseError),
404 InvalidPortNumber(core::num::ParseIntError),
405 InvalidIpv6(net::AddrParseError),
406 #[cfg(not(feature = "alloc"))]
407 UnsupportedHostname,
408}
409
410impl fmt::Display for ParseErrorInner {
411 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412 match self {
413 ParseErrorInner::MissingAtSymbol => f.write_str("missing '@' symbol"),
414 ParseErrorInner::InvalidNodeId(error) => fmt::Display::fmt(error, f),
415 ParseErrorInner::InvalidPortNumber(error) => write_err!(f, "invalid port number"; error),
416 ParseErrorInner::InvalidIpv6(error) => write_err!(f, "invalid IPv6 address"; error),
417 #[cfg(not(feature = "alloc"))]
418 ParseErrorInner::UnsupportedHostname => f.write_str("the address is a hostname which is unsupported in this build (without an allocator)"),
419 }
420 }
421}
422
423#[cfg(feature = "std")]
424impl std::error::Error for ParseErrorInner {
425 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
426 match self {
427 ParseErrorInner::MissingAtSymbol => None,
428 ParseErrorInner::InvalidNodeId(error) => error.source(),
429 ParseErrorInner::InvalidPortNumber(error) => Some(error),
430 ParseErrorInner::InvalidIpv6(error) => Some(error),
431 #[cfg(not(feature = "alloc"))]
432 ParseErrorInner::UnsupportedHostname => None,
433 }
434 }
435}
436
437#[cfg(feature = "std")]
442pub struct SocketAddrs {
443 iter: core::iter::Chain<core::option::IntoIter<net::SocketAddr>, std::vec::IntoIter<net::SocketAddr>>
444}
445
446#[cfg(feature = "std")]
447impl Iterator for SocketAddrs {
448 type Item = net::SocketAddr;
449
450 fn next(&mut self) -> Option<Self::Item> {
451 self.iter.next()
452 }
453}
454
455#[cfg(feature = "std")]
457impl<H: Borrow<Host>> std::net::ToSocketAddrs for HostPort<H> {
458 type Iter = SocketAddrs;
459
460 fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
461 if self.0.borrow().is_onion() {
462 return Err(io::Error::new(io::ErrorKind::InvalidInput, ResolveOnion));
463 }
464
465 let iter = match &self.0.borrow().0 {
466 HostInner::Ip(ip_addr) => Some(net::SocketAddr::new(*ip_addr, self.1)).into_iter().chain(Vec::new()),
467 HostInner::Hostname(hostname) => None.into_iter().chain((hostname.as_str(), self.1).to_socket_addrs()?),
468 };
469
470 Ok(SocketAddrs {
471 iter,
472 })
473 }
474}
475
476#[cfg(feature = "std")]
478impl std::net::ToSocketAddrs for P2PAddress {
479 type Iter = SocketAddrs;
480
481 fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
482 HostPort(&self.host, self.port).to_socket_addrs()
483 }
484}
485
486#[derive(Debug)]
489#[cfg(feature = "std")]
490struct ResolveOnion;
491
492#[cfg(feature = "std")]
493impl fmt::Display for ResolveOnion {
494 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
495 f.write_str("attempt to resolve onion address")
496 }
497}
498
499#[cfg(feature = "std")]
500impl std::error::Error for ResolveOnion {}
501
502#[cfg(feature = "parse_arg")]
503mod parse_arg_impl {
504 use core::fmt;
505 use super::P2PAddress;
506
507 impl parse_arg::ParseArgFromStr for P2PAddress {
508 fn describe_type<W: fmt::Write>(mut writer: W) -> fmt::Result {
509 writer.write_str("a Lightning Network address in the form `nodeid@host:port`")
510 }
511 }
512}
513
514#[cfg(feature = "serde")]
515mod serde_impl {
516 use core::fmt;
517 use super::P2PAddress;
518 use serde::{Serialize, Deserialize, Serializer, Deserializer, de::{Visitor, Error}};
519 use core::convert::TryInto;
520
521 #[cfg(feature = "serde_alloc")]
522 use alloc::string::String;
523
524 struct HRVisitor;
525
526 impl<'de> Visitor<'de> for HRVisitor {
527 type Value = P2PAddress;
528
529 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
530 formatter.write_str("a 66 digits long hex string")
531 }
532
533 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: Error {
534 v.try_into().map_err(|error| {
535 E::custom(error)
536 })
537 }
538
539 #[cfg(feature = "serde_alloc")]
540 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> where E: Error {
541 v.try_into().map_err(|error| {
542 E::custom(error)
543 })
544 }
545 }
546
547 impl Serialize for P2PAddress {
554 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
555 use serde::ser::Error;
556
557 if serializer.is_human_readable() {
558 serializer.collect_str(self)
559 } else {
560 Err(S::Error::custom("serialization is not yet implemented for non-human-readable formats, please file a request"))
561 }
562 }
563 }
564
565 impl<'de> Deserialize<'de> for P2PAddress {
572 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
573 if deserializer.is_human_readable() {
574 deserializer.deserialize_str(HRVisitor)
575 } else {
576 Err(D::Error::custom("deserialization is not yet implemented for non-human-readable formats, please file a request"))
577 }
578 }
579 }
580}
581
582#[cfg(feature = "postgres-types")]
583mod postgres_impl {
584 use alloc::boxed::Box;
585 use super::P2PAddress;
586 use postgres_types::{ToSql, FromSql, IsNull, Type};
587 use bytes::BytesMut;
588 use std::error::Error;
589
590 impl ToSql for P2PAddress {
592 fn to_sql(&self, _ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Send + Sync + 'static>> {
593 use core::fmt::Write;
594
595 write!(out, "{}", self).map(|_| IsNull::No).map_err(|error| Box::new(error) as _)
596 }
597
598 fn accepts(ty: &Type) -> bool {
599 <&str as ToSql>::accepts(ty)
600 }
601
602 postgres_types::to_sql_checked!();
603 }
604
605 impl<'a> FromSql<'a> for P2PAddress {
607 fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
608 <&str>::from_sql(ty, raw)?.parse().map_err(|error| Box::new(error) as _)
609 }
610
611 fn accepts(ty: &Type) -> bool {
612 <&str as FromSql>::accepts(ty)
613 }
614 }
615}
616
617#[cfg(feature = "slog")]
619mod slog_impl {
620 use super::P2PAddress;
621 use slog::{Key, Value, KV, Record, Serializer};
622
623 impl Value for P2PAddress {
625 fn serialize(&self, _rec: &Record, key: Key, serializer: &mut dyn Serializer) -> slog::Result {
626 serializer.emit_arguments(key, &format_args!("{}", self))
627 }
628 }
629
630 impl KV for P2PAddress {
638 fn serialize(&self, rec: &Record, serializer: &mut dyn Serializer) -> slog::Result {
639 #![allow(clippy::useless_conversion)]
641 self.node_id.serialize(rec, Key::from("node_id"), serializer)?;
642 serializer.emit_arguments(Key::from("host"), &format_args!("{}", self.host))?;
643 serializer.emit_u16(Key::from("port"), self.port)?;
644 Ok(())
645 }
646 }
647
648 impl_error_value!(super::ParseError);
649}
650
651#[cfg(test)]
652mod tests {
653 use super::P2PAddress;
654 use alloc::{format, string::ToString};
655
656 #[test]
657 fn empty() {
658 assert!("".parse::<P2PAddress>().is_err());
659 }
660
661 #[test]
662 fn invalid_node_id() {
663 assert!("@example.com".parse::<P2PAddress>().is_err());
664 }
665
666 #[test]
667 fn invalid_port() {
668 assert!("022345678901234567890123456789012345678901234567890123456789abcdef@example.com:foo".parse::<P2PAddress>().is_err());
669 }
670
671 #[test]
672 #[cfg(feature = "alloc")]
673 fn correct_hostname_no_port() {
674 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@example.com";
675 let parsed = input.parse::<P2PAddress>().unwrap();
676 let output = parsed.to_string();
677 let expected = format!("{}{}", input, ":9735");
678 assert_eq!(output, expected);
679 }
680
681 #[test]
682 #[cfg(feature = "alloc")]
683 fn correct_with_hostname_port() {
684 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@example.com:1234";
685 let parsed = input.parse::<P2PAddress>().unwrap();
686 let output = parsed.to_string();
687 assert_eq!(output, input);
688 }
689
690 #[test]
691 fn correct_ipv4_no_port() {
692 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@127.0.0.1";
693 let parsed = input.parse::<P2PAddress>().unwrap();
694 let output = parsed.to_string();
695 let expected = format!("{}{}", input, ":9735");
696 assert_eq!(output, expected);
697 }
698
699 #[test]
700 fn correct_with_ipv4_port() {
701 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@127.0.0.1:1234";
702 let parsed = input.parse::<P2PAddress>().unwrap();
703 let output = parsed.to_string();
704 assert_eq!(output, input);
705 }
706
707 #[test]
708 fn ipv6_no_port() {
709 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@[::1]";
710 let parsed = input.parse::<P2PAddress>().unwrap();
711 let output = parsed.to_string();
712 let expected = format!("{}{}", input, ":9735");
713 assert_eq!(output, expected);
714 }
715
716 #[test]
717 fn ipv6_with_port() {
718 let input = "022345678901234567890123456789012345678901234567890123456789abcdef@[::1]:1234";
719 let parsed = input.parse::<P2PAddress>().unwrap();
720 let output = parsed.to_string();
721 assert_eq!(output, input);
722 }
723
724 chk_err_impl! {
725 parse_p2p_address_error_empty, "", P2PAddress, ["failed to parse '' as Lightning Network P2P address", "missing '@' symbol"], ["failed to parse Lightning Network P2P address", "missing '@' symbol"];
726 parse_p2p_address_error_empty_node_id, "@127.0.0.1", P2PAddress, [
727 "failed to parse '@127.0.0.1' as Lightning Network P2P address",
728 "failed to parse '' as Lightning Network node ID",
729 "invalid length (must be 66 chars)",
730 ], [
731 "failed to parse Lightning Network P2P address",
732 "failed to parse Lightning Network node ID",
733 "invalid length (must be 66 chars)",
734 ];
735 }
736}