Skip to main content

tor_netdoc/types/
family.rs

1//! Implements the relay 'family' type.
2//!
3//! Families are opt-in lists of relays with the same operators,
4//! used to avoid building insecure circuits.
5
6use std::sync::Arc;
7
8use crate::types::misc::LongIdent;
9use crate::{Error, NetdocErrorKind, NormalItemArgument, Pos, Result};
10use base64ct::Encoding;
11use derive_deftly::Deftly;
12use tor_basic_utils::intern::InternCache;
13use tor_llcrypto::pk::ed25519::{ED25519_ID_LEN, Ed25519Identity};
14use tor_llcrypto::pk::rsa::RsaIdentity;
15
16/// Information about a relay family.
17///
18/// Tor relays may declare that they belong to the same family, to
19/// indicate that they are controlled by the same party or parties,
20/// and as such should not be used in the same circuit. Two relays
21/// belong to the same family if and only if each one lists the other
22/// as belonging to its family.
23///
24/// NOTE: when parsing, this type always discards incorrectly-formatted
25/// entries, including entries that are only nicknames.
26///
27/// TODO: This type probably belongs in a different crate.
28#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Deftly)]
29#[derive_deftly(ItemValueParseable)]
30pub struct RelayFamily(Vec<LongIdent>);
31
32/// Cache of RelayFamily objects, for saving memory.
33//
34/// This only holds weak references to the policy objects, so we don't
35/// need to worry about running out of space because of stale entries.
36static FAMILY_CACHE: InternCache<RelayFamily> = InternCache::new();
37
38impl RelayFamily {
39    /// Return a new empty RelayFamily.
40    pub fn new() -> Self {
41        RelayFamily::default()
42    }
43
44    /// Add `rsa_id` to this family.
45    pub fn push(&mut self, rsa_id: RsaIdentity) {
46        self.0.push(rsa_id.into());
47    }
48
49    /// Convert this family to a standard format (with all IDs sorted and de-duplicated).
50    fn normalize(&mut self) {
51        self.0.sort_by_key(|v| v.0);
52        self.0.dedup();
53    }
54
55    /// Consume this family, and return a new canonical interned representation
56    /// of the family.
57    pub fn intern(mut self) -> Arc<Self> {
58        self.normalize();
59        FAMILY_CACHE.intern(self)
60    }
61
62    /// Does this family include the given relay?
63    pub fn contains(&self, rsa_id: &RsaIdentity) -> bool {
64        self.0.contains(&LongIdent(*rsa_id))
65    }
66
67    /// Return an iterator over the RSA identity keys listed in this
68    /// family.
69    pub fn members(&self) -> impl Iterator<Item = &RsaIdentity> {
70        self.0.iter().map(|id| &id.0)
71    }
72
73    /// Return true if this family has no members.
74    pub fn is_empty(&self) -> bool {
75        self.0.is_empty()
76    }
77}
78
79impl std::str::FromStr for RelayFamily {
80    type Err = Error;
81    fn from_str(s: &str) -> Result<Self> {
82        let v: Result<Vec<LongIdent>> = s
83            .split(crate::parse::tokenize::is_sp)
84            .map(|e| e.parse::<LongIdent>())
85            .filter(Result::is_ok)
86            .collect();
87        Ok(RelayFamily(v?))
88    }
89}
90
91/// An identifier representing a relay family.
92///
93/// In the ["happy families"](https://spec.torproject.org/proposals/321) scheme,
94/// microdescriptors will no longer have to contain a list of relay members,
95/// but will instead contain these identifiers.
96///
97/// If two relays have a `RelayFamilyId` in common, they belong to the same family.
98#[derive(Clone, Debug, Eq, PartialEq)]
99#[non_exhaustive]
100pub enum RelayFamilyId {
101    /// An identifier derived from an Ed25519 relay family key. (`KP_familyid_ed`)
102    Ed25519(Ed25519Identity),
103    /// An unrecognized string.
104    Unrecognized(String),
105}
106
107/// Prefix for a RelayFamilyId derived from an ed25519 `KP_familyid_ed`.
108const ED25519_ID_PREFIX: &str = "ed25519:";
109
110impl std::str::FromStr for RelayFamilyId {
111    type Err = Error;
112
113    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
114        let mut buf = [0_u8; ED25519_ID_LEN];
115        if let Some(s) = s.strip_prefix(ED25519_ID_PREFIX) {
116            if let Ok(decoded) = base64ct::Base64Unpadded::decode(s, &mut buf) {
117                if let Some(ed_id) = Ed25519Identity::from_bytes(decoded) {
118                    return Ok(RelayFamilyId::Ed25519(ed_id));
119                }
120            }
121            return Err(NetdocErrorKind::BadArgument
122                .with_msg("Invalid ed25519 family ID")
123                .at_pos(Pos::at(s)));
124        }
125        Ok(RelayFamilyId::Unrecognized(s.to_string()))
126    }
127}
128
129impl std::fmt::Display for RelayFamilyId {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            RelayFamilyId::Ed25519(id) => write!(f, "{}{}", ED25519_ID_PREFIX, id),
133            RelayFamilyId::Unrecognized(s) => write!(f, "{}", s),
134        }
135    }
136}
137
138impl PartialOrd for RelayFamilyId {
139    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
140        Some(Ord::cmp(self, other))
141    }
142}
143impl Ord for RelayFamilyId {
144    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
145        // We sort RelayFamilyId values by string representation.
146        // This is not super-efficient, but we don't need to do it very often.
147        Ord::cmp(&self.to_string(), &other.to_string())
148    }
149}
150
151impl NormalItemArgument for RelayFamilyId {}
152
153/// A list of multiple [`RelayFamilyId`] entries as found in microdescs.
154#[derive(Clone, Debug, Default, Eq, PartialEq, Deftly, derive_more::AsRef)]
155#[derive_deftly(ItemValueParseable)]
156pub struct RelayFamilyIds(
157    // TODO DIRMIRROR: Replace with BTreeSet at one point.
158    // TODO could/should this be a type alias instead?
159    Vec<RelayFamilyId>,
160);
161
162impl RelayFamilyIds {
163    /// Return a new empty [`RelayFamilyIds`].
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    /// Push `family_id` onto this instance.
169    pub fn push(&mut self, family_id: RelayFamilyId) {
170        self.0.push(family_id);
171    }
172
173    /// Sort entries ascending by their [`RelayFamilyId`].
174    pub fn sort(&mut self) {
175        self.0.sort();
176    }
177
178    /// Deduplicates entries.
179    pub fn dedup(&mut self) {
180        self.0.dedup();
181    }
182}
183
184impl FromIterator<RelayFamilyId> for RelayFamilyIds {
185    fn from_iter<T: IntoIterator<Item = RelayFamilyId>>(iter: T) -> Self {
186        Self(iter.into_iter().collect())
187    }
188}
189
190#[cfg(test)]
191mod test {
192    // @@ begin test lint list maintained by maint/add_warning @@
193    #![allow(clippy::bool_assert_comparison)]
194    #![allow(clippy::clone_on_copy)]
195    #![allow(clippy::dbg_macro)]
196    #![allow(clippy::mixed_attributes_style)]
197    #![allow(clippy::print_stderr)]
198    #![allow(clippy::print_stdout)]
199    #![allow(clippy::single_char_pattern)]
200    #![allow(clippy::unwrap_used)]
201    #![allow(clippy::unchecked_time_subtraction)]
202    #![allow(clippy::useless_vec)]
203    #![allow(clippy::needless_pass_by_value)]
204    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
205    use std::str::FromStr;
206
207    use super::*;
208    use crate::Result;
209    #[test]
210    fn family() -> Result<()> {
211        let f = "nickname1 nickname2 $ffffffffffffffffffffffffffffffffffffffff=foo eeeeeeeeeeeeeeeeeeeEEEeeeeeeeeeeeeeeeeee ddddddddddddddddddddddddddddddddd  $cccccccccccccccccccccccccccccccccccccccc~blarg ".parse::<RelayFamily>()?;
212        let v = vec![
213            RsaIdentity::from_bytes(
214                &hex::decode("ffffffffffffffffffffffffffffffffffffffff").unwrap()[..],
215            )
216            .unwrap(),
217            RsaIdentity::from_bytes(
218                &hex::decode("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap()[..],
219            )
220            .unwrap(),
221            RsaIdentity::from_bytes(
222                &hex::decode("cccccccccccccccccccccccccccccccccccccccc").unwrap()[..],
223            )
224            .unwrap(),
225        ];
226        assert_eq!(f.members().cloned().collect::<Vec<_>>(), v);
227        Ok(())
228    }
229
230    #[test]
231    fn test_contains() -> Result<()> {
232        let family =
233            "ffffffffffffffffffffffffffffffffffffffff eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
234                .parse::<RelayFamily>()?;
235        let in_family = RsaIdentity::from_bytes(
236            &hex::decode("ffffffffffffffffffffffffffffffffffffffff").unwrap()[..],
237        )
238        .unwrap();
239        let not_in_family = RsaIdentity::from_bytes(
240            &hex::decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()[..],
241        )
242        .unwrap();
243        assert!(family.contains(&in_family), "Relay not found in family");
244        assert!(
245            !family.contains(&not_in_family),
246            "Extra relay found in family"
247        );
248        Ok(())
249    }
250
251    #[test]
252    fn mutable() {
253        let mut family = RelayFamily::default();
254        let key = RsaIdentity::from_hex("ffffffffffffffffffffffffffffffffffffffff").unwrap();
255        assert!(!family.contains(&key));
256        family.push(key);
257        assert!(family.contains(&key));
258    }
259
260    #[test]
261    fn family_ids() {
262        let ed_str_rep = "ed25519:7sToQRuge1bU2hS0CG0ViMndc4m82JhO4B4kdrQey80";
263        let ed_id = RelayFamilyId::from_str(ed_str_rep).unwrap();
264        assert!(matches!(ed_id, RelayFamilyId::Ed25519(_)));
265        assert_eq!(ed_id.to_string().as_str(), ed_str_rep);
266
267        let other_str_rep = "hello-world";
268        let other_id = RelayFamilyId::from_str(other_str_rep).unwrap();
269        assert!(matches!(other_id, RelayFamilyId::Unrecognized(_)));
270        assert_eq!(other_id.to_string().as_str(), other_str_rep);
271
272        assert_eq!(ed_id, ed_id);
273        assert_ne!(ed_id, other_id);
274    }
275
276    #[test]
277    fn parse2() {
278        #[derive(Debug, PartialEq, Eq, derive_deftly::Deftly)]
279        #[derive_deftly(NetdocParseable)]
280        struct Wrapper {
281            family: RelayFamily,
282        }
283
284        const LINE: &str = "family $0000000000000000000000000000000000000000 $FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
285        let parsed =
286            crate::parse2::parse_netdoc::<Wrapper>(&crate::parse2::ParseInput::new(LINE, ""))
287                .unwrap();
288        assert_eq!(
289            parsed,
290            Wrapper {
291                family: RelayFamily(vec![
292                    RsaIdentity::from_hex("0000000000000000000000000000000000000000")
293                        .unwrap()
294                        .into(),
295                    RsaIdentity::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
296                        .unwrap()
297                        .into()
298                ])
299            }
300        );
301    }
302}