tor_linkspec/
ls.rs

1//! Link specifier objects
2//!
3//! (These are in a separate crate, since they get used both by
4//! directory code and protocol code.)
5
6use std::net::{IpAddr, SocketAddr};
7
8use caret::caret_int;
9use derive_deftly::Deftly;
10use tor_bytes::{EncodeResult, Readable, Reader, Result, Writeable, Writer};
11use tor_llcrypto::pk::ed25519;
12use tor_llcrypto::pk::rsa::RsaIdentity;
13use tor_memquota::derive_deftly_template_HasMemoryCost;
14
15use crate::RelayId;
16
17/// A piece of information about a relay and how to connect to it.
18#[non_exhaustive]
19#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
20#[derive_deftly(HasMemoryCost)]
21pub enum LinkSpec {
22    /// The TCP address of an OR Port for a relay
23    OrPort(IpAddr, u16),
24    /// The RSA identity fingerprint of the relay
25    RsaId(RsaIdentity),
26    /// The Ed25519 identity of the relay
27    Ed25519Id(ed25519::Ed25519Identity),
28    /// A link specifier that we didn't recognize
29    Unrecognized(LinkSpecType, Vec<u8>),
30}
31
32caret_int! {
33    /// A numeric identifier for the type of a [`LinkSpec`].
34    #[derive(Deftly)]
35    #[derive_deftly(HasMemoryCost)]
36    pub struct LinkSpecType(u8) {
37        /// Indicates an IPv4 ORPORT link specifier.
38        ORPORT_V4 = 0,
39        /// Indicates an IPv6 ORPORT link specifier.
40        ORPORT_V6 = 1,
41        /// Indicates an RSA ID fingerprint link specifier
42        RSAID = 2,
43        /// Indicates an Ed25519 link specifier
44        ED25519ID = 3,
45    }
46}
47
48impl Readable for LinkSpec {
49    fn take_from(b: &mut Reader<'_>) -> Result<Self> {
50        let lstype = b.take_u8()?.into();
51        b.read_nested_u8len(|r| Self::from_type_and_body(lstype, r))
52    }
53}
54impl Writeable for LinkSpec {
55    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
56        w.write_u8(self.lstype().into());
57        {
58            let mut inner = w.write_nested_u8len();
59            self.encode_body(&mut *inner)?;
60            inner.finish()?;
61        }
62        Ok(())
63    }
64}
65
66impl From<&SocketAddr> for LinkSpec {
67    fn from(sa: &SocketAddr) -> Self {
68        LinkSpec::OrPort(sa.ip(), sa.port())
69    }
70}
71impl From<SocketAddr> for LinkSpec {
72    fn from(sa: SocketAddr) -> Self {
73        (&sa).into()
74    }
75}
76impl From<RsaIdentity> for LinkSpec {
77    fn from(id: RsaIdentity) -> Self {
78        LinkSpec::RsaId(id)
79    }
80}
81impl From<ed25519::Ed25519Identity> for LinkSpec {
82    fn from(id: ed25519::Ed25519Identity) -> Self {
83        LinkSpec::Ed25519Id(id)
84    }
85}
86impl From<ed25519::PublicKey> for LinkSpec {
87    fn from(pk: ed25519::PublicKey) -> Self {
88        LinkSpec::Ed25519Id(pk.into())
89    }
90}
91impl From<RelayId> for LinkSpec {
92    fn from(id: RelayId) -> Self {
93        match id {
94            RelayId::Ed25519(key) => LinkSpec::Ed25519Id(key),
95            RelayId::Rsa(key) => LinkSpec::RsaId(key),
96        }
97    }
98}
99
100impl LinkSpec {
101    /// Helper: return the position in the list of identifiers
102    /// in which a given linkspec should occur.
103    fn sort_pos(&self) -> u8 {
104        use LinkSpec::*;
105        match self {
106            OrPort(IpAddr::V4(_), _) => 0,
107            RsaId(_) => 1,
108            Ed25519Id(_) => 2,
109            OrPort(IpAddr::V6(_), _) => 3,
110            Unrecognized(n, _) => (*n).into(),
111        }
112    }
113
114    /// Sort a slice of LinkSpec based on the order in which they should
115    /// appear in an EXTEND cell.
116    pub fn sort_by_type(lst: &mut [Self]) {
117        lst.sort_by_key(LinkSpec::sort_pos);
118    }
119
120    /// Try to create a LinkSpec of encoded type `lstype`, taking its body from a
121    /// given reader `r`.
122    ///
123    /// Does not check whether `r` is exhausted at the end of the operation or not.
124    fn from_type_and_body(lstype: LinkSpecType, r: &mut Reader<'_>) -> Result<Self> {
125        use LinkSpecType as LST;
126        Ok(match lstype {
127            LST::ORPORT_V4 => {
128                let addr = IpAddr::V4(r.extract()?);
129                LinkSpec::OrPort(addr, r.take_u16()?)
130            }
131            LST::ORPORT_V6 => {
132                let addr = IpAddr::V6(r.extract()?);
133                LinkSpec::OrPort(addr, r.take_u16()?)
134            }
135            LST::RSAID => LinkSpec::RsaId(r.extract()?),
136            LST::ED25519ID => LinkSpec::Ed25519Id(r.extract()?),
137            _ => LinkSpec::Unrecognized(lstype, r.take_rest().into()),
138        })
139    }
140
141    /// Return the command for this linkspec.
142    fn lstype(&self) -> LinkSpecType {
143        use LinkSpecType as LST;
144        match self {
145            LinkSpec::OrPort(IpAddr::V4(_), _) => LST::ORPORT_V4,
146            LinkSpec::OrPort(IpAddr::V6(_), _) => LST::ORPORT_V6,
147
148            LinkSpec::RsaId(_) => LST::RSAID,
149            LinkSpec::Ed25519Id(_) => LST::ED25519ID,
150            LinkSpec::Unrecognized(lstype, _) => *lstype,
151        }
152    }
153
154    /// Try to encode the body of this linkspec onto a given writer.
155    fn encode_body<W: Writer + ?Sized>(&self, w: &mut W) -> EncodeResult<()> {
156        use LinkSpec::*;
157        match self {
158            OrPort(IpAddr::V4(v4), port) => {
159                w.write(v4)?;
160                w.write_u16(*port);
161            }
162            OrPort(IpAddr::V6(v6), port) => {
163                w.write(v6)?;
164                w.write_u16(*port);
165            }
166            RsaId(r) => {
167                w.write(r)?;
168            }
169            Ed25519Id(e) => {
170                w.write(e)?;
171            }
172            Unrecognized(_, vec) => {
173                w.write_all(&vec[..]);
174            }
175        }
176        Ok(())
177    }
178
179    /// Return an encoded version of this link specifier.
180    pub fn encode(&self) -> EncodeResult<EncodedLinkSpec> {
181        let tp = self.lstype();
182        let mut body = Vec::new();
183        self.encode_body(&mut body)?;
184        Ok(EncodedLinkSpec::new(tp, body))
185    }
186}
187
188/// An unparsed piece of information about a relay and how to connect to it.
189///
190/// Unlike [`LinkSpec`], this can't be used directly; we only pass it on.
191#[derive(Debug, Clone, PartialEq, Eq, Deftly)]
192#[derive_deftly(HasMemoryCost)]
193pub struct EncodedLinkSpec {
194    /// The link specifier type.
195    lstype: LinkSpecType,
196    /// The body of the link speciier.
197    body: Vec<u8>,
198}
199
200impl EncodedLinkSpec {
201    /// Create a new `EncodedLinkSpec`.
202    pub fn new(lstype: LinkSpecType, body: impl Into<Vec<u8>>) -> Self {
203        EncodedLinkSpec {
204            lstype,
205            body: body.into(),
206        }
207    }
208
209    /// Try to parse this into a `LinkSpec`, if it appears well-formed.
210    pub fn parse(&self) -> Result<LinkSpec> {
211        let mut r = Reader::from_slice(&self.body[..]);
212        let ls = LinkSpec::from_type_and_body(self.lstype, &mut r)?;
213        r.should_be_exhausted()?;
214        Ok(ls)
215    }
216
217    /// Return the link spec type for this `EncodedLinkSpec`.
218    pub fn lstype(&self) -> LinkSpecType {
219        self.lstype
220    }
221}
222
223impl Readable for EncodedLinkSpec {
224    fn take_from(r: &mut Reader<'_>) -> Result<Self> {
225        let lstype = r.take_u8()?.into();
226        r.read_nested_u8len(|r| {
227            let body = r.take_rest().to_vec();
228            Ok(Self { lstype, body })
229        })
230    }
231}
232impl Writeable for EncodedLinkSpec {
233    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
234        w.write_u8(self.lstype.into());
235        let mut nested = w.write_nested_u8len();
236        nested.write_all(&self.body[..]);
237        nested.finish()
238    }
239}
240
241#[cfg(test)]
242mod test {
243    // @@ begin test lint list maintained by maint/add_warning @@
244    #![allow(clippy::bool_assert_comparison)]
245    #![allow(clippy::clone_on_copy)]
246    #![allow(clippy::dbg_macro)]
247    #![allow(clippy::mixed_attributes_style)]
248    #![allow(clippy::print_stderr)]
249    #![allow(clippy::print_stdout)]
250    #![allow(clippy::single_char_pattern)]
251    #![allow(clippy::unwrap_used)]
252    #![allow(clippy::unchecked_duration_subtraction)]
253    #![allow(clippy::useless_vec)]
254    #![allow(clippy::needless_pass_by_value)]
255    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
256    use super::*;
257    use hex_literal::hex;
258    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
259    use tor_bytes::{Reader, Writer};
260
261    #[test]
262    fn test_parse_enc() {
263        fn t(b: &[u8], val: &LinkSpec) {
264            let mut r = Reader::from_slice_for_test(b);
265            let got: LinkSpec = r.extract().unwrap();
266            assert_eq!(r.remaining(), 0);
267            assert_eq!(&got, val);
268            let mut v = Vec::new();
269            v.write(val).expect("Encoding failure");
270            assert_eq!(&v[..], b);
271        }
272
273        t(
274            &hex!("00 06 01020304 0050"),
275            &LinkSpec::OrPort(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 80),
276        );
277        t(
278            &hex!("01 12 0001 0002 0003 0004 0005 0006 0007 0008 01bb"),
279            &LinkSpec::OrPort(IpAddr::V6(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8)), 443),
280        );
281        t(
282            &[
283                2, 20, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 33, 33, 33, 33,
284                33, 33, 33, 33,
285            ],
286            &LinkSpec::RsaId(RsaIdentity::from_bytes(b"hello world!!!!!!!!!").unwrap()),
287        );
288        let key = ed25519::PublicKey::from_bytes(&hex!(
289            "B440EEDB32D5C89EF21D6B16BE85A658774CE5992355737411678EE1041BDFBA"
290        ))
291        .unwrap()
292        .into();
293        t(
294            &hex!("03 20 B440EEDB32D5C89EF21D6B16BE85A658774CE5992355737411678EE1041BDFBA"),
295            &LinkSpec::Ed25519Id(key),
296        );
297
298        t(
299            &[77, 7, 115, 116, 114, 97, 110, 103, 101],
300            &LinkSpec::Unrecognized(77.into(), (&b"strange"[..]).into()),
301        );
302    }
303
304    #[test]
305    fn test_parse_bad() {
306        use tor_bytes::Error;
307
308        fn t(b: &[u8]) -> Error {
309            let mut r = Reader::from_slice_for_test(b);
310            let got: Result<LinkSpec> = r.extract();
311            got.err().unwrap()
312        }
313
314        assert_eq!(t(&hex!("00 03")), Error::new_incomplete_for_test(3));
315        assert_eq!(
316            t(&hex!("00 06 01020304")),
317            Error::new_incomplete_for_test(2)
318        );
319        assert_eq!(t(&hex!("99 07 010203")), Error::new_incomplete_for_test(4));
320    }
321
322    #[test]
323    fn test_unparsed() {
324        fn t(b: &[u8], val: &EncodedLinkSpec) {
325            let mut r = Reader::from_slice_for_test(b);
326            let got: EncodedLinkSpec = r.extract().unwrap();
327            assert_eq!(r.remaining(), 0);
328            assert_eq!(&got, val);
329            let mut v = Vec::new();
330            v.write(val).expect("Encoding failure");
331            assert_eq!(&v[..], b);
332        }
333
334        // Note that these are not valid linkspecs, but we accept them here.
335        t(
336            &hex!("00 00"),
337            &EncodedLinkSpec {
338                lstype: 0.into(),
339                body: vec![],
340            },
341        );
342        t(
343            &hex!("00 03 010203"),
344            &EncodedLinkSpec {
345                lstype: 0.into(),
346                body: vec![1, 2, 3],
347            },
348        );
349
350        t(
351            &hex!("99 10 000102030405060708090a0b0c0d0e0f"),
352            &EncodedLinkSpec {
353                lstype: 0x99.into(),
354                body: (0..=15).collect(),
355            },
356        );
357    }
358
359    #[test]
360    fn test_unparsed_bad() {
361        use tor_bytes::Error;
362        fn t(b: &[u8]) -> Error {
363            let mut r = Reader::from_slice_for_test(b);
364            let got: Result<EncodedLinkSpec> = r.extract();
365            got.err().unwrap()
366        }
367
368        assert_eq!(t(&hex!("00")), Error::new_incomplete_for_test(1));
369        assert_eq!(t(&hex!("00 04 010203")), Error::new_incomplete_for_test(1));
370        assert_eq!(
371            t(&hex!("00 05 01020304")),
372            Error::new_incomplete_for_test(1)
373        );
374    }
375}