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
    #[allow(clippy::string_slice)] // TODO
62
302
    pub fn from_offset(s: &str, off: usize) -> Self {
63
302
        if off > s.len() || !s.is_char_boundary(off) {
64
            Pos::Invalid(off)
65
        } else {
66
302
            let s = &s[..off];
67
302
            let last_nl = s.rfind('\n');
68
302
            match last_nl {
69
116
                Some(pos) => {
70
106136
                    let newlines = s.bytes().filter(|b| *b == b'\n').count();
71
116
                    Pos::PosInLine {
72
116
                        line: newlines + 1,
73
116
                        byte: off - pos,
74
116
                    }
75
                }
76
186
                None => Pos::PosInLine {
77
186
                    line: 1,
78
186
                    byte: off + 1,
79
186
                },
80
            }
81
        }
82
302
    }
83
    /// Construct a Pos from a slice of some other string.  This
84
    /// Pos won't be terribly helpful, but it may be converted
85
    /// into a useful Pos with `within`.
86
59796
    pub fn at(s: &str) -> Self {
87
59796
        let ptr = s.as_ptr();
88
59796
        Pos::Raw { ptr }
89
59796
    }
90
    /// Construct Pos from the end of some other string.
91
    #[allow(clippy::string_slice)] // TODO
92
1388
    pub fn at_end_of(s: &str) -> Self {
93
1388
        let ending = &s[s.len()..];
94
1388
        Pos::at(ending)
95
1388
    }
96
    /// Construct a position from a byte offset.
97
14
    pub fn from_byte(off: usize) -> Self {
98
14
        Pos::Byte { off }
99
14
    }
100
    /// Construct a position from a line and a byte offset within that line.
101
98
    pub fn from_line(line: usize, byte: usize) -> Self {
102
98
        Pos::PosInLine { line, byte }
103
98
    }
104
    /// If this position appears within `s`, and has not yet been mapped to
105
    /// a line-and-byte position, return its offset.
106
3730
    pub(crate) fn offset_within(&self, s: &str) -> Option<usize> {
107
3730
        match self {
108
10
            Pos::Byte { off } => Some(*off),
109
3720
            Pos::Raw { ptr } => offset_in(*ptr, s),
110
            _ => None,
111
        }
112
3730
    }
113
    /// Given a position, if it was at a byte offset, convert it to a
114
    /// line-and-byte position within `s`.
115
    ///
116
    /// Requires that this position was actually generated from `s`.
117
    /// If it was not, the results here may be nonsensical.
118
    ///
119
    /// TODO: I wish I knew an efficient safe way to do this that
120
    /// guaranteed that we always talking about the right string.
121
    #[must_use]
122
276
    pub fn within(self, s: &str) -> Self {
123
276
        match self {
124
4
            Pos::Byte { off } => Self::from_offset(s, off),
125
88
            Pos::Raw { ptr } => {
126
88
                if let Some(off) = offset_in(ptr, s) {
127
88
                    Self::from_offset(s, off)
128
                } else {
129
                    self
130
                }
131
            }
132
184
            _ => self,
133
        }
134
276
    }
