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, 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#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
31#[display("{}", Ticket::serialize(self))]
32pub struct NodeTicket {
33    node: NodeAddr,
34}
35
36/// Wire format for [`NodeTicket`].
37#[derive(Serialize, Deserialize)]
38enum TicketWireFormat {
39    Variant0(Variant0NodeTicket),
40}
41
42// Legacy
43#[derive(Serialize, Deserialize)]
44struct Variant0NodeTicket {
45    node: Variant0NodeAddr,
46}
47
48impl Ticket for NodeTicket {
49    const KIND: &'static str = "node";
50
51    fn to_bytes(&self) -> Vec<u8> {
52        let data = TicketWireFormat::Variant0(Variant0NodeTicket {
53            node: Variant0NodeAddr {
54                node_id: self.node.node_id,
55                info: Variant0AddrInfo {
56                    relay_url: self.node.relay_url.clone(),
57                    direct_addresses: self.node.direct_addresses.clone(),
58                },
59            },
60        });
61        postcard::to_stdvec(&data).expect("postcard serialization failed")
62    }
63
64    fn from_bytes(bytes: &[u8]) -> Result<Self, ticket::Error> {
65        let res: TicketWireFormat = postcard::from_bytes(bytes).map_err(ticket::Error::Postcard)?;
66        let TicketWireFormat::Variant0(Variant0NodeTicket { node }) = res;
67        Ok(Self {
68            node: NodeAddr {
69                node_id: node.node_id,
70                relay_url: node.info.relay_url,
71                direct_addresses: node.info.direct_addresses,
72            },
73        })
74    }
75}
76
77impl FromStr for NodeTicket {
78    type Err = ticket::Error;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        ticket::Ticket::deserialize(s)
82    }
83}
84
85impl NodeTicket {
86    /// Creates a new ticket.
87    pub fn new(node: NodeAddr) -> Self {
88        Self { node }
89    }
90
91    /// The [`NodeAddr`] of the provider for this ticket.
92    pub fn node_addr(&self) -> &NodeAddr {
93        &self.node
94    }
95}
96
97impl From<NodeAddr> for NodeTicket {
98    /// Creates a ticket from given addressing info.
99    fn from(addr: NodeAddr) -> Self {
100        Self { node: addr }
101    }
102}
103
104impl From<NodeTicket> for NodeAddr {
105    /// Returns the addressing info from given ticket.
106    fn from(ticket: NodeTicket) -> Self {
107        ticket.node
108    }
109}
110
111impl Serialize for NodeTicket {
112    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
113        if serializer.is_human_readable() {
114            serializer.serialize_str(&self.to_string())
115        } else {
116            let NodeTicket { node } = self;
117            (node).serialize(serializer)
118        }
119    }
120}
121
122impl<'de> Deserialize<'de> for NodeTicket {
123    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
124        if deserializer.is_human_readable() {
125            let s = String::deserialize(deserializer)?;
126            Self::from_str(&s).map_err(serde::de::Error::custom)
127        } else {
128            let peer = Deserialize::deserialize(deserializer)?;
129            Ok(Self::new(peer))
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use std::net::{Ipv4Addr, SocketAddr};
137
138    use data_encoding::HEXLOWER;
139
140    use super::*;
141    use crate::key::{PublicKey, SecretKey};
142
143    fn make_ticket() -> NodeTicket {
144        let peer = SecretKey::generate(&mut rand::thread_rng()).public();
145        let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 1234));
146        let relay_url = None;
147        NodeTicket {
148            node: NodeAddr::from_parts(peer, relay_url, [addr]),
149        }
150    }
151
152    #[test]
153    fn test_ticket_postcard() {
154        let ticket = make_ticket();
155        let bytes = postcard::to_stdvec(&ticket).unwrap();
156        let ticket2: NodeTicket = postcard::from_bytes(&bytes).unwrap();
157        assert_eq!(ticket2, ticket);
158    }
159
160    #[test]
161    fn test_ticket_json() {
162        let ticket = make_ticket();
163        let json = serde_json::to_string(&ticket).unwrap();
164        let ticket2: NodeTicket = serde_json::from_str(&json).unwrap();
165        assert_eq!(ticket2, ticket);
166    }
167
168    #[test]
169    fn test_ticket_base32() {
170        let node_id =
171            PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6")
172                .unwrap();
173
174        let ticket = NodeTicket {
175            node: NodeAddr::from_parts(
176                node_id,
177                Some("http://derp.me./".parse().unwrap()),
178                ["127.0.0.1:1024".parse().unwrap()],
179            ),
180        };
181        let base32 = data_encoding::BASE32_NOPAD
182            .decode(
183                ticket
184                    .to_string()
185                    .strip_prefix("node")
186                    .unwrap()
187                    .to_ascii_uppercase()
188                    .as_bytes(),
189            )
190            .unwrap();
191        let expected = [
192            // variant
193            "00",
194            // node id, 32 bytes, see above
195            "ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6",
196            // relay url present
197            "01",
198            // relay url, 16 bytes, see above
199            "10",
200            "687474703a2f2f646572702e6d652e2f",
201            // one direct address
202            "01",
203            // ipv4
204            "00",
205            // address, see above
206            "7f0000018008",
207        ];
208        let expected = HEXLOWER.decode(expected.concat().as_bytes()).unwrap();
209        assert_eq!(base32, expected);
210    }
211}