iroh_base/ticket/
node.rs

1//! Tickets for nodes.
2
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7use super::{Variant0AddrInfo, Variant0NodeAddr};
8use crate::{
9    node_addr::NodeAddr,
10    ticket::{self, ParseError, Ticket},
11};
12
13/// A token containing information for establishing a connection to a node.
14///
15/// Contains
16/// - The [`NodeId`] of the node to connect to (a 32-byte ed25519 public key).
17/// - If used, the ['RelayUrl`] of on which the node can be reached.
18/// - Any *direct addresses* on which the node might be reachable.
19///
20/// This allows establishing a connection to the node in most circumstances where it is
21/// possible to do so.
22///
23/// This [`NodeTicket`] is a single item which can be easily serialized and deserialized and
24/// implements the [`Ticket`] trait.  The [`Display`] and [`FromStr`] traits can also be
25/// used to round-trip the ticket to string.
26///
27/// [`NodeId`]: crate::key::NodeId
28/// [`Display`]: std::fmt::Display
29/// [`FromStr`]: std::str::FromStr
30/// ['RelayUrl`]: crate::relay_url::RelayUrl
31#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
32#[display("{}", Ticket::serialize(self))]
33pub struct NodeTicket {
34    node: NodeAddr,
35}
36
37/// Wire format for [`NodeTicket`].
38#[derive(Serialize, Deserialize)]
39enum TicketWireFormat {
40    Variant0(Variant0NodeTicket),
41}
42
43// Legacy
44#[derive(Serialize, Deserialize)]
45struct Variant0NodeTicket {
46    node: Variant0NodeAddr,
47}
48
49impl Ticket for NodeTicket {
50    const KIND: &'static str = "node";
51
52    fn to_bytes(&self) -> Vec<u8> {
53        let data = TicketWireFormat::Variant0(Variant0NodeTicket {
54            node: Variant0NodeAddr {
55                node_id: self.node.node_id,
56                info: Variant0AddrInfo {
57                    relay_url: self.node.relay_url.clone(),
58                    direct_addresses: self.node.direct_addresses.clone(),
59                },
60            },
61        });
62        postcard::to_stdvec(&data).expect("postcard serialization failed")
63    }
64
65    fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
66        let res: TicketWireFormat = postcard::from_bytes(bytes)?;
67        let TicketWireFormat::Variant0(Variant0NodeTicket { node }) = res;
68        Ok(Self {
69            node: NodeAddr {
70                node_id: node.node_id,
71                relay_url: node.info.relay_url,
72                direct_addresses: node.info.direct_addresses,
73            },
74        })
75    }
76}
77
78impl FromStr for NodeTicket {
79    type Err = ParseError;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        ticket::Ticket::deserialize(s)
83    }
84}
85
86impl NodeTicket {
87    /// Creates a new ticket.
88    pub fn new(node: NodeAddr) -> Self {
89        Self { node }
90    }
91
92    /// The [`NodeAddr`] of the provider for this ticket.
93    pub fn node_addr(&self) -> &NodeAddr {
94        &self.node
95    }
96}
97
98impl From<NodeAddr> for NodeTicket {
99    /// Creates a ticket from given addressing info.
100    fn from(addr: NodeAddr) -> Self {
101        Self { node: addr }
102    }
103}
104
105impl From<NodeTicket> for NodeAddr {
106    /// Returns the addressing info from given ticket.
107    fn from(ticket: NodeTicket) -> Self {
108        ticket.node
109    }
110}
111
112impl Serialize for NodeTicket {
113    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
114        if serializer.is_human_readable() {
115            serializer.serialize_str(&self.to_string())
116        } else {
117            let NodeTicket { node } = self;
118            (node).serialize(serializer)
119        }
120    }
121}
122
123impl<'de> Deserialize<'de> for NodeTicket {
124    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
125        if deserializer.is_human_readable() {
126            let s = String::deserialize(deserializer)?;
127            Self::from_str(&s).map_err(serde::de::Error::custom)
128        } else {
129            let peer = Deserialize::deserialize(deserializer)?;
130            Ok(Self::new(peer))
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use std::net::{Ipv4Addr, SocketAddr};
138
139    use data_encoding::HEXLOWER;
140    use rand::SeedableRng;
141
142    use super::*;
143    use crate::key::{PublicKey, SecretKey};
144
145    fn make_ticket() -> NodeTicket {
146        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
147        let peer = SecretKey::generate(&mut rng).public();
148        let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 1234));
149        let relay_url = None;
150        NodeTicket {
151            node: NodeAddr::from_parts(peer, relay_url, [addr]),
152        }
153    }
154
155    #[test]
156    fn test_ticket_postcard() {
157        let ticket = make_ticket();
158        let bytes = postcard::to_stdvec(&ticket).unwrap();
159        let ticket2: NodeTicket = postcard::from_bytes(&bytes).unwrap();
160        assert_eq!(ticket2, ticket);
161    }
162
163    #[test]
164    fn test_ticket_json() {
165        let ticket = make_ticket();
166        let json = serde_json::to_string(&ticket).unwrap();
167        let ticket2: NodeTicket = serde_json::from_str(&json).unwrap();
168        assert_eq!(ticket2, ticket);
169    }
170
171    #[test]
172    fn test_ticket_base32() {
173        let node_id =
174            PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
175                .unwrap();
176
177        let ticket = NodeTicket {
178            node: NodeAddr::from_parts(
179                node_id,
180                Some("http://derp.me./".parse().unwrap()),
181                ["127.0.0.1:1024".parse().unwrap()],
182            ),
183        };
184        let base32 = data_encoding::BASE32_NOPAD
185            .decode(
186                ticket
187                    .to_string()
188                    .strip_prefix("node")
189                    .unwrap()
190                    .to_ascii_uppercase()
191                    .as_bytes(),
192            )
193            .unwrap();
194        let expected = [
195            // variant
196            "00",
197            // node id, 32 bytes, see above
198            "ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6",
199            // relay url present
200            "01",
201            // relay url, 16 bytes, see above
202            "10",
203            "687474703a2f2f646572702e6d652e2f",
204            // one direct address
205            "01",
206            // ipv4
207            "00",
208            // address, see above
209            "7f0000018008",
210        ];
211        let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap();
212        assert_eq!(base32, expected);
213    }
214}