Introduce a RelayIdSet and use it in place of HashSet<RelayId>.

This makes lookups a little more efficient.  I do with that HashSet
just supported this use-case, but for now this seems our best option.
This commit is contained in:
Nick Mathewson 2022-08-04 17:41:27 -04:00
parent deaf8b657d
commit b56e1bf119
7 changed files with 167 additions and 20 deletions

View File

@ -7,7 +7,7 @@ use std::time::SystemTime;
use tor_basic_utils::iter::FilterCount;
use tor_error::{bad_api_usage, internal};
use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable};
use tor_linkspec::RelayId;
use tor_linkspec::RelayIdSet;
use tor_netdir::{NetDir, Relay, SubnetConfig, WeightRole};
use tor_rtcompat::Runtime;
@ -166,14 +166,17 @@ impl<'a> ExitPathBuilder<'a> {
b.kind(tor_guardmgr::GuardUsageKind::Data);
guardmgr.update_network(netdir); // possibly unnecessary.
if let Some(exit_relay) = chosen_exit {
let mut family = std::collections::HashSet::new();
family.insert(RelayId::from(*exit_relay.id()));
// TODO(nickm): Our way of building a family here is
// somewhat questionable. We're only adding the ed25519
// identities of the exit relay and its family to the
// RelayId set. That's fine for now, since we will only use
// relays at this point if they have a known Ed25519
// identity. But if in the future the ed25519 identity
// becomes optional, this will need to change.
let mut family = RelayIdSet::new();
family.insert(*exit_relay.id());
// TODO(nickm): See "limitations" note on `known_family_members`.
family.extend(
netdir
.known_family_members(exit_relay)
.map(|r| RelayId::from(*r.id())),
);
family.extend(netdir.known_family_members(exit_relay).map(|r| *r.id()));
b.restrictions()
.push(tor_guardmgr::GuardRestriction::AvoidAllIds(family));
}

View File

@ -1,3 +1,6 @@
BREAKING: Exposed objects implement the new versions of the linkspec traits
BREAKING: note_external_{success,failure} functions now take HasRelayIds
BREAKING: GuardRestriction now takes a RelayId or RelayIdSet. This doesn't
affect serde users, but anybody calling it directly will need to
change.

View File

@ -395,12 +395,7 @@ impl Guard {
match r {
GuardRestriction::AvoidId(avoid_id) => !self.id.0.has_identity(avoid_id.as_ref()),
GuardRestriction::AvoidAllIds(avoid_ids) => {
// TODO(nickm): This copies all of our IDs!
// We should use a contains method on a RelayIdSet or something.
self.id
.0
.identities()
.all(|id| !avoid_ids.contains(&id.to_owned()))
self.id.0.identities().all(|id| !avoid_ids.contains(id))
}
}
}

View File

@ -137,11 +137,11 @@ use educe::Educe;
use futures::channel::mpsc;
use futures::task::SpawnExt;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex, Weak};
use std::time::{Duration, Instant, SystemTime};
use tor_linkspec::RelayId;
use tor_linkspec::{RelayId, RelayIdSet};
use tor_netdir::NetDirProvider;
use tor_proto::ClockSkew;
use tracing::{debug, info, trace, warn};
@ -1414,9 +1414,7 @@ pub enum GuardRestriction {
/// Don't pick a guard with the provided identity.
AvoidId(RelayId),
/// Don't pick a guard with any of the provided Ed25519 identities.
//
// TODO(nickm): Switch this to a type that can actually work well with RelayId.
AvoidAllIds(HashSet<RelayId>),
AvoidAllIds(RelayIdSet),
}
#[cfg(test)]

View File

@ -11,6 +11,8 @@ use tor_llcrypto::pk::{
rsa::{RsaIdentity, RSA_ID_LEN},
};
pub(crate) mod set;
/// The type of a relay identity.
///
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Display, strum::EnumIter)]
@ -197,6 +199,17 @@ impl serde::Serialize for RelayId {
self.as_ref().serialize(serializer)
}
}
impl<'a> serde::Serialize for RelayIdRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// TODO(nickm): maybe encode this as bytes when dealing with
// non-human-readable formats.
self.to_string().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for RelayId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where

