File: fuzz_rfc4648.ml

package info (click to toggle)
ocaml-base64 3.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 220 kB
  • sloc: ml: 1,314; makefile: 10
file content (169 lines) | stat: -rw-r--r-- 5,788 bytes parent folder | download
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
open Crowbar

let pp_chr =
  let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
  Fmt.using escaped Fmt.string

let pp_scalar :
    type buffer.
    get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
 fun ~get ~length ppf b ->
  let l = length b in
  for i = 0 to l / 16 do
    Fmt.pf ppf "%08x: " (i * 16) ;
    let j = ref 0 in
    while !j < 16 do
      if (i * 16) + !j < l
      then Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
      else Fmt.pf ppf "  " ;
      if !j mod 2 <> 0 then Fmt.pf ppf " " ;
      incr j
    done ;
    Fmt.pf ppf "  " ;
    j := 0 ;
    while !j < 16 do
      if (i * 16) + !j < l
      then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
      else Fmt.pf ppf " " ;
      incr j
    done ;
    Fmt.pf ppf "@\n"
  done

let pp = pp_scalar ~get:String.get ~length:String.length

let ( <.> ) f g x = f (g x)

let char_from_alphabet alphabet : string gen =
  map [ range 64 ] (String.make 1 <.> String.get (Base64.alphabet alphabet))

let random_string_from_alphabet alphabet len : string gen =
  let rec add_char_from_alphabet acc = function
    | 0 -> acc
    | n ->
        add_char_from_alphabet
          (concat_gen_list (const "") [ acc; char_from_alphabet alphabet ])
          (n - 1) in
  add_char_from_alphabet (const "") len

let random_string_from_alphabet ~max alphabet =
  dynamic_bind (range max) @@ fun real_len ->
  dynamic_bind (random_string_from_alphabet alphabet real_len) @@ fun input ->
  if real_len <= 1
  then const (input, 0, real_len)
  else
    dynamic_bind (range (real_len / 2)) @@ fun off ->
    map [ range (real_len - off) ] (fun len -> (input, off, len))

let encode_and_decode (input, off, len) =
  match Base64.encode ~pad:true ~off ~len input with
  | Error (`Msg err) -> fail err
  | Ok result ->
  match Base64.decode ~pad:true result with
  | Error (`Msg err) -> fail err
  | Ok result ->
      check_eq ~pp ~cmp:String.compare ~eq:String.equal result
        (String.sub input off len)

let decode_and_encode (input, off, len) =
  match Base64.decode ~pad:true ~off ~len input with
  | Error (`Msg err) -> fail err
  | Ok result ->
  match Base64.encode ~pad:true result with
  | Error (`Msg err) -> fail err
  | Ok result ->
      check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result
        (String.sub input off len)

let ( // ) x y =
  if y < 1 then raise Division_by_zero ;
  if x > 0 then 1 + ((x - 1) / y) else 0
  [@@inline]

let canonic alphabet =
  let dmap = Array.make 256 (-1) in
  String.iteri (fun i x -> dmap.(Char.code x) <- i) (Base64.alphabet alphabet) ;
  fun (input, off, len) ->
    let real_len = String.length input in
    let input_len = len in
    let normalized_len = input_len // 4 * 4 in
    if normalized_len = input_len
    then (input, off, input_len)
    else if normalized_len - input_len = 3
    then (input, off, input_len - 1)
    else
      let remainder_len = normalized_len - input_len in
      let last = input.[off + input_len - 1] in
      let output = Bytes.make (max real_len (off + normalized_len)) '=' in

      Bytes.blit_string input 0 output 0 (off + input_len) ;
      if off + normalized_len < real_len
      then
        Bytes.blit_string input (off + normalized_len) output
          (off + normalized_len)
          (real_len - (off + normalized_len)) ;

      let mask =
        match remainder_len with 1 -> 0x3c | 2 -> 0x30 | _ -> assert false in
      let decoded = dmap.(Char.code last) in
      let canonic = decoded land mask in
      let encoded = (Base64.alphabet alphabet).[canonic] in
      Bytes.set output (off + input_len - 1) encoded ;
      (Bytes.unsafe_to_string output, off, normalized_len)

let isomorphism0 (input, off, len) =
  (* x0 = decode(input) && x1 = decode(encode(x0)) && x0 = x1 *)
  match Base64.decode ~pad:false ~off ~len input with
  | Error (`Msg err) -> fail err
  | Ok result0 -> (
      let result1 = Base64.encode_exn result0 in
      match Base64.decode ~pad:true result1 with
      | Error (`Msg err) -> fail err
      | Ok result2 ->
          check_eq ~pp ~cmp:String.compare ~eq:String.equal result0 result2)

let isomorphism1 (input, off, len) =
  let result0 = Base64.encode_exn ~off ~len input in
  match Base64.decode ~pad:true result0 with
  | Error (`Msg err) -> fail err
  | Ok result1 ->
      let result2 = Base64.encode_exn result1 in
      check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result0
        result2

let bytes_and_range : (string * int * int) gen =
  dynamic_bind bytes @@ fun t ->
  let real_length = String.length t in
  if real_length <= 1
  then const (t, 0, real_length)
  else
    dynamic_bind (range (real_length / 2)) @@ fun off ->
    map [ range (real_length - off) ] (fun len -> (t, off, len))

let range_of_max max : (int * int) gen =
  dynamic_bind (range (max / 2)) @@ fun off ->
  map [ range (max - off) ] (fun len -> (off, len))

let failf fmt = Fmt.kstr fail fmt

let no_exception pad off len input =
  try
    let _ =
      Base64.decode ?pad ?off ?len ~alphabet:Base64.default_alphabet input in
    ()
  with exn -> failf "decode fails with: %s." (Printexc.to_string exn)

let () =
  add_test ~name:"rfc4648: encode -> decode" [ bytes_and_range ]
    encode_and_decode ;
  add_test ~name:"rfc4648: decode -> encode"
    [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ]
    (decode_and_encode <.> canonic Base64.default_alphabet) ;
  add_test ~name:"rfc4648: x = decode(encode(x))"
    [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ]
    isomorphism0 ;
  add_test ~name:"rfc4648: x = encode(decode(x))" [ bytes_and_range ]
    isomorphism1 ;
  add_test ~name:"rfc4648: no exception leak"
    [ option bool; option int; option int; bytes ]
    no_exception