//! Link specifier objects //! //! (These are in a separate crate, since they get used both by //! directory code and protocol code.) use std::net::{IpAddr, SocketAddr}; use caret::caret_int; use tor_bytes::{EncodeResult, Readable, Reader, Result, Writeable, Writer}; use tor_llcrypto::pk::ed25519; use tor_llcrypto::pk::rsa::RsaIdentity; use crate::RelayId; /// A piece of information about a relay and how to connect to it. #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum LinkSpec { /// The TCP address of an OR Port for a relay OrPort(IpAddr, u16), /// The RSA identity fingerprint of the relay RsaId(RsaIdentity), /// The Ed25519 identity of the relay Ed25519Id(ed25519::Ed25519Identity), /// A link specifier that we didn't recognize Unrecognized(LinkSpecType, Vec), } caret_int! { /// A numeric identifier for the type of a [`LinkSpec`]. pub struct LinkSpecType(u8) { /// Indicates an IPv4 ORPORT link specifier. ORPORT_V4 = 0, /// Indicates an IPv6 ORPORT link specifier. ORPORT_V6 = 1, /// Indicates an RSA ID fingerprint link specifier RSAID = 2, /// Indicates an Ed25519 link specifier ED25519ID = 3, } } impl Readable for LinkSpec { fn take_from(r: &mut Reader<'_>) -> Result { let lstype = r.take_u8()?.into(); r.read_nested_u8len(|r| Self::from_type_and_body(lstype, r)) } } impl Writeable for LinkSpec { fn write_onto(&self, w: &mut B) -> EncodeResult<()> { w.write_u8(self.lstype().into()); { let mut inner = w.write_nested_u8len(); self.encode_body(&mut *inner)?; inner.finish()?; } Ok(()) } } impl From<&SocketAddr> for LinkSpec { fn from(sa: &SocketAddr) -> Self { LinkSpec::OrPort(sa.ip(), sa.port()) } } impl From for LinkSpec { fn from(sa: SocketAddr) -> Self { (&sa).into() } } impl From for LinkSpec { fn from(id: RsaIdentity) -> Self { LinkSpec::RsaId(id) } } impl From for LinkSpec { fn from(id: ed25519::Ed25519Identity) -> Self { LinkSpec::Ed25519Id(id) } } impl From for LinkSpec { fn from(pk: ed25519::PublicKey) -> Self { LinkSpec::Ed25519Id(pk.into()) } } impl From for LinkSpec { fn from(id: RelayId) -> Self { match id { RelayId::Ed25519(key) => LinkSpec::Ed25519Id(key), RelayId::Rsa(key) => LinkSpec::RsaId(key), } } } impl LinkSpec { /// Helper: return the position in the list of identifiers /// in which a given linkspec should occur. fn sort_pos(&self) -> u8 { use LinkSpec::*; match self { OrPort(IpAddr::V4(_), _) => 0, RsaId(_) => 1, Ed25519Id(_) => 2, OrPort(IpAddr::V6(_), _) => 3, Unrecognized(n, _) => (*n).into(), } } /// Sort a slice of LinkSpec based on the order in which they should /// appear in an EXTEND cell. pub fn sort_by_type(lst: &mut [Self]) { lst.sort_by_key(LinkSpec::sort_pos); } /// Try to create a LinkSpec of encoded type `lstype`, taking its body from a /// given reader `r`. /// /// Does not check whether `r` is exhausted at the end of the operation or not. fn from_type_and_body(lstype: LinkSpecType, r: &mut Reader<'_>) -> Result { use LinkSpecType as LST; Ok(match lstype { LST::ORPORT_V4 => { let addr = IpAddr::V4(r.extract()?); LinkSpec::OrPort(addr, r.take_u16()?) } LST::ORPORT_V6 => { let addr = IpAddr::V6(r.extract()?); LinkSpec::OrPort(addr, r.take_u16()?) } LST::RSAID => LinkSpec::RsaId(r.extract()?), LST::ED25519ID => LinkSpec::Ed25519Id(r.extract()?), _ => LinkSpec::Unrecognized(lstype, r.take_rest().into()), }) } /// Return the command for this linkspec. fn lstype(&self) -> LinkSpecType { use LinkSpecType as LST; match self { LinkSpec::OrPort(IpAddr::V4(_), _) => LST::ORPORT_V4, LinkSpec::OrPort(IpAddr::V6(_), _) => LST::ORPORT_V6, LinkSpec::RsaId(_) => LST::RSAID, LinkSpec::Ed25519Id(_) => LST::ED25519ID, LinkSpec::Unrecognized(lstype, _) => *lstype, } } /// Try to encode the body of this linkspec onto a given writer. fn encode_body(&self, w: &mut W) -> EncodeResult<()> { use LinkSpec::*; match self { OrPort(IpAddr::V4(v4), port) => { w.write(v4)?; w.write_u16(*port); } OrPort(IpAddr::V6(v6), port) => { w.write(v6)?; w.write_u16(*port); } RsaId(r) => { w.write(r)?; } Ed25519Id(e) => { w.write(e)?; } Unrecognized(_, vec) => { w.write_all(&vec[..]); } } Ok(()) } /// Return an encoded version of this link specifier. pub fn encode(&self) -> EncodeResult { let tp = self.lstype(); let mut body = Vec::new(); self.encode_body(&mut body)?; Ok(EncodedLinkSpec::new(tp, body)) } } /// An unparsed piece of information about a relay and how to connect to it. /// /// Unlike [`LinkSpec`], this can't be used directly; we only pass it on. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncodedLinkSpec { /// The link specifier type. lstype: LinkSpecType, /// The body of the link speciier. body: Vec, } impl EncodedLinkSpec { /// Create a new `EncodedLinkSpec`. pub fn new(lstype: LinkSpecType, body: impl Into>) -> Self { EncodedLinkSpec { lstype, body: body.into(), } } /// Try to parse this into a `LinkSpec`, if it appears well-formed. pub fn parse(&self) -> Result { let mut r = Reader::from_slice(&self.body[..]); let ls = LinkSpec::from_type_and_body(self.lstype, &mut r)?; r.should_be_exhausted()?; Ok(ls) } /// Return the link spec type for this `EncodedLinkSpec`. pub fn lstype(&self) -> LinkSpecType { self.lstype } } impl Readable for EncodedLinkSpec { fn take_from(r: &mut Reader<'_>) -> Result { let lstype = r.take_u8()?.into(); r.read_nested_u8len(|r| { let body = r.take_rest().to_vec(); Ok(Self { lstype, body }) }) } } impl Writeable for EncodedLinkSpec { fn write_onto(&self, w: &mut B) -> EncodeResult<()> { w.write_u8(self.lstype.into()); let mut nested = w.write_nested_u8len(); nested.write_all(&self.body[..]); nested.finish() } } #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_duration_subtraction)] #![allow(clippy::useless_vec)] #![allow(clippy::needless_pass_by_value)] //! use super::*; use hex_literal::hex; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tor_bytes::{Reader, Writer}; #[test] fn test_parse_enc() { fn t(b: &[u8], val: &LinkSpec) { let mut r = Reader::from_slice(b); let got: LinkSpec = r.extract().unwrap(); assert_eq!(r.remaining(), 0); assert_eq!(&got, val); let mut v = Vec::new(); v.write(val).expect("Encoding failure"); assert_eq!(&v[..], b); } t( &hex!("00 06 01020304 0050"), &LinkSpec::OrPort(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 80), ); t( &hex!("01 12 0001 0002 0003 0004 0005 0006 0007 0008 01bb"), &LinkSpec::OrPort(IpAddr::V6(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8)), 443), ); t( &[ 2, 20, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 33, 33, 33, 33, 33, 33, 33, 33, ], &LinkSpec::RsaId(RsaIdentity::from_bytes(b"hello world!!!!!!!!!").unwrap()), ); let key = ed25519::PublicKey::from_bytes(&hex!( "B440EEDB32D5C89EF21D6B16BE85A658774CE5992355737411678EE1041BDFBA" )) .unwrap() .into(); t( &hex!("03 20 B440EEDB32D5C89EF21D6B16BE85A658774CE5992355737411678EE1041BDFBA"), &LinkSpec::Ed25519Id(key), ); t( &[77, 7, 115, 116, 114, 97, 110, 103, 101], &LinkSpec::Unrecognized(77.into(), (&b"strange"[..]).into()), ); } #[test] fn test_parse_bad() { use tor_bytes::Error; fn t(b: &[u8]) -> Error { let mut r = Reader::from_slice(b); let got: Result = r.extract(); got.err().unwrap() } assert!(matches!(t(&hex!("00 03")), Error::Truncated)); assert!(matches!(t(&hex!("00 06 01020304")), Error::Truncated)); assert!(matches!(t(&hex!("99 07 010203")), Error::Truncated)); } #[test] fn test_unparsed() { fn t(b: &[u8], val: &EncodedLinkSpec) { let mut r = Reader::from_slice(b); let got: EncodedLinkSpec = r.extract().unwrap(); assert_eq!(r.remaining(), 0); assert_eq!(&got, val); let mut v = Vec::new(); v.write(val).expect("Encoding failure"); assert_eq!(&v[..], b); } // Note that these are not valid linkspecs, but we accept them here. t( &hex!("00 00"), &EncodedLinkSpec { lstype: 0.into(), body: vec![], }, ); t( &hex!("00 03 010203"), &EncodedLinkSpec { lstype: 0.into(), body: vec![1, 2, 3], }, ); t( &hex!("99 10 000102030405060708090a0b0c0d0e0f"), &EncodedLinkSpec { lstype: 0x99.into(), body: (0..=15).collect(), }, ); } #[test] fn test_unparsed_bad() { use tor_bytes::Error; fn t(b: &[u8]) -> Error { let mut r = Reader::from_slice(b); let got: Result = r.extract(); got.err().unwrap() } assert!(matches!(t(&hex!("00")), Error::Truncated)); assert!(matches!(t(&hex!("00 04 010203")), Error::Truncated)); assert!(matches!(t(&hex!("00 05 01020304")), Error::Truncated)); } }