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(
42    multiaddr: &'_ Multiaddr,
43) -> Result<AddressOrMultiStreamAddress<'_>, Error> {
44    let mut iter = multiaddr.iter().fuse();
45
46    let proto1 = iter.next().ok_or(Error::UnknownCombination)?;
47    let proto2 = iter.next().ok_or(Error::UnknownCombination)?;
48    let proto3 = iter.next();
49    let proto4 = iter.next();
50
51    if iter.next().is_some() {
52        return Err(Error::UnknownCombination);
53    }
54
55    Ok(match (proto1, proto2, proto3, proto4) {
56        (Protocol::Ip4(ip), Protocol::Tcp(port), None, None) => {
57            AddressOrMultiStreamAddress::Address(Address::TcpIp {
58                ip: IpAddr::V4(Ipv4Addr::from(ip)),
59                port,
60            })
61        }
62        (Protocol::Ip6(ip), Protocol::Tcp(port), None, None) => {
63            AddressOrMultiStreamAddress::Address(Address::TcpIp {
64                ip: IpAddr::V6(Ipv6Addr::from(ip)),
65                port,
66            })
67        }
68        (
69            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
70            Protocol::Tcp(port),
71            None,
72            None,
73        ) => AddressOrMultiStreamAddress::Address(Address::TcpDns {
74            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
75            port,
76        }),
77        (Protocol::Ip4(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
78            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
79                ip: IpAddr::V4(Ipv4Addr::from(ip)),
80                port,
81            })
82        }
83        (Protocol::Ip6(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
84            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
85                ip: IpAddr::V6(Ipv6Addr::from(ip)),
86                port,
87            })
88        }
89        (
90            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
91            Protocol::Tcp(port),
92            Some(Protocol::Ws),
93            None,
94        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
95            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
96            port,
97            secure: false,
98        }),
99        (
100            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
101            Protocol::Tcp(port),
102            Some(Protocol::Wss),
103            None,
104        )
105        | (
106            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
107            Protocol::Tcp(port),
108            Some(Protocol::Tls),
109            Some(Protocol::Ws),
110        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
111            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
112            port,
113            secure: true,
114        }),
115
116        (
117            Protocol::Ip4(ip),
118            Protocol::Udp(port),
119            Some(Protocol::WebRtcDirect),
120            Some(Protocol::Certhash(multihash)),
121        ) => {
122            if multihash.hash_algorithm_code() != 0x12 {
123                return Err(Error::NonSha256Certhash);
124            }
125            let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
126                return Err(Error::InvalidMultihashLength);
127            };
128            AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
129                ip: IpAddr::V4(Ipv4Addr::from(ip)),
130                port,
131                remote_certificate_sha256,
132            })
133        }
134
135        (
136            Protocol::Ip6(ip),
137            Protocol::Udp(port),
138            Some(Protocol::WebRtcDirect),
139            Some(Protocol::Certhash(multihash)),
140        ) => {
141            if multihash.hash_algorithm_code() != 0x12 {
142                return Err(Error::NonSha256Certhash);
143            }
144            let Ok(remote_certificate_sha256) = <&[u8; 32]>::try_from(multihash.data_ref()) else {
145                return Err(Error::InvalidMultihashLength);
146            };
147            AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
148                ip: IpAddr::V6(Ipv6Addr::from(ip)),
149                port,
150                remote_certificate_sha256,
151            })
152        }
153
154        _ => return Err(Error::UnknownCombination),
155    })
156}
157
158#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
159pub enum Error {
160    /// Unknown combination of protocols.
161    UnknownCombination,
162
163    /// Multiaddress contains a domain name that isn't UTF-8.
164    ///
165    /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
166    /// >           string. Any binary data can be used as a domain name, provided it follows
167    /// >           a few restrictions (notably its length). However, in this context, we
168    /// >           automatically consider as non-supported a multiaddress that contains a
169    /// >           non-UTF-8 domain name, for the sake of simplicity.
170    NonUtf8DomainName(str::Utf8Error),
171
172    /// Multiaddr contains a `/certhash` components whose multihash isn't using SHA-256, but the
173    /// rest of the multiaddr requires SHA-256.
174    NonSha256Certhash,
175
176    /// Multiaddr contains a multihash whose length doesn't match its hash algorithm.
177    InvalidMultihashLength,
178}