libp2p_webtransport_websys/
endpoint.rs1use std::collections::HashSet;
2
3use js_sys::{Array, Uint8Array};
4use libp2p_identity::PeerId;
5use multiaddr::{Multiaddr, Protocol};
6use multihash::Multihash;
7
8use crate::{
9 bindings::{WebTransportHash, WebTransportOptions},
10 Error,
11};
12
13pub(crate) struct Endpoint {
14 pub(crate) host: String,
15 pub(crate) port: u16,
16 pub(crate) is_ipv6: bool,
17 pub(crate) certhashes: HashSet<Multihash<64>>,
18 pub(crate) remote_peer: Option<PeerId>,
19}
20
21impl Endpoint {
22 pub(crate) fn from_multiaddr(addr: &Multiaddr) -> Result<Self, Error> {
23 let mut host = None;
24 let mut port = None;
25 let mut found_quic = false;
26 let mut found_webtransport = false;
27 let mut certhashes = HashSet::new();
28 let mut remote_peer = None;
29 let mut is_ipv6 = false;
30
31 for proto in addr.iter() {
32 match proto {
33 Protocol::Ip4(addr) => {
34 if host.is_some() {
35 return Err(Error::InvalidMultiaddr("More than one host definitions"));
36 }
37
38 host = Some(addr.to_string());
39 }
40 Protocol::Ip6(addr) => {
41 if host.is_some() {
42 return Err(Error::InvalidMultiaddr("More than one host definitions"));
43 }
44
45 is_ipv6 = true;
46 host = Some(addr.to_string());
47 }
48 Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain) => {
49 if port.is_some() {
50 return Err(Error::InvalidMultiaddr("More than one host definitions"));
51 }
52
53 host = Some(domain.to_string())
54 }
55 Protocol::Dnsaddr(_) => {
56 return Err(Error::InvalidMultiaddr(
57 "/dnsaddr not supported from within a browser",
58 ));
59 }
60 Protocol::Udp(p) => {
61 if port.is_some() {
62 return Err(Error::InvalidMultiaddr("More than one port definitions"));
63 }
64
65 port = Some(p);
66 }
67 Protocol::Quic | Protocol::QuicV1 => {
68 if host.is_none() || port.is_none() {
69 return Err(Error::InvalidMultiaddr(
70 "No host and port definition before /quic/webtransport",
71 ));
72 }
73
74 found_quic = true;
75 }
76 Protocol::WebTransport => {
77 if !found_quic {
78 return Err(Error::InvalidMultiaddr(
79 "/quic is not found before /webtransport",
80 ));
81 }
82
83 found_webtransport = true;
84 }
85 Protocol::Certhash(hash) => {
86 if !found_webtransport {
87 return Err(Error::InvalidMultiaddr(
88 "/certhashes must be after /quic/found_webtransport",
89 ));
90 }
91
92 certhashes.insert(hash);
93 }
94 Protocol::P2p(peer) => {
95 if remote_peer.is_some() {
96 return Err(Error::InvalidMultiaddr("More than one peer definitions"));
97 }
98
99 remote_peer = Some(peer);
100 }
101 _ => {}
102 }
103 }
104
105 if !found_quic || !found_webtransport {
106 return Err(Error::InvalidMultiaddr(
107 "Not a /quic/webtransport multiaddr",
108 ));
109 }
110
111 let host = host.ok_or_else(|| Error::InvalidMultiaddr("Host is not defined"))?;
112 let port = port.ok_or_else(|| Error::InvalidMultiaddr("Port is not defined"))?;
113
114 Ok(Endpoint {
115 host,
116 port,
117 is_ipv6,
118 certhashes,
119 remote_peer,
120 })
121 }
122
123 pub(crate) fn url(&self) -> String {
124 let host = &self.host;
125 let port = self.port;
126
127 if self.is_ipv6 {
128 format!("https://[{host}]:{port}/.well-known/libp2p-webtransport?type=noise")
129 } else {
130 format!("https://{host}:{port}/.well-known/libp2p-webtransport?type=noise")
131 }
132 }
133
134 pub(crate) fn webtransport_opts(&self) -> WebTransportOptions {
135 let mut opts = WebTransportOptions::new();
136 let hashes = Array::new();
137
138 for hash in &self.certhashes {
139 let digest = Uint8Array::from(hash.digest());
140
141 let mut jshash = WebTransportHash::new();
142 jshash.algorithm("sha-256").value(&digest);
143
144 hashes.push(&jshash);
145 }
146
147 opts.server_certificate_hashes(&hashes);
148
149 opts
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use std::str::FromStr;
156
157 use super::*;
158
159 fn multihash_from_str(s: &str) -> Multihash<64> {
160 let (_base, bytes) = multibase::decode(s).unwrap();
161 Multihash::from_bytes(&bytes).unwrap()
162 }
163
164 #[test]
165 fn valid_webtransport_multiaddr() {
166 let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap();
167 let endpoint = Endpoint::from_multiaddr(&addr).unwrap();
168
169 assert_eq!(endpoint.host, "127.0.0.1");
170 assert_eq!(endpoint.port, 44874);
171 assert_eq!(endpoint.certhashes.len(), 2);
172
173 assert!(endpoint.certhashes.contains(&multihash_from_str(
174 "uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng"
175 )));
176
177 assert!(endpoint.certhashes.contains(&multihash_from_str(
178 "uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ"
179 )));
180
181 assert_eq!(
182 endpoint.remote_peer.unwrap(),
183 PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap()
184 );
185
186 assert_eq!(
187 endpoint.url(),
188 "https://127.0.0.1:44874/.well-known/libp2p-webtransport?type=noise"
189 );
190 }
191
192 #[test]
193 fn valid_webtransport_multiaddr_without_certhashes() {
194 let addr = Multiaddr::from_str("/ip4/127.0.0.1/udp/44874/quic-v1/webtransport/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap();
195 let endpoint = Endpoint::from_multiaddr(&addr).unwrap();
196
197 assert_eq!(endpoint.host, "127.0.0.1");
198 assert_eq!(endpoint.port, 44874);
199 assert_eq!(endpoint.certhashes.len(), 0);
200 assert_eq!(
201 endpoint.remote_peer.unwrap(),
202 PeerId::from_str("12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap()
203 );
204 }
205
206 #[test]
207 fn ipv6_webtransport() {
208 let addr = Multiaddr::from_str("/ip6/::1/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap();
209 let endpoint = Endpoint::from_multiaddr(&addr).unwrap();
210
211 assert_eq!(endpoint.host, "::1");
212 assert_eq!(endpoint.port, 44874);
213 assert_eq!(
214 endpoint.url(),
215 "https://[::1]:44874/.well-known/libp2p-webtransport?type=noise"
216 );
217 }
218
219 #[test]
220 fn dns_webtransport() {
221 let addr = Multiaddr::from_str("/dns/libp2p.io/udp/44874/quic-v1/webtransport/certhash/uEiCaDd1Ca1A8IVJ3hsIxIyi11cwxaDKqzVrBkGJbKZU5ng/certhash/uEiDv-VGW8oXxui_G_Kqp-87YjvET-Hr2qYAMYPePJDcsjQ/p2p/12D3KooWR7EfNv5SLtgjMRjUwR8AvNu3hP4fLrtSa9fmHHXKYWNG").unwrap();
222 let endpoint = Endpoint::from_multiaddr(&addr).unwrap();
223
224 assert_eq!(endpoint.host, "libp2p.io");
225 assert_eq!(endpoint.port, 44874);
226 assert_eq!(
227 endpoint.url(),
228 "https://libp2p.io:44874/.well-known/libp2p-webtransport?type=noise"
229 );
230 }
231}