1use super::super::iana::exterr::{
9 ExtendedErrorCode, EDE_PRIVATE_RANGE_BEGIN,
10};
11use super::super::iana::OptionCode;
12use super::super::message_builder::OptBuilder;
13use super::super::wire::ParseError;
14use super::super::wire::{Compose, Composer};
15use super::{
16 BuildDataError, ComposeOptData, LongOptData, Opt, OptData, ParseOptData,
17};
18use core::convert::Infallible;
19use core::{fmt, hash, str};
20use octseq::builder::OctetsBuilder;
21use octseq::octets::{Octets, OctetsFrom};
22use octseq::parse::Parser;
23use octseq::str::Str;
24use octseq::{EmptyBuilder, FromBuilder};
25
26#[derive(Clone)]
35#[cfg_attr(
36 feature = "serde",
37 derive(serde::Serialize),
38 serde(bound(serialize = "Octs: AsRef<[u8]>"))
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,
78 text: Option<Str<Octs>>,
79 ) -> Result<Self, LongOptData>
80 where
81 Octs: AsRef<[u8]>,
82 {
83 if let Some(ref text) = text {
84 LongOptData::check_len(
85 text.len() + usize::from(ExtendedErrorCode::COMPOSE_LEN),
86 )?
87 }
88 Ok(unsafe { Self::new_unchecked(code, text.map(Ok)) })
89 }
90
91 pub fn new_with_str(
92 code: ExtendedErrorCode,
93 text: &str,
94 ) -> Result<Self, LongOptData>
95 where
96 Octs: AsRef<[u8]> + FromBuilder,
97 <Octs as FromBuilder>::Builder: EmptyBuilder,
98 <<Octs as FromBuilder>::Builder as OctetsBuilder>::AppendError:
99 Into<Infallible>,
100 {
101 Self::new(code, Some(Str::copy_from_str(text)))
102 }
103
104 pub unsafe fn new_unchecked(
111 code: ExtendedErrorCode,
112 text: Option<Result<Str<Octs>, Octs>>,
113 ) -> Self {
114 Self {
115 code,
116 text: text.map(|res| res.map_err(LossyOctets)),
117 }
118 }
119
120 pub fn code(&self) -> ExtendedErrorCode {
122 self.code
123 }
124
125 pub fn text(&self) -> Option<Result<&Str<Octs>, &Octs>> {
131 self.text
132 .as_ref()
133 .map(|res| res.as_ref().map_err(|err| err.as_ref()))
134 }
135
136 pub fn text_slice(&self) -> Option<&[u8]>
138 where
139 Octs: AsRef<[u8]>,
140 {
141 match self.text {
142 Some(Ok(ref text)) => Some(text.as_slice()),
143 Some(Err(ref text)) => Some(text.as_slice()),
144 None => None,
145 }
146 }
147
148 pub fn set_text(&mut self, text: Str<Octs>) {
150 self.text = Some(Ok(text));
151 }
152
153 pub fn is_private(&self) -> bool {
155 self.code().to_int() >= EDE_PRIVATE_RANGE_BEGIN
156 }
157
158 pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
159 parser: &mut Parser<'a, Src>,
160 ) -> Result<Self, ParseError>
161 where
162 Octs: AsRef<[u8]>,
163 {
164 let code = ExtendedErrorCode::parse(parser)?;
165 let text = match parser.remaining() {
166 0 => None,
167 n => Some(
168 Str::from_utf8(parser.parse_octets(n)?)
169 .map_err(|err| err.into_octets()),
170 ),
171 };
172 Ok(unsafe { Self::new_unchecked(code, text) })
173 }
174}
175
176impl<Octs> From<ExtendedErrorCode> for ExtendedError<Octs> {
179 fn from(code: ExtendedErrorCode) -> Self {
180 Self { code, text: None }
181 }
182}
183
184impl<Octs> From<u16> for ExtendedError<Octs> {
185 fn from(code: u16) -> Self {
186 Self {
187 code: ExtendedErrorCode::from_int(code),
188 text: None,
189 }
190 }
191}
192
193impl<Octs> TryFrom<(ExtendedErrorCode, Str<Octs>)> for ExtendedError<Octs>
194where
195 Octs: AsRef<[u8]>,
196{
197 type Error = LongOptData;
198
199 fn try_from(
200 (code, text): (ExtendedErrorCode, Str<Octs>),
201 ) -> Result<Self, Self::Error> {
202 Self::new(code, Some(text))
203 }
204}
205
206impl<Octs, SrcOcts> OctetsFrom<ExtendedError<SrcOcts>> for ExtendedError<Octs>
209where
210 Octs: OctetsFrom<SrcOcts>,
211{
212 type Error = Octs::Error;
213
214 fn try_octets_from(
215 source: ExtendedError<SrcOcts>,
216 ) -> Result<Self, Self::Error> {
217 let text = match source.text {
218 Some(Ok(text)) => Some(Ok(Str::try_octets_from(text)?)),
219 Some(Err(octs)) => {
220 Some(Err(LossyOctets(Octs::try_octets_from(octs.0)?)))
221 }
222 None => None,
223 };
224 Ok(Self {
225 code: source.code,
226 text,
227 })
228 }
229}
230impl<Octs> OptData for ExtendedError<Octs> {
233 fn code(&self) -> OptionCode {
234 OptionCode::EXTENDED_ERROR
235 }
236}
237
238impl<'a, Octs> ParseOptData<'a, Octs> for ExtendedError<Octs::Range<'a>>
239where
240 Octs: Octets + ?Sized,
241{
242 fn parse_option(
243 code: OptionCode,
244 parser: &mut Parser<'a, Octs>,
245 ) -> Result<Option<Self>, ParseError> {
246 if code == OptionCode::EXTENDED_ERROR {
247 Self::parse(parser).map(Some)
248 } else {
249 Ok(None)
250 }
251 }
252}
253
254impl<Octs: AsRef<[u8]>> ComposeOptData for ExtendedError<Octs> {
255 fn compose_len(&self) -> u16 {
256 if let Some(text) = self.text_slice() {
257 text.len()
258 .checked_add(ExtendedErrorCode::COMPOSE_LEN.into())
259 .expect("long option data")
260 .try_into()
261 .expect("long option data")
262 } else {
263 ExtendedErrorCode::COMPOSE_LEN
264 }
265 }
266
267 fn compose_option<Target: OctetsBuilder + ?Sized>(
268 &self,
269 target: &mut Target,
270 ) -> Result<(), Target::AppendError> {
271 self.code.to_int().compose(target)?;
272 if let Some(text) = self.text_slice() {
273 target.append_slice(text)?;
274 }
275 Ok(())
276 }
277}
278
279impl<Octs: AsRef<[u8]>> fmt::Display for ExtendedError<Octs> {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 self.code.fmt(f)?;
284 match self.text {
285 Some(Ok(ref text)) => write!(f, " {}", text)?,
286 Some(Err(ref text)) => write!(f, " {}", text)?,
287 None => {}
288 }
289 Ok(())
290 }
291}
292
293impl<Octs: AsRef<[u8]>> fmt::Debug for ExtendedError<Octs> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.debug_struct("ExtendedError")
296 .field("code", &self.code)
297 .field(
298 "text",
299 &self
300 .text
301 .as_ref()
302 .map(|text| text.as_ref().map_err(|err| err.0.as_ref())),
303 )
304 .finish()
305 }
306}
307
308impl<Octs, Other> PartialEq<ExtendedError<Other>> for ExtendedError<Octs>
311where
312 Octs: AsRef<[u8]>,
313 Other: AsRef<[u8]>,
314{
315 fn eq(&self, other: &ExtendedError<Other>) -> bool {
316 self.code.eq(&other.code) && self.text_slice().eq(&other.text_slice())
317 }
318}
319
320impl<Octs: AsRef<[u8]>> Eq for ExtendedError<Octs> {}
321
322impl<Octs: AsRef<[u8]>> hash::Hash for ExtendedError<Octs> {
325 fn hash<H: hash::Hasher>(&self, state: &mut H) {
326 self.code.hash(state);
327 self.text_slice().hash(state);
328 }
329}
330
331impl<Octs: Octets> Opt<Octs> {
334 pub fn extended_error(&self) -> Option<ExtendedError<Octs::Range<'_>>> {
339 self.first()
340 }
341}
342
343impl<Target: Composer> OptBuilder<'_, Target> {
344 pub fn extended_error<Octs: AsRef<[u8]>>(
353 &mut self,
354 code: ExtendedErrorCode,
355 text: Option<&Str<Octs>>,
356 ) -> Result<(), BuildDataError> {
357 self.push(&ExtendedError::new(
358 code,
359 text.map(|text| unsafe {
360 Str::from_utf8_unchecked(text.as_slice())
361 }),
362 )?)?;
363 Ok(())
364 }
365}
366
367#[derive(Clone)]
371struct LossyOctets<Octs>(Octs);
372
373impl<Octs: AsRef<[u8]>> LossyOctets<Octs> {
374 fn as_slice(&self) -> &[u8] {
375 self.0.as_ref()
376 }
377}
378
379impl<Octs> From<Octs> for LossyOctets<Octs> {
380 fn from(src: Octs) -> Self {
381 Self(src)
382 }
383}
384
385impl<Octs> AsRef<Octs> for LossyOctets<Octs> {
386 fn as_ref(&self) -> &Octs {
387 &self.0
388 }
389}
390
391impl<Octs: AsRef<[u8]>> fmt::Display for LossyOctets<Octs> {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 let mut source = self.0.as_ref();
394 loop {
395 match str::from_utf8(source) {
396 Ok(s) => {
397 f.write_str(s)?;
398 break;
399 }
400 Err(err) => {
401 let (good, mut tail) = source.split_at(err.valid_up_to());
402 f.write_str(
403 unsafe { str::from_utf8_unchecked(good) },
406 )?;
407 f.write_str("\u{fffd}")?;
408 match err.error_len() {
409 None => break,
410 Some(len) => {
411 tail = &tail[len..];
412 }
413 }
414 source = tail;
415 }
416 }
417 }
418 Ok(())
419 }
420}
421
422#[cfg(all(test, feature = "std", feature = "bytes"))]
425mod tests {
426 use super::super::test::test_option_compose_parse;
427 use super::*;
428
429 #[test]
430 #[allow(clippy::redundant_closure)] fn nsid_compose_parse() {
432 let ede = ExtendedError::new(
433 ExtendedErrorCode::STALE_ANSWER,
434 Some(Str::from_string("some text".into())),
435 )
436 .unwrap();
437 test_option_compose_parse(&ede, |parser| {
438 ExtendedError::parse(parser)
439 });
440 }
441
442 #[test]
443 fn private() {
444 let ede: ExtendedError<&[u8]> =
445 ExtendedErrorCode::DNSSEC_BOGUS.into();
446 assert!(!ede.is_private());
447
448 let ede: ExtendedError<&[u8]> = EDE_PRIVATE_RANGE_BEGIN.into();
449 assert!(ede.is_private());
450 }
451
452 #[test]
453 fn display_lossy_octets() {
454 use std::string::ToString;
455
456 assert_eq!(LossyOctets(b"foo").to_string(), "foo");
457 assert_eq!(LossyOctets(b"foo\xe7").to_string(), "foo\u{fffd}");
458 assert_eq!(LossyOctets(b"foo\xe7foo").to_string(), "foo\u{fffd}foo");
459 }
460}