Skip to main content

iroh_tickets/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use n0_error::{e, stack_error};
4
5pub mod endpoint;
6
7/// A ticket is a serializable object combining information required for an operation.
8///
9/// Tickets are convertible to and from a byte representation via [`encode_bytes`] /
10/// [`decode_bytes`], and to and from a canonical string form (the lowercase [`KIND`]
11/// prefix followed by base32 of the bytes) via [`encode_string`] / [`decode_string`].
12/// Implementers only need to provide [`KIND`], [`encode_bytes`], and [`decode_bytes`].
13///
14/// Versioning is left to the implementer. Some kinds of tickets might need
15/// versioning, others might not.
16///
17/// The serialization format for converting the ticket from and to bytes is left
18/// to the implementer. We recommend using [postcard] for serialization.
19///
20/// [`KIND`]: Ticket::KIND
21/// [`encode_bytes`]: Ticket::encode_bytes
22/// [`decode_bytes`]: Ticket::decode_bytes
23/// [`encode_string`]: Ticket::encode_string
24/// [`decode_string`]: Ticket::decode_string
25/// [postcard]: https://docs.rs/postcard/latest/postcard/
26pub trait Ticket: Sized {
27    /// String prefix describing the kind of iroh ticket.
28    ///
29    /// This should be lower case ascii characters.
30    const KIND: &'static str;
31
32    /// Encode the ticket into its byte representation.
33    fn encode_bytes(&self) -> Vec<u8>;
34
35    /// Decode a ticket from its byte representation.
36    fn decode_bytes(bytes: &[u8]) -> Result<Self, ParseError>;
37
38    /// Encode the ticket into its canonical string form.
39    ///
40    /// The default implementation produces the lowercase [`KIND`](Self::KIND) prefix
41    /// followed by base32 (no padding) of [`encode_bytes`](Self::encode_bytes).
42    /// Implementers may override this to use a different string encoding, in which
43    /// case [`decode_string`](Self::decode_string) must be overridden to match.
44    fn encode_string(&self) -> String {
45        let mut out = Self::KIND.to_string();
46        data_encoding::BASE32_NOPAD.encode_append(&self.encode_bytes(), &mut out);
47        out.make_ascii_lowercase();
48        out
49    }
50
51    /// Decode a ticket from its canonical string form.
52    ///
53    /// The default implementation expects the lowercase [`KIND`](Self::KIND) prefix
54    /// followed by base32 (no padding) of the bytes accepted by
55    /// [`decode_bytes`](Self::decode_bytes). Implementers that override
56    /// [`encode_string`](Self::encode_string) must override this to match.
57    fn decode_string(s: &str) -> Result<Self, ParseError> {
58        let expected = Self::KIND;
59        let Some(rest) = s.strip_prefix(expected) else {
60            return Err(e!(ParseError::Kind { expected }));
61        };
62        let bytes = data_encoding::BASE32_NOPAD.decode(rest.to_ascii_uppercase().as_bytes())?;
63        Self::decode_bytes(&bytes)
64    }
65}
66
67/// An error deserializing an iroh ticket.
68#[stack_error(derive, add_meta, from_sources)]
69#[allow(missing_docs)]
70#[non_exhaustive]
71pub enum ParseError {
72    /// Found a ticket with the wrong prefix, indicating the wrong kind.
73    #[error("wrong prefix, expected {expected}")]
74    Kind {
75        /// The expected prefix.
76        expected: &'static str,
77    },
78    /// This looks like a ticket, but postcard deserialization failed.
79    #[error(transparent)]
80    Postcard {
81        #[error(source, std_err)]
82        source: postcard::Error,
83    },
84    /// This looks like a ticket, but base32 decoding failed.
85    #[error(transparent)]
86    Encoding {
87        #[error(source, std_err)]
88        source: data_encoding::DecodeError,
89    },
90    /// Verification of the deserialized bytes failed.
91    #[error("verification failed: {message}")]
92    Verify { message: &'static str },
93}
94
95impl ParseError {
96    /// Returns a [`ParseError`] that indicates the given ticket has the wrong
97    /// prefix.
98    ///
99    /// Indicate the expected prefix.
100    pub fn wrong_prefix(expected: &'static str) -> Self {
101        e!(ParseError::Kind { expected })
102    }
103
104    /// Return a `ParseError` variant that indicates verification of the
105    /// deserialized bytes failed.
106    pub fn verification_failed(message: &'static str) -> Self {
107        e!(ParseError::Verify { message })
108    }
109}