domain/base/opt/
chain.rs

1//! EDNS option to request the complete DNSSEC validation chain.
2//!
3//! The option in this module – [`Chain<Name>`] – allows a validating resolver
4//! to request to include all records necessary to validate the answer in the
5//! response.
6//!
7//! The option is defined in [RFC 7901](https://tools.ietf.org/html/rfc7901).
8
9use core::fmt;
10use super::super::iana::OptionCode;
11use super::super::message_builder::OptBuilder;
12use super::super::name::{Dname, ToDname};
13use super::super::wire::{Composer, ParseError};
14use super::{Opt, OptData, ComposeOptData, ParseOptData};
15use octseq::builder::OctetsBuilder;
16use octseq::octets::{Octets, OctetsFrom};
17use octseq::parse::Parser;
18use core::hash;
19use core::cmp::Ordering;
20
21
22//------------ Chain --------------------------------------------------------
23
24/// Option data for the CHAIN option.
25///
26/// The CHAIN option can be used to request that the queried include all the
27/// records necessary to validate the DNSSEC signatures of an answer. The
28/// option includes the absolute domain name that serves as the starting
29/// point of the included records, i.e., the suffix of the queried name
30/// furthest away from the root to which the requesting resolver already has
31/// all necessary records.
32#[derive(Clone, Copy, Debug)]
33pub struct Chain<Name: ?Sized> {
34    /// The start name AKA ‘closest trust point.’
35    start: Name
36}
37
38impl<Name: ?Sized> Chain<Name> {
39    /// Creates new CHAIN option data using the given name as the start.
40    pub fn new(start: Name) -> Self
41    where
42        Name: Sized
43    {
44        Chain { start }
45    }
46
47    /// Creates a reference to CHAIN option data from a reference to the start.
48    pub fn new_ref(start: &Name) -> &Self {
49        unsafe {
50            &*(start as *const Name as *const Self)
51        }
52    }
53
54    /// Returns a reference to the start point.
55    ///
56    /// The start point is the name furthest along the chain to which the
57    /// requester already has all necessary records.
58    pub fn start(&self) -> &Name {
59        &self.start
60    }
61
62    /// Converts the value into the start point.
63    pub fn into_start(self) -> Name
64    where
65        Name: Sized
66    {
67        self.start
68    }
69}
70
71impl<Octs> Chain<Dname<Octs>> {
72    /// Parses CHAIN option data from its wire format.
73    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
74        parser: &mut Parser<'a, Src>
75    ) -> Result<Self, ParseError> {
76        Dname::parse(parser).map(Self::new)
77    }
78}
79
80//--- OctetsFrom
81
82impl<Name, SrcName> OctetsFrom<Chain<SrcName>> for Chain<Name>
83where Name: OctetsFrom<SrcName> {
84    type Error = Name::Error;
85
86    fn try_octets_from(src: Chain<SrcName>) -> Result<Self, Self::Error> {
87        Name::try_octets_from(src.start).map(Self::new)
88    }
89}
90
91//--- PartialEq and Eq
92
93impl<Name, OtherName> PartialEq<Chain<OtherName>> for Chain<Name>
94where
95    Name: ToDname,
96    OtherName: ToDname
97{
98    fn eq(&self, other: &Chain<OtherName>) -> bool {
99        self.start().name_eq(other.start())
100    }
101}
102
103impl<Name: ToDname> Eq for Chain<Name> { }
104
105//--- PartialOrd and Ord
106
107impl<Name, OtherName> PartialOrd<Chain<OtherName>> for Chain<Name>
108where
109    Name: ToDname,
110    OtherName: ToDname
111{
112    fn partial_cmp(&self, other: &Chain<OtherName>) -> Option<Ordering> {
113        Some(self.start().name_cmp(other.start()))
114    }
115}
116
117impl<Name: ToDname> Ord for Chain<Name> {
118    fn cmp(&self, other: &Self) -> Ordering {
119        self.start().name_cmp(other.start())
120    }
121}
122
123//--- Hash
124
125impl<Name: hash::Hash> hash::Hash for Chain<Name> {
126    fn hash<H: hash::Hasher>(&self, state: &mut H) {
127        self.start().hash(state)
128    }
129}
130
131//--- OptData
132
133impl<Name> OptData for Chain<Name> {
134    fn code(&self) -> OptionCode {
135        OptionCode::Chain
136    }
137}
138
139impl<'a, Octs> ParseOptData<'a, Octs> for Chain<Dname<Octs::Range<'a>>>
140where Octs: Octets {
141    fn parse_option(
142        code: OptionCode,
143        parser: &mut Parser<'a, Octs>,
144    ) -> Result<Option<Self>, ParseError> {
145        if code == OptionCode::Chain {
146            Self::parse(parser).map(Some)
147        }
148        else {
149            Ok(None)
150        }
151    }
152}
153
154impl<Name: ToDname> ComposeOptData for Chain<Name> {
155    fn compose_len(&self) -> u16 {
156        self.start.compose_len()
157    }
158
159    fn compose_option<Target: OctetsBuilder + ?Sized>(
160        &self, target: &mut Target
161    ) -> Result<(), Target::AppendError> {
162        self.start.compose(target)
163    }
164}
165
166//--- Display
167
168impl<Name: fmt::Display> fmt::Display for Chain<Name> {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        write!(f, "{}", self.start)
171    }
172}
173
174//--- Extended Opt and OptBuilder
175
176impl<Octs: Octets> Opt<Octs> {
177    /// Returns the first CHAIN option if present.
178    ///
179    /// The CHAIN option allows a client to request that all records that
180    /// are necessary for DNSSEC validation are included in the response.
181    pub fn chain(&self) -> Option<Chain<Dname<Octs::Range<'_>>>> {
182        self.first()
183    }
184}
185
186impl<'a, Target: Composer> OptBuilder<'a, Target> {
187    /// Appends the CHAIN option.
188    ///
189    /// The CHAIN option allows a client to request that all records that
190    /// are necessary for DNSSEC validation are included in the response.
191    /// The `start` name is the longest suffix of the queried owner name
192    /// for which the client already has all necessary records.
193    pub fn chain(
194        &mut self, start: impl ToDname
195    ) -> Result<(), Target::AppendError> {
196        self.push(&Chain::new(start))
197    }
198}
199
200//============ Testing ======================================================
201
202#[cfg(test)]
203#[cfg(all(feature = "std", feature = "bytes"))]
204mod test {
205    use super::*;
206    use super::super::test::test_option_compose_parse;
207    use std::vec::Vec;
208    use core::str::FromStr;
209    
210    #[test]
211    #[allow(clippy::redundant_closure)] // lifetimes ...
212    fn chain_compose_parse() {
213        test_option_compose_parse(
214            &Chain::new(Dname::<Vec<u8>>::from_str("example.com").unwrap()),
215            |parser| Chain::parse(parser)
216        );
217    }
218}
219