1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
2
3import binascii
4import struct
5
6import dns.immutable
7import dns.rdata
8import dns.rdatatype
9import dns.zonetypes
10
11
12@dns.immutable.immutable
13class ZONEMD(dns.rdata.Rdata):
14 """ZONEMD record"""
15
16 # See RFC 8976
17
18 __slots__ = ["serial", "scheme", "hash_algorithm", "digest"]
19
20 def __init__(self, rdclass, rdtype, serial, scheme, hash_algorithm, digest):
21 super().__init__(rdclass, rdtype)
22 self.serial = self._as_uint32(serial)
23 self.scheme = dns.zonetypes.DigestScheme.make(scheme)
24 self.hash_algorithm = dns.zonetypes.DigestHashAlgorithm.make(hash_algorithm)
25 self.digest = self._as_bytes(digest)
26
27 if self.scheme == 0: # reserved, RFC 8976 Sec. 5.2
28 raise ValueError("scheme 0 is reserved")
29 if self.hash_algorithm == 0: # reserved, RFC 8976 Sec. 5.3
30 raise ValueError("hash_algorithm 0 is reserved")
31
32 # RFC 8976 Sec. 2.2.4: the digest is never shorter than 12 octets,
33 # regardless of the hash algorithm, including private-use ones.
34 if len(self.digest) < 12:
35 raise ValueError("digest must be at least 12 octets")
36
37 hasher = dns.zonetypes._digest_hashers.get(self.hash_algorithm)
38 if hasher and hasher().digest_size != len(self.digest):
39 raise ValueError("digest length inconsistent with hash algorithm")
40
41 def to_styled_text(self, style: dns.rdata.RdataStyle) -> str:
42 digest = dns.rdata._styled_hexify(self.digest, style, True)
43 return f"{self.serial} {self.scheme} {self.hash_algorithm} {digest}"
44
45 @classmethod
46 def from_text(
47 cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None
48 ):
49 serial = tok.get_uint32()
50 scheme = tok.get_uint8()
51 hash_algorithm = tok.get_uint8()
52 digest = tok.concatenate_remaining_identifiers().encode()
53 digest = binascii.unhexlify(digest)
54 return cls(rdclass, rdtype, serial, scheme, hash_algorithm, digest)
55
56 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
57 header = struct.pack("!IBB", self.serial, self.scheme, self.hash_algorithm)
58 file.write(header)
59 file.write(self.digest)
60
61 @classmethod
62 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
63 header = parser.get_struct("!IBB")
64 digest = parser.get_remaining()
65 return cls(rdclass, rdtype, header[0], header[1], header[2], digest)