use crate::{
crypto::sha256::Sha256,
netdb::{routing_table::RoutingTable, types::Key},
primitives::RouterId,
router::context::RouterContext,
runtime::Runtime,
};
use hashbrown::HashSet;
use alloc::{collections::BTreeMap, string::String, vec::Vec};
const LOOKUP_REPLY_NOT_RECEIVED_SCORE: isize = -5isize;
const LOOKUP_SUCCEEDED_SCORE: isize = 10isize;
const LOOKUP_FAILED_SCORE: isize = 1isize;
pub struct Dht<R: Runtime> {
routing_table: RoutingTable,
router_ctx: RouterContext<R>,
}
impl<R: Runtime> Dht<R> {
pub(super) fn new(
local_router_id: RouterId,
routers: HashSet<RouterId>,
router_ctx: RouterContext<R>,
floodfill: bool,
) -> Self {
let routing_table = if floodfill {
let mut routing_table = RoutingTable::new(Key::from(local_router_id));
let reader = router_ctx.profile_storage().reader();
let mut scores = routers
.into_iter()
.map(|router_id| match reader.profile(&router_id) {
Some(profile) => (router_id, profile.floodfill_score()),
None => (router_id, 0isize),
})
.collect::<Vec<_>>();
scores.sort_by(|(_, a), (_, b)| b.cmp(a));
scores.into_iter().for_each(|(router_id, _)| {
routing_table.add_router(router_id);
});
routing_table
} else {
let mut routing_table = RoutingTable::new(Key::from(local_router_id));
routers.into_iter().for_each(|router_id| {
routing_table.add_router(router_id);
});
routing_table
};
Self {
routing_table,
router_ctx,
}
}
fn utc_date(unix_timestamp: u64) -> String {
const DAYS_PER_YEAR: u64 = 365;
const DAYS_PER_4_YEARS: u64 = 4 * DAYS_PER_YEAR + 1;
const DAYS_PER_100_YEARS: u64 = 25 * DAYS_PER_4_YEARS - 1;
const DAYS_PER_400_YEARS: u64 = 4 * DAYS_PER_100_YEARS + 1;
const SECONDS_PER_DAY: u64 = 86_400;
const CUMUL_NORMAL: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
const CUMUL_LEAP: [u16; 12] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
let mut days = unix_timestamp / SECONDS_PER_DAY;
let mut year = 1970;
{
let num_400_years = days / DAYS_PER_400_YEARS;
year += num_400_years * 400;
days -= num_400_years * DAYS_PER_400_YEARS;
}
while days >= DAYS_PER_100_YEARS {
if (year % 400) / 100 == 3 {
break;
}
year += 100;
days -= DAYS_PER_100_YEARS;
}
while days >= DAYS_PER_4_YEARS {
if (year % 100) / 4 == 24 && (year % 400) / 100 != 3 {
break;
}
year += 4;
days -= DAYS_PER_4_YEARS;
}
let is_leap_year = |year: u64| -> bool {
(year.is_multiple_of(4) && !year.is_multiple_of(100)) || year.is_multiple_of(400)
};
loop {
let days_in_year = if is_leap_year(year) {
DAYS_PER_YEAR + 1
} else {
DAYS_PER_YEAR
};
if days < days_in_year {
break;
}
days -= days_in_year;
year += 1;
}
let cumul_days = if is_leap_year(year) {
&CUMUL_LEAP
} else {
&CUMUL_NORMAL
};
let month = (0..11).find(|&m| cumul_days[m + 1] > days as u16).unwrap_or(11);
alloc::format!(
"{:04}{:02}{:02}",
year,
month + 1,
days as u16 - cumul_days[month] + 1
)
}
pub(super) fn add_router(&mut self, router_id: RouterId) {
self.routing_table.add_router(router_id);
}
pub(super) fn register_lookup_success(&mut self, router_id: &RouterId) {
self.routing_table.adjust_score(router_id, LOOKUP_SUCCEEDED_SCORE);
self.router_ctx.profile_storage().database_lookup_success(router_id);
}
pub(super) fn register_lookup_failure(&mut self, router_id: &RouterId) {
self.routing_table.adjust_score(router_id, LOOKUP_FAILED_SCORE);
self.router_ctx.profile_storage().database_lookup_failure(router_id);
}
pub(super) fn register_lookup_timeout(&mut self, router_id: &RouterId) {
self.routing_table.adjust_score(router_id, LOOKUP_REPLY_NOT_RECEIVED_SCORE);
self.router_ctx.profile_storage().database_lookup_no_response(router_id);
}
pub(super) fn closest(
&mut self,
key: impl AsRef<[u8]>,
limit: usize,
) -> impl Iterator<Item = RouterId> + '_ {
let target = Key::from(
Sha256::new()
.update(&key)
.update(Self::utc_date(R::time_since_epoch().as_secs()).as_str())
.finalize(),
);
self.routing_table.closest(target, limit)
}
pub(super) fn closest_with_ignore<'a>(
&'a self,
key: impl AsRef<[u8]>,
limit: usize,
ignore: &'a HashSet<RouterId>,
) -> impl Iterator<Item = RouterId> + 'a {
let target = Key::from(
Sha256::new()
.update(&key)
.update(Self::utc_date(R::time_since_epoch().as_secs()).as_str())
.finalize(),
);
self.routing_table.closest_with_ignore(target, limit, ignore)
}
pub fn get_closest(key: impl AsRef<[u8]>, routers: &HashSet<RouterId>) -> Option<RouterId> {
if routers.is_empty() {
return None;
}
let target = Key::from(
Sha256::new()
.update(&key)
.update(Self::utc_date(R::time_since_epoch().as_secs()).as_str())
.finalize(),
);
let mut routers = routers
.iter()
.map(|router_id| {
let distance = target.distance(&Key::from(router_id.clone()));
(distance, router_id)
})
.collect::<BTreeMap<_, _>>();
routers.pop_first().map(|(_, router_id)| router_id.clone())
}
pub fn get_n_closest(
key: impl AsRef<[u8]>,
routers: &HashSet<RouterId>,
limit: usize,
) -> HashSet<RouterId> {
if routers.is_empty() {
return HashSet::new();
}
let target = Key::from(
Sha256::new()
.update(&key)
.update(Self::utc_date(R::time_since_epoch().as_secs()).as_str())
.finalize(),
);
let routers = routers
.iter()
.map(|router_id| {
let distance = target.distance(&Key::from(router_id.clone()));
(distance, router_id)
})
.collect::<BTreeMap<_, _>>();
routers
.into_iter()
.take(limit)
.map(|(_, router_id)| router_id.clone())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
crypto::{base32_decode, base64_decode},
events::EventManager,
primitives::RouterInfoBuilder,
profile::ProfileStorage,
runtime::mock::MockRuntime,
};
use bytes::Bytes;
#[tokio::test]
async fn lookup() {
let routers = HashSet::from_iter([
RouterId::from(&base64_decode("4wlqrFG46mv7ujZi18KwEf9uJz2MgOIebdMMxDHsh~0=").unwrap()),
RouterId::from(&base64_decode("909NkRdvZz4UnYKrEdkcPR0-nyjgIyXfcltdus3KbvI=").unwrap()),
RouterId::from(&base64_decode("9FpLdQFPuslwleztm87UKZm9voRCErVkC5tQIzTIveE=").unwrap()),
RouterId::from(&base64_decode("A27bo5gy~L8C9dMPm24YNVkQkkPUqr3jz74-zkHjr4E=").unwrap()),
RouterId::from(&base64_decode("AFHNc~4qEeDC0pX35aKVZXlJYejqXlwIavJkb51X7Hw=").unwrap()),
RouterId::from(&base64_decode("gOcHmAy4wEnAwiB5MGdVZUFMSd8R4xVXShLlLMK33ak=").unwrap()),
RouterId::from(&base64_decode("-HrTE27w0UKFx98GgdKhZDtNzFAaqquctMvuUjwqKnw=").unwrap()),
RouterId::from(&base64_decode("JT58CgCdJNk9Y9PiykRx7wz9cZIEI7a68sDNV8MBsLk=").unwrap()),
RouterId::from(&base64_decode("o6~ANVCIdIiUomPN-GxHscI6KetllgsecHFFWNIzFYM=").unwrap()),
RouterId::from(&base64_decode("o6Ax4-AapSSlKGTzDW8R6qUldj7sg9AszSYlvTxApwI=").unwrap()),
RouterId::from(&base64_decode("O8Ih-eljywJJ-mQpn4Al1y~GQKU25nvlPRzktoeRnPQ=").unwrap()),
RouterId::from(&base64_decode("o8qvvGZroVu1Jlo-9ICTamn5t8XlnNq49oJ2QywLVUQ=").unwrap()),
RouterId::from(&base64_decode("QRbIdWrPvAp58Qf~asFdm1s-oz9NDmwimu61pndVpNY=").unwrap()),
RouterId::from(&base64_decode("QVGqliH7Pdye7P7UAtM~fKQIfjKOzKbMVvhdKVSGlQ8=").unwrap()),
RouterId::from(&base64_decode("x4Q9dpbvHfyUuIhK9xDiy1XL9lvrpe9Kmmy9Gg~wFeQ=").unwrap()),
]);
let (router_info, static_key, signing_key) = RouterInfoBuilder::default().build();
let (_event_mgr, _event_subscriber, event_handle) =
EventManager::new(None, MockRuntime::register_metrics(vec![], None));
let mut dht = Dht::<MockRuntime>::new(
router_info.identity.id(),
routers,
RouterContext::new(
MockRuntime::register_metrics(vec![], None),
ProfileStorage::new(&[], &[]),
router_info.identity.id(),
Bytes::from(router_info.serialize(&signing_key)),
static_key,
signing_key,
2u8,
event_handle.clone(),
),
true,
);
let key = Bytes::from(
base32_decode("shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa").unwrap(),
);
let target = Key::from(Sha256::new().update(&key).update("20250105").finalize());
let closest = dht.routing_table.closest(target, 3usize).collect::<Vec<_>>();
assert_eq!(
closest[0],
RouterId::from(&base64_decode("o6~ANVCIdIiUomPN-GxHscI6KetllgsecHFFWNIzFYM=").unwrap())
);
assert_eq!(
closest[1],
RouterId::from(&base64_decode("o6Ax4-AapSSlKGTzDW8R6qUldj7sg9AszSYlvTxApwI=").unwrap())
);
assert_eq!(
closest[2],
RouterId::from(&base64_decode("o8qvvGZroVu1Jlo-9ICTamn5t8XlnNq49oJ2QywLVUQ=").unwrap())
);
}
#[tokio::test]
async fn utc_date() {
type D = Dht<MockRuntime>;
assert_eq!("19700101", D::utc_date(0));
assert_eq!("19700101", D::utc_date(1));
assert_eq!("19700101", D::utc_date(59));
assert_eq!("19700101", D::utc_date(86399));
assert_eq!("19700102", D::utc_date(86400));
assert_eq!("5845540512231109", D::utc_date(u64::MAX));
assert_eq!("2922770265961204", D::utc_date(i64::MAX as u64));
assert_eq!("20290724", D::utc_date(1879574397));
assert_eq!("20250531", D::utc_date(1748652952));
assert_eq!("19801231", D::utc_date(347081248));
assert_eq!("20231130", D::utc_date(1701388799));
assert_eq!("20231201", D::utc_date(1701388800));
assert_eq!("20241212", D::utc_date(1733998283));
assert_eq!("20240228", D::utc_date(1709164799));
assert_eq!("20240229", D::utc_date(1709164800));
assert_eq!("20000228", D::utc_date(951782399));
assert_eq!("20000229", D::utc_date(951782400));
assert_eq!("20230228", D::utc_date(1677628799));
assert_eq!("20230301", D::utc_date(1677628800));
assert_eq!("20991231", D::utc_date(4102444799));
assert_eq!("21000101", D::utc_date(4102444800));
}
}