@@ -45,7 +45,7 @@ use core::fmt;
45
45
use core:: fmt:: Debug ;
46
46
use core:: ops:: Deref ;
47
47
#[ cfg( feature = "std" ) ]
48
- use core:: str:: FromStr ;
48
+ use core:: { fmt :: Display , str:: FromStr } ;
49
49
use crate :: io:: { self , Cursor , Read } ;
50
50
use crate :: io_extras:: read_to_end;
51
51
@@ -918,6 +918,8 @@ pub enum SocketAddressParseError {
918
918
InvalidInput ,
919
919
/// Invalid port
920
920
InvalidPort ,
921
+ /// Invalid onion v2 address
922
+ InvalidOnionV2 ,
921
923
/// Invalid onion v3 address
922
924
InvalidOnionV3 ,
923
925
}
@@ -929,6 +931,7 @@ impl fmt::Display for SocketAddressParseError {
929
931
SocketAddressParseError :: InvalidInput => write ! ( f, "Invalid input format. \
930
932
Expected: \" <ipv4>:<port>\" , \" [<ipv6>]:<port>\" , \" <onion address>.onion:<port>\" or \" <hostname>:<port>\" ") ,
931
933
SocketAddressParseError :: InvalidPort => write ! ( f, "Invalid port" ) ,
934
+ SocketAddressParseError :: InvalidOnionV2 => write ! ( f, "Invalid onion v2 address" ) ,
932
935
SocketAddressParseError :: InvalidOnionV3 => write ! ( f, "Invalid onion v3 address" ) ,
933
936
}
934
937
}
@@ -958,16 +961,37 @@ impl From<std::net::SocketAddr> for SocketAddress {
958
961
}
959
962
}
960
963
964
+ /// Parses an OnionV2 host and port into a [`SocketAddress::OnionV2`].
965
+ ///
966
+ /// The host part must end with ".onion".
967
+ pub fn parse_onion_v2_address ( host : & str ) -> Result < SocketAddress , SocketAddressParseError > {
968
+ if host. ends_with ( ".onion" ) {
969
+ let domain = & host[ ..host. len ( ) - ".onion" . len ( ) ] ;
970
+ if domain. len ( ) != 16 {
971
+ return Err ( SocketAddressParseError :: InvalidOnionV2 ) ;
972
+ }
973
+ let onion = base32:: Alphabet :: RFC4648 { padding : false } . decode ( & domain) . map_err ( |_| SocketAddressParseError :: InvalidOnionV2 ) ?;
974
+ if onion. len ( ) != 12 {
975
+ return Err ( SocketAddressParseError :: InvalidOnionV2 ) ;
976
+ }
977
+ let mut bytes = [ 0 ; 12 ] ;
978
+ bytes. copy_from_slice ( & onion) ;
979
+ return Ok ( SocketAddress :: OnionV2 ( bytes) ) ;
980
+ } else {
981
+ return Err ( SocketAddressParseError :: InvalidInput ) ;
982
+ }
983
+ }
984
+
961
985
/// Parses an OnionV3 host and port into a [`SocketAddress::OnionV3`].
962
986
///
963
987
/// The host part must end with ".onion".
964
- pub fn parse_onion_address ( host : & str , port : u16 ) -> Result < SocketAddress , SocketAddressParseError > {
988
+ pub fn parse_onion_v3_address ( host : & str , port : u16 ) -> Result < SocketAddress , SocketAddressParseError > {
965
989
if host. ends_with ( ".onion" ) {
966
990
let domain = & host[ ..host. len ( ) - ".onion" . len ( ) ] ;
967
991
if domain. len ( ) != 56 {
968
992
return Err ( SocketAddressParseError :: InvalidOnionV3 ) ;
969
993
}
970
- let onion = base32:: Alphabet :: RFC4648 { padding : false } . decode ( & domain) . map_err ( |_| SocketAddressParseError :: InvalidOnionV3 ) ?;
994
+ let onion = base32:: Alphabet :: RFC4648 { padding : false } . decode ( & domain) . map_err ( |_| SocketAddressParseError :: InvalidOnionV3 ) ?;
971
995
if onion. len ( ) != 35 {
972
996
return Err ( SocketAddressParseError :: InvalidOnionV3 ) ;
973
997
}
@@ -984,6 +1008,40 @@ pub fn parse_onion_address(host: &str, port: u16) -> Result<SocketAddress, Socke
984
1008
}
985
1009
}
986
1010
1011
+ /// [`SocketAddress::OnionV2`] to onion address string
1012
+ pub fn to_onion_v2_string ( bytes : & [ u8 ; 12 ] ) -> String {
1013
+ let onion = base32:: Alphabet :: RFC4648 { padding : false } . encode ( & bytes[ ..] ) ;
1014
+ format ! ( "{}.onion" , onion)
1015
+ }
1016
+
1017
+ /// [`SocketAddress::OnionV3`] to onion address string
1018
+ pub fn to_onion_v3_string ( key : & [ u8 ; 32 ] , checksum : & u16 , version : & u8 , port : & u16 ) -> String {
1019
+ let [ first_checksum_flag, second_checksum_flag] = checksum. to_be_bytes ( ) ;
1020
+ let mut addr = vec ! [ * version, first_checksum_flag, second_checksum_flag] ;
1021
+ addr. extend_from_slice ( key) ;
1022
+ let onion = base32:: Alphabet :: RFC4648 { padding : false } . encode ( & addr) ;
1023
+ format ! ( "{}.onion:{}" , onion, port)
1024
+ }
1025
+
1026
+ #[ cfg( feature = "std" ) ]
1027
+ impl Display for SocketAddress {
1028
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1029
+ match self {
1030
+ SocketAddress :: TcpIpV4 { addr, port} => write ! ( f, "{}:{}" , std:: net:: Ipv4Addr :: from( * addr) , port) ?,
1031
+ SocketAddress :: TcpIpV6 { addr, port} => write ! ( f, "[{}]:{}" , std:: net:: Ipv6Addr :: from( * addr) , port) ?,
1032
+ SocketAddress :: OnionV2 ( bytes) => write ! ( f, "{}" , to_onion_v2_string( & bytes) ) ?,
1033
+ SocketAddress :: OnionV3 {
1034
+ ed25519_pubkey,
1035
+ checksum,
1036
+ version,
1037
+ port,
1038
+ } => write ! ( f, "{}" , to_onion_v3_string( & ed25519_pubkey, checksum, version, port) ) ?,
1039
+ SocketAddress :: Hostname { hostname, port } => write ! ( f, "{}:{}" , hostname, port) ?,
1040
+ }
1041
+ Ok ( ( ) )
1042
+ }
1043
+ }
1044
+
987
1045
#[ cfg( feature = "std" ) ]
988
1046
impl FromStr for SocketAddress {
989
1047
type Err = SocketAddressParseError ;
@@ -994,12 +1052,13 @@ impl FromStr for SocketAddress {
994
1052
Err ( _) => {
995
1053
let trimmed_input = match s. rfind ( ":" ) {
996
1054
Some ( pos) => pos,
997
- None => return Err ( SocketAddressParseError :: InvalidInput ) ,
1055
+ None if s. ends_with ( ".onion" ) => return parse_onion_v2_address ( s) ,
1056
+ _ => return Err ( SocketAddressParseError :: InvalidInput ) ,
998
1057
} ;
999
1058
let host = & s[ ..trimmed_input] ;
1000
1059
let port: u16 = s[ trimmed_input + 1 ..] . parse ( ) . map_err ( |_| SocketAddressParseError :: InvalidPort ) ?;
1001
1060
if host. ends_with ( ".onion" ) {
1002
- return parse_onion_address ( host, port) ;
1061
+ return parse_onion_v3_address ( host, port) ;
1003
1062
} ;
1004
1063
if let Ok ( hostname) = Hostname :: try_from ( s[ ..trimmed_input] . to_string ( ) ) {
1005
1064
return Ok ( SocketAddress :: Hostname { hostname, port } ) ;
@@ -4066,32 +4125,41 @@ mod tests {
4066
4125
#[ test]
4067
4126
#[ cfg( feature = "std" ) ]
4068
4127
fn test_socket_address_from_str ( ) {
4069
- assert_eq ! ( SocketAddress :: TcpIpV4 {
4128
+ let v4 = SocketAddress :: TcpIpV4 {
4070
4129
addr : Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) . octets ( ) ,
4071
4130
port : 1234 ,
4072
- } , SocketAddress :: from_str( "127.0.0.1:1234" ) . unwrap( ) ) ;
4131
+ } ;
4132
+ assert_eq ! ( v4, SocketAddress :: from_str( "127.0.0.1:1234" ) . unwrap( ) ) ;
4133
+ assert_eq ! ( v4, SocketAddress :: from_str( & v4. to_string( ) ) . unwrap( ) ) ;
4073
4134
4074
- assert_eq ! ( SocketAddress :: TcpIpV6 {
4135
+ let v6 = SocketAddress :: TcpIpV6 {
4075
4136
addr : Ipv6Addr :: new ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ) . octets ( ) ,
4076
4137
port : 1234 ,
4077
- } , SocketAddress :: from_str( "[0:0:0:0:0:0:0:1]:1234" ) . unwrap( ) ) ;
4078
- assert_eq ! (
4079
- SocketAddress :: Hostname {
4138
+ } ;
4139
+ assert_eq ! ( v6, SocketAddress :: from_str( "[0:0:0:0:0:0:0:1]:1234" ) . unwrap( ) ) ;
4140
+ assert_eq ! ( v6, SocketAddress :: from_str( & v6. to_string( ) ) . unwrap( ) ) ;
4141
+
4142
+ let hostname = SocketAddress :: Hostname {
4080
4143
hostname : Hostname :: try_from ( "lightning-node.mydomain.com" . to_string ( ) ) . unwrap ( ) ,
4081
4144
port : 1234 ,
4082
- } , SocketAddress :: from_str( "lightning-node.mydomain.com:1234" ) . unwrap( ) ) ;
4083
- assert_eq ! (
4084
- SocketAddress :: Hostname {
4085
- hostname: Hostname :: try_from( "example.com" . to_string( ) ) . unwrap( ) ,
4086
- port: 1234 ,
4087
- } , SocketAddress :: from_str( "example.com:1234" ) . unwrap( ) ) ;
4088
- assert_eq ! ( SocketAddress :: OnionV3 {
4145
+ } ;
4146
+ assert_eq ! ( hostname, SocketAddress :: from_str( "lightning-node.mydomain.com:1234" ) . unwrap( ) ) ;
4147
+ assert_eq ! ( hostname, SocketAddress :: from_str( & hostname. to_string( ) ) . unwrap( ) ) ;
4148
+
4149
+ let v2 = SocketAddress :: OnionV2 ( [ 37 , 24 , 75 , 5 , 25 , 73 , 117 , 194 , 139 , 102 , 182 , 107 ] ) ;
4150
+ assert_eq ! ( v2, SocketAddress :: from_str( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234" ) . unwrap( ) ) ;
4151
+ assert_eq ! ( v2, SocketAddress :: from_str( & v2. to_string( ) ) . unwrap( ) ) ;
4152
+
4153
+ let v3 = SocketAddress :: OnionV3 {
4089
4154
ed25519_pubkey : [ 37 , 24 , 75 , 5 , 25 , 73 , 117 , 194 , 139 , 102 , 182 , 107 , 4 , 105 , 247 , 246 , 85 ,
4090
4155
111 , 177 , 172 , 49 , 137 , 167 , 155 , 64 , 221 , 163 , 47 , 31 , 33 , 71 , 3 ] ,
4091
4156
checksum : 48326 ,
4092
4157
version : 121 ,
4093
4158
port : 1234
4094
- } , SocketAddress :: from_str( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234" ) . unwrap( ) ) ;
4159
+ } ;
4160
+ assert_eq ! ( v3, SocketAddress :: from_str( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234" ) . unwrap( ) ) ;
4161
+ assert_eq ! ( v3, SocketAddress :: from_str( & v3. to_string( ) ) . unwrap( ) ) ;
4162
+
4095
4163
assert_eq ! ( Err ( SocketAddressParseError :: InvalidOnionV3 ) , SocketAddress :: from_str( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234" ) ) ;
4096
4164
assert_eq ! ( Err ( SocketAddressParseError :: InvalidInput ) , SocketAddress :: from_str( "127.0.0.1@1234" ) ) ;
4097
4165
assert_eq ! ( Err ( SocketAddressParseError :: InvalidInput ) , "" . parse:: <SocketAddress >( ) ) ;
0 commit comments