domain/rdata/svcb/
rdata.rs

1//! The SCVB/HTTPS record data.
2//!
3//! This is a private module. It’s public types are re-exported by the
4//! parent.
5use 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//------------ Svcb and Https ------------------------------------------------
20
21/// Service binding record data.
22///
23/// This type provides the record data for the various service binding record
24/// types. The particular record type is encoded via the `Variant` type
25/// argument with marker types representing the concrete types. Currently
26/// these are [`SvcbVariant`] for the SVCB record type and [`HttpsVariant`] for
27/// HTTPS. The aliases [`Svcb<..>`] and [`Https<..>`] are available for less
28/// typing.
29///
30/// The service binding record data contains three fields: a integer
31/// priority, a target name – the type of which is determined by the `Name`
32/// type argument –, and a sequence of service parameters. A separate type
33/// [`SvcParams`] has been defined for those which is generic over an
34/// octets sequence determined by the `Octs` type argument.
35///
36/// The record can be used in one of two modes, ‘alias mode’ or ‘service
37/// mode.’
38///
39/// In alias mode, there should only be one record with its priority set to
40/// 0 and no service parameters. In this case, the target name indicates the
41/// name that actually provides the service and should be resolved further.
42/// The root name can be used as the target name to indicate that the
43/// particular service is not being provided.
44///
45/// In ‘service mode,’ one or more records can exist and used in the order
46/// provided by the priority field with lower priority looked at first. Each
47/// record describes an alternative endpoint for the service and parameters
48/// for its use. What exactly this means depends on the protocol in question.
49///
50/// The owner name of service binding records determines which service the
51/// records apply to. The domain name for which the service is provided is
52/// prefixed by first the port and protocol of the service, both as
53/// underscore labels. So, for HTTPS on port 443, the prefix would be
54/// `_443._https`. However, the HTTPS record type allows to drop the prefix
55/// in that particular case.
56///
57/// Note that the above is a wholy inadequate summary of service bindings
58/// records. For accurate details, see
59/// [draft-ietf-dnsop-svcb-https](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https/).
60#[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    /// The priority field of the service binding record.
77    priority: u16,
78
79    /// The target field of the service binding record.
80    target: Name,
81
82    /// The parameters field of the service binding record.
83    params: SvcParams<Octs>,
84
85    /// A marker for the variant.
86    marker: PhantomData<Variant>,
87}
88
89/// The marker type for the SVCB record type.
90///
91/// Use this as the `Variant` type argument of the
92/// [`SvcbRdata<..>`] type to select an SVCB record.
93#[derive(Clone, Copy, Debug)]
94pub struct SvcbVariant;
95
96/// The marker type for the HTTPS record type.
97///
98/// Use this as the `Variant` type argument of the
99/// [`SvcbRdata<..>`] type to select an HTTPS record.
100#[derive(Clone, Copy, Debug)]
101pub struct HttpsVariant;
102
103/// A type alias for record data of the SVCB record type.
104///
105/// The SVCB record type is the generic type for service bindings of any
106/// service for which no special record type exists.
107///
108/// See [`SvcbRdata<..>`] for details.
109pub type Svcb<Octs, Name> = SvcbRdata<SvcbVariant, Octs, Name>;
110
111/// A type alias for record data of the HTTPS record type.
112///
113/// The HTTPS record type is the service binding type for the HTTPS service.
114///
115/// See [`SvcbRdata<..>`] for details.
116pub type Https<Octs, Name> = SvcbRdata<HttpsVariant, Octs, Name>;
117
118impl SvcbRdata<SvcbVariant, (), ()> {
119    /// The rtype of this record data type.
120    pub(crate) const RTYPE: Rtype = Rtype::SVCB;
121}
122
123impl SvcbRdata<HttpsVariant, (), ()> {
124    /// The rtype of this record data type.
125    pub(crate) const RTYPE: Rtype = Rtype::HTTPS;
126}
127
128impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
129    /// Create a new value from its components.
130    ///
131    /// Returns an error if the wire format of the record data would exceed
132    /// the length of 65,535 octets.
133    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    /// Creates a new value from its components without checking.
151    ///
152    /// # Safety
153    ///
154    /// The called must ensure that the wire format of the record data does
155    /// not exceed a length of 65,535 octets.
156    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    /// Parses service bindings record data from its wire format.
172    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    /// Returns the priority.
184    pub fn priority(&self) -> u16 {
185        self.priority
186    }
187
188    /// Returns whether this service binding is in alias mode.
189    ///
190    /// This is identical to `self.priority() == 0`.
191    pub fn is_alias(&self) -> bool {
192        self.priority == 0
193    }
194
195    /// Returns whether this service binding is in service mode.
196    ///
197    /// This is identical to `self.priority() != 0`.
198    pub fn is_service(&self) -> bool {
199        self.priority != 0
200    }
201
202    /// Returns the target name.
203    ///
204    /// Note the target name won't be translated to the owner automatically
205    /// in service mode if it equals the root name.
206    pub fn target(&self) -> &Name {
207        &self.target
208    }
209
210    /// Returns the parameters.
211    pub fn params(&self) -> &SvcParams<Octs> {
212        &self.params
213    }
214
215    /// Returns an identical value using different octets sequence types.
216    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
249//--- OctetsFrom and FlattenInto
250
251impl<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
283//--- PartialEq and Eq
284
285impl<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
309//--- Hash
310
311impl<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
321//--- PartialOrd, Ord, and CanonicalOrd
322
323impl<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
389//--- RecordData, ParseRecordData, ComposeRecordData
390
391impl<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
466//--- Display and Debug
467
468impl<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
492//--- ZonefileFmt
493
494impl<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//============ Tests =========================================================
511
512#[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    // We only do two tests here to see if the SvcbRdata type itself is
526    // working properly. Tests for all the value types live in
527    // super::params::test.
528
529    #[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        // parse test
539        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        // compose test
549        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        // parse test
571        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        // compose test
590        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}