Skip to main content

iroh_tickets/
endpoint.rs

1//! Tickets for endpoints.
2
3use std::{collections::BTreeSet, str::FromStr};
4
5use iroh_base::{EndpointAddr, EndpointId, TransportAddr};
6use serde::{Deserialize, Serialize};
7
8use crate::{ParseError, Ticket};
9
10/// A token containing information for establishing a connection to an endpoint.
11///
12/// Contains
13/// - The [`EndpointId`] of the endpoint to connect to (a 32-byte ed25519 public key).
14/// - Any known [`TransportAddr`]s on which the endpoint can be reached.
15///
16/// This allows establishing a connection to the endpoint in most circumstances where it is
17/// possible to do so.
18///
19/// This [`EndpointTicket`] is a single item which can be easily serialized and deserialized
20/// and implements the [`Ticket`] trait. The [`Display`] and [`FromStr`] traits round-trip
21/// the canonical string form via [`Ticket::encode_string`] / [`Ticket::decode_string`].
22///
23/// [`Display`]: std::fmt::Display
24/// [`FromStr`]: std::str::FromStr
25///
26/// [`EndpointId`]: iroh_base::EndpointId
27/// [`TransportAddr`]: iroh_base::TransportAddr
28#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
29#[display("{}", Ticket::encode_string(self))]
30pub struct EndpointTicket {
31    addr: EndpointAddr,
32}
33
34/// Wire format for [`EndpointTicket`].
35#[derive(Serialize, Deserialize)]
36enum TicketWireFormat {
37    Variant1(Variant1EndpointTicket),
38}
39
40#[derive(Serialize, Deserialize)]
41struct Variant1EndpointTicket {
42    addr: Variant1EndpointAddr,
43}
44
45impl Ticket for EndpointTicket {
46    const KIND: &'static str = "endpoint";
47
48    fn encode_bytes(&self) -> Vec<u8> {
49        let data = TicketWireFormat::Variant1(Variant1EndpointTicket {
50            addr: Variant1EndpointAddr {
51                id: self.addr.id,
52                info: Variant1AddrInfo {
53                    addrs: self.addr.addrs.clone(),
54                },
55            },
56        });
57        postcard::to_stdvec(&data).expect("postcard serialization failed")
58    }
59
60    fn decode_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
61        let res: TicketWireFormat = postcard::from_bytes(bytes)?;
62        let TicketWireFormat::Variant1(Variant1EndpointTicket { addr }) = res;
63        Ok(Self {
64            addr: EndpointAddr {
65                id: addr.id,
66                addrs: addr.info.addrs,
67            },
68        })
69    }
70}
71
72impl FromStr for EndpointTicket {
73    type Err = ParseError;
74
75    fn from_str(s: &str) -> Result<Self, Self::Err> {
76        Ticket::decode_string(s)
77    }
78}
79
80impl EndpointTicket {
81    /// Creates a new ticket.
82    pub fn new(addr: EndpointAddr) -> Self {
83        Self { addr }
84    }
85
86    /// The [`EndpointAddr`] of the provider for this ticket.
87    pub fn endpoint_addr(&self) -> &EndpointAddr {
88        &self.addr
89    }
90}
91
92impl From<EndpointAddr> for EndpointTicket {
93    /// Creates a ticket from given addressing info.
94    fn from(addr: EndpointAddr) -> Self {
95        Self { addr }
96    }
97}
98
99impl From<EndpointTicket> for EndpointAddr {
100    /// Returns the addressing info from given ticket.
101    fn from(ticket: EndpointTicket) -> Self {
102        ticket.addr
103    }
104}
105
106impl Serialize for EndpointTicket {
107    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
108        if serializer.is_human_readable() {
109            serializer.serialize_str(&self.encode_string())
110        } else {
111            let EndpointTicket { addr } = self;
112            (addr).serialize(serializer)
113        }
114    }
115}
116
117impl<'de> Deserialize<'de> for EndpointTicket {
118    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
119        if deserializer.is_human_readable() {
120            let s = String::deserialize(deserializer)?;
121            Self::decode_string(&s).map_err(serde::de::Error::custom)
122        } else {
123            let peer = Deserialize::deserialize(deserializer)?;
124            Ok(Self::new(peer))
125        }
126    }
127}
128
129#[derive(Serialize, Deserialize)]
130struct Variant1EndpointAddr {
131    id: EndpointId,
132    info: Variant1AddrInfo,
133}
134
135#[derive(Serialize, Deserialize)]
136struct Variant1AddrInfo {
137    addrs: BTreeSet<TransportAddr>,
138}
139
140#[cfg(test)]
141mod tests {
142    use std::{
143        net::{Ipv4Addr, SocketAddr},
144        str::FromStr,
145    };
146
147    use data_encoding::HEXLOWER;
148    use iroh_base::{PublicKey, SecretKey, TransportAddr};
149    use rand::{RngExt, SeedableRng};
150
151    use super::*;
152
153    fn make_ticket() -> EndpointTicket {
154        let mut rng = rand::rngs::ChaCha8Rng::seed_from_u64(0u64);
155        let peer = SecretKey::from_bytes(&rng.random()).public();
156        let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 1234));
157        EndpointTicket {
158            addr: EndpointAddr::from_parts(peer, [TransportAddr::Ip(addr)]),
159        }
160    }
161
162    #[test]
163    fn test_ticket_postcard() {
164        let ticket = make_ticket();
165        let bytes = postcard::to_stdvec(&ticket).unwrap();
166        let ticket2: EndpointTicket = postcard::from_bytes(&bytes).unwrap();
167        assert_eq!(ticket2, ticket);
168    }
169
170    #[test]
171    fn test_ticket_json() {
172        let ticket = make_ticket();
173        let json = serde_json::to_string(&ticket).unwrap();
174        let ticket2: EndpointTicket = serde_json::from_str(&json).unwrap();
175        assert_eq!(ticket2, ticket);
176    }
177
178    #[test]
179    fn test_ticket_base32() {
180        let endpoint_id =
181            PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
182                .unwrap();
183
184        let ticket = EndpointTicket {
185            addr: EndpointAddr::from_parts(
186                endpoint_id,
187                [
188                    TransportAddr::Relay("http://derp.me./".parse().unwrap()),
189                    TransportAddr::Ip("127.0.0.1:1024".parse().unwrap()),
190                ],
191            ),
192        };
193        let base32 = data_encoding::BASE32_NOPAD
194            .decode(
195                ticket
196                    .encode_string()
197                    .strip_prefix("endpoint")
198                    .unwrap()
199                    .to_ascii_uppercase()
200                    .as_bytes(),
201            )
202            .unwrap();
203        let expected = [
204            // variant
205            "00",
206            // endpoint id, 32 bytes, see above
207            "ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6",
208            // two addrs
209            "02",
210            // TransportAddr: Relay
211            "00",
212            // 16 bytes
213            "10",
214            // RelayUrl
215            "687474703a2f2f646572702e6d652e2f",
216            // TransportAddr: IP
217            "01",
218            // IPv4
219            "00",
220            // address, see above
221            "7f0000018008",
222        ];
223
224        // 00ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6
225        // 02
226        // 00
227        // 10
228        // 687474703a2f2f646572702e6d652e2f
229        // 01
230        // 00
231        // 7f0000018008
232        let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap();
233        assert_eq!(base32, expected);
234    }
235}