tor-bytes: add the missing documentation
This commit is contained in:
parent
641576aa44
commit
671a266e13
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue