1use std::{collections::BTreeSet, net::SocketAddr, str::FromStr};
3
4use anyhow::Result;
5use iroh::{NodeAddr, NodeId, RelayUrl};
6use iroh_base::ticket::{self, 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 node: NodeAddr,
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 node_id: NodeId,
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 node_id: self.node.node_id,
71 info: Variant0AddrInfo {
72 relay_url: self.node.relay_url.clone(),
73 direct_addresses: self.node.direct_addresses.clone(),
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, ticket::ParseError> {
83 let res: TicketWireFormat = postcard::from_bytes(bytes)?;
84 let TicketWireFormat::Variant0(Variant0BlobTicket { node, format, hash }) = res;
85 Ok(Self {
86 node: NodeAddr {
87 node_id: node.node_id,
88 relay_url: node.info.relay_url,
89 direct_addresses: node.info.direct_addresses,
90 },
91 format,
92 hash,
93 })
94 }
95}
96
97impl FromStr for BlobTicket {
98 type Err = ticket::ParseError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Ticket::deserialize(s)
102 }
103}
104
105impl BlobTicket {
106 pub fn new(node: NodeAddr, hash: Hash, format: BlobFormat) -> Self {
108 Self { hash, format, node }
109 }
110
111 pub fn hash(&self) -> Hash {
113 self.hash
114 }
115
116 pub fn node_addr(&self) -> &NodeAddr {
118 &self.node
119 }
120
121 pub fn format(&self) -> BlobFormat {
123 self.format
124 }
125
126 pub fn hash_and_format(&self) -> HashAndFormat {
127 HashAndFormat {
128 hash: self.hash,
129 format: self.format,
130 }
131 }
132
133 pub fn recursive(&self) -> bool {
135 self.format.is_hash_seq()
136 }
137
138 pub fn into_parts(self) -> (NodeAddr, Hash, BlobFormat) {
140 let BlobTicket { node, hash, format } = self;
141 (node, hash, format)
142 }
143}
144
145impl Serialize for BlobTicket {
146 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
147 if serializer.is_human_readable() {
148 serializer.serialize_str(&self.to_string())
149 } else {
150 let BlobTicket { node, format, hash } = self;
151 (node, format, hash).serialize(serializer)
152 }
153 }
154}
155
156impl<'de> Deserialize<'de> for BlobTicket {
157 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 if deserializer.is_human_readable() {
159 let s = String::deserialize(deserializer)?;
160 Self::from_str(&s).map_err(serde::de::Error::custom)
161 } else {
162 let (peer, format, hash) = Deserialize::deserialize(deserializer)?;
163 Ok(Self::new(peer, hash, format))
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use std::net::SocketAddr;
171
172 use iroh::{PublicKey, SecretKey};
173 use iroh_test::{assert_eq_hex, hexdump::parse_hexdump};
174
175 use super::*;
176
177 fn make_ticket() -> BlobTicket {
178 let hash = Hash::new(b"hi there");
179 let peer = SecretKey::generate(rand::thread_rng()).public();
180 let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap();
181 let relay_url = None;
182 BlobTicket {
183 hash,
184 node: NodeAddr::from_parts(peer, relay_url, [addr]),
185 format: BlobFormat::HashSeq,
186 }
187 }
188
189 #[test]
190 fn test_ticket_postcard() {
191 let ticket = make_ticket();
192 let bytes = postcard::to_stdvec(&ticket).unwrap();
193 let ticket2: BlobTicket = postcard::from_bytes(&bytes).unwrap();
194 assert_eq!(ticket2, ticket);
195 }
196
197 #[test]
198 fn test_ticket_json() {
199 let ticket = make_ticket();
200 let json = serde_json::to_string(&ticket).unwrap();
201 let ticket2: BlobTicket = serde_json::from_str(&json).unwrap();
202 assert_eq!(ticket2, ticket);
203 }
204
205 #[test]
206 fn test_ticket_base32() {
207 let hash =
208 Hash::from_str("0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072")
209 .unwrap();
210 let node_id =
211 PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
212 .unwrap();
213
214 let ticket = BlobTicket {
215 node: NodeAddr::from_parts(node_id, None, []),
216 format: BlobFormat::Raw,
217 hash,
218 };
219 let encoded = ticket.to_string();
220 let stripped = encoded.strip_prefix("blob").unwrap();
221 let base32 = data_encoding::BASE32_NOPAD
222 .decode(stripped.to_ascii_uppercase().as_bytes())
223 .unwrap();
224 let expected = parse_hexdump("
225 00 # discriminator for variant 0
226 ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 # node id, 32 bytes, see above
227 00 # relay url
228 00 # number of addresses (0)
229 00 # format (raw)
230 0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072 # hash, 32 bytes, see above
231 ").unwrap();
232 assert_eq_hex!(base32, expected);
233 }
234}