domain/base/opt/
exterr.rs
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;
18use octseq::parse::Parser;
19use octseq::str::Str;
20use core::{fmt, hash, str};
21
22#[derive(Clone)]
31pub struct ExtendedError<Octs> {
32 code: ExtendedErrorCode,
34
35 text: Option<Result<Str<Octs>, Octs>>,
39}
40
41impl<Octs> ExtendedError<Octs> {
42 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 pub unsafe fn new_unchecked(
65 code: ExtendedErrorCode, text: Option<Result<Str<Octs>, Octs>>
66 ) -> Self {
67 Self { code, text }
68 }
69
70 pub fn code(&self) -> ExtendedErrorCode {
72 self.code
73 }
74
75 pub fn text(&self) -> Option<Result<&Str<Octs>, &Octs>> {
81 self.text.as_ref().map(Result::as_ref)
82 }
83
84 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 pub fn set_text(&mut self, text: Str<Octs>) {
96 self.text = Some(Ok(text));
97 }
98
99 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
121impl<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
149impl<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
195impl<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
250impl<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
264impl<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
273impl<Octs: Octets> Opt<Octs> {
276 pub fn extended_error(&self) -> Option<ExtendedError<Octs::Range<'_>>> {
281 self.first()
282 }
283}
284
285impl<'a, Target: Composer> OptBuilder<'a, Target> {
286 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#[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)] 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}