1use std::{collections::BTreeSet, str::FromStr};
4
5use iroh_base::{EndpointAddr, EndpointId, TransportAddr};
6use serde::{Deserialize, Serialize};
7
8use crate::{ParseError, Ticket};
9
10#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
29#[display("{}", Ticket::encode_string(self))]
30pub struct EndpointTicket {
31 addr: EndpointAddr,
32}
33
34#[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 pub fn new(addr: EndpointAddr) -> Self {
83 Self { addr }
84 }
85
86 pub fn endpoint_addr(&self) -> &EndpointAddr {
88 &self.addr
89 }
90}
91
92impl From<EndpointAddr> for EndpointTicket {
93 fn from(addr: EndpointAddr) -> Self {
95 Self { addr }
96 }
97}
98
99impl From<EndpointTicket> for EndpointAddr {
100 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 "00",
206 "ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6",
208 "02",
210 "00",
212 "10",
214 "687474703a2f2f646572702e6d652e2f",
216 "01",
218 "00",
220 "7f0000018008",
222 ];
223
224 let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap();
233 assert_eq!(base32, expected);
234 }
235}