135
}
136

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

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

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

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

            
328
/// Error parsing what was supposed to be a fixed, constant, string
329
#[derive(Clone, Debug, Error, Eq, PartialEq)]
330
#[error("expected fixed string {expected:?}, got {got:?}")]
331
// This is unlikely to change - and our macro expansion wants to be able to construct i.
332
// Making it exhaustive seems better than the bureaucracy of a constructor.
333
#[allow(clippy::exhaustive_structs)]
334
pub struct ExpectedConstantString {
335
    /// The fixed, constant string we expected
336
    pub expected: &'static str,
337

            
338
    /// The actual value we encountered
339
    pub got: String,
340
}
341

            
342
impl NetdocErrorKind {
343
    /// Construct a new Error with this kind.
344
    #[must_use]
345
38902
    pub(crate) fn err(self) -> Error {
346
38902
        Error {
347
38902
            kind: self,
348
38902
            msg: None,
349
38902
            pos: Pos::Unknown,
350
38902
            source: None,
351
38902
        }
352
38902
    }
353

            
354
    /// Construct a new error with this kind at a given position.
355
    #[must_use]
356
2572
    pub(crate) fn at_pos(self, pos: Pos) -> Error {
357
2572
        self.err().at_pos(pos)
358
2572
    }
359

            
360
    /// Construct a new error with this kind and a given message.
361
    #[must_use]
362
36110
    pub(crate) fn with_msg<T>(self, msg: T) -> Error
363
36110
    where
364
36110
        T: Into<Cow<'static, str>>,
365
    {
366
36110
        self.err().with_msg(msg)
367
36110
    }
368
}
369

            
370
impl From<signature::Error> for NetdocErrorSource {
371
    fn from(err: signature::Error) -> Self {
372
        NetdocErrorSource::Signature(Arc::new(err))
373
    }
374
}
375

            
376
/// An error that occurred while parsing a directory object of some kind.
377
#[derive(Debug, Clone)]
378
#[non_exhaustive]
379
pub struct Error {
380
    /// What kind of error occurred?
381
    pub(crate) kind: NetdocErrorKind,
382
    /// Do we have more information about the error?>
383
    msg: Option<Cow<'static, str>>,
384
    /// Where did the error occur?
385
    pos: Pos,
386
    /// Was this caused by another error?
387
    source: Option<NetdocErrorSource>,
388
}
389

            
390
impl PartialEq for Error {
391
100
    fn eq(&self, other: &Self) -> bool {
392
100
        self.kind == other.kind && self.msg == other.msg && self.pos == other.pos
393
100
    }
394
}
395

            
396
impl Error {
397
    /// Helper: return this error's position.
398
18
    pub(crate) fn pos(&self) -> Pos {
399
18
        self.pos
400
18
    }
401

            
402
    /// Return a new error based on this one, with any byte-based
403
    /// position mapped to some line within a string.
404
    #[must_use]
405
258
    pub fn within(mut self, s: &str) -> Error {
406
258
        self.pos = self.pos.within(s);
407
258
        self
408
258
    }
409

            
410
    /// Return a new error based on this one, with the position (if
411
    /// any) replaced by 'p'.
412
    #[must_use]
413
38670
    pub fn at_pos(mut self, p: Pos) -> Error {
414
38670
        self.pos = p;
415
38670
        self
416
38670
    }
417

            
418
    /// Return a new error based on this one, with the position (if
419
    /// replaced by 'p' if it had no position before.
420
    #[must_use]
421
10
    pub fn or_at_pos(mut self, p: Pos) -> Error {
422
10
        match self.pos {
423
8
            Pos::None | Pos::Unknown => {
424
8
                self.pos = p;
425
8
            }
426
2
            _ => (),
427
        }
428
10
        self
429
10
    }
430

            
431
    /// Return a new error based on this one, with the message
432
    /// value set to a provided static string.
433
    #[must_use]
434
38378
    pub(crate) fn with_msg<T>(mut self, message: T) -> Error
435
38378
    where
436
38378
        T: Into<Cow<'static, str>>,
437
    {
438
38378
        self.msg = Some(message.into());
439
38378
        self
440
38378
    }
441

            
442
    /// Return a new error based on this one, with the source-error
443
    /// value set to the provided error.
444
    #[must_use]
445
14
    pub(crate) fn with_source<T>(mut self, source: T) -> Error
446
14
    where
447
14
        T: Into<NetdocErrorSource>,
448
    {
449
14
        self.source = Some(source.into());
450
14
        self
451
14
    }
452

            
453
    /// Return the [`NetdocErrorKind`] of this error.
454
260
    pub fn netdoc_error_kind(&self) -> NetdocErrorKind {
455
260
        self.kind
456
260
    }
457
}
458

            
459
impl fmt::Display for Error {
460
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461
        write!(f, "{}{}", self.kind, self.pos)?;
462
        if let Some(msg) = &self.msg {
463
            write!(f, ": {}", msg)?;
464
        }
465
        Ok(())
466
    }
467
}
468

            
469
impl std::error::Error for Error {
470
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
471
        self.source.as_ref().map(|s| s as _)
472
    }
473
}
474

            
475
/// Helper: declare an Into<> implementation to automatically convert a $source
476
/// into an Error with kind $kind.
477
macro_rules! declare_into  {
478
    {$source:ty => $kind:ident} => {
479
        impl From<$source> for Error {
480
8
            fn from(source: $source) -> Error {
481
8
                Error {
482
8
                    kind: NetdocErrorKind::$kind,
483
8
                    msg: None,
484
8
                    pos: Pos::Unknown,
485
8
                    source: Some(source.into())
486
8
                }
487
8
            }
488
        }
489
    }
490
}
491

            
492
declare_into! { signature::Error => BadSignature }
493
declare_into! { tor_checkable::TimeValidityError => BadTimeBound }
494
declare_into! { tor_bytes::Error => Undecodable }
495
declare_into! { std::num::ParseIntError => BadArgument }
496
declare_into! { std::net::AddrParseError => BadArgument }
497
declare_into! { PolicyError => BadPolicy }
498

            
499
impl From<std::convert::Infallible> for Error {
500
    fn from(e: std::convert::Infallible) -> Error {
501
        match e {}
502
    }
503
}
504

            
505
impl From<tor_error::Bug> for Error {
506
8
    fn from(err: tor_error::Bug) -> Self {
507
        use tor_error::HasKind;
508
8
        let kind = match err.kind() {
509
            tor_error::ErrorKind::BadApiUsage => NetdocErrorKind::BadApiUsage,
510
8
            _ => NetdocErrorKind::Internal,
511
        };
512

            
513
8
        Error {
514
8
            kind,
515
8
            msg: None,
516
8
            pos: Pos::Unknown,
517
8
            source: Some(err.into()),
518
8
        }
519
8
    }
520
}
521

            
522
/// An error that occurs while trying to construct a network document.
523
#[derive(Clone, Debug, Error)]
524
#[non_exhaustive]
525
pub enum BuildError {
526
    /// We were unable to build the document, probably due to an invalid
527
    /// argument of some kind.
528
    #[error("cannot build document: {0}")]
529
    CannotBuild(&'static str),
530

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