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