1use super::SvcParams;
6use crate::base::cmp::CanonicalOrd;
7use crate::base::iana::Rtype;
8use crate::base::name::{FlattenInto, ParsedName, ToName};
9use crate::base::rdata::{
10 ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
11};
12use crate::base::wire::{Compose, Composer, Parse, ParseError};
13use crate::base::zonefile_fmt::{self, Formatter, ZonefileFmt};
14use core::marker::PhantomData;
15use core::{cmp, fmt, hash};
16use octseq::octets::{Octets, OctetsFrom, OctetsInto};
17use octseq::parse::Parser;
18
19#[derive(Clone)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[cfg_attr(
63 feature = "serde",
64 serde(bound(
65 serialize = "
66 Name: serde::Serialize,
67 Octs: octseq::serde::SerializeOctets
68 ",
69 deserialize = "
70 Name: serde::Deserialize<'de>,
71 Octs: octseq::serde::DeserializeOctets<'de>
72 ",
73 ))
74)]
75pub struct SvcbRdata<Variant, Octs, Name> {
76 priority: u16,
78
79 target: Name,
81
82 params: SvcParams<Octs>,
84
85 marker: PhantomData<Variant>,
87}
88
89#[derive(Clone, Copy, Debug)]
94pub struct SvcbVariant;
95
96#[derive(Clone, Copy, Debug)]
101pub struct HttpsVariant;
102
103pub type Svcb<Octs, Name> = SvcbRdata<SvcbVariant, Octs, Name>;
110
111pub type Https<Octs, Name> = SvcbRdata<HttpsVariant, Octs, Name>;
117
118impl SvcbRdata<SvcbVariant, (), ()> {
119 pub(crate) const RTYPE: Rtype = Rtype::SVCB;
121}
122
123impl SvcbRdata<HttpsVariant, (), ()> {
124 pub(crate) const RTYPE: Rtype = Rtype::HTTPS;
126}
127
128impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
129 pub fn new(
134 priority: u16,
135 target: Name,
136 params: SvcParams<Octs>,
137 ) -> Result<Self, LongRecordData>
138 where
139 Octs: AsRef<[u8]>,
140 Name: ToName,
141 {
142 LongRecordData::check_len(
143 usize::from(u16::COMPOSE_LEN + target.compose_len())
144 .checked_add(params.len())
145 .expect("long params"),
146 )?;
147 Ok(unsafe { Self::new_unchecked(priority, target, params) })
148 }
149
150 pub unsafe fn new_unchecked(
157 priority: u16,
158 target: Name,
159 params: SvcParams<Octs>,
160 ) -> Self {
161 SvcbRdata {
162 priority,
163 target,
164 params,
165 marker: PhantomData,
166 }
167 }
168}
169
170impl<Variant, Octs: AsRef<[u8]>> SvcbRdata<Variant, Octs, ParsedName<Octs>> {
171 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
173 parser: &mut Parser<'a, Src>,
174 ) -> Result<Self, ParseError> {
175 let priority = u16::parse(parser)?;
176 let target = ParsedName::parse(parser)?;
177 let params = SvcParams::parse(parser)?;
178 Ok(unsafe { Self::new_unchecked(priority, target, params) })
179 }
180}
181
182impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
183 pub fn priority(&self) -> u16 {
185 self.priority
186 }
187
188 pub fn is_alias(&self) -> bool {
192 self.priority == 0
193 }
194
195 pub fn is_service(&self) -> bool {
199 self.priority != 0
200 }
201
202 pub fn target(&self) -> &Name {
207 &self.target
208 }
209
210 pub fn params(&self) -> &SvcParams<Octs> {
212 &self.params
213 }
214
215 pub(crate) fn convert_octets<TOcts, TName>(
217 self,
218 ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
219 where
220 TOcts: OctetsFrom<Octs>,
221 TName: OctetsFrom<Name, Error = TOcts::Error>,
222 {
223 Ok(unsafe {
224 SvcbRdata::new_unchecked(
225 self.priority,
226 self.target.try_octets_into()?,
227 self.params.try_octets_into()?,
228 )
229 })
230 }
231
232 pub(crate) fn flatten<TOcts, TName>(
233 self,
234 ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
235 where
236 TOcts: OctetsFrom<Octs>,
237 Name: FlattenInto<TName, AppendError = TOcts::Error>,
238 {
239 Ok(unsafe {
240 SvcbRdata::new_unchecked(
241 self.priority,
242 self.target.try_flatten_into()?,
243 self.params.try_octets_into()?,
244 )
245 })
246 }
247}
248
249impl<Variant, Octs, SrcOctets, Name, SrcName>
252 OctetsFrom<SvcbRdata<Variant, SrcOctets, SrcName>>
253 for SvcbRdata<Variant, Octs, Name>
254where
255 Octs: OctetsFrom<SrcOctets>,
256 Name: OctetsFrom<SrcName, Error = Octs::Error>,
257{
258 type Error = Octs::Error;
259
260 fn try_octets_from(
261 source: SvcbRdata<Variant, SrcOctets, SrcName>,
262 ) -> Result<Self, Self::Error> {
263 source.convert_octets()
264 }
265}
266
267impl<Variant, Octs, TOcts, Name, TName>
268 FlattenInto<SvcbRdata<Variant, TOcts, TName>>
269 for SvcbRdata<Variant, Octs, Name>
270where
271 TOcts: OctetsFrom<Octs>,
272 Name: FlattenInto<TName, AppendError = TOcts::Error>,
273{
274 type AppendError = TOcts::Error;
275
276 fn try_flatten_into(
277 self,
278 ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error> {
279 self.flatten()
280 }
281}
282
283impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
286 PartialEq<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
287 for SvcbRdata<Variant, Octs, Name>
288where
289 Octs: AsRef<[u8]>,
290 OtherOcts: AsRef<[u8]>,
291 Name: ToName,
292 OtherName: ToName,
293{
294 fn eq(
295 &self,
296 other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>,
297 ) -> bool {
298 self.priority == other.priority
299 && self.target.name_eq(&other.target)
300 && self.params == other.params
301 }
302}
303
304impl<Variant, Octs: AsRef<[u8]>, Name: ToName> Eq
305 for SvcbRdata<Variant, Octs, Name>
306{
307}
308
309impl<Variant, Octs: AsRef<[u8]>, Name: hash::Hash> hash::Hash
312 for SvcbRdata<Variant, Octs, Name>
313{
314 fn hash<H: hash::Hasher>(&self, state: &mut H) {
315 self.priority.hash(state);
316 self.target.hash(state);
317 self.params.hash(state);
318 }
319}
320
321impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
324 PartialOrd<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
325 for SvcbRdata<Variant, Octs, Name>
326where
327 Octs: AsRef<[u8]>,
328 OtherOcts: AsRef<[u8]>,
329 Name: ToName,
330 OtherName: ToName,
331{
332 fn partial_cmp(
333 &self,
334 other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>,
335 ) -> Option<cmp::Ordering> {
336 match self.priority.partial_cmp(&other.priority) {
337 Some(cmp::Ordering::Equal) => {}
338 other => return other,
339 }
340 match self.target.name_cmp(&other.target) {
341 cmp::Ordering::Equal => {}
342 other => return Some(other),
343 }
344 self.params.partial_cmp(&other.params)
345 }
346}
347
348impl<Variant, Octs: AsRef<[u8]>, Name: ToName> Ord
349 for SvcbRdata<Variant, Octs, Name>
350{
351 fn cmp(&self, other: &Self) -> cmp::Ordering {
352 match self.priority.cmp(&other.priority) {
353 cmp::Ordering::Equal => {}
354 other => return other,
355 }
356 match self.target.name_cmp(&other.target) {
357 cmp::Ordering::Equal => {}
358 other => return other,
359 }
360 self.params.cmp(&other.params)
361 }
362}
363
364impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
365 CanonicalOrd<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
366 for SvcbRdata<Variant, Octs, Name>
367where
368 Octs: AsRef<[u8]>,
369 OtherOcts: AsRef<[u8]>,
370 Name: ToName,
371 OtherName: ToName,
372{
373 fn canonical_cmp(
374 &self,
375 other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>,
376 ) -> cmp::Ordering {
377 match self.priority.cmp(&other.priority) {
378 cmp::Ordering::Equal => {}
379 other => return other,
380 }
381 match self.target.name_cmp(&other.target) {
382 cmp::Ordering::Equal => {}
383 other => return other,
384 }
385 self.params.canonical_cmp(&other.params)
386 }
387}
388
389impl<Octs, Name> RecordData for SvcbRdata<SvcbVariant, Octs, Name> {
392 fn rtype(&self) -> Rtype {
393 Rtype::SVCB
394 }
395}
396
397impl<Octs, Name> RecordData for SvcbRdata<HttpsVariant, Octs, Name> {
398 fn rtype(&self) -> Rtype {
399 Rtype::HTTPS
400 }
401}
402
403impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
404 for SvcbRdata<SvcbVariant, Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
405{
406 fn parse_rdata(
407 rtype: Rtype,
408 parser: &mut Parser<'a, Octs>,
409 ) -> Result<Option<Self>, ParseError> {
410 if rtype == Rtype::SVCB {
411 Self::parse(parser).map(Some)
412 } else {
413 Ok(None)
414 }
415 }
416}
417
418impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
419 for SvcbRdata<HttpsVariant, Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
420{
421 fn parse_rdata(
422 rtype: Rtype,
423 parser: &mut Parser<'a, Octs>,
424 ) -> Result<Option<Self>, ParseError> {
425 if rtype == Rtype::HTTPS {
426 Self::parse(parser).map(Some)
427 } else {
428 Ok(None)
429 }
430 }
431}
432
433impl<Variant, Octs, Name> ComposeRecordData for SvcbRdata<Variant, Octs, Name>
434where
435 Self: RecordData,
436 Octs: AsRef<[u8]>,
437 Name: ToName,
438{
439 fn rdlen(&self, _compress: bool) -> Option<u16> {
440 Some(
441 u16::checked_add(
442 u16::COMPOSE_LEN + self.target.compose_len(),
443 self.params.len().try_into().expect("long params"),
444 )
445 .expect("long record data"),
446 )
447 }
448
449 fn compose_rdata<Target: Composer + ?Sized>(
450 &self,
451 target: &mut Target,
452 ) -> Result<(), Target::AppendError> {
453 self.priority.compose(target)?;
454 self.target.compose(target)?;
455 self.params.compose(target)
456 }
457
458 fn compose_canonical_rdata<Target: Composer + ?Sized>(
459 &self,
460 target: &mut Target,
461 ) -> Result<(), Target::AppendError> {
462 self.compose_rdata(target)
463 }
464}
465
466impl<Variant, Octs, Name> fmt::Display for SvcbRdata<Variant, Octs, Name>
469where
470 Octs: Octets,
471 Name: fmt::Display,
472{
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 write!(f, "{} {}. {}", self.priority, self.target, self.params)
475 }
476}
477
478impl<Variant, Octs, Name> fmt::Debug for SvcbRdata<Variant, Octs, Name>
479where
480 Octs: Octets,
481 Name: fmt::Debug,
482{
483 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484 f.debug_struct("SvcbRdata")
485 .field("priority", &self.priority)
486 .field("target", &self.target)
487 .field("params", &self.params)
488 .finish()
489 }
490}
491
492impl<Variant, Octs, Name> ZonefileFmt for SvcbRdata<Variant, Octs, Name>
495where
496 Octs: Octets,
497 Name: ToName,
498{
499 fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
500 p.block(|p| {
501 p.write_token(self.priority)?;
502 p.write_comment("priority")?;
503 p.write_token(self.target.fmt_with_dot())?;
504 p.write_comment("target")?;
505 p.write_show(&self.params)
506 })
507 }
508}
509
510#[cfg(test)]
513mod test {
514 use super::super::value::AllValues;
515 use super::super::UnknownSvcParam;
516 use super::*;
517 use crate::base::Name;
518 use core::str::FromStr;
519 use octseq::array::Array;
520
521 type Octets512 = Array<512>;
522 type Dname512 = Name<Array<512>>;
523 type Params512 = SvcParams<Array<512>>;
524
525 #[test]
530 fn test_vectors_alias() {
531 let rdata = b"\x00\x00\
532 \x03\x66\x6f\x6f\
533 \x07\x65\x78\x61\x6d\x70\x6c\x65\
534 \x03\x63\x6f\x6d\
535 \x00\
536 ";
537
538 let mut parser = Parser::from_ref(rdata.as_ref());
540 let svcb = Svcb::parse(&mut parser).unwrap();
541 assert_eq!(0, svcb.priority);
542 assert_eq!(
543 Dname512::from_str("foo.example.com").unwrap(),
544 svcb.target
545 );
546 assert_eq!(0, svcb.params.len());
547
548 let svcb_builder =
550 Svcb::new(svcb.priority, svcb.target, Params512::default())
551 .unwrap();
552
553 let mut buf = Octets512::new();
554 svcb_builder.compose_rdata(&mut buf).unwrap();
555 assert_eq!(rdata.as_ref(), buf.as_ref());
556 }
557
558 #[test]
559 fn test_vectors_unknown_param() {
560 let rdata = b"\x00\x01\
561 \x03\x66\x6f\x6f\
562 \x07\x65\x78\x61\x6d\x70\x6c\x65\
563 \x03\x63\x6f\x6d\
564 \x00\
565 \x02\x9b\
566 \x00\x05\
567 \x68\x65\x6c\x6c\x6f\
568 ";
569
570 let mut parser = Parser::from_ref(rdata.as_ref());
572 let svcb = Svcb::parse(&mut parser).unwrap();
573 assert_eq!(1, svcb.priority);
574 assert_eq!(
575 Dname512::from_str("foo.example.com").unwrap(),
576 svcb.target
577 );
578
579 let mut param_iter = svcb.params().iter();
580 match param_iter.next() {
581 Some(Ok(AllValues::Unknown(param))) => {
582 assert_eq!(0x029b, param.key().to_int());
583 assert_eq!(b"\x68\x65\x6c\x6c\x6f".as_ref(), *param.value(),);
584 }
585 r => panic!("{:?}", r),
586 }
587 assert_eq!(None, param_iter.next());
588
589 let svcb_builder = Svcb::new(
591 svcb.priority,
592 svcb.target,
593 Params512::from_values(|builder| {
594 builder.push(
595 &UnknownSvcParam::new(0x029b.into(), b"hello").unwrap(),
596 )
597 })
598 .unwrap(),
599 )
600 .unwrap();
601 let mut buf = Octets512::new();
602 svcb_builder.compose_rdata(&mut buf).unwrap();
603 assert_eq!(rdata.as_ref(), buf.as_ref());
604 }
605}