aboutsummaryrefslogtreecommitdiffstats
path: root/lib/concepts/Encoding.ml
blob: 733ff67787918578d25f0d38d0113a3ac31f0d53 (plain) (blame)
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
(* A blob is a one-byte constructor tag followed by its payload. The
   tag makes the encoding self-describing so [value_of_blob] can
   recover the constructor:
   - String  ['\000'] ++ raw utf-8 bytes
   - Integer ['\001'] ++ 8-byte big-endian int64 *)
let tag_string = '\000'
let tag_integer = '\001'

let blob_of_value = function
  | Value.String s ->
      let b = Blob.create (1 + String.length s) in
      Blob.set b 0 tag_string;
      Blob.blit_string s 0 b 1 (String.length s);
      b
  | Value.Integer n ->
      let b = Blob.create 9 in
      Blob.set b 0 tag_integer;
      Blob.set_int64_be b 1 (Int64.of_int n);
      b

let value_of_blob (b : Blob.t) =
  let n = Blob.length b in
  if n = 0 then invalid_arg "value_of_blob: empty blob";
  match Blob.get b 0 with
  | c when c = tag_string -> Value.String (Blob.sub_string b 1 (n - 1))
  | c when c = tag_integer ->
      if n <> 9 then invalid_arg "value_of_blob: malformed integer payload";
      Value.Integer (Int64.to_int (Blob.get_int64_be b 1))
  | c -> invalid_arg (Printf.sprintf "value_of_blob: unknown tag %C" c)

module Error = struct
  open Condition

  let unexpected_end =
    condition "codec-unexpected-end" "Unexpected end of input while decoding" empty

  let malformed_integer digits =
    condition "codec-malformed-integer" "A byte run is not a valid integer"
      ("digits" |=| Value.String digits)

  let unexpected_byte c =
    condition "codec-unexpected-byte" "Unexpected byte while decoding"
      ("byte" |=| Value.String (String.make 1 c))

  let string_out_of_bounds length =
    condition "codec-string-out-of-bounds" "A string length runs past the end of input"
      ("length" |=| Value.Integer length)

  let trailing_bytes = condition "codec-trailing-bytes" "Bytes remain after a complete value" empty

  let tag_mismatch ~expected ~actual =
    condition "codec-tag-mismatch" "A value with a different tag was expected"
      ( "expected" |=| Value.String (String.make 1 expected)
      & "actual" |=| Value.String (String.make 1 actual) )

  let type_mismatch ~expected =
    condition "codec-type-mismatch" "A Bencode value of a different shape was expected"
      ("expected" |=| Value.String expected)

  let missing_field key =
    condition "codec-missing-field" "A required field is absent" ("field" |=| Value.String key)

  let unexpected_object ~expected =
    condition "codec-unexpected-object" "The decoded object is not of the expected kind"
      ("expected" |=| Value.String expected)
end

(* A self-describing serialization used as the uniform wire format for
   durable objects. Chosen over an ad-hoc key=value scheme because it
   nests (a multigroup holds a map of relation hashes) and has a
   single byte form for a given value (dict keys are emitted sorted),
   which matters once these bytes are content-addressed.

   Grammar:
   - Int    "i" <decimal> "e" (negatives allowed)
   - String <length> ":" <raw bytes>
   - List   "l" <element>* "e"
   - Dict   "d" (<String key> <value>)* "e" keys sorted on encode *)
