use std::{
io::Write,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use crate::{
Error, Limits, Match, Target,
layout::{Layout, MAGIC, add, multiply, slice, u32_at, u128_at},
};
pub(crate) const VERSION: u32 = 5;
const HEADER_BYTES: usize = 16;
const DESCRIPTOR_BYTES: usize = 24;
const MAX_WINDOWS: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SharedAlias {
pub address: Ipv6Addr,
pub prefix: u8,
pub first_id: u32,
pub interleaved: bool,
}
impl SharedAlias {
pub fn project(self, target: Target) -> Result<Target, Error> {
if !self.is_canonical() {
return Err(Error::Invalid("shared alias window"));
}
let Target::Network {
address: IpAddr::V4(address),
prefix,
} = target
else {
return Err(Error::Invalid("shared alias source network"));
};
target.entry(0)?;
Ok(self.project_network(address, prefix))
}
pub(crate) fn start(self) -> u128 {
u128::from(self.address)
}
pub(crate) fn end(self) -> u128 {
self.start() | self.host_mask()
}
pub(crate) fn source_address(self, value: u128) -> Ipv4Addr {
Ipv4Addr::from((value >> (96 - self.prefix)) as u32)
}
pub(crate) fn matched(self, row: Match<'_>) -> Result<Match<'_>, Error> {
let Target::Network {
address: IpAddr::V4(address),
prefix,
} = row.target
else {
return Err(Error::Invalid("shared alias source network"));
};
Ok(Match {
id: self.first_id + row.id * if self.interleaved { 2 } else { 1 },
target: self.project_network(address, prefix),
payload: row.payload,
})
}
fn is_canonical(self) -> bool {
self.prefix <= 96 && self.start() & self.host_mask() == 0
}
fn host_mask(self) -> u128 {
u128::MAX.checked_shr(u32::from(self.prefix)).unwrap_or(0)
}
fn project_network(self, address: Ipv4Addr, prefix: u8) -> Target {
Target::Network {
address: Ipv6Addr::from(
self.start() | (u128::from(u32::from(address)) << (96 - self.prefix)),
)
.into(),
prefix: self.prefix + prefix,
}
}
}
#[derive(Debug)]
pub(crate) struct Aliases {
pub(crate) windows: Vec<SharedAlias>,
block_starts: Vec<usize>,
source_count: usize,
interleaved: bool,
pub(crate) records: usize,
}
impl Aliases {
pub(crate) fn read(bytes: &[u8]) -> Result<(Vec<SharedAlias>, usize), Error> {
let count = u32_at(bytes, 12)? as usize;
if !(1..=MAX_WINDOWS).contains(&count) {
return Err(Error::Invalid("shared alias count"));
}
let length = count * DESCRIPTOR_BYTES;
let mut windows = Vec::new();
windows.try_reserve_exact(count)?;
let (rows, _) = slice(bytes, HEADER_BYTES, length)?.as_chunks::<DESCRIPTOR_BYTES>();
for row in rows {
let [prefix, stride, zero_a, zero_b] = slice(row, 20, 4)? else {
return Err(Error::Invalid("shared alias descriptor"));
};
if !matches!(stride, 1 | 2) || *zero_a != 0 || *zero_b != 0 {
return Err(Error::Invalid("shared alias descriptor"));
}
windows.push(SharedAlias {
address: u128_at(row, 0)?.into(),
first_id: u32_at(row, 16)?,
prefix: *prefix,
interleaved: *stride == 2,
});
}
Ok((windows, HEADER_BYTES + length))
}
pub(crate) fn validate(
windows: &[SharedAlias],
data: &[u8],
layout: Layout,
limits: Limits,
) -> Result<Self, Error> {
let source_count = layout.v4.count;
if source_count == 0 || !(1..=MAX_WINDOWS).contains(&windows.len()) {
return Err(Error::Invalid("shared alias count or source"));
}
let records = add(layout.records, multiply(source_count, windows.len())?)?;
if records > limits.records || records > u32::MAX as usize {
return Err(Error::Limit("records"));
}
let (block_starts, interleaved) = validate_windows(windows, source_count, records)?;
let mut position = 0;
layout.v4.visit_entries(data, |entry| {
if entry.id as usize != position || entry.kind != 1 {
return Err(Error::Invalid("shared alias IPv4 order or kind"));
}
position += 1;
Ok(())
})?;
layout.v6.visit_entries(data, |entry| {
let at = windows.partition_point(|window| window.end() < entry.start);
if windows
.get(at)
.is_some_and(|window| window.start() <= entry.end)
{
return Err(Error::Invalid("shared alias overlaps native IPv6"));
}
Ok(())
})?;
let mut owned = Vec::new();
owned.try_reserve_exact(windows.len())?;
owned.extend_from_slice(windows);
Ok(Self {
windows: owned,
block_starts,
source_count,
interleaved,
records,
})
}
pub(crate) fn heap_bytes(&self) -> usize {
self.windows.capacity() * size_of::<SharedAlias>()
+ self.block_starts.capacity() * size_of::<usize>()
}
pub(crate) fn logical_id(&self, id: u32) -> u32 {
let mut id = id as usize;
if id < self.source_count {
return id as u32 * (1 + u32::from(self.interleaved));
}
id += self.source_count * usize::from(self.interleaved);
for &start in &self.block_starts {
if id < start {
break;
}
id += self.source_count;
}
id as u32
}
pub(crate) fn lookup(&self, value: u128) -> Option<SharedAlias> {
let at = self.windows.partition_point(|window| window.end() < value);
self.windows
.get(at)
.copied()
.filter(|window| window.start() <= value)
}
pub(crate) fn write(
windows: &[SharedAlias],
mut output: impl Write,
data: &[u8],
) -> Result<(), Error> {
output.write_all(MAGIC)?;
output.write_all(&VERSION.to_le_bytes())?;
output.write_all(&(windows.len() as u32).to_le_bytes())?;
for &window in windows {
output.write_all(&window.start().to_le_bytes())?;
output.write_all(&window.first_id.to_le_bytes())?;
output.write_all(&[window.prefix, if window.interleaved { 2 } else { 1 }, 0, 0])?;
}
output.write_all(data)?;
output.flush()?;
Ok(())
}
pub(crate) fn total_bytes(windows: &[SharedAlias], base: usize) -> Result<usize, Error> {
add(
add(HEADER_BYTES, multiply(windows.len(), DESCRIPTOR_BYTES)?)?,
base,
)
}
}
fn validate_windows(
windows: &[SharedAlias],
source_count: usize,
records: usize,
) -> Result<(Vec<usize>, bool), Error> {
let mut block_starts = Vec::new();
block_starts.try_reserve_exact(windows.len())?;
let mut previous_end = None;
let mut interleaved = false;
for &window in windows {
if !window.is_canonical() || previous_end.is_some_and(|end| end >= window.start()) {
return Err(Error::Invalid("shared alias windows"));
}
previous_end = Some(window.end());
if window.interleaved {
if interleaved || window.first_id != 1 {
return Err(Error::Invalid("shared alias interleaved IDs"));
}
interleaved = true;
} else {
block_starts.push(window.first_id as usize);
}
}
block_starts.sort_unstable();
let mut end = source_count * (1 + usize::from(interleaved));
for &start in &block_starts {
if start < end || start > records - source_count {
return Err(Error::Invalid("shared alias ID blocks"));
}
end = start + source_count;
}
Ok((block_starts, interleaved))
}