use super::removal::{RemovedRoute, RouteRemovalCause};
use super::RoutingTable;
use crate::engine::InstantMillis;
use crate::interfaces::AttachedInterfaces;
use crate::routing::announce::stored::{
AnnounceAppData, AnnounceIdHistory, AnnounceRecord, AnnounceRecordTable,
};
use crate::routing::announce::AnnounceArrival;
use crate::routing::route_expiry::RouteExpiryIndex;
use crate::routing::routes::{RouteEntry, RouteResponsiveness, RouteTable};
use crate::routing::warmth::RouteWarmth;
use crate::storage::TablePushError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Eviction {
Evicted,
NothingToEvict,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DropCause {
RoutingTableFull,
PayloadArenaFull,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpsertRouteOutcome {
Inserted,
Updated,
Dropped(DropCause),
}
impl<R, A, H, D, I> RoutingTable<R, A, H, D, I>
where
R: RouteTable,
A: AnnounceRecordTable,
H: AnnounceIdHistory,
D: AnnounceAppData,
I: RouteExpiryIndex,
{
pub fn upsert_route(
&mut self,
arrival: &AnnounceArrival<'_>,
interfaces: AttachedInterfaces<'_>,
on_removed: &mut impl FnMut(RemovedRoute),
) -> UpsertRouteOutcome {
self.upsert_route_with_warmth(arrival, interfaces, &(), on_removed)
}
pub fn upsert_route_with_warmth(
&mut self,
arrival: &AnnounceArrival<'_>,
interfaces: AttachedInterfaces<'_>,
warmth: &dyn RouteWarmth,
on_removed: &mut impl FnMut(RemovedRoute),
) -> UpsertRouteOutcome {
match self.index_of(&arrival.announce.destination) {
None => {
if self.routes.len() >= self.destination_capacity() {
self.cull_expired_routes_with_warmth(
arrival.arrived_at,
interfaces,
warmth,
on_removed,
);
if self.routes.len() >= self.destination_capacity() {
self.evict_route_nearest_expiry(interfaces, warmth, on_removed);
}
}
self.insert_new_route(arrival, interfaces, warmth, on_removed)
}
Some(i) => self.refresh_existing_route(i, arrival, interfaces, warmth),
}
}
pub(super) fn destination_capacity(&self) -> usize {
self.routes.capacity().min(self.announce_records.capacity())
}
fn evict_route_nearest_expiry(
&mut self,
interfaces: AttachedInterfaces<'_>,
warmth: &dyn RouteWarmth,
on_removed: &mut impl FnMut(RemovedRoute),
) -> Eviction {
let Some(i) = (0..self.routes.len())
.min_by_key(|&i| self.expiry_of_with_warmth(i, interfaces, warmth))
else {
return Eviction::NothingToEvict;
};
on_removed(self.route_removal_at(i, RouteRemovalCause::Evicted));
self.remove_route_at(i);
Eviction::Evicted
}
fn insert_new_route(
&mut self,
arrival: &AnnounceArrival<'_>,
interfaces: AttachedInterfaces<'_>,
warmth: &dyn RouteWarmth,
on_removed: &mut impl FnMut(RemovedRoute),
) -> UpsertRouteOutcome {
let &AnnounceArrival {
ref announce,
hops,
arrived_at,
receiving_interface,
next_hop,
..
} = arrival;
if self.routes.len() >= self.destination_capacity() {
return UpsertRouteOutcome::Dropped(DropCause::RoutingTableFull);
}
let handle = match self.announce_app_data.insert(announce.app_data) {
Ok(handle) => handle,
Err(_) => {
if self.evict_route_nearest_expiry(interfaces, warmth, on_removed)
== Eviction::NothingToEvict
{
return UpsertRouteOutcome::Dropped(DropCause::PayloadArenaFull);
}
match self.announce_app_data.insert(announce.app_data) {
Ok(handle) => handle,
Err(_) => return UpsertRouteOutcome::Dropped(DropCause::PayloadArenaFull),
}
}
};
let route_entry = RouteEntry {
hops,
learned_at: arrived_at,
last_relayed_at: InstantMillis(0),
responsiveness: RouteResponsiveness::Unknown,
receiving_interface,
next_hop,
};
let announce_entry = AnnounceRecord {
public_keys: announce.public_keys,
dotted_name_hash: announce.dotted_name_hash,
announce_id: announce.announce_id,
ratchet: announce.ratchet,
signature: announce.signature,
maybe_app_data_handle: Some(handle),
};
let routes_slot = match self.routes.push(announce.destination, route_entry) {
Ok(i) => i,
Err(TablePushError::TableFull) => {
self.announce_app_data.free(handle);
return UpsertRouteOutcome::Dropped(DropCause::RoutingTableFull);
}
};
if self.announce_records.push(announce_entry).is_err() {
self.announce_app_data.free(handle);
self.routes.swap_remove(routes_slot, self.routes.len() - 1);
return UpsertRouteOutcome::Dropped(DropCause::RoutingTableFull);
}
self.announce_id_history
.remember(routes_slot, announce.announce_id);
let expiry = self.expiry_of_with_warmth(routes_slot, interfaces, warmth);
self.route_expiries.insert(routes_slot, expiry);
UpsertRouteOutcome::Inserted
}
fn refresh_existing_route(
&mut self,
i: usize,
arrival: &AnnounceArrival<'_>,
interfaces: AttachedInterfaces<'_>,
warmth: &dyn RouteWarmth,
) -> UpsertRouteOutcome {
let &AnnounceArrival {
ref announce,
hops,
arrived_at,
receiving_interface,
next_hop,
..
} = arrival;
let Some(handle) = self.announce_records.app_data_handles()[i] else {
debug_assert!(false, "existing destination missing app_data handle");
return UpsertRouteOutcome::Dropped(DropCause::PayloadArenaFull);
};
if self
.announce_app_data
.replace(handle, announce.app_data)
.is_err()
{
return UpsertRouteOutcome::Dropped(DropCause::PayloadArenaFull);
}
self.routes.set_row(
i,
RouteEntry {
hops,
learned_at: arrived_at,
last_relayed_at: InstantMillis(0),
responsiveness: RouteResponsiveness::Unknown,
receiving_interface,
next_hop,
},
);
self.announce_records.set_row(
i,
AnnounceRecord {
public_keys: announce.public_keys,
dotted_name_hash: announce.dotted_name_hash,
announce_id: announce.announce_id,
ratchet: announce.ratchet,
signature: announce.signature,
maybe_app_data_handle: Some(handle),
},
);
self.announce_id_history.remember(i, announce.announce_id);
let expiry = self.expiry_of_with_warmth(i, interfaces, warmth);
self.route_expiries.update(i, expiry);
UpsertRouteOutcome::Updated
}
}