Module scroll::ctx

source ·
Expand description

Generic context-aware conversion traits, for automatic downstream extension of Pread, et. al

The context traits are arguably the center piece of the scroll crate. In simple terms they define how to actually read and write, respectively, a data type from a container, being able to take context into account.

Reading

Types implementing TryFromCtx and it’s infallible cousin FromCtx allow a user of Pread::pread or respectively Cread::cread and IOread::ioread to read that data type from a data source one of the *read traits has been implemented for.

Implementations of TryFromCtx specify a source (called This) and an Error type for failed reads. The source defines the kind of container the type can be read from, and defaults to [u8] for any type that implements AsRef<[u8]>.

FromCtx is slightly more restricted; it requires the implementer to use [u8] as source and never fail, and thus does not have an Error type.

Types chosen here are of relevance to Pread implementations; of course only a container which can produce a source of the type This can be used to read a TryFromCtx requiring it and the Error type returned in Err of Pread::pread’s Result.

Writing

TryIntoCtx and the infallible IntoCtx work similarly to the above traits, allowing Pwrite::pwrite or respectively Cwrite::cwrite and IOwrite::iowrite to write data into a byte sink for which one of the *write traits has been implemented for.

IntoCtx is similarly restricted as FromCtx is to TryFromCtx. And equally the types chosen affect usable Pwrite implementation.

Context

Each of the traits passes along a Ctx to the marshalling logic. This context type contains any additional information that may be required to successfully parse or write the data: Examples would be endianness to use, field lengths of a serialized struct, or delimiters to use when reading/writing &str. The context type can be any type but must derive Copy. In addition if you want to use the *read-methods instead of the *read_with ones you must also implement default::Default.

Example

Let’s expand on the previous example.

use scroll::{self, ctx, Pread, Endian};
use scroll::ctx::StrCtx;

#[derive(Copy, Clone, PartialEq, Eq)]
enum FieldSize {
    U32,
    U64
}

// Our custom context type. As said above it has to derive Copy.
#[derive(Copy, Clone)]
struct Context {
    fieldsize: FieldSize,
    endianess: Endian,
}

// Our custom data type
struct Data<'b> {
  // These u64 are encoded either as 32-bit or 64-bit wide ints. Which one it is is defined in
  // the Context.
  // Also, let's imagine they have a strict relationship: A < B < C otherwise the struct is
  // invalid.
  field_a: u64,
  field_b: u64,
  field_c: u64,

  // Both of these are marshalled with a prefixed length.
  name: &'b str,
  value: &'b [u8],
}

#[derive(Debug)]
enum Error {
    // We'll return this custom error if the field* relationship doesn't hold
    BadFieldMatchup,
    Scroll(scroll::Error),
}

impl<'a> ctx::TryFromCtx<'a, Context> for Data<'a> {
  type Error = Error;

  // Using the explicit lifetime specification again you ensure that read data doesn't outlife
  // its source buffer without having to resort to copying.
  fn try_from_ctx (src: &'a [u8], ctx: Context)
    // the `usize` returned here is the amount of bytes read.
    -> Result<(Self, usize), Self::Error>
  {
    // The offset counter; gread and gread_with increment a given counter automatically so we
    // don't have to manually care.
    let offset = &mut 0;

    let field_a;
    let field_b;
    let field_c;

    // Switch the amount of bytes read depending on the parsing context
    if ctx.fieldsize == FieldSize::U32 {
      field_a = src.gread_with::<u32>(offset, ctx.endianess)? as u64;
      field_b = src.gread_with::<u32>(offset, ctx.endianess)? as u64;
      field_c = src.gread_with::<u32>(offset, ctx.endianess)? as u64;
    } else {
      field_a = src.gread_with::<u64>(offset, ctx.endianess)?;
      field_b = src.gread_with::<u64>(offset, ctx.endianess)?;
      field_c = src.gread_with::<u64>(offset, ctx.endianess)?;
    }

    // You can use type ascribition or turbofish operators, whichever you prefer.
    let namelen = src.gread_with::<u16>(offset, ctx.endianess)? as usize;
    let name: &str = src.gread_with(offset, scroll::ctx::StrCtx::Length(namelen))?;

    let vallen = src.gread_with::<u16>(offset, ctx.endianess)? as usize;
    let value = &src[*offset..(*offset+vallen)];

    // Let's sanity check those fields, shall we?
    if ! (field_a < field_b && field_b < field_c) {
      return Err(Error::BadFieldMatchup);
    }

    Ok((Data { field_a, field_b, field_c, name, value }, *offset))
  }
}

// In lieu of a complex byte buffer we hearken back to the venerable &[u8]; do note however
// that the implementation of TryFromCtx did not specify such. In fact any type that implements
// Pread can now read `Data` as it implements TryFromCtx.
let bytes = b"\x00\x02\x03\x04\x01\x02\x03\x04\xde\xad\xbe\xef\x00\x08UserName\x00\x02\xCA\xFE";

// We define an appropiate context, and get going
let contextA = Context {
    fieldsize: FieldSize::U32,
    endianess: Endian::Big,
};
let data: Data = bytes.pread_with(0, contextA).unwrap();

assert_eq!(data.field_a, 0x00020304);
assert_eq!(data.field_b, 0x01020304);
assert_eq!(data.field_c, 0xdeadbeef);
assert_eq!(data.name, "UserName");
assert_eq!(data.value, [0xCA, 0xFE]);

// Here we have a context with a different FieldSize, changing parsing information at runtime.
let contextB = Context {
    fieldsize: FieldSize::U64,
    endianess: Endian::Big,
};

// Which will of course error with a malformed input for the context
let err: Result<Data, Error> = bytes.pread_with(0, contextB);
assert!(err.is_err());

let bytes_long = [0x00,0x00,0x00,0x00,0x00,0x02,0x03,0x04,0x00,0x00,0x00,0x00,0x01,0x02,0x03,
                  0x04,0x00,0x00,0x00,0x00,0xde,0xad,0xbe,0xef,0x00,0x08,0x55,0x73,0x65,0x72,
                  0x4e,0x61,0x6d,0x65,0x00,0x02,0xCA,0xFE];

let data: Data = bytes_long.pread_with(0, contextB).unwrap();

assert_eq!(data.field_a, 0x00020304);
assert_eq!(data.field_b, 0x01020304);
assert_eq!(data.field_c, 0xdeadbeef);
assert_eq!(data.name, "UserName");
assert_eq!(data.value, [0xCA, 0xFE]);

// Ergonomic conversion, not relevant really.
use std::convert::From;
impl From<scroll::Error> for Error {
  fn from(error: scroll::Error) -> Error {
    Error::Scroll(error)
  }
}

Enums

  • The parsing context for converting a byte sequence to a &str

Constants

  • A C-style, null terminator based delimiter
  • A newline-based delimiter
  • A space-based delimiter
  • A tab-based delimiter

Traits

  • Reads Self from This using the context Ctx; must not fail
  • Writes Self into This using the context Ctx
  • A trait for measuring how large something is; for a byte sequence, it will be its length.
  • Gets the size of Self with a Ctx, and in Self::Units. Implementors can then call Gread related functions
  • Tries to read Self from This using the context Ctx
  • Tries to write Self into This using the context Ctx To implement writing into an arbitrary byte buffer, implement TryIntoCtx