smoldot_light/platform/
address_parse.rs

1// Smoldot
2// Copyright (C) 2023  Pierre Krieger
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18use smoldot::libp2p::multiaddr::{Multiaddr, Protocol};
19
20use super::{Address, ConnectionType, MultiStreamAddress};
21use core::{
22    net::{IpAddr, Ipv4Addr, Ipv6Addr},
23    str,
24};
25
26pub enum AddressOrMultiStreamAddress<'a> {
27    Address(Address<'a>),
28    MultiStreamAddress(MultiStreamAddress<'a>),
29}
30
31impl<'a> From<&'a AddressOrMultiStreamAddress<'a>> for ConnectionType {
32    fn from(address: &'a AddressOrMultiStreamAddress<'a>) -> ConnectionType {
33        match address {
34            AddressOrMultiStreamAddress::Address(a) => ConnectionType::from(a),
35            AddressOrMultiStreamAddress::MultiStreamAddress(a) => ConnectionType::from(a),
36        }
37    }
38}
39
40/// Parses a [`Multiaddr`] into an [`Address`] or [`MultiStreamAddress`].
41pub fn multiaddr_to_address(multiaddr: &Multiaddr) -> Result<AddressOrMultiStreamAddress, Error> {
42    let mut iter = multiaddr.iter().fuse();
43
44    let proto1 = iter.next().ok_or(Error::UnknownCombination)?;
45    let proto2 = iter.next().ok_or(Error::UnknownCombination)?;
46    let proto3 = iter.next();
47    let proto4 = iter.next();
48
49    if iter.next().is_some() {
50        return Err(Error::UnknownCombination);
51    }
52
53    Ok(match (proto1, proto2, proto3, proto4) {
54        (Protocol::Ip4(ip), Protocol::Tcp(port), None, None) => {
55            AddressOrMultiStreamAddress::Address(Address::TcpIp {
56                ip: IpAddr::V4(Ipv4Addr::from(ip)),
57                port,
58            })
59        }
60        (Protocol::Ip6(ip), Protocol::Tcp(port), None, None) => {
61            AddressOrMultiStreamAddress::Address(Address::TcpIp {
62                ip: IpAddr::V6(Ipv6Addr::from(ip)),
63                port,
64            })
65        }
66        (
67            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
68            Protocol::Tcp(port),
69            None,
70            None,
71        ) => AddressOrMultiStreamAddress::Address(Address::TcpDns {
72            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
73            port,
74        }),
75        (Protocol::Ip4(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
76            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
77                ip: IpAddr::V4(Ipv4Addr::from(ip)),
78                port,
79            })
80        }
81        (Protocol::Ip6(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
82            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
83                ip: IpAddr::V6(Ipv6Addr::from(ip)),
84                port,
85            })
86        }
87        (
88            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
89            Protocol::Tcp(port),
90            Some(Protocol::Ws),
91            None,
92        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
93            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
94            port,
95            secure: false,
96        }),
97        (
98            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
99            Protocol::Tcp(port),
100            Some(Protocol::Wss),
101            None,
102        )
103        | (
104            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
105            Protocol::Tcp(port),
106            Some(Protocol::Tls),
107            Some(Protocol::Ws),
108        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
109            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
110            port,
111            secure: true,
112        }),
113
114        (
115            Protocol::Ip4(ip),
116            Protocol::Udp(port),
117            Some(Protocol::WebRtcDirect),
118            Some(Protocol::Certhash(multihash)),
119        ) => {
120            if multihash.hash_algorithm_code() != 0x12 {
121                return Err(Error::NonSha256Certhash);
122            }
123            let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
124                return Err(Error::InvalidMultihashLength);
125            };
126            AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
127                ip: IpAddr::V4(Ipv4Addr::from(ip)),
128                port,
129                remote_certificate_sha256,
130            })
131        }
132
133        (
134            Protocol::Ip6(ip),
135            Protocol::Udp(port),
136            Some(Protocol::WebRtcDirect),
137            Some(Protocol::Certhash(multihash)),
138        ) => {
139            if multihash.hash_algorithm_code() != 0x12 {
140                return Err(Error::NonSha256Certhash);
141            }
142            let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
143                return Err(Error::InvalidMultihashLength);
144            };
145            AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
146                ip: IpAddr::V6(Ipv6Addr::from(ip)),
147                port,
148                remote_certificate_sha256,
149            })
150        }
151
152        _ => return Err(Error::UnknownCombination),
153    })
154}
155
156#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
157pub enum Error {
158    /// Unknown combination of protocols.
159    UnknownCombination,
160
161    /// Multiaddress contains a domain name that isn't UTF-8.
162    ///
163    /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
164    /// >           string. Any binary data can be used as a domain name, provided it follows
165    /// >           a few restrictions (notably its length). However, in this context, we
166    /// >           automatically consider as non-supported a multiaddress that contains a
167    /// >           non-UTF-8 domain name, for the sake of simplicity.
168    NonUtf8DomainName(str::Utf8Error),
169
170    /// Multiaddr contains a `/certhash` components whose multihash isn't using SHA-256, but the
171    /// rest of the multiaddr requires SHA-256.
172    NonSha256Certhash,
173
174    /// Multiaddr contains a multihash whose length doesn't match its hash algorithm.
175    InvalidMultihashLength,
176}