tor-bytes: add the missing documentation

This commit is contained in:
Nick Mathewson 2020-05-08 12:24:02 -04:00
parent 641576aa44
commit 671a266e13
5 changed files with 224 additions and 18 deletions

View File

@ -4,12 +4,24 @@ use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// Tried to read something, but we didn't find enough bytes.
///
/// This can mean that the object is truncated, or that we need to
/// read more and try again, depending on the context in which it
/// was received.
#[error("object truncated (or not fully present)")]
Truncated,
/// Called Reader::should_be_exhausted(), but found bytes anyway.
#[error("extra bytes at end of object")]
ExtraneousBytes,
/// An attempt to parse an object failed for some reason related to its
/// contents.
#[error("bad object: {0}")]
BadMessage(&'static str),
/// A parsing error that should never happen.
///
/// We use this one in lieu of calling assert() and expect() and
/// unwrap() from within parsing code.
#[error("internal programming error")]
Internal,
}

View File

@ -1,16 +1,25 @@
//! Implementations of Writeable and Readable for several items that
//! we use in Tor.
//!
//! These don't need to be in a separate module, but for convenience
//! this is where I'm putting them.
use super::*;
use generic_array::GenericArray;
// ----------------------------------------------------------------------
/// Vec<u8> is the main type that implements Writer.
impl Writer for Vec<u8> {
fn write_all(&mut self, bytes: &[u8]) {
self.extend_from_slice(bytes);
}
fn write_u8(&mut self, byte: u8) {
// specialize for performance
self.push(byte);
}
fn write_zeros(&mut self, n: usize) {
// specialize for performance
let new_len = self.len() + n;
self.resize(new_len, 0);
}
@ -53,6 +62,8 @@ where
}
*/
/// The GenericArray type is defined to work around a limitation in Rust's
/// typesystem.
impl<T, N> Readable for GenericArray<T, N>
where
T: Readable + Clone,
@ -80,6 +91,7 @@ where
}
}
// Implementations for reading and writing the unsigned types.
macro_rules! impl_u {
( $t:ty, $wrfn:ident, $rdfn:ident ) => {
impl Writeable for $t {

View File

@ -6,14 +6,23 @@
//!
//! These tools are generally unsuitable for handling anything bigger
//! than a few kilobytes in size.
//!
//! # Alternatives
//!
//! The Reader/Writer traits in std::io are more appropriate for
//! operations that can fail because of some IO problem. These can't;
//! they are for handling things that are already in memory.
//!
//! TODO: There are other crates that try the "bytes" crate that can
//! handle stuff like this, but it's rather bigger than I need. They
//! seem to be optimized for working with things that are split across
//! multiple chunks.
//!
//! TODO: The "untrusted" crate is designed for parsing untrusted
//! inputs in a way that can never panic. We might want to look into
//! using that as a backend instead.
// TODO: There are other crates that try the "bytes" crate that can
// handle stuff like this, but it's rather bigger than I need. This is
// for encoding stuff less than 1-2K.
//
// TODO: The "untrusted" crate is designed for parsing untrusted
// inputs in a way that can never panic. We might want to look into
// using that as a backend instead.
#![deny(missing_docs)]
mod err;
mod impls;
@ -26,24 +35,101 @@ pub use writer::Writer;
use arrayref::array_ref;
/// Result type returned byt his crate.
/// Result type returned by this crate.
pub type Result<T> = std::result::Result<T, Error>;
/// Trait for an object that can be encoded onto a Writer by reference.
///
/// Implement this trait in order to make an object writeable.
///
/// Most code won't need to call this directly, but will instead use
/// it implicitly via the Writer::write() method.
///
/// # Example
///
/// ```
/// use tor_bytes::{Writeable, Writer};
/// #[derive(Debug, Eq, PartialEq)]
/// struct Message {
/// flags: u32,
/// cmd: u8
/// }
///
/// impl Writeable for Message {
/// fn write_onto<B:Writer+?Sized>(&self, b: &mut B) {
/// // We'll say that a "Message" is encoded as flags, then command.
/// b.write_u32(self.flags);
/// b.write_u8(self.cmd);
/// }
/// }
///
/// let msg = Message { flags: 0x43, cmd: 0x07 };
/// let mut writer: Vec<u8> = Vec::new();
/// writer.write(&msg);
/// assert_eq!(writer, &[0x00, 0x00, 0x00, 0x43, 0x07 ]);
/// ```
pub trait Writeable {
/// Encode this object into the writer `b`.
fn write_onto<B: Writer + ?Sized>(&self, b: &mut B);
}
/// Trait for an object that can be encoded onto a Writer in a way that
/// consumes the original object.
/// Trait for an object that can be encoded and consumed by a Writer.
///
/// Implement this trait in order to make an object that can be
/// written more efficiently by absorbing it into the writer.
///
/// Most code won't need to call this directly, but will instead use
/// it implicitly via the Writer::write_and_consume() method.
pub trait WriteableOnce {
/// Encode this object into the writer `b`, and consume it.
fn write_into<B: Writer + ?Sized>(self, b: &mut B);
}
// ----------------------------------------------------------------------
/// Trait for an object that can be extracted from a Reader.
///
/// Implement this trait in order to make an object that can (maybe)
/// be decoded from a reader.
//
/// Most code won't need to call this directly, but will instead use
/// it implicitly via the Reader::extract() method.
///
/// # Example
///
/// ```
/// use tor_bytes::{Readable,Reader,Result};
/// #[derive(Debug, Eq, PartialEq)]
/// struct Message {
/// flags: u32,
/// cmd: u8
/// }
///
/// impl Readable for Message {
/// fn take_from(r: &mut Reader<'_>) -> Result<Self> {
/// // A "Message" is encoded as flags, then command.
/// let flags = r.take_u32()?;
/// let cmd = r.take_u8()?;
/// Ok(Message{ flags, cmd })
/// }
/// }
///
/// let encoded = [0x00, 0x00, 0x00, 0x43, 0x07 ];
/// let mut reader = Reader::from_slice(&encoded);
/// let m: Message = reader.extract()?;
/// assert_eq!(m, Message { flags: 0x43, cmd: 0x07 });
/// reader.should_be_exhausted()?; // make sure there are no bytes left over
/// # Result::Ok(())
/// ```
pub trait Readable: Sized {
/// Try to extract an object of this type from a Reader.
///
/// Implementations should generally try to be efficient: this is
/// not the right place to check signatures or perform expensive
/// operations. If you have an object that must not be used until
/// it is finally validated, consider making this function return
/// a wrapped type that can be unwrapped later on once it gets
/// checked.
fn take_from(b: &mut Reader<'_>) -> Result<Self>;
}

View File

@ -3,8 +3,45 @@ use arrayref::array_ref;
/// A type for reading messages from a slice of bytes.
///
/// Unlike io::Read, this trait has a simpler error type, and is designed
/// Unlike io::Read, this object has a simpler error type, and is designed
/// for in-memory parsing only.
///
/// # Examples
///
/// You can use a Reader to extract information byte-by-byte:
///
/// ```
/// use tor_bytes::{Reader,Result};
/// let msg = [ 0x00, 0x01, 0x23, 0x45, 0x22, 0x00, 0x00, 0x00 ];
/// let mut r = Reader::from_slice(&msg[..]);
/// // Multi-byte values are always big-endian.
/// assert_eq!(r.take_u32()?, 0x12345);
/// assert_eq!(r.take_u8()?, 0x22);
///
/// // You can check on the length of the message...
/// assert_eq!(r.total_len(), 8);
/// assert_eq!(r.consumed(), 5);
/// assert_eq!(r.remaining(), 3);
/// // then skip over a some bytes...
/// r.advance(3)?;
/// // ... and check that the message is really exhausted.
/// r.should_be_exhausted()?;
/// # Result::Ok(())
/// ```
///
/// You can also use a Reader to extract objects that implement Readable.
/// ```
/// use tor_bytes::{Reader,Result,Readable};
/// use std::net::Ipv4Addr;
/// let msg = [ 0x00, 0x04, 0x7f, 0x00, 0x00, 0x01];
/// let mut r = Reader::from_slice(&msg[..]);
///
/// let tp: u16 = r.extract()?;
/// let ip: Ipv4Addr = r.extract()?;
/// assert_eq!(tp, 4);
/// assert_eq!(ip, Ipv4Addr::LOCALHOST);
/// # Result::Ok(())
/// ```
pub struct Reader<'a> {
// The underlying slice that we're reading from
b: &'a [u8],
@ -48,8 +85,9 @@ impl<'a> Reader<'a> {
self.off += n;
Ok(())
}
/// Check whether this reader is exhausted (out of bytes to
/// return). Return Ok if it is, and Err(Error::ExtraneousBytes)
/// Check whether this reader is exhausted (out of bytes).
///
/// Return Ok if it is, and Err(Error::ExtraneousBytes)
/// if there were extra bytes.
pub fn should_be_exhausted(&self) -> Result<()> {
if self.remaining() != 0 {
@ -80,6 +118,18 @@ impl<'a> Reader<'a> {
///
/// On success, returns Ok(Slice). If there are fewer than n
/// bytes, returns Err(Error::Truncated).
///
/// # Example
/// ```
/// use tor_bytes::{Reader,Result};
/// let m = b"Hello World";
/// let mut r = Reader::from_slice(m);
/// assert_eq!(r.take(5)?, b"Hello");
/// assert_eq!(r.take_u8()?, 0x20);
/// assert_eq!(r.take(5)?, b"World");
/// r.should_be_exhausted()?;
/// # Result::Ok(())
/// ```
pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let b = self.peek(n)?;
self.advance(n)?;
@ -120,6 +170,16 @@ impl<'a> Reader<'a> {
/// On success, returns Ok(Slice), where the slice does not
/// include the terminating byte. Returns Err(Error::Truncated)
/// if we do not find the terminating bytes.
///
/// # Example
/// ```
/// use tor_bytes::{Reader,Result};
/// let m = b"Hello\0wrld";
/// let mut r = Reader::from_slice(m);
/// assert_eq!(r.take_until(0)?, b"Hello");
/// assert_eq!(r.into_rest(), b"wrld");
/// # Result::Ok(())
/// ```
pub fn take_until(&mut self, term: u8) -> Result<&'a [u8]> {
let pos = self.b[self.off..]
.iter()
@ -131,6 +191,8 @@ impl<'a> Reader<'a> {
}
/// Try to decode and remove a Readable from this reader, using its
/// take_from() method.
///
/// On failure, consumes nothing.
pub fn extract<E: Readable>(&mut self) -> Result<E> {
let off_orig = self.off;
let result = E::take_from(self);
@ -143,6 +205,8 @@ impl<'a> Reader<'a> {
/// Try to decode and remove `n` Readables from this reader, using the
/// Readable's take_from() method.
///
/// On failure, consumes nothing.
pub fn extract_n<E: Readable>(&mut self, n: usize) -> Result<Vec<E>> {
let mut result = Vec::new();
let off_orig = self.off;

View File

@ -5,6 +5,37 @@ use crate::WriteableOnce;
///
/// Unlike std::io::Write, this trait's methods are not allowed to
/// fail. It's not for IO.
///
/// Most code will want to use the fact that Vec<u8> implements this trait.
/// To define a new implementation, just define the write_all method.
///
/// # Examples
///
/// You can use a Writer to add bytes explicitly:
/// ```
/// use tor_bytes::Writer;
/// let mut w: Vec<u8> = Vec::new(); // Vec<u8> implements Writer.
/// w.write_u32(0x12345);
/// w.write_u8(0x22);
/// w.write_zeros(3);
/// assert_eq!(w, &[0x00, 0x01, 0x23, 0x45, 0x22, 0x00, 0x00, 0x00]);
/// ```
///
/// You can also use a Writer to encode things that implement the
/// Writeable trait:
///
/// ```
/// use tor_bytes::{Writer,Writeable};
/// let mut w: Vec<u8> = Vec::new();
/// w.write(&4u16); // The unsigned types all implement Writeable.
///
/// // We also provide Writeable implementations for several important types.
/// use std::net::Ipv4Addr;
/// let ip = Ipv4Addr::new(127, 0, 0, 1);
/// w.write(&ip);
///
/// assert_eq!(w, &[0x00, 0x04, 0x7f, 0x00, 0x00, 0x01]);
/// ```
pub trait Writer {
/// Append a slice to the end of this writer.
fn write_all(&mut self, b: &[u8]);
@ -29,19 +60,20 @@ pub trait Writer {
fn write_u128(&mut self, x: u128) {
self.write_all(&x.to_be_bytes())
}
/// Write n bytes to this buffer, all with the value zero.
/// Write n bytes to this writer, all with the value zero.
///
/// NOTE: This implementation is somewhat inefficient, since it allocates
/// a vector. You should probably replace it if you can.
fn write_zeros(&mut self, n: usize) {
// NOTE: This implementation is inefficient. Why do we need to
// allocate it then copy it in? Implementations should specialize.
let v = vec![0u8; n];
self.write_all(&v[..])
}
/// Encode a Writeable object onto this buffer, using its
/// Encode a Writeable object onto this writer, using its
/// write_onto method.
fn write<E: Writeable + ?Sized>(&mut self, e: &E) {
e.write_onto(self)
}
/// Encode a WriteableOnce object onto this buffer, using its
/// Encode a WriteableOnce object onto this writer, using its
/// write_into method.
fn write_and_consume<E: WriteableOnce>(&mut self, e: E) {
e.write_into(self)