1use crate::base::cmp::CanonicalOrd;
8use crate::base::iana::Rtype;
9use crate::base::rdata::{ComposeRecordData, RecordData};
10use crate::base::scan::{Scan, Scanner};
11use crate::base::serial::Serial;
12use crate::base::wire::{Composer, ParseError};
13use crate::utils::base16;
14use core::cmp::Ordering;
15use core::{fmt, hash};
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19const DIGEST_MIN_LEN: usize = 12;
21
22#[derive(Clone)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Zonemd<Octs: ?Sized> {
26 serial: Serial,
27 scheme: Scheme,
28 algo: Algorithm,
29 #[cfg_attr(
30 feature = "serde",
31 serde(
32 serialize_with = "octseq::serde::SerializeOctets::serialize_octets",
33 deserialize_with = "octseq::serde::DeserializeOctets::deserialize_octets",
34 bound(
35 serialize = "Octs: octseq::serde::SerializeOctets",
36 deserialize = "Octs: octseq::serde::DeserializeOctets<'de>",
37 )
38 )
39 )]
40 digest: Octs,
41}
42
43impl<Octs> Zonemd<Octs> {
44 pub fn new(
46 serial: Serial,
47 scheme: Scheme,
48 algo: Algorithm,
49 digest: Octs,
50 ) -> Self {
51 Self {
52 serial,
53 scheme,
54 algo,
55 digest,
56 }
57 }
58
59 pub fn serial(&self) -> Serial {
61 self.serial
62 }
63
64 pub fn scheme(&self) -> Scheme {
66 self.scheme
67 }
68
69 pub fn algorithm(&self) -> Algorithm {
71 self.algo
72 }
73
74 pub fn digest(&self) -> &Octs {
76 &self.digest
77 }
78
79 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
81 parser: &mut Parser<'a, Src>,
82 ) -> Result<Self, ParseError> {
83 let serial = Serial::parse(parser)?;
84 let scheme = parser.parse_u8()?.into();
85 let algo = parser.parse_u8()?.into();
86 let len = parser.remaining();
87 if len < DIGEST_MIN_LEN {
88 return Err(ParseError::ShortInput);
89 }
90 let digest = parser.parse_octets(len)?;
91 Ok(Self {
92 serial,
93 scheme,
94 algo,
95 digest,
96 })
97 }
98
99 pub fn scan<S: Scanner<Octets = Octs>>(
101 scanner: &mut S,
102 ) -> Result<Self, S::Error> {
103 let serial = Serial::scan(scanner)?;
104 let scheme = u8::scan(scanner)?.into();
105 let algo = u8::scan(scanner)?.into();
106 let digest = scanner.convert_entry(base16::SymbolConverter::new())?;
107
108 Ok(Self {
109 serial,
110 scheme,
111 algo,
112 digest,
113 })
114 }
115
116 pub(super) fn flatten<Target: OctetsFrom<Octs>>(
117 self
118 ) -> Result<Zonemd<Target>, Target::Error> {
119 self.convert_octets()
120 }
121
122 pub(super) fn convert_octets<Target: OctetsFrom<Octs>>(
123 self,
124 ) -> Result<Zonemd<Target>, Target::Error> {
125 let Zonemd {
126 serial,
127 scheme,
128 algo,
129 digest,
130 } = self;
131
132 Ok(Zonemd {
133 serial,
134 scheme,
135 algo,
136 digest: digest.try_octets_into()?,
137 })
138 }
139}
140
141impl<Octs> RecordData for Zonemd<Octs> {
142 fn rtype(&self) -> Rtype {
143 Rtype::Zonemd
144 }
145}
146
147impl<Octs: AsRef<[u8]>> ComposeRecordData for Zonemd<Octs> {
148 fn rdlen(&self, _compress: bool) -> Option<u16> {
149 Some(
150 u16::try_from(4 + 1 + 1 + self.digest.as_ref().len())
152 .expect("long ZONEMD rdata"),
153 )
154 }
155
156 fn compose_rdata<Target: Composer + ?Sized>(
157 &self,
158 target: &mut Target,
159 ) -> Result<(), Target::AppendError> {
160 target.append_slice(&self.serial.into_int().to_be_bytes())?;
161 target.append_slice(&[self.scheme.into()])?;
162 target.append_slice(&[self.algo.into()])?;
163 target.append_slice(self.digest.as_ref())
164 }
165
166 fn compose_canonical_rdata<Target: Composer + ?Sized>(
167 &self,
168 target: &mut Target,
169 ) -> Result<(), Target::AppendError> {
170 self.compose_rdata(target)
171 }
172}
173
174impl<Octs: AsRef<[u8]>> hash::Hash for Zonemd<Octs> {
175 fn hash<H: hash::Hasher>(&self, state: &mut H) {
176 self.serial.hash(state);
177 self.scheme.hash(state);
178 self.algo.hash(state);
179 self.digest.as_ref().hash(state);
180 }
181}
182
183impl<Octs, Other> PartialEq<Zonemd<Other>> for Zonemd<Octs>
184where
185 Octs: AsRef<[u8]> + ?Sized,
186 Other: AsRef<[u8]> + ?Sized,
187{
188 fn eq(&self, other: &Zonemd<Other>) -> bool {
189 self.serial.eq(&other.serial)
190 && self.scheme.eq(&other.scheme)
191 && self.algo.eq(&other.algo)
192 && self.digest.as_ref().eq(other.digest.as_ref())
193 }
194}
195
196impl<Octs: AsRef<[u8]> + ?Sized> Eq for Zonemd<Octs> {}
197
198impl<Octs: AsRef<[u8]>> fmt::Display for Zonemd<Octs> {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 write!(
202 f,
203 "{} {} {} ( ",
204 self.serial,
205 u8::from(self.scheme),
206 u8::from(self.algo)
207 )?;
208 base16::display(&self.digest, f)?;
209 write!(f, " )")
210 }
211}
212
213impl<Octs: AsRef<[u8]>> fmt::Debug for Zonemd<Octs> {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 f.write_str("Zonemd(")?;
216 fmt::Display::fmt(self, f)?;
217 f.write_str(")")
218 }
219}
220
221impl<Octs, Other> PartialOrd<Zonemd<Other>> for Zonemd<Octs>
222where
223 Octs: AsRef<[u8]>,
224 Other: AsRef<[u8]>,
225{
226 fn partial_cmp(&self, other: &Zonemd<Other>) -> Option<Ordering> {
227 match self.serial.partial_cmp(&other.serial) {
228 Some(Ordering::Equal) => {}
229 other => return other,
230 }
231 match self.scheme.partial_cmp(&other.scheme) {
232 Some(Ordering::Equal) => {}
233 other => return other,
234 }
235 match self.algo.partial_cmp(&other.algo) {
236 Some(Ordering::Equal) => {}
237 other => return other,
238 }
239 self.digest.as_ref().partial_cmp(other.digest.as_ref())
240 }
241}
242
243impl<Octs, Other> CanonicalOrd<Zonemd<Other>> for Zonemd<Octs>
244where
245 Octs: AsRef<[u8]>,
246 Other: AsRef<[u8]>,
247{
248 fn canonical_cmp(&self, other: &Zonemd<Other>) -> Ordering {
249 match self.serial.into_int().cmp(&other.serial.into_int()) {
250 Ordering::Equal => {}
251 other => return other,
252 }
253 match self.scheme.cmp(&other.scheme) {
254 Ordering::Equal => {}
255 other => return other,
256 }
257 match self.algo.cmp(&other.algo) {
258 Ordering::Equal => {}
259 other => return other,
260 }
261 self.digest.as_ref().cmp(other.digest.as_ref())
262 }
263}
264
265impl<Octs: AsRef<[u8]>> Ord for Zonemd<Octs> {
266 fn cmp(&self, other: &Self) -> Ordering {
267 match self.serial.into_int().cmp(&other.serial.into_int()) {
268 Ordering::Equal => {}
269 other => return other,
270 }
271 match self.scheme.cmp(&other.scheme) {
272 Ordering::Equal => {}
273 other => return other,
274 }
275 match self.algo.cmp(&other.algo) {
276 Ordering::Equal => {}
277 other => return other,
278 }
279 self.digest.as_ref().cmp(other.digest.as_ref())
280 }
281}
282
283#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
289#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
290pub enum Scheme {
291 Reserved,
292 Simple,
293 Unassigned(u8),
294 Private(u8),
295}
296
297impl From<Scheme> for u8 {
298 fn from(s: Scheme) -> u8 {
299 match s {
300 Scheme::Reserved => 0,
301 Scheme::Simple => 1,
302 Scheme::Unassigned(n) => n,
303 Scheme::Private(n) => n,
304 }
305 }
306}
307
308impl From<u8> for Scheme {
309 fn from(n: u8) -> Self {
310 match n {
311 0 | 255 => Self::Reserved,
312 1 => Self::Simple,
313 2..=239 => Self::Unassigned(n),
314 240..=254 => Self::Private(n),
315 }
316 }
317}
318
319#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
324#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
325pub enum Algorithm {
326 Reserved,
327 Sha384,
328 Sha512,
329 Unassigned(u8),
330 Private(u8),
331}
332
333impl From<Algorithm> for u8 {
334 fn from(algo: Algorithm) -> u8 {
335 match algo {
336 Algorithm::Reserved => 0,
337 Algorithm::Sha384 => 1,
338 Algorithm::Sha512 => 2,
339 Algorithm::Unassigned(n) => n,
340 Algorithm::Private(n) => n,
341 }
342 }
343}
344
345impl From<u8> for Algorithm {
346 fn from(n: u8) -> Self {
347 match n {
348 0 | 255 => Self::Reserved,
349 1 => Self::Sha384,
350 2 => Self::Sha512,
351 3..=239 => Self::Unassigned(n),
352 240..=254 => Self::Private(n),
353 }
354 }
355}
356
357#[cfg(test)]
358#[cfg(all(feature = "std", feature = "bytes"))]
359mod test {
360 use super::*;
361 use crate::base::rdata::test::{
362 test_compose_parse, test_rdlen, test_scan,
363 };
364 use crate::base::Dname;
365 use crate::rdata::ZoneRecordData;
366 use crate::utils::base16::decode;
367 use std::string::ToString;
368 use std::vec::Vec;
369
370 #[test]
371 #[allow(clippy::redundant_closure)] fn zonemd_compose_parse_scan() {
373 let serial = 2023092203;
374 let scheme = 1.into();
375 let algo = 241.into();
376 let digest_str = "CDBE0DED9484490493580583BF868A3E95F89FC3515BF26ADBD230A6C23987F36BC6E504EFC83606F9445476D4E57FFB";
377 let digest: Vec<u8> = decode(digest_str).unwrap();
378 let rdata = Zonemd::new(serial.into(), scheme, algo, digest);
379 test_rdlen(&rdata);
380 test_compose_parse(&rdata, |parser| Zonemd::parse(parser));
381 test_scan(
382 &[
383 &serial.to_string(),
384 &u8::from(scheme).to_string(),
385 &u8::from(algo).to_string(),
386 digest_str,
387 ],
388 Zonemd::scan,
389 &rdata,
390 );
391 }
392
393 #[cfg(feature = "zonefile")]
394 #[test]
395 fn zonemd_parse_zonefile() {
396 use crate::zonefile::inplace::{Entry, Zonefile};
397
398 let content = r#"
400example. 86400 IN SOA ns1 admin 2018031900 (
401 1800 900 604800 86400 )
402 86400 IN NS ns1
403 86400 IN NS ns2
404 86400 IN ZONEMD 2018031900 1 1 (
405 c68090d90a7aed71
406 6bc459f9340e3d7c
407 1370d4d24b7e2fc3
408 a1ddc0b9a87153b9
409 a9713b3c9ae5cc27
410 777f98b8e730044c )
411ns1 3600 IN A 203.0.113.63
412ns2 3600 IN AAAA 2001:db8::63
413"#;
414
415 let mut zone = Zonefile::load(&mut content.as_bytes()).unwrap();
416 zone.set_origin(Dname::root());
417 while let Some(entry) = zone.next_entry().unwrap() {
418 match entry {
419 Entry::Record(record) => {
420 if record.rtype() != Rtype::Zonemd {
421 continue;
422 }
423 match record.into_data() {
424 ZoneRecordData::Zonemd(rd) => {
425 assert_eq!(2018031900, rd.serial().into_int());
426 assert_eq!(Scheme::Simple, rd.scheme());
427 assert_eq!(Algorithm::Sha384, rd.algorithm());
428 }
429 _ => panic!(),
430 }
431 }
432 _ => panic!(),
433 }
434 }
435 }
436}