use std::collections::HashMap;
use std::fmt::Debug;
use derive_more::{AsRef, From, Into};
use digest::Digest;
use typed_index_collections::TiVec;
use tor_basic_utils::impl_debug_hex;
use tor_hscrypto::{pk::HsBlindId, time::TimePeriod};
use tor_llcrypto::d::Sha3_256;
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use crate::hsdir_params::HsDirParams;
use crate::{NetDir, RouterStatusIdx};
#[derive(Copy, Clone, Eq, Hash, PartialEq, Ord, PartialOrd, AsRef)]
pub(crate) struct HsDirIndex(#[as_ref] [u8; 32]);
impl_debug_hex! { HsDirIndex .0 }
#[derive(Debug, From, Into, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub(crate) struct HsDirPos(usize);
#[derive(Clone, Debug)]
pub(crate) struct HsDirRing {
params: HsDirParams,
ring: TiVec<HsDirPos, (HsDirIndex, RouterStatusIdx)>,
}
pub(crate) fn relay_hsdir_index(
kp_relayid_ed: &Ed25519Identity,
params: &HsDirParams,
) -> HsDirIndex {
let mut h = Sha3_256::default();
h.update(b"node-idx");
h.update(kp_relayid_ed.as_bytes());
h.update(params.shared_rand.as_ref());
h.update(params.time_period.interval_num().to_be_bytes());
h.update(u64::from(params.time_period.length().as_minutes()).to_be_bytes());
HsDirIndex(h.finalize().into())
}
pub(crate) fn service_hsdir_index(
kp_hs_blind_id: &HsBlindId,
replica: u8,
params: &HsDirParams,
) -> HsDirIndex {
let mut h = Sha3_256::new();
h.update(b"store-at-idx");
h.update(kp_hs_blind_id.as_ref());
h.update(u64::from(replica).to_be_bytes());
h.update(u64::from(params.time_period.length().as_minutes()).to_be_bytes());
h.update(params.time_period.interval_num().to_be_bytes());
HsDirIndex(h.finalize().into())
}
impl HsDirRing {
pub(crate) fn empty_from_params(params: HsDirParams) -> Self {
Self {
params,
ring: TiVec::new(),
}
}
pub(crate) fn compute(
new_params: HsDirParams,
this_netdir: &NetDir,
prev_netdir: Option<&NetDir>,
) -> Self {
let reuse_index_values: HashMap<&Ed25519Identity, &HsDirIndex> = (|| {
let prev_netdir = prev_netdir?;
let prev_ring = prev_netdir
.hsdir_rings
.iter()
.find(|prev_ring| prev_ring.params == new_params)?;
let reuse_index_values = prev_ring
.ring
.iter()
.filter_map(|(hsdir_index, rsidx)| {
Some((prev_netdir.md_by_rsidx(*rsidx)?.ed25519_id(), hsdir_index))
})
.collect();
Some(reuse_index_values)
})()
.unwrap_or_default();
let mut new_ring: TiVec<_, _> = this_netdir
.all_hsdirs()
.map(|(rsidx, relay)| {
let ed_id = relay.md.ed25519_id();
let hsdir_index = reuse_index_values
.get(ed_id)
.cloned()
.cloned()
.unwrap_or_else(|| relay_hsdir_index(ed_id, &new_params));
(hsdir_index, rsidx)
})
.collect();
new_ring.sort_by_key(|(hsdir_index, _rsidx)| *hsdir_index);
HsDirRing {
ring: new_ring,
params: new_params,
}
}
pub(crate) fn params(&self) -> &HsDirParams {
&self.params
}
fn find_pos(&self, hsdir_index: HsDirIndex) -> HsDirPos {
self.ring
.binary_search_by_key(&hsdir_index, |(hsdir_index, _rs_idx)| *hsdir_index)
.unwrap_or_else(|pos| pos)
}
pub(crate) fn ring_items_at(
&self,
hsdir_index: HsDirIndex,
spread: usize,
f: impl FnMut(&&(HsDirIndex, RouterStatusIdx)) -> bool,
) -> impl Iterator<Item = &(HsDirIndex, RouterStatusIdx)> {
let pos = self.find_pos(hsdir_index);
self.ring[pos..]
.iter()
.chain(&self.ring[..pos])
.filter(f)
.take(spread)
}
pub(crate) fn time_period(&self) -> TimePeriod {
self.params.time_period
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use std::time::Duration;
#[test]
fn test_hs_indexes() {
let time_period = TimePeriod::new(
Duration::from_secs(24 * 3600),
humantime::parse_rfc3339("1970-02-13T01:00:00Z").unwrap(),
Duration::from_secs(12 * 3600),
)
.unwrap();
assert_eq!(time_period.interval_num(), 42);
let shared_rand = [0x43; 32].into();
let params = HsDirParams {
time_period,
shared_rand,
srv_lifespan: time_period.range().unwrap(),
};
{
let kp_hs_blind_id = [0x42; 32].into();
let replica = 1;
let got = service_hsdir_index(&kp_hs_blind_id, replica, ¶ms);
assert_eq!(
hex::encode(got.as_ref()),
"37e5cbbd56a22823714f18f1623ece5983a0d64c78495a8cfab854245e5f9a8a",
);
}
{
let kp_relayid_ed = [0x42; 32].into();
let got = relay_hsdir_index(&kp_relayid_ed, ¶ms);
assert_eq!(
hex::encode(got.as_ref()),
"db475361014a09965e7e5e4d4a25b8f8d4b8f16cb1d8a7e95eed50249cc1a2d5",
);
}
}
}