simple_dns/dns/
question.rs1use std::{
2 collections::HashMap,
3 convert::{TryFrom, TryInto},
4};
5
6use super::{name::Label, Name, WireFormat, QCLASS, QTYPE};
7
8#[derive(Debug, Clone)]
10pub struct Question<'a> {
11 pub qname: Name<'a>,
13 pub qtype: QTYPE,
15 pub qclass: QCLASS,
17 pub unicast_response: bool,
20}
21
22impl<'a> Question<'a> {
23 pub fn new(qname: Name<'a>, qtype: QTYPE, qclass: QCLASS, unicast_response: bool) -> Self {
25 Self {
26 qname,
27 qtype,
28 qclass,
29 unicast_response,
30 }
31 }
32
33 pub fn into_owned<'b>(self) -> Question<'b> {
35 Question {
36 qname: self.qname.into_owned(),
37 qtype: self.qtype,
38 qclass: self.qclass,
39 unicast_response: self.unicast_response,
40 }
41 }
42
43 fn write_common<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
44 let qclass: u16 = match self.unicast_response {
45 true => Into::<u16>::into(self.qclass) | 0x8000,
46 false => self.qclass.into(),
47 };
48
49 out.write_all(&Into::<u16>::into(self.qtype).to_be_bytes())?;
50 out.write_all(&qclass.to_be_bytes())
51 .map_err(crate::SimpleDnsError::from)
52 }
53}
54
55impl<'a> WireFormat<'a> for Question<'a> {
56 fn parse(data: &'a [u8], position: &mut usize) -> crate::Result<Self> {
57 let qname = Name::parse(data, position)?;
58 if *position + 4 > data.len() {
59 return Err(crate::SimpleDnsError::InsufficientData);
60 }
61
62 let qtype = u16::from_be_bytes(data[*position..*position + 2].try_into()?);
63 let qclass = u16::from_be_bytes(data[*position + 2..*position + 4].try_into()?);
64
65 *position += 4;
66
67 Ok(Self {
68 qname,
69 qtype: QTYPE::try_from(qtype)?,
70 qclass: QCLASS::try_from(qclass & 0x7FFF)?,
71 unicast_response: qclass & 0x8000 == 0x8000,
72 })
73 }
74
75 fn len(&self) -> usize {
76 self.qname.len() + 4
77 }
78
79 fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
80 self.qname.write_to(out)?;
81 self.write_common(out)
82 }
83
84 fn write_compressed_to<T: std::io::Write + std::io::Seek>(
85 &'a self,
86 out: &mut T,
87 name_refs: &mut HashMap<&'a [Label<'a>], usize>,
88 ) -> crate::Result<()> {
89 self.qname.write_compressed_to(out, name_refs)?;
90 self.write_common(out)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::{CLASS, TYPE};
97
98 use super::*;
99 use std::convert::TryInto;
100
101 #[test]
102 fn parse_question() {
103 let bytes = b"\x00\x00\x04_srv\x04_udp\x05local\x00\x00\x10\x00\x01";
104 let question = Question::parse(bytes, &mut 2);
105
106 assert!(question.is_ok());
107 let question = question.unwrap();
108
109 assert_eq!(QCLASS::CLASS(CLASS::IN), question.qclass);
110 assert_eq!(QTYPE::TYPE(TYPE::TXT), question.qtype);
111 assert!(!question.unicast_response);
112 }
113
114 #[test]
115 fn append_to_vec() {
116 let question = Question::new(
117 "_srv._udp.local".try_into().unwrap(),
118 TYPE::TXT.into(),
119 CLASS::IN.into(),
120 false,
121 );
122 let mut bytes = Vec::new();
123 question.write_to(&mut bytes).unwrap();
124
125 assert_eq!(b"\x04_srv\x04_udp\x05local\x00\x00\x10\x00\x01", &bytes[..]);
126 assert_eq!(bytes.len(), question.len());
127 }
128
129 #[test]
130 fn unicast_response() {
131 let mut bytes = Vec::new();
132 Question::new(
133 "x.local".try_into().unwrap(),
134 TYPE::TXT.into(),
135 CLASS::IN.into(),
136 true,
137 )
138 .write_to(&mut bytes)
139 .unwrap();
140 let parsed = Question::parse(&bytes, &mut 0).unwrap();
141
142 assert!(parsed.unicast_response);
143 }
144}