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(
65        bound(
66            serialize = "
67                Name: serde::Serialize,
68                Octs: octseq::serde::SerializeOctets
69            ",
70            deserialize = "
71                Name: serde::Deserialize<'de>,
72                Octs: octseq::serde::DeserializeOctets<'de>
73            ",
74        )
75    )
76)]
77pub struct SvcbRdata<Variant, Octs, Name> {
78    /// The priority field of the service binding record.
79    priority: u16,
80
81    /// The target field of the service binding record.
82    target: Name,
83
84    /// The parameters field of the service binding record.
85    params: SvcParams<Octs>,
86
87    /// A marker for the variant.
88    marker: PhantomData<Variant>,
89}
90
91/// The marker type for the SVCB record type.
92///
93/// Use this as the `Variant` type argument of the
94/// [`SvcbRdata<..>`] type to select an SVCB record.
95#[derive(Clone, Copy, Debug)]
96pub struct SvcbVariant;
97
98/// The marker type for the HTTPS record type.
99///
100/// Use this as the `Variant` type argument of the
101/// [`SvcbRdata<..>`] type to select an HTTPS record.
102#[derive(Clone, Copy, Debug)]
103pub struct HttpsVariant;
104
105/// A type alias for record data of the SVCB record type.
106///
107/// The SVCB record type is the generic type for service bindings of any
108/// service for which no special record type exists.
109///
110/// See [`SvcbRdata<..>`] for details.
111pub type Svcb<Octs, Name> = SvcbRdata<SvcbVariant, Octs, Name>;
112
113/// A type alias for record data of the HTTPS record type.
114///
115/// The HTTPS record type is the service binding type for the HTTPS service.
116///
117/// See [`SvcbRdata<..>`] for details.
118pub type Https<Octs, Name> = SvcbRdata<HttpsVariant, Octs, Name>;
119
120impl SvcbRdata<SvcbVariant, (), ()> {
121    /// The rtype of this record data type.
122    pub(crate) const RTYPE: Rtype = Rtype::SVCB;
123}
124
125impl SvcbRdata<HttpsVariant, (), ()> {
126    /// The rtype of this record data type.
127    pub(crate) const RTYPE: Rtype = Rtype::HTTPS;
128}
129
130impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
131    /// Create a new value from its components.
132    ///
133    /// Returns an error if the wire format of the record data would exceed
134    /// the length of 65,535 octets.
135    pub fn new(
136        priority: u16, target: Name, params: SvcParams<Octs>
137    ) -> Result<Self, LongRecordData>
138    where Octs: AsRef<[u8]>, Name: ToName {
139        LongRecordData::check_len(
140            usize::from(
141                u16::COMPOSE_LEN + target.compose_len()
142            ).checked_add(params.len()).expect("long params")
143        )?;
144        Ok( unsafe { Self::new_unchecked(priority, target, params) })
145    }
146
147    /// Creates a new value from its components without checking.
148    ///
149    /// # Safety
150    ///
151    /// The called must ensure that the wire format of the record data does
152    /// not exceed a length of 65,535 octets.
153    pub unsafe fn new_unchecked(
154        priority: u16, target: Name, params: SvcParams<Octs>
155    ) -> Self {
156        SvcbRdata { priority, target, params, marker: PhantomData }
157    }
158}
159
160impl<Variant, Octs: AsRef<[u8]>> SvcbRdata<Variant, Octs, ParsedName<Octs>> {
161    /// Parses service bindings record data from its wire format.
162    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
163        parser: &mut Parser<'a, Src>,
164    ) -> Result<Self, ParseError> {
165        let priority = u16::parse(parser)?;
166        let target = ParsedName::parse(parser)?;
167        let params = SvcParams::parse(parser)?;
168        Ok(unsafe { Self::new_unchecked(priority, target, params) })
169    }
170}
171
172impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
173    /// Returns the priority.
174    pub fn priority(&self) -> u16 {
175        self.priority
176    }
177
178    /// Returns whether this service binding is in alias mode.
179    ///
180    /// This is identical to `self.priority() == 0`.
181    pub fn is_alias(&self) -> bool {
182        self.priority == 0
183    }
184
185    /// Returns whether this service binding is in service mode.
186    ///
187    /// This is identical to `self.priority() != 0`.
188    pub fn is_service(&self) -> bool {
189        self.priority != 0
190    }
191
192    /// Returns the target name.
193    ///
194    /// Note the target name won't be translated to the owner automatically
195    /// in service mode if it equals the root name.
196    pub fn target(&self) -> &Name {
197        &self.target
198    }
199
200    /// Returns the parameters.
201    pub fn params(&self) -> &SvcParams<Octs> {
202        &self.params
203    }
204
205    /// Returns an identical value using different octets sequence types.
206    pub(crate) fn convert_octets<TOcts, TName>(
207        self
208    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
209    where
210        TOcts: OctetsFrom<Octs>,
211        TName: OctetsFrom<Name, Error = TOcts::Error>,
212    {
213        Ok(unsafe {
214            SvcbRdata::new_unchecked(
215                self.priority,
216                self.target.try_octets_into()?,
217                self.params.try_octets_into()?,
218            )
219        })
220    }
221
222    pub(crate) fn flatten<TOcts, TName>(
223        self,
224    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
225    where
226        TOcts: OctetsFrom<Octs>,
227        Name: FlattenInto<TName, AppendError = TOcts::Error>,
228    {
229        Ok(unsafe {
230            SvcbRdata::new_unchecked(
231                self.priority,
232                self.target.try_flatten_into()?,
233                self.params.try_octets_into()?,
234            )
235        })
236    }
237}
238
239//--- OctetsFrom and FlattenInto
240
241impl<Variant, Octs, SrcOctets, Name, SrcName>
242    OctetsFrom<SvcbRdata<Variant, SrcOctets, SrcName>>
243    for SvcbRdata<Variant, Octs, Name>
244where
245    Octs: OctetsFrom<SrcOctets>,
246    Name: OctetsFrom<SrcName, Error = Octs::Error>,
247{
248    type Error = Octs::Error;
249
250    fn try_octets_from(
251        source: SvcbRdata<Variant, SrcOctets, SrcName>,
252    ) -> Result<Self, Self::Error> {
253        source.convert_octets()
254    }
255}
256
257impl<Variant, Octs, TOcts, Name, TName>
258    FlattenInto<SvcbRdata<Variant, TOcts, TName>>
259    for SvcbRdata<Variant, Octs, Name>
260where
261    TOcts: OctetsFrom<Octs>,
262    Name: FlattenInto<TName, AppendError = TOcts::Error>,
263{
264    type AppendError = TOcts::Error;
265
266    fn try_flatten_into(
267        self,
268    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error> {
269        self.flatten()
270    }
271}
272
273//--- PartialEq and Eq
274
275impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
276    PartialEq<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
277for SvcbRdata<Variant, Octs, Name>
278where
279    Octs: AsRef<[u8]>,
280    OtherOcts: AsRef<[u8]>,
281    Name: ToName,
282    OtherName: ToName,
283{
284    fn eq(
285        &self, other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>
286    ) -> bool {
287        self.priority == other.priority
288            && self.target.name_eq(&other.target)
289            && self.params == other.params
290    }
291}
292
293impl<Variant, Octs: AsRef<[u8]>, Name: ToName> Eq
294for SvcbRdata<Variant, Octs, Name> { }
295
296//--- Hash
297
298impl<Variant, Octs: AsRef<[u8]>, Name: hash::Hash> hash::Hash
299for SvcbRdata<Variant, Octs, Name> {
300    fn hash<H: hash::Hasher>(&self, state: &mut H) {
301        self.priority.hash(state);
302        self.target.hash(state);
303        self.params.hash(state);
304    }
305}
306
307//--- PartialOrd, Ord, and CanonicalOrd
308
309impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
310    PartialOrd<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
311for SvcbRdata<Variant, Octs, Name>
312where
313    Octs: AsRef<[u8]>,
314    OtherOcts: AsRef<[u8]>,
315    Name: ToName,
316    OtherName: ToName,
317{
318    fn partial_cmp(
319        &self, other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>
320    ) -> Option<cmp::Ordering> {
321        match self.priority.partial_cmp(&other.priority) {
322            Some(cmp::Ordering::Equal) => { }
323            other => return other
324        }
325        match self.target.name_cmp(&other.target) {
326            cmp::Ordering::Equal => { }
327            other => return Some(other)
328        }
329        self.params.partial_cmp(&other.params)
330    }
331}
332
333impl<Variant, Octs: AsRef<[u8]>, Name: ToName> Ord
334for SvcbRdata<Variant, Octs, Name> {
335    fn cmp(&self, other: &Self) -> cmp::Ordering {
336        match self.priority.cmp(&other.priority) {
337            cmp::Ordering::Equal => { }
338            other => return other
339        }
340        match self.target.name_cmp(&other.target) {
341            cmp::Ordering::Equal => { }
342            other => return other
343        }
344        self.params.cmp(&other.params)
345    }
346}
347
348impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
349    CanonicalOrd<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
350for SvcbRdata<Variant, Octs, Name>
351where
352    Octs: AsRef<[u8]>,
353    OtherOcts: AsRef<[u8]>,
354    Name: ToName,
355    OtherName: ToName,
356{
357    fn canonical_cmp(
358        &self, other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>
359    ) -> cmp::Ordering {
360        match self.priority.cmp(&other.priority) {
361            cmp::Ordering::Equal => { }
362            other => return other
363        }
364        match self.target.name_cmp(&other.target) {
365            cmp::Ordering::Equal => { }
366            other => return other
367        }
368        self.params.canonical_cmp(&other.params)
369    }
370}
371
372//--- RecordData, ParseRecordData, ComposeRecordData
373
374impl<Octs, Name> RecordData for SvcbRdata<SvcbVariant, Octs, Name> {
375    fn rtype(&self) -> Rtype {
376        Rtype::SVCB
377    }
378}
379
380impl<Octs, Name> RecordData for SvcbRdata<HttpsVariant, Octs, Name> {
381    fn rtype(&self) -> Rtype {
382        Rtype::HTTPS
383    }
384}
385
386impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
387for SvcbRdata<SvcbVariant, Octs::Range<'a>, ParsedName<Octs::Range<'a>>> {
388    fn parse_rdata(
389        rtype: Rtype, parser: &mut Parser<'a, Octs>
390    ) -> Result<Option<Self>, ParseError> {
391        if rtype == Rtype::SVCB {
392            Self::parse(parser).map(Some)
393        }
394        else {
395            Ok(None)
396        }
397    }
398}
399
400impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
401for SvcbRdata<HttpsVariant, Octs::Range<'a>, ParsedName<Octs::Range<'a>>> {
402    fn parse_rdata(
403        rtype: Rtype, parser: &mut Parser<'a, Octs>
404    ) -> Result<Option<Self>, ParseError> {
405        if rtype == Rtype::HTTPS {
406            Self::parse(parser).map(Some)
407        }
408        else {
409            Ok(None)
410        }
411    }
412}
413
414impl<Variant, Octs, Name> ComposeRecordData for SvcbRdata<Variant, Octs, Name>
415where Self: RecordData, Octs: AsRef<[u8]>, Name: ToName {
416    fn rdlen(&self, _compress: bool) -> Option<u16> {
417        Some(
418            u16::checked_add(
419                u16::COMPOSE_LEN + self.target.compose_len(),
420                self.params.len().try_into().expect("long params"),
421            ).expect("long record data")
422        )
423    }
424
425    fn compose_rdata<Target: Composer + ?Sized>(
426        &self, target: &mut Target
427    ) -> Result<(), Target::AppendError> {
428        self.priority.compose(target)?;
429        self.target.compose(target)?;
430        self.params.compose(target)
431    }
432
433    fn compose_canonical_rdata<Target: Composer + ?Sized>(
434        &self, target: &mut Target
435    ) -> Result<(), Target::AppendError> {
436        self.compose_rdata(target)
437    }
438}
439
440//--- Display and Debug
441
442impl<Variant, Octs, Name> fmt::Display for SvcbRdata<Variant, Octs, Name>
443where
444    Octs: Octets,
445    Name: fmt::Display,
446{
447    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
448        write!(f, "{} {}. {}", self.priority, self.target, self.params)
449    }
450}
451
452impl<Variant, Octs, Name> fmt::Debug for SvcbRdata<Variant, Octs, Name>
453where
454    Octs: Octets,
455    Name: fmt::Debug,
456{
457    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458        f.debug_struct("SvcbRdata")
459            .field("priority", &self.priority)
460            .field("target", &self.target)
461            .field("params", &self.params)
462            .finish()
463    }
464}
465
466//--- ZonefileFmt
467
468impl<Variant, Octs, Name> ZonefileFmt for SvcbRdata<Variant, Octs, Name>
469where
470    Octs: Octets,
471    Name: ToName,
472{
473    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
474        p.block(|p| {
475            p.write_token(self.priority)?;
476            p.write_comment("priority")?;
477            p.write_token(self.target.fmt_with_dot())?;
478            p.write_comment("target")?;
479            p.write_show(&self.params)
480        })
481    }
482}
483
484//============ Tests =========================================================
485
486#[cfg(test)]
487mod test {
488    use super::*;
489    use super::super::UnknownSvcParam;
490    use super::super::value::AllValues;
491    use crate::base::Name;
492    use octseq::array::Array;
493    use core::str::FromStr;
494
495    type Octets512 = Array<512>;
496    type Dname512 = Name<Array<512>>;
497    type Params512 = SvcParams<Array<512>>;
498
499    // We only do two tests here to see if the SvcbRdata type itself is
500    // working properly. Tests for all the value types live in
501    // super::params::test.
502
503    #[test]
504    fn test_vectors_alias() {
505        let rdata =
506            b"\x00\x00\
507              \x03\x66\x6f\x6f\
508                \x07\x65\x78\x61\x6d\x70\x6c\x65\
509                \x03\x63\x6f\x6d\
510                \x00\
511            ";
512
513        // parse test
514        let mut parser = Parser::from_ref(rdata.as_ref());
515        let svcb = Svcb::parse(&mut parser).unwrap();
516        assert_eq!(0, svcb.priority);
517        assert_eq!(
518            Dname512::from_str("foo.example.com").unwrap(),
519            svcb.target
520        );
521        assert_eq!(0, svcb.params.len());
522
523        // compose test
524        let svcb_builder = Svcb::new(
525            svcb.priority, svcb.target, Params512::default()
526        ).unwrap();
527
528        let mut buf = Octets512::new();
529        svcb_builder.compose_rdata(&mut buf).unwrap();
530        assert_eq!(rdata.as_ref(), buf.as_ref());
531    }
532
533    #[test]
534    fn test_vectors_unknown_param() {
535        let rdata =
536            b"\x00\x01\
537              \x03\x66\x6f\x6f\
538                \x07\x65\x78\x61\x6d\x70\x6c\x65\
539                \x03\x63\x6f\x6d\
540                \x00\
541              \x02\x9b\
542              \x00\x05\
543              \x68\x65\x6c\x6c\x6f\
544            ";
545
546        // parse test
547        let mut parser = Parser::from_ref(rdata.as_ref());
548        let svcb = Svcb::parse(&mut parser).unwrap();
549        assert_eq!(1, svcb.priority);
550        assert_eq!(
551            Dname512::from_str("foo.example.com").unwrap(),
552            svcb.target
553        );
554
555        let mut param_iter = svcb.params().iter();
556        match param_iter.next() {
557            Some(Ok(AllValues::Unknown(param))) => {
558                assert_eq!(0x029b, param.key().to_int());
559                assert_eq!(b"\x68\x65\x6c\x6c\x6f".as_ref(), *param.value(),);
560            }
561            r => panic!("{:?}", r),
562        }
563        assert_eq!(None, param_iter.next());
564
565        // compose test
566        let svcb_builder = Svcb::new(
567            svcb.priority, svcb.target,
568            Params512::from_values(|builder| {
569                builder.push(
570                    &UnknownSvcParam::new(0x029b.into(), b"hello").unwrap()
571                )
572            }).unwrap()
573        ).unwrap();
574        let mut buf = Octets512::new();
575        svcb_builder.compose_rdata(&mut buf).unwrap();
576        assert_eq!(rdata.as_ref(), buf.as_ref());
577    }
578}
579