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::iana::Rtype;
7use crate::base::name::{FlattenInto, ParsedDname, ToDname};
8use crate::base::rdata::{
9    ComposeRecordData, LongRecordData, ParseRecordData, RecordData,
10};
11use crate::base::wire::{Compose, Composer, Parse, ParseError};
12use octseq::octets::{Octets, OctetsFrom, OctetsInto};
13use octseq::parse::Parser;
14use core::{cmp, fmt, hash};
15use core::marker::PhantomData;
16
17//------------ Svcb and Https ------------------------------------------------
18
19/// Service binding record data.
20///
21/// This type provides the record data for the various service binding record
22/// types. The particular record type is encoded via the `Variant` type
23/// argument with marker types representing the concrete types. Currently
24/// these are `ScvbVariant` for the SVCB record type and `HttpsVariant` for
25/// HTTPS. The aliases `Svcb<..>` and `Https<..>` are available for less
26/// typing.
27///
28/// The service binding record data contains three fields: a integer
29/// priority, a target name – the type of which is determined by the `Name`
30/// type argument –, and a sequence of service parameters. A separate type
31/// [`SvcParams`] has been defined for those which is generic over an
32/// octets sequence determined by the `Octs` type argument.
33///
34/// The record can be used in one of two modes, ‘alias mode’ or ‘service
35/// mode.’
36///
37/// In alias mode, there should only be one record with its priority set to
38/// 0 and no service parameters. In this case, the target name indicates the
39/// name that actually provides the service and should be resolved further.
40/// The root name can be used as the target name to indicate that the
41/// particular service is not being provided.
42///
43/// In ‘service mode,’ one or more records can exist and used in the order
44/// provided by the priority field with lower priority looked at first. Each
45/// record describes an alternative endpoint for the service and parameters
46/// for its use. What exactly this means depends on the protocol in question.
47///
48/// The owner name of service binding records determines which service the
49/// records apply to. The domain name for which the service is provided is
50/// prefixed by first the port and protocol of the service, both as
51/// underscore labels. So, for HTTPS on port 443, the prefix would be
52/// `_443._https`. However, the HTTPS record type allows to drop the prefix
53/// in that particular case.
54///
55/// Note that the above is a wholy inadequate summary of service bindings
56/// records. For accurate details, see
57/// [draft-ietf-dnsop-svcb-https](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https/).
58#[derive(Clone)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(
61    feature = "serde",
62    serde(
63        bound(
64            serialize = "
65                Name: serde::Serialize,
66                Octs: octseq::serde::SerializeOctets
67            ",
68            deserialize = "
69                Name: serde::Deserialize<'de>,
70                Octs: octseq::serde::DeserializeOctets<'de>
71            ",
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<..>`][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<..>`][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<..>`][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<..>`][SvcbRdata] for details.
116pub type Https<Octs, Name> = SvcbRdata<HttpsVariant, Octs, Name>;
117
118impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
119    /// Create a new value from its components.
120    ///
121    /// Returns an error if the wire format of the record data would exceed
122    /// the length of 65,535 octets.
123    pub fn new(
124        priority: u16, target: Name, params: SvcParams<Octs>
125    ) -> Result<Self, LongRecordData>
126    where Octs: AsRef<[u8]>, Name: ToDname {
127        LongRecordData::check_len(
128            usize::from(
129                u16::COMPOSE_LEN + target.compose_len()
130            ).checked_add(params.len()).expect("long params")
131        )?;
132        Ok( unsafe { Self::new_unchecked(priority, target, params) })
133    }
134
135    /// Creates a new value from its components without checking.
136    ///
137    /// # Safety
138    ///
139    /// The called must ensure that the wire format of the record data does
140    /// not exceed a length of 65,535 octets.
141    pub unsafe fn new_unchecked(
142        priority: u16, target: Name, params: SvcParams<Octs>
143    ) -> Self {
144        SvcbRdata { priority, target, params, marker: PhantomData }
145    }
146}
147
148impl<Variant, Octs: AsRef<[u8]>> SvcbRdata<Variant, Octs, ParsedDname<Octs>> {
149    /// Parses service bindings record data from its wire format.
150    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
151        parser: &mut Parser<'a, Src>
152    ) -> Result<Self, ParseError> {
153        let priority = u16::parse(parser)?;
154        let target = ParsedDname::parse(parser)?;
155        let params = SvcParams::parse(parser)?;
156        Ok(unsafe {
157            Self::new_unchecked(priority, target, params)
158        })
159    }
160}
161
162impl<Variant, Octs, Name> SvcbRdata<Variant, Octs, Name> {
163    /// Returns the priority.
164    pub fn priority(&self) -> u16 {
165        self.priority
166    }
167
168    /// Returns whether this service binding is in alias mode.
169    ///
170    /// This is identical to `self.priority() == 0`.
171    pub fn is_alias(&self) -> bool {
172        self.priority == 0
173    }
174
175    /// Returns whether this service binding is in service mode.
176    ///
177    /// This is identical to `self.priority() != 0`.
178    pub fn is_service(&self) -> bool {
179        self.priority != 0
180    }
181
182    /// Returns the target name.
183    ///
184    /// Note the target name won't be translated to the owner automatically
185    /// in service mode if it equals the root name.
186    pub fn target(&self) -> &Name {
187        &self.target
188    }
189
190    /// Returns the parameters.
191    pub fn params(&self) -> &SvcParams<Octs> {
192        &self.params
193    }
194
195    /// Returns an identical value using different octets sequence types.
196    pub(crate) fn convert_octets<TOcts, TName>(
197        self
198    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
199    where
200        TOcts: OctetsFrom<Octs>,
201        TName: OctetsFrom<Name, Error = TOcts::Error>,
202    {
203        Ok(unsafe {
204            SvcbRdata::new_unchecked(
205                self.priority,
206                self.target.try_octets_into()?,
207                self.params.try_octets_into()?,
208            )
209        })
210    }
211
212    pub(crate) fn flatten<TOcts, TName>(
213        self,
214    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error>
215    where
216        TOcts: OctetsFrom<Octs>,
217        Name: FlattenInto<TName, AppendError = TOcts::Error>,
218    {
219        Ok(unsafe {
220            SvcbRdata::new_unchecked(
221                self.priority,
222                self.target.try_flatten_into()?,
223                self.params.try_octets_into()?,
224            )
225        })
226    }
227}
228
229//--- OctetsFrom and FlattenInto
230
231impl<Variant, Octs, SrcOctets, Name, SrcName>
232    OctetsFrom<SvcbRdata<Variant, SrcOctets, SrcName>>
233    for SvcbRdata<Variant, Octs, Name>
234where
235    Octs: OctetsFrom<SrcOctets>,
236    Name: OctetsFrom<SrcName, Error = Octs::Error>,
237{
238    type Error = Octs::Error;
239
240    fn try_octets_from(
241        source: SvcbRdata<Variant, SrcOctets, SrcName>,
242    ) -> Result<Self, Self::Error> {
243        source.convert_octets()
244    }
245}
246
247impl<Variant, Octs, TOcts, Name, TName>
248    FlattenInto<SvcbRdata<Variant, TOcts, TName>>
249    for SvcbRdata<Variant, Octs, Name>
250where
251    TOcts: OctetsFrom<Octs>,
252    Name: FlattenInto<TName, AppendError = TOcts::Error>
253{
254    type AppendError = TOcts::Error;
255
256    fn try_flatten_into(
257        self
258    ) -> Result<SvcbRdata<Variant, TOcts, TName>, TOcts::Error> {
259        self.flatten()
260    }
261}
262
263//--- PartialEq and Eq
264
265impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
266    PartialEq<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
267for SvcbRdata<Variant, Octs, Name>
268where
269    Octs: AsRef<[u8]>,
270    OtherOcts: AsRef<[u8]>,
271    Name: ToDname,
272    OtherName: ToDname,
273{
274    fn eq(
275        &self, other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>
276    ) -> bool {
277        self.priority == other.priority
278            && self.target.name_eq(&other.target)
279            && self.params == other.params
280    }
281}
282
283impl<Variant, Octs: AsRef<[u8]>, Name: ToDname> Eq
284for SvcbRdata<Variant, Octs, Name> { }
285
286//--- Hash
287
288impl<Variant, Octs: AsRef<[u8]>, Name: hash::Hash> hash::Hash
289for SvcbRdata<Variant, Octs, Name> {
290    fn hash<H: hash::Hasher>(&self, state: &mut H) {
291        self.priority.hash(state);
292        self.target.hash(state);
293        self.params.hash(state);
294    }
295}
296
297//--- PartialOrd, Ord, and CanonicalOrd
298
299impl<Variant, OtherVariant, Octs, OtherOcts, Name, OtherName>
300    PartialOrd<SvcbRdata<OtherVariant, OtherOcts, OtherName>>
301for SvcbRdata<Variant, Octs, Name>
302where
303    Octs: AsRef<[u8]>,
304    OtherOcts: AsRef<[u8]>,
305    Name: ToDname,
306    OtherName: ToDname,
307{
308    fn partial_cmp(
309        &self, other: &SvcbRdata<OtherVariant, OtherOcts, OtherName>
310    ) -> Option<cmp::Ordering> {
311        match self.priority.partial_cmp(&other.priority) {
312            Some(cmp::Ordering::Equal) => { }
313            other => return other
314        }
315        match self.target.name_cmp(&other.target) {
316            cmp::Ordering::Equal => { }
317            other => return Some(other)
318        }
319        self.params.partial_cmp(&other.params)
320    }
321}
322
323impl<Variant, Octs: AsRef<[u8]>, Name: ToDname> Ord
324for SvcbRdata<Variant, Octs, Name> {
325    fn cmp(&self, other: &Self) -> cmp::Ordering {
326        match self.priority.cmp(&other.priority) {
327            cmp::Ordering::Equal => { }
328            other => return other
329        }
330        match self.target.name_cmp(&other.target) {
331            cmp::Ordering::Equal => { }
332            other => return other
333        }
334        self.params.cmp(&other.params)
335    }
336}
337
338//--- RecordData, ParseRecordData, ComposeRecordData
339
340impl<Octs, Name> RecordData for SvcbRdata<SvcbVariant, Octs, Name> {
341    fn rtype(&self) -> Rtype {
342        Rtype::Svcb
343    }
344}
345
346impl<Octs, Name> RecordData for SvcbRdata<HttpsVariant, Octs, Name> {
347    fn rtype(&self) -> Rtype {
348        Rtype::Https
349    }
350}
351
352impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
353for SvcbRdata<SvcbVariant, Octs::Range<'a>, ParsedDname<Octs::Range<'a>>> {
354    fn parse_rdata(
355        rtype: Rtype, parser: &mut Parser<'a, Octs>
356    ) -> Result<Option<Self>, ParseError> {
357        if rtype == Rtype::Svcb {
358            Self::parse(parser).map(Some)
359        }
360        else {
361            Ok(None)
362        }
363    }
364}
365
366impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
367for SvcbRdata<HttpsVariant, Octs::Range<'a>, ParsedDname<Octs::Range<'a>>> {
368    fn parse_rdata(
369        rtype: Rtype, parser: &mut Parser<'a, Octs>
370    ) -> Result<Option<Self>, ParseError> {
371        if rtype == Rtype::Https{
372            Self::parse(parser).map(Some)
373        }
374        else {
375            Ok(None)
376        }
377    }
378}
379
380impl<Variant, Octs, Name> ComposeRecordData for SvcbRdata<Variant, Octs, Name>
381where Self: RecordData, Octs: AsRef<[u8]>, Name: ToDname {
382    fn rdlen(&self, _compress: bool) -> Option<u16> {
383        Some(
384            u16::checked_add(
385                u16::COMPOSE_LEN + self.target.compose_len(),
386                self.params.len().try_into().expect("long params"),
387            ).expect("long record data")
388        )
389    }
390
391    fn compose_rdata<Target: Composer + ?Sized>(
392        &self, target: &mut Target
393    ) -> Result<(), Target::AppendError> {
394        self.priority.compose(target)?;
395        self.target.compose(target)?;
396        self.params.compose(target)
397    }
398
399    fn compose_canonical_rdata<Target: Composer + ?Sized>(
400        &self, target: &mut Target
401    ) -> Result<(), Target::AppendError> {
402        self.compose_rdata(target)
403    }
404}
405
406//--- Display and Debug
407
408impl<Variant, Octs, Name> fmt::Display for SvcbRdata<Variant, Octs, Name>
409where
410    Octs: Octets,
411    Name: fmt::Display,
412{
413    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
414        write!(f, "{} {} {}", self.priority, self.target, self.params)
415    }
416}
417
418impl<Variant, Octs, Name> fmt::Debug for SvcbRdata<Variant, Octs, Name>
419where
420    Octs: Octets,
421    Name: fmt::Debug,
422{
423    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424        f.debug_struct("SvcbRdata")
425            .field("priority", &self.priority)
426            .field("target", &self.target)
427            .field("params", &self.params)
428            .finish()
429    }
430}
431
432//============ Tests =========================================================
433
434#[cfg(test)]
435mod test {
436    use super::*;
437    use super::super::UnknownSvcParam;
438    use super::super::value::AllValues;
439    use crate::base::Dname;
440    use octseq::array::Array;
441    use core::str::FromStr;
442
443    type Octets512 = Array<512>;
444    type Dname512 = Dname<Array<512>>;
445    type Params512 = SvcParams<Array<512>>;
446
447    // We only do two tests here to see if the SvcbRdata type itself is
448    // working properly. Tests for all the value types live in
449    // super::params::test.
450
451    #[test]
452    fn test_vectors_alias() {
453        let rdata =
454            b"\x00\x00\
455              \x03\x66\x6f\x6f\
456                \x07\x65\x78\x61\x6d\x70\x6c\x65\
457                \x03\x63\x6f\x6d\
458                \x00\
459            ";
460
461        // parse test
462        let mut parser = Parser::from_ref(rdata.as_ref());
463        let svcb = Svcb::parse(&mut parser).unwrap();
464        assert_eq!(0, svcb.priority);
465        assert_eq!(
466            Dname512::from_str("foo.example.com").unwrap(),
467            svcb.target
468        );
469        assert_eq!(0, svcb.params.len());
470
471        // compose test
472        let svcb_builder = Svcb::new(
473            svcb.priority, svcb.target, Params512::default()
474        ).unwrap();
475
476        let mut buf = Octets512::new();
477        svcb_builder.compose_rdata(&mut buf).unwrap();
478        assert_eq!(rdata.as_ref(), buf.as_ref());
479    }
480
481    #[test]
482    fn test_vectors_unknown_param() {
483        let rdata =
484            b"\x00\x01\
485              \x03\x66\x6f\x6f\
486                \x07\x65\x78\x61\x6d\x70\x6c\x65\
487                \x03\x63\x6f\x6d\
488                \x00\
489              \x02\x9b\
490              \x00\x05\
491              \x68\x65\x6c\x6c\x6f\
492            ";
493
494        // parse test
495        let mut parser = Parser::from_ref(rdata.as_ref());
496        let svcb = Svcb::parse(&mut parser).unwrap();
497        assert_eq!(1, svcb.priority);
498        assert_eq!(
499            Dname512::from_str("foo.example.com").unwrap(),
500            svcb.target
501        );
502
503        let mut param_iter = svcb.params().iter();
504        match param_iter.next() {
505            Some(Ok(AllValues::Unknown(param))) => {
506                assert_eq!(0x029b, param.key());
507                assert_eq!(b"\x68\x65\x6c\x6c\x6f".as_ref(), *param.value(),);
508            }
509            r => panic!("{:?}", r),
510        }
511        assert_eq!(None, param_iter.next());
512
513        // compose test
514        let svcb_builder = Svcb::new(
515            svcb.priority, svcb.target,
516            Params512::from_values(|builder| {
517                builder.push(
518                    &UnknownSvcParam::new(0x029b.into(), b"hello").unwrap()
519                )
520            }).unwrap()
521        ).unwrap();
522        let mut buf = Octets512::new();
523        svcb_builder.compose_rdata(&mut buf).unwrap();
524        assert_eq!(rdata.as_ref(), buf.as_ref());
525    }
526}
527