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