1use std::{collections::BTreeSet, net::SocketAddr, str::FromStr};
3
4use anyhow::Result;
5use iroh::{EndpointAddr, EndpointId, RelayUrl};
6use iroh_tickets::{ParseError, Ticket};
7use serde::{Deserialize, Serialize};
8
9use crate::{BlobFormat, Hash, HashAndFormat};
10
11#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
15#[display("{}", Ticket::serialize(self))]
16pub struct BlobTicket {
17 addr: EndpointAddr,
19 format: BlobFormat,
21 hash: Hash,
23}
24
25impl From<BlobTicket> for HashAndFormat {
26 fn from(val: BlobTicket) -> Self {
27 HashAndFormat {
28 hash: val.hash,
29 format: val.format,
30 }
31 }
32}
33
34#[derive(Serialize, Deserialize)]
40enum TicketWireFormat {
41 Variant0(Variant0BlobTicket),
42}
43
44#[derive(Serialize, Deserialize)]
46struct Variant0BlobTicket {
47 node: Variant0NodeAddr,
48 format: BlobFormat,
49 hash: Hash,
50}
51
52#[derive(Serialize, Deserialize)]
53struct Variant0NodeAddr {
54 endpoint_id: EndpointId,
55 info: Variant0AddrInfo,
56}
57
58#[derive(Serialize, Deserialize)]
59struct Variant0AddrInfo {
60 relay_url: Option<RelayUrl>,
61 direct_addresses: BTreeSet<SocketAddr>,
62}
63
64impl Ticket for BlobTicket {
65 const KIND: &'static str = "blob";
66
67 fn to_bytes(&self) -> Vec<u8> {
68 let data = TicketWireFormat::Variant0(Variant0BlobTicket {
69 node: Variant0NodeAddr {
70 endpoint_id: self.addr.id,
71 info: Variant0AddrInfo {
72 relay_url: self.addr.relay_urls().next().cloned(),
73 direct_addresses: self.addr.ip_addrs().cloned().collect(),
74 },
75 },
76 format: self.format,
77 hash: self.hash,
78 });
79 postcard::to_stdvec(&data).expect("postcard serialization failed")
80 }
81
82 fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, ParseError> {
83 let res: TicketWireFormat = postcard::from_bytes(bytes)?;
84 let TicketWireFormat::Variant0(Variant0BlobTicket { node, format, hash }) = res;
85 let mut addr = EndpointAddr::new(node.endpoint_id);
86 if let Some(relay_url) = node.info.relay_url {
87 addr = addr.with_relay_url(relay_url);
88 }
89 for ip_addr in node.info.direct_addresses {
90 addr = addr.with_ip_addr(ip_addr);
91 }
92 Ok(Self { addr, format, hash })
93 }
94}
95
96impl FromStr for BlobTicket {
97 type Err = ParseError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Ticket::deserialize(s)
101 }
102}
103
104impl BlobTicket {
105 pub fn new(addr: EndpointAddr, hash: Hash, format: BlobFormat) -> Self {
107 Self { hash, format, addr }
108 }
109
110 pub fn hash(&self) -> Hash {
112 self.hash
113 }
114
115 pub fn addr(&self) -> &EndpointAddr {
117 &self.addr
118 }
119
120 pub fn format(&self) -> BlobFormat {
122 self.format
123 }
124
125 pub fn hash_and_format(&self) -> HashAndFormat {
126 HashAndFormat {
127 hash: self.hash,
128 format: self.format,
129 }
130 }
131
132 pub fn recursive(&self) -> bool {
134 self.format.is_hash_seq()
135 }
136
137 pub fn into_parts(self) -> (EndpointAddr, Hash, BlobFormat) {
139 let BlobTicket { addr, hash, format } = self;
140 (addr, hash, format)
141 }
142}
143
144impl Serialize for BlobTicket {
145 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
146 if serializer.is_human_readable() {
147 serializer.serialize_str(&self.to_string())
148 } else {
149 let BlobTicket {
150 addr: node,
151 format,
152 hash,
153 } = self;
154 (node, format, hash).serialize(serializer)
155 }
156 }
157}
158
159impl<'de> Deserialize<'de> for BlobTicket {
160 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
161 if deserializer.is_human_readable() {
162 let s = String::deserialize(deserializer)?;
163 Self::from_str(&s).map_err(serde::de::Error::custom)
164 } else {
165 let (peer, format, hash) = Deserialize::deserialize(deserializer)?;
166 Ok(Self::new(peer, hash, format))
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use std::net::SocketAddr;
174
175 use iroh::{PublicKey, SecretKey, TransportAddr};
176 use iroh_test::{assert_eq_hex, hexdump::parse_hexdump};
177
178 use super::*;
179
180 fn make_ticket() -> BlobTicket {
181 let hash = Hash::new(b"hi there");
182 let peer = SecretKey::generate(&mut rand::rng()).public();
183 let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap();
184 BlobTicket {
185 hash,
186 addr: EndpointAddr::from_parts(peer, [TransportAddr::Ip(addr)]),
187 format: BlobFormat::HashSeq,
188 }
189 }
190
191 #[test]
192 fn test_ticket_postcard() {
193 let ticket = make_ticket();
194 let bytes = postcard::to_stdvec(&ticket).unwrap();
195 let ticket2: BlobTicket = postcard::from_bytes(&bytes).unwrap();
196 assert_eq!(ticket2, ticket);
197 }
198
199 #[test]
200 fn test_ticket_json() {
201 let ticket = make_ticket();
202 let json = serde_json::to_string(&ticket).unwrap();
203 let ticket2: BlobTicket = serde_json::from_str(&json).unwrap();
204 assert_eq!(ticket2, ticket);
205 }
206
207 #[test]
208 fn test_ticket_base32() {
209 let hash =
210 Hash::from_str("0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072")
211 .unwrap();
212 let endpoint_id =
213 PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
214 .unwrap();
215
216 let ticket = BlobTicket {
217 addr: EndpointAddr::new(endpoint_id),
218 format: BlobFormat::Raw,
219 hash,
220 };
221 let encoded = ticket.to_string();
222 let stripped = encoded.strip_prefix("blob").unwrap();
223 let base32 = data_encoding::BASE32_NOPAD
224 .decode(stripped.to_ascii_uppercase().as_bytes())
225 .unwrap();
226 let expected = parse_hexdump("
227 00 # discriminator for variant 0
228 ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 # endpoint id, 32 bytes, see above
229 00 # relay url
230 00 # number of addresses (0)
231 00 # format (raw)
232 0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072 # hash, 32 bytes, see above
233 ").unwrap();
234 assert_eq_hex!(base32, expected);
235 }
236}