use crate::{
bytes_buffer::BytesBuffer,
dns::WireFormat,
lib::Write,
lib::{BTreeMap, Cow, Vec},
Name,
};
use super::{RR, TYPE};
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct NSEC<'a> {
pub next_name: Name<'a>,
pub type_bit_maps: Vec<NsecTypeBitMap<'a>>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct NsecTypeBitMap<'a> {
pub window_block: u8,
pub bitmap: Cow<'a, [u8]>,
}
impl RR for NSEC<'_> {
const TYPE_CODE: u16 = 47;
}
impl<'a> WireFormat<'a> for NSEC<'a> {
const MINIMUM_LEN: usize = 0;
fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
where
Self: Sized,
{
let next_name = Name::parse(data)?;
let mut type_bit_maps = Vec::new();
let mut prev_window_block = None;
while data.has_remaining() {
let window_block = data.get_u8()?;
if let Some(prev_window_block) = prev_window_block {
if window_block <= prev_window_block {
return Err(crate::SimpleDnsError::InvalidDnsPacket);
}
}
prev_window_block = Some(window_block);
let bitmap_length = data.get_u8()? as usize;
if bitmap_length > 32 {
return Err(crate::SimpleDnsError::InvalidDnsPacket);
}
let bitmap = data.get_slice(bitmap_length)?;
type_bit_maps.push(NsecTypeBitMap {
window_block,
bitmap: Cow::Borrowed(bitmap),
});
}
Ok(Self {
next_name,
type_bit_maps,
})
}
fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
self.next_name.write_to(out)?;
let mut sorted = self.type_bit_maps.clone();
sorted.sort_by_key(|a| a.window_block);
for record in sorted.iter() {
out.write_all(&[record.window_block])?;
out.write_all(&[record.bitmap.len() as u8])?;
out.write_all(&record.bitmap)?;
}
Ok(())
}
fn len(&self) -> usize {
self.next_name.len()
+ self
.type_bit_maps
.iter()
.map(|el| el.bitmap.len() + 2) .sum::<usize>()
}
}
impl<'a> NSEC<'a> {
pub fn from_types(next_name: Name<'a>, types: &[TYPE]) -> Self {
let mut windows: BTreeMap<u8, [u8; 32]> = BTreeMap::new();
let mut max_bytes: BTreeMap<u8, usize> = BTreeMap::new();
for &ty in types {
let type_code = u16::from(ty);
let window = (type_code / 256) as u8;
let offset_in_window = (type_code % 256) as usize;
let byte_idx = offset_in_window / 8;
let bit_idx = offset_in_window % 8;
let bitmap = windows.entry(window).or_insert([0u8; 32]);
bitmap[byte_idx] |= 0x80 >> bit_idx;
let max_byte = max_bytes.entry(window).or_insert(0);
if byte_idx > *max_byte {
*max_byte = byte_idx;
}
}
let type_bit_maps = windows
.into_iter()
.map(|(window_block, bitmap)| {
let len = max_bytes.get(&window_block).copied().unwrap_or(0) + 1;
NsecTypeBitMap {
window_block,
bitmap: Cow::Owned(bitmap[..len].to_vec()),
}
})
.collect();
Self {
next_name,
type_bit_maps,
}
}
pub fn contains(&self, ty: TYPE) -> bool {
let type_code = u16::from(ty);
let window = (type_code / 256) as u8;
let offset_in_window = (type_code % 256) as usize;
let byte_idx = offset_in_window / 8;
let bit_mask = 0x80 >> (offset_in_window % 8);
for map in &self.type_bit_maps {
if map.window_block == window {
return map
.bitmap
.get(byte_idx)
.is_some_and(|byte| (byte & bit_mask) != 0);
}
}
false
}
pub fn types(&self) -> impl Iterator<Item = TYPE> + '_ {
self.type_bit_maps.iter().flat_map(|map| {
let window_base = (map.window_block as u16) * 256;
map.bitmap
.iter()
.enumerate()
.flat_map(move |(byte_idx, &byte)| {
(0..8).filter_map(move |bit_idx| {
if (byte & (0x80 >> bit_idx)) != 0 {
let type_code = window_base + (byte_idx as u16) * 8 + (bit_idx as u16);
Some(TYPE::from(type_code))
} else {
None
}
})
})
})
}
pub fn into_owned<'b>(self) -> NSEC<'b> {
let type_bit_maps = self
.type_bit_maps
.into_iter()
.map(|x| NsecTypeBitMap {
window_block: x.window_block,
bitmap: x.bitmap.into_owned().into(),
})
.collect();
NSEC {
next_name: self.next_name.into_owned(),
type_bit_maps,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lib::vec;
#[test]
fn parse_and_write_nsec() {
let nsec = NSEC {
next_name: Name::new("host.example.com.").unwrap(),
type_bit_maps: vec![NsecTypeBitMap {
window_block: 0,
bitmap: vec![64, 1, 0, 0, 0, 1].into(),
}],
};
let mut data = Vec::new();
nsec.write_to(&mut data).unwrap();
let nsec = NSEC::parse(&mut data[..].into()).unwrap();
assert_eq!(nsec.next_name, Name::new("host.example.com.").unwrap());
assert_eq!(nsec.type_bit_maps.len(), 1);
assert_eq!(nsec.type_bit_maps[0].window_block, 0);
assert_eq!(nsec.type_bit_maps[0].bitmap, vec![64, 1, 0, 0, 0, 1]);
}
#[test]
fn nsec_reports_correct_len() {
let nsec = NSEC {
next_name: Name::new("host.example.com.").unwrap(),
type_bit_maps: vec![
NsecTypeBitMap {
window_block: 0,
bitmap: vec![0x40, 0x01, 0x00, 0x00, 0x00, 0x01].into(),
},
NsecTypeBitMap {
window_block: 1,
bitmap: vec![0x40, 0x01, 0x00, 0x00, 0x00, 0x01].into(),
},
],
};
let mut buffer = Vec::new();
nsec.write_to(&mut buffer).unwrap();
assert_eq!(buffer.len(), nsec.len());
}
#[test]
#[cfg(feature = "std")]
fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
use crate::{rdata::RData, ResourceRecord};
let sample_file = std::fs::read("samples/zonefile/NSEC.sample")?;
let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata {
RData::NSEC(rdata) => rdata,
_ => unreachable!(),
};
assert_eq!(
sample_rdata.next_name,
Name::new("host.example.com.").unwrap()
);
assert_eq!(sample_rdata.type_bit_maps.len(), 1);
assert_eq!(sample_rdata.type_bit_maps[0].window_block, 0);
assert_eq!(
sample_rdata.type_bit_maps[0].bitmap,
vec![64, 1, 0, 0, 0, 1]
);
Ok(())
}
#[test]
fn test_nsec_from_types_and_helpers() {
let name = Name::new("host.example.com.").unwrap();
let types = [TYPE::A, TYPE::MX, TYPE::NSEC, TYPE::Unknown(300)];
let nsec = NSEC::from_types(name.clone(), &types);
assert_eq!(nsec.next_name, name);
assert_eq!(nsec.type_bit_maps.len(), 2);
assert_eq!(nsec.type_bit_maps[0].window_block, 0);
assert_eq!(nsec.type_bit_maps[0].bitmap, vec![64, 1, 0, 0, 0, 1]);
assert_eq!(nsec.type_bit_maps[1].window_block, 1);
assert!(nsec.contains(TYPE::A));
assert!(nsec.contains(TYPE::MX));
assert!(nsec.contains(TYPE::NSEC));
assert!(nsec.contains(TYPE::Unknown(300)));
assert!(!nsec.contains(TYPE::AAAA));
assert!(!nsec.contains(TYPE::TXT));
let recovered: Vec<_> = nsec.types().collect();
assert_eq!(recovered, types);
assert_eq!(u16::from(crate::CLASS::IN), 1);
assert_eq!(u16::from(crate::CLASS::CS), 2);
}
}