OCaml wire format DSL with EverParse 3D output for verified parsers
at main 120 lines 3.6 kB view raw
1(** Generic differential testing: OCaml codec vs wire-generated C code. 2 3 Each schema needs [c_read] and [c_write] functions (generated by 4 {!Wire.to_c_stubs} and {!Wire.to_ml_stub}). All diff logic is generic over 5 any record codec. *) 6 7type 'r schema = { 8 name : string; 9 c_read : string -> string option; 10 c_write : string -> string option; 11 equal : 'r -> 'r -> bool; 12 codec_decode : bytes -> int -> 'r; 13 codec_encode : 'r -> bytes -> int -> unit; 14 wire_size : int; 15} 16 17let schema ~name ~codec ~c_read ~c_write ~equal = 18 let wire_size = Wire.Codec.wire_size codec in 19 { 20 name; 21 c_read; 22 c_write; 23 equal; 24 codec_decode = Wire.Codec.decode codec; 25 codec_encode = Wire.Codec.encode codec; 26 wire_size; 27 } 28 29type result = 30 | Match 31 | Both_failed 32 | Value_mismatch of string 33 | Only_c_ok of string 34 | Only_ocaml_ok of string 35 36let c_roundtrip schema buf = 37 match schema.c_read buf with 38 | None -> None 39 | Some fields -> schema.c_write fields 40 41let encode_to_string schema v = 42 let buf = Bytes.create schema.wire_size in 43 schema.codec_encode v buf 0; 44 Bytes.unsafe_to_string buf 45 46let decode_from_string schema s = schema.codec_decode (Bytes.of_string s) 0 47 48let read schema buf = 49 let c_result = c_roundtrip schema buf in 50 let buf_too_short = String.length buf < schema.wire_size in 51 let ocaml_result = 52 if String.length buf = 0 || buf_too_short then None 53 else Some (decode_from_string schema buf) 54 in 55 match (c_result, ocaml_result) with 56 | Some c_bytes, Some v -> 57 let ocaml_bytes = encode_to_string schema v in 58 if c_bytes = ocaml_bytes then Match 59 else Value_mismatch "roundtrip bytes differ" 60 | None, None -> Both_failed 61 | Some _, None -> Only_c_ok "OCaml decode returned empty" 62 | None, Some _ -> Only_ocaml_ok "C roundtrip failed" 63 64let write schema value = 65 let ocaml_bytes = encode_to_string schema value in 66 match c_roundtrip schema ocaml_bytes with 67 | Some c_bytes -> 68 if c_bytes = ocaml_bytes then Match 69 else Value_mismatch "C roundtrip bytes differ from OCaml encoding" 70 | None -> Only_ocaml_ok "C rejected OCaml-encoded bytes" 71 72let full_roundtrip schema value = 73 let ocaml_bytes = encode_to_string schema value in 74 match c_roundtrip schema ocaml_bytes with 75 | None -> Only_c_ok "C rejected OCaml-encoded bytes" 76 | Some c_bytes -> 77 let final = decode_from_string schema c_bytes in 78 if schema.equal value final then Match 79 else Value_mismatch "values differ after full roundtrip" 80 81let roundtrip_struct s buf = 82 match Wire.read_struct s buf with 83 | Error e -> Error e 84 | Ok ps -> Wire.write_struct s ps 85 86type packed_test = { 87 name : string; 88 wire_size : int; 89 test_read : string -> result; 90 test_write : string -> result; 91 test_roundtrip : string -> result; 92} 93 94let pack (type r) (schema : r schema) ~wire_size = 95 let decode_value buf = 96 let padded = 97 if String.length buf >= wire_size then String.sub buf 0 wire_size 98 else 99 let b = Bytes.make wire_size '\000' in 100 Bytes.blit_string buf 0 b 0 (String.length buf); 101 Bytes.to_string b 102 in 103 if String.length padded = 0 then None 104 else Some (decode_from_string schema padded) 105 in 106 { 107 name = schema.name; 108 wire_size; 109 test_read = (fun buf -> read schema buf); 110 test_write = 111 (fun buf -> 112 match decode_value buf with 113 | Some v -> write schema v 114 | None -> Both_failed); 115 test_roundtrip = 116 (fun buf -> 117 match decode_value buf with 118 | Some v -> full_roundtrip schema v 119 | None -> Both_failed); 120 }