diff --git a/examples/read-zone.rs b/examples/read-zone.rs index 110aaa804..7b8393ec8 100644 --- a/examples/read-zone.rs +++ b/examples/read-zone.rs @@ -42,7 +42,7 @@ fn main() { ); eprintln!(" Error: {err}"); if let Some(entry) = &last_entry { - if let Entry::Record(record) = &entry { + if let Entry::Record(record, _) = &entry { eprintln!( "\nThe last record read was:\n{record}." ); diff --git a/src/rdata/zonemd.rs b/src/rdata/zonemd.rs index 66f41d400..630a8dc79 100644 --- a/src/rdata/zonemd.rs +++ b/src/rdata/zonemd.rs @@ -462,7 +462,7 @@ ns2 3600 IN AAAA 2001:db8::63 zone.set_origin(Name::root()); while let Some(entry) = zone.next_entry().unwrap() { match entry { - Entry::Record(record) => { + Entry::Record(record, _) => { if record.rtype() != Rtype::ZONEMD { continue; } diff --git a/src/sign/records.rs b/src/sign/records.rs index 7b98ba99b..d1838b949 100644 --- a/src/sign/records.rs +++ b/src/sign/records.rs @@ -146,6 +146,14 @@ where self.records.iter() } + pub fn len(&self) -> usize { + self.records.len() + } + + pub fn is_empty(&self) -> bool { + self.records.is_empty() + } + pub fn as_slice(&self) -> &[Record] { self.records.as_slice() } @@ -279,9 +287,10 @@ where /// [RFC 5155]: https://www.rfc-editor.org/rfc/rfc5155.html /// [RFC 9077]: https://www.rfc-editor.org/rfc/rfc9077.html /// [RFC 9276]: https://www.rfc-editor.org/rfc/rfc9276.html + #[allow(clippy::too_many_arguments)] // TODO: Move to Signer and do HashProvider = OnDemandNsec3HashProvider // TODO: Does it make sense to take both Nsec3param AND HashProvider as input? - pub fn nsec3s( + pub fn nsec3s( &self, apex: &FamilyName, ttl: Ttl, @@ -289,6 +298,7 @@ where opt_out: Nsec3OptOut, assume_dnskeys_will_be_added: bool, hash_provider: &mut HashProvider, + progress_cb: CB, ) -> Result, Nsec3HashError> where N: ToName + Clone + From> + Display + Ord + Hash, @@ -303,6 +313,7 @@ where + EmptyBuilder + FreezeBuilder, ::Octets: AsRef<[u8]>, + CB: Fn(usize, usize, Option<&'static str>), HashProvider: Nsec3HashProvider, Nsec3: Into, { @@ -332,6 +343,9 @@ where // The owner name of a zone cut if we currently are at or below one. let mut cut: Option> = None; + let num_families = self.families().count(); + (progress_cb)(0, num_families, Some("Creating NSEC3 RRs")); + let mut families = self.families(); // Since the records are ordered, the first family is the apex -- @@ -353,12 +367,14 @@ where // If the owner is out of zone, we have moved out of our zone and // are done. if !family.is_in_zone(apex) { + (progress_cb)(1, 0, None); break; } // If the family is below a zone cut, we must ignore it. if let Some(ref cut) = cut { if family.owner().ends_with(cut.owner()) { + (progress_cb)(1, 0, None); continue; } } @@ -380,6 +396,7 @@ where // delegations MAY be excluded." let has_ds = family.records().any(|rec| rec.rtype() == Rtype::DS); if cut.is_some() && !has_ds && opt_out == Nsec3OptOut::OptOut { + (progress_cb)(1, 0, None); continue; } @@ -435,6 +452,7 @@ where builder.append_origin(&apex_owner).unwrap().into(); if let Err(pos) = ents.binary_search(&name) { + (progress_cb)(n, 1, None); ents.insert(pos, name); } } @@ -487,8 +505,10 @@ where last_nent_stack.push(last_nent); } last_nent_stack.push(name.owner().clone()); + (progress_cb)(1, 1, None); } + (progress_cb)(0, 0, Some("Creating ENTs NSEC3 RRs")); for name in ents { // Create the type bitmap, empty for an ENT NSEC3. let bitmap = RtypeBitmap::::builder(); @@ -511,6 +531,8 @@ where // Store the record by order of its owner name. nsec3s.push(rec); + + (progress_cb)(1, 1, None); } // RFC 5155 7.1 step 7: @@ -518,7 +540,9 @@ where // value of the next NSEC3 RR in hash order. The next hashed owner // name of the last NSEC3 RR in the zone contains the value of the // hashed owner name of the first NSEC3 RR in the hash order." + (progress_cb)(0, 0, Some("Sorting")); let mut nsec3s = SortedRecords::, S>::from(nsec3s); + (progress_cb)(0, 0, Some("Hashing NSEC3 owner names")); for i in 1..=nsec3s.records.len() { // TODO: Detect duplicate hashes. let next_i = if i == nsec3s.records.len() { 0 } else { i }; @@ -536,6 +560,7 @@ where let last_rec = &mut nsec3s.records[i - 1]; let last_nsec3: &mut Nsec3 = last_rec.data_mut(); last_nsec3.set_next_owner(owner_hash.clone()); + (progress_cb)(1, 0, None); } // RFC 5155 7.1 step 8: @@ -1287,50 +1312,78 @@ impl SigningKeyUsageStrategy const NAME: &'static str = "Default key usage strategy"; } +pub trait ProgressReporter { + fn make_progress(&self, _increment: usize) {} + + fn add_work(&self, _increment: usize) {} + + fn change_phase(&self, _new_phase: &'static str) {} +} + +impl ProgressReporter for () {} + pub struct Signer< Octs, Inner, KeyStrat = DefaultSigningKeyUsageStrategy, Sort = DefaultSorter, + Progress = (), > where Inner: SignRaw, KeyStrat: SigningKeyUsageStrategy, Sort: Sorter, + Progress: ProgressReporter, { + progress_reporter: Option, + _phantom: PhantomData<(Octs, Inner, KeyStrat, Sort)>, } -impl Default - for Signer +impl Default + for Signer where Inner: SignRaw, KeyStrat: SigningKeyUsageStrategy, Sort: Sorter, + Progress: ProgressReporter, { fn default() -> Self { Self::new() } } -impl Signer +impl + Signer where Inner: SignRaw, KeyStrat: SigningKeyUsageStrategy, Sort: Sorter, + Progress: ProgressReporter, { pub fn new() -> Self { Self { + progress_reporter: None, _phantom: PhantomData, } } + + pub fn with_progress_reporter( + mut self, + progress_reporter: Progress, + ) -> Self { + self.progress_reporter = Some(progress_reporter); + self + } } -impl Signer +impl + Signer where Octs: AsRef<[u8]> + From> + OctetsFrom>, Inner: SignRaw, KeyStrat: SigningKeyUsageStrategy, Sort: Sorter, + Progress: ProgressReporter, { /// Sign a zone using the given keys. /// @@ -1424,6 +1477,17 @@ where } } + if let Some(reporter) = &self.progress_reporter { + let (num_families_low, num_families_high) = families.size_hint(); + let estimated_num_families = std::cmp::max( + num_families_low, + num_families_high.unwrap_or_default(), + ); + reporter.add_work(estimated_num_families); + reporter.add_work(keys_in_use_idxs.len()); + reporter.change_phase("Creating DNSKEYs"); + } + let mut res: Vec>> = Vec::new(); let mut buf = Vec::new(); let mut cut: Option> = None; @@ -1545,6 +1609,10 @@ where ); } } + + if let Some(progress) = &self.progress_reporter { + progress.make_progress(1); + } } // For all RRSETs below the apex @@ -1552,12 +1620,18 @@ where // If the owner is out of zone, we have moved out of our zone and // are done. if !family.is_in_zone(apex) { + if let Some(progress) = &self.progress_reporter { + progress.make_progress(1); + } break; } // If the family is below a zone cut, we must ignore it. if let Some(ref cut) = cut { if family.owner().ends_with(cut.owner()) { + if let Some(progress) = &self.progress_reporter { + progress.make_progress(1); + } continue; } } @@ -1605,6 +1679,10 @@ where ); } } + + if let Some(progress) = &self.progress_reporter { + progress.make_progress(1); + } } debug!("Returning {} records from signing", res.len()); @@ -1726,7 +1804,10 @@ where //------------ Nsec3HashProvider --------------------------------------------- pub trait Nsec3HashProvider { - fn get_or_create(&mut self, unhashed_owner_name: &N) -> Result; + fn get_or_create( + &mut self, + unhashed_owner_name: &N, + ) -> Result; } pub struct OnDemandNsec3HashProvider { @@ -1772,7 +1853,10 @@ where ::Builder: EmptyBuilder + AsRef<[u8]> + AsMut<[u8]>, SaltOcts: AsRef<[u8]>, { - fn get_or_create(&mut self, unhashed_owner_name: &N) -> Result { + fn get_or_create( + &mut self, + unhashed_owner_name: &N, + ) -> Result { mk_hashed_nsec3_owner_name( unhashed_owner_name, self.alg, diff --git a/src/stelline/client.rs b/src/stelline/client.rs index 63e194b93..3dd450fcb 100644 --- a/src/stelline/client.rs +++ b/src/stelline/client.rs @@ -778,13 +778,13 @@ fn entry2msg(entry: &Entry) -> (&Sections, Reply, Message>) { } let mut msg = msg.authority(); for zone_file_entry in §ions.authority { - if let Record(rec) = zone_file_entry { + if let Record(rec, _) = zone_file_entry { msg.push(rec).unwrap(); } } let mut msg = msg.additional(); for zone_file_entry in §ions.additional.zone_entries { - if let Record(rec) = zone_file_entry { + if let Record(rec, _) = zone_file_entry { msg.push(rec).unwrap(); } } diff --git a/src/stelline/matches.rs b/src/stelline/matches.rs index 3aa073f70..c47da1e50 100644 --- a/src/stelline/matches.rs +++ b/src/stelline/matches.rs @@ -376,7 +376,7 @@ fn match_section< } for (index, mat_rr) in match_section.iter().enumerate() { // Remove outer Record - let mat_rr = if let ZonefileEntry::Record(record) = mat_rr { + let mat_rr = if let ZonefileEntry::Record(record, _) = mat_rr { record } else { panic!("include not expected"); diff --git a/src/stelline/server.rs b/src/stelline/server.rs index b9d06da9d..8ff0874ab 100644 --- a/src/stelline/server.rs +++ b/src/stelline/server.rs @@ -95,7 +95,7 @@ where } let mut msg = msg.answer(); for a in §ions.answer[0] { - let rec = if let ZonefileEntry::Record(record) = a { + let rec = if let ZonefileEntry::Record(record, _) = a { record } else { panic!("include not expected") @@ -104,7 +104,7 @@ where } let mut msg = msg.authority(); for a in §ions.authority { - let rec = if let ZonefileEntry::Record(record) = a { + let rec = if let ZonefileEntry::Record(record, _) = a { record } else { panic!("include not expected") @@ -113,7 +113,7 @@ where } let mut msg = msg.additional(); for a in §ions.additional.zone_entries { - let rec = if let ZonefileEntry::Record(record) = a { + let rec = if let ZonefileEntry::Record(record, _) = a { record } else { panic!("include not expected") diff --git a/src/validator/anchor.rs b/src/validator/anchor.rs index ef6a70042..37a6eb68e 100644 --- a/src/validator/anchor.rs +++ b/src/validator/anchor.rs @@ -103,7 +103,7 @@ impl TrustAnchors { for e in zonefile { let e = e?; match e { - Entry::Record(r) => { + Entry::Record(r, _) => { new_self.add(r); } Entry::Include { path: _, origin: _ } => continue, // Just ignore include @@ -123,7 +123,7 @@ impl TrustAnchors { for e in zonefile { let e = e?; match e { - Entry::Record(r) => { + Entry::Record(r, _) => { new_self.add(r); } Entry::Include { path: _, origin: _ } => continue, // Just ignore include @@ -142,7 +142,7 @@ impl TrustAnchors { for e in zonefile { let e = e?; match e { - Entry::Record(r) => { + Entry::Record(r, _) => { self.add(r); } Entry::Include { path: _, origin: _ } => continue, // Just ignore include diff --git a/src/zonefile/inplace.rs b/src/zonefile/inplace.rs index 98451d8ef..8231e2254 100644 --- a/src/zonefile/inplace.rs +++ b/src/zonefile/inplace.rs @@ -110,6 +110,18 @@ impl Zonefile { std::io::copy(read, &mut buf)?; Ok(buf.into_inner()) } + + pub fn pos(&self) -> usize { + self.buf.pos() + } + + pub fn len(&self) -> usize { + self.buf.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } impl Default for Zonefile { @@ -213,8 +225,9 @@ impl Iterator for Zonefile { /// An entry of a zonefile. #[derive(Clone, Debug)] pub enum Entry { - /// A DNS record. - Record(ScannedRecord), + /// A DNS record and the approximate index into the source buffer after + /// the record was read from it. + Record(ScannedRecord, usize), /// An include directive. /// @@ -362,9 +375,10 @@ impl<'a> EntryScanner<'a> { self.zonefile.buf.require_line_feed()?; - Ok(ScannedEntry::Entry(Entry::Record(Record::new( - owner, class, ttl, data, - )))) + Ok(ScannedEntry::Entry(Entry::Record( + Record::new(owner, class, ttl, data), + self.zonefile.pos(), + ))) } /// Scans the TTL, class, and type portions of a regular record. @@ -985,6 +999,9 @@ struct SourceBuf { /// /// This may be negative if we cut off bits of the current line. line_start: isize, + + /// The number of bytes read so far from the start of the data given to Zonefile. + pos: usize, } impl SourceBuf { @@ -1002,9 +1019,18 @@ impl SourceBuf { parens: 0, line_num: 1, line_start: 1, + pos: 0, } } + fn pos(&self) -> usize { + self.pos + } + + fn len(&self) -> usize { + self.buf.len() + } + /// Enriches an entry error with position information. fn error(&self, err: EntryError) -> Error { Error { @@ -1379,6 +1405,7 @@ impl SourceBuf { fn split_to(&mut self, at: usize) -> BytesMut { assert!(at <= self.start); let res = self.buf.split_to(at); + self.pos += at; self.start -= at; self.line_start -= at as isize; res @@ -1392,6 +1419,7 @@ impl SourceBuf { fn trim_to(&mut self, at: usize) { assert!(at <= self.start); self.buf.advance(at); + self.pos += at; self.start -= at; self.line_start -= at as isize; } @@ -1614,7 +1642,7 @@ mod test { let mut result = case.result.as_slice(); while let Some(entry) = zone.next_entry().unwrap() { match entry { - Entry::Record(record) => { + Entry::Record(record, _) => { let (first, tail) = result.split_first().unwrap(); assert_eq!(first, &record); result = tail; diff --git a/src/zonetree/parsed.rs b/src/zonetree/parsed.rs index dc4cab13f..296ad5bb0 100644 --- a/src/zonetree/parsed.rs +++ b/src/zonetree/parsed.rs @@ -309,7 +309,7 @@ impl TryFrom for Zonefile { for res in source { match res.map_err(RecordError::MalformedRecord) { - Ok(Entry::Record(r)) => { + Ok(Entry::Record(r, _)) => { let stored_rec = r.flatten_into(); let name = stored_rec.owner().clone(); if let Err(err) = zonefile.insert(stored_rec) {