Crate scroll [−] [src]
Scroll
_______________
()==( (@==()
'______________'|
| |
| ἀρετή |
__)_____________|
()==( (@==()
'--------------'
Scroll is a library for efficiently and easily reading/writing types from byte arrays. All the builtin types are supported, e.g., u32
, i8
, etc., where the type is specified as a type parameter, or type inferred when possible. In addition, it supports zero-copy reading of string slices, or any other kind of slice. The library can be used in a no_std context as well; the Error type only has the IO
and String
variants if the default features are used, and is no_std
safe when compiled without default features.
There are 3 traits for reading that you can import:
- Pread, for reading (immutable) data at an offset;
- Gread, for reading data at an offset which automatically gets incremented by the size;
- Lread, for reading data out of a
std::io::Read
based interface, e.g., a stream. (Note: only available when compiled withstd
)
Each of these interfaces also have their corresponding writer versions as well, e.g., Pwrite, Gwrite, and Lwrite, respectively.
Most familiar will likely be the Pread
trait (inspired from the C function), which in our case takes an immutable reference to self, an immutable offset to read at, (and optionally a parsing context, more on that later), and then returns the deserialized value.
Because self is immutable, all reads can be performed in parallel and hence are trivially parallelizable.
Example
A simple example demonstrates its flexibility:
use scroll::{ctx, Pread, LE}; let bytes: [u8; 4] = [0xde, 0xad, 0xbe, 0xef]; // reads a u32 out of `b` with the endianness of the host machine, at offset 0, turbofish-style let number: u32 = bytes.pread::<u32>(0).unwrap(); // ...or a byte, with type ascription on the binding. let byte: u8 = bytes.pread(0).unwrap(); //If the type is known another way by the compiler, say reading into a struct field, we can omit the turbofish, and type ascription altogether! // If we want, we can explicitly add a endianness to read with by calling `pread_with`. // The following reads a u32 out of `b` with Big Endian byte order, at offset 0 let be_number: u32 = bytes.pread_with(0, scroll::BE).unwrap(); // or a u16 - specify the type either on the variable or with the beloved turbofish let be_number2 = bytes.pread_with::<u16>(2, scroll::BE).unwrap(); // Scroll has core friendly errors (no allocation). This will have the type `scroll::Error::BadOffset` because it tried to read beyond the bound let byte: scroll::Result<i64> = bytes.pread(0); //If you know the operation can't fail, you can also use the `pread_unsafe` api: let byte: u8 = bytes.pread_unsafe(0, LE); // we can also get _zero copy_ str and byte references from the underlying buffer/bytes using `pread_slice` let slice = bytes.pread_slice::<str>(0, 2).unwrap(); let byte_slice: &[u8] = bytes.pread_slice(0, 2).unwrap(); // Here is an example of parsing a variable length uleb128 custom datatype let leb128_bytes: [u8; 5] = [0xde | 128, 0xad | 128, 0xbe | 128, 0xef | 128, 0x1]; // parses a uleb128 (variable length encoded integer) from the above bytes let uleb128: u64 = leb128_bytes.pread::<scroll::Uleb128>(0).unwrap().into(); assert_eq!(uleb128, 0x01def96deu64); // Scroll is extensible: as long as the type implements `TryWithCtx`, then you can read your type out of the byte array! // We can parse out custom datatypes, or types with lifetimes // if they implement the conversion trait `TryFromCtx`; here we parse a C-style \0 delimited &str (safely) let hello: &[u8] = b"hello_world\0more words"; let hello_world: &str = hello.pread(0).unwrap(); assert_eq!("hello_world", hello_world); // ... and this parses the string if its space separated! let spaces: &[u8] = b"hello world some junk"; let world: &str = spaces.pread_with(6, ctx::SPACE).unwrap(); assert_eq!("world", world);
std::io
API
Scroll can also read/write simple types from a std::io::Read
or std::io::Write
implementor. The built-in numeric types are taken care of for you. If you want to read a custom type, you need to implement the FromCtx (how to parse) and SizeWith (how big the parsed thing will be) traits. You must compile with default features. For example:
use std::io::Cursor; use scroll::Lread; let bytes_ = [0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xef,0xbe,0x00,0x00,]; let mut bytes = Cursor::new(bytes_); // this will bump the cursor's Seek let foo = bytes.lread::<usize>().unwrap(); // ..ditto let bar = bytes.lread::<u32>().unwrap();
Similarly, we can write to anything that implements std::io::Write
quite naturally:
use scroll::{Lwrite, LE, BE}; use std::io::{Write, Cursor}; let mut bytes = [0x0u8; 10]; let mut cursor = Cursor::new(&mut bytes[..]); cursor.write_all(b"hello").unwrap(); cursor.lwrite_with(0xdeadbeef as u32, BE).unwrap(); assert_eq!(cursor.into_inner(), [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xde, 0xad, 0xbe, 0xef, 0x0]);
Advanced Uses
Scroll is designed to be highly configurable - it allows you to implement various context (Ctx
) sensitive traits, which then grants the implementor automatic uses of the Pread
/Gread
and/or Pwrite
/Gwrite
traits.
For example, suppose we have a datatype and we want to specify how to parse or serialize this datatype out of some arbitrary byte buffer. In order to do this, we need to provide a TryFromCtx impl for our datatype.
In particular, if we do this for the [u8]
target, using the convention (usize, YourCtx)
, you will automatically get access to
calling pread_with::<YourDatatype>
on arrays of bytes.
use scroll::{self, ctx, Pread, BE}; struct Data<'a> { name: &'a str, id: u32, } // we could use a `(usize, endian::Scroll)` if we wanted #[derive(Debug, Clone, Copy, Default)] struct DataCtx { pub size: usize, pub endian: scroll::Endian } // note the lifetime specified here impl<'a> ctx::TryFromCtx<'a, (usize, DataCtx)> for Data<'a> { type Error = scroll::Error; // and the lifetime annotation on `&'a [u8]` here fn try_from_ctx (src: &'a [u8], (offset, DataCtx {size, endian}): (usize, DataCtx)) -> Result<Self, Self::Error> { let name = src.pread_slice::<str>(offset, size)?; let id = src.pread_with(offset+size, endian)?; Ok(Data { name: name, id: id }) } } let bytes = scroll::Buffer::new(b"UserName\x01\x02\x03\x04"); let data = bytes.pread_with::<Data>(0, DataCtx { size: 8, endian: BE }).unwrap(); assert_eq!(data.id, 0x01020304); assert_eq!(data.name.to_string(), "UserName".to_string());
Please see the Pread documentation examples
Modules
ctx |
Generic context-aware conversion traits, for automatic downstream extension of |
Structs
Buffer |
A byte buffer which is versed in both the Greater and Lesser arts |
Sleb128 |
An signed leb128 integer |
Uleb128 |
An unsigned leb128 integer |
Enums
Endian |
The endianness (byte order) of a stream of bytes |
Error |
A custom Scroll error |
Constants
BE |
Big Endian byte order context |
LE |
Little Endian byte order context |
LEB128 |
This context instructs the underlying |
NATIVE |
The machine's native byte order |
NETWORK |
Network byte order context |
Traits
Gread |
The Greater Read ( |
Gwrite |
The Greater Write ( |
Lread |
An extension trait to |
Lwrite |
An extension trait to |
Pread |
A very generic, contextual pread interface in Rust. Allows completely parallelized reads, as |
Pwrite |
Writes into |
TryOffsetWith |
Attempt to add an offset for a given |
Type Definitions
Leb128 |
A variable length integer parsing |
Result |