Page
Library
Module
Module type
Parameter
Class
Class type
Source
BinSourceBin is a small library for encoding and decoding information from a buffer (like a bytes or a bigstring). Unlike a parser combinator, Bin cannot decode a stream.
Bin can be used to project values coming from a pre-allocated buffer such as a "framebuffer" (video, ethernet, etc.) or to inject values into it. Bin can be seen as a library for describing (fairly basic) "C-like" types of values that can be injected/projected into/from a particular memory area:
#define PROPTAG_GET_COMMAND_LINE 0x00050001
#define VALUE_LENGTH_RESPONSE (1 << 31)
struct __attribute__((packed)) cmdline {
uint32_t id;
uint32_t value_len;
uint32_t param_len;
uint8 str[2048];
};
struct __attribute__((packed)) property_tag {
uint32_t id;
uint32_t value_len;
uint32_t param_len;
};
extern char _tags;
char *get_cmdline() {
struct cmdline p;
p.id = PROPTAG_GET_COMMAND_LINE;
p.value_len = len - sizeof(struct property_tag);
p.param_len = 2048 & ~VALUE_LENGTH_RESPONSE;
memcpy(&_tags, &p, sizeof(struct cmdline)); // inject
...
}Bin therefore allows you to describe a representation of a serialized value in bytes and to associate with it a function that allows you to obtain an OCaml value such as a record or a variant.
open Bin
type cmdline = {
id: int32
; value_len: int32
; param_len: int32
; cmdline: string
}
let cmdline =
record (fun id value_len param_len -> { id; value_len; param_len })
|+ field neint32 (Fun.const 0x00050001l)
|+ field neint32 (fun t -> t.value_len)
|+ field neint32 (fun t -> t.param_len)
|+ field cstring (fun t -> t.cmdline)
|> sealr
let encode_into tags ?(off = 0) value =
let off = ref off in
Bin.encode_bstr cmdline value tags off (* inject *)Of course, it's not as fast as what we can do in C, but Bin has the advantage of offering a small DSL that allows us to describe these types and go directly to OCaml values, which is generally more pleasant to manipulate with OCaml than to make C stubs.
For performance reasons, it is sometimes preferable to handle functions specialising in one or two arguments rather than applying all the function’s arguments (and repeating internal calculations whenever that first or second argument appears). The Staged module allows you to define a boundary between what should be ‘pre-applied’ and what should be fully applied afterwards.
Decoding and encoding walk a buffer with a cursor. The pos given to decode_bstr, decode and encode_bstr is a reference on the index of the next byte to read or to write, and each of these functions advances it by what it consumed or produced. The same cursor can therefore be used to chain several values:
let dec = Bin.Staged.unstage (Bin.decode_bstr Bin.beuint16) in
let pos = ref Bin.Off.zero in
let a = dec buf pos in (* reads bytes 0 and 1, [pos] becomes 2 *)
let b = dec buf pos in (* reads bytes 2 and 3, [pos] becomes 4 *)
...Two distinct notions are involved, and they have distinct types so that they cannot be mixed up: an Off.t says where a byte is, a Len.t says how many bytes there are. Both are private int: reading one back as an int is the free coercion (x :> int), while going the other way is impossible by accident.
The cursor of a decoder or an encoder: the index of the next byte to read or to write. Start it with ref Off.zero and read it back with (!pos :> int).
A type to describe a binary format.
varint is a representation of an integer value using a sequence of bytes. The number of bytes that is needed depends on the value being represented, with larger magnitudes using more bytes than smaller ones.
prefix t is a representation of a value which size is specified by a header/prefix t.
seq len t is a consecutive sequence of len elements represented by t. It returns an array.
list len t is a consecutive sequence of len elements represented by t. It returns an list.
map t f g describes a value of type 'a via the representation t of another type 'b, by supplying the coercions between them: decoding reads a 'b with t and turns it into an 'a with f, encoding turns the 'a back into a 'b with g and writes it with t.
f and g must be inverse of each other, otherwise encoding a value then decoding it does not give that value back.
type kind = Query | Response
let kind =
map uint8
(function 0 -> Query | _ -> Response)
(function Query -> 0 | Response -> 1)The shape of the encoding does not depend on the value: map is therefore transparent for the decoder, which can still fuse it into its static path. Use it whenever it is enough — see bind otherwise.
bind t prj inj is like map, except that the representation of what remains depends on the value which was just read. It is the combinator for the formats where a header decides how the rest must be interpreted: a length, a tag, a version number.
Decoding reads an 'a with t, then decodes the rest with prj a. Encoding is the mirror image and this is where inj comes in: from the value to write, inj recovers the 'a which describes it, bind writes that 'a with t, and then writes the value itself with prj (inj v). Without inj, the encoder would have no way to know which header to emit.
inj must therefore agree with what prj accepts: for any value v that prj a can decode, inj v must be a. Otherwise the header and the payload disagree and the result cannot be decoded back.
Before reaching for bind, check that a simpler combinator does not already cover the case. A size in bytes is prefix on bytes, a number of elements is prefix on list or seq, a tag which selects a constructor is a variant sealed with sealv, and a format which refers to itself is fix. bind is for what remains: when the value which was read has to be computed with to know what follows.
Here is such a case, a payload whose length is given in 32-bit words (as the IHL of an IPv4 header or the data offset of a TCP one). prefix cannot express it, since it would take the number for a count of bytes:
let payload =
bind uint8
(fun words -> bytes (fixed (words * 4)))
(fun payload -> String.length payload / 4)Note prj is called for every value encoded or decoded, and the representation it returns is interpreted on the fly. Unlike map, bind therefore never takes part in the static (fused) path of the decoder, and size_of_value can only be dynamic on it. Prefer map when the shape of the encoding does not depend on the value.
See fix for a recursive format, where bind is what lets the recursion stop on a value read from the input.
let* is the binding operator of bind: let* t = (x, f, g) in expr is let t = bind x f g in expr. As with (let+), t is the representation, not a value.
It exists to keep a large bind readable: name the two functions, then assemble them.
let payload =
let prj words = bytes (fixed (words * 4)) in
let inj payload = String.length payload / 4 in
let* t = (uint8, prj, inj) in
tOn a bind as small as this one the plain function reads just as well; see fix for the shape which motivates the operator.
The type for fields holding values of type 'b and belonging to a record of type 'a.
field n t g is the representation of the field called n of type t with getter g. For instance:
type t = { foo: string }
let foo = field cstring (fun t -> t.foo)r |+ f is the open record r augmented with the field f.
sealr r seals the open record r.
type t = Foo | Bar of string
let t =
variant (fun foo bar -> function Foo -> foo | Bar s -> bar s)
|~ case0 Foo
|~ case1 cstring (fun x -> Bar x)
|> sealvThe type for representing variant cases of type 'a with patterns of type 'b.
The type for representing patterns for a variant of type 'a.
case0 v is a representation of a variant constructor v with no arguments. For instance:
type t = Foo
let foo = case0 Foocase1 n t c is a representation of a variant constructor c with an argument of type t. For instances:
type t = Foo of string
let foo = case1 cstring (fun s -> Foo s)v |~ c is the open variant v augmented with the case c.
sealv v seals the open variant v.
val (|*) :
(bits_base * bit_order * (('c, 'd -> 'e) bit_fields -> 'f)) ->
('c, 'd) bit_field ->
bits_base * bit_order * (('c, 'e) bit_fields -> 'f)val sealb :
(bits_base
* bit_order
* (('a, 'a) bit_fields ->
string * 'b * ('c, 'b) bit_fields)) ->
'c tfix @@ fun t -> ... computes the fixpoint of the given function and runs the resultant codec. The argument that fn receives is the result of fix fn, which fn must use, paradoxically, to define fix fn.
fix is useful when constructing codecs for inductively-defined types such as sequences, trees, etc. Consider for example the codec of a QNAME (see RFC1035 § 4.1.2). They describe it as:
> a domain name represented as a sequence of labels, where each label consists of a length octet followed by that number of octets. The domain name terminates with the zero length octet for the null label of the root.
Here is the equivalent using Bin:
type name = string list
let qname =
let open Bin in
fix @@ fun qname ->
let prj = function
| 0 -> const []
| len ->
record (fun label rest -> label :: rest)
|+ field (bytes (fixed len)) List.hd
|+ field qname List.tl
|> sealr
in
let inj = function [] -> 0 | label :: _ -> String.length label in
bind uint8 prj injdecode_bstr repr is the binary decoder for values of type repr on Bstr.t.
len is the window the decoder is allowed to read within: len byte(s) counted from the cursor. It defaults to the rest of the buffer, and it is what rest means and what bounds a delim search. Such a window is what a slice describes, so decoding from one is:
let decode = Bin.Staged.unstage (Bin.decode_bstr t) in
let { Slice.buf; off; len } = slice in
let pos = ref (Bin.Off.v off) in
let v = decode buf ~len pos in
...NOTE: that len is counted from the cursor, not from the beginning of the buffer.
decode repr is the binary decoder for values of type repr on string. len bounds the decoder as in decode_bstr.
encode_bstr repr is the binary encoder for value of type repr on Bstr.t. As for decode_bstr, len is the window the encoder is allowed to write within: len byte(s) counted from the cursor, the rest of the buffer by default. Writing into a slice is therefore:
let encode = Bin.Staged.unstage (Bin.encode_bstr t) in
let { Slice.buf; off; len } = slice in
encode v buf ~len (ref (Bin.Off.v off))to_string repr is a function which returns the string representation of values of type repr.