Document more of 'netdoc', including errors.

This commit is contained in:
Nick Mathewson 2020-05-09 10:27:26 -04:00
parent c4d0f59e7d
commit 07e41734ec
4 changed files with 118 additions and 30 deletions

View File

@ -1,16 +1,27 @@
//! Types used to parse arguments of entries in a directory document.
//!
//! There are some types that are pretty common, like "ISOTime",
//! "base64-encoded data", and so on.
//!
//! These types shouldn't be exposed outside of the netdoc crate.
pub use b64impl::*;
pub use curve25519impl::*;
pub use timeimpl::*;
use thiserror::Error;
/// A problem that can occur when parsing an argument.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ArgError {
/// Invalid base64.
#[error("bad base64 encoding: {0}")]
Base64(#[from] base64::DecodeError),
/// A time that was in the wrong form, or out of range.
#[error("invalid time: {0}")]
BadTime(#[from] chrono::ParseError),
/// Some other error, represented as a string.
#[error("{0}")]
Generic(&'static str),
}

View File

@ -1,3 +1,4 @@
//! Error type from parsing a document, and the position where it occurred
use thiserror::Error;
use crate::policy::PolicyError;
@ -5,17 +6,33 @@ use std::fmt;
/// A position within a directory object. Used to tell where an error
/// occurred.
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Position {
/// The error did not occur at any particular position.
///
/// This can happen when the error is something like a missing entry:
/// the entry is supposed to go _somewhere_, but we can't say where.
None,
/// The error occurred at an unknown position.
///
/// We should avoid using this case.
Unknown,
/// The error occurred at an invalid offset within the string, or
/// outside the string entirely.
///
/// This can only occur because of an internal error of some kind.
Invalid(usize),
/// The error occurred at a particular byte within the string.
///
/// We try to conver these to a Pos before displaying them to the user.
Byte { off: usize },
/// The error occurred at a particular line (and possibly at a
/// particular byte within the line.)
Pos { line: usize, byte: usize },
}
impl Position {
/// Construct a Position from an offset within a slice.
/// Construct a Position from an offset within a &str slice.
pub fn from_offset(s: &str, off: usize) -> Self {
if off > s.len() || !s.is_char_boundary(off) {
Position::Invalid(off)
@ -34,9 +51,18 @@ impl Position {
}
}
}
/// Construct a position from a byte offset.
pub fn from_byte(off: usize) -> Self {
Position::Byte { off }
}
/// Given a position, if it was at a byte offset, convert it to a
/// line-and-byte position within `s`.
///
/// Requires that this position was actually generated from `s`.
/// If it was not, the results here may be nonsensical.
///
/// TODO: I wish I knew an efficient safe way to do this that
/// guaranteed that we we always talking about the right string.
pub fn within(self, s: &str) -> Self {
match self {
Position::Byte { off } => Self::from_offset(s, off),
@ -58,81 +84,122 @@ impl fmt::Display for Position {
}
}
/// An error that occurred while parsing a directory object of some kind.
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum Error {
/// An internal error in the parser: these should never happen.
#[error("internal error{0}")]
Internal(Position),
Internal(Position), // TODO string.
/// An entry was found with no keyword.
#[error("no keyword for entry{0}")]
MissingKeyword(Position),
/// An entry was found with no newline at the end.
#[error("line truncated before newline{0}")]
TruncatedLine(Position),
/// A bad string was found in the keyword position.
#[error("invalid keyword{0}")]
BadKeyword(Position),
/// We found an ill-formed "BEGIN FOO" tag.
#[error("invalid PEM BEGIN tag{0}")]
BadObjectBeginTag(Position),
/// We found an ill-formed "END FOO" tag.
#[error("invalid PEM END tag{0}")]
BadObjectEndTag(Position),
/// We found a "BEGIN FOO" tag with an "END FOO" tag that didn't match.
#[error("mismatched PEM tags{0}")]
BadObjectMismatchedTag(Position),
/// We found a base64 object with an invalid base64 encoding.
#[error("invalid base64 in object around byte {0}")]
BadObjectBase64(Position),
/// The document is not supposed to contain more than one of some
/// kind of entry, but we found one anyway.
#[error("duplicate entry for {0}{1}")]
DuplicateToken(&'static str, Position),
/// The document is not supposed to contain any of some particular kind
/// of entry, but we found one anyway.
#[error("entry {0} unexpected{1}")]
UnexpectedToken(&'static str, Position),
/// The document is not supposed to contain any of some particular kind
/// of entry, but we found one anyway.
#[error("didn't find required entry {0}")]
MissingToken(&'static str),
/// We found more arguments on an entry than it is allowed to hav.e
#[error("too many arguments for {0}{1}")]
TooManyArguments(&'static str, Position),
/// We didn't fine enough arguments for some entry.
#[error("too few arguments for {0}{1}")]
TooFewArguments(&'static str, Position),
/// We found an object attached to an entry that isn't supposed to
/// have one.
#[error("unexpected object for {0}{1}")]
UnexpectedObject(&'static str, Position),
/// An entry was supposed to have an object, but it didn't.
#[error("missing object for {0}{1}")]
MissingObject(&'static str, Position),
/// We found an object on an entry, but the type was wrong.
#[error("wrong object type for entry{0}")]
WrongObject(Position),
/// We tried to find an argument that we were sure would be there,
/// but it wasn't!
///
/// This error should never occur in correct code; it should be
/// caught earlier by TooFewArguments.
#[error("missing argument for entry{0}")]
MissingArgument(Position),
/// We found an argument that couldn't be parsed.
#[error("bad argument {0} for entry{1}: {2}")]
BadArgument(usize, Position, String), // converting to a string doesn't sit well with me. XXXX
/// We found an object that couldn't be parsed after it was decoded.
#[error("bad object for entry{0}: {1}")]
BadObjectVal(Position, String), // converting to a string doesn't sit well with me. XXXX
/// There was some signature that we couldn't validate.
#[error("couldn't validate signature{0}")]
BadSignature(Position),
BadSignature(Position), // say which kind of signature. TODO
/// There was a tor version we couldn't parse.
#[error("couldn't parse Tor version{0}")]
BadVersion(Position),
BadVersion(Position), // collapse into something else.
/// There was an ipv4 or ipv6 policy entry that we couldn't parse.
#[error("invalid policy entry{0}: {1}")]
BadPolicy(Position, #[source] PolicyError),
}
impl Error {
pub fn within(self, s: &str) -> Error {
/// Helper: return a mutable reference to this error's position (if any)
fn pos_mut(&mut self) -> Option<&mut Position> {
use Error::*;
match self {
Internal(p) => Internal(p.within(s)),
MissingKeyword(p) => MissingKeyword(p.within(s)),
TruncatedLine(p) => TruncatedLine(p.within(s)),
BadKeyword(p) => BadKeyword(p.within(s)),
BadObjectBeginTag(p) => BadObjectBeginTag(p.within(s)),
BadObjectEndTag(p) => BadObjectEndTag(p.within(s)),
BadObjectMismatchedTag(p) => BadObjectMismatchedTag(p.within(s)),
BadObjectBase64(p) => BadObjectBase64(p.within(s)),
DuplicateToken(s2, p) => DuplicateToken(s2, p.within(s)),
UnexpectedToken(s2, p) => UnexpectedToken(s2, p.within(s)),
MissingToken(s2) => MissingToken(s2),
TooManyArguments(s2, p) => TooManyArguments(s2, p.within(s)),
TooFewArguments(s2, p) => TooFewArguments(s2, p.within(s)),
UnexpectedObject(s2, p) => UnexpectedObject(s2, p.within(s)),
MissingObject(s2, p) => MissingObject(s2, p.within(s)),
WrongObject(p) => WrongObject(p.within(s)),
MissingArgument(p) => MissingArgument(p.within(s)),
BadArgument(n, p, s2) => BadArgument(n, p.within(s), s2),
BadObjectVal(p, s2) => BadObjectVal(p.within(s), s2),
BadSignature(p) => BadSignature(p.within(s)),
BadVersion(p) => BadVersion(p.within(s)),
BadPolicy(p, e) => BadPolicy(p.within(s), e),
Internal(p) => Some(p),
MissingKeyword(p) => Some(p),
TruncatedLine(p) => Some(p),
BadKeyword(p) => Some(p),
BadObjectBeginTag(p) => Some(p),
BadObjectEndTag(p) => Some(p),
BadObjectMismatchedTag(p) => Some(p),
BadObjectBase64(p) => Some(p),
DuplicateToken(_, p) => Some(p),
UnexpectedToken(_, p) => Some(p),
MissingToken(_) => None,
TooManyArguments(_, p) => Some(p),
TooFewArguments(_, p) => Some(p),
UnexpectedObject(_, p) => Some(p),
MissingObject(_, p) => Some(p),
WrongObject(p) => Some(p),
MissingArgument(p) => Some(p),
BadArgument(_, p, _) => Some(p),
BadObjectVal(p, _) => Some(p),
BadSignature(p) => Some(p),
BadVersion(p) => Some(p),
BadPolicy(p, _) => Some(p),
}
}
/// Return a new error based on this one, with any byte-based
/// position mapped to some line within a string.
pub fn within(mut self, s: &str) -> Error {
if let Some(p) = self.pos_mut() {
*p = p.within(s);
}
self
}
}

View File

@ -16,6 +16,10 @@
//! TODO: This crate has several pieces that should probably be split out
//! into other smaller cases, including handling for version numbers
//! and exit policies.
//!
//! TODO: Many parts of this crate that should be public aren't.
//!
//! TODO: this crate needs far more tests!
#![allow(dead_code)]
//#![warn(missing_docs)]
@ -27,7 +31,7 @@ mod rules;
mod tokenize;
mod util;
#[macro_use]
mod macros; // xxxx
mod macros;
pub mod policy;
pub mod routerdesc;
pub mod version;

View File

@ -1,4 +1,8 @@
//! Break a string into a set of directory-object Items.
//!
//! This module defines Item, which represents a basic entry in a
//! directory document, and NetDocReader, which is used to break a
//! string into Items.
use crate::{Error, Position, Result};
use std::cell::{Ref, RefCell};
@ -8,7 +12,9 @@ use std::str::FromStr;
///
/// This represents a single blob within a pair of "-----BEGIN
/// FOO-----" and "-----END FOO-----". The data is not guaranteed to
/// be actual base64 when this object is created.
/// be actual base64 when this object is created: doing so would
/// require either that we parse the base64 twice, or that we allocate
/// a buffer to hold the data before it's needed.
#[derive(Clone, Copy, Debug)]
pub struct Object<'a> {
tag: &'a str,