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
fromThis
using the contextCtx
; must not fail - Writes
Self
intoThis
using the contextCtx
- A trait for measuring how large something is; for a byte sequence, it will be its length.
- Gets the size of
Self
with aCtx
, and inSelf::Units
. Implementors can then callGread
related functions - Tries to read
Self
fromThis
using the contextCtx
- Tries to write
Self
intoThis
using the contextCtx
To implement writing into an arbitrary byte buffer, implementTryIntoCtx