use super::super::iana::exterr::{
ExtendedErrorCode, EDE_PRIVATE_RANGE_BEGIN,
};
use super::super::iana::OptionCode;
use super::super::message_builder::OptBuilder;
use super::super::wire::ParseError;
use super::super::wire::{Compose, Composer};
use super::{
BuildDataError, ComposeOptData, LongOptData, Opt, OptData, ParseOptData,
};
use core::convert::Infallible;
use core::{fmt, hash, str};
use octseq::builder::OctetsBuilder;
use octseq::octets::{Octets, OctetsFrom};
use octseq::parse::Parser;
use octseq::str::Str;
use octseq::{EmptyBuilder, FromBuilder};
#[derive(Clone)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize),
serde(bound(serialize = "Octs: AsRef<[u8]>"))
)]
pub struct ExtendedError<Octs> {
code: ExtendedErrorCode,
#[cfg_attr(feature = "serde", serde(serialize_with = "lossy_text"))]
text: Option<Result<Str<Octs>, LossyOctets<Octs>>>,
}
#[cfg(feature = "serde")]
fn lossy_text<S, Octs: AsRef<[u8]>>(
text: &Option<Result<Str<Octs>, LossyOctets<Octs>>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match text {
Some(Ok(text)) => serializer.serialize_str(text),
Some(Err(text)) => serializer.collect_str(text),
None => serializer.serialize_none(),
}
}
impl ExtendedError<()> {
pub(super) const CODE: OptionCode = OptionCode::EXTENDED_ERROR;
}
impl<Octs> ExtendedError<Octs> {
pub fn new(
code: ExtendedErrorCode,
text: Option<Str<Octs>>,
) -> Result<Self, LongOptData>
where
Octs: AsRef<[u8]>,
{
if let Some(ref text) = text {
LongOptData::check_len(
text.len() + usize::from(ExtendedErrorCode::COMPOSE_LEN),
)?
}
Ok(unsafe { Self::new_unchecked(code, text.map(Ok)) })
}
pub fn new_with_str(
code: ExtendedErrorCode,
text: &str,
) -> Result<Self, LongOptData>
where
Octs: AsRef<[u8]> + FromBuilder,
<Octs as FromBuilder>::Builder: EmptyBuilder,
<<Octs as FromBuilder>::Builder as OctetsBuilder>::AppendError:
Into<Infallible>,
{
Self::new(code, Some(Str::copy_from_str(text)))
}
pub unsafe fn new_unchecked(
code: ExtendedErrorCode,
text: Option<Result<Str<Octs>, Octs>>,
) -> Self {
Self {
code,
text: text.map(|res| res.map_err(LossyOctets)),
}
}
pub fn code(&self) -> ExtendedErrorCode {
self.code
}
pub fn text(&self) -> Option<Result<&Str<Octs>, &Octs>> {
self.text
.as_ref()
.map(|res| res.as_ref().map_err(|err| err.as_ref()))
}
pub fn text_slice(&self) -> Option<&[u8]>
where
Octs: AsRef<[u8]>,
{
match self.text {
Some(Ok(ref text)) => Some(text.as_slice()),
Some(Err(ref text)) => Some(text.as_slice()),
None => None,
}
}
pub fn set_text(&mut self, text: Str<Octs>) {
self.text = Some(Ok(text));
}
pub fn is_private(&self) -> bool {
self.code().to_int() >= EDE_PRIVATE_RANGE_BEGIN
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError>
where
Octs: AsRef<[u8]>,
{
let code = ExtendedErrorCode::parse(parser)?;
let text = match parser.remaining() {
0 => None,
n => Some(
Str::from_utf8(parser.parse_octets(n)?)
.map_err(|err| err.into_octets()),
),
};
Ok(unsafe { Self::new_unchecked(code, text) })
}
}
impl<Octs> From<ExtendedErrorCode> for ExtendedError<Octs> {
fn from(code: ExtendedErrorCode) -> Self {
Self { code, text: None }
}
}
impl<Octs> From<u16> for ExtendedError<Octs> {
fn from(code: u16) -> Self {
Self {
code: ExtendedErrorCode::from_int(code),
text: None,
}
}
}
impl<Octs> TryFrom<(ExtendedErrorCode, Str<Octs>)> for ExtendedError<Octs>
where
Octs: AsRef<[u8]>,
{
type Error = LongOptData;
fn try_from(
(code, text): (ExtendedErrorCode, Str<Octs>),
) -> Result<Self, Self::Error> {
Self::new(code, Some(text))
}
}
impl<Octs, SrcOcts> OctetsFrom<ExtendedError<SrcOcts>> for ExtendedError<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(
source: ExtendedError<SrcOcts>,
) -> Result<Self, Self::Error> {
let text = match source.text {
Some(Ok(text)) => Some(Ok(Str::try_octets_from(text)?)),
Some(Err(octs)) => {
Some(Err(LossyOctets(Octs::try_octets_from(octs.0)?)))
}
None => None,
};
Ok(Self {
code: source.code,
text,
})
}
}
impl<Octs> OptData for ExtendedError<Octs> {
fn code(&self) -> OptionCode {
OptionCode::EXTENDED_ERROR
}
}
impl<'a, Octs> ParseOptData<'a, Octs> for ExtendedError<Octs::Range<'a>>
where
Octs: Octets + ?Sized,
{
fn parse_option(
code: OptionCode,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError> {
if code == OptionCode::EXTENDED_ERROR {
Self::parse(parser).map(Some)
} else {
Ok(None)
}
}
}
impl<Octs: AsRef<[u8]>> ComposeOptData for ExtendedError<Octs> {
fn compose_len(&self) -> u16 {
if let Some(text) = self.text_slice() {
text.len()
.checked_add(ExtendedErrorCode::COMPOSE_LEN.into())
.expect("long option data")
.try_into()
.expect("long option data")
} else {
ExtendedErrorCode::COMPOSE_LEN
}
}
fn compose_option<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.code.to_int().compose(target)?;
if let Some(text) = self.text_slice() {
target.append_slice(text)?;
}
Ok(())
}
}
impl<Octs: AsRef<[u8]>> fmt::Display for ExtendedError<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.code.fmt(f)?;
match self.text {
Some(Ok(ref text)) => write!(f, " {}", text)?,
Some(Err(ref text)) => write!(f, " {}", text)?,
None => {}
}
Ok(())
}
}
impl<Octs: AsRef<[u8]>> fmt::Debug for ExtendedError<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExtendedError")
.field("code", &self.code)
.field(
"text",
&self
.text
.as_ref()
.map(|text| text.as_ref().map_err(|err| err.0.as_ref())),
)
.finish()
}
}
impl<Octs, Other> PartialEq<ExtendedError<Other>> for ExtendedError<Octs>
where
Octs: AsRef<[u8]>,
Other: AsRef<[u8]>,
{
fn eq(&self, other: &ExtendedError<Other>) -> bool {
self.code.eq(&other.code) && self.text_slice().eq(&other.text_slice())
}
}
impl<Octs: AsRef<[u8]>> Eq for ExtendedError<Octs> {}
impl<Octs: AsRef<[u8]>> hash::Hash for ExtendedError<Octs> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.code.hash(state);
self.text_slice().hash(state);
}
}
impl<Octs: Octets> Opt<Octs> {
pub fn extended_error(&self) -> Option<ExtendedError<Octs::Range<'_>>> {
self.first()
}
}
impl<Target: Composer> OptBuilder<'_, Target> {
pub fn extended_error<Octs: AsRef<[u8]>>(
&mut self,
code: ExtendedErrorCode,
text: Option<&Str<Octs>>,
) -> Result<(), BuildDataError> {
self.push(&ExtendedError::new(
code,
text.map(|text| unsafe {
Str::from_utf8_unchecked(text.as_slice())
}),
)?)?;
Ok(())
}
}
#[derive(Clone)]
struct LossyOctets<Octs>(Octs);
impl<Octs: AsRef<[u8]>> LossyOctets<Octs> {
fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<Octs> From<Octs> for LossyOctets<Octs> {
fn from(src: Octs) -> Self {
Self(src)
}
}
impl<Octs> AsRef<Octs> for LossyOctets<Octs> {
fn as_ref(&self) -> &Octs {
&self.0
}
}
impl<Octs: AsRef<[u8]>> fmt::Display for LossyOctets<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut source = self.0.as_ref();
loop {
match str::from_utf8(source) {
Ok(s) => {
f.write_str(s)?;
break;
}
Err(err) => {
let (good, mut tail) = source.split_at(err.valid_up_to());
f.write_str(
unsafe { str::from_utf8_unchecked(good) },
)?;
f.write_str("\u{fffd}")?;
match err.error_len() {
None => break,
Some(len) => {
tail = &tail[len..];
}
}
source = tail;
}
}
}
Ok(())
}
}
#[cfg(all(test, feature = "std", feature = "bytes"))]
mod tests {
use super::super::test::test_option_compose_parse;
use super::*;
#[test]
#[allow(clippy::redundant_closure)] fn nsid_compose_parse() {
let ede = ExtendedError::new(
ExtendedErrorCode::STALE_ANSWER,
Some(Str::from_string("some text".into())),
)
.unwrap();
test_option_compose_parse(&ede, |parser| {
ExtendedError::parse(parser)
});
}
#[test]
fn private() {
let ede: ExtendedError<&[u8]> =
ExtendedErrorCode::DNSSEC_BOGUS.into();
assert!(!ede.is_private());
let ede: ExtendedError<&[u8]> = EDE_PRIVATE_RANGE_BEGIN.into();
assert!(ede.is_private());
}
#[test]
fn display_lossy_octets() {
use std::string::ToString;
assert_eq!(LossyOctets(b"foo").to_string(), "foo");
assert_eq!(LossyOctets(b"foo\xe7").to_string(), "foo\u{fffd}");
assert_eq!(LossyOctets(b"foo\xe7foo").to_string(), "foo\u{fffd}foo");
}
}