use std::{fmt, net::IpAddr};
use crate::{
layout::{slice, u32_at, Encoding, Layout, Section, HEADER},
packed,
search::Search,
target::number,
Coverage, Error, Limits, Target,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Match<'a> {
pub id: u32,
pub target: Target,
pub payload: &'a [u8],
}
pub struct Reader<B: AsRef<[u8]>> {
bytes: B,
layout: Layout,
search: Search,
}
impl<B: AsRef<[u8]>> Reader<B> {
pub fn open(bytes: B, limits: Limits) -> Result<Self, Error> {
let data = bytes.as_ref();
let layout = Layout::read(data, limits)?;
validate(data, layout)?;
Ok(Self {
bytes,
layout,
search: Search::detect(),
})
}
pub fn len(&self) -> usize {
self.layout.records
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn metadata(&self) -> Result<&[u8], Error> {
slice(self.bytes.as_ref(), HEADER, self.layout.metadata)
}
pub fn coverage(&self) -> Result<Coverage, Error> {
let mut coverage = Coverage::empty();
let bytes = self.bytes.as_ref();
for (section, ipv6) in [(self.layout.v4, false), (self.layout.v6, true)] {
for position in 0..section.count {
let entry = section.entry(bytes, position)?;
entry.target(ipv6)?;
coverage.insert(entry.start, entry.end, ipv6);
}
}
Ok(coverage)
}
pub fn lookup_ip(&self, address: IpAddr) -> Result<Vec<Match<'_>>, Error> {
let mut matches = Vec::new();
self.visit_ip(address, |matched| {
matches.try_reserve(1)?;
matches.push(matched);
Ok::<_, Error>(())
})?;
Ok(matches)
}
pub fn lookup_asn(&self, number: u32) -> Result<Vec<Match<'_>>, Error> {
let mut matches = Vec::new();
self.visit_asn(number, |matched| {
matches.try_reserve(1)?;
matches.push(matched);
Ok::<_, Error>(())
})?;
Ok(matches)
}
pub fn visit_ip<'a, E: From<Error>>(
&'a self,
address: IpAddr,
mut emit: impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
let section = if address.is_ipv6() {
self.layout.v6
} else {
self.layout.v4
};
if section.encoding == Encoding::Packed {
let bytes = self.bytes.as_ref();
let data = slice(bytes, section.offset, section.length)?;
if let Some(position) =
packed::predecessor(data, section.count, number(address), self.search)?
{
let entry = section.entry(bytes, position)?;
if entry.start <= number(address) && number(address) <= entry.end {
emit(Match {
id: entry.id,
target: entry.target(address.is_ipv6())?,
payload: self.layout.payload(bytes, entry.id)?,
})?;
}
}
return Ok(());
}
self.visit(
section,
0,
section.count,
number(address),
address.is_ipv6(),
&mut emit,
)
}
pub fn visit_asn<'a, E: From<Error>>(
&'a self,
number: u32,
mut emit: impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
let bytes = self.bytes.as_ref();
let section = self.layout.asns;
let (mut low, mut high) = (0, section.count);
while low < high {
let mid = low + (high - low) / 2;
if u32_at(section.row(bytes, mid)?, 0)? < number {
low = mid + 1;
} else {
high = mid;
}
}
while low < section.count {
let row = section.row(bytes, low)?;
if u32_at(row, 0)? != number {
break;
}
let id = u32_at(row, 4)?;
emit(Match {
id,
target: Target::Asn(number),
payload: self.layout.payload(bytes, id)?,
})?;
low += 1;
}
Ok(())
}
pub fn visit_all<'a, E: From<Error>>(
&'a self,
mut emit: impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
let bytes = self.bytes.as_ref();
for (section, ipv6) in [(self.layout.v4, false), (self.layout.v6, true)] {
for position in 0..section.count {
let entry = section.entry(bytes, position)?;
emit(Match {
id: entry.id,
target: entry.target(ipv6)?,
payload: self.layout.payload(bytes, entry.id)?,
})?;
}
}
for position in 0..self.layout.asns.count {
let row = self.layout.asns.row(bytes, position)?;
let id = u32_at(row, 4)?;
emit(Match {
id,
target: Target::Asn(u32_at(row, 0)?),
payload: self.layout.payload(bytes, id)?,
})?;
}
Ok(())
}
fn visit<'a, E: From<Error>>(
&'a self,
section: Section,
low: usize,
high: usize,
query: u128,
ipv6: bool,
emit: &mut impl FnMut(Match<'a>) -> Result<(), E>,
) -> Result<(), E> {
if low == high {
return Ok(());
}
let bytes = self.bytes.as_ref();
let mid = low + (high - low) / 2;
if section.maximum(bytes, mid)? < query || section.start(bytes, low)? > query {
return Ok(());
}
self.visit(section, low, mid, query, ipv6, emit)?;
if section.start(bytes, mid)? > query {
return Ok(());
}
let entry = section.entry(bytes, mid)?;
if query <= entry.end {
emit(Match {
id: entry.id,
target: entry.target(ipv6)?,
payload: self.layout.payload(bytes, entry.id)?,
})?;
}
self.visit(section, mid + 1, high, query, ipv6, emit)?;
Ok(())
}
}
impl<B: AsRef<[u8]>> fmt::Debug for Reader<B> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Reader")
.field("records", &self.layout.records)
.field("artifact_bytes", &self.layout.bytes)
.finish()
}
}
fn validate(bytes: &[u8], layout: Layout) -> Result<(), Error> {
let mut seen = Vec::new();
let words = layout.records.div_ceil(64);
seen.try_reserve_exact(words)?;
seen.resize(words, 0u64);
for (section, ipv6) in [(layout.v4, false), (layout.v6, true)] {
if section.encoding == Encoding::Packed {
packed::validate(slice(bytes, section.offset, section.length)?, section.count)?;
}
let mut previous = None;
let mut previous_end = None;
for position in 0..section.count {
let entry = section.entry(bytes, position)?;
entry.target(ipv6)?;
let key = (entry.start, entry.end, entry.id);
if previous.is_some_and(|previous| previous >= key) {
return Err(Error::Invalid("IP entry order"));
}
if section.encoding == Encoding::Packed
&& previous_end.is_some_and(|end| end >= entry.start)
{
return Err(Error::Invalid("packed overlapping targets"));
}
mark(&mut seen, layout.records, entry.id)?;
previous = Some(key);
previous_end = Some(entry.end);
}
if section.encoding == Encoding::Interval {
validate_maximum(bytes, section, 0, section.count)?;
}
}
let mut previous = None;
for position in 0..layout.asns.count {
let row = layout.asns.row(bytes, position)?;
let key = (u32_at(row, 0)?, u32_at(row, 4)?);
if key.0 == 0 || previous.is_some_and(|previous| previous >= key) {
return Err(Error::Invalid("ASN entry order or key"));
}
mark(&mut seen, layout.records, key.1)?;
previous = Some(key);
}
let mut offset = 0usize;
let mut spans = Vec::new();
for id in 0..layout.records {
let (start, length) = layout.span(bytes, id as u32)?;
if (start < offset || length == 0) && spans.binary_search(&(start, length)).is_ok() {
continue;
}
if start != offset {
return Err(Error::Invalid("payload offset"));
}
slice(bytes, layout.payloads + offset, length)?;
spans.try_reserve(1)?;
spans.push((start, length));
offset = offset
.checked_add(length)
.ok_or(Error::Limit("payload bytes"))?;
}
if offset != layout.bytes - layout.payloads {
return Err(Error::Invalid("payload length"));
}
Ok(())
}
fn mark(seen: &mut [u64], records: usize, id: u32) -> Result<(), Error> {
let id = id as usize;
if id >= records {
return Err(Error::Invalid("record ID"));
}
let seen = seen.get_mut(id / 64).ok_or(Error::Invalid("record ID"))?;
let mask = 1u64 << (id % 64);
if *seen & mask != 0 {
return Err(Error::Invalid("duplicate record ID"));
}
*seen |= mask;
Ok(())
}
fn validate_maximum(
bytes: &[u8],
section: Section,
low: usize,
high: usize,
) -> Result<u128, Error> {
if low == high {
return Ok(0);
}
let mid = low + (high - low) / 2;
let expected = section
.endpoint(bytes, mid)?
.max(validate_maximum(bytes, section, low, mid)?)
.max(validate_maximum(bytes, section, mid + 1, high)?);
if expected != section.maximum(bytes, mid)? {
return Err(Error::Invalid("interval maximum"));
}
Ok(expected)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_marks_reject_duplicates_word_boundaries_and_padding_ids() {
let mut seen = [0; 2];
for id in [0, 63, 64] {
mark(&mut seen, 65, id).unwrap();
}
for id in [0, 63, 64] {
assert!(matches!(
mark(&mut seen, 65, id),
Err(Error::Invalid("duplicate record ID"))
));
}
for id in [65, 127, u32::MAX] {
assert!(matches!(
mark(&mut seen, 65, id),
Err(Error::Invalid("record ID"))
));
}
assert!(matches!(
mark(&mut [], 0, 0),
Err(Error::Invalid("record ID"))
));
assert!(matches!(
mark(&mut [], 1, 0),
Err(Error::Invalid("record ID"))
));
}
}