use std::collections::HashMap;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LinkId(pub u64);
const NEIGHBOUR_ID_TAG: &str = "ntkd.NeighbourId";
impl LinkId {
#[must_use]
pub fn hooking(self) -> ntk_hooking::ArcId {
ntk_hooking::ArcId(self.0)
}
#[must_use]
pub fn identities(self) -> ntk_identities::ArcId {
ntk_identities::ArcId(self.0)
}
}
#[must_use]
pub fn encode_caller_id(id: ntk_neighborhood::NodeId) -> ntk_proto::v1::TypedValue {
ntk_proto::v1::TypedValue::new(NEIGHBOUR_ID_TAG, id.get().to_be_bytes().to_vec())
}
pub(crate) fn decode_caller_id(tv: &ntk_proto::v1::TypedValue) -> Option<ntk_neighborhood::NodeId> {
if tv.type_tag != NEIGHBOUR_ID_TAG {
return None;
}
let raw = i32::from_be_bytes(tv.payload.as_slice().try_into().ok()?);
ntk_neighborhood::NodeId::from_raw(raw).ok()
}
const IDENTITY_ID_TAG: &str = "ntkd.IdentityId";
#[cfg(test)]
#[must_use]
pub(crate) fn encode_identity_id(id: ntk_identities::IdentityId) -> ntk_proto::v1::TypedValue {
ntk_proto::v1::TypedValue::new(IDENTITY_ID_TAG, id.into_raw().to_be_bytes().to_vec())
}
pub(crate) fn decode_identity_id(
tv: &ntk_proto::v1::TypedValue,
) -> Option<ntk_identities::IdentityId> {
if tv.type_tag != IDENTITY_ID_TAG {
return None;
}
let raw = u64::from_be_bytes(tv.payload.as_slice().try_into().ok()?);
Some(ntk_identities::IdentityId::from_raw(raw))
}
#[derive(Debug, Clone)]
pub struct LinkEntry {
pub id: LinkId,
pub mac: String,
pub dev: String,
pub neighbour_id: ntk_neighborhood::NodeId,
pub qspn_arc: Option<ntk_qspn::ArcId>,
}
#[derive(Debug, Default)]
pub struct LinkRegistry {
next: AtomicU64,
by_mac: Mutex<HashMap<String, LinkEntry>>,
by_id: Mutex<HashMap<LinkId, String>>,
bad_link_last_retry: Mutex<HashMap<LinkId, std::time::Instant>>,
}
impl LinkRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn link_for_neighbour(
&self,
neighbour_id: ntk_neighborhood::NodeId,
mac: &str,
dev: &str,
) -> LinkId {
let mut by_mac = self.by_mac.lock().unwrap_or_else(|e| e.into_inner());
if let Some(entry) = by_mac.get(mac) {
return entry.id;
}
let id = LinkId(self.next.fetch_add(1, Ordering::Relaxed));
by_mac.insert(
mac.to_owned(),
LinkEntry {
id,
mac: mac.to_owned(),
dev: dev.to_owned(),
neighbour_id,
qspn_arc: None,
},
);
self.by_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(id, mac.to_owned());
id
}
#[must_use]
pub fn link_for_caller(&self, tv: &ntk_proto::v1::TypedValue) -> Option<LinkId> {
let id = decode_caller_id(tv)?;
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.values()
.find(|e| e.neighbour_id == id)
.map(|e| e.id)
}
pub fn set_qspn_arc(&self, link: LinkId, arc: ntk_qspn::ArcId) {
if let Some(mac) = self
.by_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&link)
&& let Some(entry) = self
.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.get_mut(mac)
{
entry.qspn_arc = Some(arc);
}
}
#[must_use]
pub fn qspn_arc_of(&self, link: LinkId) -> Option<ntk_qspn::ArcId> {
let mac = self
.by_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&link)?
.clone();
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&mac)
.and_then(|e| e.qspn_arc)
}
#[must_use]
pub fn link_of_qspn_arc(&self, arc: ntk_qspn::ArcId) -> Option<LinkId> {
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.values()
.find(|e| e.qspn_arc == Some(arc))
.map(|e| e.id)
}
#[must_use]
pub fn entry(&self, link: LinkId) -> Option<LinkEntry> {
let mac = self
.by_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&link)?
.clone();
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&mac)
.cloned()
}
#[must_use]
pub fn link_for_dev_and_mac(&self, mac: &str) -> Option<LinkId> {
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(mac)
.map(|e| e.id)
}
pub fn remove(&self, mac: &str) -> Option<LinkEntry> {
let entry = self
.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(mac);
if let Some(entry) = &entry {
self.by_id
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&entry.id);
}
entry
}
#[must_use]
pub fn all(&self) -> Vec<LinkEntry> {
self.by_mac
.lock()
.unwrap_or_else(|e| e.into_inner())
.values()
.cloned()
.collect()
}
pub fn should_retry_bad_link(&self, link: LinkId, min_interval: std::time::Duration) -> bool {
let mut last = self
.bad_link_last_retry
.lock()
.unwrap_or_else(|e| e.into_inner());
let now = std::time::Instant::now();
match last.get(&link) {
Some(prev) if now.duration_since(*prev) < min_interval => false,
_ => {
last.insert(link, now);
true
}
}
}
}