use core::slice;
use core::ops::Deref;
use crate::managed::Ordered;
use crate::time::{Duration, Expiration, Instant};
use crate::wire::{ethernet, ip};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Neighbor {
protocol_addr: ip::Address,
hardware_addr: Mapping,
expires_at: Expiration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Answer {
Found(ethernet::Address),
NotFound,
RateLimited,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mapping {
Address(ethernet::Address),
LookingFor,
Requesting,
}
impl Default for Mapping {
fn default() -> Self {
Mapping::LookingFor
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
NoSpace,
ExpiresTooSoon,
EntryNotFound,
}
#[derive(Debug)]
pub struct Cache<'a> {
storage: Ordered<'a, Neighbor>,
silent_until: Instant,
}
pub struct Missing<'a> {
inner: slice::Iter<'a, Neighbor>,
}
#[derive(Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Table([Neighbor]);
impl<'a> Cache<'a> {
pub(crate) const ENTRY_LIFETIME: Duration = Duration::from_millis(60_000);
pub fn new<T>(storage: T) -> Cache<'a>
where T: Into<Ordered<'a, Neighbor>>
{
Self::import(storage.into())
}
pub fn import(storage: Ordered<'a, Neighbor>) -> Self {
Cache { storage, silent_until: Instant::from_millis(0) }
}
pub fn fill_looking(
&mut self,
protocol_addr: ip::Address,
timestamp: Option<Instant>,
) -> Result<(), Error> {
self.update_or_insert(protocol_addr, Mapping::LookingFor, timestamp)
}
pub fn requesting(
&mut self,
protocol_addr: ip::Address,
timestamp: Instant,
) -> Result<(), Error> {
self.update_or_insert(protocol_addr, Mapping::Requesting, Some(timestamp))
}
pub fn fill(
&mut self,
protocol_addr: ip::Address,
hardware_addr: ethernet::Address,
timestamp: Option<Instant>,
) -> Result<(), Error> {
self.update_or_insert(protocol_addr, Mapping::Address(hardware_addr), timestamp)
}
fn update_or_insert(
&mut self,
protocol_addr: ip::Address,
hardware_addr: Mapping,
timestamp: Option<Instant>,
) -> Result<(), Error> {
debug_assert!(protocol_addr.is_unicast());
if let Mapping::Address(hw_addr) = hardware_addr {
debug_assert!(hw_addr.is_unicast());
}
let new_neighbor = Neighbor {
protocol_addr,
hardware_addr,
expires_at: timestamp.map(|ts| ts + Self::ENTRY_LIFETIME).into(),
};
let exists = self.storage.ordered_slice()
.binary_search_by_key(&protocol_addr, |neighbor| neighbor.protocol_addr);
if let Ok(index) = exists {
let old = self.storage[index];
assert_eq!(old.protocol_addr, new_neighbor.protocol_addr);
if let (Mapping::Requesting, Mapping::LookingFor) = (old.hardware_addr, new_neighbor.hardware_addr) {
if old.expires_at >= Expiration::from(timestamp) {
return Ok(())
}
}
let _old = self.storage.replace_at(index, new_neighbor)
.expect("Sorting didn't change since we only have one entry per protocol addr");
return Ok(());
}
let free = match self.storage.init() {
Some(entry) => {
entry
},
None => {
let (idx, oldest) = self.storage.ordered_slice()
.iter()
.enumerate()
.min_by_key(|(_, neighbor)| neighbor.expires_at)
.ok_or(Error::NoSpace)?;
if oldest.expires_at > new_neighbor.expires_at {
return Err(Error::ExpiresTooSoon)
}
self.storage.pop(idx)
.expect("Entry we just found is valid.");
self.storage.init()
.expect("At least one entry is now free")
},
};
debug_assert!(new_neighbor.hardware_addr != Mapping::Requesting);
*free = new_neighbor;
self.storage.push()
.expect("There was one to insert");
Ok(())
}
}
impl Table {
fn from_slice(data: &[Neighbor]) -> &Self {
unsafe { &*(data as *const [Neighbor] as *const Self) }
}
pub fn lookup_pure(
&self,
protocol_addr: ip::Address,
timestamp: Instant
) -> Option<ethernet::Address> {
match self.lookup(protocol_addr, timestamp) {
Some(Mapping::Address(addr)) => Some(addr),
_ => None,
}
}
pub fn lookup(
&self,
protocol_addr: ip::Address,
timestamp: Instant
) -> Option<Mapping> {
if protocol_addr.is_broadcast() {
return Some(Mapping::Address(ethernet::Address::BROADCAST))
}
let existing = self
.binary_search_by_key(&protocol_addr, |neighbor| neighbor.protocol_addr)
.ok()?;
let entry = &self[existing];
if Expiration::When(timestamp) >= entry.expires_at {
return None;
}
Some(entry.hardware_addr)
}
pub fn missing(&self) -> Missing {
Missing {
inner: self.0.iter(),
}
}
}
impl Neighbor {
pub fn protocol_addr(&self) -> ip::Address {
self.protocol_addr
}
pub fn hardware_addr(&self) -> Option<ethernet::Address> {
match self.hardware_addr {
Mapping::Address(addr) => Some(addr),
Mapping::LookingFor => None,
Mapping::Requesting => None,
}
}
pub fn is_alive(&self, ts: Instant) -> bool {
Expiration::When(ts) <= self.expires_at
}
pub fn is_expired(&self, ts: Instant) -> bool {
!self.is_alive(ts)
}
pub fn looking_for(&self) -> bool {
self.hardware_addr == Mapping::LookingFor
}
}
impl Deref for Cache<'_> {
type Target = Table;
fn deref(&self) -> &Table {
Table::from_slice(self.storage.ordered_slice())
}
}
impl Deref for Table {
type Target = [Neighbor];
fn deref(&self) -> &[Neighbor] {
&self.0
}
}
impl Iterator for Missing<'_> {
type Item = Neighbor;
fn next(&mut self) -> Option<Neighbor> {
self.inner.by_ref()
.filter(|entry| entry.hardware_addr().is_none())
.next()
.copied()
}
}
#[cfg(test)]
mod test {
use super::*;
pub(crate) const MOCK_IP_ADDR_1: ip::Address = ip::Address::Ipv6(ip::v6::Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]));
pub(crate) const MOCK_IP_ADDR_2: ip::Address = ip::Address::Ipv6(ip::v6::Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]));
pub(crate) const MOCK_IP_ADDR_3: ip::Address = ip::Address::Ipv6(ip::v6::Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]));
pub(crate) const MOCK_IP_ADDR_4: ip::Address = ip::Address::Ipv6(ip::v6::Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]));
const HADDR_A: ethernet::Address = ethernet::Address([0, 0, 0, 0, 0, 1]);
const HADDR_B: ethernet::Address = ethernet::Address([0, 0, 0, 0, 0, 2]);
const HADDR_C: ethernet::Address = ethernet::Address([0, 0, 0, 0, 0, 3]);
const HADDR_D: ethernet::Address = ethernet::Address([0, 0, 0, 0, 0, 4]);
#[test]
fn fill() {
let mut cache_storage = [Default::default(); 3];
let mut cache = Cache::new(&mut cache_storage[..]);
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0)), None);
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Some(Instant::from_millis(0)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2),
None);
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Some(Instant::from_millis(0)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_2, Instant::from_millis(0)), None);
}
#[test]
fn expire() {
let mut cache_storage = [Default::default(); 3];
let mut cache = Cache::new(&mut cache_storage[..]);
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Some(Instant::from_millis(0)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2),
None);
}
#[test]
fn replace() {
let mut cache_storage = [Default::default(); 3];
let mut cache = Cache::new(&mut cache_storage[..]);
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Some(Instant::from_millis(0)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_A));
cache.fill(MOCK_IP_ADDR_1, HADDR_B, Some(Instant::from_millis(0)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_1, Instant::from_millis(0)), Some(HADDR_B));
assert_eq!(cache.len(), 1);
}
#[test]
fn evict() {
let mut cache_storage = [Default::default(); 3];
let mut cache = Cache::new(&mut cache_storage[..]);
cache.fill(MOCK_IP_ADDR_1, HADDR_A, Some(Instant::from_millis(100)))
.unwrap();
cache.fill(MOCK_IP_ADDR_2, HADDR_B, Some(Instant::from_millis(50)))
.unwrap();
cache.fill(MOCK_IP_ADDR_3, HADDR_C, Some(Instant::from_millis(200)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_2, Instant::from_millis(1000)), Some(HADDR_B));
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_4, Instant::from_millis(1000)), None);
cache.fill(MOCK_IP_ADDR_4, HADDR_D, Some(Instant::from_millis(300)))
.unwrap();
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_2, Instant::from_millis(1000)), None);
assert_eq!(cache.lookup_pure(MOCK_IP_ADDR_4, Instant::from_millis(1000)), Some(HADDR_D));
}
#[test]
fn full() {
let mut cache_storage = [Default::default(); 1];
let mut cache = Cache::new(&mut cache_storage[..]);
assert!(cache.fill(MOCK_IP_ADDR_1, HADDR_A, None).is_ok());
assert!(cache.fill(MOCK_IP_ADDR_2, HADDR_A, Some(Instant::from_millis(0))).is_err());
assert!(cache.fill(MOCK_IP_ADDR_1, HADDR_B, None).is_ok());
assert!(cache.fill(MOCK_IP_ADDR_2, HADDR_A, None).is_ok());
}
}