1use 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, OctetsFrom};
18use octseq::parse::Parser;
19use octseq::str::Str;
20use octseq::{EmptyBuilder, FromBuilder};
21use core::{fmt, hash, str};
22use core::convert::Infallible;
23
24#[derive(Clone)]
33#[cfg_attr(
34 feature = "serde",
35 derive(serde::Serialize),
36 serde(bound(
37 serialize = "Octs: AsRef<[u8]>"
38 ))
39)]
40pub struct ExtendedError<Octs> {
41 code: ExtendedErrorCode,
43
44 #[cfg_attr(feature = "serde", serde(serialize_with = "lossy_text"))]
48 text: Option<Result<Str<Octs>, LossyOctets<Octs>>>,
49}
50
51#[cfg(feature = "serde")]
52fn lossy_text<S, Octs: AsRef<[u8]>>(
53 text: &Option<Result<Str<Octs>, LossyOctets<Octs>>>,
54 serializer: S,
55) -> Result<S::Ok, S::Error>
56where
57 S: serde::Serializer,
58{
59 match text {
60 Some(Ok(text)) => serializer.serialize_str(text),
61 Some(Err(text)) => serializer.collect_str(text),
62 None => serializer.serialize_none(),
63 }
64}
65
66impl ExtendedError<()> {
67 pub(super) const CODE: OptionCode = OptionCode::EXTENDED_ERROR;
69}
70
71impl<Octs> ExtendedError<Octs> {
72 pub fn new(
77 code: ExtendedErrorCode, text: Option<Str<Octs>>
78 ) -> Result<Self, LongOptData>
79 where Octs: AsRef<[u8]> {
80 if let Some(ref text) = text {
81 LongOptData::check_len(
82 text.len() + usize::from(ExtendedErrorCode::COMPOSE_LEN)
83 )?
84 }
85 Ok(unsafe { Self::new_unchecked(code, text.map(Ok)) })
86 }
87
88 pub fn new_with_str(code: ExtendedErrorCode, text: &str) ->
89 Result<Self, LongOptData>
90 where Octs: AsRef<[u8]> + FromBuilder,
91 <Octs as FromBuilder>::Builder: EmptyBuilder,
92 <<Octs as FromBuilder>::Builder as OctetsBuilder>::AppendError: Into<Infallible>,
93 {
94 Self::new(code, Some(Str::copy_from_str(text)))
95 }
96
97 pub unsafe fn new_unchecked(
104 code: ExtendedErrorCode, text: Option<Result<Str<Octs>, Octs>>
105 ) -> Self {
106 Self {
107 code,
108 text: text.map(|res| res.map_err(LossyOctets))
109 }
110 }
111
112 pub fn code(&self) -> ExtendedErrorCode {
114 self.code
115 }
116
117 pub fn text(&self) -> Option<Result<&Str<Octs>, &Octs>> {
123 self.text.as_ref().map(|res| res.as_ref().map_err(|err| err.as_ref()))
124 }
125
126 pub fn text_slice(&self) -> Option<&[u8]>
128 where Octs: AsRef<[u8]> {
129 match self.text {
130 Some(Ok(ref text)) => Some(text.as_slice()),
131 Some(Err(ref text)) => Some(text.as_slice()),
132 None => None
133 }
134 }
135
136 pub fn set_text(&mut self, text: Str<Octs>) {
138 self.text = Some(Ok(text));
139 }
140
141 pub fn is_private(&self) -> bool {
143 self.code().to_int() >= EDE_PRIVATE_RANGE_BEGIN
144 }
145
146 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
147 parser: &mut Parser<'a, Src>
148 ) -> Result<Self, ParseError>
149 where Octs: AsRef<[u8]> {
150 let code = ExtendedErrorCode::parse(parser)?;
151 let text = match parser.remaining() {
152 0 => None,
153 n => {
154 Some(Str::from_utf8(parser.parse_octets(n)?).map_err(|err| {
155 err.into_octets()
156 }))
157 }
158 };
159 Ok(unsafe { Self::new_unchecked(code, text) })
160 }
161}
162
163impl<Octs> From<ExtendedErrorCode> for ExtendedError<Octs> {
166 fn from(code: ExtendedErrorCode) -> Self {
167 Self { code, text: None }
168 }
169}
170
171impl<Octs> From<u16> for ExtendedError<Octs> {
172 fn from(code: u16) -> Self {
173 Self {
174 code: ExtendedErrorCode::from_int(code),
175 text: None,
176 }
177 }
178}
179
180impl<Octs> TryFrom<(ExtendedErrorCode, Str<Octs>)> for ExtendedError<Octs>
181where Octs: AsRef<[u8]> {
182 type Error = LongOptData;
183
184 fn try_from(
185 (code, text): (ExtendedErrorCode, Str<Octs>)
186 ) -> Result<Self, Self::Error> {
187 Self::new(code, Some(text))
188 }
189}
190
191impl<Octs, SrcOcts> OctetsFrom<ExtendedError<SrcOcts>> for ExtendedError<Octs>
194where
195 Octs: OctetsFrom<SrcOcts>
196{
197 type Error = Octs::Error;
198
199 fn try_octets_from(
200 source: ExtendedError<SrcOcts>
201 ) -> Result<Self, Self::Error> {
202 let text = match source.text {
203 Some(Ok(text)) => Some(Ok(Str::try_octets_from(text)?)),
204 Some(Err(octs)) => {
205 Some(Err(LossyOctets(Octs::try_octets_from(octs.0)?)))
206 }
207 None => None,
208 };
209 Ok(Self { code: source.code, text })
210 }
211}
212impl<Octs> OptData for ExtendedError<Octs> {
215 fn code(&self) -> OptionCode {
216 OptionCode::EXTENDED_ERROR
217 }
218}
219
220impl<'a, Octs> ParseOptData<'a, Octs> for ExtendedError<Octs::Range<'a>>
221where Octs: Octets + ?Sized {
222 fn parse_option(
223 code: OptionCode,
224 parser: &mut Parser<'a, Octs>,
225 ) -> Result<Option<Self>, ParseError> {
226 if code == OptionCode::EXTENDED_ERROR {
227 Self::parse(parser).map(Some)
228 }
229 else {
230 Ok(None)
231 }
232 }
233}
234
235impl<Octs: AsRef<[u8]>> ComposeOptData for ExtendedError<Octs> {
236 fn compose_len(&self) -> u16 {
237 if let Some(text) = self.text_slice() {
238 text.len().checked_add(
239 ExtendedErrorCode::COMPOSE_LEN.into()
240 ).expect("long option data").try_into().expect("long option data")
241 }
242 else {
243 ExtendedErrorCode::COMPOSE_LEN
244 }
245 }
246
247 fn compose_option<Target: OctetsBuilder + ?Sized>(
248 &self, target: &mut Target
249 ) -> Result<(), Target::AppendError> {
250 self.code.to_int().compose(target)?;
251 if let Some(text) = self.text_slice() {
252 target.append_slice(text)?;
253 }
254 Ok(())
255 }
256}
257
258impl<Octs: AsRef<[u8]>> fmt::Display for ExtendedError<Octs> {
261 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262 self.code.fmt(f)?;
263 match self.text {
264 Some(Ok(ref text)) => write!(f, " {}", text)?,
265 Some(Err(ref text)) => write!(f, " {}", text)?,
266 None => { }
267 }
268 Ok(())
269 }
270}
271
272impl<Octs: AsRef<[u8]>> fmt::Debug for ExtendedError<Octs> {
273 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
274 f.debug_struct("ExtendedError")
275 .field("code", &self.code)
276 .field("text", &self.text.as_ref().map(|text| {
277 text.as_ref().map_err(|err| err.0.as_ref())
278 }))
279 .finish()
280 }
281}
282
283impl<Octs, Other> PartialEq<ExtendedError<Other>> for ExtendedError<Octs>
286where
287 Octs: AsRef<[u8]>,
288 Other: AsRef<[u8]>,
289{
290 fn eq(&self, other: &ExtendedError<Other>) -> bool {
291 self.code.eq(&other.code) && self.text_slice().eq(&other.text_slice())
292 }
293}
294
295impl<Octs: AsRef<[u8]>> Eq for ExtendedError<Octs> { }
296
297impl<Octs: AsRef<[u8]>> hash::Hash for ExtendedError<Octs> {
300 fn hash<H: hash::Hasher>(&self, state: &mut H) {
301 self.code.hash(state);
302 self.text_slice().hash(state);
303 }
304}
305
306impl<Octs: Octets> Opt<Octs> {
309 pub fn extended_error(&self) -> Option<ExtendedError<Octs::Range<'_>>> {
314 self.first()
315 }
316}
317
318impl<Target: Composer> OptBuilder<'_, Target> {
319 pub fn extended_error<Octs: AsRef<[u8]>>(
328 &mut self, code: ExtendedErrorCode, text: Option<&Str<Octs>>
329 ) -> Result<(), BuildDataError> {
330 self.push(
331 &ExtendedError::new(
332 code,
333 text.map(|text| {
334 unsafe { Str::from_utf8_unchecked(text.as_slice()) }
335 })
336 )?
337 )?;
338 Ok(())
339 }
340}
341
342
343#[derive(Clone)]
347struct LossyOctets<Octs>(Octs);
348
349impl<Octs: AsRef<[u8]>> LossyOctets<Octs> {
350 fn as_slice(&self) -> &[u8] {
351 self.0.as_ref()
352 }
353}
354
355impl<Octs> From<Octs> for LossyOctets<Octs> {
356 fn from(src: Octs) -> Self {
357 Self(src)
358 }
359}
360
361impl<Octs> AsRef<Octs> for LossyOctets<Octs> {
362 fn as_ref(&self) -> &Octs {
363 &self.0
364 }
365}
366
367impl<Octs: AsRef<[u8]>> fmt::Display for LossyOctets<Octs> {
368 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
369 let mut source = self.0.as_ref();
370 loop {
371 match str::from_utf8(source) {
372 Ok(s) => {
373 f.write_str(s)?;
374 break;
375 }
376 Err(err) => {
377 let (good, mut tail) = source.split_at(err.valid_up_to());
378 f.write_str(
379 unsafe {
382 str::from_utf8_unchecked(good)
383 }
384 )?;
385 f.write_str("\u{fffd}")?;
386 match err.error_len() {
387 None => break,
388 Some(len) => {
389 tail = &tail[len..];
390 }
391 }
392 source = tail;
393 }
394 }
395 }
396 Ok(())
397 }
398}
399
400
401#[cfg(all(test, feature="std", feature = "bytes"))]
404mod tests {
405 use super::*;
406 use super::super::test::test_option_compose_parse;
407
408 #[test]
409 #[allow(clippy::redundant_closure)] fn nsid_compose_parse() {
411 let ede = ExtendedError::new(
412 ExtendedErrorCode::STALE_ANSWER,
413 Some(Str::from_string("some text".into()))
414 ).unwrap();
415 test_option_compose_parse(
416 &ede,
417 |parser| ExtendedError::parse(parser)
418 );
419 }
420
421 #[test]
422 fn private() {
423 let ede: ExtendedError<&[u8]> = ExtendedErrorCode::DNSSEC_BOGUS.into();
424 assert!(!ede.is_private());
425
426 let ede: ExtendedError<&[u8]> = EDE_PRIVATE_RANGE_BEGIN.into();
427 assert!(ede.is_private());
428 }
429
430 #[test]
431 fn display_lossy_octets() {
432 use std::string::ToString;
433
434 assert_eq!(
435 LossyOctets(b"foo").to_string(),
436 "foo"
437 );
438 assert_eq!(
439 LossyOctets(b"foo\xe7").to_string(),
440 "foo\u{fffd}"
441 );
442 assert_eq!(
443 LossyOctets(b"foo\xe7foo").to_string(),
444 "foo\u{fffd}foo"
445 );
446 }
447}