use smoldot::libp2p::multiaddr::{Multiaddr, Protocol};
use super::{Address, ConnectionType, MultiStreamAddress};
use core::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str,
};
pub enum AddressOrMultiStreamAddress<'a> {
Address(Address<'a>),
MultiStreamAddress(MultiStreamAddress<'a>),
}
impl<'a> From<&'a AddressOrMultiStreamAddress<'a>> for ConnectionType {
fn from(address: &'a AddressOrMultiStreamAddress<'a>) -> ConnectionType {
match address {
AddressOrMultiStreamAddress::Address(a) => ConnectionType::from(a),
AddressOrMultiStreamAddress::MultiStreamAddress(a) => ConnectionType::from(a),
}
}
}
pub fn multiaddr_to_address(multiaddr: &Multiaddr) -> Result<AddressOrMultiStreamAddress, Error> {
let mut iter = multiaddr.iter().fuse();
let proto1 = iter.next().ok_or(Error::UnknownCombination)?;
let proto2 = iter.next().ok_or(Error::UnknownCombination)?;
let proto3 = iter.next();
let proto4 = iter.next();
if iter.next().is_some() {
return Err(Error::UnknownCombination);
}
Ok(match (proto1, proto2, proto3, proto4) {
(Protocol::Ip4(ip), Protocol::Tcp(port), None, None) => {
AddressOrMultiStreamAddress::Address(Address::TcpIp {
ip: IpAddr::V4(Ipv4Addr::from(ip)),
port,
})
}
(Protocol::Ip6(ip), Protocol::Tcp(port), None, None) => {
AddressOrMultiStreamAddress::Address(Address::TcpIp {
ip: IpAddr::V6(Ipv6Addr::from(ip)),
port,
})
}
(
Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
Protocol::Tcp(port),
None,
None,
) => AddressOrMultiStreamAddress::Address(Address::TcpDns {
hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
port,
}),
(Protocol::Ip4(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
ip: IpAddr::V4(Ipv4Addr::from(ip)),
port,
})
}
(Protocol::Ip6(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
ip: IpAddr::V6(Ipv6Addr::from(ip)),
port,
})
}
(
Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
Protocol::Tcp(port),
Some(Protocol::Ws),
None,
) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
port,
secure: false,
}),
(
Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
Protocol::Tcp(port),
Some(Protocol::Wss),
None,
)
| (
Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
Protocol::Tcp(port),
Some(Protocol::Tls),
Some(Protocol::Ws),
) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
port,
secure: true,
}),
(
Protocol::Ip4(ip),
Protocol::Udp(port),
Some(Protocol::WebRtcDirect),
Some(Protocol::Certhash(multihash)),
) => {
if multihash.hash_algorithm_code() != 0x12 {
return Err(Error::NonSha256Certhash);
}
let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
return Err(Error::InvalidMultihashLength);
};
AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
ip: IpAddr::V4(Ipv4Addr::from(ip)),
port,
remote_certificate_sha256,
})
}
(
Protocol::Ip6(ip),
Protocol::Udp(port),
Some(Protocol::WebRtcDirect),
Some(Protocol::Certhash(multihash)),
) => {
if multihash.hash_algorithm_code() != 0x12 {
return Err(Error::NonSha256Certhash);
}
let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
return Err(Error::InvalidMultihashLength);
};
AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
ip: IpAddr::V6(Ipv6Addr::from(ip)),
port,
remote_certificate_sha256,
})
}
_ => return Err(Error::UnknownCombination),
})
}
#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
pub enum Error {
UnknownCombination,
NonUtf8DomainName(str::Utf8Error),
NonSha256Certhash,
InvalidMultihashLength,
}