1use crate::{
2 bytes_buffer::BytesBuffer,
3 lib::{Hash, Hasher, Seek, SeekFrom, Write},
4 QCLASS, QTYPE,
5};
6
7use super::{rdata::RData, Name, WireFormat, CLASS, TYPE};
8use core::fmt::Debug;
9
10mod flag {
11 pub const CACHE_FLUSH: u16 = 0b1000_0000_0000_0000;
12}
13#[derive(Debug, Eq, Clone)]
15pub struct ResourceRecord<'a> {
16 pub name: Name<'a>,
18 pub class: CLASS,
20 pub ttl: u32,
23 pub rdata: RData<'a>,
25
26 pub cache_flush: bool,
28}
29
30impl<'a> ResourceRecord<'a> {
31 pub fn new(name: Name<'a>, class: CLASS, ttl: u32, rdata: RData<'a>) -> Self {
33 Self {
34 name,
35 class,
36 ttl,
37 rdata,
38 cache_flush: false,
39 }
40 }
41
42 pub fn with_cache_flush(mut self, cache_flush: bool) -> Self {
44 self.cache_flush = cache_flush;
45 self
46 }
47
48 pub fn to_cache_flush_record(&self) -> Self {
50 self.clone().with_cache_flush(true)
51 }
52
53 pub fn match_qclass(&self, qclass: QCLASS) -> bool {
55 match qclass {
56 QCLASS::CLASS(class) => class == self.class,
57 QCLASS::ANY => true,
58 }
59 }
60
61 pub fn match_qtype(&self, qtype: QTYPE) -> bool {
63 let type_code = self.rdata.type_code();
64 match qtype {
65 QTYPE::ANY => true,
66 QTYPE::IXFR => false,
67 QTYPE::AXFR => true, QTYPE::MAILB => type_code == TYPE::MR || type_code == TYPE::MB || type_code == TYPE::MG,
69 QTYPE::MAILA => type_code == TYPE::MX,
70 QTYPE::TYPE(ty) => ty == type_code,
71 }
72 }
73
74 pub fn into_owned<'b>(self) -> ResourceRecord<'b> {
76 ResourceRecord {
77 name: self.name.into_owned(),
78 class: self.class,
79 ttl: self.ttl,
80 rdata: self.rdata.into_owned(),
81 cache_flush: self.cache_flush,
82 }
83 }
84
85 fn write_common<T: Write>(&self, out: &mut T) -> crate::Result<()> {
86 out.write_all(&u16::from(self.rdata.type_code()).to_be_bytes())?;
87
88 if let RData::OPT(ref opt) = self.rdata {
89 out.write_all(&opt.udp_packet_size.to_be_bytes())?;
90 } else {
91 let class = if self.cache_flush {
92 ((self.class as u16) | flag::CACHE_FLUSH).to_be_bytes()
93 } else {
94 (self.class as u16).to_be_bytes()
95 };
96
97 out.write_all(&class)?;
98 }
99
100 out.write_all(&self.ttl.to_be_bytes())
101 }
102}
103
104impl<'a> WireFormat<'a> for ResourceRecord<'a> {
105 const MINIMUM_LEN: usize = 10;
106
107 fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
109 where
110 Self: Sized,
111 {
112 let name = Name::parse(data)?;
113
114 let class_value = data.peek_u16_in(2)?;
115 let ttl = data.peek_u32_in(4)?;
116
117 let rdata = RData::parse(data)?;
118
119 if rdata.type_code() == TYPE::OPT {
120 Ok(Self {
121 name,
122 class: CLASS::IN,
123 ttl,
124 rdata,
125 cache_flush: false,
126 })
127 } else {
128 let cache_flush = class_value & flag::CACHE_FLUSH == flag::CACHE_FLUSH;
129 let class = (class_value & !flag::CACHE_FLUSH).try_into()?;
130
131 Ok(Self {
132 name,
133 class,
134 ttl,
135 rdata,
136 cache_flush,
137 })
138 }
139 }
140
141 fn len(&self) -> usize {
142 self.name.len() + self.rdata.len() + Self::MINIMUM_LEN
143 }
144
145 fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
146 self.name.write_to(out)?;
147 self.write_common(out)?;
148 out.write_all(&(self.rdata.len() as u16).to_be_bytes())?;
149 self.rdata.write_to(out)
150 }
151
152 fn write_compressed_to<T: Write + Seek>(
153 &'a self,
154 out: &mut T,
155 name_refs: &mut crate::lib::BTreeMap<&[crate::Label<'a>], u16>,
156 ) -> crate::Result<()> {
157 self.name.write_compressed_to(out, name_refs)?;
158 self.write_common(out)?;
159
160 let len_position = out.stream_position()?;
161 out.write_all(&[0, 0])?;
162
163 self.rdata.write_compressed_to(out, name_refs)?;
164 let end = out.stream_position()?;
165
166 out.seek(SeekFrom::Start(len_position))?;
167 out.write_all(&((end - len_position - 2) as u16).to_be_bytes())?;
168 out.seek(SeekFrom::End(0))?;
169 Ok(())
170 }
171}
172
173impl Hash for ResourceRecord<'_> {
174 fn hash<H: Hasher>(&self, state: &mut H) {
175 self.name.hash(state);
176 self.class.hash(state);
177 self.rdata.hash(state);
178 }
179}
180
181impl PartialEq for ResourceRecord<'_> {
182 fn eq(&self, other: &Self) -> bool {
183 self.name == other.name && self.class == other.class && self.rdata == other.rdata
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::{
191 dns::rdata::NULL,
192 lib::{ToString, Vec},
193 };
194
195 #[cfg(feature = "std")]
196 use crate::rdata::TXT;
197
198 #[test]
199 fn test_parse() {
200 let bytes = b"\x04_srv\x04_udp\x05local\x00\x00\x01\x00\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff";
201 let rr = ResourceRecord::parse(&mut BytesBuffer::new(bytes)).unwrap();
202
203 assert_eq!("_srv._udp.local", rr.name.to_string());
204 assert_eq!(CLASS::IN, rr.class);
205 assert_eq!(10, rr.ttl);
206 assert_eq!(4, rr.rdata.len());
207 assert!(!rr.cache_flush);
208
209 match rr.rdata {
210 RData::A(a) => assert_eq!(4294967295, a.address),
211 _ => panic!("invalid rdata"),
212 }
213 }
214
215 #[test]
216 fn test_empty_rdata() {
217 let rr = ResourceRecord {
218 class: CLASS::NONE,
219 name: "_srv._udp.local".try_into().unwrap(),
220 ttl: 0,
221 rdata: RData::Empty(TYPE::A),
222 cache_flush: false,
223 };
224
225 assert_eq!(rr.rdata.type_code(), TYPE::A);
226 assert_eq!(rr.rdata.len(), 0);
227
228 let mut data = Vec::new();
229 rr.write_to(&mut data).expect("failed to write");
230
231 let parsed_rr =
232 ResourceRecord::parse(&mut BytesBuffer::new(&data)).expect("failed to parse");
233 assert_eq!(parsed_rr.rdata.type_code(), TYPE::A);
234 assert_eq!(parsed_rr.rdata.len(), 0);
235 assert!(matches!(parsed_rr.rdata, RData::Empty(TYPE::A)));
236 }
237
238 #[test]
239 fn test_cache_flush_parse() {
240 let bytes = b"\x04_srv\x04_udp\x05local\x00\x00\x01\x80\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff";
241 let rr = ResourceRecord::parse(&mut BytesBuffer::new(bytes)).unwrap();
242
243 assert_eq!(CLASS::IN, rr.class);
244 assert!(rr.cache_flush);
245 }
246
247 #[test]
248 fn test_write() {
249 let mut out = Vec::new();
250 let rdata = [255u8; 4];
251
252 let rr = ResourceRecord {
253 class: CLASS::IN,
254 name: "_srv._udp.local".try_into().unwrap(),
255 ttl: 10,
256 rdata: RData::NULL(0, NULL::new(&rdata).unwrap()),
257 cache_flush: false,
258 };
259
260 assert!(rr.write_to(&mut out).is_ok());
261 assert_eq!(
262 b"\x04_srv\x04_udp\x05local\x00\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff",
263 &out[..]
264 );
265 assert_eq!(out.len(), rr.len());
266 }
267
268 #[test]
269 fn test_append_to_vec_cache_flush() {
270 let mut out = Vec::new();
271 let rdata = [255u8; 4];
272
273 let rr = ResourceRecord {
274 class: CLASS::IN,
275 name: "_srv._udp.local".try_into().unwrap(),
276 ttl: 10,
277 rdata: RData::NULL(0, NULL::new(&rdata).unwrap()),
278 cache_flush: true,
279 };
280
281 assert!(rr.write_to(&mut out).is_ok());
282 assert_eq!(
283 b"\x04_srv\x04_udp\x05local\x00\x00\x00\x80\x01\x00\x00\x00\x0a\x00\x04\xff\xff\xff\xff",
284 &out[..]
285 );
286 assert_eq!(out.len(), rr.len());
287 }
288
289 #[test]
290 fn test_match_qclass() {
291 let rr = ResourceRecord {
292 class: CLASS::IN,
293 name: "_srv._udp.local".try_into().unwrap(),
294 ttl: 10,
295 rdata: RData::NULL(0, NULL::new(&[255u8; 4]).unwrap()),
296 cache_flush: false,
297 };
298
299 assert!(rr.match_qclass(QCLASS::ANY));
300 assert!(rr.match_qclass(CLASS::IN.into()));
301 assert!(!rr.match_qclass(CLASS::CS.into()));
302 }
303
304 #[test]
305 fn test_match_qtype() {
306 let rr = ResourceRecord {
307 class: CLASS::IN,
308 name: "_srv._udp.local".try_into().unwrap(),
309 ttl: 10,
310 rdata: RData::A(crate::rdata::A { address: 0 }),
311 cache_flush: false,
312 };
313
314 assert!(rr.match_qtype(QTYPE::ANY));
315 assert!(rr.match_qtype(TYPE::A.into()));
316 assert!(!rr.match_qtype(TYPE::WKS.into()));
317 }
318
319 #[test]
320 #[cfg(feature = "std")]
321 fn test_eq() {
322 let a = ResourceRecord::new(
323 Name::new_unchecked("_srv.local"),
324 CLASS::IN,
325 10,
326 RData::TXT(TXT::new().with_string("text").unwrap()),
327 );
328 let b = ResourceRecord::new(
329 Name::new_unchecked("_srv.local"),
330 CLASS::IN,
331 10,
332 RData::TXT(TXT::new().with_string("text").unwrap()),
333 );
334
335 assert_eq!(a, b);
336 assert_eq!(get_hash(&a), get_hash(&b));
337 }
338
339 #[test]
340 #[cfg(feature = "std")]
341 fn test_hash_ignore_ttl() {
342 let a = ResourceRecord::new(
343 Name::new_unchecked("_srv.local"),
344 CLASS::IN,
345 10,
346 RData::TXT(TXT::new().with_string("text").unwrap()),
347 );
348 let mut b = ResourceRecord::new(
349 Name::new_unchecked("_srv.local"),
350 CLASS::IN,
351 10,
352 RData::TXT(TXT::new().with_string("text").unwrap()),
353 );
354
355 assert_eq!(get_hash(&a), get_hash(&b));
356 b.ttl = 50;
357
358 assert_eq!(get_hash(&a), get_hash(&b));
359 }
360
361 #[cfg(feature = "std")]
362 fn get_hash(rr: &ResourceRecord) -> u64 {
363 let mut hasher = std::hash::DefaultHasher::default();
364 rr.hash(&mut hasher);
365 hasher.finish()
366 }
367
368 #[test]
369 #[cfg(feature = "std")]
370 fn parse_sample_files() -> Result<(), Box<dyn std::error::Error>> {
371 for file_path in std::fs::read_dir("samples/zonefile")? {
372 let bytes = std::fs::read(file_path?.path())?;
373 let mut data = BytesBuffer::new(&bytes);
374 while data.has_remaining() {
375 crate::ResourceRecord::parse(&mut data)?;
376 }
377 }
378
379 Ok(())
380 }
381}