Module scroll::ctx [−][src]
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
StrCtx | The parsing context for converting a byte sequence to a |
Constants
NULL | A C-style, null terminator based delimiter |
RET | A newline-based delimiter |
SPACE | A space-based delimiter |
TAB | A tab-based delimiter |
Traits
FromCtx | Reads |
IntoCtx | Writes |
MeasureWith | A trait for measuring how large something is; for a byte sequence, it will be its length. |
SizeWith | Gets the size of |
TryFromCtx | Tries to read |
TryIntoCtx | Tries to write |