domain/base/opt/
exterr.rs

1//! EDNS option for extended DNS errors.
2//!
3//! The option in this module – [`ExtendedError<Octs>`] – allows a server to
4//! provide more detailed information why a query has failed.
5//!
6//! The option is defined in [RFC 8914](https://tools.ietf.org/html/rfc8914).
7
8use super::super::iana::exterr::{ExtendedErrorCode, EDE_PRIVATE_RANGE_BEGIN};
9use super::super::iana::OptionCode;
10use super::super::message_builder::OptBuilder;
11use super::super::wire::ParseError;
12use super::super::wire::{Compose, Composer};
13use super::{
14    BuildDataError, LongOptData, Opt, OptData, ComposeOptData, ParseOptData
15};
16use octseq::builder::OctetsBuilder;
17use octseq::octets::Octets;
18use octseq::parse::Parser;
19use octseq::str::Str;
20use core::{fmt, hash, str};
21
22//------------ ExtendedError -------------------------------------------------
23
24/// Option data for an extended DNS error.
25///
26/// The Extended DNS Error option allows a server to include more detailed
27/// information in a response to a failed query why it did. It contains a
28/// standardized [`ExtendedErrorCode`] for machines and an optional UTF-8
29/// error text for humans.
30#[derive(Clone)]
31pub struct ExtendedError<Octs> {
32    /// The extended error code.
33    code: ExtendedErrorCode,
34
35    /// Optional human-readable error information.
36    ///
37    /// See `text` for the interpretation of the result.
38    text: Option<Result<Str<Octs>, Octs>>,
39}
40
41impl<Octs> ExtendedError<Octs> {
42    /// Creates a new value from a code and optional text.
43    ///
44    /// Returns an error if `text` is present but is too long to fit into
45    /// an option.
46    pub fn new(
47        code: ExtendedErrorCode, text: Option<Str<Octs>>
48    ) -> Result<Self, LongOptData>
49    where Octs: AsRef<[u8]> {
50        if let Some(ref text) = text {
51            LongOptData::check_len(
52                text.len() + usize::from(ExtendedErrorCode::COMPOSE_LEN)
53            )?
54        }
55        Ok(unsafe { Self::new_unchecked(code, text.map(Ok)) })
56    }
57
58    /// Creates a new value without checking for the option length.
59    ///
60    /// # Safety
61    ///
62    /// The caller must ensure that the length of the wire format of the
63    /// value does not exceed 65,535 octets.
64    pub unsafe fn new_unchecked(
65        code: ExtendedErrorCode, text: Option<Result<Str<Octs>, Octs>>
66    ) -> Self {
67        Self { code, text }
68    }
69
70    /// Returns the error code.
71    pub fn code(&self) -> ExtendedErrorCode {
72        self.code
73    }
74
75    /// Returns the text.
76    ///
77    /// If there is no text, returns `None`. If there is text and it is
78    /// correctly encoded UTF-8, returns `Some(Ok(_))`. If there is text but
79    /// it is not UTF-8, returns `Some(Err(_))`.
80    pub fn text(&self) -> Option<Result<&Str<Octs>, &Octs>> {
81        self.text.as_ref().map(Result::as_ref)
82    }
83
84    /// Returns the text as an octets slice.
85    pub fn text_slice(&self) -> Option<&[u8]>
86    where Octs: AsRef<[u8]> {
87        match self.text {
88            Some(Ok(ref text)) => Some(text.as_slice()),
89            Some(Err(ref text)) => Some(text.as_ref()),
90            None => None
91        }
92    }
93
94    /// Sets the text field.
95    pub fn set_text(&mut self, text: Str<Octs>) {
96        self.text = Some(Ok(text));
97    }
98
99    /// Returns true if the code is in the private range.
100    pub fn is_private(&self) -> bool {
101        self.code().to_int() >= EDE_PRIVATE_RANGE_BEGIN
102    }
103
104    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
105        parser: &mut Parser<'a, Src>
106    ) -> Result<Self, ParseError>
107    where Octs: AsRef<[u8]> {
108        let code = ExtendedErrorCode::parse(parser)?;
109        let text = match parser.remaining() {
110            0 => None,
111            n => {
112                Some(Str::from_utf8(parser.parse_octets(n)?).map_err(|err| {
113                    err.into_octets()
114                }))
115            }
116        };
117        Ok(unsafe { Self::new_unchecked(code, text) })
118    }
119}
120
121//--- From and TryFrom
122
123impl<Octs> From<ExtendedErrorCode> for ExtendedError<Octs> {
124    fn from(code: ExtendedErrorCode) -> Self {
125        Self { code, text: None }
126    }
127}
128
129impl<Octs> From<u16> for ExtendedError<Octs> {
130    fn from(code: u16) -> Self {
131        Self {
132            code: ExtendedErrorCode::from_int(code),
133            text: None,
134        }
135    }
136}
137
138impl<Octs> TryFrom<(ExtendedErrorCode, Str<Octs>)> for ExtendedError<Octs> 
139where Octs: AsRef<[u8]> {
140    type Error = LongOptData;
141
142    fn try_from(
143        (code, text): (ExtendedErrorCode, Str<Octs>)
144    ) -> Result<Self, Self::Error> {
145        Self::new(code, Some(text))
146    }
147}
148
149//--- OptData, ParseOptData, and ComposeOptData
150
151impl<Octs> OptData for ExtendedError<Octs> {
152    fn code(&self) -> OptionCode {
153        OptionCode::ExtendedError
154    }
155}
156
157impl<'a, Octs> ParseOptData<'a, Octs> for ExtendedError<Octs::Range<'a>> 
158where Octs: Octets + ?Sized {
159    fn parse_option(
160        code: OptionCode,
161        parser: &mut Parser<'a, Octs>,
162    ) -> Result<Option<Self>, ParseError> {
163        if code == OptionCode::ExtendedError {
164            Self::parse(parser).map(Some)
165        }
166        else {
167            Ok(None)
168        }
169    }
170}
171
172impl<Octs: AsRef<[u8]>> ComposeOptData for ExtendedError<Octs> {
173    fn compose_len(&self) -> u16 {
174        if let Some(text) = self.text_slice() {
175            text.len().checked_add(
176                ExtendedErrorCode::COMPOSE_LEN.into()
177            ).expect("long option data").try_into().expect("long option data")
178        }
179        else {
180            ExtendedErrorCode::COMPOSE_LEN
181        }
182    }
183
184    fn compose_option<Target: OctetsBuilder + ?Sized>(
185        &self, target: &mut Target
186    ) -> Result<(), Target::AppendError> {
187        self.code.to_int().compose(target)?;
188        if let Some(text) = self.text_slice() {
189            target.append_slice(text)?;
190        }
191        Ok(())
192    }
193}
194
195//--- Display and Debug
196
197impl<Octs: AsRef<[u8]>> fmt::Display for ExtendedError<Octs> {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        self.code.fmt(f)?;
200        match self.text {
201            Some(Ok(ref text)) => write!(f, " {}", text)?,
202            Some(Err(ref text)) => {
203                let mut text = text.as_ref();
204                f.write_str(" ")?;
205                while !text.is_empty() {
206                    let tail = match str::from_utf8(text) {
207                        Ok(text) => {
208                            f.write_str(text)?;
209                            break;
210                        }
211                        Err(err) => {
212                            let (head, tail) = text.split_at(
213                                err.valid_up_to()
214                            );
215                            f.write_str(
216                                unsafe {
217                                    str::from_utf8_unchecked(head)
218                                }
219                            )?;
220                            f.write_str("\u{FFFD}")?;
221
222                            if let Some(err_len) = err.error_len() {
223                                &tail[err_len..]
224                            }
225                            else {
226                                break;
227                            }
228                        }
229                    };
230                    text = tail;
231                }
232            }
233            None => { }
234        }
235        Ok(())
236    }
237}
238
239impl<Octs: AsRef<[u8]>> fmt::Debug for ExtendedError<Octs> {
240    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241        f.debug_struct("ExtendedError")
242            .field("code", &self.code)
243            .field("text", &self.text.as_ref().map(|text| {
244                text.as_ref().map_err(|err| err.as_ref())
245            }))
246            .finish()
247    }
248}
249
250//--- PartialEq and Eq
251
252impl<Octs, Other> PartialEq<ExtendedError<Other>> for ExtendedError<Octs>
253where
254    Octs: AsRef<[u8]>,
255    Other: AsRef<[u8]>,
256{ 
257    fn eq(&self, other: &ExtendedError<Other>) -> bool {
258       self.code.eq(&other.code) && self.text_slice().eq(&other.text_slice())
259    }
260}
261
262impl<Octs: AsRef<[u8]>> Eq for ExtendedError<Octs> { }
263
264//--- Hash
265
266impl<Octs: AsRef<[u8]>> hash::Hash for ExtendedError<Octs> {
267    fn hash<H: hash::Hasher>(&self, state: &mut H) {
268        self.code.hash(state);
269        self.text_slice().hash(state);
270    }
271}
272
273//--- Extended Opt and OptBuilder
274
275impl<Octs: Octets> Opt<Octs> {
276    /// Returns the first extended DNS error option if present.
277    ///
278    /// The extended DNS error option carries additional error information in
279    /// a failed answer.
280    pub fn extended_error(&self) -> Option<ExtendedError<Octs::Range<'_>>> {
281        self.first()
282    }
283}
284
285impl<'a, Target: Composer> OptBuilder<'a, Target> {
286    /// Appends an extended DNS error option.
287    ///
288    /// The extended DNS error option carries additional error information in
289    /// a failed answer. The `code` argument is a standardized error code
290    /// while the optional `text` carries human-readable information.
291    ///
292    /// The method fails if `text` is too long to be part of an option or if
293    /// target runs out of space.
294    pub fn extended_error<Octs: AsRef<[u8]>>(
295        &mut self, code: ExtendedErrorCode, text: Option<&Str<Octs>>
296    ) -> Result<(), BuildDataError> {
297        self.push(
298            &ExtendedError::new(
299                code,
300                text.map(|text| {
301                    unsafe { Str::from_utf8_unchecked(text.as_slice()) }
302                })
303            )?
304        )?;
305        Ok(())
306    }
307}
308
309//============ Tests =========================================================
310
311#[cfg(all(test, feature="std", feature = "bytes"))]
312mod tests {
313    use super::*;
314    use super::super::test::test_option_compose_parse;
315
316    #[test]
317    #[allow(clippy::redundant_closure)] // lifetimes ...
318    fn nsid_compose_parse() {
319        let ede = ExtendedError::new(
320            ExtendedErrorCode::StaleAnswer,
321            Some(Str::from_string("some text".into()))
322        ).unwrap();
323        test_option_compose_parse(
324            &ede,
325            |parser| ExtendedError::parse(parser)
326        );
327    }
328
329    #[test]
330    fn private() {
331        let ede: ExtendedError<&[u8]> = ExtendedErrorCode::DnssecBogus.into();
332        assert!(!ede.is_private());
333
334        let ede: ExtendedError<&[u8]> = EDE_PRIVATE_RANGE_BEGIN.into();
335        assert!(ede.is_private());
336    }
337}