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