1use std::marker::PhantomData;
2
3use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
4
5use super::{Header, Name, Opcode, QueryClass, QueryType, RRData, ResponseCode};
6
7pub enum Questions {}
8pub enum Answers {}
9#[allow(dead_code)]
10pub enum Nameservers {}
11pub enum Additional {}
12
13pub trait MoveTo<T> {}
14impl<T> MoveTo<T> for T {}
15
16impl MoveTo<Answers> for Questions {}
17
18impl MoveTo<Nameservers> for Questions {}
19impl MoveTo<Nameservers> for Answers {}
20
21impl MoveTo<Additional> for Questions {}
22impl MoveTo<Additional> for Answers {}
23impl MoveTo<Additional> for Nameservers {}
24
25pub struct Builder<S> {
30 buf: Vec<u8>,
31 max_size: Option<usize>,
32 _state: PhantomData<S>,
33}
34
35impl Builder<Questions> {
36 #[allow(dead_code)]
41 pub fn new_query(id: u16, recursion: bool) -> Builder<Questions> {
42 let mut buf = Vec::with_capacity(512);
43 let head = Header {
44 id,
45 query: true,
46 opcode: Opcode::StandardQuery,
47 authoritative: false,
48 truncated: false,
49 recursion_desired: recursion,
50 recursion_available: false,
51 response_code: ResponseCode::NoError,
52 questions: 0,
53 answers: 0,
54 nameservers: 0,
55 additional: 0,
56 };
57 buf.extend([0u8; 12].iter());
58 head.write(&mut buf[..12]);
59 Builder {
60 buf,
61 max_size: Some(512),
62 _state: PhantomData,
63 }
64 }
65
66 pub fn new_response(id: u16, recursion: bool, authoritative: bool) -> Builder<Questions> {
67 let mut buf = Vec::with_capacity(512);
68 let head = Header {
69 id,
70 query: false,
71 opcode: Opcode::StandardQuery,
72 authoritative,
73 truncated: false,
74 recursion_desired: recursion,
75 recursion_available: false,
76 response_code: ResponseCode::NoError,
77 questions: 0,
78 answers: 0,
79 nameservers: 0,
80 additional: 0,
81 };
82 buf.extend([0u8; 12].iter());
83 head.write(&mut buf[..12]);
84 Builder {
85 buf,
86 max_size: Some(512),
87 _state: PhantomData,
88 }
89 }
90}
91
92impl<T> Builder<T> {
93 fn write_rr(&mut self, name: &Name<'_>, cls: QueryClass, ttl: u32, data: &RRData<'_>) {
94 name.write_to(&mut self.buf).unwrap();
95 self.buf.write_u16::<BigEndian>(data.typ() as u16).unwrap();
96 self.buf.write_u16::<BigEndian>(cls as u16).unwrap();
97 self.buf.write_u32::<BigEndian>(ttl).unwrap();
98
99 let size_offset = self.buf.len();
100 self.buf.write_u16::<BigEndian>(0).unwrap();
101
102 let data_offset = self.buf.len();
103 data.write_to(&mut self.buf).unwrap();
104 let data_size = self.buf.len() - data_offset;
105
106 assert!(
107 (data_offset <= 65535),
108 "{} is too long to write to a RR record",
109 data_offset
110 );
111 #[allow(clippy::cast_possible_truncation)]
112 BigEndian::write_u16(
113 &mut self.buf[size_offset..size_offset + 2],
114 data_size as u16,
115 );
116 }
117
118 pub fn build(mut self) -> Result<Vec<u8>, Vec<u8>> {
133 match self.max_size {
135 Some(max_size) if self.buf.len() > max_size => {
136 Header::set_truncated(&mut self.buf[..12]);
137 Err(self.buf)
138 }
139 _ => Ok(self.buf),
140 }
141 }
142
143 pub fn move_to<U>(self) -> Builder<U>
144 where
145 T: MoveTo<U>,
146 {
147 Builder {
148 buf: self.buf,
149 max_size: self.max_size,
150 _state: PhantomData,
151 }
152 }
153
154 pub fn set_max_size(&mut self, max_size: Option<usize>) {
155 self.max_size = max_size;
156 }
157
158 pub fn is_empty(&self) -> bool {
159 Header::question_count(&self.buf) == 0
160 && Header::answer_count(&self.buf) == 0
161 && Header::nameserver_count(&self.buf) == 0
162 && Header::additional_count(&self.buf) == 0
163 }
164}
165
166impl<T: MoveTo<Questions>> Builder<T> {
167 #[allow(dead_code)]
173 pub fn add_question(
174 self,
175 qname: &Name<'_>,
176 qtype: QueryType,
177 qclass: QueryClass,
178 ) -> Builder<Questions> {
179 let mut builder = self.move_to::<Questions>();
180
181 qname.write_to(&mut builder.buf).unwrap();
182 builder.buf.write_u16::<BigEndian>(qtype as u16).unwrap();
183 builder.buf.write_u16::<BigEndian>(qclass as u16).unwrap();
184 Header::inc_questions(&mut builder.buf).expect("Too many questions");
185 builder
186 }
187}
188
189impl<T: MoveTo<Answers>> Builder<T> {
190 pub fn add_answer(
191 self,
192 name: &Name<'_>,
193 cls: QueryClass,
194 ttl: u32,
195 data: &RRData<'_>,
196 ) -> Builder<Answers> {
197 let mut builder = self.move_to::<Answers>();
198
199 builder.write_rr(name, cls, ttl, data);
200 Header::inc_answers(&mut builder.buf).expect("Too many answers");
201
202 builder
203 }
204}
205
206impl<T: MoveTo<Nameservers>> Builder<T> {
207 #[allow(dead_code)]
208 pub fn add_nameserver(
209 self,
210 name: &Name<'_>,
211 cls: QueryClass,
212 ttl: u32,
213 data: &RRData<'_>,
214 ) -> Builder<Nameservers> {
215 let mut builder = self.move_to::<Nameservers>();
216
217 builder.write_rr(name, cls, ttl, data);
218 Header::inc_nameservers(&mut builder.buf).expect("Too many nameservers");
219
220 builder
221 }
222}
223
224impl Builder<Additional> {
225 #[allow(dead_code)]
226 pub fn add_additional(
227 self,
228 name: &Name<'_>,
229 cls: QueryClass,
230 ttl: u32,
231 data: &RRData<'_>,
232 ) -> Builder<Additional> {
233 let mut builder = self.move_to::<Additional>();
234
235 builder.write_rr(name, cls, ttl, data);
236 Header::inc_nameservers(&mut builder.buf).expect("Too many additional answers");
237
238 builder
239 }
240}
241
242#[cfg(test)]
243mod test {
244 use super::Builder;
245 use super::Name;
246 use super::QueryClass as QC;
247 use super::QueryType as QT;
248
249 #[test]
250 fn build_query() {
251 let mut bld = Builder::new_query(1573, true);
252 let name = Name::from_str("example.com");
253 bld = bld.add_question(&name, QT::A, QC::IN);
254 let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
255 \x07example\x03com\x00\x00\x01\x00\x01";
256 assert_eq!(&bld.build().unwrap()[..], &result[..]);
257 }
258
259 #[test]
260 fn build_srv_query() {
261 let mut bld = Builder::new_query(23513, true);
262 let name = Name::from_str("_xmpp-server._tcp.gmail.com");
263 bld = bld.add_question(&name, QT::SRV, QC::IN);
264 let result = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\
265 \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01";
266 assert_eq!(&bld.build().unwrap()[..], &result[..]);
267 }
268}