1 //! Error type from parsing a document, and the position where it occurred
2 use thiserror::Error;
3
4 use crate::types::policy::PolicyError;
5 use std::fmt;
6
7 /// A position within a directory object. Used to tell where an error
8 /// occurred.
9 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
10 #[non_exhaustive]
11 pub enum Pos {
12 /// The error did not occur at any particular position.
13 ///
14 /// This can happen when the error is something like a missing entry:
15 /// the entry is supposed to go _somewhere_, but we can't say where.
16 None,
17 /// The error occurred at an unknown position.
18 ///
19 /// We should avoid using this case.
20 Unknown,
21 /// The error occurred at an invalid offset within the string, or
22 /// outside the string entirely.
23 ///
24 /// This can only occur because of an internal error of some kind.
25 Invalid(usize),
26 /// The error occurred at a particular byte within the string.
27 ///
28 /// We try to convert these to a Pos before displaying them to the user.
29 Byte {
30 /// Byte offset within a string.
31 off: usize,
32 },
33 /// The error occurred at a particular line (and possibly at a
34 /// particular byte within the line.)
35 PosInLine {
36 /// Line offset within a string.
37 line: usize,
38 /// Byte offset within the line.
39 byte: usize,
40 },
41 /// The error occurred at a position in memory. This shouldn't be
42 /// exposed to the user, but rather should be mapped to a position
43 /// in the string.
44 Raw {
45 /// A raw pointer to the position where the error occurred.
46 ptr: *const u8,
47 },
48 }
49
50 // It's okay to send a Pos to another thread, even though its Raw
51 // variant contains a pointer. That's because we never dereference the
52 // pointer: we only compare it to another pointer representing a
53 // string.
54 //
55 // TODO: Find a better way to have Pos work.
56 unsafe impl Send for Pos {}
57 unsafe impl Sync for Pos {}
58
59 impl Pos {
60 /// Construct a Pos from an offset within a &str slice.
from_offset(s: &str, off: usize) -> Self61 pub fn from_offset(s: &str, off: usize) -> Self {
62 if off > s.len() || !s.is_char_boundary(off) {
63 Pos::Invalid(off)
64 } else {
65 let s = &s[..off];
66 let last_nl = s.rfind('\n');
67 match last_nl {
68 Some(pos) => {
69 let newlines = s.bytes().filter(|b| *b == b'\n').count();
70 Pos::PosInLine {
71 line: newlines + 1,
72 byte: off - pos,
73 }
74 }
75 None => Pos::PosInLine {
76 line: 1,
77 byte: off + 1,
78 },
79 }
80 }
81 }
82 /// Construct a Pos from a slice of some other string. This
83 /// Pos won't be terribly helpful, but it may be converted
84 /// into a useful Pos with `within`.
at(s: &str) -> Self85 pub fn at(s: &str) -> Self {
86 let ptr = s.as_ptr();
87 Pos::Raw { ptr }
88 }
89 /// Construct Pos from the end of some other string.
at_end_of(s: &str) -> Self90 pub fn at_end_of(s: &str) -> Self {
91 let ending = &s[s.len()..];
92 Pos::at(ending)
93 }
94 /// Construct a position from a byte offset.
from_byte(off: usize) -> Self95 pub fn from_byte(off: usize) -> Self {
96 Pos::Byte { off }
97 }
98 /// Construct a position from a line and a byte offset within that line.
from_line(line: usize, byte: usize) -> Self99 pub fn from_line(line: usize, byte: usize) -> Self {
100 Pos::PosInLine { line, byte }
101 }
102 /// If this position appears within `s`, and has not yet been mapped to
103 /// a line-and-byte position, return its offset.
offset_within(&self, s: &str) -> Option<usize>104 pub(crate) fn offset_within(&self, s: &str) -> Option<usize> {
105 match self {
106 Pos::Byte { off } => Some(*off),
107 Pos::Raw { ptr } => offset_in(*ptr, s),
108 _ => None,
109 }
110 }
111 /// Given a position, if it was at a byte offset, convert it to a
112 /// line-and-byte position within `s`.
113 ///
114 /// Requires that this position was actually generated from `s`.
115 /// If it was not, the results here may be nonsensical.
116 ///
117 /// TODO: I wish I knew an efficient safe way to do this that
118 /// guaranteed that we we always talking about the right string.
within(self, s: &str) -> Self119 pub fn within(self, s: &str) -> Self {
120 match self {
121 Pos::Byte { off } => Self::from_offset(s, off),
122 Pos::Raw { ptr } => {
123 if let Some(off) = offset_in(ptr, s) {
124 Self::from_offset(s, off)
125 } else {
126 self
127 }
128 }
129 _ => self,
130 }
131 }
132 }
133
134 /// If `ptr` is within `s`, return its byte offset.
offset_in(ptr: *const u8, s: &str) -> Option<usize>135 fn offset_in(ptr: *const u8, s: &str) -> Option<usize> {
136 // We need to confirm that 'ptr' falls within 's' in order
137 // to subtract it meaningfully and find its offset.
138 // Otherwise, we'll get a bogus result.
139 //
140 // Fortunately, we _only_ get a bogus result: we don't
141 // hit unsafe behavior.
142 let ptr_u = ptr as usize;
143 let start_u = s.as_ptr() as usize;
144 let end_u = (s.as_ptr() as usize) + s.len();
145 if start_u <= ptr_u && ptr_u < end_u {
146 Some(ptr_u - start_u)
147 } else {
148 None
149 }
150 }
151
152 impl fmt::Display for Pos {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 use Pos::*;
155 match self {
156 None => write!(f, ""),
157 Unknown => write!(f, " at unknown position"),
158 Invalid(off) => write!(f, " at invalid offset at index {}", off),
159 Byte { off } => write!(f, " at byte {}", off),
160 PosInLine { line, byte } => write!(f, " on line {}, byte {}", line, byte),
161 Raw { ptr } => write!(f, " at {:?}", ptr),
162 }
163 }
164 }
165
166 /// An error that occurred while parsing a directory object of some kind.
167 #[derive(Error, Debug, Clone, PartialEq)]
168 #[non_exhaustive]
169 pub enum Error {
170 /// An internal error in the parser: these should never happen.
171 #[error("internal error{0}")]
172 Internal(Pos), // TODO string.
173 /// An entry was found with no keyword.
174 #[error("no keyword for entry{0}")]
175 MissingKeyword(Pos),
176 /// An entry was found with no newline at the end.
177 #[error("line truncated before newline{0}")]
178 TruncatedLine(Pos),
179 /// A bad string was found in the keyword position.
180 #[error("invalid keyword{0}")]
181 BadKeyword(Pos),
182 /// We found an ill-formed "BEGIN FOO" tag.
183 #[error("invalid PEM BEGIN tag{0}")]
184 BadObjectBeginTag(Pos),
185 /// We found an ill-formed "END FOO" tag.
186 #[error("invalid PEM END tag{0}")]
187 BadObjectEndTag(Pos),
188 /// We found a "BEGIN FOO" tag with an "END FOO" tag that didn't match.
189 #[error("mismatched PEM tags{0}")]
190 BadObjectMismatchedTag(Pos),
191 /// We found a base64 object with an invalid base64 encoding.
192 #[error("invalid base64 in object around byte {0}")]
193 BadObjectBase64(Pos),
194 /// The document is not supposed to contain more than one of some
195 /// kind of entry, but we found one anyway.
196 #[error("duplicate entry for {0}{1}")]
197 DuplicateToken(&'static str, Pos),
198 /// The document is not supposed to contain any of some particular kind
199 /// of entry, but we found one anyway.
200 #[error("entry {0} unexpected{1}")]
201 UnexpectedToken(&'static str, Pos),
202 /// The document is supposed to contain any of some particular kind
203 /// of entry, but we didn't find one one anyway.
204 #[error("didn't find required entry {0}")]
205 MissingToken(&'static str),
206 /// The document was supposed to have one of these, but not where we
207 /// found it.
208 #[error("found {0} out of place{1}")]
209 MisplacedToken(&'static str, Pos),
210 /// We found more arguments on an entry than it is allowed to have.
211 #[error("too many arguments for {0}{1}")]
212 TooManyArguments(&'static str, Pos),
213 /// We didn't fine enough arguments for some entry.
214 #[error("too few arguments for {0}{1}")]
215 TooFewArguments(&'static str, Pos),
216 /// We found an object attached to an entry that isn't supposed to
217 /// have one.
218 #[error("unexpected object for {0}{1}")]
219 UnexpectedObject(&'static str, Pos),
220 /// An entry was supposed to have an object, but it didn't.
221 #[error("missing object for {0}{1}")]
222 MissingObject(&'static str, Pos),
223 /// We found an object on an entry, but the type was wrong.
224 #[error("wrong object type for entry{0}")]
225 WrongObject(Pos),
226 /// We tried to find an argument that we were sure would be there,
227 /// but it wasn't!
228 ///
229 /// This error should never occur in correct code; it should be
230 /// caught earlier by TooFewArguments.
231 #[error("missing argument for entry{0}")]
232 MissingArgument(Pos),
233 /// We found an argument that couldn't be parsed.
234 #[error("bad argument for entry{0}: {1}")]
235 BadArgument(Pos, String), // converting to a string doesn't sit well with me. XXXX
236 /// We found an object that couldn't be parsed after it was decoded.
237 #[error("bad object for entry{0}: {1}")]
238 BadObjectVal(Pos, String), // converting to a string doesn't sit well with me. XXXX
239 /// There was some signature that we couldn't validate.
240 #[error("couldn't validate signature{0}")]
241 BadSignature(Pos), // say which kind of signature. TODO
242 /// There was a tor version we couldn't parse.
243 #[error("couldn't parse Tor version{0}")]
244 BadTorVersion(Pos),
245 /// There was an ipv4 or ipv6 policy entry that we couldn't parse.
246 #[error("invalid policy entry{0}: {1}")]
247 BadPolicy(Pos, #[source] PolicyError),
248 /// An object was expired or not yet valid.
249 #[error("untimely object{0}: {1}")]
250 Untimely(Pos, #[source] tor_checkable::TimeValidityError),
251 /// An underlying byte sequence couldn't be decoded.
252 #[error("decoding error{0}: {1}")]
253 Undecodable(Pos, #[source] tor_bytes::Error),
254 /// Versioned document with an unrecognized version.
255 #[error("unrecognized document version {0}")]
256 BadDocumentVersion(u32),
257 /// Unexpected document type
258 #[error("unexpected document type")]
259 BadDocumentType,
260 /// Document or section started with wrong token
261 #[error("Wrong starting token {0}{1}")]
262 WrongStartingToken(String, Pos),
263 /// Document or section ended with wrong token
264 #[error("Wrong ending token {0}{1}")]
265 WrongEndingToken(String, Pos),
266 /// Items not sorted as expected
267 #[error("Incorrect sort order{0}")]
268 WrongSortOrder(Pos),
269 /// A consensus lifetime was ill-formed.
270 #[error("Invalid consensus lifetime")]
271 InvalidLifetime,
272 /// We're unable to finish building an object, for some reason.
273 #[error("Unable to construct object: {0}")]
274 CannotBuild(&'static str),
275 }
276
277 impl Error {
278 /// Helper: return a mutable reference to this error's position (if any)
pos_mut(&mut self) -> Option<&mut Pos>279 fn pos_mut(&mut self) -> Option<&mut Pos> {
280 use Error::*;
281 match self {
282 Internal(p) => Some(p),
283 MissingKeyword(p) => Some(p),
284 TruncatedLine(p) => Some(p),
285 BadKeyword(p) => Some(p),
286 BadObjectBeginTag(p) => Some(p),
287 BadObjectEndTag(p) => Some(p),
288 BadObjectMismatchedTag(p) => Some(p),
289 BadObjectBase64(p) => Some(p),
290 DuplicateToken(_, p) => Some(p),
291 UnexpectedToken(_, p) => Some(p),
292 MissingToken(_) => None,
293 MisplacedToken(_, p) => Some(p),
294 TooManyArguments(_, p) => Some(p),
295 TooFewArguments(_, p) => Some(p),
296 UnexpectedObject(_, p) => Some(p),
297 MissingObject(_, p) => Some(p),
298 WrongObject(p) => Some(p),
299 MissingArgument(p) => Some(p),
300 BadArgument(p, _) => Some(p),
301 BadObjectVal(p, _) => Some(p),
302 BadSignature(p) => Some(p),
303 BadTorVersion(p) => Some(p),
304 BadPolicy(p, _) => Some(p),
305 Untimely(p, _) => Some(p),
306 Undecodable(p, _) => Some(p),
307 BadDocumentVersion(_) => None,
308 BadDocumentType => None,
309 WrongStartingToken(_, p) => Some(p),
310 WrongEndingToken(_, p) => Some(p),
311 WrongSortOrder(p) => Some(p),
312 InvalidLifetime => None,
313 CannotBuild(_) => None,
314 }
315 }
316
317 /// Helper: return this error's position.
pos(&self) -> Pos318 pub(crate) fn pos(&self) -> Pos {
319 // XXXX This duplicate code is yucky. We should refactor this error
320 // type to use an ErrorKind pattern.
321 use Error::*;
322 let pos = match self {
323 Internal(p) => Some(p),
324 MissingKeyword(p) => Some(p),
325 TruncatedLine(p) => Some(p),
326 BadKeyword(p) => Some(p),
327 BadObjectBeginTag(p) => Some(p),
328 BadObjectEndTag(p) => Some(p),
329 BadObjectMismatchedTag(p) => Some(p),
330 BadObjectBase64(p) => Some(p),
331 DuplicateToken(_, p) => Some(p),
332 UnexpectedToken(_, p) => Some(p),
333 MissingToken(_) => None,
334 MisplacedToken(_, p) => Some(p),
335 TooManyArguments(_, p) => Some(p),
336 TooFewArguments(_, p) => Some(p),
337 UnexpectedObject(_, p) => Some(p),
338 MissingObject(_, p) => Some(p),
339 WrongObject(p) => Some(p),
340 MissingArgument(p) => Some(p),
341 BadArgument(p, _) => Some(p),
342 BadObjectVal(p, _) => Some(p),
343 BadSignature(p) => Some(p),
344 BadTorVersion(p) => Some(p),
345 BadPolicy(p, _) => Some(p),
346 Untimely(p, _) => Some(p),
347 Undecodable(p, _) => Some(p),
348 BadDocumentVersion(_) => None,
349 BadDocumentType => None,
350 WrongStartingToken(_, p) => Some(p),
351 WrongEndingToken(_, p) => Some(p),
352 WrongSortOrder(p) => Some(p),
353 InvalidLifetime => None,
354 CannotBuild(_) => None,
355 };
356 *pos.unwrap_or(&Pos::Unknown)
357 }
358
359 /// Return a new error based on this one, with any byte-based
360 /// position mapped to some line within a string.
within(mut self, s: &str) -> Error361 pub fn within(mut self, s: &str) -> Error {
362 if let Some(p) = self.pos_mut() {
363 *p = p.within(s);
364 }
365 self
366 }
367
368 /// Return a new error based on this one, with the position (if
369 /// any) replaced by 'p'.
at_pos(mut self, p: Pos) -> Error370 pub fn at_pos(mut self, p: Pos) -> Error {
371 if let Some(mypos) = self.pos_mut() {
372 *mypos = p;
373 }
374 self
375 }
376
377 /// Return a new error based on this one, with the position (if
378 /// replaced by 'p' if it had no position before.
or_at_pos(mut self, p: Pos) -> Error379 pub fn or_at_pos(mut self, p: Pos) -> Error {
380 if let Some(mypos) = self.pos_mut() {
381 if *mypos == Pos::None {
382 *mypos = p;
383 }
384 }
385 self
386 }
387 }
388
389 /// Create a From<> implementation to construct an Error::BadArgument
390 /// from another error type.
391 macro_rules! derive_from_err{
392 { $etype:ty } => {
393 impl From<$etype> for Error {
394 fn from(e: $etype) -> Error {
395 Error::BadArgument(Pos::None, e.to_string())
396 }
397 }
398 }
399 }
400 derive_from_err! {std::num::ParseIntError}
401 derive_from_err! {std::net::AddrParseError}
402
403 impl From<crate::types::policy::PolicyError> for Error {
from(e: crate::types::policy::PolicyError) -> Error404 fn from(e: crate::types::policy::PolicyError) -> Error {
405 Error::BadPolicy(Pos::None, e)
406 }
407 }
408
409 impl From<tor_bytes::Error> for Error {
from(e: tor_bytes::Error) -> Error410 fn from(e: tor_bytes::Error) -> Error {
411 Error::Undecodable(Pos::None, e)
412 }
413 }
414
415 impl From<tor_checkable::TimeValidityError> for Error {
from(e: tor_checkable::TimeValidityError) -> Error416 fn from(e: tor_checkable::TimeValidityError) -> Error {
417 Error::Untimely(Pos::None, e)
418 }
419 }
420
421 impl From<signature::Error> for Error {
from(_e: signature::Error) -> Error422 fn from(_e: signature::Error) -> Error {
423 Error::BadSignature(Pos::None)
424 }
425 }
426