use std::{fmt, net::IpAddr, sync::Arc};
use crate::{CoverageRouter, Error, Match, Reader};
pub struct ReaderSet<B: AsRef<[u8]>> {
readers: Vec<Arc<Reader<B>>>,
router: Routing,
}
impl<B: AsRef<[u8]>> ReaderSet<B> {
pub fn new(readers: Vec<Arc<Reader<B>>>) -> Result<Self, Error> {
if readers.len() > 64 {
return Err(Error::Limit("routed readers"));
}
let mut coverage = Vec::new();
coverage.try_reserve_exact(readers.len())?;
for reader in &readers {
coverage.push(reader.coverage());
}
let router = Routing::Coarse(CoverageRouter::new(&coverage)?);
Ok(Self { readers, router })
}
pub fn with_fine_routing(readers: Vec<Arc<Reader<B>>>) -> Result<Self, Error> {
if readers.len() > 64 {
return Err(Error::Limit("routed readers"));
}
let mut router = FineRouter::empty()?;
for (position, reader) in readers.iter().enumerate() {
router.insert(position, reader)?;
}
Ok(Self {
readers,
router: Routing::Fine(router),
})
}
pub fn len(&self) -> usize {
self.readers.len()
}
pub fn is_empty(&self) -> bool {
self.readers.is_empty()
}
pub fn reader(&self, position: usize) -> Option<&Arc<Reader<B>>> {
self.readers.get(position)
}
pub fn visit_ip<'a, E: From<Error>>(
&'a self,
address: IpAddr,
mut emit: impl FnMut(usize, Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
for position in crate::coverage::candidates(self.router.mask(address)) {
let reader = self
.readers
.get(position)
.ok_or(Error::Invalid("routed reader position"))?;
reader.visit_ip(address, |matched| emit(position, matched))?;
}
Ok(())
}
pub fn visit_asn<'a, E: From<Error>>(
&'a self,
number: u32,
mut emit: impl FnMut(usize, Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
for (position, reader) in self.readers.iter().enumerate() {
reader.visit_asn(number, |matched| emit(position, matched))?;
}
Ok(())
}
pub fn replaced(&self, position: usize, reader: Arc<Reader<B>>) -> Result<Self, Error> {
if position >= self.len() {
return Err(Error::Invalid("reader position"));
}
let mut readers = Vec::new();
readers.try_reserve_exact(self.len())?;
readers.extend(self.readers.iter().enumerate().map(|(index, existing)| {
if index == position {
Arc::clone(&reader)
} else {
Arc::clone(existing)
}
}));
let router = match &self.router {
Routing::Coarse(_) => {
let mut coverage = Vec::new();
coverage.try_reserve_exact(self.len())?;
coverage.extend(readers.iter().map(|reader| reader.coverage()));
Routing::Coarse(CoverageRouter::new(&coverage)?)
}
Routing::Fine(existing) => {
let mut router = FineRouter::empty()?;
let keep = !(1u64 << position);
for (output, &mask) in router.masks.iter_mut().zip(&existing.masks) {
*output = mask & keep;
}
router.insert(position, &reader)?;
Routing::Fine(router)
}
};
Ok(Self { readers, router })
}
}
impl<B: AsRef<[u8]>> fmt::Debug for ReaderSet<B> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ReaderSet")
.field("readers", &self.readers)
.field("fine_routing", &matches!(self.router, Routing::Fine(_)))
.finish()
}
}
enum Routing {
Coarse(CoverageRouter),
Fine(FineRouter),
}
impl Routing {
fn mask(&self, address: IpAddr) -> u64 {
match self {
Self::Coarse(router) => router.mask(address),
Self::Fine(router) => {
let bucket = fine_bucket(crate::target::number(address), address.is_ipv6());
router.masks.get(bucket).copied().unwrap_or(u64::MAX)
}
}
}
}
struct FineRouter {
masks: Vec<u64>,
}
impl FineRouter {
fn empty() -> Result<Self, Error> {
let mut masks = Vec::new();
masks.try_reserve_exact(2 * 65536)?;
masks.resize(2 * 65536, 0);
Ok(Self { masks })
}
fn insert<B: AsRef<[u8]>>(&mut self, position: usize, reader: &Reader<B>) -> Result<(), Error> {
reader.visit_all(|matched| {
if matches!(matched.target, crate::Target::Asn(_)) {
return Ok(());
}
let (entry, ipv6) = matched.target.entry(matched.id)?;
let first = fine_bucket(entry.start, ipv6);
let last = fine_bucket(entry.end, ipv6);
let masks = self
.masks
.get_mut(first..=last)
.ok_or(Error::Invalid("coverage bucket"))?;
for mask in masks {
*mask |= 1u64 << position;
}
Ok::<_, Error>(())
})
}
}
fn fine_bucket(address: u128, ipv6: bool) -> usize {
usize::from(ipv6) * 65536 + (address >> if ipv6 { 112 } else { 16 }) as usize
}