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::{borrow::Cow, fmt, sync::Arc};
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.
61
261
    pub fn from_offset(s: &str, off: usize) -> Self {
62
261
        if off > s.len() || !s.is_char_boundary(off) {
63
            Pos::Invalid(off)
64
        } else {
65
261
            let s = &s[..off];
66
261
            let last_nl = s.rfind('\n');
67
261
            match last_nl {
68
104
                Some(pos) => {
69
103406
                    let newlines = s.bytes().filter(|b| *b == b'\n').count();
70
104
                    Pos::PosInLine {
71
104
                        line: newlines + 1,
72
104
                        byte: off - pos,
73
104
                    }
74
                }
75
157
                None => Pos::PosInLine {
76
157
                    line: 1,
77
157
                    byte: off + 1,
78
157
                },
79
            }
80
        }
81
261
    }
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`.
85
53004
    pub fn at(s: &str) -> Self {
86
53004
        let ptr = s.as_ptr();
87
53004
        Pos::Raw { ptr }
88
53004
    }
89
    /// Construct Pos from the end of some other string.
90
579
    pub fn at_end_of(s: &str) -> Self {
91
579
        let ending = &s[s.len()..];
92
579
        Pos::at(ending)
93
579
    }
94
    /// Construct a position from a byte offset.
95
10
    pub fn from_byte(off: usize) -> Self {
96
10
        Pos::Byte { off }
97
10
    }
98
    /// Construct a position from a line and a byte offset within that line.
99
94
    pub fn from_line(line: usize, byte: usize) -> Self {
100
94
        Pos::PosInLine { line, byte }
101
94
    }
102
    /// If this position appears within `s`, and has not yet been mapped to
103
    /// a line-and-byte position, return its offset.
104
1430
    pub(crate) fn offset_within(&self, s: &str) -> Option<usize> {
105
1430
        match self {
106
10
            Pos::Byte { off } => Some(*off),
107
1420
            Pos::Raw { ptr } => offset_in(*ptr, s),
108
            _ => None,
109
        }
110
1430
    }
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.
119
    #[must_use]
120
241
    pub fn within(self, s: &str) -> Self {
121
241
        match self {
122
            Pos::Byte { off } => Self::from_offset(s, off),
123
78
            Pos::Raw { ptr } => {
124
78
                if let Some(off) = offset_in(ptr, s) {
125
78
                    Self::from_offset(s, off)
126
                } else {
127
                    self
128
                }
129
            }
130
163
            _ => self,
131
        }
132
241
    }
133
}
134

            
135
/// If `ptr` is within `s`, return its byte offset.
136
1498
fn offset_in(ptr: *const u8, s: &str) -> Option<usize> {
137
1498
    // We need to confirm that 'ptr' falls within 's' in order
138
1498
    // to subtract it meaningfully and find its offset.
139
1498
    // Otherwise, we'll get a bogus result.
140
1498
    //
141
1498
    // Fortunately, we _only_ get a bogus result: we don't
142
1498
    // hit unsafe behavior.
143
1498
    let ptr_u = ptr as usize;
144
1498
    let start_u = s.as_ptr() as usize;
145
1498
    let end_u = (s.as_ptr() as usize) + s.len();
146
1498
    if start_u <= ptr_u && ptr_u < end_u {
147
1498
        Some(ptr_u - start_u)
148
    } else {
149
        None
150
    }
151
1498
}
152

            
153
impl fmt::Display for Pos {
154
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155
        use Pos::*;
156
        match self {
157
            None => write!(f, ""),
158
            Unknown => write!(f, " at unknown position"),
159
            Invalid(off) => write!(f, " at invalid offset at index {}", off),
160
            Byte { off } => write!(f, " at byte {}", off),
161
            PosInLine { line, byte } => write!(f, " on line {}, byte {}", line, byte),
162
            Raw { ptr } => write!(f, " at {:?}", ptr),
163
        }
164
    }
165
}
166

            
167
/// A variety of parsing error.
168
#[derive(Copy, Clone, Debug, derive_more::Display, PartialEq, Eq)]
169
#[non_exhaustive]
170
pub enum NetdocErrorKind {
171
    /// An internal error in the parser: these should never happen.
172
    #[display("internal error")]
173
    Internal,
174
    /// Invoked an API in an incorrect manner.
175
    #[display("bad API usage")]
176
    BadApiUsage,
177
    /// An entry was found with no keyword.
178
    #[display("no keyword for entry")]
179
    MissingKeyword,
180
    /// An entry was found with no newline at the end.
181
    #[display("line truncated before newline")]
182
    TruncatedLine,
183
    /// A bad string was found in the keyword position.
184
    #[display("invalid keyword")]
185
    BadKeyword,
186
    /// We found an ill-formed "BEGIN FOO" tag.
187
    #[display("invalid PEM BEGIN tag")]
188
    BadObjectBeginTag,
189
    /// We found an ill-formed "END FOO" tag.
190
    #[display("invalid PEM END tag")]
191
    BadObjectEndTag,
192
    /// We found a "BEGIN FOO" tag with an "END FOO" tag that didn't match.
193
    #[display("mismatched PEM tags")]
194
    BadObjectMismatchedTag,
195
    /// We found a base64 object with an invalid base64 encoding.
196
    #[display("invalid base64 in object")]
197
    BadObjectBase64,
198
    /// The document is not supposed to contain more than one of some
199
    /// kind of entry, but we found one anyway.
200
    #[display("duplicate entry")]
201
    DuplicateToken,
202
    /// The document is not supposed to contain any of some particular kind
203
    /// of entry, but we found one anyway.
204
    #[display("unexpected entry")]
205
    UnexpectedToken,
206
    /// The document is supposed to contain any of some particular kind
207
    /// of entry, but we didn't find one one anyway.
208
    #[display("didn't find required entry")]
209
    MissingToken,
210
    /// The document was supposed to have one of these, but not where we
211
    /// found it.
212
    #[display("entry out of place")]
213
    MisplacedToken,
214
    /// We found more arguments on an entry than it is allowed to have.
215
    #[display("too many arguments")]
216
    TooManyArguments,
217
    /// We didn't fine enough arguments for some entry.
218
    #[display("too few arguments")]
219
    TooFewArguments,
220
    /// We found an object attached to an entry that isn't supposed to
221
    /// have one.
222
    #[display("unexpected object")]
223
    UnexpectedObject,
224
    /// An entry was supposed to have an object, but it didn't.
225
    #[display("missing object")]
226
    MissingObject,
227
    /// We found an object on an entry, but the type was wrong.
228
    #[display("wrong object type")]
229
    WrongObject,
230
    /// We tried to find an argument that we were sure would be there,
231
    /// but it wasn't!
232
    ///
233
    /// This error should never occur in correct code; it should be
234
    /// caught earlier by TooFewArguments.
235
    #[display("missing argument")]
236
    MissingArgument,
237
    /// We found an argument that couldn't be parsed.
238
    #[display("bad argument for entry")]
239
    BadArgument,
240
    /// We found an object that couldn't be parsed after it was decoded.
241
    #[display("bad object for entry")]
242
    BadObjectVal,
243
    /// There was some signature that we couldn't validate.
244
    #[display("couldn't validate signature")]
245
    BadSignature, // TODO(nickm): say which kind of signature.
246
    /// The object is not valid at the required time.
247
    #[display("couldn't validate time bound")]
248
    BadTimeBound,
249
    /// There was a tor version we couldn't parse.
250
    #[display("couldn't parse Tor version")]
251
    BadTorVersion,
252
    /// There was an ipv4 or ipv6 policy entry that we couldn't parse.
253
    #[display("invalid policy entry")]
254
    BadPolicy,
255
    /// An underlying byte sequence couldn't be decoded.
256
    #[display("decoding error")]
257
    Undecodable,
258
    /// Versioned document with an unrecognized version.
259
    #[display("unrecognized document version")]
260
    BadDocumentVersion,
261
    /// Unexpected document type
262
    #[display("unexpected document type")]
263
    BadDocumentType,
264
    /// We expected a kind of entry that we didn't find
265
    #[display("missing entry")]
266
    MissingEntry,
267
    /// Document or section started with wrong token
268
    #[display("Wrong starting token")]
269
    WrongStartingToken,
270
    /// Document or section ended with wrong token
271
    #[display("Wrong ending token")]
272
    WrongEndingToken,
273
    /// Items not sorted as expected
274
    #[display("Incorrect sort order")]
275
    WrongSortOrder,
276
    /// A consensus lifetime was ill-formed.
277
    #[display("Invalid consensus lifetime")]
278
    InvalidLifetime,
279
    /// Found an empty line in the middle of a document
280
    #[display("Empty line")]
281
    EmptyLine,
282
    /// The document began with a deprecated unicode BOM marker.
283
    #[display("unexpecteed byte-order marker")]
284
    BomMarkerFound,
285
    /// The document contained an internal NUL byte
286
    #[display("unexpected NUL")]
287
    NulFound,
288
}
289

            
290
/// The underlying source for an [`Error`](struct@Error).
291
#[derive(Clone, Debug, Error)]
292
#[non_exhaustive]
293
pub(crate) enum NetdocErrorSource {
294
    /// An error when parsing a binary object.
295
    #[error("Error parsing binary object")]
296
    Bytes(#[from] tor_bytes::Error),
297
    /// An error when parsing an exit policy.
298
    #[error("Error parsing policy")]
299
    Policy(#[from] PolicyError),
300
    /// An error when parsing an integer.
301
    #[error("Couldn't parse integer")]
302
    Int(#[from] std::num::ParseIntError),
303
    /// An error when parsing an IP or socket address.
304
    #[error("Couldn't parse address")]
305
    Address(#[from] std::net::AddrParseError),
306
    /// An error when validating a signature.
307
    #[error("Invalid signature")]
308
    Signature(#[source] Arc<signature::Error>),
309
    /// An error when validating a signature on an embedded binary certificate.
310
    #[error("Invalid certificate")]
311
    CertSignature(#[from] tor_cert::CertError),
312
    /// An error caused by an expired or not-yet-valid descriptor.
313
    #[error("Descriptor expired or not yet valid")]
314
    UntimelyDescriptor(#[from] tor_checkable::TimeValidityError),
315
    /// Invalid protocol versions.
316
    #[error("Protocol versions")]
317
    Protovers(#[from] tor_protover::ParseError),
318
    /// A bug in our programming, or somebody else's.
319
    #[error("Internal error or bug")]
320
    Bug(#[from] tor_error::Bug),
321
}
322

            
323
impl NetdocErrorKind {
324
    /// Construct a new Error with this kind.
325
    #[must_use]
326
38373
    pub(crate) fn err(self) -> Error {
327
38373
        Error {
328
38373
            kind: self,
329
38373
            msg: None,
330
38373
            pos: Pos::Unknown,
331
38373
            source: None,
332
38373
        }
333
38373
    }
334

            
335
    /// Construct a new error with this kind at a given position.
336
    #[must_use]
337
2259
    pub(crate) fn at_pos(self, pos: Pos) -> Error {
338
2259
        self.err().at_pos(pos)
339
2259
    }
340

            
341
    /// Construct a new error with this kind and a given message.
342
    #[must_use]
343
36110
    pub(crate) fn with_msg<T>(self, msg: T) -> Error
344
36110
    where
345
36110
        T: Into<Cow<'static, str>>,
346
36110
    {
347
36110
        self.err().with_msg(msg)
348
36110
    }
349
}
350

            
351
impl From<signature::Error> for NetdocErrorSource {
352
    fn from(err: signature::Error) -> Self {
353
        NetdocErrorSource::Signature(Arc::new(err))
354
    }
355
}
356

            
357
/// An error that occurred while parsing a directory object of some kind.
358
#[derive(Debug, Clone)]
359
#[non_exhaustive]
360
pub struct Error {
361
    /// What kind of error occurred?
362
    pub(crate) kind: NetdocErrorKind,
363
    /// Do we have more information about the error?>
364
    msg: Option<Cow<'static, str>>,
365
    /// Where did the error occur?
366
    pos: Pos,
367
    /// Was this caused by another error?
368
    source: Option<NetdocErrorSource>,
369
}
370

            
371
impl PartialEq for Error {
372
96
    fn eq(&self, other: &Self) -> bool {
373
96
        self.kind == other.kind && self.msg == other.msg && self.pos == other.pos
374
96
    }
375
}
376

            
377
impl Error {
378
    /// Helper: return this error's position.
379
18
    pub(crate) fn pos(&self) -> Pos {
380
18
        self.pos
381
18
    }
382

            
383
    /// Return a new error based on this one, with any byte-based
384
    /// position mapped to some line within a string.
385
    #[must_use]
386
231
    pub fn within(mut self, s: &str) -> Error {
387
231
        self.pos = self.pos.within(s);
388
231
        self
389
231
    }
390

            
391
    /// Return a new error based on this one, with the position (if
392
    /// any) replaced by 'p'.
393
    #[must_use]
394
38357
    pub fn at_pos(mut self, p: Pos) -> Error {
395
38357
        self.pos = p;
396
38357
        self
397
38357
    }
398

            
399
    /// Return a new error based on this one, with the position (if
400
    /// replaced by 'p' if it had no position before.
401
    #[must_use]
402
8
    pub fn or_at_pos(mut self, p: Pos) -> Error {
403
8
        match self.pos {
404
6
            Pos::None | Pos::Unknown => {
405
6
                self.pos = p;
406
6
            }
407
2
            _ => (),
408
        }
409
8
        self
410
8
    }
411

            
412
    /// Return a new error based on this one, with the message
413
    /// value set to a provided static string.
414
    #[must_use]
415
38104
    pub(crate) fn with_msg<T>(mut self, message: T) -> Error
416
38104
    where
417
38104
        T: Into<Cow<'static, str>>,
418
38104
    {
419
38104
        self.msg = Some(message.into());
420
38104
        self
421
38104
    }
422

            
423
    /// Return a new error based on this one, with the source-error
424
    /// value set to the provided error.
425
    #[must_use]
426
12
    pub(crate) fn with_source<T>(mut self, source: T) -> Error
427
12
    where
428
12
        T: Into<NetdocErrorSource>,
429
12
    {
430
12
        self.source = Some(source.into());
431
12
        self
432
12
    }
433

            
434
    /// Return the [`NetdocErrorKind`] of this error.
435
32
    pub fn netdoc_error_kind(&self) -> NetdocErrorKind {
436
32
        self.kind
437
32
    }
438
}
439

            
440
impl fmt::Display for Error {
441
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442
        write!(f, "{}{}", self.kind, self.pos)?;
443
        if let Some(msg) = &self.msg {
444
            write!(f, ": {}", msg)?;
445
        }
446
        Ok(())
447
    }
448
}
449

            
450
impl std::error::Error for Error {
451
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
452
        self.source.as_ref().map(|s| s as _)
453
    }
454
}
455

            
456
/// Helper: declare an Into<> implementation to automatically convert a $source
457
/// into an Error with kind $kind.
458
macro_rules! declare_into  {
459
    {$source:ty => $kind:ident} => {
460
        impl From<$source> for Error {
461
6
            fn from(source: $source) -> Error {
462
6
                Error {
463
6
                    kind: NetdocErrorKind::$kind,
464
6
                    msg: None,
465
6
                    pos: Pos::Unknown,
466
6
                    source: Some(source.into())
467
6
                }
468
6
            }
469
        }
470
    }
471
}
472

            
473
declare_into! { signature::Error => BadSignature }
474
declare_into! { tor_checkable::TimeValidityError => BadTimeBound }
475
declare_into! { tor_bytes::Error => Undecodable }
476
declare_into! { std::num::ParseIntError => BadArgument }
477
declare_into! { std::net::AddrParseError => BadArgument }
478
declare_into! { PolicyError => BadPolicy }
479

            
480
impl From<tor_error::Bug> for Error {
481
8
    fn from(err: tor_error::Bug) -> Self {
482
        use tor_error::HasKind;
483
8
        let kind = match err.kind() {
484
            tor_error::ErrorKind::BadApiUsage => NetdocErrorKind::BadApiUsage,
485
8
            _ => NetdocErrorKind::Internal,
486
        };
487

            
488
8
        Error {
489
8
            kind,
490
8
            msg: None,
491
8
            pos: Pos::Unknown,
492
8
            source: Some(err.into()),
493
8
        }
494
8
    }
495
}
496

            
497
/// An error that occurs while trying to construct a network document.
498
#[derive(Clone, Debug, Error)]
499
#[non_exhaustive]
500
pub enum BuildError {
501
    /// We were unable to build the document, probably due to an invalid
502
    /// argument of some kind.
503
    #[error("cannot build document: {0}")]
504
    CannotBuild(&'static str),
505

            
506
    /// An argument that was given as a string turned out to be unparsable.
507
    #[error("unable to parse argument")]
508
    Parse(#[from] crate::err::Error),
509
}