module Bencode = struct
  type t =
    | Tagged of char * t
    | Int of int
    | String of string
    | List of t list
    | Dict of (string * t) list

  let rec encode buf = function
    | Tagged (tag, value) -> Buffer.add_char buf 't'; Buffer.add_char buf tag; encode buf value
    | Int n ->
        Buffer.add_char buf 'i';
        Buffer.add_string buf (string_of_int n);
        Buffer.add_char buf 'e'
    | String s ->
        Buffer.add_string buf (string_of_int (String.length s));
        Buffer.add_char buf ':';
        Buffer.add_string buf s
    | List xs ->
        Buffer.add_char buf 'l';
        List.iter (encode buf) xs;
        Buffer.add_char buf 'e'
    | Dict kvs ->
        Buffer.add_char buf 'd';
        List.iter
          (fun (k, v) -> encode buf (String k); encode buf v)
          (List.sort (fun (a, _) (b, _) -> String.compare a b) kvs);
        Buffer.add_char buf 'e'

  let to_bytes (value : t) : bytes =
    let buf = Buffer.create 256 in
    encode buf value; Buffer.to_bytes buf

  let as_string = function String s -> Ok s | _ -> Error (Error.type_mismatch ~expected:"string")
  let as_int = function Int n -> Ok n | _ -> Error (Error.type_mismatch ~expected:"integer")
  let as_list = function List l -> Ok l | _ -> Error (Error.type_mismatch ~expected:"list")
  let as_dict = function Dict d -> Ok d | _ -> Error (Error.type_mismatch ~expected:"dict")

  let of_bytes (bytes : bytes) : (t, Condition.condition) result =
    let open Utilities.Result in
    let s = Bytes.to_string bytes in
    let n = String.length s in
    let pos = ref 0 in
    let peek () = if !pos >= n then Error Error.unexpected_end else Ok s.[!pos] in
    let advance () = incr pos in
    (* Read the decimal run up to (and consuming) [term]. *)
    let read_int_from start term =
      let rec scan () =
        let* c = peek () in
        if c = term then begin
          let digits = String.sub s start (!pos - start) in
          advance ();
          match int_of_string_opt digits with
          | Some i -> Ok i
          | None -> Error (Error.malformed_integer digits)
        end
        else begin
          advance (); scan ()
        end
      in
      scan ()
    in
    let read_int term = read_int_from !pos term in
    let read_char () =
      let* c = peek () in
      advance (); Ok c
    in
    let rec parse () =
      let* c = peek () in
      advance ();
      match c with
      | 't' ->
          let* tag = read_char () in
          let* data = parse () in
          Ok (Tagged (tag, data))
      | 'i' ->
          let* n = read_int 'e' in
          Ok (Int n)
      | 'l' -> parse_list []
      | 'd' -> parse_dict []
      | c when c >= '0' && c <= '9' -> parse_string_from (!pos - 1)
      | c -> Error (Error.unexpected_byte c)
    and parse_string () = parse_string_from !pos
    and parse_string_from start =
      let* len = read_int_from start ':' in
      if len < 0 || !pos + len > n then Error (Error.string_out_of_bounds len)
      else
        let str = String.sub s !pos len in
        pos := !pos + len;
        Ok (String str)
    and parse_list acc =
      let* c = peek () in
      if c = 'e' then (
        advance ();
        Ok (List (List.rev acc)) )
      else
        let* result = parse () in
        parse_list (result :: acc)
    and parse_dict acc =
      let* c = peek () in
      if c = 'e' then (
        advance ();
        Ok (Dict (List.rev acc)) )
      else
        let* key = parse_string () in
        let* key = as_string key in
        let* value = parse () in
        parse_dict ((key, value) :: acc)
    in
    let* value = parse () in
    if !pos <> n then Error Error.trailing_bytes else Ok value

  let to_blob x = to_bytes x |> Blob.blob_of_bytes
  let of_blob b = Blob.bytes_of_blob b |> of_bytes

  let with_tag t = function
    | Tagged (t', v) -> if t = t' then Ok v else Error (Error.tag_mismatch ~expected:t ~actual:t')
    | _ -> Error (Error.type_mismatch ~expected:"tagged")

  let field key = function
    | Dict kvs -> (
      match List.assoc_opt key kvs with Some v -> Ok v | None -> Error (Error.missing_field key) )
    | _ -> Error (Error.type_mismatch ~expected:"dict")
end

module Value = struct
  let bencode_of_hash hash = Bencode.Tagged ('h', Bencode.String (Hash.to_raw_string hash))

  let hash_of_bencode value =
    let open Utilities.Result in
    Bencode.with_tag 'h' value |> fmap Bencode.as_string |> Result.map Hash.of_raw_string

  let bencode_of_option f option =
    Bencode.Tagged ('?', Option.map f option |> Option.to_list |> fun x -> Bencode.List x)

  let option_of_bencode f value =
    let open Utilities.Result in
    Bencode.with_tag '?' value
    |> fmap Bencode.as_list
    |> Result.map Utilities.List.hd_opt
    |> fmap (function None -> Ok None | Some v -> f v |> Result.map Option.some)
end

module Field = struct
  let string value = Bencode.String value
  let address address = Bencode.String (Hash.to_raw_string address)
  let require_string key data = Bencode.field key data |> Utilities.Result.fmap Bencode.as_string

  let require_address ~malformed key data =
    let open Utilities.Result in
    let* raw = require_string key data in
    if String.length raw = Hash.size then Ok (Hash.of_raw_string raw)
    else Error (malformed (String.length raw))
end

module Record = struct
  module type S = sig
    type t

    val to_bencode : t -> Bencode.t
    val of_bencode : Bencode.t -> (t, Condition.condition) result
    val to_blob : t -> Blob.t
    val of_blob : Blob.t -> (t, Condition.condition) result
    val to_bytes : t -> bytes
    val of_bytes : bytes -> (t, Condition.condition) result
  end

  module type BODY = sig
    type t

    val tag : char
    val malformed : unit -> Condition.condition
    val fields : t -> (string * Bencode.t) list
    val of_fields : Bencode.t -> (t, Condition.condition) result
  end

  module Make (B : BODY) : S with type t = B.t = struct
    type t = B.t

    let to_bencode object_ = Bencode.Tagged (B.tag, Bencode.Dict (B.fields object_))

    let of_bencode = function
      | Bencode.Tagged (tag, (Bencode.Dict _ as data)) when Char.equal tag B.tag -> B.of_fields data
      | _ -> Error (B.malformed ())

    let to_blob object_ = to_bencode object_ |> Bencode.to_blob
    let of_blob blob = Bencode.of_blob blob |> Utilities.Result.fmap of_bencode
    let to_bytes object_ = to_blob object_ |> Blob.bytes_of_blob
    let of_bytes bytes = Bencode.of_bytes bytes |> Utilities.Result.fmap of_bencode
  end
end