From e3bd7e70911c1afb5ad31d2368ed94961a973f62 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 31 Mar 2008 22:52:17 +0000 Subject: [PATCH] Bitmatch syntax extension, working on bits and bitstrings. --- .cvsignore | 5 + .depend | 2 + Makefile | 66 ++++++++ bitmatch.ml | 48 ++++++ bitmatch.mli | 23 +++ examples/.cvsignore | 5 + examples/ipv4_header.ml | 78 +++++++++ examples/make_ipv4_header.ml | 45 ++++++ pa_bitmatch.ml | 373 +++++++++++++++++++++++++++++++++++++++++++ tests/.cvsignore | 7 + tests/01_load.ml | 5 + tests/05_bits.ml | 22 +++ 12 files changed, 679 insertions(+) create mode 100644 .cvsignore create mode 100644 .depend create mode 100644 Makefile create mode 100644 bitmatch.ml create mode 100644 bitmatch.mli create mode 100644 examples/.cvsignore create mode 100644 examples/ipv4_header.ml create mode 100644 examples/make_ipv4_header.ml create mode 100644 pa_bitmatch.ml create mode 100644 tests/.cvsignore create mode 100644 tests/01_load.ml create mode 100644 tests/05_bits.ml diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..6ccf209 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,5 @@ +*.cmi +*.cmo +*.cmx +*.cma +*.cmxa \ No newline at end of file diff --git a/.depend b/.depend new file mode 100644 index 0000000..4797d7b --- /dev/null +++ b/.depend @@ -0,0 +1,2 @@ +bitmatch.cmo: bitmatch.cmi +bitmatch.cmx: bitmatch.cmi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..348e320 --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +# $Id: Makefile,v 1.1 2008-03-31 22:52:17 rjones Exp $ + +OCAMLFIND = ocamlfind +OCAMLMKLIB = ocamlmklib + + +OCAMLCFLAGS = -g +OCAMLCPACKAGES = +OCAMLOPTFLAGS = +OCAMLOPTPACKAGES = + +EXAMPLES := $(wildcard examples/*.ml) + +TESTS := $(patsubst %.ml,%,$(wildcard tests/*.ml)) + +all: pa_bitmatch.cmo bitmatch.cma bitmatch.cmxa + +pa_bitmatch.cmo: pa_bitmatch.ml + ocamlfind ocamlc -I +camlp4 camlp4lib.cma -pp camlp4of.opt -c $< -o $@ + +bitmatch.cma: bitmatch.cmo + $(OCAMLFIND) ocamlc -a -o $@ $^ + +bitmatch.cmxa: bitmatch.cmx + $(OCAMLFIND) ocamlopt -a -o $@ $^ + +test: + @for f in $(TESTS); do \ + echo Test: $$f; \ + $(OCAMLFIND) ocamlc -pp "camlp4o pa_bitmatch.cmo" \ + -I . bitmatch.cma $$f.ml -o $$f; \ + $$f; \ + done + +print-tests: pa_bitmatch.cmo + @for f in $(TESTS); do \ + echo Test: $$f.ml; \ + camlp4o pa_bitmatch.cmo -printer pr_o.cmo $$f.ml; \ + done + +print-examples: pa_bitmatch.cmo + @for f in $(EXAMPLES); do \ + echo Example: $$f; \ + camlp4o pa_bitmatch.cmo -printer pr_o.cmo $$f; \ + done + +.mli.cmi: + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) $(OCAMLCPACKAGES) -c $< +.ml.cmo: + $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) $(OCAMLCPACKAGES) -c $< +.ml.cmx: + $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) $(OCAMLOPTPACKAGES) -c $< + +depend: .depend + +.depend: bitmatch.ml bitmatch.mli + rm -f .depend + $(OCAMLFIND) ocamldep $(OCAMLCPACKAGES) $^ > $@ + +ifeq ($(wildcard .depend),.depend) +include .depend +endif + +.PHONY: depend dist check-manifest dpkg doc print-examples print-tests test + +.SUFFIXES: .cmo .cmi .cmx .ml .mli .mll diff --git a/bitmatch.ml b/bitmatch.ml new file mode 100644 index 0000000..3135c89 --- /dev/null +++ b/bitmatch.ml @@ -0,0 +1,48 @@ +(* Bitmatch library. + * $Id: bitmatch.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +(* A bitstring is simply the data itself (as a string), and the + * bitoffset and the bitlength within the string. Note offset/length + * are counted in bits, not bytes. + *) +type bitstring = string * int * int + +(* Functions to create and load bitstrings. *) +let empty_bitstring = "", 0, 0 + +let make_bitstring len c = String.make ((len+7) lsr 3) c, 0, len + +let create_bitstring len = make_bitstring len '\000' + +let bitstring_of_chan chan = + let tmpsize = 16384 in + let buf = Buffer.create tmpsize in + let tmp = String.create tmpsize in + let n = ref 0 in + while n := input chan tmp 0 tmpsize; !n > 0 do + Buffer.add_substring buf tmp 0 !n; + done; + Buffer.contents buf, 0, Buffer.length buf lsl 3 + +let bitstring_of_file fname = + let chan = open_in_bin fname in + let bs = bitstring_of_chan chan in + close_in chan; + bs + +(* Extraction functions (internal: called from the generated macros, + * and the parameters should have been checked for sanity already). + *) +let extract_bitstring data off len flen = + (data, off, flen), off+flen, len-flen + +let extract_remainder data off len = + (data, off, len), off+len, 0 + +(* Extract and convert to numeric. *) +let extract_bit data off len _ = (* final param is always 1 *) + let byteoff = off lsr 3 in + let bitmask = 1 lsl (7 - (off land 7)) in + let b = Char.code data.[byteoff] land bitmask <> 0 in + b, off+1, len-1 diff --git a/bitmatch.mli b/bitmatch.mli new file mode 100644 index 0000000..7760d5a --- /dev/null +++ b/bitmatch.mli @@ -0,0 +1,23 @@ +(* Bitmatch library. + * $Id: bitmatch.mli,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +type bitstring = string * int * int + +val empty_bitstring : bitstring + +val create_bitstring : int -> bitstring + +val make_bitstring : int -> char -> bitstring + +val bitstring_of_chan : in_channel -> bitstring + +val bitstring_of_file : string -> bitstring + +(**/**) + +val extract_bitstring : string -> int -> int -> int -> bitstring * int * int + +val extract_remainder : string -> int -> int -> bitstring * int * int + +val extract_bit : string -> int -> int -> int -> bool * int * int diff --git a/examples/.cvsignore b/examples/.cvsignore new file mode 100644 index 0000000..6ccf209 --- /dev/null +++ b/examples/.cvsignore @@ -0,0 +1,5 @@ +*.cmi +*.cmo +*.cmx +*.cma +*.cmxa \ No newline at end of file diff --git a/examples/ipv4_header.ml b/examples/ipv4_header.ml new file mode 100644 index 0000000..c5d6945 --- /dev/null +++ b/examples/ipv4_header.ml @@ -0,0 +1,78 @@ +(* Parse and display an IPv4 header from a file. + * $Id: ipv4_header.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +open Printf + +let header = Bitmatch.bitstring_of_file "ipv4_header.dat" + +let () = + bitmatch header with + | version : 4; hdrlen : 4; tos : 8; length : 16; + identification : 16; flags : 3; fragoffset : 13; + ttl : 8; protocol : 8; checksum : 16; + source : 32; + dest : 32; + options : (hdrlen-5)*32 : bitstring; + payload : -1 : bitstring + when version = 4 -> + + printf "IPv%d:\n" version; + printf " header length: %d * 32 bit words\n" hdrlen; + printf " type of service: %d\n" tos; + printf " packet length: %d bytes\n" length; + printf " identification: %d\n" identification; + printf " flags: %d\n" flags; + printf " fragment offset: %d\n" fragoffset; + printf " ttl: %d\n" ttl; + printf " protocol: %d\n" protocol; + printf " checksum: %d\n" checksum; + printf " source: %lx dest: %lx\n" source dest; + printf " header options + padding:\n"; + Bitmatch.hexdump_bitstring stdout options; + printf " packet payload:\n"; + Bitmatch.hexdump_bitstring stdout payload + + | version : 4 -> + eprintf "cannot parse IP version %d\n" version + + | _ as header -> + eprintf "data is smaller than one nibble:\n"; + Bitmatch.hexdump_bitstring stderr header + + +(* converted into: + + let (data, off, len) = header in + let result = ref None in + try + if len >= 4 then ( + let version, off, len = Bitmatch.extract_unsigned_be data off len 4 in + if len >= 4 then ( + let hdrlen, off, len = Bitmatch.extract_unsigned_be data off len 4 in + (* ... *) + if (hdrlen-5)*32 >= 0 && len >= (hdrlen-5)*32 then ( + let options, off, len = + Bitmatch.extract_bitstring data off len ((hdrlen-5)*32) in + let payload, off, len = + Bitmatch.extract_remainder data off len in + + if version = 4 then ( + ... + raise Exit + ) + ) + ) + ) + if len >= 4 then ( + let version, off, len = Bitmatch.extract_unsigned_be data off len 4 in + ...; + raise Exit + ) + ... + with Exit -> (); + match !result with + | Some x -> x + | None -> raise Match_failure _loc + +*) diff --git a/examples/make_ipv4_header.ml b/examples/make_ipv4_header.ml new file mode 100644 index 0000000..55ba2e1 --- /dev/null +++ b/examples/make_ipv4_header.ml @@ -0,0 +1,45 @@ +(* Create an IPv4 header. + * $Id: make_ipv4_header.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +open Printf + +let version = 4 +let hdrlen = 5 (* no options *) +let tos = 16 +let length = 64 (* total packet length *) +let identification = 0 +let flags = 0 +let fragoffset = 0 +let ttl = 255 +let protocol = 17 (* UDP *) +let checksum = 0 +let source = 0xc0a80202 (* 192.168.2.2 *) +let dest = 0xc0a80201 (* 192.168.2.1 *) +let options = Bitmatch.empty_bitstring +let payload_length = (length - hdrlen*4) * 8 +let payload = Bitmatch.create_bitstring payload_length + +let header = + <| version : 4; hdrlen : 4; tos : 8; length : 16; + identification : 16; flags : 3; fragoffset : 13; + ttl : 8; protocol : 8; checksum : 16; + source : 32; + dest : 32; + options : -1, bitstring; + payload : payload_length, bitstring |> + +(* + generates: + + let header = Bitmatch.join_bitstrings [ + Bitmatch.create_unsigned_be version 4; + Bitmatch.create_unsigned_be hdrlen 4; (* etc. *) + options; + Bitmatch.check_bitstring_length payload payload_length + ] + + which can throw an exception if values are out of range. +*) + +let () = Bitmatch.file_of_bitstring header "ipv4_header_out.dat" diff --git a/pa_bitmatch.ml b/pa_bitmatch.ml new file mode 100644 index 0000000..2c5a6de --- /dev/null +++ b/pa_bitmatch.ml @@ -0,0 +1,373 @@ +(* Bitmatch syntax extension. + * $Id: pa_bitmatch.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +open Printf + +open Camlp4.PreCast +open Syntax +open Ast + +type m = Fields of f list (* field ; field -> ... *) + | Bind of string option (* _ -> ... *) +and f = { + ident : string; (* field name *) + flen : expr; (* length in bits, may be non-const *) + endian : endian; (* endianness *) + signed : bool; (* true if signed, false if unsigned *) + t : t; (* type *) +} +and endian = BigEndian | LittleEndian | NativeEndian +and t = Int | Bitstring + +(* Generate a fresh, unique symbol each time called. *) +let gensym = + let i = ref 1000 in + fun name -> + incr i; let i = !i in + sprintf "__pabitmatch_%s_%d" name i + +(* Deal with the qualifiers which appear for a field. *) +let output_field _loc name flen qs = + let endian, signed, t = + match qs with + | None -> (None, None, None) + | Some qs -> + List.fold_left ( + fun (endian, signed, t) q -> + match q with + | "bigendian" -> + if endian <> None then + Loc.raise _loc (Failure "an endian flag has been set already") + else ( + let endian = Some BigEndian in + (endian, signed, t) + ) + | "littleendian" -> + if endian <> None then + Loc.raise _loc (Failure "an endian flag has been set already") + else ( + let endian = Some LittleEndian in + (endian, signed, t) + ) + | "nativeendian" -> + if endian <> None then + Loc.raise _loc (Failure "an endian flag has been set already") + else ( + let endian = Some NativeEndian in + (endian, signed, t) + ) + | "signed" -> + if signed <> None then + Loc.raise _loc (Failure "a signed flag has been set already") + else ( + let signed = Some true in + (endian, signed, t) + ) + | "unsigned" -> + if signed <> None then + Loc.raise _loc (Failure "a signed flag has been set already") + else ( + let signed = Some false in + (endian, signed, t) + ) + | "int" -> + if t <> None then + Loc.raise _loc (Failure "a type flag has been set already") + else ( + let t = Some Int in + (endian, signed, t) + ) + | "bitstring" -> + if t <> None then + Loc.raise _loc (Failure "a type flag has been set already") + else ( + let t = Some Bitstring in + (endian, signed, t) + ) + | s -> + Loc.raise _loc (Failure (s ^ ": unknown qualifier")) + ) (None, None, None) qs in + + (* If type is set to bitstring then endianness and signedness + * qualifiers are meaningless and must not be set. + *) + if t = Some Bitstring && (endian <> None || signed <> None) then + Loc.raise _loc ( + Failure "bitstring type and endian or signed qualifiers cannot be mixed" + ); + + (* Default endianness, signedness, type. *) + let endian = match endian with None -> BigEndian | Some e -> e in + let signed = match signed with None -> false | Some s -> s in + let t = match t with None -> Int | Some t -> t in + + { + ident = name; + flen = flen; + endian = endian; + signed = signed; + t = t; + } + +(* Generate the code for a bitmatch statement. '_loc' is the + * location, 'bs' is the bitstring parameter, 'cases' are + * the list of cases to test against. + *) +let output_bitmatch _loc bs cases = + let data = gensym "data" and off = gensym "off" and len = gensym "len" in + let result = gensym "result" in + + (* This generates the field extraction code for each + * field a single case. Each field must be wider than + * the minimum permitted for the type and there must be + * enough remaining data in the bitstring to satisfy it. + * As we go through the fields, symbols 'data', 'off' and 'len' + * track our position and remaining length in the bitstring. + * + * The whole thing is a lot of nested 'if' statements. Code + * is generated from the inner-most (last) field outwards. + *) + let rec output_field_extraction inner = function + | [] -> inner + | {ident=ident; flen=flen; endian=endian; signed=signed; t=t} :: fields -> + (* If length an integer constant? If so, what is it? This + * is very simple-minded and only detects simple constants. + *) + let flen_is_const = + match flen with + | <:expr< $int:i$ >> -> Some (int_of_string i) + | _ -> None in + + let name_of_int_extract_const = function + | (1, _, _) -> "extract_bit" + | ((2|3|4|5|6|7), _, false) -> "extract_char_unsigned" + | ((2|3|4|5|6|7), _, true) -> "extract_char_signed" + | (i, BigEndian, false) when i <= 31 -> "extract_int_be_unsigned" + | (i, BigEndian, true) when i <= 31 -> "extract_int_be_signed" + | (i, LittleEndian, false) when i <= 31 -> "extract_int_le_unsigned" + | (i, LittleEndian, true) when i <= 31 -> "extract_int_le_signed" + | (i, NativeEndian, false) when i <= 31 -> "extract_int_ne_unsigned" + | (i, NativeEndian, true) when i <= 31 -> "extract_int_ne_signed" + | (32, BigEndian, false) -> "extract_int32_be_unsigned" + | (32, BigEndian, true) -> "extract_int32_be_signed" + | (32, LittleEndian, false) -> "extract_int32_le_unsigned" + | (32, LittleEndian, true) -> "extract_int32_le_signed" + | (32, NativeEndian, false) -> "extract_int32_ne_unsigned" + | (32, NativeEndian, true) -> "extract_int32_ne_signed" + | (_, BigEndian, false) -> "extract_int64_be_unsigned" + | (_, BigEndian, true) -> "extract_int64_be_signed" + | (_, LittleEndian, false) -> "extract_int64_le_unsigned" + | (_, LittleEndian, true) -> "extract_int64_le_signed" + | (_, NativeEndian, false) -> "extract_int64_ne_unsigned" + | (_, NativeEndian, true) -> "extract_int64_ne_signed" + in + let name_of_int_extract = function + (* XXX As an enhancement we should allow users to + * specify that a field length can fit into a char/int/int32 + * (of course, this would have to be checked at runtime). + *) + | (BigEndian, false) -> "extract_int64_be_unsigned" + | (BigEndian, true) -> "extract_int64_be_signed" + | (LittleEndian, false) -> "extract_int64_le_unsigned" + | (LittleEndian, true) -> "extract_int64_le_signed" + | (NativeEndian, false) -> "extract_int64_ne_unsigned" + | (NativeEndian, true) -> "extract_int64_ne_signed" + in + + let expr = + match t, flen_is_const with + (* Common case: int field, constant flen *) + | Int, Some i when i > 0 && i <= 64 -> + let extract_func = name_of_int_extract_const (i,endian,signed) in + <:expr< + if $lid:len$ >= $flen$ then ( + let $lid:ident$, $lid:off$, $lid:len$ = + Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$ + $flen$ in + $inner$ + ) + >> + + | Int, Some _ -> + Loc.raise _loc (Failure "length of int field must be [1..64]") + + (* Int field, non-const flen. We have to test the range of + * the field at runtime. If outside the range it's a no-match + * (not an error). + *) + | Int, None -> + let extract_func = name_of_int_extract (endian,signed) in + <:expr< + if $flen$ >= 1 && $flen$ <= 64 && $flen$ >= $lid:len$ then ( + let $lid:ident$, $lid:off$, $lid:len$ = + Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$ + $flen$ in + $inner$ + ) + >> + + (* Bitstring, constant flen >= 0. *) + | Bitstring, Some i when i >= 0 -> + <:expr< + if $lid:len$ >= $flen$ then ( + let $lid:ident$, $lid:off$, $lid:len$ = + Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$ + $flen$ in + $inner$ + ) + >> + + (* Bitstring, constant flen = -1, means consume all the + * rest of the input. + *) + | Bitstring, Some i when i = -1 -> + <:expr< + let $lid:ident$, $lid:off$, $lid:len$ = + Bitmatch.extract_remainder $lid:data$ $lid:off$ $lid:len$ in + $inner$ + >> + + | Bitstring, Some _ -> + Loc.raise _loc (Failure "length of bitstring must be >= 0 or the special value -1") + + (* Bitstring field, non-const flen. We check the flen is >= 0 + * (-1 is not allowed here) at runtime. + *) + | Bitstring, None -> + <:expr< + if $flen$ >= 0 && $lid:len$ >= $flen$ then ( + let $lid:ident$, $lid:off$, $lid:len$ = + Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$ + $flen$ in + $inner$ + ) + >> + in + + output_field_extraction expr fields + in + + (* Convert each case in the match. *) + let cases = List.map ( + function + (* field : len ; field : len when .. -> ..*) + | (Fields fields, Some whenclause, code) -> + let inner = + <:expr< + if $whenclause$ then ( + $lid:result$ := Some ($code$); + raise Exit + ) + >> in + output_field_extraction inner (List.rev fields) + + (* field : len ; field : len -> ... *) + | (Fields fields, None, code) -> + let inner = + <:expr< + $lid:result$ := Some ($code$); + raise Exit + >> in + output_field_extraction inner (List.rev fields) + + (* _ as name when ... -> ... *) + | (Bind (Some name), Some whenclause, code) -> + <:expr< + let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in + if $whenclause$ then ( + $lid:result$ := Some ($code$); + raise Exit + ) + >> + + (* _ as name -> ... *) + | (Bind (Some name), None, code) -> + <:expr< + let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in + $lid:result$ := Some ($code$); + raise Exit + >> + + (* _ when ... -> ... *) + | (Bind None, Some whenclause, code) -> + <:expr< + if $whenclause$ then ( + $lid:result$ := Some ($code$); + raise Exit + ) + >> + + (* _ -> ... *) + | (Bind None, None, code) -> + <:expr< + $lid:result$ := Some ($code$); + raise Exit + >> + + ) cases in + + let cases = + List.fold_right (fun case base -> <:expr< $case$ ; $base$ >>) + cases <:expr< () >> in + + (* The final code just wraps the list of cases in a + * try/with construct so that each case is tried in + * turn until one case matches (that case sets 'result' + * and raises 'Exit' to leave the whole statement). + * If result isn't set by the end then we will raise + * Match_failure with the location of the bitmatch + * statement in the original code. + *) + let loc_fname = Loc.file_name _loc in + let loc_line = string_of_int (Loc.start_line _loc) in + let loc_char = string_of_int (Loc.start_off _loc - Loc.start_bol _loc) in + + <:expr< + let ($lid:data$, $lid:off$, $lid:len$) = $bs$ in + let $lid:result$ = ref None in + (try + $cases$ + with Exit -> ()); + match ! $lid:result$ with + | Some x -> x + | None -> raise (Match_failure ($str:loc_fname$, + $int:loc_line$, $int:loc_char$)) + >> + +EXTEND Gram + GLOBAL: expr; + + qualifiers: [ + [ LIST0 [ q = LIDENT -> q ] SEP "," ] + ]; + + field: [ + [ name = LIDENT; ":"; len = expr LEVEL "top"; + qs = OPT [ ":"; qs = qualifiers -> qs ] -> + output_field _loc name len qs + ] + ]; + + match_case: [ + [ fields = LIST0 field SEP ";"; + w = OPT [ "when"; e = expr -> e ]; "->"; + code = expr -> + (Fields fields, w, code) + ] + | [ "_"; + bind = OPT [ "as"; name = LIDENT -> name ]; + w = OPT [ "when"; e = expr -> e ]; "->"; + code = expr -> + (Bind bind, w, code) + ] + ]; + + expr: LEVEL ";" [ + [ "bitmatch"; bs = expr; "with"; OPT "|"; + cases = LIST1 match_case SEP "|" -> + output_bitmatch _loc bs cases + ] + ]; + +END diff --git a/tests/.cvsignore b/tests/.cvsignore new file mode 100644 index 0000000..af0d569 --- /dev/null +++ b/tests/.cvsignore @@ -0,0 +1,7 @@ +*.cmi +*.cmo +*.cmx +*.cma +*.cmxa +01_load +05_bits diff --git a/tests/01_load.ml b/tests/01_load.ml new file mode 100644 index 0000000..64374bf --- /dev/null +++ b/tests/01_load.ml @@ -0,0 +1,5 @@ +(* Just check that the extension and library load. + * $Id: 01_load.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +let _ = Bitmatch.extract_bit diff --git a/tests/05_bits.ml b/tests/05_bits.ml new file mode 100644 index 0000000..df59527 --- /dev/null +++ b/tests/05_bits.ml @@ -0,0 +1,22 @@ +(* Extract bits. + * $Id: 05_bits.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $ + *) + +open Printf + +let bits = Bitmatch.make_bitstring 24 '\x5a' (* makes the string 0x5a5a5a *) + +let () = + bitmatch bits with + | b0 : 1; b1 : 1; b2 : 1; b3 : 1; b4 : 1; b5 : 1; b6 : 1; b7 : 1; + b8 : 1; b9 : 1; b10 : 1; b11 : 1; b12 : 1; b13 : 1; b14 : 1; b15 : 1; + b16 : 1; b17 : 1; b18 : 1; b19 : 1; b20 : 1; b21 : 1; b22 : 1; b23 : 1; + rest : -1 : bitstring -> + assert (not b0 && b1 && not b2 && b3 && b4 && not b5 && b6 && not b7); + assert (not b8 && b9 && not b10 && b11 && b12 && not b13 && b14 && not b15); + assert (not b16 && b17 && not b18 && b19 && b20 && not b21 && b22 && not b23); + let _, off, len = rest in + assert (off = 24 && len = 0) + + | _ -> + failwith "error: did not match\n" -- 1.8.3.1