View File

@ -0,0 +1,135 @@
//! Implement a set of RelayId.
use std::collections::HashSet;
use serde::de::Visitor;
use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
use crate::{RelayId, RelayIdRef};
/// A set of relay identities, backed by `HashSet`.
///
/// # Note
///
/// I'd rather use `HashSet` entirely, but that doesn't let us index by
/// RelayIdRef.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RelayIdSet {
/// The Ed25519 members of this set.
ed25519: HashSet<Ed25519Identity>,
/// The RSA members of this set.
rsa: HashSet<RsaIdentity>,
}
impl RelayIdSet {
/// Construct a new empty RelayIdSet.
pub fn new() -> Self {
Self::default()
}
/// Insert `key` into this set.
///
/// Return true if it was not already there.
pub fn insert<T: Into<RelayId>>(&mut self, key: T) -> bool {
let key: RelayId = key.into();
match key {
RelayId::Ed25519(key) => self.ed25519.insert(key),
RelayId::Rsa(key) => self.rsa.insert(key),
}
}
/// Remove `key` from the set.
///
/// Return true if `key` was present.
pub fn remove<'a, T: Into<RelayIdRef<'a>>>(&mut self, key: T) -> bool {
let key: RelayIdRef<'a> = key.into();
match key {
RelayIdRef::Ed25519(key) => self.ed25519.remove(key),
RelayIdRef::Rsa(key) => self.rsa.remove(key),
}
}
/// Return true if `key` is a member of this set.
pub fn contains<'a, T: Into<RelayIdRef<'a>>>(&self, key: T) -> bool {
let key: RelayIdRef<'a> = key.into();
match key {
RelayIdRef::Ed25519(key) => self.ed25519.contains(key),
RelayIdRef::Rsa(key) => self.rsa.contains(key),
}
}
/// Return an iterator over the members of this set.
///
/// The ordering of the iterator is undefined; do not rely on it.
pub fn iter(&self) -> impl Iterator<Item = RelayIdRef<'_>> {
self.ed25519
.iter()
.map(|id| id.into())
.chain(self.rsa.iter().map(|id| id.into()))
}
/// Return the number of keys in this set.
pub fn len(&self) -> usize {
self.ed25519.len() + self.rsa.len()
}
/// Return true if there are not keys in this set.
pub fn is_empty(&self) -> bool {
self.ed25519.is_empty() && self.rsa.is_empty()
}
}
impl<ID: Into<RelayId>> Extend<ID> for RelayIdSet {
fn extend<T: IntoIterator<Item = ID>>(&mut self, iter: T) {
for item in iter {
self.insert(item);
}
}
}
impl FromIterator<RelayId> for RelayIdSet {
fn from_iter<T: IntoIterator<Item = RelayId>>(iter: T) -> Self {
let mut set = RelayIdSet::new();
set.extend(iter);
set
}
}
impl serde::Serialize for RelayIdSet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_seq(self.iter())
}
}
impl<'de> serde::Deserialize<'de> for RelayIdSet {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
/// A serde visitor to deserialize a sequence of RelayIds into a
/// RelayIdSet.
struct IdSetVisitor;
impl<'de> Visitor<'de> for IdSetVisitor {
type Value = RelayIdSet;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a list of relay identities")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut set = RelayIdSet::new();
while let Some(key) = seq.next_element::<RelayId>()? {
set.insert(key);
}
Ok(set)
}
}
deserializer.deserialize_seq(IdSetVisitor)
}
}

View File

@ -77,7 +77,7 @@ mod ls;
mod owned;
mod traits;
pub use ids::{RelayId, RelayIdError, RelayIdRef, RelayIdType, RelayIdTypeIter};
pub use ids::{set::RelayIdSet, RelayId, RelayIdError, RelayIdRef, RelayIdType, RelayIdTypeIter};
pub use ls::LinkSpec;
pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds};
pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds};