package wire

  1. Overview
  2. Docs

Source file eval.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
(* Top-level expression evaluator.

   The full struct-internal expression machinery (with [Ref]/[Sizeof_this]/
   [Field_pos] resolution against bound fields) lives in [Codec] as the
   [compile_int_arr] family, which compiles expressions to [int array]
   accessors at codec construction. This module is the residual evaluator
   for the [Wire.of_string]/[Wire.encode] paths, which only ever
   evaluate expressions in [empty]: no field references, no cross-field
   dependencies. *)

open Types

(* The binding is mutable so a per-byte refinement can be decided in place: a
   fresh association per byte would make scanning a [byte_array_where] allocate
   in proportion to the span it is protecting. *)
type binding = { name : string; mutable value : int }
type ctx = binding list

let empty : ctx = []
let bind name value ctx = { name; value } :: ctx

let rec lookup name = function
  | [] ->
      (* A description referencing another field, evaluated where there is no
         record to read it from, cannot be decoded whatever the input: the
         entry points document that as [Invalid_argument], and the [Param_ref]
         arm below already raises it. *)
      Fmt.invalid_arg
        "Eval.expr: unbound field %s (cross-field references are only valid \
         inside a struct)"
        name
  | b :: tl -> if String.equal b.name name then b.value else lookup name tl

(* Value-to-int conversion is a fold over the type description, so it is
   decided next to the type definitions; re-exported here because the
   evaluator's callers reach for it alongside [expr]. *)
let int_of = Types.int_of
let int_of_exn = Types.int_of_exn

let rec expr : type a. ctx -> a expr -> a =
 fun ctx e ->
  match e with
  | Int n -> n
  | Int64 n -> n
  | Bool b -> b
  | Ref (I, name) -> lookup name ctx
  | Ref (I64, name) ->
      Fmt.invalid_arg
        "Eval.expr: unbound int64 field %s (cross-field references are only \
         valid inside a struct)"
        name
  | Param_ref p ->
      Fmt.invalid_arg
        "Eval.expr: parameter %S requires a codec evaluation context" p.name
  | Sizeof t -> field_wire_size t |> Option.value ~default:0
  | Sizeof_this -> 0
  | Field_pos -> 0
  | Add (a, b) -> checked_add (expr ctx a) (expr ctx b)
  | Sub (a, b) -> checked_sub (expr ctx a) (expr ctx b)
  | Mul (a, b) -> checked_mul (expr ctx a) (expr ctx b)
  | Div (a, b) -> checked_div (expr ctx a) (expr ctx b)
  | Mod (a, b) -> checked_mod (expr ctx a) (expr ctx b)
  | Land (a, b) -> expr ctx a land expr ctx b
  | Land64 (a, b) -> Int64.logand (expr ctx a) (expr ctx b)
  | Lsr64 (a, b) -> Int64.shift_right_logical (expr ctx a) (expr ctx b)
  | Lor (a, b) -> expr ctx a lor expr ctx b
  | Lxor (a, b) -> expr ctx a lxor expr ctx b
  | Lnot a -> lnot (expr ctx a)
  | Lsl (a, b) -> expr ctx a lsl expr ctx b
  | Lsr (a, b) -> expr ctx a lsr expr ctx b
  | Eq (a, b) -> expr ctx a = expr ctx b
  | Ne (a, b) -> expr ctx a <> expr ctx b
  | Lt (a, b) -> compare_expr ctx a b < 0
  | Le (a, b) -> compare_expr ctx a b <= 0
  | Gt (a, b) -> compare_expr ctx a b > 0
  | Ge (a, b) -> compare_expr ctx a b >= 0
  | And (a, b) -> expr ctx a && expr ctx b
  | Or (a, b) -> expr ctx a || expr ctx b
  | Not a -> not (expr ctx a)
  | Cast (width, e) -> (
      let v = expr ctx e in
      match width with
      | `U8 -> v land 0xFF
      | `U16 -> v land 0xFFFF
      | `U32 -> v land UInt32.mask32
      | `U64 -> v)
  | If_then_else (c, t, e) -> if expr ctx c then expr ctx t else expr ctx e

and compare_expr : type a. ctx -> a expr -> a expr -> int =
 fun ctx a b ->
  match a with
  | Int64 _ -> compare_int64_expr ctx a b
  | Ref (I64, _) -> compare_int64_expr ctx a b
  | _ -> Stdlib.compare (expr ctx a) (expr ctx b)

and compare_int64_expr ctx (a : int64 expr) (b : int64 expr) =
  Int64.unsigned_compare (expr ctx a) (expr ctx b)

(* [byte_array_where] refines every byte of the span, and every path that reads
   or writes one decides it here: the compiled codec, the direct parser, both
   encoders and the EverParse validator built from the same schema all admit
   exactly the same bytes. The loop stays at top level rather than closing over
   the span, so a scan allocates only its single binding. *)
let rec scan_bytes ctx elt cond buf ~first ~len i =
  if i >= len then -1
  else begin
    elt.value <- Bytes.get_uint8 buf (first + i);
    if expr ctx cond then scan_bytes ctx elt cond buf ~first ~len (i + 1) else i
  end

let bad_byte ~elt_var ~cond buf ~first ~len =
  let elt = { name = elt_var; value = 0 } in
  scan_bytes [ elt ] elt cond buf ~first ~len 0

let check_byte_refinement ~elt_var ~cond s =
  let len = String.length s in
  (* [bad_byte] only reads, so the aliased bytes never escape as mutable. *)
  let i = bad_byte ~elt_var ~cond (Bytes.unsafe_of_string s) ~first:0 ~len in
  if i >= 0 then
    Fmt.invalid_arg
      "Wire.encode: byte_array_where byte %d = 0x%02x violates its per-byte \
       constraint %a"
      i
      (Char.code s.[i])
      Types.pp_expr cond