1use core::fmt;
4
5use super::{ComposeRdata, ParseRdata, ParseRdataText};
6use crate::wire::{Composer, OutBuf, WireReader};
7use crate::zone::Scanner;
8use crate::{Error, Result, Rtype};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub struct Dhcid<'a> {
17 data: &'a [u8],
18}
19
20impl<'a> Dhcid<'a> {
21 pub const ID_CHADDR: u16 = 0x0000;
24 pub const ID_CLIENT_ID: u16 = 0x0001;
27 pub const ID_DUID: u16 = 0x0002;
29 pub const DIGEST_SHA256: u8 = 1;
31
32 #[inline]
36 pub const fn from_wire(data: &'a [u8]) -> Result<Self> {
37 if data.len() < 3 {
38 return Err(Error::InvalidRdata);
39 }
40 Ok(Dhcid { data })
41 }
42
43 #[inline]
45 #[must_use]
46 pub const fn as_wire(&self) -> &'a [u8] {
47 self.data
48 }
49
50 #[inline]
52 #[must_use]
53 pub const fn identifier_type(&self) -> u16 {
54 match self.data {
55 [a, b, ..] => u16::from_be_bytes([*a, *b]),
56 _ => 0,
57 }
58 }
59
60 #[inline]
62 #[must_use]
63 pub const fn digest_type(&self) -> u8 {
64 match self.data {
65 [_, _, d, ..] => *d,
66 _ => 0,
67 }
68 }
69
70 #[inline]
72 #[must_use]
73 pub const fn digest(&self) -> &'a [u8] {
74 match self.data {
75 [_, _, _, rest @ ..] => rest,
76 _ => &[],
77 }
78 }
79}
80
81impl ParseRdataText for Dhcid<'_> {
82 fn parse_text<B: OutBuf + ?Sized>(s: &mut Scanner<'_>, out: &mut B) -> Result<()> {
86 if s.base64_rest_into(out)? == 0 {
87 return Err(Error::UnexpectedEof);
88 }
89 Ok(())
90 }
91}
92
93impl<'a> ParseRdata<'a> for Dhcid<'a> {
94 const RTYPE: Rtype = Rtype::DHCID;
95
96 fn parse_rdata(rdata: &mut WireReader<'a>) -> Result<Self> {
97 let d = Dhcid::from_wire(rdata.peek_rest())?;
98 rdata.read_rest();
99 Ok(d)
100 }
101}
102
103impl ComposeRdata for Dhcid<'_> {
104 fn rtype(&self) -> Rtype {
105 Rtype::DHCID
106 }
107
108 fn compose_rdata<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
109 c.put_bytes(self.data)
110 }
111}
112
113impl fmt::Display for Dhcid<'_> {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 fmt::Display::fmt(&crate::text::Base64(self.data), f)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::rdata::tests::{parse, round_trip, text_error, text_round_trip};
124 use crate::testutil::hex;
125 use crate::{Class, RData};
126
127 #[test]
128 fn rfc4701_examples() {
129 for (id_type, wire, text) in [
133 (
134 Dhcid::ID_DUID,
135 "000201636fc0b8271c82825bb1ac5c41cf5351aa69b4febd94e8f17cdb95000da48c40",
136 "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=",
137 ),
138 (
139 Dhcid::ID_CLIENT_ID,
140 "0001013920fe5d1dceb3fd0ba3379756a70d73b17009f41d58bddbfcd6a2503956d8da",
141 "AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No=",
142 ),
143 (
144 Dhcid::ID_CHADDR,
145 "000001c4b9a5b249651343158dde7bcc77169841f7a4243a572b5c283fffedeb3f75e6",
146 "AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY=",
147 ),
148 ] {
149 let wire = hex(wire);
150 round_trip(Rtype::DHCID, &wire, text);
151 let Ok(RData::Dhcid(d)) = parse(Rtype::DHCID, Class::IN, &wire) else {
152 panic!("not DHCID")
153 };
154 assert_eq!(d.identifier_type(), id_type);
155 assert_eq!(d.digest_type(), Dhcid::DIGEST_SHA256);
156 assert_eq!(d.digest().len(), 32);
157 assert_eq!(d.as_wire(), &wire[..]);
158 }
159 }
160
161 #[test]
162 fn too_short() {
163 for bad in [&b""[..], b"\x00", b"\x00\x02"] {
164 assert_eq!(Dhcid::from_wire(bad), Err(Error::InvalidRdata));
165 assert_eq!(
166 parse(Rtype::DHCID, Class::IN, bad),
167 Err(Error::InvalidRdata)
168 );
169 }
170 let d = Dhcid::from_wire(b"\x00\x02\x01").unwrap();
171 assert!(d.digest().is_empty());
172 round_trip(Rtype::DHCID, b"\x00\x02\x01", "AAIB");
173 }
174
175 #[test]
176 fn text() {
177 for (text, wire, shown) in [
180 (
181 "( AAIBY2/AuCccgoJbsaxcQc9TUapptP69l\n OjxfNuVAA2kjEA= )",
182 "000201636fc0b8271c82825bb1ac5c41cf5351aa69b4febd94e8f17cdb95000da48c40",
183 "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=",
184 ),
185 (
186 "( AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdW\n L3b/NaiUDlW2No= )",
187 "0001013920fe5d1dceb3fd0ba3379756a70d73b17009f41d58bddbfcd6a2503956d8da",
188 "AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No=",
189 ),
190 (
191 "( AAABxLmlskllE0MVjd57zHcWmEH3pCQ6V\n ytcKD//7es/deY= )",
192 "000001c4b9a5b249651343158dde7bcc77169841f7a4243a572b5c283fffedeb3f75e6",
193 "AAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY=",
194 ),
195 ] {
196 text_round_trip(Rtype::DHCID, text, &hex(wire), shown);
197 }
198 text_round_trip(Rtype::DHCID, "AAIB", b"\x00\x02\x01", "AAIB");
199 }
200
201 #[test]
202 fn text_malformed() {
203 for (text, err) in [
204 ("", Error::UnexpectedEof),
205 ("AAI=", Error::InvalidRdata),
207 ("AA==", Error::InvalidRdata),
208 ("AAIB=", Error::InvalidText),
209 ("AAI", Error::InvalidText),
210 ("AA!B", Error::InvalidText),
211 ("\"AAIB\"", Error::InvalidText),
212 ] {
213 assert_eq!(text_error(Rtype::DHCID, text), err, "{text:?}");
214 }
215 }
216}