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).
89use 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;
202122//------------ Chain --------------------------------------------------------
2324/// 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.’
35start: Name
36}
3738impl<Name: ?Sized> Chain<Name> {
39/// Creates new CHAIN option data using the given name as the start.
40pub fn new(start: Name) -> Self
41where
42Name: Sized
43 {
44 Chain { start }
45 }
4647/// Creates a reference to CHAIN option data from a reference to the start.
48pub fn new_ref(start: &Name) -> &Self {
49unsafe {
50&*(start as *const Name as *const Self)
51 }
52 }
5354/// 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.
58pub fn start(&self) -> &Name {
59&self.start
60 }
6162/// Converts the value into the start point.
63pub fn into_start(self) -> Name
64where
65Name: Sized
66 {
67self.start
68 }
69}
7071impl<Octs> Chain<Dname<Octs>> {
72/// Parses CHAIN option data from its wire format.
73pub 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}
7980//--- OctetsFrom
8182impl<Name, SrcName> OctetsFrom<Chain<SrcName>> for Chain<Name>
83where Name: OctetsFrom<SrcName> {
84type Error = Name::Error;
8586fn try_octets_from(src: Chain<SrcName>) -> Result<Self, Self::Error> {
87 Name::try_octets_from(src.start).map(Self::new)
88 }
89}
9091//--- PartialEq and Eq
9293impl<Name, OtherName> PartialEq<Chain<OtherName>> for Chain<Name>
94where
95Name: ToDname,
96 OtherName: ToDname
97{
98fn eq(&self, other: &Chain<OtherName>) -> bool {
99self.start().name_eq(other.start())
100 }
101}
102103impl<Name: ToDname> Eq for Chain<Name> { }
104105//--- PartialOrd and Ord
106107impl<Name, OtherName> PartialOrd<Chain<OtherName>> for Chain<Name>
108where
109Name: ToDname,
110 OtherName: ToDname
111{
112fn partial_cmp(&self, other: &Chain<OtherName>) -> Option<Ordering> {
113Some(self.start().name_cmp(other.start()))
114 }
115}
116117impl<Name: ToDname> Ord for Chain<Name> {
118fn cmp(&self, other: &Self) -> Ordering {
119self.start().name_cmp(other.start())
120 }
121}
122123//--- Hash
124125impl<Name: hash::Hash> hash::Hash for Chain<Name> {
126fn hash<H: hash::Hasher>(&self, state: &mut H) {
127self.start().hash(state)
128 }
129}
130131//--- OptData
132133impl<Name> OptData for Chain<Name> {
134fn code(&self) -> OptionCode {
135 OptionCode::Chain
136 }
137}
138139impl<'a, Octs> ParseOptData<'a, Octs> for Chain<Dname<Octs::Range<'a>>>
140where Octs: Octets {
141fn parse_option(
142 code: OptionCode,
143 parser: &mut Parser<'a, Octs>,
144 ) -> Result<Option<Self>, ParseError> {
145if code == OptionCode::Chain {
146Self::parse(parser).map(Some)
147 }
148else {
149Ok(None)
150 }
151 }
152}
153154impl<Name: ToDname> ComposeOptData for Chain<Name> {
155fn compose_len(&self) -> u16 {
156self.start.compose_len()
157 }
158159fn compose_option<Target: OctetsBuilder + ?Sized>(
160&self, target: &mut Target
161 ) -> Result<(), Target::AppendError> {
162self.start.compose(target)
163 }
164}
165166//--- Display
167168impl<Name: fmt::Display> fmt::Display for Chain<Name> {
169fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170write!(f, "{}", self.start)
171 }
172}
173174//--- Extended Opt and OptBuilder
175176impl<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.
181pub fn chain(&self) -> Option<Chain<Dname<Octs::Range<'_>>>> {
182self.first()
183 }
184}
185186impl<'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.
193pub fn chain(
194&mut self, start: impl ToDname
195 ) -> Result<(), Target::AppendError> {
196self.push(&Chain::new(start))
197 }
198}
199200//============ Testing ======================================================
201202#[cfg(test)]
203#[cfg(all(feature = "std", feature = "bytes"))]
204mod test {
205use super::*;
206use super::super::test::test_option_compose_parse;
207use std::vec::Vec;
208use core::str::FromStr;
209210#[test]
211 #[allow(clippy::redundant_closure)] // lifetimes ...
212fn 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