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