iroh_base/
ticket.rs

1//! Tickets is a serializable object combining information required for an operation.
2//! Typically tickets contain all information required for an operation, e.g. an iroh blob
3//! ticket would contain the hash of the data as well as information about how to reach the
4//! provider.
5
6use std::{collections::BTreeSet, net::SocketAddr};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{key::NodeId, relay_url::RelayUrl};
11
12mod node;
13
14pub use self::node::NodeTicket;
15
16/// A ticket is a serializable object combining information required for an operation.
17///
18/// Tickets support serialization to a string using base32 encoding. The kind of
19/// ticket will be prepended to the string to make it somewhat self describing.
20///
21/// Versioning is left to the implementer. Some kinds of tickets might need
22/// versioning, others might not.
23///
24/// The serialization format for converting the ticket from and to bytes is left
25/// to the implementer. We recommend using [postcard] for serialization.
26///
27/// [postcard]: https://docs.rs/postcard/latest/postcard/
28pub trait Ticket: Sized {
29    /// String prefix describing the kind of iroh ticket.
30    ///
31    /// This should be lower case ascii characters.
32    const KIND: &'static str;
33
34    /// Serialize to bytes used in the base32 string representation.
35    fn to_bytes(&self) -> Vec<u8>;
36
37    /// Deserialize from the base32 string representation bytes.
38    fn from_bytes(bytes: &[u8]) -> Result<Self, Error>;
39
40    /// Serialize to string.
41    fn serialize(&self) -> String {
42        let mut out = Self::KIND.to_string();
43        data_encoding::BASE32_NOPAD.encode_append(&self.to_bytes(), &mut out);
44        out.to_ascii_lowercase()
45    }
46
47    /// Deserialize from a string.
48    fn deserialize(str: &str) -> Result<Self, Error> {
49        let expected = Self::KIND;
50        let Some(rest) = str.strip_prefix(expected) else {
51            return Err(Error::Kind { expected });
52        };
53        let bytes = data_encoding::BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())?;
54        let ticket = Self::from_bytes(&bytes)?;
55        Ok(ticket)
56    }
57}
58
59/// An error deserializing an iroh ticket.
60#[derive(Debug, thiserror::Error)]
61pub enum Error {
62    /// Found a ticket of with the wrong prefix, indicating the wrong kind.
63    #[error("wrong prefix, expected {expected}")]
64    Kind {
65        /// The expected prefix.
66        expected: &'static str,
67    },
68    /// This looks like a ticket, but postcard deserialization failed.
69    #[error("deserialization failed: {_0}")]
70    Postcard(#[from] postcard::Error),
71    /// This looks like a ticket, but base32 decoding failed.
72    #[error("decoding failed: {_0}")]
73    Encoding(#[from] data_encoding::DecodeError),
74    /// Verification of the deserialized bytes failed.
75    #[error("verification failed: {_0}")]
76    Verify(&'static str),
77}
78
79#[derive(Serialize, Deserialize)]
80struct Variant0NodeAddr {
81    node_id: NodeId,
82    info: Variant0AddrInfo,
83}
84
85#[derive(Serialize, Deserialize)]
86struct Variant0AddrInfo {
87    relay_url: Option<RelayUrl>,
88    direct_addresses: BTreeSet<SocketAddr>,
89}