#![allow(missing_docs)]
use core::marker::Copy;
use core::mem::MaybeUninit;
use core::net::{IpAddr, SocketAddr};
pub trait Hash32 {
fn hash32(&self) -> u32;
}
const EMPTY: u32 = u32::MAX;
const TOMBSTONE: u32 = u32::MAX - 1;
#[derive(Copy)]
struct Entry<K: Copy> {
key: MaybeUninit<K>,
slot_idx: u32,
}
impl<K: Copy> Clone for Entry<K> {
fn clone(&self) -> Self {
*self
}
}
impl<K: Copy> Entry<K> {
#[inline]
fn empty() -> Self {
Self {
key: MaybeUninit::uninit(),
slot_idx: EMPTY,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProbeExhausted;
pub struct BoundedIndex<K: Copy + Eq + Hash32> {
table: Vec<Entry<K>>,
mask: usize,
occupied: usize,
probe_exhausted: u64,
}
impl<K: Copy + Eq + Hash32> BoundedIndex<K> {
pub const MAX_PROBE: usize = 64;
pub fn new(capacity: usize) -> Self {
let target = capacity.saturating_mul(2).max(2);
let size = target.next_power_of_two();
let table = vec![Entry::empty(); size];
Self {
table,
mask: size - 1,
occupied: 0,
probe_exhausted: 0,
}
}
pub fn len(&self) -> usize {
self.occupied
}
pub fn get(&self, key: K) -> Option<usize> {
let mut i = (key.hash32() as usize) & self.mask;
for _ in 0..Self::MAX_PROBE {
let entry = self.table.get(i)?;
match entry.slot_idx {
EMPTY => return None,
TOMBSTONE => {}
_ => {
let entry_key = unsafe { entry.key.assume_init() };
if entry_key == key {
return Some(entry.slot_idx as usize);
}
}
}
i = (i + 1) & self.mask;
}
None
}
pub fn insert(&mut self, key: K, slot_idx: usize) -> Result<(), ProbeExhausted> {
debug_assert!((slot_idx as u32) < TOMBSTONE);
let mut i = (key.hash32() as usize) & self.mask;
let mut first_tombstone: Option<usize> = None;
for _ in 0..Self::MAX_PROBE {
let entry = match self.table.get(i) {
Some(e) => *e,
None => {
self.probe_exhausted = self.probe_exhausted.saturating_add(1);
return Err(ProbeExhausted);
}
};
match entry.slot_idx {
EMPTY => {
let target = first_tombstone.unwrap_or(i);
if let Some(slot) = self.table.get_mut(target) {
slot.key.write(key);
slot.slot_idx = slot_idx as u32;
self.occupied = self.occupied.saturating_add(1);
return Ok(());
}
self.probe_exhausted = self.probe_exhausted.saturating_add(1);
return Err(ProbeExhausted);
}
TOMBSTONE => {
if first_tombstone.is_none() {
first_tombstone = Some(i);
}
}
_ => {
let entry_key = unsafe { entry.key.assume_init() };
if entry_key == key {
if let Some(slot) = self.table.get_mut(i) {
slot.slot_idx = slot_idx as u32;
return Ok(());
}
self.probe_exhausted = self.probe_exhausted.saturating_add(1);
return Err(ProbeExhausted);
}
}
}
i = (i + 1) & self.mask;
}
self.probe_exhausted = self.probe_exhausted.saturating_add(1);
Err(ProbeExhausted)
}
pub fn remove(&mut self, key: K) -> Option<usize> {
let mut i = (key.hash32() as usize) & self.mask;
for _ in 0..Self::MAX_PROBE {
let entry = *self.table.get(i)?;
match entry.slot_idx {
EMPTY => return None,
TOMBSTONE => {}
_ => {
let entry_key = unsafe { entry.key.assume_init() };
if entry_key == key {
let prev = entry.slot_idx as usize;
if let Some(slot) = self.table.get_mut(i) {
slot.slot_idx = TOMBSTONE;
self.occupied = self.occupied.saturating_sub(1);
}
return Some(prev);
}
}
}
i = (i + 1) & self.mask;
}
None
}
pub fn iter(&self) -> impl Iterator<Item = (K, usize)> + '_ {
self.table.iter().filter_map(|e| match e.slot_idx {
EMPTY | TOMBSTONE => None,
other => Some((unsafe { e.key.assume_init() }, other as usize)),
})
}
pub fn take_probe_exhausted(&mut self) -> u64 {
let n = self.probe_exhausted;
self.probe_exhausted = 0;
n
}
pub fn clear(&mut self) {
for entry in self.table.iter_mut() {
entry.slot_idx = EMPTY;
}
self.occupied = 0;
}
}
#[inline]
pub fn mix32(x: u32) -> u32 {
let mut x = x;
x = (x ^ (x >> 16)).wrapping_mul(0x85eb_ca6b);
x = (x ^ (x >> 13)).wrapping_mul(0xc2b2_ae35);
x ^ (x >> 16)
}
impl Hash32 for u32 {
#[inline]
fn hash32(&self) -> u32 {
mix32(*self)
}
}
impl Hash32 for IpAddr {
#[inline]
fn hash32(&self) -> u32 {
match self {
IpAddr::V4(v) => mix32(u32::from_be_bytes(v.octets())),
IpAddr::V6(v) => {
let b = v.octets();
let c0 = u32::from_be_bytes([b[0], b[1], b[2], b[3]]);
let c1 = u32::from_be_bytes([b[4], b[5], b[6], b[7]]);
let c2 = u32::from_be_bytes([b[8], b[9], b[10], b[11]]);
let c3 = u32::from_be_bytes([b[12], b[13], b[14], b[15]]);
mix32(c0 ^ c1.rotate_left(7) ^ c2.rotate_left(13) ^ c3.rotate_left(19))
}
}
}
}
impl Hash32 for SocketAddr {
#[inline]
fn hash32(&self) -> u32 {
let port = u32::from(self.port());
match self.ip() {
IpAddr::V4(v) => mix32(u32::from_be_bytes(v.octets()) ^ port.rotate_left(11)),
IpAddr::V6(v) => {
let b = v.octets();
let c0 = u32::from_be_bytes([b[0], b[1], b[2], b[3]]);
let c1 = u32::from_be_bytes([b[4], b[5], b[6], b[7]]);
let c2 = u32::from_be_bytes([b[8], b[9], b[10], b[11]]);
let c3 = u32::from_be_bytes([b[12], b[13], b[14], b[15]]);
mix32(
c0 ^ c1.rotate_left(7)
^ c2.rotate_left(13)
^ c3.rotate_left(19)
^ port.rotate_left(23),
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
#[test]
fn entry_u32_is_8_bytes() {
assert_eq!(core::mem::size_of::<Entry<u32>>(), 8);
}
#[test]
fn roundtrip_u32() {
let mut t: BoundedIndex<u32> = BoundedIndex::new(16);
assert_eq!(t.len(), 0);
t.insert(7, 0).unwrap();
t.insert(13, 1).unwrap();
t.insert(u32::MAX - 4, 2).unwrap();
assert_eq!(t.len(), 3);
assert_eq!(t.get(7), Some(0));
assert_eq!(t.get(13), Some(1));
assert_eq!(t.get(u32::MAX - 4), Some(2));
assert_eq!(t.get(999), None);
assert_eq!(t.remove(13), Some(1));
assert_eq!(t.get(13), None);
assert_eq!(t.len(), 2);
t.insert(13, 9).unwrap();
assert_eq!(t.get(13), Some(9));
assert_eq!(t.len(), 3);
}
#[test]
fn roundtrip_ipv4() {
let mut t: BoundedIndex<IpAddr> = BoundedIndex::new(8);
let a = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
let b = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
t.insert(a, 0).unwrap();
t.insert(b, 1).unwrap();
assert_eq!(t.get(a), Some(0));
assert_eq!(t.get(b), Some(1));
}
#[test]
fn roundtrip_ipv6() {
let mut t: BoundedIndex<IpAddr> = BoundedIndex::new(8);
let a = IpAddr::V6(Ipv6Addr::LOCALHOST);
let b = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
t.insert(a, 0).unwrap();
t.insert(b, 1).unwrap();
assert_eq!(t.get(a), Some(0));
assert_eq!(t.get(b), Some(1));
assert_eq!(t.get(IpAddr::V6(Ipv6Addr::UNSPECIFIED)), None);
}
#[test]
fn tombstone_reuse_does_not_break_long_probe_chains() {
let mut t: BoundedIndex<u32> = BoundedIndex::new(64);
for k in 0..50u32 {
t.insert(k, k as usize).unwrap();
}
for k in (0..50).step_by(2) {
assert_eq!(t.remove(k), Some(k as usize));
}
for k in (0..50).step_by(2) {
assert_eq!(t.get(k), None);
}
for k in (1..50).step_by(2) {
assert_eq!(t.get(k), Some(k as usize));
}
for k in (0..50).step_by(2) {
t.insert(k, (k * 2) as usize).unwrap();
}
for k in (0..50).step_by(2) {
assert_eq!(t.get(k), Some((k * 2) as usize));
}
}
#[test]
fn probe_exhaustion_is_observable() {
let mut t: BoundedIndex<u32> = BoundedIndex::new(1);
let _ = t.insert(0, 0);
let _ = t.insert(1, 1);
for k in 2..200u32 {
let _ = t.insert(k, k as usize);
}
let n = t.take_probe_exhausted();
assert_eq!(t.take_probe_exhausted(), 0);
let _ = n;
}
#[test]
fn iter_yields_each_live_entry_exactly_once() {
let mut t: BoundedIndex<u32> = BoundedIndex::new(32);
for k in 0..20u32 {
t.insert(k, k as usize).unwrap();
}
for k in (0..20).step_by(3) {
t.remove(k);
}
let mut seen: Vec<(u32, usize)> = t.iter().collect();
seen.sort_unstable();
let expected: Vec<(u32, usize)> = (0..20u32)
.filter(|k| k % 3 != 0)
.map(|k| (k, k as usize))
.collect();
assert_eq!(seen, expected);
}
#[test]
fn clear_resets_all_state() {
let mut t: BoundedIndex<u32> = BoundedIndex::new(16);
for k in 0..10u32 {
t.insert(k, k as usize).unwrap();
}
t.clear();
assert_eq!(t.len(), 0);
for k in 0..10u32 {
assert_eq!(t.get(k), None);
}
t.insert(42, 42).unwrap();
assert_eq!(t.get(42), Some(42));
}
#[test]
fn hash32_is_deterministic_across_calls() {
let k: u32 = 0xdead_beef;
assert_eq!(k.hash32(), k.hash32());
let ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
assert_eq!(ip.hash32(), ip.hash32());
let sa = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 4242));
assert_eq!(sa.hash32(), sa.hash32());
}
#[test]
fn roundtrip_socketaddr_v4() {
let mut t: BoundedIndex<SocketAddr> = BoundedIndex::new(8);
let a = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 1), 4242));
let b = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 1, 1), 9000));
t.insert(a, 0).unwrap();
t.insert(b, 1).unwrap();
assert_eq!(t.get(a), Some(0));
assert_eq!(t.get(b), Some(1));
assert_eq!(
t.get(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(10, 0, 0, 1),
4243
))),
None,
"different port on same IP must miss"
);
}
#[test]
fn roundtrip_socketaddr_v6() {
let mut t: BoundedIndex<SocketAddr> = BoundedIndex::new(8);
let a = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 4242, 0, 0));
let b = SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
9000,
0,
0,
));
t.insert(a, 0).unwrap();
t.insert(b, 1).unwrap();
assert_eq!(t.get(a), Some(0));
assert_eq!(t.get(b), Some(1));
assert_eq!(
t.get(SocketAddr::V6(SocketAddrV6::new(
Ipv6Addr::LOCALHOST,
4243,
0,
0
))),
None,
"different port on same IPv6 must miss"
);
}
#[test]
fn socketaddr_hash_distinguishes_port_changes() {
let ip4 = Ipv4Addr::new(10, 0, 0, 1);
let h1 = SocketAddr::V4(SocketAddrV4::new(ip4, 1)).hash32();
let h2 = SocketAddr::V4(SocketAddrV4::new(ip4, 2)).hash32();
assert_ne!(h1, h2);
let ip6 = Ipv6Addr::LOCALHOST;
let h3 = SocketAddr::V6(SocketAddrV6::new(ip6, 1, 0, 0)).hash32();
let h4 = SocketAddr::V6(SocketAddrV6::new(ip6, 2, 0, 0)).hash32();
assert_ne!(h3, h4);
}
}