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:

  1. Pread, for reading (immutable) data at an offset;
  2. Gread, for reading data at an offset which automatically gets incremented by the size;
  3. IOread, for reading simple data out of a std::io::Read based interface, e.g., a stream. (Note: only available when compiled with std)

Each of these interfaces also have their corresponding writer versions as well, e.g., Pwrite, Gwrite, and IOwrite, 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.

For most usecases, you can use scroll_derive to annotate your types with derive(Pread, Pwrite, IOread, IOwrite, SizeWith) to automatically add sensible derive defaults, and you should be ready to roll. For more complex usescases, you can implement the conversion traits yourself, see the context module for more information.

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);

// 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!
use scroll::ctx::*;
let spaces: &[u8] = b"hello world some junk";
let world: &str = spaces.pread_with(6, StrCtx::Delimiter(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::IOread;
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.ioread::<u64>().unwrap();
// ..ditto
let bar = bytes.ioread::<u32>().unwrap();

Similarly, we can write to anything that implements std::io::Write quite naturally:

use scroll::{IOwrite, 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.iowrite_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 and/or Pwrite 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, Endian};
 
struct Data<'a> {
  name: &'a str,
  id: u32,
}
 
// note the lifetime specified here
impl<'a> ctx::TryFromCtx<'a, Endian> for Data<'a> {
  type Error = scroll::Error;
  type Size = usize;
  // and the lifetime annotation on `&'a [u8]` here
  fn try_from_ctx (src: &'a [u8], endian: Endian)
    -> Result<(Self, Self::Size), Self::Error> {
    let offset = &mut 0;
    let name = src.gread::<&str>(offset)?;
    let id = src.gread_with(offset, endian)?;
    Ok((Data { name: name, id: id }, *offset))
  }
}
 
let bytes = b"UserName\x00\x01\x02\x03\x04";
let data = bytes.pread_with::<Data>(0, 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 Pread, et. al

Structs

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

NATIVE

The machine's native byte order

NETWORK

Network byte order context

Traits

Cread

Core-read - core, no_std friendly trait for reading basic traits from byte buffers. Cannot fail unless the buffer is too small, in which case an assert fires and the program panics.

Cwrite

Core-write - core, no_std friendly trait for writing basic types into byte buffers. Cannot fail unless the buffer is too small, in which case an assert fires and the program panics. Similar to Cread, if your type implements IntoCtx then you can cwrite(your_type, offset).

IOread

An extension trait to std::io::Read streams; this only deserializes simple types, like u8, i32, f32, usize, etc.

IOwrite

An extension trait to std::io::Write streams; this only serializes simple types, like u8, i32, f32, usize, etc.

Pread

A very generic, contextual pread interface in Rust. Allows completely parallelized reads, as Self is immutable

Pwrite

Writes into Self at an offset of type I using a Ctx

Type Definitions

Result