simple_dns/dns/
resource_record.rs

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/// Resource Records are used to represent the answer, authority, and additional sections in DNS packets.
14#[derive(Debug, Eq, Clone)]
15pub struct ResourceRecord<'a> {
16    /// A [`Name`] to which this resource record pertains.
17    pub name: Name<'a>,
18    /// A [`CLASS`] that defines the class of the rdata field
19    pub class: CLASS,
20    /// The time interval (in seconds) that the resource record may becached before it should be discarded.  
21    /// Zero values are interpreted to mean that the RR can only be used for the transaction in progress, and should not be cached.
22    pub ttl: u32,
23    /// A [`RData`] with the contents of this resource record
24    pub rdata: RData<'a>,
25
26    /// Indicates if this RR is a cache flush
27    pub cache_flush: bool,
28}
29
30impl<'a> ResourceRecord<'a> {
31    /// Creates a new ResourceRecord
32    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    /// Consume self and change the cache_flush bit
43    pub fn with_cache_flush(mut self, cache_flush: bool) -> Self {
44        self.cache_flush = cache_flush;
45        self
46    }
47
48    /// Returns a cloned self with cache_flush = true
49    pub fn to_cache_flush_record(&self) -> Self {
50        self.clone().with_cache_flush(true)
51    }
52
53    /// Return true if current resource match given query class
54    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    /// Return true if current resource match given query type
62    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, // TODO: figure out what to do here
68            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    /// Transforms the inner data into its owned type
75    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    // Disable redundant length check.
108    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}