1use std::collections::BTreeSet;
2use std::{borrow::Cow, collections::BTreeMap};
3
4use crate::bytes_buffer::BytesBuffer;
5use crate::dns::WireFormat;
6use crate::{CharacterString, Name};
7
8use super::RR;
9
10#[derive(Debug, PartialEq, Eq, Hash, Clone)]
13pub struct SVCB<'a> {
14 pub priority: u16,
18
19 pub target: Name<'a>,
22
23 params: BTreeMap<u16, SVCParam<'a>>,
25}
26
27impl RR for SVCB<'_> {
28 const TYPE_CODE: u16 = 64;
29}
30
31impl<'a> SVCB<'a> {
32 pub fn new(priority: u16, target: Name<'a>) -> Self {
34 Self {
35 priority,
36 target,
37 params: BTreeMap::new(),
38 }
39 }
40
41 pub fn set_param(&mut self, param: SVCParam<'a>) {
43 self.params.insert(param.key_code(), param);
44 }
45
46 pub fn with_param(mut self, param: SVCParam<'a>) -> Self {
48 self.set_param(param);
49 self
50 }
51
52 pub fn set_mandatory(&mut self, keys: impl Iterator<Item = u16>) {
56 let keys: BTreeSet<_> = keys.collect();
57 if keys.is_empty() {
58 return;
59 }
60
61 self.set_param(SVCParam::Mandatory(keys));
62 }
63
64 pub fn set_alpn(&mut self, alpn_ids: &[CharacterString<'a>]) {
68 if alpn_ids.is_empty() {
69 return;
70 }
71
72 self.set_param(SVCParam::Alpn(alpn_ids.into()));
73 }
74
75 pub fn set_no_default_alpn(&mut self) {
77 self.set_param(SVCParam::NoDefaultAlpn);
78 }
79
80 pub fn set_port(&mut self, port: u16) {
82 self.set_param(SVCParam::Port(port));
83 }
84
85 pub fn set_ipv4hint(&mut self, ips: &[u32]) {
89 if ips.is_empty() {
90 return;
91 }
92
93 self.set_param(SVCParam::Ipv4Hint(ips.into()));
94 }
95
96 pub fn set_ipv6hint(&mut self, ips: &[u128]) {
100 if ips.is_empty() {
101 return;
102 }
103
104 self.set_param(SVCParam::Ipv6Hint(ips.into()))
105 }
106
107 pub fn get_param(&'a self, key: u16) -> Option<&'a SVCParam<'a>> {
111 self.params.get(&key)
112 }
113
114 pub fn iter_params(&self) -> impl Iterator<Item = &SVCParam> {
116 self.params.values()
117 }
118
119 pub fn into_owned<'b>(self) -> SVCB<'b> {
121 SVCB {
122 priority: self.priority,
123 target: self.target.into_owned(),
124 params: self
125 .params
126 .into_iter()
127 .map(|(k, v)| (k, v.into_owned()))
128 .collect(),
129 }
130 }
131}
132
133impl<'a> WireFormat<'a> for SVCB<'a> {
134 const MINIMUM_LEN: usize = 2;
135
136 fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
137 where
138 Self: Sized,
139 {
140 let priority = data.get_u16()?;
141
142 let target = Name::parse(data)?;
143 let mut params = BTreeMap::new();
144
145 let mut previous_key: Option<u16> = None;
146 while data.has_remaining() {
147 let param = SVCParam::parse(data)?;
148 let key = param.key_code();
149
150 if let Some(p_key) = previous_key {
151 if key <= p_key {
152 return Err(crate::SimpleDnsError::InvalidDnsPacket);
153 }
154 }
155
156 previous_key = Some(key);
157 params.insert(key, param);
158 }
159 Ok(Self {
160 priority,
161 target,
162 params,
163 })
164 }
165
166 fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
167 out.write_all(&self.priority.to_be_bytes())?;
168 self.target.write_to(out)?;
169 for param in self.params.values() {
170 param.write_to(out)?;
171 }
172 Ok(())
173 }
174
175 fn len(&self) -> usize {
179 self.target.len() + self.params.values().map(|p| p.len()).sum::<usize>() + Self::MINIMUM_LEN
180 }
181}
182
183#[derive(Debug, Clone, Eq, PartialEq, Hash)]
189pub enum SVCParam<'a> {
190 Mandatory(BTreeSet<u16>),
192
193 Alpn(Vec<CharacterString<'a>>),
195
196 NoDefaultAlpn,
198
199 Port(u16),
201
202 Ipv4Hint(Vec<u32>),
204
205 Ech(Cow<'a, [u8]>),
207
208 Ipv6Hint(Vec<u128>),
210
211 InvalidKey,
213
214 Unknown(u16, Cow<'a, [u8]>),
216}
217
218impl SVCParam<'_> {
219 pub fn key_code(&self) -> u16 {
221 match self {
222 SVCParam::Mandatory(_) => 0,
223 SVCParam::Alpn(_) => 1,
224 SVCParam::NoDefaultAlpn => 2,
225 SVCParam::Port(_) => 3,
226 SVCParam::Ipv4Hint(_) => 4,
227 SVCParam::Ech(_) => 5,
228 SVCParam::Ipv6Hint(_) => 6,
229 SVCParam::InvalidKey => 65535,
230 SVCParam::Unknown(key, _) => *key,
231 }
232 }
233
234 pub fn into_owned<'b>(self) -> SVCParam<'b> {
236 match self {
237 SVCParam::Mandatory(keys) => SVCParam::Mandatory(keys),
238 SVCParam::Alpn(alpns) => {
239 SVCParam::Alpn(alpns.into_iter().map(|a| a.into_owned()).collect())
240 }
241 SVCParam::NoDefaultAlpn => SVCParam::NoDefaultAlpn,
242 SVCParam::Port(port) => SVCParam::Port(port),
243 SVCParam::Ipv4Hint(ips) => SVCParam::Ipv4Hint(ips),
244 SVCParam::Ech(ech) => SVCParam::Ech(ech.into_owned().into()),
245 SVCParam::Ipv6Hint(ips) => SVCParam::Ipv6Hint(ips),
246 SVCParam::InvalidKey => SVCParam::InvalidKey,
247 SVCParam::Unknown(key, value) => SVCParam::Unknown(key, value.into_owned().into()),
248 }
249 }
250}
251
252impl<'a> WireFormat<'a> for SVCParam<'a> {
253 const MINIMUM_LEN: usize = 4;
254
255 fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
256 where
257 Self: Sized,
258 {
259 let key = data.get_u16()?;
260 let len = data.get_u16()? as usize;
261
262 let mut data = data.new_limited_to(len)?;
263 match key {
264 0 => {
265 let mut keys = BTreeSet::new();
266 while data.has_remaining() {
267 keys.insert(data.get_u16()?);
268 }
269 Ok(SVCParam::Mandatory(keys))
270 }
271 1 => {
272 let mut alpns = Vec::new();
273 while data.has_remaining() {
274 alpns.push(CharacterString::parse(&mut data)?);
275 }
276 Ok(SVCParam::Alpn(alpns))
277 }
278 2 => Ok(SVCParam::NoDefaultAlpn),
279 3 => Ok(SVCParam::Port(data.get_u16()?)),
280 4 => {
281 let mut ips = Vec::new();
282 while data.has_remaining() {
283 ips.push(data.get_u32()?);
284 }
285 Ok(SVCParam::Ipv4Hint(ips))
286 }
287 5 => {
288 let len = data.get_u16()? as usize;
289 let data = data.get_remaining();
290 if data.len() != len {
291 Err(crate::SimpleDnsError::InvalidDnsPacket)
292 } else {
293 Ok(SVCParam::Ech(Cow::Borrowed(data)))
294 }
295 }
296 6 => {
297 let mut ips = Vec::new();
298 while data.has_remaining() {
299 ips.push(data.get_u128()?);
300 }
301 Ok(SVCParam::Ipv6Hint(ips))
302 }
303 _ => {
304 let value = Cow::Borrowed(data.get_remaining());
305 Ok(SVCParam::Unknown(key, value))
306 }
307 }
308 }
309
310 fn write_to<T: std::io::Write>(&self, out: &mut T) -> crate::Result<()> {
311 out.write_all(&self.key_code().to_be_bytes())?;
312 out.write_all(&(self.len() as u16 - 4).to_be_bytes())?;
313
314 match self {
315 SVCParam::Mandatory(keys) => {
316 for key in keys {
317 out.write_all(&key.to_be_bytes())?;
318 }
319 }
320 SVCParam::Alpn(alpns) => {
321 for alpn in alpns.iter() {
322 alpn.write_to(out)?;
323 }
324 }
325 SVCParam::NoDefaultAlpn => {}
326 SVCParam::Port(port) => {
327 out.write_all(&port.to_be_bytes())?;
328 }
329 SVCParam::Ipv4Hint(ips) => {
330 for ip in ips.iter() {
331 out.write_all(&ip.to_be_bytes())?;
332 }
333 }
334 SVCParam::Ech(ech) => {
335 out.write_all(&(ech.len() as u16).to_be_bytes())?;
336 out.write_all(ech)?;
337 }
338 SVCParam::Ipv6Hint(ips) => {
339 for ip in ips.iter() {
340 out.write_all(&ip.to_be_bytes())?;
341 }
342 }
343 SVCParam::Unknown(_, value) => {
344 out.write_all(value)?;
345 }
346 _ => return Err(crate::SimpleDnsError::InvalidDnsPacket),
347 };
348
349 Ok(())
350 }
351
352 fn len(&self) -> usize {
353 Self::MINIMUM_LEN
355 + match self {
356 SVCParam::Mandatory(keys) => keys.len() * 2,
357 SVCParam::Alpn(alpns) => alpns.iter().map(|a| a.len()).sum(),
358 SVCParam::NoDefaultAlpn => 0,
359 SVCParam::Port(_) => 2,
360 SVCParam::Ipv4Hint(ips) => ips.len() * 4,
361 SVCParam::Ech(ech) => 2 + ech.len(),
362 SVCParam::Ipv6Hint(ips) => ips.len() * 16,
363 SVCParam::Unknown(_, value) => value.len(),
364 _ => 0,
365 }
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::{rdata::RData, ResourceRecord};
373
374 #[test]
375 fn parse_sample() -> Result<(), Box<dyn std::error::Error>> {
376 let sample_file = std::fs::read("samples/zonefile/HTTPS.sample")?;
378
379 let sample_rdata = match ResourceRecord::parse(&mut sample_file[..].into())?.rdata {
380 RData::HTTPS(rdata) => rdata,
381 _ => unreachable!(),
382 };
383
384 let mut expected_rdata = SVCB::new(1, Name::new_unchecked(""));
385 expected_rdata.set_alpn(&["http/1.1".try_into()?, "h2".try_into()?]);
386 expected_rdata.set_ipv4hint(&[0xa2_9f_89_55, 0xa2_9f_8a_55]);
387 expected_rdata.set_param(SVCParam::Ech(
388 b"\xfe\x0d\x00\x41\x44\x00\x20\x00\x20\x1a\xd1\x4d\x5c\xa9\x52\xda\
389 \x88\x18\xae\xaf\xd7\xc6\xc8\x7d\x47\xb4\xb3\x45\x7f\x8e\x58\xbc\
390 \x87\xb8\x95\xfc\xb3\xde\x1b\x34\x33\x00\x04\x00\x01\x00\x01\x00\
391 \x12cloudflare-ech.com\x00\x00"
392 .into(),
393 ));
394 expected_rdata.set_ipv6hint(&[
395 0x2606_4700_0007_0000_0000_0000_a29f_8955,
396 0x2606_4700_0007_0000_0000_0000_a29f_8a55,
397 ]);
398
399 assert_eq!(*sample_rdata, expected_rdata);
400
401 assert_eq!(
402 sample_rdata.get_param(1),
403 Some(&SVCParam::Alpn(vec![
404 "http/1.1".try_into().unwrap(),
405 "h2".try_into().unwrap()
406 ]))
407 );
408 assert_eq!(sample_rdata.get_param(3), None);
409
410 Ok(())
411 }
412
413 #[test]
414 fn parse_and_write_svcb() {
415 let tests: &[(&str, &[u8], SVCB<'_>)] = &[
418 (
419 "D.1. AliasMode",
420 b"\x00\x00\x03foo\x07example\x03com\x00",
421 SVCB::new(0, Name::new_unchecked("foo.example.com")),
422 ),
423 (
424 "D.2.3. TargetName Is '.'",
425 b"\x00\x01\x00",
426 SVCB::new(1, Name::new_unchecked("")),
427 ),
428 (
429 "D.2.4. Specified a Port",
430 b"\x00\x10\x03foo\x07example\x03com\x00\x00\x03\x00\x02\x00\x35",
431 {
432 let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.com"));
433 svcb.set_port(53);
434 svcb
435 }
436 ),
437 (
438 "D.2.6. A Generic Key and Quoted Value with a Decimal Escape",
439 b"\x00\x01\x03foo\x07example\x03com\x00\x02\x9b\x00\x09hello\xd2qoo",
440 {
441 let svcb = SVCB::new(1, Name::new_unchecked("foo.example.com")).with_param(SVCParam::Unknown(667, b"hello\xd2qoo"[..].into()));
442 svcb
443 }
444 ),
445 (
446 "D.2.7. Two Quoted IPv6 Hints",
447 b"\x00\x01\x03foo\x07example\x03com\x00\x00\x06\x00\x20\
448 \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\
449 \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01",
450 {
451 let mut svcb = SVCB::new(1, Name::new_unchecked("foo.example.com"));
452 svcb.set_ipv6hint(&[
453 0x2001_0db8_0000_0000_0000_0000_0000_0001,
454 0x2001_0db8_0000_0000_0000_0000_0053_0001,
455 ]);
456 svcb
457 },
458 ),
459 (
460 "D.2.10. SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format",
461 b"\x00\x10\x03foo\x07example\x03org\x00\
462 \x00\x00\x00\x04\x00\x01\x00\x04\
463 \x00\x01\x00\x09\x02h2\x05h3-19\
464 \x00\x04\x00\x04\xc0\x00\x02\x01",
465 {
466 let mut svcb = SVCB::new(16, Name::new_unchecked("foo.example.org"));
467 svcb.set_alpn(&["h2".try_into().unwrap(), "h3-19".try_into().unwrap()]);
468 svcb.set_mandatory([1, 4].into_iter());
469 svcb.set_ipv4hint(&[0xc0_00_02_01]);
470 svcb
471 },
472 ),
473 ];
474
475 for (name, expected_bytes, svcb) in tests {
476 let mut data = Vec::new();
477 svcb.write_to(&mut data).unwrap();
478 assert_eq!(expected_bytes, &data, "Test {name}");
479
480 let svcb2 = SVCB::parse(&mut data[..].into()).unwrap();
481 assert_eq!(svcb, &svcb2, "Test {name}");
482 }
483 }
484}