use std::{fmt, net::IpAddr, ops::ControlFlow};
use crate::{
Error, Match, Reader, SharedAlias, Target,
layout::{Encoding, Section, slice},
packed,
target::{Entry, number},
};
use super::asn_key;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Query {
Ip(IpAddr),
Asn(u32),
Intersecting(Target),
}
#[derive(Debug)]
pub struct BatchError<E> {
pub query_index: usize,
pub error: E,
}
impl<E: fmt::Display> fmt::Display for BatchError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "batch query {} failed: {}", self.query_index, self.error)
}
}
impl<E: std::error::Error + 'static> std::error::Error for BatchError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
impl<B: AsRef<[u8]>> Reader<B> {
pub fn visit_batch<'a, E: From<Error>>(
&'a self,
queries: &[Query],
mut emit: impl FnMut(usize, Match<'a>) -> Result<(), E>,
) -> Result<(), BatchError<E>> {
for (query_index, &query) in queries.iter().enumerate() {
let result = match query {
Query::Ip(address) => self.visit_ip(address, |row| emit(query_index, row)),
Query::Asn(number) => self.visit_asn(number, |row| emit(query_index, row)),
Query::Intersecting(target) => {
self.visit_intersecting(target, |row| emit(query_index, row))
}
};
result.map_err(|error| BatchError { query_index, error })?;
}
Ok(())
}
pub fn try_visit_ip<'a, T, E: From<Error>>(
&'a self,
address: IpAddr,
mut emit: impl FnMut(Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
finish(self.visit_ip(address, |row| step(emit(row))))
}
pub fn try_visit_asn<'a, T, E: From<Error>>(
&'a self,
number: u32,
mut emit: impl FnMut(Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
finish(self.visit_asn(number, |row| step(emit(row))))
}
pub fn try_visit_all<'a, T, E: From<Error>>(
&'a self,
mut emit: impl FnMut(Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
finish(self.visit_all(|row| step(emit(row))))
}
pub fn try_visit_batch<'a, T, E: From<Error>>(
&'a self,
queries: &[Query],
mut emit: impl FnMut(usize, Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<(usize, T)>, BatchError<E>> {
match self.visit_batch(queries, |index, row| step(emit(index, row))) {
Ok(()) => Ok(ControlFlow::Continue(())),
Err(BatchError {
query_index,
error: Stop::Break(value),
}) => Ok(ControlFlow::Break((query_index, value))),
Err(BatchError {
query_index,
error: Stop::Error(error),
}) => Err(BatchError { query_index, error }),
}
}
pub fn contains_ip(&self, address: IpAddr) -> Result<bool, Error> {
let address = if let IpAddr::V6(ip) = address
&& let Some(window) = self
.aliases
.as_ref()
.and_then(|aliases| aliases.lookup(ip.into()))
{
window.source_address(ip.into()).into()
} else {
address
};
let ipv6 = address.is_ipv6();
let query = number(address);
let (first, last) = if ipv6 { self.bounds[1] } else { self.bounds[0] };
if query < first || query > last {
return Ok(false);
}
let section = if ipv6 { self.layout.v6 } else { self.layout.v4 };
let bytes = self.bytes.as_ref();
if section.encoding == Encoding::Packed {
if let Some(entry) = self.lookup_packed_ip(bytes, section, query)? {
entry.validate(ipv6)?;
return Ok(true);
}
return Ok(false);
}
contains_interval(section, bytes, 0, section.count, query)
}
pub fn contains_asn(&self, number: u32) -> Result<bool, Error> {
let rows = self.asn_rows(self.bytes.as_ref())?;
let first = rows.partition_point(|row| asn_key(row) < number);
Ok(rows.get(first).is_some_and(|row| asn_key(row) == number))
}
pub fn visit_intersecting<'a, E: From<Error>>(
&'a self,
target: Target,
mut emit: impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
let (query, ipv6) = target.entry(0)?;
let section = if ipv6 { self.layout.v6 } else { self.layout.v4 };
let mut windows = self
.aliases
.as_ref()
.into_iter()
.flat_map(|aliases| aliases.windows.iter())
.peekable();
self.intersect_physical(section, query.start, query.end, ipv6, &mut |row| {
if ipv6 {
let (entry, _) = row.target.entry(0)?;
while windows
.peek()
.is_some_and(|window| window.end() < entry.start)
{
if let Some(&window) = windows.next() {
self.intersect_alias(window, query.start, query.end, &mut emit)?;
}
}
}
emit(row)
})?;
if ipv6 {
for &window in windows {
self.intersect_alias(window, query.start, query.end, &mut emit)?;
}
}
Ok(())
}
pub fn try_visit_intersecting<'a, T, E: From<Error>>(
&'a self,
target: Target,
mut emit: impl FnMut(Match<'a>) -> Result<ControlFlow<T>, E>,
) -> Result<ControlFlow<T>, E> {
finish(self.visit_intersecting(target, |row| step(emit(row))))
}
fn intersect_alias<'a, E: From<Error>>(
&'a self,
window: SharedAlias,
first: u128,
last: u128,
emit: &mut impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
if first > window.end() || last < window.start() {
return Ok(());
}
self.intersect_entries(
self.layout.v4,
0,
self.layout.v4.count,
number(window.source_address(first.max(window.start())).into()),
number(window.source_address(last.min(window.end())).into()),
false,
&mut |entry| {
emit(window.matched(Match {
id: entry.id,
target: entry.target(false)?,
payload: self.layout.payload(self.bytes.as_ref(), entry.id)?,
})?)
},
)
}
fn intersect_physical<'a, E: From<Error>>(
&'a self,
section: Section,
first: u128,
last: u128,
ipv6: bool,
emit: &mut impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
self.intersect_entries(section, 0, section.count, first, last, ipv6, &mut |entry| {
emit(self.physical_match(entry, ipv6)?)
})
}
#[expect(
clippy::too_many_arguments,
reason = "bounded interval traversal carries explicit query and subtree bounds"
)]
fn intersect_entries<E: From<Error>>(
&self,
section: Section,
low: usize,
high: usize,
first: u128,
last: u128,
ipv6: bool,
emit: &mut impl FnMut(Entry) -> Result<(), E>,
) -> Result<(), E> {
if section.encoding == Encoding::Packed {
return packed::visit_intersecting(
slice(self.bytes.as_ref(), section.offset, section.length)?,
section.count,
first,
last,
ipv6,
self.search,
emit,
);
}
if low == high {
return Ok(());
}
let bytes = self.bytes.as_ref();
let mid = low + (high - low) / 2;
if section.maximum(bytes, mid)? < first || section.start(bytes, low)? > last {
return Ok(());
}
self.intersect_entries(section, low, mid, first, last, ipv6, emit)?;
if section.start(bytes, mid)? > last {
return Ok(());
}
let entry = section.entry(bytes, mid)?;
if entry.end >= first {
emit(entry)?;
}
self.intersect_entries(section, mid + 1, high, first, last, ipv6, emit)
}
}
fn contains_interval(
section: Section,
bytes: &[u8],
low: usize,
high: usize,
query: u128,
) -> Result<bool, Error> {
if low == high {
return Ok(false);
}
let mid = low + (high - low) / 2;
if section.maximum(bytes, mid)? < query || section.start(bytes, low)? > query {
return Ok(false);
}
if contains_interval(section, bytes, low, mid, query)? {
return Ok(true);
}
if section.start(bytes, mid)? > query {
return Ok(false);
}
let entry = section.entry(bytes, mid)?;
if entry.end >= query {
entry.validate(section.ipv6)?;
return Ok(true);
}
contains_interval(section, bytes, mid + 1, high, query)
}
enum Stop<E, T> {
Error(E),
Break(T),
}
impl<E: From<Error>, T> From<Error> for Stop<E, T> {
fn from(error: Error) -> Self {
Self::Error(error.into())
}
}
fn step<E, T>(result: Result<ControlFlow<T>, E>) -> Result<(), Stop<E, T>> {
match result {
Ok(ControlFlow::Continue(())) => Ok(()),
Ok(ControlFlow::Break(value)) => Err(Stop::Break(value)),
Err(error) => Err(Stop::Error(error)),
}
}
fn finish<E, T>(result: Result<(), Stop<E, T>>) -> Result<ControlFlow<T>, E> {
match result {
Ok(()) => Ok(ControlFlow::Continue(())),
Err(Stop::Break(value)) => Ok(ControlFlow::Break(value)),
Err(Stop::Error(error)) => Err(error),
}
}