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
open! Core
open! Async
open! Import
module Non_zero_char = struct
type t = char [@@deriving compare, equal, hash, quickcheck, sexp]
let quickcheck_generator = Char.gen_uniform_inclusive '\x01' Char.max_value
end
module Null_terminated_string = struct
module T = struct
type t = string [@@deriving compare, equal, hash, quickcheck, sexp]
type comparator_witness = String.comparator_witness
let comparator = String.comparator
let quickcheck_generator = String.gen' [%quickcheck.generator: Non_zero_char.t]
let validate t =
match String.mem t '\x00' with
| false -> Validate.pass
| true -> Validate.fail "string may not contain nulls"
;;
let payload_length t = String.length t + 1
let fill (local_ iobuf) t =
Iobuf.Fill.stringo iobuf t;
Iobuf.Fill.char iobuf '\x00'
;;
let consume_exn iobuf =
match Iobuf.Peek.index iobuf '\x00' with
| None ->
raise
(Not_found_s
[%sexp
"Null_terminated_string.consume_exn: could not find terminating null byte"])
| Some len ->
let t = Iobuf.Consume.stringo iobuf ~len in
[%test_result: char] (Iobuf.Consume.char iobuf) ~expect:'\x00';
t
;;
end
include T
module Nonempty = struct
include T
let quickcheck_generator =
String.gen_nonempty' [%quickcheck.generator: Non_zero_char.t]
;;
let validate t =
if String.is_empty t then Validate.fail "string may not be empty" else validate t
;;
let consume_exn iobuf =
match consume_exn iobuf with
| "" -> Error `Empty_string
| s -> Ok s
;;
end
end
let validate_null_terminated_exn ~field_name str =
if String.mem str '\x00'
then raise_s [%message "String may not contain nulls" field_name str]
;;
let fill_null_terminated iobuf str =
Iobuf.Fill.stringo iobuf str;
Iobuf.Fill.char iobuf '\x00'
;;
let uint16_min = 0
let uint16_max = 65535
let int32_min =
match Word_size.word_size with
| W64 -> Int32.to_int_exn Int32.min_value
| W32 -> Int.min_value
;;
let int32_max =
match Word_size.word_size with
| W64 -> Int32.to_int_exn Int32.max_value
| W32 -> Int.max_value
;;
let () =
match Word_size.word_size with
| W64 ->
assert (String.equal (Int.to_string int32_min) "-2147483648");
assert (String.equal (Int.to_string int32_max) "2147483647")
| W32 ->
assert (String.equal (Int.to_string int32_min) "-1073741824");
assert (String.equal (Int.to_string int32_max) "1073741823")
;;
let[@inline always] fill_uint16_be iobuf value =
match uint16_min <= value && value <= uint16_max with
| true -> Iobuf.Fill.uint16_be_trunc iobuf value
| false -> failwithf "uint16 out of range: %i" value ()
;;
let[@inline always] fill_int32_be iobuf value =
match int32_min <= value && value <= int32_max with
| true -> Iobuf.Fill.int32_be_trunc iobuf value
| false -> failwithf "int32 out of range: %i" value ()
;;
let find_null_exn iobuf =
let rec loop ~iobuf ~length ~pos =
if Char.( = ) (Iobuf.Peek.char iobuf ~pos) '\x00'
then pos
else if pos > length - 1
then failwith "find_null_exn could not find \\x00"
else loop ~iobuf ~length ~pos:(pos + 1)
in
loop ~iobuf ~length:(Iobuf.length iobuf) ~pos:0
;;
let consume_cstring_exn iobuf =
let len = find_null_exn iobuf in
let res = Iobuf.Consume.string iobuf ~len ~str_pos:0 in
let zero = Iobuf.Consume.char iobuf in
assert (Char.( = ) zero '\x00');
res
;;
module type Message_type = sig
val message_type_char : char option
type t
val validate_exn : t -> unit
val payload_length : t -> int
val fill : t -> (read_write, Iobuf.seek, Iobuf.global) Iobuf.t -> unit
end
type 'a with_computed_length =
{ payload_length : int
; value : 'a
}
let write_message (type a) (module M : Message_type with type t = a) =
let full_length { payload_length; _ } =
match M.message_type_char with
| None -> payload_length + 4
| Some _ -> payload_length + 5
in
let blit_to_bigstring with_computed_length bigstring ~pos =
let iobuf =
Iobuf.of_bigstring bigstring ~pos ~len:(full_length with_computed_length)
in
(match M.message_type_char with
| None -> ()
| Some c -> Iobuf.Fill.char iobuf c);
let { payload_length; value } = with_computed_length in
fill_int32_be iobuf (payload_length + 4);
M.fill value iobuf;
match Iobuf.is_empty iobuf with
| true -> ()
| false -> failwith "postgres message filler lied about length"
in
Staged.stage (fun writer value ->
M.validate_exn value;
let payload_length = M.payload_length value in
Async.Writer.write_gen_whole
writer
{ payload_length; value }
~length:full_length
~blit_to_bigstring)
;;
module No_arg = struct
let gen ~constructor =
let tmp = Iobuf.create ~len:5 in
Iobuf.Poke.char tmp ~pos:0 constructor;
Iobuf.Poke.int32_be_trunc tmp ~pos:1 4;
Iobuf.to_string tmp
;;
end
module CopyData = struct
let message_type_char = Some 'd'
type t = string
let skip iobuf = Iobuf.advance iobuf (Iobuf.length iobuf)
let validate_exn (_ : t) = ()
let payload_length t = String.length t
let fill t iobuf = Iobuf.Fill.stringo iobuf t
end
module CopyDone = struct
let consume (_ : _ Iobuf.t) = ()
end