Skip to content

Commit 2cfcbe3

Browse files
committed
Introduce a new 'zonefile' module
This is a manual squash of #491 for internal testing.
1 parent f549b77 commit 2cfcbe3

32 files changed

Lines changed: 6397 additions & 78 deletions

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ net = ["bytes", "futures-util", "rand", "std", "tokio"]
7878
resolv = ["net", "smallvec", "unstable-client-transport"]
7979
resolv-sync = ["resolv", "tokio/rt"]
8080
tsig = ["bytes", "ring", "smallvec"]
81-
zonefile = ["bytes", "serde", "std"]
81+
zonefile = ["bytes", "serde", "std", "bumpalo"] # new: ["std", "bumpalo", "dep:time"]
8282

8383
# Unstable features
8484
unstable-new = []
@@ -152,7 +152,7 @@ required-features = ["net", "tokio-stream", "tracing-subscriber", "unstable-clie
152152

153153
[[example]]
154154
name = "read-zone"
155-
required-features = ["zonefile"]
155+
required-features = ["zonefile", "unstable-new"]
156156

157157
[[example]]
158158
name = "query-zone"

examples/read-zone.rs

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
//! Reads a zone file.
22
3-
use std::env;
43
use std::fs::File;
54
use std::process::exit;
65
use std::time::SystemTime;
6+
use std::{env, io::BufReader};
77

8-
use domain::zonefile::inplace::Entry;
9-
use domain::zonefile::inplace::Zonefile;
8+
use domain::new::zonefile::simple::ZonefileScanner;
109

1110
fn main() {
1211
let mut args = env::args();
@@ -21,40 +20,17 @@ fn main() {
2120
for zone_file in zone_files {
2221
print!("Processing {zone_file}: ");
2322
let start = SystemTime::now();
24-
let mut reader =
25-
Zonefile::load(&mut File::open(&zone_file).unwrap()).unwrap();
26-
println!(
27-
"Data loaded ({:.03}s).",
28-
start.elapsed().unwrap().as_secs_f32()
29-
);
23+
let file = BufReader::new(File::open(&zone_file).unwrap());
24+
let mut scanner = ZonefileScanner::new(file, None);
3025

3126
let mut i = 0;
32-
let mut last_entry = None;
33-
loop {
34-
match reader.next_entry() {
35-
Ok(entry) if entry.is_some() => {
36-
last_entry = entry;
37-
}
38-
Ok(_) => break, // EOF
39-
Err(err) => {
40-
eprintln!(
41-
"\nAn error occurred while reading {zone_file}:"
42-
);
43-
eprintln!(" Error: {err}");
44-
if let Some(entry) = &last_entry {
45-
if let Entry::Record(record) = &entry {
46-
eprintln!(
47-
"\nThe last record read was:\n{record}."
48-
);
49-
} else {
50-
eprintln!("\nThe last record read was:\n{last_entry:#?}.");
51-
}
52-
eprintln!("\nTry commenting out the line after that record with a leading ; (semi-colon) character.")
53-
}
54-
exit(1);
55-
}
56-
}
27+
while let Some(entry) = scanner.scan().transpose() {
5728
i += 1;
29+
if let Err(err) = entry {
30+
eprintln!("Could not parse {zone_file}: {err}");
31+
exit(1);
32+
}
33+
5834
if i % 100_000_000 == 0 {
5935
println!(
6036
"Processed {}M records ({:.03}s)",

src/new/base/charstr.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use core::str::FromStr;
77

88
use crate::utils::dst::{UnsizedCopy, UnsizedCopyFrom};
99

10+
#[cfg(feature = "zonefile")]
11+
use crate::new::zonefile::scanner::{Scan, ScanError, Scanner};
12+
1013
use super::{
1114
build::{BuildInMessage, NameCompressor},
1215
parse::{ParseMessageBytes, SplitMessageBytes},
@@ -437,6 +440,42 @@ impl fmt::Display for CharStrParseError {
437440
}
438441
}
439442

443+
//--- Parsing from the zonefile format
444+
445+
#[cfg(feature = "zonefile")]
446+
impl<'a> Scan<'a> for &'a CharStr {
447+
/// Scan a character string.
448+
///
449+
/// This parses the `d-word` syntax from [the specification].
450+
///
451+
/// [the specification]: crate::new::zonefile#specification
452+
fn scan(
453+
scanner: &mut Scanner<'_>,
454+
alloc: &'a bumpalo::Bump,
455+
buffer: &mut std::vec::Vec<u8>,
456+
) -> Result<Self, ScanError> {
457+
let start = buffer.len();
458+
match scanner.scan_token(buffer)? {
459+
Some(token) if token.len() > 255 => {
460+
buffer.truncate(start);
461+
Err(ScanError::Custom("overlong character string"))
462+
}
463+
464+
Some(token) => {
465+
let bytes = alloc.alloc_slice_copy(token);
466+
buffer.truncate(start);
467+
// SAFETY: 'token' consists of up to 255 bytes.
468+
Ok(unsafe { core::mem::transmute::<&[u8], Self>(bytes) })
469+
}
470+
471+
None => {
472+
buffer.truncate(start);
473+
Err(ScanError::Incomplete)
474+
}
475+
}
476+
}
477+
}
478+
440479
//============ Tests =========================================================
441480

442481
#[cfg(test)]
@@ -464,4 +503,28 @@ mod test {
464503
);
465504
assert_eq!(buffer, &bytes[..6]);
466505
}
506+
507+
#[cfg(feature = "zonefile")]
508+
#[test]
509+
fn scan() {
510+
use crate::new::zonefile::scanner::{Scan, ScanError, Scanner};
511+
512+
let cases = [
513+
(b"hello" as &[u8], Ok(b"hello" as &[u8])),
514+
(b"\"hi\"and\"bye\"" as &[u8], Ok(b"hiandbye")),
515+
(b"\"\"" as &[u8], Ok(b"")),
516+
(b"" as &[u8], Err(ScanError::Incomplete)),
517+
];
518+
519+
let alloc = bumpalo::Bump::new();
520+
let mut buffer = std::vec::Vec::new();
521+
for (input, expected) in cases {
522+
let mut scanner = Scanner::new(input, None);
523+
assert_eq!(
524+
<&CharStr>::scan(&mut scanner, &alloc, &mut buffer)
525+
.map(|c| &c.octets),
526+
expected
527+
);
528+
}
529+
}
467530
}

src/new/base/name/absolute.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use crate::{
2121
utils::dst::{UnsizedCopy, UnsizedCopyFrom},
2222
};
2323

24+
#[cfg(feature = "zonefile")]
25+
use crate::new::zonefile::scanner::{Scan, ScanError, Scanner};
26+
2427
use super::{
2528
CanonicalName, Label, LabelBuf, LabelIter, LabelParseError,
2629
NameCompressor,
@@ -190,6 +193,35 @@ impl BuildInMessage for Name {
190193
}
191194
}
192195

196+
//--- Parsing from the zonefile format
197+
198+
#[cfg(feature = "zonefile")]
199+
impl<'a> Scan<'a> for &'a Name {
200+
/// Scan a domain name token.
201+
///
202+
/// This parses a domain name, following the [specification].
203+
///
204+
/// [specification]: crate::new::zonefile#specification
205+
fn scan(
206+
scanner: &mut Scanner<'_>,
207+
alloc: &'a bumpalo::Bump,
208+
buffer: &mut std::vec::Vec<u8>,
209+
) -> Result<Self, ScanError> {
210+
let name = NameBuf::scan(scanner, alloc, buffer)?;
211+
let bytes = alloc.alloc_slice_copy(name.as_bytes());
212+
Ok(unsafe { Name::from_bytes_unchecked(bytes) })
213+
}
214+
}
215+
216+
//--- Cloning
217+
218+
#[cfg(feature = "alloc")]
219+
impl Clone for alloc::boxed::Box<Name> {
220+
fn clone(&self) -> Self {
221+
(*self).unsized_copy_into()
222+
}
223+
}
224+
193225
//--- Equality
194226

195227
impl PartialEq for Name {
@@ -575,6 +607,102 @@ impl FromStr for NameBuf {
575607
}
576608
}
577609

610+
//--- Parsing from the zonefile format
611+
612+
#[cfg(feature = "zonefile")]
613+
impl Scan<'_> for NameBuf {
614+
/// Scan a domain name token.
615+
///
616+
/// This parses a domain name, following the [specification].
617+
///
618+
/// [specification]: crate::new::zonefile#specification
619+
fn scan(
620+
scanner: &mut Scanner<'_>,
621+
alloc: &'_ bumpalo::Bump,
622+
buffer: &mut std::vec::Vec<u8>,
623+
) -> Result<Self, ScanError> {
624+
// Build up a 'Name'.
625+
let mut this = Self::empty();
626+
627+
// Try parsing '@', indicating the origin name.
628+
if let [b'@', b' ' | b'\t' | b'\r' | b'\n', ..] | [b'@'] =
629+
scanner.remaining()
630+
{
631+
scanner.consume(1);
632+
let origin = scanner
633+
.origin()
634+
.ok_or(ScanError::Custom("unknown origin name"))?;
635+
636+
origin
637+
.build_bytes(&mut this.buffer)
638+
.expect("Valid 'RevName's are at most 255 bytes");
639+
this.size = origin.len() as u8;
640+
return Ok(this);
641+
}
642+
643+
while let Some(&c) = scanner.remaining().first() {
644+
if c.is_ascii_whitespace() {
645+
break;
646+
}
647+
648+
if !c.is_ascii_alphanumeric() && !b"\\-_".contains(&c) {
649+
return Err(ScanError::Custom(
650+
"irregular character in domain name",
651+
));
652+
}
653+
654+
// Parse a label and prepend it to the buffer.
655+
let label = LabelBuf::scan(scanner, alloc, buffer)?;
656+
if 255 - this.size < 1 + label.as_bytes().len() as u8 {
657+
return Err(ScanError::Custom(
658+
"domain name exceeds 255 bytes",
659+
));
660+
}
661+
this.append_label(&label);
662+
663+
// Check if this is the end of the domain name.
664+
match scanner.remaining() {
665+
&[b' ' | b'\t' | b'\r' | b'\n', ..] | &[] => {
666+
// This is a relative domain name.
667+
let origin = scanner
668+
.origin()
669+
.ok_or(ScanError::Custom("unknown origin name"))?;
670+
671+
// Append the origin to this name.
672+
origin
673+
.build_bytes(&mut this.buffer[this.size as usize..])
674+
.map_err(|_| {
675+
ScanError::Custom(
676+
"relative domain name exceeds 255 bytes",
677+
)
678+
})?;
679+
// We exclude the root label, which gets added manually.
680+
this.size += origin.len() as u8 - 1;
681+
break;
682+
}
683+
684+
&[b'.', ..] => {
685+
scanner.consume(1);
686+
}
687+
688+
_ => {
689+
return Err(ScanError::Custom(
690+
"irregular character in domain name",
691+
));
692+
}
693+
}
694+
}
695+
696+
if this.size == 0 {
697+
return Err(ScanError::Incomplete);
698+
}
699+
700+
// Add a root label and stop.
701+
this.append_label(Label::ROOT);
702+
Ok(this)
703+
}
704+
}
705+
578706
//--- Access to the underlying 'Name'
579707

580708
impl Deref for NameBuf {
@@ -692,3 +820,61 @@ impl fmt::Display for NameParseError {
692820
})
693821
}
694822
}
823+
824+
//============ Unit tests ====================================================
825+
826+
#[cfg(test)]
827+
mod test {
828+
#[cfg(feature = "zonefile")]
829+
#[test]
830+
fn scan() {
831+
use std::vec::Vec;
832+
833+
use crate::{
834+
new::base::name::RevNameBuf,
835+
new::zonefile::scanner::{Scan, ScanError, Scanner},
836+
};
837+
838+
use super::NameBuf;
839+
840+
let cases = [
841+
(b"".as_slice(), Err(ScanError::Incomplete)),
842+
(b" ".as_slice(), Err(ScanError::Incomplete)),
843+
(b"a", Ok(&[b"a" as &[u8], b"org", b""] as &[&[u8]])),
844+
(b"xn--hello.", Ok(&[b"xn--hello", b""])),
845+
(
846+
b"hello\\.world.sld",
847+
Ok(&[b"hello.world", b"sld", b"org", b""]),
848+
),
849+
(b"a\\046b.c.", Ok(&[b"a.b", b"c", b""])),
850+
(b"a.b\\ c.d", Ok(&[b"a", b"b c", b"d", b"org", b""])),
851+
];
852+
853+
let alloc = bumpalo::Bump::new();
854+
let mut buffer = Vec::new();
855+
for (input, expected) in cases {
856+
let origin = "org".parse::<RevNameBuf>().unwrap();
857+
let mut scanner = Scanner::new(input, Some(&origin));
858+
let mut name_buf = None;
859+
let actual = NameBuf::scan(&mut scanner, &alloc, &mut buffer)
860+
.map(|name| name_buf.insert(name).labels());
861+
match expected {
862+
Ok(labels) => {
863+
assert!(
864+
actual.clone().is_ok_and(|actual| actual
865+
.map(|l| &l.as_bytes()[1..])
866+
.eq(labels.iter().copied())),
867+
"{actual:?} == Ok({labels:?})"
868+
);
869+
}
870+
871+
Err(err) => {
872+
assert!(
873+
actual.clone().is_err_and(|e| e == err),
874+
"{actual:?} == Err({err:?})"
875+
);
876+
}
877+
}
878+
}
879+
}
880+
}

0 commit comments

Comments
 (0)