http.bend

bend-kit-http@0.38.1.1 · 7,074 lines · 0 definitions

1# HTTP/1.1 and HTTP/2 client, with an HTTP/1.1 server. Source: https://github.com/paymog/bend-kit/tree/main/http
2import Base
3import bend-kit-http@0.38.1.1/admission.bend as Admission
4import bend-kit-http@0.38.1.1/completion.bend as Completion
5import bend-kit-wire@0.4.7.0/wire.bend as Wire
6import bend-kit-http2@0.4.0.0/http2.bend as H2
7import bend-kit-http2@0.4.0.0/hpack.bend as Hpack
8import bend-kit-bytes@0.3.3.0/bytes.bend as HBytes
9import bend-kit-time@0.3.0.0/time.bend as Time
10import bend-kit-int@0.2.0.0/int.bend as Int
11import bend-kit-concurrency@0.2.0.0/concurrency.bend as Conc
12import bend-kit-url@0.4.1.0/url.bend as Url
13import bend-kit-encoding@0.5.0.0/encoding.bend as Enc
14import bend-kit-json@0.7.0.0/json.bend as Json
15import bend-kit-bytes@0.3.3.0/bytes.bend as Bytes
16import bend-kit-dns@0.7.0.0/dns.bend as Dns
17import bend-kit-zlib@0.5.0.0/zlib.bend as Zlib
18
19# HTTP/1.1 codec and client (RFC 9112) for http:// and https:// (OpenSSL 3 at run time).
20# parse: start-line → field lines → blank → body length (§6.3); chunked (§7.1).
21# Bodies are Bytes. The String parsers below are the spec: one Char per octet.
22# fetch reads until frame() says the response is whole. It returns Result, not Maybe.
23# Build with -o for big bodies: the `bend file.bend` runner overflows on ~30 KB strings; native binaries do not.
24#   import bend-kit-http@0.37.0.0/http.bend as Http
25
26type Req is Type:
27  Req{method: String, path: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes}
28
29type Res is Type:
30  Res{status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes}
31
32# A typed Data payload can be captured in receipt; the response remains affine.
33type Reply is Type:
34  Reply{response: Res, receipt: Completion.Receipt}
35
36def reply.complete(~D: Data, ~done: Completion.Completion<D> -> IO(Unit), payload: D, response: Res) -> Reply:
37  Reply{response, Completion.receipt(~D, ~done, payload)}
38
39
40# The body type and its conversions, so a caller needs no bytes import.
41def Body() -> Type:
42  Bytes.Bytes
43
44# A byte string (one Char per octet) to a body, and back.
45def from_string(+s: String) -> Bytes.Bytes:
46  Bytes.from_string(s)
47
48def to_string(b: Bytes.Bytes) -> String:
49  Bytes.to_string(b)
50
51def length(b: Bytes.Bytes) -> Bytes.Bytes & U32:
52  Bytes.length(b)
53
54# Why fetch failed. Wire errors keep the errno and the text the effect already had.
55# ponytail: 60 and 110 are ETIMEDOUT on macOS and Linux. Windows is out of scope.
56type Err is Data:
57  ErrUrl{}
58  ErrDns{}
59  ErrConnect{code: U32, why: String}
60  ErrTls{code: U32, why: String}
61  ErrRead{code: U32, why: String}
62  ErrWrite{code: U32, why: String}
63  ErrTimeout{}
64  ErrRedirect{}
65  ErrBad{}
66
67def err.late(+code: U32) -> Bool:
68  Bool.or(U32.is_eq(code, 60), U32.is_eq(code, 110))
69
70def err.pick(e: Err, late: Bool) -> Err:
71  match late:
72    case True{}:
73      ErrTimeout{}
74    case False{}:
75      e
76
77def err.or_late(code: U32, e: Err) -> Err:
78  err.pick(e, err.late(code))
79
80type Fields is Data:
81  FieldsBad{}
82  FieldsOk{m: Map<&2, List<&2, String>>}
83
84# One list per field name, in arrival order. header() is the first value.
85# Set-Cookie is never joined. Encode writes one line per value.
86def empty() -> Map<&2, List<&2, String>>:
87  Map.new(&2, List<&2, String>)
88
89def fields.of(r: Map<&2, List<&2, String>> & List<&2, String>) -> List<&2, String>:
90  (h, xs) = r
91  xs
92
93def fields(+h: Map<&2, List<&2, String>>, k: String) -> List<&2, String>:
94  fields.of(Map.get(List<&2, String>, Nil{}, h, k))
95
96def header.first(xs: List<&2, String>) -> String:
97  match xs:
98    case Nil{}:
99      ""
100    case Con{v, t}:
101      v
102
103def header(+h: Map<&2, List<&2, String>>, k: String) -> String:
104  header.first(fields(h, k))
105
106def field.last(xs: List<&2, String>) -> String:
107  match xs:
108    case Nil{}:
109      ""
110    case Con{v, Nil{}}:
111      v
112    case Con{v, t}:
113      field.last(t)
114
115# Transfer-Encoding is a list; chunked, when present, is the last coding.
116def header.last(+h: Map<&2, List<&2, String>>, k: String) -> String:
117  field.last(fields(h, k))
118
119def snoc(xs: List<&2, String>, v: String) -> List<&2, String>:
120  match xs:
121    case Nil{}:
122      [v]
123    case Con{h, t}:
124      Con{h, snoc(t, v)}
125
126def set(m: Map<&2, List<&2, String>>, k: String, v: String) -> Map<&2, List<&2, String>>:
127  Map.set(&2, List<&2, String>, m, k, [v])
128
129def add(+m: Map<&2, List<&2, String>>, +k: String, v: String) -> Map<&2, List<&2, String>>:
130  Map.set(&2, List<&2, String>, m, k, snoc(fields(m, k), v))
131
132def sanitize.cons(ch: Char, rest: String, drop: Bool) -> String:
133  match drop:
134    case True{}:
135      rest
136    case False{}:
137      SCon{ch, rest}
138
139def sanitize(s: String) -> String:
140  match s:
141    case SNil{}:
142      SNil{}
143    case SCon{Chr{+c}, t}:
144      sanitize.cons(Chr{c}, sanitize(t), Bool.or(U32.is_eq(c, 13), U32.is_eq(c, 10)))
145
146def drop_cr.last.if(cr: Bool, c: U32) -> String:
147  match cr:
148    case True{}:
149      SNil{}
150    case False{}:
151      SCon{Chr{c}, SNil{}}
152
153def drop_cr.last(+c: U32) -> String:
154  drop_cr.last.if(U32.is_eq(c, 13), c)
155
156# prev is the char before s; it is kept unless it is a final CR.
157def drop_cr.go(s: String, prev: U32) -> String:
158  match s:
159    case SNil{}:
160      drop_cr.last(prev)
161    case SCon{Chr{c}, t}:
162      SCon{Chr{prev}, drop_cr.go(t, c)}
163
164def drop_cr(s: String) -> String:
165  match s:
166    case SNil{}:
167      SNil{}
168    case SCon{Chr{c}, t}:
169      drop_cr.go(t, c)
170
171# w holds the last four octets; a char past 255 clears it.
172def blank_end.go(s: String, w: U32) -> Bool:
173  match s:
174    case SNil{}:
175      U32.is_eq(w, 218762506)
176    case SCon{Chr{+c}, t}:
177      blank_end.go(t, Bool.pick(U32, U32.is_lt(c, 256), (w * 256 + c : U32), 0))
178
179# String.ends_with(s, "\r\n\r\n") in one pass, with no reversed copy.
180def blank_end(s: String) -> Bool:
181  blank_end.go(s, 0)
182
183def trim_end.cons(h: Char, r: String, space: Bool) -> String:
184  match r:
185    case SNil{}:
186      match space:
187        case True{}:
188          SNil{}
189        case False{}:
190          SCon{h, SNil{}}
191    case SCon{a, t}:
192      SCon{h, SCon{a, t}}
193
194# String.trim_end without the two reversed copies.
195def trim_end(s: String) -> String:
196  match s:
197    case SNil{}:
198      SNil{}
199    case SCon{+h, t}:
200      trim_end.cons(h, trim_end(t), Char.is_space(h))
201
202def trim(s: String) -> String:
203  trim_end(String.trim_start(s))
204
205# Bool.pick evaluates both arms; a split that recursed inside it would reverse acc at every byte.
206def take.cut(acc: String, rest: String) -> String & String:
207  (String.reverse(String.drop(acc, 1n)), rest)
208
209# hit: the last char pushed onto acc was the stop char.
210def take_sp.go(s: String, acc: String, hit: Bool) -> String & String:
211  match s:
212    case SNil{}:
213      match hit:
214        case True{}:
215          take.cut(acc, SNil{})
216        case False{}:
217          (String.reverse(acc), SNil{})
218    case SCon{Chr{+c}, t}:
219      match hit:
220        case True{}:
221          take.cut(acc, SCon{Chr{c}, t})
222        case False{}:
223          take_sp.go(t, SCon{Chr{c}, acc}, U32.is_eq(c, 32))
224
225def take_sp(s: String) -> String & String:
226  take_sp.go(s, SNil{}, False{})
227
228def start_line.ver(m: String, pv: String & String) -> String & String & String:
229  (p, v) = pv
230  (m, p, drop_cr(v))
231
232def start_line.rest(mr: String & String) -> String & String & String:
233  (m, r) = mr
234  start_line.ver(m, take_sp(r))
235
236def start_line(s: String) -> String & String & String:
237  start_line.rest(take_sp(s))
238
239def split_at_blank.go(s: String, e: Bool, w: U32, acc: String) -> String & String:
240  match s:
241    case SNil{}:
242      (String.reverse(acc), SNil{})
243    case SCon{Chr{+a}, t}:
244      match e:
245        case True{}:
246          (String.reverse(acc), SCon{Chr{a}, t})
247        case False{}:
248          +w2 = (w * 256 + a : U32)
249          split_at_blank.go(t, U32.is_eq(w2, 218762506), w2, SCon{Chr{a}, acc})
250
251# split_at_blank, plus whether the head ends in the blank line.
252def split_at_blank.flag.go(+raw: String, s: String, off: Nat, w: U32, e: Bool) -> String & String & Bool:
253  match s:
254    case SNil{}:
255      (String.take(raw, off), SNil{}, e)
256    case SCon{Chr{+a}, t}:
257      match e:
258        case True{}:
259          (String.take(raw, off), SCon{Chr{a}, t}, True{})
260        case False{}:
261          +w2 = (w * 256 + a : U32)
262          +off1 = (1n + off : Nat)
263          split_at_blank.flag.go(raw, t, off1, w2, U32.is_eq(w2, 218762506))
264
265def split_at_blank.pair(r: String & String & Bool) -> String & String:
266  (head, rest, whole) = r
267  (head, rest)
268
269def split_at_blank(+s: String) -> String & String:
270  split_at_blank.pair(split_at_blank.flag.go(s, s, 0n, 0, False{}))
271
272def split_colon.go(s: String, acc: String, hit: Bool) -> String & String:
273  match s:
274    case SNil{}:
275      match hit:
276        case True{}:
277          take.cut(acc, SNil{})
278        case False{}:
279          (String.reverse(acc), SNil{})
280    case SCon{Chr{+c}, t}:
281      match hit:
282        case True{}:
283          take.cut(acc, SCon{Chr{c}, t})
284        case False{}:
285          split_colon.go(t, SCon{Char.to_lower(Chr{c}), acc}, U32.is_eq(c, 58))
286
287def split_colon(+s: String) -> String & String:
288  split_colon.go(s, SNil{}, False{})
289
290def has_header.of(r: Map<&2, List<&2, String>> & Bool) -> Bool:
291  (h, b) = r
292  b
293
294def has_header(+h: Map<&2, List<&2, String>>, k: String) -> Bool:
295  has_header.of(Map.has(&2, List<&2, String>, h, k))
296
297def folded(+line: String) -> Bool:
298  Bool.or(String.starts_with(line, " "), String.starts_with(line, "\t"))
299
300def tracked(+k: String) -> Bool:
301  Bool.or(String.eq(k, "host"), String.eq(k, "content-length"))
302
303def fields_put.dup(m: Map<&2, List<&2, String>>, k: String, v: String, bad: Bool) -> Fields:
304  match bad:
305    case True{}:
306      FieldsBad{}
307    case False{}:
308      FieldsOk{add(m, k, v)}
309
310def fields_put.tracked(+m: Map<&2, List<&2, String>>, +k: String, v: String, t: Bool) -> Fields:
311  match t:
312    case True{}:
313      fields_put.dup(m, k, v, has_header(m, k))
314    case False{}:
315      FieldsOk{add(m, k, v)}
316
317def fields_put.key(+m: Map<&2, List<&2, String>>, +k: String, v: String) -> Fields:
318  fields_put.tracked(m, k, v, tracked(k))
319
320def fields_put.kv(m: Map<&2, List<&2, String>>, kv: String & String) -> Fields:
321  (k, v) = kv
322  fields_put.key(m, k, trim(v))
323
324def fields_put.fold(m: Map<&2, List<&2, String>>, line: String, fold: Bool) -> Fields:
325  match fold:
326    case True{}:
327      FieldsBad{}
328    case False{}:
329      fields_put.kv(m, split_colon(line))
330
331def fields_put.empty(m: Map<&2, List<&2, String>>, +line: String, e: Bool) -> Fields:
332  match e:
333    case True{}:
334      FieldsOk{m}
335    case False{}:
336      fields_put.fold(m, line, folded(line))
337
338def fields_put.ok(m: Map<&2, List<&2, String>>, +line: String) -> Fields:
339  fields_put.empty(m, line, String.is_empty(line))
340
341def fields_put(acc: Fields, line: String) -> Fields:
342  match acc:
343    case FieldsBad{}:
344      FieldsBad{}
345    case FieldsOk{m}:
346      fields_put.ok(m, line)
347
348def parse.headers.done(acc: Fields) -> Maybe<&2, Map<&2, List<&2, String>>>:
349  match acc:
350    case FieldsBad{}:
351      None{}
352    case FieldsOk{m}:
353      Some{m}
354
355
356def parse.headers.scan.line(acc: String, fields: Fields) -> Fields:
357  match acc:
358    case SCon{Chr{13}, t}:
359      fields_put(fields, String.reverse(t))
360    case other:
361      fields_put(fields, String.reverse(other))
362
363def parse.headers.scan(s: String, acc: String, fields: Fields) -> Fields:
364  match s:
365    case SNil{}:
366      parse.headers.scan.line(acc, fields)
367    case SCon{Chr{10}, t}:
368      parse.headers.scan(t, SNil{}, parse.headers.scan.line(acc, fields))
369    case SCon{c, t}:
370      parse.headers.scan(t, SCon{c, acc}, fields)
371
372def parse.headers.raw(s: String) -> Maybe<&2, Map<&2, List<&2, String>>>:
373  parse.headers.done(parse.headers.scan(s, SNil{}, FieldsOk{empty()}))
374
375def parse.first(s: String, acc: String) -> String & String:
376  match s:
377    case SNil{}:
378      (String.reverse(acc), SNil{})
379    case SCon{Chr{10}, t}:
380      (String.reverse(acc), t)
381    case SCon{c, t}:
382      parse.first(t, SCon{c, acc})
383
384def parse_u32.digit(+c: U32) -> Bool:
385  Bool.and(U32.is_le(48, c), U32.is_le(c, 57))
386
387def parse_u32.add(acc: U32, c: U32) -> U32:
388  (acc * 10 + (c - 48 : U32) : U32)
389
390def parse_u32.done(acc: U32, bad: Bool) -> Maybe<&2, U32>:
391  match bad:
392    case True{}:
393      None{}
394    case False{}:
395      Some{acc}
396
397def parse_u32.go(s: String, acc: U32, bad: Bool) -> Maybe<&2, U32>:
398  match s:
399    case SNil{}:
400      parse_u32.done(acc, bad)
401    case SCon{Chr{+c}, t}:
402      parse_u32.go(t, parse_u32.add(acc, c), Bool.or(bad, Bool.not(parse_u32.digit(c))))
403
404def parse_u32(s: String) -> Maybe<&2, U32>:
405  match s:
406    case SNil{}:
407      None{}
408    case SCon{Chr{+c}, t}:
409      parse_u32.go(t, parse_u32.add(0, c), Bool.not(parse_u32.digit(c)))
410
411# More: a valid prefix. Bad: cannot become a body. Body: a whole chunked body.
412type Hold is Data:
413  HoldMore{}
414  HoldBad{}
415  HoldBody{b: String}
416  HoldUntil{b: String}
417
418def is_hex(+c: U32) -> Bool:
419  Bool.or(parse_u32.digit(c), Bool.or(Bool.and(U32.is_le(97, c), U32.is_le(c, 102)), Bool.and(U32.is_le(65, c), U32.is_le(c, 70))))
420
421def hexv(+c: U32) -> U32:
422  Bool.pick(U32, parse_u32.digit(c), (c - 48 : U32), Bool.pick(U32, U32.is_le(c, 70), (c - 55 : U32), (c - 87 : U32)))
423
424# A chunked body decoder that reads each byte once, so a body fed in pieces
425# costs O(bytes) in total. Tr{j}: j bytes of the closing CRLF CRLF matched.
426type Dc is Data:
427  DcSize{acc: U32, seen: Bool}
428  DcExt{n: U32}
429  DcSizeLf{n: U32}
430  DcData{k: U32}
431  DcDataCr{}
432  DcDataLf{}
433  DcTr{j: U32}
434  DcDone{}
435  DcBad{}
436
437# st: where the decoder is. racc: the body so far, reversed. rest: bytes after a Done body.
438type Dco is Data:
439  Dco{st: Dc, racc: String, rest: String}
440
441def dc.start() -> Dc:
442  DcSize{0, False{}}
443
444def dc.size.semi(+n: U32, semi: Bool) -> Dc:
445  match semi:
446    case True{}:
447      DcExt{n}
448    case False{}:
449      DcBad{}
450
451def dc.size.cr(+n: U32, +c: U32, cr: Bool) -> Dc:
452  match cr:
453    case True{}:
454      DcSizeLf{n}
455    case False{}:
456      dc.size.semi(n, U32.is_eq(c, 59))
457
458def dc.size.end(+n: U32, +c: U32, seen: Bool) -> Dc:
459  match seen:
460    case False{}:
461      DcBad{}
462    case True{}:
463      dc.size.cr(n, c, U32.is_eq(c, 13))
464
465# A size that would overflow 32 bits ends the digits, and size.end then refuses it.
466def dc.size(+acc: U32, seen: Bool, +c: U32, digit: Bool) -> Dc:
467  match digit:
468    case True{}:
469      DcSize{(acc * 16 + hexv(c) : U32), True{}}
470    case False{}:
471      dc.size.end(acc, c, seen)
472
473# RFC 9112 §7.1.1: recipients ignore unknown chunk extensions; a bare LF is not CRLF.
474def dc.ext.lf(+n: U32, lf: Bool) -> Dc:
475  match lf:
476    case True{}:
477      DcBad{}
478    case False{}:
479      DcExt{n}
480
481def dc.ext(+n: U32, +c: U32, cr: Bool) -> Dc:
482  match cr:
483    case True{}:
484      DcSizeLf{n}
485    case False{}:
486      dc.ext.lf(n, U32.is_eq(c, 10))
487
488def dc.sized(+n: U32, zero: Bool) -> Dc:
489  match zero:
490    case True{}:
491      DcTr{2}
492    case False{}:
493      DcData{n}
494
495def dc.size.lf(+n: U32, lf: Bool) -> Dc:
496  match lf:
497    case True{}:
498      dc.sized(n, U32.is_eq(n, 0))
499    case False{}:
500      DcBad{}
501
502# k data bytes are left and m of them were just read.
503def dc.data(+k: U32, +m: U32) -> Dc:
504  Bool.pick(Dc, U32.is_le(k, m), DcDataCr{}, DcData{(k - m : U32)})
505
506def dc.pick(ok: Bool, next: Dc) -> Dc:
507  match ok:
508    case True{}:
509      next
510    case False{}:
511      DcBad{}
512
513# ponytail: trailer fields are dropped unparsed (§7.1.2 allows discarding)
514def dc.tr.hit(+j: U32) -> Dc:
515  Bool.pick(Dc, U32.is_eq(j, 3), DcDone{}, DcTr{(j + 1 : U32)})
516
517def dc.tr(+j: U32, +c: U32, hit: Bool) -> Dc:
518  match hit:
519    case True{}:
520      dc.tr.hit(j)
521    case False{}:
522      Bool.pick(Dc, U32.is_eq(c, 13), DcTr{1}, DcTr{0})
523
524def dc.tr.want(+j: U32) -> U32:
525  Bool.pick(U32, U32.is_eq(U32.mod(j, 2), 0), 13, 10)
526
527def dc.byte(h: Char) -> U32:
528  Chr{c} = h
529  c
530
531# One byte c. Done and Bad never look at it, and a data byte is not inspected.
532def dc.step(st: Dc, +c: U32) -> Dc:
533  match st:
534    case DcBad{}:
535      DcBad{}
536    case DcSize{+acc, seen}:
537      dc.size(acc, seen, c, Bool.and(is_hex(c), U32.is_le(acc, 268435455)))
538    case DcExt{+n}:
539      dc.ext(n, c, U32.is_eq(c, 13))
540    case DcSizeLf{+n}:
541      dc.size.lf(n, U32.is_eq(c, 10))
542    case DcDataCr{}:
543      dc.pick(U32.is_eq(c, 13), DcDataLf{})
544    case DcDataLf{}:
545      dc.pick(U32.is_eq(c, 10), dc.start())
546    case DcTr{+j}:
547      dc.tr(j, c, U32.is_eq(c, dc.tr.want(j)))
548    case DcData{+k}:
549      dc.data(k, 1)
550    case DcDone{}:
551      DcDone{}
552
553def dc.live(st: Dc) -> Bool:
554  match st:
555    case DcDone{}:
556      False{}
557    case DcBad{}:
558      False{}
559    case DcSize{a, b}:
560      True{}
561    case DcExt{n}:
562      True{}
563    case DcSizeLf{n}:
564      True{}
565    case DcData{k}:
566      True{}
567    case DcDataCr{}:
568      True{}
569    case DcDataLf{}:
570      True{}
571    case DcTr{j}:
572      True{}
573
574def dc.feed(s: String, st: Dc, racc: String) -> Dco:
575  match s:
576    case SNil{}:
577      Dco{st, racc, ""}
578    case SCon{h, t}:
579      match st:
580        case DcBad{}:
581          Dco{DcBad{}, racc, ""}
582        case DcDone{}:
583          Dco{DcDone{}, racc, SCon{h, t}}
584        case DcData{+k}:
585          dc.feed(t, dc.data(k, 1), SCon{h, racc})
586        case DcSize{+acc, seen}:
587          dc.feed(t, dc.step(DcSize{acc, seen}, dc.byte(h)), racc)
588        case DcExt{+n}:
589          dc.feed(t, dc.step(DcExt{n}, dc.byte(h)), racc)
590        case DcSizeLf{+n}:
591          dc.feed(t, dc.step(DcSizeLf{n}, dc.byte(h)), racc)
592        case DcDataCr{}:
593          dc.feed(t, dc.step(DcDataCr{}, dc.byte(h)), racc)
594        case DcDataLf{}:
595          dc.feed(t, dc.step(DcDataLf{}, dc.byte(h)), racc)
596        case DcTr{+j}:
597          dc.feed(t, dc.step(DcTr{j}, dc.byte(h)), racc)
598
599def chunk.hold(o: Dco) -> Hold:
600  Dco{st, racc, rest} = o
601  match st:
602    case DcDone{}:
603      HoldBody{String.reverse(racc)}
604    case DcBad{}:
605      HoldBad{}
606    case DcSize{a, b}:
607      HoldMore{}
608    case DcExt{n}:
609      HoldMore{}
610    case DcSizeLf{n}:
611      HoldMore{}
612    case DcData{k}:
613      HoldMore{}
614    case DcDataCr{}:
615      HoldMore{}
616    case DcDataLf{}:
617      HoldMore{}
618    case DcTr{j}:
619      HoldMore{}
620
621def chunk.live(s: String) -> Hold:
622  chunk.hold(dc.feed(s, dc.start(), ""))
623
624def chunk.rest(o: Dco) -> Maybe<&2, String>:
625  Dco{st, racc, rest} = o
626  match st:
627    case DcDone{}:
628      Some{rest}
629    case DcBad{}:
630      None{}
631    case DcSize{a, b}:
632      None{}
633    case DcExt{n}:
634      None{}
635    case DcSizeLf{n}:
636      None{}
637    case DcData{k}:
638      None{}
639    case DcDataCr{}:
640      None{}
641    case DcDataLf{}:
642      None{}
643    case DcTr{j}:
644      None{}
645
646def chunk.suffix(s: String) -> Maybe<&2, String>:
647  chunk.rest(dc.feed(s, dc.start(), ""))
648
649def chunk.decode.hold(h: Hold) -> Maybe<&2, String>:
650  match h:
651    case HoldMore{}:
652      None{}
653    case HoldBad{}:
654      None{}
655    case HoldUntil{b}:
656      None{}
657    case HoldBody{b}:
658      Some{b}
659
660def chunk.decode(s: String) -> Maybe<&2, String>:
661  chunk.decode.hold(chunk.live(s))
662
663def bytes.snd(r: Bytes.Bytes & Bytes.Bytes) -> Bytes.Bytes:
664  (a, b) = r
665  b
666
667def found.at(+from: U32, m: Maybe<&2, U32>) -> Maybe<&2, U32>:
668  match m:
669    case None{}:
670      None{}
671    case Some{+j}:
672      Some{(from + j : U32)}
673
674# A chunked body decoded from piece[i..]: a data run is sliced whole, and every other byte steps dc.
675type Cs is Type:
676  Cs{piece: Bytes.Bytes, i: U32, st: Dc, parts: List<&1, Bytes.Bytes>}
677
678# st: where the decoder stopped. parts: the data so far, newest first. rest: the bytes after a Done body.
679type Ck is Type:
680  Ck{st: Dc, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes}
681
682def ck.next(+len: U32, +i: U32, +st: Dc, piece: Bytes.Bytes, parts: List<&1, Bytes.Bytes>) -> Bool & Cs:
683  (Bool.and(U32.is_lt(i, len), dc.live(st)), Cs{piece, i, st, parts})
684
685def ck.data(+len: U32, +i: U32, +k: U32, +m: U32, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Bytes.Bytes) -> Bool & Cs:
686  (piece, run) = r
687  ck.next(len, (i + m : U32), dc.data(k, m), piece, Con{run, parts})
688
689def ck.byte(+len: U32, +i: U32, st: Dc, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Maybe<&2, U32>) -> Bool & Cs:
690  (piece, m) = r
691  match m:
692    case None{}:
693      ck.next(len, i, DcBad{}, piece, parts)
694    case Some{+c}:
695      ck.next(len, (i + 1 : U32), dc.step(st, c), piece, parts)
696
697def ck.step(+len: U32, cs: Cs) -> Bool & Cs:
698  Cs{piece, +i, st, parts} = cs
699  match st:
700    case DcData{+k}:
701      +m = U32.min(k, (len - i : U32))
702      ck.data(len, i, k, m, parts, Bytes.slice(piece, i, m))
703    case DcDone{}:
704      (False{}, Cs{piece, i, DcDone{}, parts})
705    case DcBad{}:
706      (False{}, Cs{piece, i, DcBad{}, parts})
707    case DcSize{+acc, seen}:
708      ck.byte(len, i, DcSize{acc, seen}, parts, Bytes.get(piece, i))
709    case DcExt{+n}:
710      ck.byte(len, i, DcExt{n}, parts, Bytes.get(piece, i))
711    case DcSizeLf{+n}:
712      ck.byte(len, i, DcSizeLf{n}, parts, Bytes.get(piece, i))
713    case DcDataCr{}:
714      ck.byte(len, i, DcDataCr{}, parts, Bytes.get(piece, i))
715    case DcDataLf{}:
716      ck.byte(len, i, DcDataLf{}, parts, Bytes.get(piece, i))
717    case DcTr{+j}:
718      ck.byte(len, i, DcTr{j}, parts, Bytes.get(piece, i))
719
720def ck.rest(st: Dc, parts: List<&1, Bytes.Bytes>, r: Bytes.Bytes & Bytes.Bytes) -> Ck:
721  (piece, rest) = r
722  Ck{st, parts, rest}
723
724def ck.end(+len: U32, cs: Cs) -> Ck:
725  Cs{piece, +i, st, parts} = cs
726  ck.rest(st, parts, Bytes.slice(piece, i, (len - i : U32)))
727
728# Each step reads at least one byte, so len steps are enough.
729def ck.feed(f: Nat, +len: U32, r: Bool & Cs) -> Ck:
730  match f:
731    case 0n:
732      (go, cs) = r
733      ck.end(len, cs)
734    case 1n+p:
735      (go, cs) = r
736      match go:
737        case False{}:
738          ck.end(len, cs)
739        case True{}:
740          ck.feed(p, len, ck.step(len, cs))
741
742def ck(piece: Bytes.Bytes, +st: Dc, parts: List<&1, Bytes.Bytes>) -> Ck:
743  Bytes.Bytes{+len, buf} = piece
744  ck.feed(U32.to_nat(len), len, ck.next(len, 0, st, Bytes.Bytes{len, buf}, parts))
745
746def parse.res.cl.have(+rest: String, +k: Nat, short: Bool) -> Hold:
747  match short:
748    case True{}:
749      HoldMore{}
750    case False{}:
751      HoldBody{String.take(rest, k)}
752
753def parse.res.cl.k(+rest: String, +k: Nat) -> Hold:
754  parse.res.cl.have(rest, k, Nat.is_lt(String.length(rest), k))
755
756def parse.res.cl.n(rest: String, n: Maybe<&2, U32>) -> Hold:
757  match n:
758    case None{}:
759      HoldBad{}
760    case Some{k}:
761      parse.res.cl.k(rest, U32.to_nat(k))
762
763def parse.body.cl(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold:
764  match hascl:
765    case False{}:
766      HoldBody{""}
767    case True{}:
768      parse.res.cl.n(rest, parse_u32(header(h, "content-length")))
769
770# RFC 9112 §6.3 (4): a request coding other than chunked cannot be framed.
771def parse.body.chunked.eq(rest: String, ok: Bool) -> Hold:
772  match ok:
773    case False{}:
774      HoldBad{}
775    case True{}:
776      chunk.live(rest)
777
778def parse.body.chunked.val(te: String, rest: String) -> Hold:
779  parse.body.chunked.eq(rest, String.eq(String.to_lower(te), "chunked"))
780
781def parse.body.chunked(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold:
782  match hascl:
783    case True{}:
784      HoldBad{}
785    case False{}:
786      parse.body.chunked.val(header.last(h, "transfer-encoding"), rest)
787
788def parse.body.te(+h: Map<&2, List<&2, String>>, rest: String, te: Bool) -> Hold:
789  match te:
790    case True{}:
791      parse.body.chunked(h, rest, has_header(h, "content-length"))
792    case False{}:
793      parse.body.cl(h, rest, has_header(h, "content-length"))
794
795def parse.body(+h: Map<&2, List<&2, String>>, rest: String) -> Hold:
796  parse.body.te(h, rest, has_header(h, "transfer-encoding"))
797
798# Bad: never a request. More: the head is not whole; read on. Head: the head is whole
799# and valid, and the body is still coming (req.body is empty). Req: a whole request.
800type Got is Type:
801  GotBad{}
802  GotMore{}
803  GotHead{req: Req}
804  GotReq{req: Req}
805
806def parse.finish(method: String, path: String, h: Map<&2, List<&2, String>>, body: Hold) -> Got:
807  match body:
808    case HoldBad{}:
809      GotBad{}
810    case HoldMore{}:
811      GotHead{Req{method, path, h, Bytes.new(0)}}
812    case HoldUntil{b}:
813      GotBad{}
814    case HoldBody{b}:
815      GotReq{Req{method, path, h, Bytes.from_string(b)}}
816
817def parse.host(method: String, path: String, +h: Map<&2, List<&2, String>>, rest: String, has: Bool) -> Got:
818  match has:
819    case False{}:
820      GotBad{}
821    case True{}:
822      parse.finish(method, path, h, parse.body(h, rest))
823
824# RFC 9112 §3.2: only HTTP/1.1 requires Host.
825def parse.fields(method: String, path: String, rest: String, v11: Bool, fs: Maybe<&2, Map<&2, List<&2, String>>>) -> Got:
826  match fs:
827    case None{}:
828      GotBad{}
829    case Some{+h}:
830      parse.host(method, path, h, rest, Bool.or(Bool.not(v11), has_header(h, "host")))
831
832def target_ok(+p: String) -> Bool:
833  Bool.or(String.starts_with(p, "/"), Bool.or(String.eq(p, "*"), String.starts_with(p, "http://")))
834
835def parse.target(method: String, +path: String, headers: String, rest: String, v11: Bool, ok: Bool) -> Got:
836  match ok:
837    case False{}:
838      GotBad{}
839    case True{}:
840      parse.fields(method, path, rest, v11, parse.headers.raw(headers))
841
842def parse.ver(method: String, +path: String, +v: String, headers: String, rest: String, ok: Bool) -> Got:
843  match ok:
844    case False{}:
845      GotBad{}
846    case True{}:
847      parse.target(method, path, headers, rest, String.eq(v, "HTTP/1.1"), target_ok(path))
848
849def parse.empty_method(method: String, path: String, +v: String, headers: String, rest: String, empty: Bool) -> Got:
850  match empty:
851    case True{}:
852      GotBad{}
853    case False{}:
854      parse.ver(method, path, v, headers, rest, Bool.or(String.eq(v, "HTTP/1.1"), String.eq(v, "HTTP/1.0")))
855
856def parse.start3(mpv: String & String & String, headers: String, rest: String) -> Got:
857  (+method, path, v) = mpv
858  parse.empty_method(method, path, v, headers, rest, String.is_empty(method))
859
860def parse.lines(lr: String & String, rest: String) -> Got:
861  (line, headers) = lr
862  parse.start3(start_line(line), headers, rest)
863
864
865def parse.head(+head: String, rest: String, whole: Bool) -> Got:
866  match whole:
867    case False{}:
868      GotMore{}
869    case True{}:
870      parse.lines(parse.first(head, SNil{}), rest)
871
872def parse.of(hb: String & String & Bool) -> Got:
873  (head, rest, whole) = hb
874  parse.head(head, rest, whole)
875
876# serve reads until this is not GotMore.
877def parse.got(+raw: String) -> Got:
878  parse.of(split_at_blank.flag.go(raw, raw, 0n, 0, False{}))
879
880
881def parse.req(g: Got) -> Maybe<&1, Req>:
882  match g:
883    case GotReq{req}:
884      Some{req}
885    case GotBad{}:
886      None{}
887    case GotMore{}:
888      None{}
889    case GotHead{req}:
890      None{}
891
892def parse(raw: String) -> Maybe<&1, Req>:
893  parse.req(parse.got(raw))
894
895def parse.res.cl(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold:
896  match hascl:
897    case False{}:
898      HoldBody{rest}
899    case True{}:
900      parse.res.cl.n(rest, parse_u32(header(h, "content-length")))
901
902def parse.res.chunked.eq(rest: String, ok: Bool) -> Hold:
903  match ok:
904    case False{}:
905      HoldUntil{rest}
906    case True{}:
907      chunk.live(rest)
908
909def parse.res.chunked.val(te: String, rest: String) -> Hold:
910  parse.res.chunked.eq(rest, String.eq(String.to_lower(te), "chunked"))
911
912def parse.res.chunked(+h: Map<&2, List<&2, String>>, rest: String, hascl: Bool) -> Hold:
913  match hascl:
914    case True{}:
915      HoldBad{}
916    case False{}:
917      parse.res.chunked.val(header.last(h, "transfer-encoding"), rest)
918
919def parse.res.te(+h: Map<&2, List<&2, String>>, rest: String, te: Bool) -> Hold:
920  match te:
921    case True{}:
922      parse.res.chunked(h, rest, has_header(h, "content-length"))
923    case False{}:
924      parse.res.cl(h, rest, has_header(h, "content-length"))
925
926def parse.res.body(+h: Map<&2, List<&2, String>>, rest: String) -> Hold:
927  parse.res.te(h, rest, has_header(h, "transfer-encoding"))
928
929# Bad: never a message. More: a valid prefix; read on. Done: a whole response.
930type Frame is Type:
931  FrameBad{}
932  FrameMore{}
933  FrameDone{res: Res}
934
935# RFC 9112 §8: a message cut short by close is incomplete, not valid.
936def frame.cut(closed: Bool) -> Frame:
937  match closed:
938    case True{}:
939      FrameBad{}
940    case False{}:
941      FrameMore{}
942
943def frame.wait(res: Res, wait: Bool) -> Frame:
944  match wait:
945    case True{}:
946      FrameMore{}
947    case False{}:
948      FrameDone{res}
949
950def frame.done(status: U32, h: Map<&2, List<&2, String>>, body: Hold, +closed: Bool, open: Bool) -> Frame:
951  match body:
952    case HoldBad{}:
953      FrameBad{}
954    case HoldMore{}:
955      frame.cut(closed)
956    case HoldUntil{b}:
957      frame.wait(Res{status, h, Bytes.from_string(b)}, Bool.not(closed))
958    case HoldBody{b}:
959      frame.wait(Res{status, h, Bytes.from_string(b)}, Bool.and(open, Bool.not(closed)))
960
961# RFC 9112 §6.3 (8): no TE and no CL ⇒ the body runs until the server closes.
962def frame.open(+h: Map<&2, List<&2, String>>) -> Bool:
963  Bool.not(Bool.or(has_header(h, "transfer-encoding"), has_header(h, "content-length")))
964
965# RFC 9112 §6.3 (1): HEAD, 1xx, 204 and 304 responses end at the blank line.
966def frame.nobody(+status: U32, head: Bool) -> Bool:
967  Bool.or(head, Bool.or(U32.is_lt(status, 200), Bool.or(U32.is_eq(status, 204), U32.is_eq(status, 304))))
968
969# 101 is the final response. Other 1xx are interim; a final response follows.
970def frame.interim(+status: U32) -> Bool:
971  Bool.and(U32.is_lt(status, 200), Bool.not(U32.is_eq(status, 101)))
972
973def frame.status(status: U32, +h: Map<&2, List<&2, String>>, rest: String, closed: Bool, nobody: Bool) -> Frame:
974  match nobody:
975    case True{}:
976      FrameDone{Res{status, h, Bytes.new(0)}}
977    case False{}:
978      frame.done(status, h, parse.res.body(h, rest), closed, frame.open(h))
979
980def parse.res.fields(+status: U32, rest: String, fs: Maybe<&2, Map<&2, List<&2, String>>>, closed: Bool, head: Bool) -> Frame:
981  match fs:
982    case None{}:
983      FrameBad{}
984    case Some{h}:
985      frame.status(status, h, rest, closed, frame.nobody(status, head))
986
987def parse.res.num(code: Maybe<&2, U32>, headers: String, rest: String, closed: Bool, head: Bool) -> Frame:
988  match code:
989    case None{}:
990      FrameBad{}
991    case Some{n}:
992      parse.res.fields(n, rest, parse.headers.raw(headers), closed, head)
993
994def parse.res.ver(code: String, headers: String, rest: String, closed: Bool, head: Bool, ok: Bool) -> Frame:
995  match ok:
996    case False{}:
997      FrameBad{}
998    case True{}:
999      parse.res.num(parse_u32(code), headers, rest, closed, head)
1000
1001def parse.res.start3(mpv: String & String & String, headers: String, rest: String, closed: Bool, head: Bool) -> Frame:
1002  (+ver, code, reason) = mpv
1003  parse.res.ver(code, headers, rest, closed, head, Bool.or(String.eq(ver, "HTTP/1.1"), String.eq(ver, "HTTP/1.0")))
1004
1005def parse.res.lines(lr: String & String, rest: String, closed: Bool, head: Bool) -> Frame:
1006  (line, headers) = lr
1007  parse.res.start3(start_line(line), headers, rest, closed, head)
1008
1009def parse.res.blank(hd: String, rest: String, +closed: Bool, head: Bool, ok: Bool) -> Frame:
1010  match ok:
1011    case False{}:
1012      frame.cut(closed)
1013    case True{}:
1014      parse.res.lines(parse.first(hd, SNil{}), rest, closed, head)
1015
1016def parse.res.of(hb: String & String, closed: Bool, head: Bool) -> Frame:
1017  (+hd, rest) = hb
1018  parse.res.blank(hd, rest, closed, head, blank_end(hd))
1019
1020type Lead is Data:
1021  LeadCut{}
1022  LeadFinal{}
1023  LeadSkip{+rest: String}
1024
1025def frame.lead.if(rest: String, interim: Bool) -> Lead:
1026  match interim:
1027    case True{}:
1028      LeadSkip{rest}
1029    case False{}:
1030      LeadFinal{}
1031
1032def frame.lead.num(n: Maybe<&2, U32>, rest: String) -> Lead:
1033  match n:
1034    case None{}:
1035      LeadFinal{}
1036    case Some{s}:
1037      frame.lead.if(rest, frame.interim(s))
1038
1039def frame.lead.code(mpv: String & String & String, rest: String) -> Lead:
1040  (ver, code, reason) = mpv
1041  frame.lead.num(parse_u32(code), rest)
1042
1043def frame.lead.lines(xs: List<&2, String>, rest: String) -> Lead:
1044  match xs:
1045    case Nil{}:
1046      LeadFinal{}
1047    case Con{line, headers}:
1048      frame.lead.code(start_line(line), rest)
1049
1050def frame.lead.blank(+hd: String, rest: String, ok: Bool) -> Lead:
1051  match ok:
1052    case False{}:
1053      LeadCut{}
1054    case True{}:
1055      frame.lead.lines(String.lines(hd), rest)
1056
1057def frame.lead(hb: String & String) -> Lead:
1058  (+hd, rest) = hb
1059  frame.lead.blank(hd, rest, blank_end(hd))
1060
1061def frame.at.cont(+raw: String, lead: Lead) -> String:
1062  match lead:
1063    case LeadCut{}:
1064      raw
1065    case LeadFinal{}:
1066      raw
1067    case LeadSkip{rest}:
1068      rest
1069
1070def frame.at.zero.go(+raw: String, closed: Bool, head: Bool, lead: Lead) -> Frame:
1071  match lead:
1072    case LeadCut{}:
1073      frame.cut(closed)
1074    case LeadFinal{}:
1075      parse.res.of(split_at_blank(raw), closed, head)
1076    case LeadSkip{rest}:
1077      FrameBad{}
1078
1079def frame.at.zero.of(+raw: String, closed: Bool, head: Bool) -> Frame:
1080  frame.at.zero.go(raw, closed, head, frame.lead(split_at_blank(raw)))
1081
1082def frame.at.pos.do(f: Nat, +raw: String, closed: Bool, head: Bool, hb: String & String, lead: Lead) -> Frame:
1083  match f:
1084    case 0n:
1085      match lead:
1086        case LeadCut{}:
1087          frame.cut(closed)
1088        case LeadFinal{}:
1089          parse.res.of(hb, closed, head)
1090        case LeadSkip{+rest}:
1091          frame.at.zero.of(rest, closed, head)
1092    case 1n+g:
1093      match lead:
1094        case LeadCut{}:
1095          frame.cut(closed)
1096        case LeadFinal{}:
1097          parse.res.of(hb, closed, head)
1098        case LeadSkip{+rest}:
1099          frame.at.pos.do(g, rest, closed, head, split_at_blank(rest), frame.lead(split_at_blank(rest)))
1100
1101def frame.at.zero(+raw: String, closed: Bool, head: Bool) -> Frame:
1102  frame.at.zero.of(raw, closed, head)
1103
1104# ponytail: 8 interim responses; raise the fuel if a server sends more
1105def frame.at(fuel: Nat, +raw: String, closed: Bool, head: Bool) -> Frame:
1106  match fuel:
1107    case 0n:
1108      frame.at.zero(raw, closed, head)
1109    case 1n+f:
1110      frame.at.pos.do(f, raw, closed, head, split_at_blank(raw), frame.lead(split_at_blank(raw)))
1111
1112# frame(bytes so far, server closed?, request was HEAD?)
1113def frame(raw: String, closed: Bool, head: Bool) -> Frame:
1114  frame.at(8n, raw, closed, head)
1115
1116def frame.res(f: Frame) -> Maybe<&1, Res>:
1117  match f:
1118    case FrameDone{res}:
1119      Some{res}
1120    case FrameBad{}:
1121      None{}
1122    case FrameMore{}:
1123      None{}
1124
1125def parse_res(raw: String) -> Maybe<&1, Res>:
1126  frame.res(frame(raw, True{}, False{}))
1127
1128def res_fields.res(res: Res, k: String) -> List<&2, String>:
1129  Res{status, headers, body} = res
1130  fields(headers, k)
1131
1132def res_fields(m: Maybe<&1, Res>, k: String) -> List<&2, String>:
1133  match m:
1134    case None{}:
1135      Nil{}
1136    case Some{res}:
1137      res_fields.res(res, k)
1138
1139# The response body as it arrives. Len: bytes left. Chunk: the decoder. Close: until the
1140# server closes. Done: the body is whole. Bad: it cannot become a body.
1141type Rb is Data:
1142  RbLen{left: U32}
1143  RbChunk{st: Dc}
1144  RbClose{}
1145  RbDone{}
1146  RbBad{}
1147
1148def rb.cl(n: Maybe<&2, U32>) -> Maybe<&2, Rb>:
1149  match n:
1150    case None{}:
1151      None{}
1152    case Some{+k}:
1153      Some{Bool.pick(Rb, U32.is_zero(k), RbDone{}, RbLen{k})}
1154
1155def rb.te(+h: Map<&2, List<&2, String>>, cl: Bool) -> Maybe<&2, Rb>:
1156  match cl:
1157    case True{}:
1158      None{}
1159    case False{}:
1160      Some{Bool.pick(Rb, String.eq(String.to_lower(header.last(h, "transfer-encoding")), "chunked"), RbChunk{dc.start()}, RbClose{})}
1161
1162def rb.len(+h: Map<&2, List<&2, String>>, te: Bool) -> Maybe<&2, Rb>:
1163  match te:
1164    case True{}:
1165      rb.te(h, has_header(h, "content-length"))
1166    case False{}:
1167      Bool.pick(Maybe<&2, Rb>, has_header(h, "content-length"), rb.cl(parse_u32(header(h, "content-length"))), Some{RbClose{}})
1168
1169# RFC 9112 §6.3: how the body of this response ends. None: it cannot be framed.
1170def rb.of(+h: Map<&2, List<&2, String>>, nobody: Bool) -> Maybe<&2, Rb>:
1171  match nobody:
1172    case True{}:
1173      Some{RbDone{}}
1174    case False{}:
1175      rb.len(h, has_header(h, "transfer-encoding"))
1176
1177# rb: how the body goes on. parts: body bytes so far, newest first. rest: bytes past a Done body.
1178type Bf is Type:
1179  Bf{rb: Rb, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes}
1180
1181def rb.len.cut(parts: List<&1, Bytes.Bytes>, +len: U32, +left: U32, r: Bytes.Bytes & Bytes.Bytes) -> Bf:
1182  (piece, data) = r
1183  Bf{RbDone{}, Con{data, parts}, bytes.snd(Bytes.slice(piece, left, (len - left : U32)))}
1184
1185def rb.len.have(short: Bool, +left: U32, parts: List<&1, Bytes.Bytes>, +len: U32, piece: Bytes.Bytes) -> Bf:
1186  match short:
1187    case True{}:
1188      Bf{RbLen{(left - len : U32)}, Con{piece, parts}, Bytes.new(0)}
1189    case False{}:
1190      rb.len.cut(parts, len, left, Bytes.slice(piece, 0, left))
1191
1192def rb.chunk(c: Ck) -> Bf:
1193  Ck{st, parts, rest} = c
1194  match st:
1195    case DcDone{}:
1196      Bf{RbDone{}, parts, rest}
1197    case DcBad{}:
1198      Bf{RbBad{}, parts, rest}
1199    case DcSize{a, b}:
1200      Bf{RbChunk{DcSize{a, b}}, parts, rest}
1201    case DcExt{x}:
1202      Bf{RbChunk{DcExt{x}}, parts, rest}
1203    case DcSizeLf{x}:
1204      Bf{RbChunk{DcSizeLf{x}}, parts, rest}
1205    case DcData{k}:
1206      Bf{RbChunk{DcData{k}}, parts, rest}
1207    case DcDataCr{}:
1208      Bf{RbChunk{DcDataCr{}}, parts, rest}
1209    case DcDataLf{}:
1210      Bf{RbChunk{DcDataLf{}}, parts, rest}
1211    case DcTr{j}:
1212      Bf{RbChunk{DcTr{j}}, parts, rest}
1213
1214# The body bytes in piece, added to parts.
1215def rb.feed(rb: Rb, parts: List<&1, Bytes.Bytes>, piece: Bytes.Bytes) -> Bf:
1216  match rb:
1217    case RbLen{+left}:
1218      Bytes.Bytes{+len, buf} = piece
1219      rb.len.have(U32.is_lt(len, left), left, parts, len, Bytes.Bytes{len, buf})
1220    case RbChunk{st}:
1221      rb.chunk(ck(piece, st, parts))
1222    case RbClose{}:
1223      Bf{RbClose{}, Con{piece, parts}, Bytes.new(0)}
1224    case RbDone{}:
1225      Bf{RbDone{}, parts, piece}
1226    case RbBad{}:
1227      Bf{RbBad{}, parts, piece}
1228
1229# RFC 9112 §8: only a close-delimited body may end at close.
1230def rb.eof(rb: Rb) -> Bool:
1231  match rb:
1232    case RbClose{}:
1233      True{}
1234    case RbDone{}:
1235      True{}
1236    case RbLen{n}:
1237      False{}
1238    case RbChunk{st}:
1239      False{}
1240    case RbBad{}:
1241      False{}
1242
1243def rb.body(parts: List<&1, Bytes.Bytes>) -> Bytes.Bytes:
1244  Bytes.concat(List.reverse(&1, Bytes.Bytes, parts))
1245
1246# The head scan. Seek: look for the blank line from byte from; k interim heads are behind.
1247# Wait: no blank line yet. Res: the final head, how its body ends, and the bytes after it.
1248type Hs is Type:
1249  HsSeek{buf: Bytes.Bytes, from: U32, k: U32}
1250  HsWait{buf: Bytes.Bytes, k: U32}
1251  HsBad{}
1252  HsRes{res: Res, rb: Rb, rest: Bytes.Bytes}
1253
1254def hs.final(+status: U32, +h: Map<&2, List<&2, String>>, rest: Bytes.Bytes, m: Maybe<&2, Rb>) -> Hs:
1255  match m:
1256    case None{}:
1257      HsBad{}
1258    case Some{rb}:
1259      HsRes{Res{status, h, Bytes.new(0)}, rb, rest}
1260
1261def hs.skip(more: Bool, +k: U32, rest: Bytes.Bytes) -> Hs:
1262  match more:
1263    case True{}:
1264      HsSeek{rest, 0, (k + 1 : U32)}
1265    case False{}:
1266      HsBad{}
1267
1268def hs.pick(interim: Bool, +head: Bool, +k: U32, +status: U32, +h: Map<&2, List<&2, String>>, rest: Bytes.Bytes) -> Hs:
1269  match interim:
1270    case True{}:
1271      hs.skip(U32.is_lt(k, 8), k, rest)
1272    case False{}:
1273      hs.final(status, h, rest, rb.of(h, frame.nobody(status, head)))
1274
1275def hs.res(+head: Bool, +k: U32, rest: Bytes.Bytes, res: Res) -> Hs:
1276  Res{+status, +h, body} = res
1277  hs.pick(frame.interim(status), head, k, status, h, rest)
1278
1279# The head alone, framed as a HEAD response so it stops at the blank line.
1280def hs.parsed(+head: Bool, +k: U32, rest: Bytes.Bytes, f: Frame) -> Hs:
1281  match f:
1282    case FrameDone{res}:
1283      hs.res(head, k, rest, res)
1284    case FrameBad{}:
1285      HsBad{}
1286    case FrameMore{}:
1287      HsBad{}
1288
1289def hs.split(+head: Bool, +k: U32, +hlen: U32, +len: U32, r: Bytes.Bytes & Bytes.Bytes) -> Hs:
1290  (buf, hb) = r
1291  hs.parsed(head, k, bytes.snd(Bytes.slice(buf, hlen, (len - hlen : U32))), parse.res.of(split_at_blank(Bytes.to_string(hb)), False{}, True{}))
1292
1293def hs.hit(+head: Bool, +k: U32, +len: U32, buf: Bytes.Bytes, at: Maybe<&2, U32>) -> Hs:
1294  match at:
1295    case None{}:
1296      HsWait{buf, k}
1297    case Some{+i}:
1298      +hlen = (i + 4 : U32)
1299      hs.split(head, k, hlen, len, Bytes.slice(buf, 0, hlen))
1300
1301def hs.seek.of(+head: Bool, +k: U32, +len: U32, +from: U32, buf: Bytes.Bytes, r: Bytes.Bytes & Maybe<&2, U32>) -> Hs:
1302  (tail, m) = r
1303  hs.hit(head, k, len, buf, found.at(from, m))
1304
1305def hs.seek(+head: Bool, +k: U32, +len: U32, +from: U32, r: Bytes.Bytes & Bytes.Bytes) -> Hs:
1306  (buf, tail) = r
1307  hs.seek.of(head, k, len, from, buf, Bytes.find(tail, "\r\n\r\n"))
1308
1309def hs.step(+head: Bool, st: Hs) -> Hs:
1310  match st:
1311    case HsSeek{buf, +from, +k}:
1312      Bytes.Bytes{+len, b} = buf
1313      hs.seek(head, k, len, from, Bytes.slice(Bytes.Bytes{len, b}, from, (len - from : U32)))
1314    case HsWait{buf, k}:
1315      HsWait{buf, k}
1316    case HsBad{}:
1317      HsBad{}
1318    case HsRes{res, rb, rest}:
1319      HsRes{res, rb, rest}
1320
1321# Each step ends the scan or passes one interim head, and at most 8 interim heads pass.
1322def hs.scan(fuel: Nat, +head: Bool, st: Hs) -> Hs:
1323  match fuel:
1324    case 0n:
1325      match st:
1326        case HsSeek{buf, from, k}:
1327          HsBad{}
1328        case HsWait{buf, k}:
1329          HsWait{buf, k}
1330        case HsBad{}:
1331          HsBad{}
1332        case HsRes{res, rb, rest}:
1333          HsRes{res, rb, rest}
1334    case 1n+f:
1335      hs.scan(f, head, hs.step(head, st))
1336
1337# piece joins buf; only the new bytes, and the 3 before them, can finish the blank line.
1338def hs.more(+head: Bool, +k: U32, buf: Bytes.Bytes, piece: Bytes.Bytes) -> Hs:
1339  Bytes.Bytes{+ol, ob} = buf
1340  hs.scan(10n, head, HsSeek{Bytes.append(Bytes.Bytes{ol, ob}, piece), Bool.pick(U32, U32.is_lt(ol, 3), 0, (ol - 3 : U32)), k})
1341
1342# Untrusted servers must not grow the buffer without bound.
1343def fetch.max() -> Nat:
1344  U32.to_nat(16777216)
1345
1346# A response read in pieces. Head: the head is not whole yet. Body: it is, and parts is the body so far.
1347# Len: a Content-Length body in one buffer of that size, with left bytes still to come.
1348type Rv is Type:
1349  RvHead{buf: Bytes.Bytes, k: U32}
1350  RvBody{res: Res, rb: Rb, parts: List<&1, Bytes.Bytes>}
1351  RvLen{res: Res, left: U32, body: Bytes.Bytes}
1352
1353# More: read on. Done: the response, and the bytes after it when the body is self-delimited.
1354type Rt is Type:
1355  RtMore{rv: Rv}
1356  RtBad{}
1357  RtDone{res: Res, rest: Maybe<&1, Bytes.Bytes>}
1358
1359def rv.start() -> Rv:
1360  RvHead{Bytes.new(0), 0}
1361
1362def rv.whole(res: Res, b: Bytes.Bytes, rest: Maybe<&1, Bytes.Bytes>) -> Rt:
1363  Res{status, headers, body} = res
1364  RtDone{Res{status, headers, b}, rest}
1365
1366def rv.done(res: Res, parts: List<&1, Bytes.Bytes>, rest: Maybe<&1, Bytes.Bytes>) -> Rt:
1367  rv.whole(res, rb.body(parts), rest)
1368
1369def rl.put.of(+total: U32, +len: U32, r: Array<U32> & Array<U32>) -> Bytes.Bytes & Bytes.Bytes:
1370  (src, dst) = r
1371  (Bytes.Bytes{total, dst}, Bytes.Bytes{len, src})
1372
1373# The first m bytes of piece, written into body after the total - left bytes already there.
1374def rl.put(+m: U32, +left: U32, body: Bytes.Bytes, piece: Bytes.Bytes) -> Bytes.Bytes & Bytes.Bytes:
1375  Bytes.Bytes{+total, dst} = body
1376  Bytes.Bytes{+len, src} = piece
1377  rl.put.of(total, len, Bytes.copy(m, src, dst, 0, (total - left : U32)))
1378
1379def rl.fed(short: Bool, res: Res, +left: U32, +len: U32, r: Bytes.Bytes & Bytes.Bytes) -> Rt:
1380  match short:
1381    case True{}:
1382      (body, piece) = r
1383      RtMore{RvLen{res, (left - len : U32), body}}
1384    case False{}:
1385      (body, piece) = r
1386      rv.whole(res, body, Some{bytes.snd(Bytes.slice(piece, left, (len - left : U32)))})
1387
1388def rl.feed(res: Res, +left: U32, body: Bytes.Bytes, piece: Bytes.Bytes) -> Rt:
1389  Bytes.Bytes{+len, buf} = piece
1390  rl.fed(U32.is_lt(len, left), res, left, len, rl.put(U32.min(len, left), left, body, Bytes.Bytes{len, buf}))
1391
1392def rv.fed(res: Res, bf: Bf) -> Rt:
1393  Bf{rb, parts, rest} = bf
1394  match rb:
1395    case RbDone{}:
1396      rv.done(res, parts, Some{rest})
1397    case RbBad{}:
1398      RtBad{}
1399    case RbLen{n}:
1400      RtMore{RvBody{res, RbLen{n}, parts}}
1401    case RbChunk{st}:
1402      RtMore{RvBody{res, RbChunk{st}, parts}}
1403    case RbClose{}:
1404      RtMore{RvBody{res, RbClose{}, parts}}
1405
1406# Some{left}: a Content-Length body small enough to allocate up front. Larger ones fail at the fetch cap.
1407def rb.buf(rb: Rb) -> Maybe<&2, U32>:
1408  match rb:
1409    case RbLen{+left}:
1410      Bool.pick(Maybe<&2, U32>, Nat.is_lt(fetch.max(), U32.to_nat(left)), None{}, Some{left})
1411    case RbChunk{st}:
1412      None{}
1413    case RbClose{}:
1414      None{}
1415    case RbDone{}:
1416      None{}
1417    case RbBad{}:
1418      None{}
1419
1420def rv.body(m: Maybe<&2, U32>, res: Res, rb: Rb, rest: Bytes.Bytes) -> Rt:
1421  match m:
1422    case None{}:
1423      rv.fed(res, rb.feed(rb, Nil{}, rest))
1424    case Some{+left}:
1425      rl.feed(res, left, Bytes.new(left), rest)
1426
1427def rv.head(hs: Hs) -> Rt:
1428  match hs:
1429    case HsWait{buf, k}:
1430      RtMore{RvHead{buf, k}}
1431    case HsBad{}:
1432      RtBad{}
1433    case HsSeek{buf, from, k}:
1434      RtBad{}
1435    case HsRes{res, +rb, rest}:
1436      rv.body(rb.buf(rb), res, rb, rest)
1437
1438def rv.eof(res: Res, parts: List<&1, Bytes.Bytes>, ok: Bool) -> Rt:
1439  match ok:
1440    case True{}:
1441      rv.done(res, parts, None{})
1442    case False{}:
1443      RtBad{}
1444
1445# One read: piece, or the server closed (RFC 9112 §8: a message cut short is not a message).
1446def rv.step(closed: Bool, +head: Bool, rv: Rv, piece: Bytes.Bytes) -> Rt:
1447  match closed:
1448    case True{}:
1449      match rv:
1450        case RvHead{buf, k}:
1451          RtBad{}
1452        case RvBody{res, rb, parts}:
1453          rv.eof(res, parts, rb.eof(rb))
1454        case RvLen{res, left, body}:
1455          RtBad{}
1456    case False{}:
1457      match rv:
1458        case RvHead{buf, +k}:
1459          rv.head(hs.more(head, k, buf, piece))
1460        case RvBody{res, rb, parts}:
1461          rv.fed(res, rb.feed(rb, parts, piece))
1462        case RvLen{res, +left, body}:
1463          rl.feed(res, left, body, piece)
1464
1465def rv.end(closed: Bool, +head: Bool, t: Rt) -> Rt:
1466  match closed:
1467    case False{}:
1468      t
1469    case True{}:
1470      match t:
1471        case RtMore{rv}:
1472          rv.step(True{}, head, rv, Bytes.new(0))
1473        case RtBad{}:
1474          RtBad{}
1475        case RtDone{res, rest}:
1476          RtDone{res, rest}
1477
1478def rv.go(xs: List<&2, String>, closed: Bool, +head: Bool, t: Rt) -> Rt:
1479  match xs:
1480    case Nil{}:
1481      rv.end(closed, head, t)
1482    case Con{+x, rest}:
1483      match t:
1484        case RtMore{rv}:
1485          rv.go(rest, closed, head, rv.step(False{}, head, rv, Bytes.from_string(x)))
1486        case RtBad{}:
1487          RtBad{}
1488        case RtDone{res, r}:
1489          RtDone{res, r}
1490
1491def rt.frame(t: Rt) -> Frame:
1492  match t:
1493    case RtMore{rv}:
1494      FrameMore{}
1495    case RtBad{}:
1496      FrameBad{}
1497    case RtDone{res, rest}:
1498      FrameDone{res}
1499
1500# What the read loop makes of these pieces, then a close when closed is set.
1501def res.frame(xs: List<&2, String>, closed: Bool, +head: Bool) -> Frame:
1502  rt.frame(rv.go(xs, closed, head, RtMore{rv.start()}))
1503
1504def rt.rest(t: Rt) -> Maybe<&1, Bytes.Bytes>:
1505  match t:
1506    case RtMore{rv}:
1507      None{}
1508    case RtBad{}:
1509      None{}
1510    case RtDone{res, rest}:
1511      rest
1512
1513def after.of(m: Maybe<&1, Bytes.Bytes>) -> Maybe<&2, String>:
1514  match m:
1515    case None{}:
1516      None{}
1517    case Some{b}:
1518      Some{Bytes.to_string(b)}
1519
1520# Bytes after a complete self-delimited message. None if it is not one yet.
1521def after(+raw: String, +head: Bool) -> Maybe<&2, String>:
1522  after.of(rt.rest(rv.go([raw], False{}, head, RtMore{rv.start()})))
1523
1524def req.v11.ver(mpv: String & String & String) -> Bool:
1525  (+method, path, v) = mpv
1526  String.eq(v, "HTTP/1.1")
1527
1528def req.v11.line(+line: String) -> Bool:
1529  req.v11.ver(start_line(line))
1530
1531def req.v11.lines(xs: List<&2, String>) -> Bool:
1532  match xs:
1533    case Nil{}:
1534      False{}
1535    case Con{line, headers}:
1536      req.v11.line(line)
1537
1538def req.v11.pick(+head: String, ok: Bool) -> Bool:
1539  match ok:
1540    case False{}:
1541      False{}
1542    case True{}:
1543      req.v11.lines(String.lines(head))
1544
1545def req.v11.of(hb: String & String) -> Bool:
1546  (+head, rest) = hb
1547  req.v11.pick(head, blank_end(head))
1548
1549def req.v11(+raw: String) -> Bool:
1550  req.v11.of(split_at_blank(raw))
1551
1552def req.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool:
1553  String.eq(String.to_lower(header(headers, "connection")), "close")
1554
1555def res.conn_close(+headers: Map<&2, List<&2, String>>) -> Bool:
1556  String.eq(String.to_lower(header(headers, "connection")), "close")
1557
1558def serve.fail_close(+status: U32) -> Bool:
1559  Bool.or(U32.is_eq(status, 400), Bool.or(U32.is_eq(status, 413), U32.is_eq(status, 431)))
1560
1561def serve.keep.ok(fail: Bool, res_close: Bool) -> Bool:
1562  match fail:
1563    case True{}:
1564      False{}
1565    case False{}:
1566      Bool.not(res_close)
1567
1568def serve.keep.v11(v11: Bool, +req_close: Bool, +status: U32, res_close: Bool) -> Bool:
1569  match v11:
1570    case False{}:
1571      False{}
1572    case True{}:
1573      match req_close:
1574        case True{}:
1575          False{}
1576        case False{}:
1577          serve.keep.ok(serve.fail_close(status), res_close)
1578
1579def serve.keep(+headers: Map<&2, List<&2, String>>, v11: Bool, +status: U32, +res: Map<&2, List<&2, String>>) -> Bool:
1580  serve.keep.v11(v11, req.conn_close(headers), status, res.conn_close(res))
1581
1582
1583def again.keep(close: Bool, x: Bytes.Bytes) -> Maybe<&1, Bytes.Bytes>:
1584  match close:
1585    case True{}:
1586      None{}
1587    case False{}:
1588      Some{x}
1589
1590def again.rest(close: Bool, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>:
1591  match rest:
1592    case None{}:
1593      None{}
1594    case Some{x}:
1595      again.keep(close, x)
1596
1597# The bytes past the response when the socket can take another request: the request did not
1598# ask to close, the response is self-delimited, and the peer did not say close.
1599def again(close: Bool, +h: Map<&2, List<&2, String>>, rest: Maybe<&1, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>:
1600  match close:
1601    case True{}:
1602      None{}
1603    case False{}:
1604      again.rest(String.eq(String.to_lower(header(h, "connection")), "close"), rest)
1605
1606def res_body.res(res: Res) -> Bytes.Bytes:
1607  Res{status, headers, body} = res
1608  body
1609
1610def res_body(m: Maybe<&1, Res>) -> Bytes.Bytes:
1611  match m:
1612    case None{}:
1613      Bytes.new(0)
1614    case Some{res}:
1615      res_body.res(res)
1616
1617def text(res: Res) -> String:
1618  Enc.utf8.decode(res_body.res(res))
1619
1620# UTF-8 is checked, not repaired: bad UTF-8 in a string gives None (RFC 8259 §8.1).
1621def json(res: Res) -> Maybe<&1, Json.Val>:
1622  Json.parse.bytes(res_body.res(res))
1623
1624def got_body.req(req: Req) -> Bytes.Bytes:
1625  Req{method, path, headers, body} = req
1626  body
1627
1628def got_body(m: Maybe<&1, Req>) -> Bytes.Bytes:
1629  match m:
1630    case None{}:
1631      Bytes.new(0)
1632    case Some{req}:
1633      got_body.req(req)
1634
1635def body.of(hb: String & String) -> String:
1636  (h, b) = hb
1637  b
1638
1639def body(s: String) -> String:
1640  body.of(split_at_blank.go(s, False{}, 0, SNil{}))
1641
1642def headers_lines(+k: String, vs: List<&2, String>, acc: String) -> String:
1643  match vs:
1644    case Nil{}:
1645      acc
1646    case Con{v, t}:
1647      headers_lines(k, t, acc ++ ("\r\n" ++ sanitize(k) ++ ": " ++ sanitize(v)))
1648
1649def headers_block.go(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, acc: String) -> String:
1650  match xs:
1651    case Nil{}:
1652      acc
1653    case (k, vs) <> t:
1654      headers_block.go(t, headers_lines(k, vs, acc))
1655
1656def headers_block(h: Map<&2, List<&2, String>>) -> String:
1657  headers_block.go(Map.to_list(&2, List<&2, String>, h), "")
1658
1659# RFC 9110 §15. An unknown code gets an empty reason, which RFC 9112 §4 allows.
1660type Reason is Data:
1661  Reason{code: U32, text: String}
1662
1663def reasons() -> List<&2, Reason>:
1664  [Reason{100, "Continue"}, Reason{101, "Switching Protocols"}, Reason{103, "Early Hints"},
1665   Reason{200, "OK"}, Reason{201, "Created"}, Reason{202, "Accepted"}, Reason{203, "Non-Authoritative Information"}, Reason{204, "No Content"}, Reason{205, "Reset Content"}, Reason{206, "Partial Content"},
1666   Reason{300, "Multiple Choices"}, Reason{301, "Moved Permanently"}, Reason{302, "Found"}, Reason{303, "See Other"}, Reason{304, "Not Modified"}, Reason{307, "Temporary Redirect"}, Reason{308, "Permanent Redirect"},
1667   Reason{400, "Bad Request"}, Reason{401, "Unauthorized"}, Reason{402, "Payment Required"}, Reason{403, "Forbidden"}, Reason{404, "Not Found"}, Reason{405, "Method Not Allowed"}, Reason{406, "Not Acceptable"}, Reason{407, "Proxy Authentication Required"}, Reason{408, "Request Timeout"}, Reason{409, "Conflict"}, Reason{410, "Gone"}, Reason{411, "Length Required"}, Reason{412, "Precondition Failed"}, Reason{413, "Content Too Large"}, Reason{414, "URI Too Long"}, Reason{415, "Unsupported Media Type"}, Reason{416, "Range Not Satisfiable"}, Reason{417, "Expectation Failed"}, Reason{421, "Misdirected Request"}, Reason{422, "Unprocessable Content"}, Reason{426, "Upgrade Required"}, Reason{428, "Precondition Required"}, Reason{429, "Too Many Requests"}, Reason{431, "Request Header Fields Too Large"},
1668   Reason{500, "Internal Server Error"}, Reason{501, "Not Implemented"}, Reason{502, "Bad Gateway"}, Reason{503, "Service Unavailable"}, Reason{504, "Gateway Timeout"}, Reason{505, "HTTP Version Not Supported"}]
1669
1670def reason.go(xs: List<&2, Reason>, +status: U32) -> String:
1671  match xs:
1672    case Nil{}:
1673      ""
1674    case Con{Reason{k, v}, t}:
1675      Bool.pick(String, U32.is_eq(k, status), v, reason.go(t, status))
1676
1677def reason(+status: U32) -> String:
1678  reason.go(reasons(), status)
1679
1680def response(+status: U32, headers: Map<&2, List<&2, String>>, body: String) -> String:
1681  ("HTTP/1.1 " ++ U32.show(status) ++ " " ++ reason(status) ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body)
1682
1683def encode.body(head: Bool, top: Bytes.Bytes, body: Bytes.Bytes) -> Bytes.Bytes:
1684  match head:
1685    case True{}:
1686      top
1687    case False{}:
1688      Bytes.append(top, body)
1689
1690def encode.len(+status: U32, headers: Map<&2, List<&2, String>>, head: Bool, body: Bytes.Bytes) -> Bytes.Bytes:
1691  Bytes.Bytes{+len, buf} = body
1692  encode.body(head, Bytes.from_string(response(status, set(headers, "content-length", U32.show(len)), "")), Bytes.Bytes{len, buf})
1693
1694# RFC 9110 §6.4.1, §8.6: 1xx, 204 and 304 have no body; HEAD gets the length but no body.
1695def encode.pick(+status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, head: Bool, nobody: Bool) -> Bytes.Bytes:
1696  match nobody:
1697    case True{}:
1698      Bytes.from_string(response(status, headers, ""))
1699    case False{}:
1700      encode.len(status, headers, head, body)
1701
1702def encode.on(r: Res, head: Bool) -> Bytes.Bytes:
1703  Res{+status, headers, body} = r
1704  encode.pick(status, headers, body, head, frame.nobody(status, False{}))
1705
1706def encode(r: Res) -> Bytes.Bytes:
1707  encode.on(r, False{})
1708
1709def request(method: String, path: String, headers: Map<&2, List<&2, String>>, body: String) -> String:
1710  (method ++ " " ++ path ++ " HTTP/1.1" ++ headers_block(headers)) ++ ("\r\n\r\n" ++ body)
1711
1712def req.reserved(+k: String) -> Bool:
1713  Bool.or(Bool.or(String.eq(k, "host"), String.eq(k, "connection")), Bool.or(String.eq(k, "content-length"), String.eq(k, "transfer-encoding")))
1714
1715def req.put.one(m: Map<&2, List<&2, String>>, +k: String, vs: List<&2, String>, skip: Bool) -> Map<&2, List<&2, String>>:
1716  match skip:
1717    case True{}:
1718      m
1719    case False{}:
1720      Map.set(&2, List<&2, String>, m, k, vs)
1721
1722def req.put(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
1723  match xs:
1724    case Nil{}:
1725      m
1726    case (k, vs) <> t:
1727      +lk = String.to_lower(k)
1728      req.put(t, req.put.one(m, lk, vs, req.reserved(lk)))
1729
1730# RFC 9110 §8.6: no Content-Length on a bodiless request whose method gives a body no meaning.
1731def req.cl(+m: Map<&2, List<&2, String>>, +method: String, +len: U32) -> Map<&2, List<&2, String>>:
1732  +bodied = Bool.or(Bool.not(U32.is_zero(len)), Bool.or(String.eq(method, "POST"), Bool.or(String.eq(method, "PUT"), String.eq(method, "PATCH"))))
1733  Bool.pick(Map<&2, List<&2, String>>, bodied, set(m, "content-length", U32.show(len)), m)
1734
1735# Caller headers are lowercased so they cannot duplicate ours; host, connection,
1736# content-length and transfer-encoding are always ours (no smuggling through them).
1737def encode_req.word(close: Bool) -> String:
1738  match close:
1739    case True{}:
1740      "close"
1741    case False{}:
1742      "keep-alive"
1743
1744def encode_req.on(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, close: Bool) -> Bytes.Bytes:
1745  Bytes.Bytes{+len, buf} = body
1746  +h = set(set(req.put(Map.to_list(&2, List<&2, String>, headers), empty()), "host", host), "connection", encode_req.word(close))
1747  Bytes.append(Bytes.from_string(request(method, target, req.cl(h, method, len), "")), Bytes.Bytes{len, buf})
1748
1749def encode_req(+method: String, target: String, host: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> Bytes.Bytes:
1750  encode_req.on(method, target, host, headers, body, True{})
1751
1752# Redirects (RFC 9110 §15.4, as WHATWG fetch applies them)
1753# --------------------------------------------------------
1754
1755# One request to make: headers are already lowercased (see req.put).
1756type Hop is Type:
1757  Hop{method: String, url: Url.Abs, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes}
1758
1759def redirect.code(+s: U32) -> Bool:
1760  Bool.or(Bool.or(U32.is_eq(s, 301), U32.is_eq(s, 302)), Bool.or(U32.is_eq(s, 303), Bool.or(U32.is_eq(s, 307), U32.is_eq(s, 308))))
1761
1762def redirect.origin(a: Url.Abs) -> String:
1763  Url.Abs{scheme, host, port, target} = a
1764  scheme ++ "://" ++ host ++ ":" ++ U32.show(port)
1765
1766def redirect.del(ks: List<&2, String>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
1767  match ks:
1768    case Nil{}:
1769      m
1770    case Con{k, t}:
1771      redirect.del(t, Map.del(&2, List<&2, String>, m, k))
1772
1773def redirect.hdrs(+h: Map<&2, List<&2, String>>, drop: Bool, cross: Bool) -> Map<&2, List<&2, String>>:
1774  +h2 = Bool.pick(Map<&2, List<&2, String>>, drop, redirect.del(["content-type", "content-encoding", "content-language", "content-location"], h), h)
1775  Bool.pick(Map<&2, List<&2, String>>, cross, redirect.del(["authorization", "cookie", "proxy-authorization"], h2), h2)
1776
1777# 303 turns any method but HEAD into GET; 301 and 302 turn POST into GET;
1778# 307 and 308 keep method and body. A GET carries no body or body headers.
1779# Credentials do not follow a hop to another origin.
1780def redirect.body(drop: Bool, body: Bytes.Bytes) -> Bytes.Bytes:
1781  match drop:
1782    case True{}:
1783      Bytes.new(0)
1784    case False{}:
1785      body
1786
1787def redirect.to(+status: U32, +method: String, +from: Url.Abs, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, next: Maybe<&2, Url.Abs>) -> Maybe<&1, Hop>:
1788  match next:
1789    case None{}:
1790      None{}
1791    case Some{+to}:
1792      +drop = Bool.or(Bool.and(U32.is_eq(status, 303), Bool.not(String.eq(method, "HEAD"))), Bool.and(Bool.or(U32.is_eq(status, 301), U32.is_eq(status, 302)), String.eq(method, "POST")))
1793      Some{Hop{Bool.pick(String, drop, "GET", method), to, redirect.hdrs(headers, drop, Bool.not(String.eq(redirect.origin(from), redirect.origin(to)))), redirect.body(drop, body)}}
1794
1795def redirect.hop(status: U32, h: Hop, loc: String) -> Maybe<&1, Hop>:
1796  Hop{method, +url, headers, body} = h
1797  redirect.to(status, method, url, headers, body, Url.resolve(url, loc))
1798
1799def redirect.if(status: U32, h: Hop, loc: String, ok: Bool) -> Maybe<&1, Hop>:
1800  match ok:
1801    case False{}:
1802      None{}
1803    case True{}:
1804      redirect.hop(status, h, loc)
1805
1806# The next request after a response, or None when it is not a redirect we follow.
1807def redirect(+status: U32, +headers: Map<&2, List<&2, String>>, h: Hop) -> Maybe<&1, Hop>:
1808  redirect.if(status, h, header(headers, "location"), Bool.and(redirect.code(status), has_header(headers, "location")))
1809
1810# Cookies (RFC 6265bis §5)
1811# ------------------------
1812
1813# A stored cookie. domain is lowercase, with no leading dot; host_only sends it to that host alone.
1814# expiry None is a session cookie. http_only and same_site ("strict", "lax", "none" or "default")
1815# are kept, not enforced: fetch has no browsing client, so its requests are same-site (§5.2).
1816type Cookie is Data:
1817  Cookie{name: String, value: String, domain: String, host_only: Bool, path: String, secure: Bool, http_only: Bool, same_site: String, expiry: Maybe<&2, Time.Instant>}
1818
1819# The cookies, oldest first, and the caller's clock. No cookie in it has expired by now.
1820type Jar is Data:
1821  Jar{now: Time.Instant, cookies: List<&2, Cookie>}
1822
1823def jar.new(now: Time.Instant) -> Jar:
1824  Jar{now, Nil{}}
1825
1826def jar.cookies(j: Jar) -> List<&2, Cookie>:
1827  Jar{now, cs} = j
1828  cs
1829
1830def cookie.live.at(+now: Time.Instant, e: Maybe<&2, Time.Instant>) -> Bool:
1831  match e:
1832    case None{}:
1833      True{}
1834    case Some{t}:
1835      Cmp.is_lt(Time.Instant.cmp(now, t))
1836
1837# Alive at now: no expiry, or an expiry after now.
1838def cookie.live(+now: Time.Instant, c: Cookie) -> Bool:
1839  Cookie{name, value, domain, host_only, path, secure, http_only, same_site, expiry} = c
1840  cookie.live.at(now, expiry)
1841
1842def jar.live(cs: List<&2, Cookie>, +now: Time.Instant) -> List<&2, Cookie>:
1843  match cs:
1844    case Nil{}:
1845      Nil{}
1846    case Con{+c, t}:
1847      List.filter.put(Cookie, c, jar.live(t, now), cookie.live(now, c))
1848
1849# The jar at a later time: cookies expired by now are evicted (§5.7).
1850def jar.at(j: Jar, +now: Time.Instant) -> Jar:
1851  Jar{old, cs} = j
1852  Jar{now, jar.live(cs, now)}
1853
1854def cookie.cut.found(r: String & String) -> String & String & Bool:
1855  (a, b) = r
1856  (a, b, True{})
1857
1858# hit: the last char pushed onto acc was c.
1859def cookie.cut.go(s: String, +c: U32, acc: String, hit: Bool) -> String & String & Bool:
1860  match s:
1861    case SNil{}:
1862      match hit:
1863        case True{}:
1864          cookie.cut.found(take.cut(acc, SNil{}))
1865        case False{}:
1866          (String.reverse(acc), SNil{}, False{})
1867    case SCon{Chr{+x}, t}:
1868      match hit:
1869        case True{}:
1870          cookie.cut.found(take.cut(acc, SCon{Chr{x}, t}))
1871        case False{}:
1872          cookie.cut.go(t, c, SCon{Chr{x}, acc}, U32.is_eq(x, c))
1873
1874# s split at its first c: before, after, and whether c was there.
1875def cookie.cut(s: String, +c: U32) -> String & String & Bool:
1876  cookie.cut.go(s, c, SNil{}, False{})
1877
1878def cookie.fst(r: String & String & Bool) -> String:
1879  (a, b, found) = r
1880  a
1881
1882# The path of an origin-form target: the part before "?".
1883def cookie.target_path(t: String) -> String:
1884  cookie.fst(cookie.cut(t, 63))
1885
1886# Longer than n octets.
1887def cookie.over(s: String, +n: U32) -> Bool:
1888  match s:
1889    case SNil{}:
1890      False{}
1891    case SCon{c, t}:
1892      Bool.or(U32.is_eq(n, 0), cookie.over(t, (n - 1 : U32)))
1893
1894# %x00-08 / %x0A-1F / %x7F: control characters other than HTAB.
1895def cookie.ctl(s: String) -> Bool:
1896  match s:
1897    case SNil{}:
1898      False{}
1899    case SCon{Chr{+c}, t}:
1900      Bool.or(Bool.or(U32.is_lt(c, 9), Bool.and(U32.is_gt(c, 9), U32.is_lt(c, 32))), Bool.or(U32.is_eq(c, 127), cookie.ctl(t)))
1901
1902def cookie.ascii(s: String) -> Bool:
1903  match s:
1904    case SNil{}:
1905      True{}
1906    case SCon{Chr{+c}, t}:
1907      Bool.and(U32.is_lt(c, 128), cookie.ascii(t))
1908
1909def cookie.ip4(s: String) -> Bool:
1910  match s:
1911    case SNil{}:
1912      True{}
1913    case SCon{Chr{+c}, t}:
1914      Bool.and(Bool.or(parse_u32.digit(c), U32.is_eq(c, 46)), cookie.ip4(t))
1915
1916# An IP literal rather than a host name: IPv6 has a colon, IPv4 is digits and dots.
1917def cookie.ip(+h: String) -> Bool:
1918  Bool.or(String.contains(h, ":"), cookie.ip4(h))
1919
1920# §5.1.3: s is d, or a host name under d on a label boundary.
1921def cookie.domain_match(+s: String, +d: String) -> Bool:
1922  Bool.or(String.eq(s, d), Bool.and(String.ends_with(s, "." ++ d), Bool.not(cookie.ip(s))))
1923
1924# A reversed path with its last segment and slash dropped.
1925def cookie.up(rs: String) -> String:
1926  match rs:
1927    case SNil{}:
1928      SNil{}
1929    case SCon{Chr{47}, t}:
1930      t
1931    case SCon{Chr{c}, t}:
1932      cookie.up(t)
1933
1934# §5.1.4: the request path up to its last "/", or "/".
1935def cookie.default_path(+p: String) -> String:
1936  +up = String.reverse(cookie.up(String.reverse(p)))
1937  Bool.pick(String, Bool.or(Bool.not(String.starts_with(p, "/")), String.is_empty(up)), "/", up)
1938
1939# §5.1.4: request path p path-matches cookie path c.
1940def cookie.path_match(+p: String, +c: String) -> Bool:
1941  Bool.or(String.eq(p, c), Bool.and(String.starts_with(p, c), Bool.or(String.ends_with(c, "/"), String.starts_with(String.drop(p, String.length(c)), "/"))))
1942
1943type Hms is Data:
1944  Hms{h: U32, m: U32, s: U32}
1945
1946# §5.1.1 cookie-date: the first time, day, month and year tokens found.
1947type Cdate is Data:
1948  Cdate{time: Maybe<&2, Hms>, day: Maybe<&2, U32>, month: Maybe<&2, U32>, year: Maybe<&2, U32>}
1949
1950def cookie.date.delim(+c: U32) -> Bool:
1951  Bool.or(U32.is_eq(c, 9), Bool.or(Bool.and(U32.is_ge(c, 32), U32.is_le(c, 47)), Bool.or(Bool.and(U32.is_ge(c, 59), U32.is_le(c, 64)), Bool.or(Bool.and(U32.is_ge(c, 91), U32.is_le(c, 96)), Bool.and(U32.is_ge(c, 123), U32.is_le(c, 126))))))
1952
1953def cookie.date.push(acc: String, ts: List<&2, String>) -> List<&2, String>:
1954  match acc:
1955    case SNil{}:
1956      ts
1957    case SCon{h, t}:
1958      Con{String.reverse(SCon{h, t}), ts}
1959
1960def cookie.date.step(d: Bool, +c: U32, acc: String, ts: List<&2, String>) -> String & List<&2, String>:
1961  match d:
1962    case True{}:
1963      (SNil{}, cookie.date.push(acc, ts))
1964    case False{}:
1965      (SCon{Chr{c}, acc}, ts)
1966
1967# The date-tokens, last first. st: the token so far (reversed) and the tokens before it.
1968def cookie.date.tokens(s: String, st: String & List<&2, String>) -> List<&2, String>:
1969  match s:
1970    case SNil{}:
1971      (acc, ts) = st
1972      cookie.date.push(acc, ts)
1973    case SCon{Chr{+c}, t}:
1974      (acc, ts) = st
1975      cookie.date.tokens(t, cookie.date.step(cookie.date.delim(c), c, acc, ts))
1976
1977def cookie.date.head(+s: String) -> Bool:
1978  match s:
1979    case SNil{}:
1980      False{}
1981    case SCon{Chr{+c}, t}:
1982      parse_u32.digit(c)
1983
1984# The digits at the start of s: (count, value, rest). d: s starts with a digit.
1985def cookie.date.run(s: String, d: Bool, +n: U32, +acc: U32) -> U32 & U32 & String:
1986  match s:
1987    case SNil{}:
1988      (n, acc, SNil{})
1989    case SCon{Chr{+c}, +t}:
1990      match d:
1991        case True{}:
1992          cookie.date.run(t, cookie.date.head(t), (n + 1 : U32), (acc * 10 + (c - 48 : U32) : U32))
1993        case False{}:
1994          (n, acc, SCon{Chr{c}, t})
1995
1996def cookie.date.within(r: U32 & U32 & String, +lo: U32, +hi: U32) -> Maybe<&1, U32 & String>:
1997  (+n, v, rest) = r
1998  Bool.pick(Maybe<&1, U32 & String>, Bool.and(U32.is_ge(n, lo), U32.is_le(n, hi)), Some{(v, rest)}, None{})
1999
2000# lo to hi digits and what follows them, which does not start with a digit.
2001def cookie.date.num(+s: String, +lo: U32, +hi: U32) -> Maybe<&1, U32 & String>:
2002  cookie.date.within(cookie.date.run(s, cookie.date.head(s), 0, 0), lo, hi)
2003
2004def cookie.date.val(m: Maybe<&1, U32 & String>) -> Maybe<&2, U32>:
2005  match m:
2006    case None{}:
2007      None{}
2008    case Some{(v, t)}:
2009      Some{v}
2010
2011def cookie.date.colon(s: String) -> Maybe<&2, String>:
2012  match s:
2013    case SNil{}:
2014      None{}
2015    case SCon{Chr{58}, t}:
2016      Some{t}
2017    case SCon{Chr{c}, t}:
2018      None{}
2019
2020def cookie.date.sec(+h: U32, +m: U32, r: Maybe<&1, U32 & String>) -> Maybe<&2, Hms>:
2021  match r:
2022    case None{}:
2023      None{}
2024    case Some{(s, t)}:
2025      Some{Hms{h, m, s}}
2026
2027def cookie.date.min.c(+h: U32, +m: U32, c: Maybe<&2, String>) -> Maybe<&2, Hms>:
2028  match c:
2029    case None{}:
2030      None{}
2031    case Some{+t}:
2032      cookie.date.sec(h, m, cookie.date.num(t, 1, 2))
2033
2034def cookie.date.min(+h: U32, r: Maybe<&1, U32 & String>) -> Maybe<&2, Hms>:
2035  match r:
2036    case None{}:
2037      None{}
2038    case Some{(m, t)}:
2039      cookie.date.min.c(h, m, cookie.date.colon(t))
2040
2041def cookie.date.hour.c(+h: U32, c: Maybe<&2, String>) -> Maybe<&2, Hms>:
2042  match c:
2043    case None{}:
2044      None{}
2045    case Some{+t}:
2046      cookie.date.min(h, cookie.date.num(t, 1, 2))
2047
2048def cookie.date.hour(r: Maybe<&1, U32 & String>) -> Maybe<&2, Hms>:
2049  match r:
2050    case None{}:
2051      None{}
2052    case Some{(h, t)}:
2053      cookie.date.hour.c(h, cookie.date.colon(t))
2054
2055# hms-time: 1*2DIGIT ":" 1*2DIGIT ":" 1*2DIGIT, then anything but a digit.
2056def cookie.date.time(+tok: String) -> Maybe<&2, Hms>:
2057  cookie.date.hour(cookie.date.num(tok, 1, 2))
2058
2059def cookie.date.month.go(xs: List<&2, String>, +w: String, +i: U32) -> Maybe<&2, U32>:
2060  match xs:
2061    case Nil{}:
2062      None{}
2063    case Con{x, t}:
2064      Bool.pick(Maybe<&2, U32>, String.eq(x, w), Some{i}, cookie.date.month.go(t, w, (i + 1 : U32)))
2065
2066# The month named by the first three letters, from 1.
2067def cookie.date.month(+tok: String) -> Maybe<&2, U32>:
2068  cookie.date.month.go(["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"], String.to_lower(String.take(tok, 3n)), 1)
2069
2070# Each try* below: the flag is set, so go on; or the token matches, so stop; or go on.
2071def cookie.date.year(y: Maybe<&2, U32>, +tok: String, t: Maybe<&2, Hms>, d: Maybe<&2, U32>, m: Maybe<&2, U32>) -> Cdate:
2072  match y:
2073    case None{}:
2074      Cdate{t, d, m, cookie.date.val(cookie.date.num(tok, 2, 4))}
2075    case Some{v}:
2076      Cdate{t, d, m, Some{v}}
2077
2078def cookie.date.month.got(g: Maybe<&2, U32>, +tok: String, t: Maybe<&2, Hms>, d: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2079  match g:
2080    case None{}:
2081      cookie.date.year(y, tok, t, d, None{})
2082    case Some{v}:
2083      Cdate{t, d, Some{v}, y}
2084
2085def cookie.date.month.try(m: Maybe<&2, U32>, +tok: String, t: Maybe<&2, Hms>, d: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2086  match m:
2087    case None{}:
2088      cookie.date.month.got(cookie.date.month(tok), tok, t, d, y)
2089    case Some{v}:
2090      cookie.date.year(y, tok, t, d, Some{v})
2091
2092def cookie.date.day.got(g: Maybe<&2, U32>, +tok: String, t: Maybe<&2, Hms>, m: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2093  match g:
2094    case None{}:
2095      cookie.date.month.try(m, tok, t, None{}, y)
2096    case Some{v}:
2097      Cdate{t, Some{v}, m, y}
2098
2099def cookie.date.day.try(d: Maybe<&2, U32>, +tok: String, t: Maybe<&2, Hms>, m: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2100  match d:
2101    case None{}:
2102      cookie.date.day.got(cookie.date.val(cookie.date.num(tok, 1, 2)), tok, t, m, y)
2103    case Some{v}:
2104      cookie.date.month.try(m, tok, t, Some{v}, y)
2105
2106def cookie.date.time.got(g: Maybe<&2, Hms>, +tok: String, d: Maybe<&2, U32>, m: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2107  match g:
2108    case None{}:
2109      cookie.date.day.try(d, tok, None{}, m, y)
2110    case Some{v}:
2111      Cdate{Some{v}, d, m, y}
2112
2113def cookie.date.time.try(t: Maybe<&2, Hms>, +tok: String, d: Maybe<&2, U32>, m: Maybe<&2, U32>, y: Maybe<&2, U32>) -> Cdate:
2114  match t:
2115    case None{}:
2116      cookie.date.time.got(cookie.date.time(tok), tok, d, m, y)
2117    case Some{v}:
2118      cookie.date.day.try(d, tok, Some{v}, m, y)
2119
2120def cookie.date.fold(ts: List<&2, String>, st: Cdate) -> Cdate:
2121  match ts:
2122    case Nil{}:
2123      st
2124    case Con{+tok, rest}:
2125      Cdate{t, d, m, y} = st
2126      cookie.date.fold(rest, cookie.date.time.try(t, tok, d, m, y))
2127
2128# 70–99 are 19xx, 0–69 are 20xx.
2129def cookie.date.fix(+y: U32) -> U32:
2130  Bool.pick(U32, U32.is_le(y, 69), (y + 2000 : U32), Bool.pick(U32, U32.is_le(y, 99), (y + 1900 : U32), y))
2131
2132def cookie.date.at(+y: U32, +d: U32, mo: U32, h: U32, mi: U32, s: U32) -> Maybe<&2, Time.Instant>:
2133  Bool.pick(Maybe<&2, Time.Instant>, Bool.and(U32.is_ge(y, 1601), Bool.and(U32.is_ge(d, 1), U32.is_le(d, 31))), Time.mk(Time.DateTime{Time.Date{y, mo, d}, h, mi, s, 0}), None{})
2134
2135def cookie.date.fin(st: Cdate) -> Maybe<&2, Time.Instant>:
2136  match st:
2137    case Cdate{Some{Hms{h, mi, s}}, Some{d}, Some{mo}, Some{y}}:
2138      cookie.date.at(cookie.date.fix(y), d, mo, h, mi, s)
2139    case _:
2140      None{}
2141
2142# §5.1.1: a cookie-date such as "Sun, 06 Nov 1994 08:49:37 GMT" or "Thu, 01-Jan-70 00:00:01 GMT".
2143# Time.mk refuses a day past the month's end and a time past 23:59:59.
2144def cookie.date(s: String) -> Maybe<&2, Time.Instant>:
2145  cookie.date.fin(cookie.date.fold(List.reverse(&2, String, cookie.date.tokens(s, (SNil{}, Nil{}))), Cdate{None{}, None{}, None{}, None{}}))
2146
2147# §5.6.1–§5.6.7: the last of each attribute. expires is the date as sent; max_age is capped seconds.
2148# Neither is a time from now yet: a Cav with a Time.Instant and now beside it is too wide (bendlang/bend#1069).
2149type Cav is Data:
2150  Cav{expires: Maybe<&2, Time.Instant>, max_age: Maybe<&2, U32>, domain: String, path: Maybe<&2, String>, secure: Bool, http_only: Bool, same_site: String}
2151
2152def cookie.cav() -> Cav:
2153  Cav{None{}, None{}, "", None{}, False{}, False{}, "default"}
2154
2155# §5.5: 400 days, the longest a cookie lives.
2156def cookie.limit() -> U32:
2157  34560000
2158
2159def cookie.later(+now: Time.Instant, +secs: U32) -> Time.Instant:
2160  Time.Instant.add(now, Time.Duration.of_secs(Int.I64.from_u32(secs)))
2161
2162def cookie.expires.cap(+now: Time.Instant, m: Maybe<&2, Time.Instant>) -> Maybe<&2, Time.Instant>:
2163  match m:
2164    case None{}:
2165      None{}
2166    case Some{+t}:
2167      +lim = cookie.later(now, cookie.limit())
2168      Some{Bool.pick(Time.Instant, Cmp.is_lt(Time.Instant.cmp(lim, t)), lim, t)}
2169
2170def cookie.max_age.zero(m: Maybe<&2, U32>) -> Maybe<&2, U32>:
2171  match m:
2172    case None{}:
2173      None{}
2174    case Some{s}:
2175      Some{0}
2176
2177def cookie.max_age.nz(s: String) -> String:
2178  match s:
2179    case SNil{}:
2180      SNil{}
2181    case SCon{Chr{48}, t}:
2182      cookie.max_age.nz(t)
2183    case SCon{Chr{c}, t}:
2184      SCon{Chr{c}, t}
2185
2186# More than 9 significant digits is past the limit (and would overflow parse_u32).
2187def cookie.max_age.cap(long: Bool, m: Maybe<&2, U32>) -> Maybe<&2, U32>:
2188  match m:
2189    case None{}:
2190      None{}
2191    case Some{s}:
2192      Some{Bool.pick(U32, long, cookie.limit(), s)}
2193
2194def cookie.max_age.secs(+v: String, neg: Bool) -> Maybe<&2, U32>:
2195  match neg:
2196    case True{}:
2197      cookie.max_age.zero(parse_u32(String.drop(v, 1n)))
2198    case False{}:
2199      cookie.max_age.cap(cookie.over(cookie.max_age.nz(v), 9), parse_u32(v))
2200
2201# Zero or less is the earliest time, so the cookie is already expired.
2202def cookie.max_age.at(+now: Time.Instant, m: Maybe<&2, U32>) -> Maybe<&2, Time.Instant>:
2203  match m:
2204    case None{}:
2205      None{}
2206    case Some{+s}:
2207      Some{Bool.pick(Time.Instant, U32.is_eq(s, 0), Time.Instant.epoch(), cookie.later(now, U32.min(s, cookie.limit())))}
2208
2209# §5.6.2: DIGIT*, or "-" and DIGIT*. Anything else is ignored.
2210def cookie.max_age(+v: String) -> Maybe<&2, U32>:
2211  cookie.max_age.secs(v, String.starts_with(v, "-"))
2212
2213def cookie.same_site(+v: String) -> String:
2214  +l = String.to_lower(v)
2215  Bool.pick(String, Bool.or(String.eq(l, "strict"), Bool.or(String.eq(l, "lax"), String.eq(l, "none"))), l, "default")
2216
2217type Av is Data:
2218  AvExpires{}
2219  AvMaxAge{}
2220  AvDomain{}
2221  AvPath{}
2222  AvSecure{}
2223  AvHttpOnly{}
2224  AvSameSite{}
2225  AvOther{}
2226
2227def cookie.av.kind(+k: String) -> Av:
2228  Bool.pick(Av, String.eq(k, "expires"), AvExpires{}, Bool.pick(Av, String.eq(k, "max-age"), AvMaxAge{}, Bool.pick(Av, String.eq(k, "domain"), AvDomain{}, Bool.pick(Av, String.eq(k, "path"), AvPath{}, Bool.pick(Av, String.eq(k, "secure"), AvSecure{}, Bool.pick(Av, String.eq(k, "httponly"), AvHttpOnly{}, Bool.pick(Av, String.eq(k, "samesite"), AvSameSite{}, AvOther{})))))))
2229
2230# An Expires or Max-Age that does not parse leaves the earlier one. A leading dot of Domain is dropped.
2231def cookie.av.put(k: Av, a: Cav, +v: String) -> Cav:
2232  match k a:
2233    case AvExpires{} Cav{e, m, d, p, s, h, ss}:
2234      Cav{Maybe.or(&2, Time.Instant, cookie.date(v), e), m, d, p, s, h, ss}
2235    case AvMaxAge{} Cav{e, m, d, p, s, h, ss}:
2236      Cav{e, Maybe.or(&2, U32, cookie.max_age(v), m), d, p, s, h, ss}
2237    case AvDomain{} Cav{e, m, d, p, s, h, ss}:
2238      Cav{e, m, String.to_lower(Bool.pick(String, String.starts_with(v, "."), String.drop(v, 1n), v)), p, s, h, ss}
2239    case AvPath{} Cav{e, m, d, p, s, h, ss}:
2240      Cav{e, m, d, Some{v}, s, h, ss}
2241    case AvSecure{} Cav{e, m, d, p, s, h, ss}:
2242      Cav{e, m, d, p, True{}, h, ss}
2243    case AvHttpOnly{} Cav{e, m, d, p, s, h, ss}:
2244      Cav{e, m, d, p, s, True{}, ss}
2245    case AvSameSite{} Cav{e, m, d, p, s, h, ss}:
2246      Cav{e, m, d, p, s, h, cookie.same_site(v)}
2247    case AvOther{} Cav{e, m, d, p, s, h, ss}:
2248      Cav{e, m, d, p, s, h, ss}
2249
2250# §5.6: name and value trimmed; a value over 1024 octets drops the attribute.
2251def cookie.av(+a: Cav, kv: String & String & Bool) -> Cav:
2252  (k, v, found) = kv
2253  +tv = trim(v)
2254  Bool.pick(Cav, cookie.over(tv, 1024), a, cookie.av.put(cookie.av.kind(String.to_lower(trim(k))), a, tv))
2255
2256def cookie.avs(xs: List<&2, String>, a: Cav) -> Cav:
2257  match xs:
2258    case Nil{}:
2259      a
2260    case Con{x, t}:
2261      cookie.avs(t, cookie.av(a, cookie.cut(x, 61)))
2262
2263# §5.6: with no "=", the name is empty and the pair is the value.
2264def cookie.nv(r: String & String & Bool) -> String & String:
2265  (a, b, found) = r
2266  match found:
2267    case True{}:
2268      (trim(a), trim(b))
2269    case False{}:
2270      ("", trim(a))
2271
2272def cookie.prefixed(+s: String, +p: String) -> Bool:
2273  String.starts_with(String.to_lower(s), p)
2274
2275# §5.7 steps 8–10: (domain, host-only), or None to ignore the cookie.
2276# ponytail: the public-suffix check knows single-label suffixes only (com, org, localhost). Multi-label
2277# ones (co.uk, github.io) need the publicsuffix.org list; ship it as data when a caller needs it.
2278def cookie.domain.of(+host: String, +d: String) -> Maybe<&1, String & Bool>:
2279  +suffix = Bool.and(Bool.not(String.is_empty(d)), Bool.not(String.contains(d, ".")))
2280  +d2 = Bool.pick(String, Bool.and(suffix, String.eq(d, host)), "", d)
2281  Bool.pick(Maybe<&1, String & Bool>, Bool.or(Bool.and(suffix, Bool.not(String.eq(d, host))), Bool.not(cookie.ascii(d))), None{},
2282    Bool.pick(Maybe<&1, String & Bool>, String.is_empty(d2), Some{(host, True{})},
2283      Bool.pick(Maybe<&1, String & Bool>, cookie.domain_match(host, d2), Some{(d2, False{})}, None{})))
2284
2285def cookie.path.of(p: Maybe<&2, String>, +req: String) -> String:
2286  match p:
2287    case None{}:
2288      cookie.default_path(req)
2289    case Some{+v}:
2290      Bool.pick(String, String.starts_with(v, "/"), v, cookie.default_path(req))
2291
2292# §5.7 steps 12–20: Secure needs https; SameSite=None needs Secure; the __Secure- and __Host- prefixes.
2293def cookie.make.dm(dm: Maybe<&1, String & Bool>, +name: String, +value: String, +path: String, has_path: Bool, +secure: Bool, http_only: Bool, +same_site: String, expiry: Maybe<&2, Time.Instant>, https: Bool) -> Maybe<&2, Cookie>:
2294  match dm:
2295    case None{}:
2296      None{}
2297    case Some{(domain, +host_only)}:
2298      +host = Bool.and(Bool.and(secure, host_only), Bool.and(has_path, String.eq(path, "/")))
2299      +bad = Bool.or(Bool.or(Bool.and(secure, Bool.not(https)), Bool.and(String.eq(same_site, "none"), Bool.not(secure))),
2300        Bool.or(Bool.or(Bool.and(cookie.prefixed(name, "__secure-"), Bool.not(secure)), Bool.and(cookie.prefixed(name, "__host-"), Bool.not(host))),
2301          Bool.and(String.is_empty(name), Bool.or(cookie.prefixed(value, "__secure-"), cookie.prefixed(value, "__host-")))))
2302      Bool.pick(Maybe<&2, Cookie>, bad, None{}, Some{Cookie{name, value, domain, host_only, path, secure, http_only, same_site, expiry}})
2303
2304# Max-Age wins over Expires, whatever their order (§5.7 step 6). Both count from now.
2305def cookie.expiry(+now: Time.Instant, m: Maybe<&2, U32>, e: Maybe<&2, Time.Instant>) -> Maybe<&2, Time.Instant>:
2306  Maybe.or(&2, Time.Instant, cookie.max_age.at(now, m), cookie.expires.cap(now, e))
2307
2308def cookie.make(+now: Time.Instant, a: Url.Abs, +name: String, +value: String, c: Cav) -> Maybe<&2, Cookie>:
2309  Url.Abs{scheme, host, port, target} = a
2310  Cav{e, m, d, +p, secure, http_only, same_site} = c
2311  cookie.make.dm(cookie.domain.of(host, d), name, value, cookie.path.of(p, cookie.target_path(target)), Maybe.is_some(&2, String, p), secure, http_only, same_site, cookie.expiry(now, m, e), String.eq(scheme, "https"))
2312
2313# An empty name and value, or more than 4096 octets of them, is ignored.
2314def cookie.parse.nv(+now: Time.Instant, a: Url.Abs, nv: String & String, avs: List<&2, String>) -> Maybe<&2, Cookie>:
2315  (+name, +value) = nv
2316  Bool.pick(Maybe<&2, Cookie>, Bool.or(Bool.and(String.is_empty(name), String.is_empty(value)), cookie.over(name ++ value, 4096)), None{}, cookie.make(now, a, name, value, cookie.avs(avs, cookie.cav())))
2317
2318def cookie.parse.go(+now: Time.Instant, a: Url.Abs, xs: List<&2, String>) -> Maybe<&2, Cookie>:
2319  match xs:
2320    case Nil{}:
2321      None{}
2322    case Con{first, avs}:
2323      cookie.parse.nv(now, a, cookie.nv(cookie.cut(first, 61)), avs)
2324
2325# §5.6 and §5.7: one Set-Cookie value received from a at now, or None when it is ignored.
2326def cookie.parse(+now: Time.Instant, a: Url.Abs, +line: String) -> Maybe<&2, Cookie>:
2327  Bool.pick(Maybe<&2, Cookie>, cookie.ctl(line), None{}, cookie.parse.go(now, a, String.split(line, ';')))
2328
2329def cookie.https(a: Url.Abs) -> Bool:
2330  Url.Abs{scheme, host, port, target} = a
2331  String.eq(scheme, "https")
2332
2333def cookie.secure(c: Cookie) -> Bool:
2334  Cookie{name, value, domain, host_only, path, secure, http_only, same_site, expiry} = c
2335  secure
2336
2337# Same name, domain, host-only flag and path: the new cookie replaces the old one.
2338def cookie.same(+a: Cookie, +b: Cookie) -> Bool:
2339  Cookie{n1, v1, d1, h1, p1, s1, o1, ss1, e1} = a
2340  Cookie{n2, v2, d2, h2, p2, s2, o2, ss2, e2} = b
2341  Bool.and(Bool.and(String.eq(n1, n2), String.eq(d1, d2)), Bool.and(Bool.not(Bool.xor(h1, h2)), String.eq(p1, p2)))
2342
2343# §5.7 step 16: a secure cookie that a new cookie from plain http must not overlay.
2344def cookie.shadows(+old: Cookie, +c: Cookie) -> Bool:
2345  Cookie{n1, v1, +d1, h1, p1, s1, o1, ss1, e1} = old
2346  Cookie{n2, v2, +d2, h2, p2, s2, o2, ss2, e2} = c
2347  Bool.and(Bool.and(String.eq(n1, n2), s1), Bool.and(Bool.or(cookie.domain_match(d1, d2), cookie.domain_match(d2, d1)), cookie.path_match(p2, p1)))
2348
2349def cookie.shadowed(cs: List<&2, Cookie>, +c: Cookie) -> Bool:
2350  match cs:
2351    case Nil{}:
2352      False{}
2353    case Con{+o, t}:
2354      Bool.or(cookie.shadows(o, c), cookie.shadowed(t, c))
2355
2356# cs with the cookie c replaces swapped for c (keeping its place, so its creation order), or c last.
2357# A c that is not live only removes the old one: that is how a server deletes a cookie.
2358def cookie.put(cs: List<&2, Cookie>, +c: Cookie, +live: Bool) -> List<&2, Cookie>:
2359  match cs:
2360    case Nil{}:
2361      Bool.pick(List<&2, Cookie>, live, [c], Nil{})
2362    case Con{+o, +t}:
2363      Bool.pick(List<&2, Cookie>, cookie.same(o, c), Bool.pick(List<&2, Cookie>, live, Con{c, t}, t), Con{o, cookie.put(t, c, live)})
2364
2365def jar.put.c(m: Maybe<&2, Cookie>, j: Jar, +https: Bool) -> Jar:
2366  match m:
2367    case None{}:
2368      Jar{now, cs} = j
2369      Jar{now, cs}
2370    case Some{+c}:
2371      Jar{+now, +cs} = j
2372      Jar{now, Bool.pick(List<&2, Cookie>, Bool.and(Bool.not(Bool.or(https, cookie.secure(c))), cookie.shadowed(cs, c)), cs, cookie.put(cs, c, cookie.live(now, c)))}
2373
2374def jar.put.one(j: Jar, +a: Url.Abs, x: String) -> Jar:
2375  Jar{+now, cs} = j
2376  jar.put.c(cookie.parse(now, a, x), Jar{now, cs}, cookie.https(a))
2377
2378def jar.put.all(xs: List<&2, String>, j: Jar, +a: Url.Abs) -> Jar:
2379  match xs:
2380    case Nil{}:
2381      j
2382    case Con{x, t}:
2383      jar.put.all(t, jar.put.one(j, a, x), a)
2384
2385# Every set-cookie field of h, in order, as received from a.
2386def jar.store.abs(j: Jar, +a: Url.Abs, +h: Map<&2, List<&2, String>>) -> Jar:
2387  jar.put.all(fields(h, "set-cookie"), j, a)
2388
2389def jar.store.url(j: Jar, u: Maybe<&2, Url.Abs>, +h: Map<&2, List<&2, String>>) -> Jar:
2390  match u:
2391    case None{}:
2392      j
2393    case Some{+a}:
2394      jar.store.abs(j, a, h)
2395
2396# The jar after res came from url, and res as it was. Header names are lowercase, as parse gives them.
2397# An unparsable url stores nothing.
2398def jar.store(j: Jar, url: String, res: Res) -> Jar & Res:
2399  Res{status, +headers, body} = res
2400  (jar.store.url(j, Url.absolute(url), headers), Res{status, headers, body})
2401
2402# §5.8.3: domain (exact when host-only), path, and Secure only over https.
2403def cookie.sends(+host: String, +path: String, +https: Bool, +c: Cookie) -> Bool:
2404  Cookie{n, v, +d, +ho, +p, s, o, ss, e} = c
2405  Bool.and(Bool.pick(Bool, ho, String.eq(host, d), cookie.domain_match(host, d)), Bool.and(cookie.path_match(path, p), Bool.or(Bool.not(s), https)))
2406
2407def cookie.pick(cs: List<&2, Cookie>, +host: String, +path: String, +https: Bool) -> List<&2, Cookie>:
2408  match cs:
2409    case Nil{}:
2410      Nil{}
2411    case Con{+c, t}:
2412      List.filter.put(Cookie, c, cookie.pick(t, host, path, https), cookie.sends(host, path, https, c))
2413
2414# The sort order of §5.8.3: longer paths first; the stable sort keeps creation order among equals.
2415def cookie.longer(a: Cookie, b: Cookie) -> Bool:
2416  Cookie{n1, v1, d1, h1, p1, s1, o1, ss1, e1} = a
2417  Cookie{n2, v2, d2, h2, p2, s2, o2, ss2, e2} = b
2418  Bool.not(Nat.is_lt(String.length(p1), String.length(p2)))
2419
2420def cookie.pair(c: Cookie) -> String:
2421  Cookie{+n, +v, d, h, p, s, o, ss, e} = c
2422  Bool.pick(String, String.is_empty(n), v, n ++ "=" ++ v)
2423
2424def cookie.pairs(cs: List<&2, Cookie>) -> List<&2, String>:
2425  match cs:
2426    case Nil{}:
2427      Nil{}
2428    case Con{c, t}:
2429      Con{cookie.pair(c), cookie.pairs(t)}
2430
2431# §5.8.3: the cookie-string for a request to a, or "" when no cookie matches.
2432def jar.string(j: Jar, a: Url.Abs) -> String:
2433  Jar{now, cs} = j
2434  Url.Abs{scheme, +host, port, target} = a
2435  String.join(cookie.pairs(List.sort(~Cookie, ~cookie.longer, cookie.pick(cs, host, cookie.target_path(target), String.eq(scheme, "https")))), "; ")
2436
2437# One cookie field: the caller's own cookie values first, then the jar's.
2438def jar.header(+h: Map<&2, List<&2, String>>, +s: String) -> Map<&2, List<&2, String>>:
2439  +old = String.join(fields(h, "cookie"), "; ")
2440  Bool.pick(Map<&2, List<&2, String>>, String.is_empty(s), h, set(h, "cookie", Bool.pick(String, String.is_empty(old), s, old ++ "; " ++ s)))
2441
2442def jar.apply.url(j: Jar, u: Maybe<&2, Url.Abs>, +h: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
2443  match u:
2444    case None{}:
2445      h
2446    case Some{a}:
2447      jar.header(h, jar.string(j, a))
2448
2449# The headers with the jar's cookies for a request to url in their cookie field (lowercase, as fetch
2450# sends names). An unparsable url adds nothing.
2451def jar.apply(j: Jar, url: String, +h: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
2452  jar.apply.url(j, Url.absolute(url), h)
2453
2454# The jar, if any, that a fetch threads through its hops.
2455def jar.opt.apply(m: Maybe<&2, Jar>, a: Url.Abs, +h: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
2456  match m:
2457    case None{}:
2458      h
2459    case Some{j}:
2460      jar.header(h, jar.string(j, a))
2461
2462def jar.opt.res(j: Jar, +a: Url.Abs, r: Result<&1, &1, Err, Res>) -> Maybe<&2, Jar> & Result<&1, &1, Err, Res>:
2463  match r:
2464    case Fail{e}:
2465      (Some{j}, Fail{e})
2466    case Done{res}:
2467      Res{status, +headers, body} = res
2468      (Some{jar.store.abs(j, a, headers)}, Done{Res{status, headers, body}})
2469
2470def jar.opt.store(m: Maybe<&2, Jar>, +a: Url.Abs, r: Result<&1, &1, Err, Res>) -> Maybe<&2, Jar> & Result<&1, &1, Err, Res>:
2471  match m:
2472    case None{}:
2473      (None{}, r)
2474    case Some{j}:
2475      jar.opt.res(j, a, r)
2476
2477def io.recv.words(tls: Bool, s: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>):
2478  match tls:
2479    case True{}:
2480      Wire.tls.recv.words(s, max, ms)
2481    case False{}:
2482      Wire.recv.words(s, max, ms)
2483
2484def io.send.words(tls: Bool, s: Socket, +len: U32, buf: Array<U32>) -> IO(Socket & Result<&1, &1, U32 & String, Unit>):
2485  match tls:
2486    case True{}:
2487      Wire.tls.send.words(s, len, buf)
2488    case False{}:
2489      Wire.send.words(s, len, buf)
2490
2491def io.send.bytes(tls: Bool, s: Socket, b: Bytes.Bytes) -> IO(Socket & Result<&1, &1, U32 & String, Unit>):
2492  Bytes.Bytes{+len, buf} = b
2493  io.send.words(tls, s, len, buf)
2494
2495def io.close(tls: Bool, s: Socket) -> IO(Unit):
2496  match tls:
2497    case True{}:
2498      Wire.tls.close(s)
2499    case False{}:
2500      Socket.close(s)
2501
2502def fetch.close(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>) -> IO(Result<&1, &1, Err, Res>):
2503  do IO<Result<&1, &1, Err, Res>>:
2504    io.close(tls, s)
2505    return r
2506
2507# none: no response byte arrived, so an idempotent request may be retried (RFC 9112 §9.3.1).
2508# sock: the socket, when it can take another request.
2509type Out is Type:
2510  OutDone{res: Res, sock: Maybe<&1, Socket>, rest: Bytes.Bytes}
2511  OutFail{e: Err, none: Bool}
2512
2513def fetch.boxed(r: Result<&1, &1, Err, Res>, none: Bool) -> Out:
2514  match r:
2515    case Done{res}:
2516      OutDone{res, None{}, Bytes.new(0)}
2517    case Fail{e}:
2518      OutFail{e, none}
2519
2520def fetch.bad(tls: Bool, s: Socket, e: Maybe<&2, Err>) -> IO(Result<&1, &1, Err, Res>):
2521  match e:
2522    case None{}:
2523      fetch.close(tls, s, Fail{ErrBad{}})
2524    case Some{err}:
2525      fetch.close(tls, s, Fail{err})
2526
2527def fetch.shut(tls: Bool, s: Socket, r: Result<&1, &1, Err, Res>, none: Bool) -> IO(Out):
2528  do IO<Out>:
2529    x : Result<&1, &1, Err, Res> <- fetch.close(tls, s, r)
2530    return fetch.boxed(x, none)
2531
2532def fetch.hold.go(tls: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out):
2533  match left:
2534    case Some{x}:
2535      IO.pure(Out, OutDone{res, Some{s}, x})
2536    case None{}:
2537      fetch.shut(tls, s, Done{res}, False{})
2538
2539def fetch.hold(tls: Bool, close: Bool, s: Socket, res: Res, left: Maybe<&1, Bytes.Bytes>) -> IO(Out):
2540  Res{status, +headers, body} = res
2541  fetch.hold.go(tls, s, Res{status, headers, body}, again(close, headers, left))
2542
2543def fetch.failout(tls: Bool, s: Socket, e: Maybe<&2, Err>, none: Bool) -> IO(Out):
2544  do IO<Out>:
2545    r : Result<&1, &1, Err, Res> <- fetch.bad(tls, s, e)
2546    return fetch.boxed(r, none)
2547
2548# Read state: the response so far and the bytes read, or how it ended.
2549# none: no byte arrived, so an idempotent request may be retried.
2550type Fl is Type:
2551  FlMore{rv: Rv, n: U32}
2552  FlBad{err: Maybe<&2, Err>, none: Bool}
2553  FlDone{res: Res, rest: Maybe<&1, Bytes.Bytes>}
2554
2555def fetch.rt(+n: U32, t: Rt) -> Fl:
2556  match t:
2557    case RtMore{rv}:
2558      FlMore{rv, n}
2559    case RtBad{}:
2560      FlBad{None{}, U32.is_zero(n)}
2561    case RtDone{res, rest}:
2562      FlDone{res, rest}
2563
2564# 64 KiB of head past the body cap, as serve allows.
2565def fetch.over(+n: U32) -> Bool:
2566  Nat.is_lt(Nat.add(fetch.max(), 65536n), U32.to_nat(n))
2567
2568def fetch.piece.cap(over: Bool, +head: Bool, rv: Rv, +n2: U32, +len: U32, buf: Array<U32>) -> Fl:
2569  match over:
2570    case True{}:
2571      FlBad{None{}, False{}}
2572    case False{}:
2573      fetch.rt(n2, rv.step(U32.is_zero(len), head, rv, Bytes.Bytes{len, buf}))
2574
2575def fetch.piece(+head: Bool, rv: Rv, +n: U32, p: U32 & Array<U32>) -> Fl:
2576  (+len, buf) = p
2577  +n2 = (n + len : U32)
2578  fetch.piece.cap(fetch.over(n2), head, rv, n2, len, buf)
2579
2580def fetch.next(+head: Bool, rv: Rv, +n: U32, m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> Socket & Fl:
2581  (s, r) = m
2582  match r:
2583    case Fail{(+code, why)}:
2584      (s, FlBad{Some{err.or_late(code, ErrRead{code, why})}, U32.is_zero(n)})
2585    case Done{p}:
2586      (s, fetch.piece(head, rv, n, p))
2587
2588# Reads until the response is whole or cannot be; an empty read means the server closed.
2589@unsafe
2590def fetch.loop(+close: Bool, +head: Bool, +tls: Bool, +ms: U32, st: Socket & Fl) -> IO(Out):
2591  (s, fl) = st
2592  match fl:
2593    case FlMore{rv, +n}:
2594      do IO<Out>:
2595        m : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <- io.recv.words(tls, s, 65536, ms)
2596        fetch.loop(close, head, tls, ms, fetch.next(head, rv, n, m))
2597    case FlBad{err, none}:
2598      fetch.failout(tls, s, err, none)
2599    case FlDone{res, rest}:
2600      fetch.hold(tls, close, s, res, rest)
2601
2602def exchange.sent(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Out):
2603  (s, r) = m
2604  match r:
2605    case Fail{(+code, why)}:
2606      fetch.shut(tls, s, Fail{err.or_late(code, ErrWrite{code, why})}, True{})
2607    case Done{u}:
2608      fetch.loop(close, head, tls, ms, (s, FlMore{rv.start(), 0}))
2609
2610def exchange.go(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, s: Socket, wire: Bytes.Bytes) -> IO(Out):
2611  do IO<Out>:
2612    sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, wire)
2613    exchange.sent(tls, ms, close, head, sent)
2614
2615def exchange.tup(o: Out) -> Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes:
2616  match o:
2617    case OutFail{e, none}:
2618      (None{}, Fail{e}, Bytes.new(0))
2619    case OutDone{res, sock, rest}:
2620      (sock, Done{res}, rest)
2621
2622# One request on an open socket. Some{socket}: it can take another request.
2623def exchange(+tls: Bool, +ms: U32, +close: Bool, +head: Bool, s: Socket, wire: Bytes.Bytes) -> IO(Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes):
2624  do IO<Maybe<&1, Socket> & Result<&1, &1, Err, Res> & Bytes.Bytes>:
2625    o : Out <- exchange.go(tls, ms, close, head, s, wire)
2626    return exchange.tup(o)
2627
2628# Paths to a PEM client chain and its private key for mutual TLS.
2629type Cert is Data:
2630  Cert{chain: String, key: String}
2631
2632type Proxy is Data:
2633  ProxyDirect{}
2634  ProxyVia{host: String, port: U32, authorization: String}
2635
2636type Route is Data:
2637  RouteDirect{tls: Bool, host: String, port: U32, cert: Maybe<&2, Cert>, ca: String}
2638  RouteProxy{tls: Bool, host: String, port: U32, sni: String,
2639    authority: String, authorization: String, cert: Maybe<&2, Cert>, ca: String}
2640
2641# Connection pool: idle sockets by origin and client identity, newest first, at most cap per key.
2642# fetch uses a fresh pool per call.
2643
2644type Conn is Type:
2645  Conn{tls: Bool, s: Socket}
2646  ConnH2{s: Socket, client: H2.Client}
2647
2648type Send is Type:
2649  Send{method: String, target: String, authority: String,
2650    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes}
2651
2652type H2Step is Type:
2653  H2Reply{s: Socket, reply: H2.Reply}
2654  H2Sent{client: H2.Client, events: List<&1, H2.ClientEvent>,
2655    sent: Result<&1, &1, Err, Socket>}
2656  H2Events{s: Socket, client: H2.Client, result: Result<&1, &1, Err, Maybe<&1, Res>>}
2657  H2Received{client: H2.Client,
2658    answer: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>}
2659
2660type TunnelStep is Type:
2661  TunnelRead{s: Socket, raw: String}
2662  TunnelHead{s: Socket, raw: String, frame: Frame}
2663  TunnelGot{raw: String,
2664    answer: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>}
2665
2666type Pool is Type:
2667  Pool{cap: U32, idle: Map<&1, List<&1, Conn>>}
2668
2669def pool.new.with(+cap: U32) -> Pool:
2670  Pool{cap, Map.new(&1, List<&1, Conn>)}
2671
2672# Up to 8 idle sockets per origin.
2673def pool.new() -> Pool:
2674  pool.new.with(8)
2675
2676def pool.key(+scheme: String, +host: String, +port: U32) -> String:
2677  scheme ++ "://" ++ host ++ ":" ++ U32.show(port)
2678
2679def pool.key.cert(+base: String, cert: Maybe<&2, Cert>) -> String:
2680  match cert:
2681    case None{}:
2682      base
2683    case Some{Cert{+chain, +key}}:
2684      base ++ "|cert:" ++ Nat.show(String.length(chain)) ++ ":" ++ chain
2685        ++ Nat.show(String.length(key)) ++ ":" ++ key
2686
2687def pool.key.ca(+base: String, +ca: String, empty: Bool) -> String:
2688  match empty:
2689    case True{}:
2690      base
2691    case False{}:
2692      base ++ "|ca:" ++ Nat.show(String.length(ca)) ++ ":" ++ ca
2693
2694def pool.key.trust(+base: String, cert: Maybe<&2, Cert>, +ca: String) -> String:
2695  pool.key.ca(pool.key.cert(base, cert), ca, String.is_empty(ca))
2696
2697def pool.take.of(+cap: U32, +key: String, r: Map<&1, List<&1, Conn>> & Maybe<&1, List<&1, Conn>>) -> Pool & Maybe<&1, Conn>:
2698  (m, got) = r
2699  match got:
2700    case None{}:
2701      (Pool{cap, m}, None{})
2702    case Some{xs}:
2703      match xs:
2704        case Nil{}:
2705          (Pool{cap, m}, None{})
2706        case Con{c, t}:
2707          (Pool{cap, Map.set(&1, List<&1, Conn>, m, key, t)}, Some{c})
2708
2709# The socket given back last.
2710def pool.take(p: Pool, +key: String) -> Pool & Maybe<&1, Conn>:
2711  Pool{+cap, idle} = p
2712  pool.take.of(cap, key, Map.pop(&1, List<&1, Conn>, idle, key))
2713
2714def conn.close(c: Conn) -> IO(Unit):
2715  match c:
2716    case Conn{tls, s}:
2717      io.close(tls, s)
2718    case ConnH2{s, client}:
2719      Wire.tls.close(s)
2720
2721# The first n sockets; the rest are closed.
2722def conns.cut(xs: List<&1, Conn>, n: Nat) -> IO(List<&1, Conn>):
2723  match xs:
2724    case Nil{}:
2725      IO.pure(List<&1, Conn>, Nil{})
2726    case Con{c, t}:
2727      match n:
2728        case 0n:
2729          do IO<List<&1, Conn>>:
2730            conn.close(c)
2731            conns.cut(t, 0n)
2732        case 1n+k:
2733          do IO<List<&1, Conn>>:
2734            r : List<&1, Conn> <- conns.cut(t, k)
2735            return c <> r
2736
2737type Took is Type:
2738  TookNone{}
2739  TookRaw{c: Conn}
2740  TookSeen{quiet: Bool, c: Conn}
2741
2742def took.of(pm: Pool & Maybe<&1, Conn>) -> Pool & Took:
2743  (p, m) = pm
2744  match m:
2745    case None{}:
2746      (p, TookNone{})
2747    case Some{c}:
2748      (p, TookRaw{c})
2749
2750def took.seen(+tls: Bool, r: Socket & Bool) -> Took:
2751  (s, quiet) = r
2752  TookSeen{quiet, Conn{tls, s}}
2753
2754# HTTP/2 reads its own GOAWAY, so only an HTTP/1.1 socket is peeked.
2755def conn.idle(c: Conn) -> IO(Took):
2756  match c:
2757    case Conn{+tls, s}:
2758      do IO<Took>:
2759        r : Socket & Bool <- Wire.idle(s)
2760        return took.seen(tls, r)
2761    case ConnH2{s, client}:
2762      IO.pure(Took, TookSeen{True{}, ConnH2{s, client}})
2763
2764# Each idle socket takes two steps: the peek, then the close and the next take.
2765def pool.take.go(n: Nat, +key: String, pt: Pool & Took) -> IO(Pool & Maybe<&1, Conn>):
2766  match n pt:
2767    case _ Tuple{p, TookNone{}}:
2768      IO.pure(Pool & Maybe<&1, Conn>, (p, None{}))
2769    case _ Tuple{p, TookSeen{True{}, c}}:
2770      IO.pure(Pool & Maybe<&1, Conn>, (p, Some{c}))
2771    case 0n Tuple{p, TookRaw{c}}:
2772      do IO<Pool & Maybe<&1, Conn>>:
2773        conn.close(c)
2774        return (p, None{})
2775    case 0n Tuple{p, TookSeen{False{}, c}}:
2776      do IO<Pool & Maybe<&1, Conn>>:
2777        conn.close(c)
2778        return (p, None{})
2779    case 1n+k Tuple{p, TookRaw{c}}:
2780      do IO<Pool & Maybe<&1, Conn>>:
2781        t : Took <- conn.idle(c)
2782        pool.take.go(k, key, (p, t))
2783    case 1n+k Tuple{p, TookSeen{False{}, c}}:
2784      do IO<Pool & Maybe<&1, Conn>>:
2785        conn.close(c)
2786        pool.take.go(k, key, took.of(pool.take(p, key)))
2787
2788# pool.take, but an idle socket that the peer closed or that has bytes waiting is closed, and the next one is tried.
2789def pool.take.live(p: Pool, +key: String) -> IO(Pool & Maybe<&1, Conn>):
2790  Pool{+cap, idle} = p
2791  pool.take.go((2n * U32.to_nat(cap) + 2n : Nat), key, took.of(pool.take(Pool{cap, idle}, key)))
2792
2793def pool.give.of(+cap: U32, +key: String, c: Conn, limit: Nat,
2794    r: Map<&1, List<&1, Conn>> & Maybe<&1, List<&1, Conn>>) -> IO(Pool):
2795  (m, old) = r
2796  do IO<Pool>:
2797    xs : List<&1, Conn> <- conns.cut(c <> Maybe.default(&1, List<&1, Conn>, old, Nil{}), limit)
2798    return Pool{cap, Map.set(&1, List<&1, Conn>, m, key, xs)}
2799
2800# Past the limit (cap for HTTP/1.1, one session for HTTP/2), close the oldest idle connection.
2801def pool.give(p: Pool, +key: String, c: Conn) -> IO(Pool):
2802  Pool{+cap, idle} = p
2803  match c:
2804    case Conn{tls, s}:
2805      pool.give.of(cap, key, Conn{tls, s}, U32.to_nat(cap), Map.pop(&1, List<&1, Conn>, idle, key))
2806    case ConnH2{s, client}:
2807      pool.give.of(cap, key, ConnH2{s, client}, Bool.pick(Nat, U32.is_zero(cap), 0n, 1n),
2808        Map.pop(&1, List<&1, Conn>, idle, key))
2809
2810def pool.close.go(xs: List<&1, List<&1, Conn>>) -> IO(Unit):
2811  match xs:
2812    case Nil{}:
2813      IO.pure(Unit, Unit{})
2814    case Con{cs, t}:
2815      do IO<Unit>:
2816        none : List<&1, Conn> <- conns.cut(cs, 0n)
2817        pool.close.go(t)
2818
2819def pool.close(p: Pool) -> IO(Unit):
2820  Pool{cap, idle} = p
2821  pool.close.go(Map.values(&1, List<&1, Conn>, idle))
2822
2823# RFC 9110 §9.2.2
2824def pool.idempotent(+method: String) -> Bool:
2825  Bool.or(String.eq(method, "GET"), Bool.or(String.eq(method, "HEAD"), Bool.or(String.eq(method, "OPTIONS"), Bool.or(String.eq(method, "TRACE"), Bool.or(String.eq(method, "PUT"), String.eq(method, "DELETE"))))))
2826
2827def conn.tls(m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Result<&1, &1, Err, Conn>):
2828  (s, r) = m
2829  match r:
2830    case Fail{(+code, why)}:
2831      do IO<Result<&1, &1, Err, Conn>>:
2832        Wire.tls.close(s)
2833        return Fail{err.or_late(code, ErrTls{code, why})}
2834    case Done{u}:
2835      IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{True{}, s}})
2836
2837def conn.h2.sent(client: H2.Client, r: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Result<&1, &1, Err, Conn>):
2838  (s, sent) = r
2839  match sent:
2840    case Fail{(+code, why)}:
2841      do IO<Result<&1, &1, Err, Conn>>:
2842        Wire.tls.close(s)
2843        return Fail{err.or_late(code, ErrWrite{code, why})}
2844    case Done{u}:
2845      IO.pure(Result<&1, &1, Err, Conn>, Done{ConnH2{s, client}})
2846
2847def conn.h2.start(s: Socket, r: H2.Client & HBytes.Bytes) -> IO(Result<&1, &1, Err, Conn>):
2848  (client, HBytes.Bytes{len, buf}) = r
2849  do IO<Result<&1, &1, Err, Conn>>:
2850    sent : Socket & Result<&1, &1, U32 & String, Unit> <- Wire.tls.send.words(s, len, buf)
2851    conn.h2.sent(client, sent)
2852
2853def conn.tls.protocol(proto: String, s: Socket) -> IO(Result<&1, &1, Err, Conn>):
2854  match proto:
2855    case "h2":
2856      conn.h2.start(s, H2.client.start())
2857    case "http/1.1":
2858      IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{True{}, s}})
2859    case "":
2860      IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{True{}, s}})
2861    case other:
2862      do IO<Result<&1, &1, Err, Conn>>:
2863        Wire.tls.close(s)
2864        return Fail{ErrTls{71, "unsupported ALPN: " ++ other}}
2865
2866def conn.tls.alpn(m: Socket & Result<&1, &1, U32 & String, String>) -> IO(Result<&1, &1, Err, Conn>):
2867  (s, r) = m
2868  match r:
2869    case Fail{(+code, why)}:
2870      do IO<Result<&1, &1, Err, Conn>>:
2871        Wire.tls.close(s)
2872        return Fail{err.or_late(code, ErrTls{code, why})}
2873    case Done{proto}:
2874      conn.tls.protocol(proto, s)
2875
2876def conn.secure.store(offer: Bool, s: Socket, host: String, +ms: U32) ->
2877    IO(Result<&1, &1, Err, Conn>):
2878  match offer:
2879    case True{}:
2880      do IO<Result<&1, &1, Err, Conn>>:
2881        hs : Socket & Result<&1, &1, U32 & String, String> <-
2882          Wire.tls.connect.alpn(s, host, ms, "h2,http/1.1")
2883        conn.tls.alpn(hs)
2884    case False{}:
2885      do IO<Result<&1, &1, Err, Conn>>:
2886        hs : Socket & Result<&1, &1, U32 & String, Unit> <- Wire.tls.connect(s, host, ms)
2887        conn.tls(hs)
2888
2889def conn.secure.file(offer: Bool, s: Socket, host: String, +ms: U32, +ca: String) ->
2890    IO(Result<&1, &1, Err, Conn>):
2891  match offer:
2892    case True{}:
2893      do IO<Result<&1, &1, Err, Conn>>:
2894        hs : Socket & Result<&1, &1, U32 & String, String> <-
2895          Wire.tls.connect.alpn.ca(s, host, ms, "h2,http/1.1", ca)
2896        conn.tls.alpn(hs)
2897    case False{}:
2898      do IO<Result<&1, &1, Err, Conn>>:
2899        hs : Socket & Result<&1, &1, U32 & String, Unit> <-
2900          Wire.tls.connect.ca(s, host, ms, ca)
2901        conn.tls(hs)
2902
2903def conn.secure.no.cert(empty: Bool, offer: Bool, s: Socket, host: String, +ms: U32,
2904    +ca: String) -> IO(Result<&1, &1, Err, Conn>):
2905  match empty:
2906    case True{}:
2907      conn.secure.store(offer, s, host, ms)
2908    case False{}:
2909      conn.secure.file(offer, s, host, ms, ca)
2910
2911def conn.secure.tls(cert: Maybe<&2, Cert>, s: Socket, host: String, +ms: U32,
2912    offer: Bool, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
2913  match cert:
2914    case None{}:
2915      conn.secure.no.cert(String.is_empty(ca), offer, s, host, ms, ca)
2916    case Some{Cert{chain, key}}:
2917      do IO<Result<&1, &1, Err, Conn>>:
2918        hs : Socket & Result<&1, &1, U32 & String, Unit> <-
2919          Wire.tls.connect.cert(s, host, ms, chain, key)
2920        conn.tls(hs)
2921
2922# A failed handshake is an error, never plaintext.
2923def conn.secure(+ms: U32, sni: String, s: Socket, tls: Bool,
2924    cert: Maybe<&2, Cert>, offer: Bool, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
2925  match tls:
2926    case False{}:
2927      IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{False{}, s}})
2928    case True{}:
2929      conn.secure.tls(cert, s, sni, ms, offer, ca)
2930
2931def conn.made(+ms: U32, sni: String, tls: Bool, cert: Maybe<&2, Cert>,
2932    offer: Bool, +ca: String, r: Result<&1, &1, U32 & String, Socket>) ->
2933    IO(Result<&1, &1, Err, Conn>):
2934  match r:
2935    case Fail{(+code, why)}:
2936      IO.pure(Result<&1, &1, Err, Conn>, Fail{err.or_late(code, ErrConnect{code, why})})
2937    case Done{s}:
2938      conn.secure(ms, sni, s, tls, cert, offer, ca)
2939
2940def conn.open(+tls: Bool, +ms: U32, sni: String, ip: String, port: U32) ->
2941    IO(Result<&1, &1, Err, Conn>):
2942  do IO<Result<&1, &1, Err, Conn>>:
2943    c : Result<&1, &1, U32 & String, Socket> <- Wire.connect(ip, port, ms)
2944    conn.made(ms, sni, tls, None{}, False{}, "", c)
2945
2946# ponytail: addresses are tried in order, not raced; a broken first route can consume the step timeout.
2947type Dial is Type:
2948  Dial{port: U32, ms: U32, deadline: Nat, action: IO(Result<&1, &1, U32 & String, Socket>)}
2949
2950def conn.dial.time(+ip: String, +port: U32, +deadline: Nat, now: Nat, expired: Bool) ->
2951  IO(Result<&1, &1, U32 & String, Socket>):
2952  match expired:
2953    case True{}:
2954      IO.pure(Result<&1, &1, U32 & String, Socket>, Fail{(60, "connect timeout")})
2955    case False{}:
2956      Wire.connect(ip, port, U32.from_nat(Nat.sub(deadline, now)))
2957
2958def conn.dial.with(+ip: String, +port: U32, +ms: U32, +deadline: Nat, +now: Nat, unlimited: Bool) ->
2959  IO(Result<&1, &1, U32 & String, Socket>):
2960  match unlimited:
2961    case True{}:
2962      Wire.connect(ip, port, 0)
2963    case False{}:
2964      conn.dial.time(ip, port, deadline, now, Nat.is_le(deadline, now))
2965
2966def conn.dial.try(+ip: String, +port: U32, +ms: U32, +deadline: Nat) ->
2967  IO(Result<&1, &1, U32 & String, Socket>):
2968  do IO<Result<&1, &1, U32 & String, Socket>>:
2969    now : Nat <- IO.now()
2970    conn.dial.with(ip, port, ms, deadline, now, U32.is_zero(ms))
2971
2972def conn.dial.after(r: Result<&1, &1, U32 & String, Socket>, ip: String, +port: U32, +ms: U32, +deadline: Nat) ->
2973  IO(Result<&1, &1, U32 & String, Socket>):
2974  match r:
2975    case Done{s}:
2976      IO.pure(Result<&1, &1, U32 & String, Socket>, Done{s})
2977    case Fail{e}:
2978      conn.dial.try(ip, port, ms, deadline)
2979
2980def conn.dial.more(prev: IO(Result<&1, &1, U32 & String, Socket>), ip: String, +port: U32, +ms: U32, +deadline: Nat) ->
2981  IO(Result<&1, &1, U32 & String, Socket>):
2982  do IO<Result<&1, &1, U32 & String, Socket>>:
2983    r : Result<&1, &1, U32 & String, Socket> <- prev
2984    conn.dial.after(r, ip, port, ms, deadline)
2985
2986def conn.dial.fold(d: Dial, ip: String) -> Dial:
2987  Dial{+port, +ms, +deadline, action} = d
2988  Dial{port, ms, deadline, conn.dial.more(action, ip, port, ms, deadline)}
2989
2990def conn.dial.run(d: Dial) -> IO(Result<&1, &1, U32 & String, Socket>):
2991  Dial{port, ms, deadline, action} = d
2992  action
2993
2994def conn.dial.all(+port: U32, +ms: U32, +start: Nat, first: String, rest: List<&2, String>) ->
2995  IO(Result<&1, &1, U32 & String, Socket>):
2996  match rest:
2997    case Nil{}:
2998      Wire.connect(first, port, ms)
2999    case Con{ip, tail}:
3000      +deadline = Nat.add(start, U32.to_nat(ms))
3001      conn.dial.run(List.foldl(~&2, ~String, ~Dial, ~conn.dial.fold, Con{ip, tail},
3002        Dial{port, ms, deadline, Wire.connect(first, port, ms)}))
3003
3004def conn.open.all(+tls: Bool, +ms: U32, +host: String, +port: U32,
3005    ips: List<&2, String>, cert: Maybe<&2, Cert>, offer: Bool, +ca: String) ->
3006    IO(Result<&1, &1, Err, Conn>):
3007  match ips:
3008    case Nil{}:
3009      IO.pure(Result<&1, &1, Err, Conn>, Fail{ErrDns{}})
3010    case Con{addr, rest}:
3011      do IO<Result<&1, &1, Err, Conn>>:
3012        start : Nat <- IO.now()
3013        r : Result<&1, &1, U32 & String, Socket> <- conn.dial.all(port, ms, start, addr, rest)
3014        conn.made(ms, host, tls, cert, offer, ca, r)
3015
3016def proxy.auth.safe(s: String) -> Bool:
3017  match s:
3018    case SNil{}:
3019      True{}
3020    case SCon{Chr{+c}, rest}:
3021      Bool.and(U32.is_ge(c, 32), Bool.and(U32.is_ne(c, 127), proxy.auth.safe(rest)))
3022
3023def proxy.auth.checked(+decoded: String, valid: Bool) -> Maybe<&2, String>:
3024  match valid:
3025    case False{}:
3026      None{}
3027    case True{}:
3028      Some{"Basic " ++ Bytes.to_base64(Bytes.from_string(decoded))}
3029
3030def proxy.auth.value(m: Maybe<&2, String>) -> Maybe<&2, String>:
3031  match m:
3032    case None{}:
3033      None{}
3034    case Some{+decoded}:
3035      proxy.auth.checked(decoded, Bool.and(Url.has(decoded, 58), proxy.auth.safe(decoded)))
3036
3037def proxy.from.abs(authorization: String, m: Maybe<&2, Url.Abs>) -> Maybe<&2, Proxy>:
3038  match m:
3039    case None{}:
3040      None{}
3041    case Some{Url.Abs{scheme, host, port, target}}:
3042      Some{ProxyVia{host, port, authorization}}
3043
3044def proxy.from.user(user: String, host: String) -> Maybe<&2, Proxy>:
3045  do Maybe<&2, Proxy>:
3046    authorization : String <- proxy.auth.value(Url.pct.decode(user))
3047    proxy.from.abs(authorization, Url.absolute("http://" ++ host))
3048
3049def proxy.from.authority(has_user: Bool, authority: String, split: String & String) ->
3050    Maybe<&2, Proxy>:
3051  match has_user:
3052    case False{}:
3053      proxy.from.abs("", Url.absolute("http://" ++ authority))
3054    case True{}:
3055      (user, host) = split
3056      proxy.from.user(user, host)
3057
3058def proxy.from.parts(parts: String & String) -> Maybe<&2, Proxy>:
3059  (+authority, path) = parts
3060  match path:
3061    case SNil{}:
3062      proxy.from.authority(Url.has(authority, 64), authority, Url.take_to(authority, 64))
3063    case other:
3064      None{}
3065
3066def proxy.from.url.if(valid: Bool, raw: String) -> Maybe<&2, Proxy>:
3067  match valid:
3068    case False{}:
3069      None{}
3070    case True{}:
3071      proxy.from.parts(Url.take_to(String.drop(raw, 7n), 47))
3072
3073def proxy.from.url(+raw: String) -> Maybe<&2, Proxy>:
3074  proxy.from.url.if(String.starts_with(String.to_lower(raw), "http://"), raw)
3075
3076def proxy.no.domain(+host: String, +suffix: String) -> Bool:
3077  Bool.or(String.eq(host, suffix), String.ends_with(host, "." ++ suffix))
3078
3079def proxy.no.suffix(+host: String, +raw: String) -> Bool:
3080  match raw:
3081    case SCon{Chr{46}, +suffix}:
3082      proxy.no.domain(host, suffix)
3083    case other:
3084      proxy.no.domain(host, raw)
3085
3086def proxy.no.port(valid: Bool, +host: String, suffix: String) -> Bool:
3087  match valid:
3088    case False{}:
3089      False{}
3090    case True{}:
3091      proxy.no.suffix(host, suffix)
3092
3093def proxy.no.named(+host: String, +port: U32, parts: String & String) -> Bool:
3094  (suffix, port_text) = parts
3095  match port_text:
3096    case SNil{}:
3097      proxy.no.suffix(host, suffix)
3098    case other:
3099      proxy.no.port(String.eq(port_text, U32.show(port)), host, suffix)
3100
3101def proxy.no.literal(+host: String, +port: U32, +name: String) -> Bool:
3102  Bool.or(String.eq(name, host), Bool.or(String.eq(name, "[" ++ host ++ "]"),
3103    Bool.or(String.eq(name, host ++ ":" ++ U32.show(port)),
3104      String.eq(name, "[" ++ host ++ "]:" ++ U32.show(port)))))
3105
3106def proxy.no.token(+host: String, +port: U32, token: String) -> Bool:
3107  +name = String.to_lower(trim(token))
3108  Bool.or(String.eq(name, "*"),
3109    Bool.or(proxy.no.literal(host, port, name),
3110      Bool.and(Bool.not(Url.has(host, 58)),
3111        proxy.no.named(host, port, Url.take_to(name, 58)))))
3112
3113def proxy.no.list(xs: List<&2, String>, +host: String, +port: U32) -> Bool:
3114  match xs:
3115    case Nil{}:
3116      False{}
3117    case Con{token, rest}:
3118      Bool.or(proxy.no.token(host, port, token), proxy.no.list(rest, host, port))
3119
3120def proxy.no_match(+host: String, +port: U32, no_proxy: String) -> Bool:
3121  proxy.no.list(String.split(no_proxy, ','), String.to_lower(host), port)
3122
3123def proxy.env.value(r: Result<&1, &1, U32 & String, String>) -> String:
3124  match r:
3125    case Fail{e}:
3126      ""
3127    case Done{value}:
3128      value
3129
3130def proxy.env.lower(r: Result<&1, &1, U32 & String, String>, upper: String) ->
3131    IO(String):
3132  match r:
3133    case Done{value}:
3134      IO.pure(String, value)
3135    case Fail{e}:
3136      do IO<String>:
3137        got : Result<&1, &1, U32 & String, String> <- IO.get_env(upper)
3138        return proxy.env.value(got)
3139
3140def proxy.env(lower: String, upper: String) -> IO(String):
3141  do IO<String>:
3142    got : Result<&1, &1, U32 & String, String> <- IO.get_env(lower)
3143    proxy.env.lower(got, upper)
3144
3145def proxy.from.env(value: String) -> Maybe<&2, Proxy>:
3146  match value:
3147    case SNil{}:
3148      Some{ProxyDirect{}}
3149    case other:
3150      proxy.from.url(value)
3151
3152def proxy.choose(bypass: Bool, value: String) -> Maybe<&2, Proxy>:
3153  match bypass:
3154    case True{}:
3155      Some{ProxyDirect{}}
3156    case False{}:
3157      proxy.from.env(value)
3158
3159def proxy.select(+scheme: String, +host: String, +port: U32) ->
3160    IO(Maybe<&2, Proxy>):
3161  +secure = String.eq(scheme, "https")
3162  do IO<Maybe<&2, Proxy>>:
3163    bypass : String <- proxy.env("no_proxy", "NO_PROXY")
3164    value : String <- proxy.env(Bool.pick(String, secure, "https_proxy", "http_proxy"),
3165      Bool.pick(String, secure, "HTTPS_PROXY", "HTTP_PROXY"))
3166    return proxy.choose(proxy.no_match(host, port, bypass), value)
3167
3168def h2.header.skip(+name: String) -> Bool:
3169  match name:
3170    case SCon{Chr{58}, rest}:
3171      True{}
3172    case other:
3173      Bool.or(req.reserved(name),
3174        Bool.or(String.eq(name, "keep-alive"),
3175          Bool.or(String.eq(name, "proxy-connection"),
3176            Bool.or(String.eq(name, "upgrade"), String.eq(name, "te")))))
3177
3178def h2.header.mode(+name: String) -> U32:
3179  Bool.pick(U32,
3180    Bool.or(String.eq(name, "authorization"),
3181      Bool.or(String.eq(name, "cookie"), String.eq(name, "proxy-authorization"))),
3182    2, 0)
3183
3184def h2.header.values(+name: String, +mode: U32, vs: List<&2, String>) -> List<&2, Hpack.Field>:
3185  match vs:
3186    case Nil{}:
3187      Nil{}
3188    case Con{v, tail}:
3189      Hpack.Field{name, sanitize(v), mode} <> h2.header.values(name, mode, tail)
3190
3191def h2.header.tokens(vs: List<&2, String>) -> List<&2, String>:
3192  match vs:
3193    case Nil{}:
3194      Nil{}
3195    case Con{value, rest}:
3196      List.append(&2, String, String.split(value, ','), h2.header.tokens(rest))
3197
3198def h2.header.nominated.one(enabled: Bool, vs: List<&2, String>,
3199    rest: List<&2, String>) -> List<&2, String>:
3200  match enabled:
3201    case True{}:
3202      List.append(&2, String, h2.header.tokens(vs), rest)
3203    case False{}:
3204      rest
3205
3206def h2.header.nominated(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>) ->
3207    List<&2, String>:
3208  match xs:
3209    case Nil{}:
3210      Nil{}
3211    case (name, values) <> tail:
3212      h2.header.nominated.one(String.eq(String.to_lower(name), "connection"),
3213        values, h2.header.nominated(tail))
3214
3215def h2.header.has(xs: List<&2, String>, +key: String) -> Bool:
3216  match xs:
3217    case Nil{}:
3218      False{}
3219    case Con{value, rest}:
3220      Bool.or(String.eq(String.to_lower(trim(value)), key), h2.header.has(rest, key))
3221
3222def h2.header.item(skip: Bool, +name: String, vs: List<&2, String>,
3223    tail: List<&2, Hpack.Field>) -> List<&2, Hpack.Field>:
3224  match skip:
3225    case True{}:
3226      tail
3227    case False{}:
3228      List.append(&2, Hpack.Field, h2.header.values(name, h2.header.mode(name), vs), tail)
3229
3230def h2.header.items(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>,
3231    +nominated: List<&2, String>) -> List<&2, Hpack.Field>:
3232  match xs:
3233    case Nil{}:
3234      Nil{}
3235    case (name, values) <> tail:
3236      +key = String.to_lower(name)
3237      h2.header.item(Bool.or(h2.header.skip(key), h2.header.has(nominated, key)),
3238        key, values, h2.header.items(tail, nominated))
3239
3240def h2.request.fields(+method: String, target: String, authority: String,
3241    headers: Map<&2, List<&2, String>>) -> List<&2, Hpack.Field>:
3242  +xs = Map.to_list(&2, List<&2, String>, headers)
3243  List.append(&2, Hpack.Field,
3244    [Hpack.Field{":method", method, 0}, Hpack.Field{":scheme", "https", 0},
3245      Hpack.Field{":authority", authority, 0}, Hpack.Field{":path", target, 0}],
3246    h2.header.items(xs, h2.header.nominated(xs)))
3247
3248def h2.response.status.value(+value: String, valid: Bool) -> Maybe<&2, U32>:
3249  match valid:
3250    case False{}:
3251      None{}
3252    case True{}:
3253      parse_u32(value)
3254
3255def h2.response.status(xs: List<&2, Hpack.Field>) -> Maybe<&2, U32>:
3256  match xs:
3257    case Nil{}:
3258      None{}
3259    case Con{Hpack.Field{name, +value, mode}, tail}:
3260      match name:
3261        case ":status":
3262          h2.response.status.value(value, Nat.is_eq(String.length(value), 3n))
3263        case other:
3264          h2.response.status(tail)
3265
3266def h2.response.headers(xs: List<&2, Hpack.Field>, acc: Map<&2, List<&2, String>>) ->
3267    Map<&2, List<&2, String>>:
3268  match xs:
3269    case Nil{}:
3270      acc
3271    case Con{Hpack.Field{name, value, mode}, tail}:
3272      match name:
3273        case SCon{Chr{58}, rest}:
3274          h2.response.headers(tail, acc)
3275        case other:
3276          h2.response.headers(tail, add(acc, String.to_lower(name), value))
3277
3278def h2.response.final(interim: Bool, +status: U32, headers: List<&2, Hpack.Field>,
3279    body: HBytes.Bytes) -> Result<&1, &1, Err, Maybe<&1, Res>>:
3280  match interim:
3281    case True{}:
3282      Done{None{}}
3283    case False{}:
3284      HBytes.Bytes{len, buf} = body
3285      Done{Some{Res{status, h2.response.headers(headers, empty()), Bytes.Bytes{len, buf}}}}
3286
3287def h2.response.valid(valid: Bool, +status: U32, headers: List<&2, Hpack.Field>,
3288    body: HBytes.Bytes) -> Result<&1, &1, Err, Maybe<&1, Res>>:
3289  match valid:
3290    case False{}:
3291      Fail{ErrBad{}}
3292    case True{}:
3293      h2.response.final(U32.is_lt(status, 200), status, headers, body)
3294
3295def h2.response.parsed(m: Maybe<&2, U32>, headers: List<&2, Hpack.Field>,
3296    body: HBytes.Bytes) -> Result<&1, &1, Err, Maybe<&1, Res>>:
3297  match m:
3298    case None{}:
3299      Fail{ErrBad{}}
3300    case Some{+status}:
3301      h2.response.valid(Bool.and(U32.is_ge(status, 100), U32.is_lt(status, 600)),
3302        status, headers, body)
3303
3304def h2.response(+fields: List<&2, Hpack.Field>, body: HBytes.Bytes) ->
3305    Result<&1, &1, Err, Maybe<&1, Res>>:
3306  h2.response.parsed(h2.response.status(fields), fields, body)
3307
3308def h2.event.fold(acc: Result<&1, &1, Err, Maybe<&1, Res>>,
3309    event: H2.ClientEvent) -> Result<&1, &1, Err, Maybe<&1, Res>>:
3310  match acc:
3311    case Fail{e}:
3312      Fail{e}
3313    case Done{Some{res}}:
3314      Done{Some{res}}
3315    case Done{None{}}:
3316      match event:
3317        case H2.Response{id, headers, body}:
3318          h2.response(headers, body)
3319        case H2.Reset{id, code}:
3320          Fail{ErrBad{}}
3321        case H2.Shutdown{last, code}:
3322          Done{None{}}
3323        case H2.Pong{data}:
3324          Done{None{}}
3325
3326def h2.send.report(r: Socket & Result<&1, &1, U32 & String, Unit>) ->
3327    IO(Result<&1, &1, Err, Socket>):
3328  (s, sent) = r
3329  match sent:
3330    case Fail{(+code, why)}:
3331      do IO<Result<&1, &1, Err, Socket>>:
3332        Wire.tls.close(s)
3333        return Fail{err.or_late(code, ErrWrite{code, why})}
3334    case Done{unit}:
3335      IO.pure(Result<&1, &1, Err, Socket>, Done{s})
3336
3337def h2.send.one(prev: Result<&1, &1, Err, Socket>, b: HBytes.Bytes) ->
3338    IO(Result<&1, &1, Err, Socket>):
3339  match prev:
3340    case Fail{e}:
3341      IO.pure(Result<&1, &1, Err, Socket>, Fail{e})
3342    case Done{s}:
3343      HBytes.Bytes{len, buf} = b
3344      do IO<Result<&1, &1, Err, Socket>>:
3345        sent : Socket & Result<&1, &1, U32 & String, Unit> <- Wire.tls.send.words(s, len, buf)
3346        h2.send.report(sent)
3347
3348def h2.send.fold(prev: IO(Result<&1, &1, Err, Socket>), b: HBytes.Bytes) ->
3349    IO(Result<&1, &1, Err, Socket>):
3350  do IO<Result<&1, &1, Err, Socket>>:
3351    r : Result<&1, &1, Err, Socket> <- prev
3352    h2.send.one(r, b)
3353
3354def h2.send(xs: List<&1, HBytes.Bytes>, s: Socket) -> IO(Result<&1, &1, Err, Socket>):
3355  List.foldl(~&1, ~HBytes.Bytes, ~IO(Result<&1, &1, Err, Socket>), ~h2.send.fold,
3356    xs, IO.pure(Result<&1, &1, Err, Socket>, Done{s}))
3357
3358def h2.received.step(over: Bool, s: Socket, client: H2.Client,
3359    +len: U32, buf: Array<U32>) -> H2Step:
3360  match over:
3361    case True{}:
3362      H2Reply{s, H2.Failed{H2.Error{1, True{}, 0}}}
3363    case False{}:
3364      H2Reply{s, H2.client.receive(client, HBytes.Bytes{len, buf})}
3365
3366def h2.fail(s: Socket, e: Err) -> IO(Result<&1, &1, Err, Conn & Res>):
3367  do IO<Result<&1, &1, Err, Conn & Res>>:
3368    Wire.tls.close(s)
3369    return Fail{e}
3370
3371def h2.loop(n: Nat, step: H2Step, +ms: U32, +used: U32) ->
3372    IO(Result<&1, &1, Err, Conn & Res>):
3373  match n:
3374    case 0n:
3375      match step:
3376        case H2Reply{s, reply}:
3377          h2.fail(s, ErrBad{})
3378        case H2Sent{client, events, sent}:
3379          match sent:
3380            case Fail{e}:
3381              IO.pure(Result<&1, &1, Err, Conn & Res>, Fail{e})
3382            case Done{s}:
3383              h2.fail(s, ErrBad{})
3384        case H2Events{s, client, result}:
3385          h2.fail(s, ErrBad{})
3386        case H2Received{client, answer}:
3387          (s, r) = answer
3388          h2.fail(s, ErrBad{})
3389    case 1n+p:
3390      match step:
3391        case H2Reply{s, reply}:
3392          match reply:
3393            case H2.Failed{error}:
3394              h2.fail(s, ErrBad{})
3395            case H2.Advanced{client, writes, events}:
3396              do IO<Result<&1, &1, Err, Conn & Res>>:
3397                sent : Result<&1, &1, Err, Socket> <- h2.send(writes, s)
3398                h2.loop(p, H2Sent{client, events, sent}, ms, used)
3399        case H2Sent{client, events, sent}:
3400          match sent:
3401            case Fail{e}:
3402              IO.pure(Result<&1, &1, Err, Conn & Res>, Fail{e})
3403            case Done{s}:
3404              h2.loop(p, H2Events{s, client,
3405                List.foldl(~&1, ~H2.ClientEvent,
3406                  ~Result<&1, &1, Err, Maybe<&1, Res>>, ~h2.event.fold,
3407                  events, Done{None{}})}, ms, used)
3408        case H2Events{s, client, result}:
3409          match result:
3410            case Fail{e}:
3411              h2.fail(s, e)
3412            case Done{Some{res}}:
3413              IO.pure(Result<&1, &1, Err, Conn & Res>,
3414                Done{(ConnH2{s, client}, res)})
3415            case Done{None{}}:
3416              do IO<Result<&1, &1, Err, Conn & Res>>:
3417                r : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <-
3418                  Wire.tls.recv.words(s, 16384, ms)
3419                h2.loop(p, H2Received{client, r}, ms, used)
3420        case H2Received{client, answer}:
3421          match answer:
3422            case (s, Fail{(+code, why)}):
3423              h2.fail(s, err.or_late(code, ErrRead{code, why}))
3424            case (s, Done{(0, buf)}):
3425              h2.fail(s, ErrBad{})
3426            case (s, Done{(+len, buf)}):
3427              +total = (used + len : U32)
3428              h2.loop(p, h2.received.step(fetch.over(total), s, client, len, buf), ms, total)
3429
3430def h2.exchange(s: Socket, client: H2.Client, +ms: U32, request: Send) ->
3431    IO(Result<&1, &1, Err, Conn & Res>):
3432  Send{method, target, authority, headers, Bytes.Bytes{len, buf}} = request
3433  h2.loop(Nat.mul(4n, Nat.add(fetch.max(), 65536n)),
3434    H2Reply{s, H2.client.request(client,
3435      h2.request.fields(method, target, authority, headers), HBytes.Bytes{len, buf})},
3436    ms, 0)
3437
3438def proxy.connect.fail(s: Socket, e: Err) -> IO(Result<&1, &1, Err, Conn>):
3439  do IO<Result<&1, &1, Err, Conn>>:
3440    io.close(False{}, s)
3441    return Fail{e}
3442
3443def proxy.connect.extra(m: Maybe<&2, String>, s: Socket, +ms: U32,
3444    sni: String, cert: Maybe<&2, Cert>, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
3445  match m:
3446    case None{}:
3447      proxy.connect.fail(s, ErrBad{})
3448    case Some{rest}:
3449      match rest:
3450        case SNil{}:
3451          conn.secure(ms, sni, s, True{}, cert, True{}, ca)
3452        case other:
3453          proxy.connect.fail(s, ErrBad{})
3454
3455def proxy.connect.status(ok: Bool, +status: U32, raw: String, s: Socket,
3456    +ms: U32, sni: String, cert: Maybe<&2, Cert>, +ca: String) ->
3457    IO(Result<&1, &1, Err, Conn>):
3458  match ok:
3459    case False{}:
3460      proxy.connect.fail(s, ErrConnect{status, "proxy CONNECT refused"})
3461    case True{}:
3462      proxy.connect.extra(after(raw, True{}), s, ms, sni, cert, ca)
3463
3464def proxy.connect.finish(res: Res, raw: String, s: Socket, +ms: U32,
3465    sni: String, cert: Maybe<&2, Cert>, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
3466  Res{+status, headers, body} = res
3467  proxy.connect.status(Bool.and(U32.is_ge(status, 200), U32.is_lt(status, 300)),
3468    status, raw, s, ms, sni, cert, ca)
3469
3470def proxy.connect.frame(raw: String, over: Bool) -> Frame:
3471  match over:
3472    case True{}:
3473      FrameBad{}
3474    case False{}:
3475      frame(raw, False{}, True{})
3476
3477def proxy.connect.chunk(empty: Bool, s: Socket, raw: String,
3478    +len: U32, buf: Array<U32>) -> TunnelStep:
3479  match empty:
3480    case True{}:
3481      TunnelHead{s, raw, FrameBad{}}
3482    case False{}:
3483      TunnelRead{s, raw ++ Bytes.to_string(Bytes.Bytes{len, buf})}
3484
3485def proxy.connect.loop(n: Nat, step: TunnelStep, +ms: U32, +sni: String,
3486    +cert: Maybe<&2, Cert>, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
3487  match n:
3488    case 0n:
3489      match step:
3490        case TunnelRead{s, raw}:
3491          proxy.connect.fail(s, ErrBad{})
3492        case TunnelHead{s, raw, result}:
3493          proxy.connect.fail(s, ErrBad{})
3494        case TunnelGot{raw, answer}:
3495          (s, result) = answer
3496          proxy.connect.fail(s, ErrBad{})
3497    case 1n+p:
3498      match step:
3499        case TunnelRead{s, +raw}:
3500          proxy.connect.loop(p, TunnelHead{s, raw,
3501            proxy.connect.frame(raw, Nat.is_gt(String.length(raw), 65536n))},
3502            ms, sni, cert, ca)
3503        case TunnelHead{s, raw, result}:
3504          match result:
3505            case FrameBad{}:
3506              proxy.connect.fail(s, ErrBad{})
3507            case FrameMore{}:
3508              do IO<Result<&1, &1, Err, Conn>>:
3509                answer : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <-
3510                  io.recv.words(False{}, s, 4096, ms)
3511                proxy.connect.loop(p, TunnelGot{raw, answer}, ms, sni, cert, ca)
3512            case FrameDone{res}:
3513              proxy.connect.finish(res, raw, s, ms, sni, cert, ca)
3514        case TunnelGot{raw, answer}:
3515          match answer:
3516            case (s, Fail{(+code, why)}):
3517              proxy.connect.fail(s, err.or_late(code, ErrRead{code, why}))
3518            case (s, Done{(+len, buf)}):
3519              proxy.connect.loop(p,
3520                proxy.connect.chunk(U32.is_zero(len), s, raw, len, buf),
3521                ms, sni, cert, ca)
3522
3523def proxy.connect.header(+authority: String, authorization: String) -> String:
3524  match authorization:
3525    case SNil{}:
3526      "CONNECT " ++ authority ++ " HTTP/1.1\r\nHost: " ++ authority ++ "\r\n\r\n"
3527    case other:
3528      "CONNECT " ++ authority ++ " HTTP/1.1\r\nHost: " ++ authority
3529        ++ "\r\nProxy-Authorization: " ++ authorization ++ "\r\n\r\n"
3530
3531def proxy.connect.sent(r: Socket & Result<&1, &1, U32 & String, Unit>,
3532    +ms: U32, sni: String, cert: Maybe<&2, Cert>, +ca: String) ->
3533    IO(Result<&1, &1, Err, Conn>):
3534  (s, sent) = r
3535  match sent:
3536    case Fail{(+code, why)}:
3537      proxy.connect.fail(s, err.or_late(code, ErrWrite{code, why}))
3538    case Done{unit}:
3539      proxy.connect.loop(Nat.mul(3n, 65536n), TunnelRead{s, ""}, ms, sni, cert, ca)
3540
3541def proxy.connect(s: Socket, +ms: U32, sni: String, authority: String,
3542    authorization: String, cert: Maybe<&2, Cert>, +ca: String) ->
3543    IO(Result<&1, &1, Err, Conn>):
3544  do IO<Result<&1, &1, Err, Conn>>:
3545    sent : Socket & Result<&1, &1, U32 & String, Unit> <-
3546      io.send.bytes(False{}, s, Bytes.from_string(proxy.connect.header(authority, authorization)))
3547    proxy.connect.sent(sent, ms, sni, cert, ca)
3548
3549def proxy.route.tls(tls: Bool, s: Socket, +ms: U32, sni: String,
3550    authority: String, authorization: String, cert: Maybe<&2, Cert>, +ca: String) ->
3551    IO(Result<&1, &1, Err, Conn>):
3552  match tls:
3553    case False{}:
3554      IO.pure(Result<&1, &1, Err, Conn>, Done{Conn{False{}, s}})
3555    case True{}:
3556      proxy.connect(s, ms, sni, authority, authorization, cert, ca)
3557
3558def proxy.route.connected(tls: Bool, +ms: U32, sni: String, authority: String,
3559    authorization: String, cert: Maybe<&2, Cert>, +ca: String,
3560    r: Result<&1, &1, Err, Conn>) -> IO(Result<&1, &1, Err, Conn>):
3561  match r:
3562    case Fail{e}:
3563      IO.pure(Result<&1, &1, Err, Conn>, Fail{e})
3564    case Done{c}:
3565      match c:
3566        case Conn{plain, s}:
3567          proxy.route.tls(tls, s, ms, sni, authority, authorization, cert, ca)
3568        case ConnH2{s, client}:
3569          do IO<Result<&1, &1, Err, Conn>>:
3570            Wire.tls.close(s)
3571            return Fail{ErrBad{}}
3572
3573def proxy.route.open(+ms: U32, +tls: Bool, +host: String, +port: U32,
3574    +sni: String, +authority: String, +authorization: String,
3575    cert: Maybe<&2, Cert>, +ca: String) -> IO(Result<&1, &1, Err, Conn>):
3576  do IO<Result<&1, &1, Err, Conn>>:
3577    ips : List<&2, String> <- Dns.resolve.all(host)
3578    conn : Result<&1, &1, Err, Conn> <-
3579      conn.open.all(False{}, ms, host, port, ips, None{}, False{}, "")
3580    proxy.route.connected(tls, ms, sni, authority, authorization, cert, ca, conn)
3581
3582# offer: send the h2 ALPN offer on a direct TLS connection.
3583def route.open(+ms: U32, offer: Bool, route: Route) -> IO(Result<&1, &1, Err, Conn>):
3584  match route:
3585    case RouteDirect{tls, +host, port, cert, ca}:
3586      do IO<Result<&1, &1, Err, Conn>>:
3587        ips : List<&2, String> <- Dns.resolve.all(host)
3588        conn.open.all(tls, ms, host, port, ips, cert, offer, ca)
3589    case RouteProxy{tls, host, port, sni, authority, authorization, cert, ca}:
3590      proxy.route.open(ms, tls, host, port, sni, authority, authorization, cert, ca)
3591
3592def conn.ex.h1(+tls: Bool, s: Socket, +ms: U32, request: Send) -> IO(Out):
3593  Send{+method, target, authority, headers, body} = request
3594  exchange.go(tls, ms, False{}, String.eq(method, "HEAD"), s,
3595    encode_req.on(method, target, authority, headers, body, False{}))
3596
3597# A socket with bytes past the response is out of step with the server, so it is closed.
3598def pool.keep.s(p: Pool, +key: String, +tls: Bool, res: Res, s: Socket, clean: Bool) -> IO(Pool & Result<&1, &1, Err, Res>):
3599  match clean:
3600    case True{}:
3601      do IO<Pool & Result<&1, &1, Err, Res>>:
3602        p2 : Pool <- pool.give(p, key, Conn{tls, s})
3603        return (p2, Done{res})
3604    case False{}:
3605      do IO<Pool & Result<&1, &1, Err, Res>>:
3606        io.close(tls, s)
3607        return (p, Done{res})
3608
3609def pool.keep.m(p: Pool, +key: String, +tls: Bool, res: Res, +clean: Bool, sock: Maybe<&1, Socket>) -> IO(Pool & Result<&1, &1, Err, Res>):
3610  match sock:
3611    case None{}:
3612      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Done{res}))
3613    case Some{s}:
3614      pool.keep.s(p, key, tls, res, s, clean)
3615
3616def pool.keep(p: Pool, +key: String, +tls: Bool, res: Res, sock: Maybe<&1, Socket>, rest: Bytes.Bytes) -> IO(Pool & Result<&1, &1, Err, Res>):
3617  Bytes.Bytes{+len, buf} = rest
3618  pool.keep.m(p, key, tls, res, U32.is_zero(len), sock)
3619
3620def pool.after(p: Pool, +key: String, +tls: Bool, o: Out) -> IO(Pool & Result<&1, &1, Err, Res>):
3621  match o:
3622    case OutFail{e, none}:
3623      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{e}))
3624    case OutDone{res, sock, rest}:
3625      pool.keep(p, key, tls, res, sock, rest)
3626
3627def pool.h2.reusable(c: Conn) -> Bool & Conn:
3628  match c:
3629    case Conn{tls, s}:
3630      (True{}, Conn{tls, s})
3631    case ConnH2{s, H2.Client{input, streams, encoder, decoder, +control}}:
3632      H2.Control{next, active, frame_max, initial, max_streams, send_window,
3633        recv_window, first, ack, continuation, +goaway} = control
3634      (Maybe.is_none(&2, U32, goaway),
3635        ConnH2{s, H2.Client{input, streams, encoder, decoder, control}})
3636
3637def pool.h2.keep(p: Pool, +key: String, res: Res, state: Bool & Conn) ->
3638    IO(Pool & Result<&1, &1, Err, Res>):
3639  (reusable, c) = state
3640  match reusable:
3641    case True{}:
3642      do IO<Pool & Result<&1, &1, Err, Res>>:
3643        next : Pool <- pool.give(p, key, c)
3644        return (next, Done{res})
3645    case False{}:
3646      do IO<Pool & Result<&1, &1, Err, Res>>:
3647        conn.close(c)
3648        return (p, Done{res})
3649
3650def pool.h2.result(p: Pool, +key: String, r: Result<&1, &1, Err, Conn & Res>) ->
3651    IO(Pool & Result<&1, &1, Err, Res>):
3652  match r:
3653    case Fail{e}:
3654      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{e}))
3655    case Done{(c, res)}:
3656      pool.h2.keep(p, key, res, pool.h2.reusable(c))
3657
3658def pool.h2.ex(p: Pool, +key: String, +ms: U32, request: Send,
3659    s: Socket, client: H2.Client) -> IO(Pool & Result<&1, &1, Err, Res>):
3660  do IO<Pool & Result<&1, &1, Err, Res>>:
3661    r : Result<&1, &1, Err, Conn & Res> <- h2.exchange(s, client, ms, request)
3662    pool.h2.result(p, key, r)
3663
3664def pool.fresh.conn(p: Pool, +key: String, +ms: U32, request: Send,
3665    r: Result<&1, &1, Err, Conn>) -> IO(Pool & Result<&1, &1, Err, Res>):
3666  match r:
3667    case Fail{e}:
3668      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{e}))
3669    case Done{c}:
3670      match c:
3671        case Conn{+tls, s}:
3672          do IO<Pool & Result<&1, &1, Err, Res>>:
3673            o : Out <- conn.ex.h1(tls, s, ms, request)
3674            pool.after(p, key, tls, o)
3675        case ConnH2{s, client}:
3676          pool.h2.ex(p, key, ms, request, s, client)
3677
3678def pool.fresh(p: Pool, +key: String, +ms: U32, request: Send, route: Route) ->
3679    IO(Pool & Result<&1, &1, Err, Res>):
3680  do IO<Pool & Result<&1, &1, Err, Res>>:
3681    c : Result<&1, &1, Err, Conn> <- route.open(ms, True{}, route)
3682    pool.fresh.conn(p, key, ms, request, c)
3683
3684# A reused socket the server already closed fails before any response byte.
3685def pool.retry(+none: Bool, +idem: Bool) -> Bool:
3686  Bool.and(none, idem)
3687
3688def pool.stale(p: Pool, +key: String, +ms: U32, request: Send,
3689    e: Err, again: Bool, route: Route) ->
3690    IO(Pool & Result<&1, &1, Err, Res>):
3691  match again:
3692    case True{}:
3693      pool.fresh(p, key, ms, request, route)
3694    case False{}:
3695      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{e}))
3696
3697def pool.reused(p: Pool, +key: String, +tls: Bool, +ms: U32, +idem: Bool,
3698    spare: Send, o: Out, route: Route) ->
3699    IO(Pool & Result<&1, &1, Err, Res>):
3700  match o:
3701    case OutFail{e, +none}:
3702      pool.stale(p, key, ms, spare, e, pool.retry(none, idem), route)
3703    case OutDone{res, sock, rest}:
3704      pool.keep(p, key, tls, res, sock, rest)
3705
3706# Copy only the reused HTTP/1.1 request body, for a stale-socket retry.
3707def pool.idle.h1.bodies(p: Pool, +key: String, +tls: Bool, +ms: U32, +idem: Bool,
3708    s: Socket, +method: String, +target: String, +authority: String,
3709    +headers: Map<&2, List<&2, String>>, r: Bytes.Bytes & Bytes.Bytes,
3710    route: Route) -> IO(Pool & Result<&1, &1, Err, Res>):
3711  (wire, spare) = r
3712  do IO<Pool & Result<&1, &1, Err, Res>>:
3713    o : Out <- conn.ex.h1(tls, s, ms, Send{method, target, authority, headers, wire})
3714    pool.reused(p, key, tls, ms, idem,
3715      Send{method, target, authority, headers, spare}, o, route)
3716
3717def pool.idle.h1(p: Pool, +key: String, +tls: Bool, +ms: U32, +idem: Bool,
3718    s: Socket, request: Send, route: Route) ->
3719    IO(Pool & Result<&1, &1, Err, Res>):
3720  Send{+method, +target, +authority, +headers, body} = request
3721  pool.idle.h1.bodies(p, key, tls, ms, idem, s, method, target,
3722    authority, headers, Bytes.slice(body, 0, 4294967295), route)
3723
3724def pool.idle.reuse(p: Pool, +key: String, +ms: U32, +idem: Bool,
3725    c: Conn, request: Send, route: Route) ->
3726    IO(Pool & Result<&1, &1, Err, Res>):
3727  match c:
3728    case Conn{tls, s}:
3729      pool.idle.h1(p, key, tls, ms, idem, s, request, route)
3730    case ConnH2{s, client}:
3731      pool.h2.ex(p, key, ms, request, s, client)
3732
3733def pool.idle(taken: Pool & Maybe<&1, Conn>, +key: String, +ms: U32,
3734    +idem: Bool, request: Send, route: Route) ->
3735    IO(Pool & Result<&1, &1, Err, Res>):
3736  (p, m) = taken
3737  match m:
3738    case None{}:
3739      pool.fresh(p, key, ms, request, route)
3740    case Some{c}:
3741      pool.idle.reuse(p, key, ms, idem, c, request, route)
3742
3743def proxy.http.headers(headers: Map<&2, List<&2, String>>,
3744    authorization: String) -> Map<&2, List<&2, String>>:
3745  match authorization:
3746    case SNil{}:
3747      headers
3748    case other:
3749      set(headers, "proxy-authorization", authorization)
3750
3751def proxy.absolute(scheme: String, host_field: String, target: String) -> String:
3752  scheme ++ "://" ++ host_field ++ target
3753
3754def proxy.request(secure: Bool, +scheme: String, method: String,
3755    +target: String, +hf: String, headers: Map<&2, List<&2, String>>,
3756    body: Bytes.Bytes, authorization: String) -> Send:
3757  match secure:
3758    case True{}:
3759      Send{method, target, hf, Map.del(&2, List<&2, String>, headers,
3760        "proxy-authorization"), body}
3761    case False{}:
3762      Send{method, proxy.absolute(scheme, hf, target), hf,
3763        proxy.http.headers(headers, authorization), body}
3764
3765# The pool key, the request as sent, and the route to one origin, direct or through a proxy.
3766def origin.route(+method: String, headers: Map<&2, List<&2, String>>,
3767    body: Bytes.Bytes, +hf: String, +scheme: String, +host: String, +port: U32,
3768    +target: String, +cert: Maybe<&2, Cert>, +ca: String, proxy: Proxy) ->
3769    String & Send & Route:
3770  match proxy:
3771    case ProxyDirect{}:
3772      (pool.key.trust(pool.key(scheme, host, port), cert, ca),
3773        Send{method, target, hf, headers, body},
3774        RouteDirect{String.eq(scheme, "https"), host, port, cert, ca})
3775    case ProxyVia{+proxy_host, +proxy_port, +authorization}:
3776      +secure = String.eq(scheme, "https")
3777      (pool.key.trust(pool.key(scheme, host, port)
3778          ++ "|proxy:" ++ proxy_host ++ ":" ++ U32.show(proxy_port)
3779          ++ "|auth:" ++ authorization, cert, ca),
3780        proxy.request(secure, scheme, method, target, hf, headers, body, authorization),
3781        RouteProxy{secure, proxy_host, proxy_port, host,
3782          Url.host_field.wrap(host, Url.has(host, 58)) ++ ":" ++ U32.show(port),
3783          authorization, cert, ca})
3784
3785def pool.origin.route(p: Pool, +method: String, +ms: U32, r: String & Send & Route) ->
3786    IO(Pool & Result<&1, &1, Err, Res>):
3787  (+key, request, route) = r
3788  do IO<Pool & Result<&1, &1, Err, Res>>:
3789    taken : Pool & Maybe<&1, Conn> <- pool.take.live(p, key)
3790    pool.idle(taken, key, ms, pool.idempotent(method), request, route)
3791
3792def pool.origin.choice(p: Pool, +method: String, ms: U32,
3793    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, hf: String,
3794    scheme: String, host: String, port: U32, target: String,
3795    cert: Maybe<&2, Cert>, +ca: String, choice: Maybe<&2, Proxy>) ->
3796    IO(Pool & Result<&1, &1, Err, Res>):
3797  match choice:
3798    case None{}:
3799      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{ErrUrl{}}))
3800    case Some{proxy}:
3801      pool.origin.route(p, method, ms, origin.route(method, headers, body, hf,
3802        scheme, host, port, target, cert, ca, proxy))
3803
3804def pool.origin(p: Pool, method: String, ms: U32,
3805    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, hf: String,
3806    a: Url.Abs, known: Bool, cert: Maybe<&2, Cert>, +ca: String,
3807    choice: Maybe<&2, Proxy>) ->
3808    IO(Pool & Result<&1, &1, Err, Res>):
3809  Url.Abs{scheme, host, port, target} = a
3810  match known:
3811    case False{}:
3812      IO.pure(Pool & Result<&1, &1, Err, Res>, (p, Fail{ErrUrl{}}))
3813    case True{}:
3814      pool.origin.choice(p, method, ms, headers, body, hf, scheme, host,
3815        port, target, cert, ca, choice)
3816
3817def pool.scheme.ok(a: Url.Abs) -> Bool:
3818  Url.Abs{+scheme, host, port, target} = a
3819  Bool.or(String.eq(scheme, "http"), String.eq(scheme, "https"))
3820
3821def pool.one.back(h: Hop, pr: Pool & Result<&1, &1, Err, Res>) -> Pool & Result<&1, &1, Err, Res> & Hop:
3822  (p, r) = pr
3823  (p, r, h)
3824
3825# sent is headers plus any jar cookies; the hop keeps headers, so cookies never pile up across hops.
3826def pool.one.of(p: Pool, +method: String, +url: Url.Abs,
3827    headers: Map<&2, List<&2, String>>, sent: Map<&2, List<&2, String>>,
3828    +ms: U32, r: Bytes.Bytes & Bytes.Bytes, cert: Maybe<&2, Cert>, +ca: String) ->
3829    IO(Pool & Result<&1, &1, Err, Res> & Hop):
3830  Url.Abs{+scheme, +host, +port, target} = url
3831  (body, wire) = r
3832  do IO<Pool & Result<&1, &1, Err, Res> & Hop>:
3833    choice : Maybe<&2, Proxy> <- proxy.select(scheme, host, port)
3834    pr : Pool & Result<&1, &1, Err, Res> <-
3835      pool.origin(p, method, ms, sent, wire, Url.host_field(url),
3836        url, pool.scheme.ok(url), cert, ca, choice)
3837    return pool.one.back(Hop{method, url, headers, body}, pr)
3838
3839# One request, on an idle socket to its origin when the pool has one. The hop comes back for the redirect.
3840def pool.one(p: Pool, h: Hop, +ms: U32, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>) -> IO(Pool & Result<&1, &1, Err, Res> & Hop):
3841  Hop{+method, +url, +headers, body} = h
3842  pool.one.of(p, method, url, headers, jar.opt.apply(jar, url, headers), ms, Bytes.slice(body, 0, 4294967295), cert, ca)
3843
3844type Next is Type:
3845  NDone{res: Result<&1, &1, Err, Res>}
3846  NStop{}
3847  NGo{left: Nat, hop: Hop}
3848
3849type Mode is Data:
3850  ModeFollow{}
3851  ModeManual{}
3852  ModeError{}
3853
3854# left: redirects still allowed after the request we just finished.
3855def fetch.hop(left: Nat, h: Hop) -> Next:
3856  match left:
3857    case 0n:
3858      NStop{}
3859    case 1n+p:
3860      NGo{p, h}
3861
3862def fetch.decide.r(left: Nat, r: Res, next: Maybe<&1, Hop>) -> Next:
3863  match next:
3864    case None{}:
3865      NDone{Done{r}}
3866    case Some{h}:
3867      fetch.hop(left, h)
3868
3869def fetch.decide.res(left: Nat, h: Hop, r: Res) -> Next:
3870  Res{+status, +headers, body} = r
3871  fetch.decide.r(left, Res{status, headers, body}, redirect(status, headers, h))
3872
3873def fetch.decide(left: Nat, h: Hop, m: Result<&1, &1, Err, Res>) -> Next:
3874  match m:
3875    case Fail{e}:
3876      NDone{Fail{e}}
3877    case Done{r}:
3878      fetch.decide.res(left, h, r)
3879
3880def fetch.error.res(r: Res, bad: Bool) -> Next:
3881  match bad:
3882    case True{}:
3883      NDone{Fail{ErrRedirect{}}}
3884    case False{}:
3885      NDone{Done{r}}
3886
3887def fetch.error(h: Hop, m: Result<&1, &1, Err, Res>) -> Next:
3888  match m:
3889    case Fail{e}:
3890      NDone{Fail{e}}
3891    case Done{r}:
3892      Res{+status, +headers, body} = r
3893      fetch.error.res(Res{status, headers, body}, Bool.and(redirect.code(status), has_header(headers, "location")))
3894
3895def fetch.policy(mode: Mode, left: Nat, h: Hop, m: Result<&1, &1, Err, Res>) -> Next:
3896  match mode:
3897    case ModeFollow{}:
3898      fetch.decide(left, h, m)
3899    case ModeManual{}:
3900      NDone{m}
3901    case ModeError{}:
3902      fetch.error(h, m)
3903
3904# A caller's own Accept-Encoding wins; enc is the codings fetch can undo.
3905def accept.enc(+h: Map<&2, List<&2, String>>, +enc: String) -> Map<&2, List<&2, String>>:
3906  Bool.pick(Map<&2, List<&2, String>>, has_header(h, "accept-encoding"), h, set(h, "accept-encoding", enc))
3907
3908def fetch.start(method: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, u: Maybe<&2, Url.Abs>, +enc: String) -> Next:
3909  match u:
3910    case None{}:
3911      NDone{Fail{ErrUrl{}}}
3912    case Some{a}:
3913      NGo{20n, Hop{method, a, accept.enc(req.put(Map.to_list(&2, List<&2, String>, headers), empty()), enc), body}}
3914
3915# The redirect loop's state: the pool, what to do next, the client identity, the jar, and the CA file.
3916type Hops is Type:
3917  Hops{p: Pool, next: Next, cert: Maybe<&2, Cert>, jar: Maybe<&2, Jar>, ca: String}
3918
3919def pool.next.ca(same: Bool, +ca: String) -> String:
3920  match same:
3921    case True{}:
3922      ca
3923    case False{}:
3924      ""
3925
3926def pool.next.move(p: Pool, +old: String, cert: Maybe<&2, Cert>, jar: Maybe<&2, Jar>, +ca: String, n: Next) -> Hops:
3927  match n:
3928    case NGo{left, h}:
3929      Hop{method, +url, headers, body} = h
3930      +same = String.eq(old, redirect.origin(url))
3931      Hops{p, NGo{left, Hop{method, url, headers, body}},
3932        Bool.pick(Maybe<&2, Cert>, same, cert, None{}), jar, pool.next.ca(same, ca)}
3933    case NDone{r}:
3934      Hops{p, NDone{r}, None{}, jar, ""}
3935    case NStop{}:
3936      Hops{p, NStop{}, None{}, jar, ""}
3937
3938def pool.next.of(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, p: Pool, +url: Url.Abs, h: Hop, r: Maybe<&2, Jar> & Result<&1, &1, Err, Res>) -> Hops:
3939  (jar, m) = r
3940  pool.next.move(p, redirect.origin(url), cert, jar, ca, fetch.policy(mode, left, h, m))
3941
3942# The jar takes the response's Set-Cookie fields first, redirect or not.
3943def pool.next(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>, x: Pool & Result<&1, &1, Err, Res> & Hop) -> Hops:
3944  (p, m, h) = x
3945  Hop{method, +url, headers, body} = h
3946  pool.next.of(mode, left, cert, ca, p, url, Hop{method, url, headers, body}, jar.opt.store(jar, url, m))
3947
3948def pool.hops.one(+mode: Mode, +ms: U32, +cert: Maybe<&2, Cert>, +ca: String, +jar: Maybe<&2, Jar>, p: Pool, h: Hop, left: Nat) -> IO(Hops):
3949  do IO<Hops>:
3950    x : Pool & Result<&1, &1, Err, Res> & Hop <- pool.one(p, h, ms, cert, ca, jar)
3951    return pool.next(mode, left, cert, ca, jar, x)
3952
3953# NGo.left is how many redirects may follow this request. 20 is WHATWG's limit.
3954# The jar, when there is one, gives cookies to every hop and takes them from every response.
3955@unsafe
3956def pool.hops(+mode: Mode, +ms: U32, st: Hops) -> IO(Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>):
3957  Hops{p, n, cert, cj, ca} = st
3958  match n:
3959    case NDone{m}:
3960      IO.pure(Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>, (p, m, cj))
3961    case NStop{}:
3962      IO.pure(Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>, (p, Fail{ErrRedirect{}}, cj))
3963    case NGo{+left, h}:
3964      do IO<Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>>:
3965        next : Hops <- pool.hops.one(mode, ms, cert, ca, cj, p, h, left)
3966        pool.hops(mode, ms, next)
3967
3968# Content-Encoding (RFC 9110 §8.4): each value, trimmed and lowercased, in the order applied.
3969def ce.keep(+w: String, +rest: List<&2, String>) -> List<&2, String>:
3970  Bool.pick(List<&2, String>, String.is_empty(w), rest, Con{w, rest})
3971
3972def ce.norm(xs: List<&2, String>) -> List<&2, String>:
3973  match xs:
3974    case Nil{}:
3975      Nil{}
3976    case Con{v, t}:
3977      ce.keep(String.to_lower(trim(v)), ce.norm(t))
3978
3979def ce.split(xs: List<&2, String>) -> List<&2, String>:
3980  match xs:
3981    case Nil{}:
3982      Nil{}
3983    case Con{v, t}:
3984      List.append(&2, String, ce.norm(String.split(v, ',')), ce.split(t))
3985
3986# Ok: decoded so far. Bad: a coding failed. Skip: a coding we cannot decode, so the body stays as sent.
3987type Dec is Type:
3988  DecOk{b: Bytes.Bytes}
3989  DecBad{}
3990  DecSkip{}
3991
3992def ce.of(m: Maybe<&1, Bytes.Bytes>) -> Dec:
3993  match m:
3994    case None{}:
3995      DecBad{}
3996    case Some{b}:
3997      DecOk{b}
3998
3999# Servers send deflate both with and without the zlib wrapper.
4000def ce.deflate.raw(b: Bytes.Bytes, m: Maybe<&1, Bytes.Bytes>) -> Dec:
4001  match m:
4002    case Some{out}:
4003      DecOk{out}
4004    case None{}:
4005      ce.of(Zlib.inflate(b))
4006
4007def ce.deflate.of(r: Bytes.Bytes & Bytes.Bytes) -> Dec:
4008  (a, b) = r
4009  ce.deflate.raw(b, Zlib.unzlib(a))
4010
4011def ce.deflate(b: Bytes.Bytes) -> Dec:
4012  ce.deflate.of(Bytes.clone(b))
4013
4014def ce.apply.id(on: Bool, b: Bytes.Bytes) -> Dec:
4015  match on:
4016    case True{}:
4017      DecOk{b}
4018    case False{}:
4019      DecSkip{}
4020
4021def ce.apply.de(on: Bool, +c: String, b: Bytes.Bytes) -> Dec:
4022  match on:
4023    case True{}:
4024      ce.deflate(b)
4025    case False{}:
4026      ce.apply.id(String.eq(c, "identity"), b)
4027
4028def ce.apply.gz(on: Bool, +c: String, b: Bytes.Bytes) -> Dec:
4029  match on:
4030    case True{}:
4031      ce.of(Zlib.gunzip(b))
4032    case False{}:
4033      ce.apply.de(String.eq(c, "deflate"), c, b)
4034
4035def ce.apply(+c: String, b: Bytes.Bytes) -> Dec:
4036  ce.apply.gz(Bool.or(String.eq(c, "gzip"), String.eq(c, "x-gzip")), c, b)
4037
4038def ce.go(cs: List<&2, String>, d: Dec) -> Dec:
4039  match cs:
4040    case Nil{}:
4041      d
4042    case Con{+c, t}:
4043      match d:
4044        case DecOk{b}:
4045          ce.go(t, ce.apply(c, b))
4046        case DecBad{}:
4047          DecBad{}
4048        case DecSkip{}:
4049          DecSkip{}
4050
4051def decoded.pick(d: Dec, +status: U32, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> Result<&1, &1, Err, Res>:
4052  match d:
4053    case DecOk{b}:
4054      Done{Res{status, headers, b}}
4055    case DecBad{}:
4056      Fail{ErrBad{}}
4057    case DecSkip{}:
4058      Done{Res{status, headers, body}}
4059
4060# One copy decodes; the other is the body as sent, for a coding we cannot decode.
4061def decoded.both(+status: U32, +headers: Map<&2, List<&2, String>>, cs: List<&2, String>, r: Bytes.Bytes & Bytes.Bytes) -> Result<&1, &1, Err, Res>:
4062  (a, body) = r
4063  decoded.pick(ce.go(cs, DecOk{a}), status, headers, body)
4064
4065def decoded.cs(+status: U32, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, cs: List<&2, String>) -> Result<&1, &1, Err, Res>:
4066  match cs:
4067    case Nil{}:
4068      Done{Res{status, headers, body}}
4069    case Con{c, t}:
4070      decoded.both(status, headers, Con{c, t}, Bytes.clone(body))
4071
4072def decoded.go(empty: Bool, +status: U32, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> Result<&1, &1, Err, Res>:
4073  match empty:
4074    case True{}:
4075      Done{Res{status, headers, body}}
4076    case False{}:
4077      decoded.cs(status, headers, body, List.reverse(&2, String, ce.split(fields(headers, "content-encoding"))))
4078
4079# The body decoded per Content-Encoding; the headers stay as sent. A corrupt body is ErrBad.
4080def decoded(res: Res) -> Result<&1, &1, Err, Res>:
4081  Res{+status, +headers, body} = res
4082  Bytes.Bytes{+len, buf} = body
4083  decoded.go(U32.is_zero(len), status, headers, Bytes.Bytes{len, buf})
4084
4085def decoded.m(r: Result<&1, &1, Err, Res>) -> Maybe<&1, Res>:
4086  match r:
4087    case Done{x}:
4088      Some{x}
4089    case Fail{e}:
4090      None{}
4091
4092def decoded.some(res: Res) -> Maybe<&1, Res>:
4093  decoded.m(decoded(res))
4094
4095def ce.words(r: Result<&1, &1, U32 & String, Bytes.Bytes>) -> Maybe<&1, Bytes.Bytes>:
4096  match r:
4097    case Fail{e}:
4098      None{}
4099    case Done{b}:
4100      Some{b}
4101
4102# A decoded body stops at 16 MiB, like a received one.
4103def ce.eff(~eff: U32 -> Bytes.Bytes -> IO(Result<&1, &1, U32 & String, Bytes.Bytes>), b: Bytes.Bytes) -> IO(Maybe<&1, Bytes.Bytes>):
4104  do IO<Maybe<&1, Bytes.Bytes>>:
4105    r : Result<&1, &1, U32 & String, Bytes.Bytes> <- eff(16777216, b)
4106    return ce.words(r)
4107
4108def ce.dec(d: Dec) -> Maybe<&1, Bytes.Bytes>:
4109  match d:
4110    case DecOk{b}:
4111      Some{b}
4112    case DecBad{}:
4113      None{}
4114    case DecSkip{}:
4115      None{}
4116
4117# ponytail: deflate runs on the pure decoder, which also takes raw DEFLATE; servers rarely send it.
4118def ce.io.deflate(on: Bool, b: Bytes.Bytes) -> IO(Maybe<&1, Bytes.Bytes>):
4119  match on:
4120    case True{}:
4121      IO.pure(Maybe<&1, Bytes.Bytes>, ce.dec(ce.deflate(b)))
4122    case False{}:
4123      IO.pure(Maybe<&1, Bytes.Bytes>, Some{b})
4124
4125def ce.io.zstd(on: Bool, +c: String, b: Bytes.Bytes) -> IO(Maybe<&1, Bytes.Bytes>):
4126  match on:
4127    case True{}:
4128      ce.eff(~Zlib.zstd, b)
4129    case False{}:
4130      ce.io.deflate(String.eq(c, "deflate"), b)
4131
4132def ce.io.br(on: Bool, +c: String, b: Bytes.Bytes) -> IO(Maybe<&1, Bytes.Bytes>):
4133  match on:
4134    case True{}:
4135      ce.eff(~Zlib.brotli, b)
4136    case False{}:
4137      ce.io.zstd(String.eq(c, "zstd"), c, b)
4138
4139# One known coding undone: gzip, x-gzip, br, zstd, deflate, or identity.
4140def ce.io.gz(on: Bool, +c: String, b: Bytes.Bytes) -> IO(Maybe<&1, Bytes.Bytes>):
4141  match on:
4142    case True{}:
4143      ce.eff(~Zlib.libz.inflate, b)
4144    case False{}:
4145      ce.io.br(String.eq(c, "br"), c, b)
4146
4147def ce.io.go(cs: List<&2, String>, m: Maybe<&1, Bytes.Bytes>) -> IO(Maybe<&1, Bytes.Bytes>):
4148  match cs:
4149    case Nil{}:
4150      match m:
4151        case None{}:
4152          IO.pure(Maybe<&1, Bytes.Bytes>, None{})
4153        case Some{b}:
4154          IO.pure(Maybe<&1, Bytes.Bytes>, Some{b})
4155    case Con{+c, t}:
4156      match m:
4157        case None{}:
4158          IO.pure(Maybe<&1, Bytes.Bytes>, None{})
4159        case Some{b}:
4160          do IO<Maybe<&1, Bytes.Bytes>>:
4161            n : Maybe<&1, Bytes.Bytes> <- ce.io.gz(Bool.or(String.eq(c, "gzip"), String.eq(c, "x-gzip")), c, b)
4162            ce.io.go(t, n)
4163
4164def ce.has(xs: List<&2, String>, +c: String) -> Bool:
4165  match xs:
4166    case Nil{}:
4167      False{}
4168    case Con{x, t}:
4169      Bool.or(String.eq(c, x), ce.has(t, c))
4170
4171def ce.known(cs: List<&2, String>, +ok: List<&2, String>) -> Bool:
4172  match cs:
4173    case Nil{}:
4174      True{}
4175    case Con{c, t}:
4176      Bool.and(ce.has(ok, c), ce.known(t, ok))
4177
4178def decoded.io.pick(m: Maybe<&1, Bytes.Bytes>, +status: U32, headers: Map<&2, List<&2, String>>) -> Result<&1, &1, Err, Res>:
4179  match m:
4180    case Some{b}:
4181      Done{Res{status, headers, b}}
4182    case None{}:
4183      Fail{ErrBad{}}
4184
4185def decoded.io.run(known: Bool, +status: U32, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, cs: List<&2, String>) -> IO(Result<&1, &1, Err, Res>):
4186  match known:
4187    case True{}:
4188      do IO<Result<&1, &1, Err, Res>>:
4189        m : Maybe<&1, Bytes.Bytes> <- ce.io.go(cs, Some{body})
4190        return decoded.io.pick(m, status, headers)
4191    case False{}:
4192      IO.pure(Result<&1, &1, Err, Res>, Done{Res{status, headers, body}})
4193
4194def decoded.io.go(empty: Bool, +enc: String, +status: U32, +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Result<&1, &1, Err, Res>):
4195  match empty:
4196    case True{}:
4197      IO.pure(Result<&1, &1, Err, Res>, Done{Res{status, headers, body}})
4198    case False{}:
4199      +cs = List.reverse(&2, String, ce.split(fields(headers, "content-encoding")))
4200      decoded.io.run(ce.known(cs, Con{"identity", Con{"x-gzip", ce.split(Con{enc, Nil{}})}}), status, headers, body, cs)
4201
4202# decoded through the C libraries, for the codings in enc. A body with any other coding stays as sent.
4203def decoded.io(+enc: String, res: Res) -> IO(Result<&1, &1, Err, Res>):
4204  Res{+status, +headers, body} = res
4205  Bytes.Bytes{+len, buf} = body
4206  decoded.io.go(U32.is_zero(len), enc, status, headers, Bytes.Bytes{len, buf})
4207
4208# An effect on empty input fails with ENOENT only when its library does not load.
4209def codings.ok(r: Result<&1, &1, U32 & String, Bytes.Bytes>) -> Bool:
4210  match r:
4211    case Fail{(+c, why)}:
4212      Bool.not(U32.is_eq(c, 2))
4213    case Done{p}:
4214      True{}
4215
4216def codings.has(~eff: U32 -> Bytes.Bytes -> IO(Result<&1, &1, U32 & String, Bytes.Bytes>), b: Bytes.Bytes) -> IO(Bool):
4217  do IO<Bool>:
4218    r : Result<&1, &1, U32 & String, Bytes.Bytes> <- eff(0, b)
4219    return codings.ok(r)
4220
4221def codings.add(has: Bool, +enc: String, +c: String) -> String:
4222  Bool.pick(String, has, enc ++ ", " ++ c, enc)
4223
4224# gzip and deflate, then br and zstd when their C library loads.
4225def codings() -> IO(String):
4226  do IO<String>:
4227    b : Bool <- codings.has(~Zlib.brotli, Bytes.new(0))
4228    z : Bool <- codings.has(~Zlib.zstd, Bytes.new(0))
4229    return codings.add(z, codings.add(b, "gzip, deflate", "br"), "zstd")
4230
4231# The final response of a fetch, after redirects.
4232def fetch.final(+enc: String, r: Result<&1, &1, Err, Res>) -> IO(Result<&1, &1, Err, Res>):
4233  match r:
4234    case Done{res}:
4235      decoded.io(enc, res)
4236    case Fail{e}:
4237      IO.pure(Result<&1, &1, Err, Res>, Fail{e})
4238
4239def pool.final(+enc: String, pr: Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>) -> IO(Pool & Result<&1, &1, Err, Res>):
4240  (p, r, jar) = pr
4241  do IO<Pool & Result<&1, &1, Err, Res>>:
4242    d : Result<&1, &1, Err, Res> <- fetch.final(enc, r)
4243    return (p, d)
4244
4245def pool.how.enc(+enc: String, p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32, mode: Mode, cert: Maybe<&2, Cert>, +ca: String) -> IO(Pool & Result<&1, &1, Err, Res>):
4246  do IO<Pool & Result<&1, &1, Err, Res>>:
4247    pr : Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar> <- pool.hops(mode, ms, Hops{p, fetch.start(method, headers, body, Url.absolute(url), enc), cert, None{}, ca})
4248    pool.final(enc, pr)
4249
4250def pool.how.cert(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32, mode: Mode, cert: Maybe<&2, Cert>) -> IO(Pool & Result<&1, &1, Err, Res>):
4251  do IO<Pool & Result<&1, &1, Err, Res>>:
4252    enc : String <- codings()
4253    pool.how.enc(enc, p, method, url, headers, body, ms, mode, cert, "")
4254
4255# pool.how(p, "GET", "https://example.com/x?y=1", headers, body, ms, mode) follows redirects
4256# on p's idle sockets. Fail is a bad URL, a failed lookup, connect, handshake, send, read,
4257# a malformed response, more than 20 redirects, or a step past ms. Bodies are bytes.
4258def pool.how(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32, mode: Mode) -> IO(Pool & Result<&1, &1, Err, Res>):
4259  pool.how.cert(p, method, url, headers, body, ms, mode, None{})
4260
4261def pool.fetch.with(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32) -> IO(Pool & Result<&1, &1, Err, Res>):
4262  pool.how(p, method, url, headers, body, ms, ModeFollow{})
4263
4264def pool.fetch(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Pool & Result<&1, &1, Err, Res>):
4265  pool.fetch.with(p, method, url, headers, body, 30000)
4266
4267def pool.fetch.cert.with(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, chain: String, key: String, ms: U32) -> IO(Pool & Result<&1, &1, Err, Res>):
4268  pool.how.cert(p, method, url, headers, body, ms, ModeFollow{}, Some{Cert{chain, key}})
4269
4270def pool.fetch.cert(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, chain: String, key: String) -> IO(Pool & Result<&1, &1, Err, Res>):
4271  pool.fetch.cert.with(p, method, url, headers, body, chain, key, 30000)
4272
4273def pool.fetch.ca.with(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ca: String, ms: U32) -> IO(Pool & Result<&1, &1, Err, Res>):
4274  do IO<Pool & Result<&1, &1, Err, Res>>:
4275    enc : String <- codings()
4276    pool.how.enc(enc, p, method, url, headers, body, ms, ModeFollow{}, None{}, ca)
4277
4278def pool.fetch.ca(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ca: String) -> IO(Pool & Result<&1, &1, Err, Res>):
4279  pool.fetch.ca.with(p, method, url, headers, body, ca, 30000)
4280
4281# One request for fetch.all: the arguments of fetch, as a record.
4282type Fetch is Type:
4283  Fetch{method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes}
4284
4285def fetch.all.job(p: Pool, f: Fetch) -> IO(Pool & Result<&1, &1, Err, Res>):
4286  Fetch{method, url, headers, body} = f
4287  pool.fetch(p, method, url, headers, body)
4288
4289def fetch.all.pools(n: Nat) -> List<Pool>:
4290  match n:
4291    case 0n:
4292      Nil{}
4293    case 1n+p:
4294      pool.new() <> fetch.all.pools(p)
4295
4296def fetch.all.close(ps: List<Pool>) -> IO(Unit):
4297  match ps:
4298    case Nil{}:
4299      IO.pure(Unit, Unit{})
4300    case p <> rest:
4301      do IO<Unit>:
4302        pool.close(p)
4303        fetch.all.close(rest)
4304
4305def fetch.all.done(r: List<Pool> & List<Result<&1, &1, Err, Res>>) -> IO(List<Result<&1, &1, Err, Res>>):
4306  (ps, rs) = r
4307  do IO<List<Result<&1, &1, Err, Res>>>:
4308    fetch.all.close(ps)
4309    return rs
4310
4311# No requests start no workers, since Conc.pool needs at least one.
4312def fetch.all.go(xs: List<Fetch>, +workers: Nat) -> IO(List<Result<&1, &1, Err, Res>>):
4313  match xs:
4314    case Nil{}:
4315      IO.pure(List<Result<&1, &1, Err, Res>>, Nil{})
4316    case x <> rest:
4317      do IO<List<Result<&1, &1, Err, Res>>>:
4318        r : List<Pool> & List<Result<&1, &1, Err, Res>> <-
4319          Conc.pool(~Pool, ~Fetch, ~Result<&1, &1, Err, Res>, ~fetch.all.job, fetch.all.pools(workers), x <> rest)
4320        fetch.all.done(r)
4321
4322def fetch.all.of(+n: U32, nx: Nat & List<Fetch>) -> IO(List<Result<&1, &1, Err, Res>>):
4323  (+len, xs) = nx
4324  fetch.all.go(xs, Nat.min(len, Nat.max(1n, U32.to_nat(n))))
4325
4326# Each request as fetch would make it, on at most n workers (0 counts as 1, and
4327# never more workers than requests). Each worker owns its own Pool, so an idle
4328# socket is reused only by the worker that opened it; every pool is closed at the
4329# end. Results come back in the order of reqs.
4330def fetch.all(reqs: List<Fetch>, +n: U32) -> IO(List<Result<&1, &1, Err, Res>>):
4331  fetch.all.of(n, Conc.count(Fetch, reqs))
4332
4333# The hops always hand the jar back; j is only the type's fallback.
4334def pool.jar.final(+enc: String, j: Jar, pr: Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar>) -> IO(Pool & Jar & Result<&1, &1, Err, Res>):
4335  (p, r, jar) = pr
4336  do IO<Pool & Jar & Result<&1, &1, Err, Res>>:
4337    d : Result<&1, &1, Err, Res> <- fetch.final(enc, r)
4338    return (p, Maybe.default(&2, Jar, jar, j), d)
4339
4340def pool.jar.enc(+enc: String, p: Pool, +j: Jar, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Pool & Jar & Result<&1, &1, Err, Res>):
4341  do IO<Pool & Jar & Result<&1, &1, Err, Res>>:
4342    pr : Pool & Result<&1, &1, Err, Res> & Maybe<&2, Jar> <- pool.hops(ModeFollow{}, 30000, Hops{p, fetch.start(method, headers, body, Url.absolute(url), enc), None{}, Some{j}, ""})
4343    pool.jar.final(enc, j, pr)
4344
4345# pool.fetch with a cookie jar (RFC 6265bis). Before every hop, redirects included, the jar's
4346# cookies for that URL join the request's cookie field; after every response, redirects included,
4347# its Set-Cookie fields go into the jar. The jar's clock is the caller's: advance it with jar.at.
4348def pool.fetch.jar(p: Pool, j: Jar, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Pool & Jar & Result<&1, &1, Err, Res>):
4349  do IO<Pool & Jar & Result<&1, &1, Err, Res>>:
4350    enc : String <- codings()
4351    pool.jar.enc(enc, p, j, method, url, headers, body)
4352def fetch.end(pr: Pool & Result<&1, &1, Err, Res>) -> IO(Result<&1, &1, Err, Res>):
4353  (p, r) = pr
4354  do IO<Result<&1, &1, Err, Res>>:
4355    pool.close(p)
4356    return r
4357
4358# fetch is pool.how on a fresh pool that closes afterwards.
4359def fetch.how(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32, mode: Mode) -> IO(Result<&1, &1, Err, Res>):
4360  do IO<Result<&1, &1, Err, Res>>:
4361    pr : Pool & Result<&1, &1, Err, Res> <- pool.how(pool.new(), method, url, headers, body, ms, mode)
4362    fetch.end(pr)
4363
4364def fetch.with(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ms: U32) -> IO(Result<&1, &1, Err, Res>):
4365  fetch.how(method, url, headers, body, ms, ModeFollow{})
4366
4367def fetch.cert.with(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, chain: String, key: String, ms: U32) -> IO(Result<&1, &1, Err, Res>):
4368  do IO<Result<&1, &1, Err, Res>>:
4369    pr : Pool & Result<&1, &1, Err, Res> <- pool.fetch.cert.with(pool.new(), method, url, headers, body, chain, key, ms)
4370    fetch.end(pr)
4371
4372def fetch.cert(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, chain: String, key: String) -> IO(Result<&1, &1, Err, Res>):
4373  fetch.cert.with(method, url, headers, body, chain, key, 30000)
4374
4375def fetch.ca.with(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ca: String, ms: U32) -> IO(Result<&1, &1, Err, Res>):
4376  do IO<Result<&1, &1, Err, Res>>:
4377    pr : Pool & Result<&1, &1, Err, Res> <- pool.fetch.ca.with(pool.new(), method, url, headers, body, ca, ms)
4378    fetch.end(pr)
4379
4380def fetch.ca(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, ca: String) -> IO(Result<&1, &1, Err, Res>):
4381  fetch.ca.with(method, url, headers, body, ca, 30000)
4382
4383# fetch.with and a 30 s step timeout.
4384def fetch(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Result<&1, &1, Err, Res>):
4385  fetch.with(method, url, headers, body, 30000)
4386
4387def get(url: String) -> IO(Result<&1, &1, Err, Res>):
4388  fetch("GET", url, empty(), Bytes.new(0))
4389
4390# POST v as compact JSON, with content-type: application/json.
4391def post.json(url: String, v: Json.Val) -> IO(Result<&1, &1, Err, Res>):
4392  fetch("POST", url, set(empty(), "content-type", "application/json"), Json.encode.bytes(v))
4393
4394# Base URLs: resolve(base, ref) is ref against the absolute http(s) URL base (RFC 3986 §5.2),
4395# written back as a URL for fetch. None when base is not absolute or ref has another scheme.
4396def resolve.show(a: Url.Abs) -> String:
4397  Url.Abs{+scheme, host, port, +target} = a
4398  scheme ++ "://" ++ Url.host_field(Url.Abs{scheme, host, port, target}) ++ target
4399
4400def resolve.to(m: Maybe<&2, Url.Abs>) -> Maybe<&2, String>:
4401  match m:
4402    case None{}:
4403      None{}
4404    case Some{a}:
4405      Some{resolve.show(a)}
4406
4407def resolve.base(m: Maybe<&2, Url.Abs>, +ref: String) -> Maybe<&2, String>:
4408  match m:
4409    case None{}:
4410      None{}
4411    case Some{b}:
4412      resolve.to(Url.resolve(b, ref))
4413
4414def resolve(base: String, +ref: String) -> Maybe<&2, String>:
4415  resolve.base(Url.absolute(base), ref)
4416
4417# Retry. The wait before retry n (0 first) is 500 ms doubled n times, at most 30 s;
4418# its upper half is jitter r: backoff(n, r) = half + r mod (half + 1).
4419def retry.cap() -> U32:
4420  30000
4421
4422def retry.base(n: Nat) -> U32:
4423  match n:
4424    case 0n:
4425      500
4426    case 1n+p:
4427      U32.min((retry.base(p) * 2 : U32), retry.cap())
4428
4429def retry.backoff.half(+half: U32, +r: U32) -> U32:
4430  (half + (r % (half + 1)) : U32)
4431
4432def retry.backoff(n: Nat, +r: U32) -> U32:
4433  retry.backoff.half((retry.base(n) / 2 : U32), r)
4434
4435# ms clamped to 0 .. retry.cap().
4436def retry.after.ms(+ms: Int.I64) -> U32:
4437  Bool.pick(U32, Int.I64.is_neg(ms), 0,
4438    Bool.pick(U32, Int.I64.is_lt(ms, Int.I64.from_u32(retry.cap())), Int.I64.to_u32(ms), retry.cap()))
4439
4440def retry.after.date(m: Maybe<&2, Time.Instant>, now: Time.Instant) -> Maybe<&2, U32>:
4441  match m:
4442    case None{}:
4443      None{}
4444    case Some{date}:
4445      Some{retry.after.ms(Time.Duration.to_ms(Time.Instant.since(date, now)))}
4446
4447# More than 5 digits is past the cap, and would overflow parse_u32.
4448def retry.after.secs(+v: String, now: Time.Instant, n: Maybe<&2, U32>) -> Maybe<&2, U32>:
4449  match n:
4450    case None{}:
4451      retry.after.date(Time.http_date.parse(v), now)
4452    case Some{s}:
4453      Some{Bool.pick(U32, Nat.is_lt(5n, String.length(v)), retry.cap(), U32.min((s * 1000 : U32), retry.cap()))}
4454
4455# Retry-After (RFC 9110 §10.2.3) as ms from now, at most retry.cap(): delay-seconds, or an
4456# HTTP-date (a past date is 0). None when v is empty or neither form.
4457def retry.after(+v: String, now: Time.Instant) -> Maybe<&2, U32>:
4458  retry.after.secs(v, now, parse_u32(v))
4459
4460def retry.wait(m: Maybe<&2, U32>, n: Nat, +r: U32) -> U32:
4461  match m:
4462    case None{}:
4463      retry.backoff(n, r)
4464    case Some{ms}:
4465      ms
4466
4467# The wait before retry n: Retry-After v when it reads as a delay, else backoff with jitter r.
4468def retry.delay(+v: String, now: Time.Instant, n: Nat, +r: U32) -> U32:
4469  retry.wait(retry.after(v, now), n, r)
4470
4471# Statuses a later try may clear.
4472def retry.status(+s: U32) -> Bool:
4473  Bool.or(Bool.or(U32.is_eq(s, 408), U32.is_eq(s, 429)),
4474    Bool.or(Bool.or(U32.is_eq(s, 500), U32.is_eq(s, 502)), Bool.or(U32.is_eq(s, 503), U32.is_eq(s, 504))))
4475
4476# A refused connect or a timeout may clear; any other error would repeat.
4477def retry.fail(e: Err) -> Result<&1, &1, Err, Res> & Maybe<&2, String>:
4478  match e:
4479    case ErrConnect{code, why}:
4480      (Fail{ErrConnect{code, why}}, Some{""})
4481    case ErrTimeout{}:
4482      (Fail{ErrTimeout{}}, Some{""})
4483    case other:
4484      (Fail{other}, None{})
4485
4486def retry.res(res: Res) -> Result<&1, &1, Err, Res> & Maybe<&2, String>:
4487  Res{+status, +headers, body} = res
4488  (Done{Res{status, headers, body}}, Bool.pick(Maybe<&2, String>, retry.status(status), Some{header(headers, "retry-after")}, None{}))
4489
4490def retry.judge.try(r: Result<&1, &1, Err, Res>) -> Result<&1, &1, Err, Res> & Maybe<&2, String>:
4491  match r:
4492    case Fail{e}:
4493      retry.fail(e)
4494    case Done{res}:
4495      retry.res(res)
4496
4497# One try, judged: the result as it came, and Some{Retry-After value} ("" when absent)
4498# when an idempotent request may try again.
4499def retry.judge(idem: Bool, r: Result<&1, &1, Err, Res>) -> Result<&1, &1, Err, Res> & Maybe<&2, String>:
4500  match idem:
4501    case False{}:
4502      (r, None{})
4503    case True{}:
4504      retry.judge.try(r)
4505
4506# ponytail: a failed random source gives jitter 0, so the wait is the lower half.
4507def retry.jitter(r: Result<&1, &1, U32 & String, U32>) -> U32:
4508  match r:
4509    case Fail{e}:
4510      0
4511    case Done{x}:
4512      x
4513
4514def retry.sleep(+v: String, n: Nat) -> IO(Unit):
4515  do IO<Unit>:
4516    now : Time.Instant <- Time.now()
4517    rnd : Result<&1, &1, U32 & String, U32> <- IO.random_u32()
4518    IO.sleep(retry.delay(v, now, n, retry.jitter(rnd)))
4519
4520def retry.kept(body: Bytes.Bytes, j: Result<&1, &1, Err, Res> & Maybe<&2, String>) -> Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String>:
4521  (r, m) = j
4522  (body, r, m)
4523
4524# One fetch of the copy; the body comes back for the next try.
4525def retry.one(+idem: Bool, +method: String, +url: String, +headers: Map<&2, List<&2, String>>,
4526    r: Bytes.Bytes & Bytes.Bytes) -> IO(Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String>):
4527  (body, spare) = r
4528  do IO<Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String>>:
4529    got : Result<&1, &1, Err, Res> <- fetch(method, url, headers, spare)
4530    return retry.kept(body, retry.judge(idem, got))
4531
4532# left: retries still allowed. n: the retry to wait for next, 0 first.
4533def retry.go(left: Nat, +n: Nat, +idem: Bool, +method: String, +url: String,
4534    +headers: Map<&2, List<&2, String>>,
4535    t: Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String>) -> IO(Result<&1, &1, Err, Res>):
4536  match left:
4537    case 0n:
4538      (body, r, m) = t
4539      IO.pure(Result<&1, &1, Err, Res>, r)
4540    case 1n+p:
4541      (body, r, m) = t
4542      match m:
4543        case None{}:
4544          IO.pure(Result<&1, &1, Err, Res>, r)
4545        case Some{v}:
4546          do IO<Result<&1, &1, Err, Res>>:
4547            retry.sleep(v, n)
4548            tried : Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String> <-
4549              retry.one(idem, method, url, headers, Bytes.slice(body, 0, 4294967295))
4550            retry.go(p, 1n+n, idem, method, url, headers, tried)
4551
4552# fetch.retry(n, method, url, headers, body) is fetch, tried again up to n times while an
4553# idempotent method (GET, HEAD, OPTIONS, TRACE, PUT, DELETE) gets a connect error, a timeout,
4554# or 408, 429, 500, 502, 503, or 504. POST, PATCH and other methods get one try. It waits
4555# Retry-After (seconds or HTTP-date, at most 30 s) when given, else the backoff. When retries
4556# run out, the last response or error comes back as it was.
4557def retry.start(enabled: Bool, n: U32, +method: String, +url: String,
4558    +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) ->
4559    IO(Result<&1, &1, Err, Res>):
4560  match enabled:
4561    case False{}:
4562      fetch(method, url, headers, body)
4563    case True{}:
4564      do IO<Result<&1, &1, Err, Res>>:
4565        first : Bytes.Bytes & Result<&1, &1, Err, Res> & Maybe<&2, String> <-
4566          retry.one(True{}, method, url, headers, Bytes.slice(body, 0, 4294967295))
4567        retry.go(U32.to_nat(n), 0n, True{}, method, url, headers, first)
4568
4569def fetch.retry(+n: U32, +method: String, +url: String,
4570    +headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) ->
4571    IO(Result<&1, &1, Err, Res>):
4572  retry.start(Bool.and(U32.is_ne(n, 0), pool.idempotent(method)),
4573    n, method, url, headers, body)
4574
4575# Streaming (a body read or sent in pieces). A decoded stream owns its decoders.
4576# What the next read returns: read more, a raw piece, a decoded piece, the end, or an error.
4577type Po is Type:
4578  PoNone{}
4579  PoPiece{b: Bytes.Bytes}
4580  PoReady{b: Bytes.Bytes}
4581  PoEnd{}
4582  PoBad{e: Err}
4583
4584# back: the pool key, while the connection can take another request after this body.
4585type Stream is Type:
4586  Stream{tls: Bool, s: Socket, ms: U32, res: Res, rb: Rb, out: Po, decs: List<&1, Zlib.Decoder>, back: Maybe<&2, String>}
4587
4588type Pc is Type:
4589  Pc{rb: Rb, out: Po}
4590
4591# A body piece, or none when empty: more reads the next piece, and not more is the end.
4592def pc.po(more: Bool, empty: Bool, b: Bytes.Bytes) -> Po:
4593  match empty:
4594    case True{}:
4595      Bool.pick(Po, more, PoNone{}, PoEnd{})
4596    case False{}:
4597      PoPiece{b}
4598
4599def pc.piece(more: Bool, +rb: Rb, b: Bytes.Bytes) -> Pc:
4600  Bytes.Bytes{+len, buf} = b
4601  Pc{rb, pc.po(more, U32.is_zero(len), Bytes.Bytes{len, buf})}
4602
4603def pc.fed.of(b: Bytes.Bytes, st: Rb) -> Pc:
4604  match st:
4605    case RbDone{}:
4606      pc.piece(False{}, RbDone{}, b)
4607    case RbBad{}:
4608      Pc{RbDone{}, PoBad{ErrBad{}}}
4609    case RbLen{n}:
4610      pc.piece(True{}, RbLen{n}, b)
4611    case RbChunk{st}:
4612      pc.piece(True{}, RbChunk{st}, b)
4613    case RbClose{}:
4614      pc.piece(True{}, RbClose{}, b)
4615
4616def pc.fed(bf: Bf) -> Pc:
4617  Bf{st, parts, rest} = bf
4618  pc.fed.of(rb.body(parts), st)
4619
4620# The body bytes that just arrived: what they add to the body, and how it ends.
4621def pc.of(rb: Rb, piece: Bytes.Bytes) -> Pc:
4622  pc.fed(rb.feed(rb, Nil{}, piece))
4623
4624# The server closed. Only a close-delimited body may end this way (RFC 9112 §8).
4625def pc.end(ok: Bool) -> Pc:
4626  match ok:
4627    case True{}:
4628      Pc{RbDone{}, PoEnd{}}
4629    case False{}:
4630      Pc{RbDone{}, PoBad{ErrBad{}}}
4631
4632def pc.eof(st: Rb) -> Pc:
4633  pc.end(rb.eof(st))
4634
4635# pc.of, and whether bytes came past the end of the body.
4636def stream.pc.of(bf: Bf) -> Pc & Bool:
4637  Bf{st, parts, rest} = bf
4638  Bytes.Bytes{+len, buf} = rest
4639  (pc.fed.of(rb.body(parts), st), Bool.not(U32.is_zero(len)))
4640
4641def stream.pc(rb: Rb, piece: Bytes.Bytes) -> Pc & Bool:
4642  stream.pc.of(rb.feed(rb, Nil{}, piece))
4643
4644# Bytes past the body put the socket out of step with the server, so it cannot go back to a pool.
4645def stream.mk(+tls: Bool, s: Socket, +ms: U32, res: Res, decs: List<&1, Zlib.Decoder>, back: Maybe<&2, String>, x: Pc & Bool) -> Stream:
4646  (pc, extra) = x
4647  Pc{rb, out} = pc
4648  Stream{tls, s, ms, res, rb, out, decs, Bool.pick(Maybe<&2, String>, extra, None{}, back)}
4649
4650def stream.got.piece(+tls: Bool, s: Socket, +ms: U32, res: Res, decs: List<&1, Zlib.Decoder>, back: Maybe<&2, String>, +rb: Rb, p: U32 & Array<U32>) -> Stream:
4651  (+len, buf) = p
4652  stream.mk(tls, s, ms, res, decs, back, Bool.pick(Pc & Bool, U32.is_zero(len), (pc.eof(rb), False{}), stream.pc(rb, Bytes.Bytes{len, buf})))
4653
4654def stream.got(+tls: Bool, +ms: U32, res: Res, decs: List<&1, Zlib.Decoder>, back: Maybe<&2, String>, +rb: Rb, m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> Stream:
4655  (s, r) = m
4656  match r:
4657    case Fail{(+code, why)}:
4658      Stream{tls, s, ms, res, RbDone{}, PoBad{err.or_late(code, ErrRead{code, why})}, decs, back}
4659    case Done{p}:
4660      stream.got.piece(tls, s, ms, res, decs, back, rb, p)
4661
4662def stream.after(+rb: Rb) -> Po:
4663  match rb:
4664    case RbDone{}:
4665      PoEnd{}
4666    case RbBad{}:
4667      PoEnd{}
4668    case RbLen{n}:
4669      PoNone{}
4670    case RbChunk{st}:
4671      PoNone{}
4672    case RbClose{}:
4673      PoNone{}
4674
4675type Fed is Type:
4676  Fed{decs: List<&1, Zlib.Decoder>, body: Result<&1, &1, Err, Bytes.Bytes>}
4677
4678def stream.feed.received(done: List<&1, Zlib.Decoder>, m: Zlib.Decoder & Result<&1, &1, U32 & String, Bytes.Bytes>) -> Fed:
4679  (d, r) = m
4680  match r:
4681    case Fail{e}:
4682      Fed{d <> done, Fail{ErrBad{}}}
4683    case Done{b}:
4684      Fed{d <> done, Done{b}}
4685
4686def stream.feed.step(st: Fed, d: Zlib.Decoder) -> IO(Fed):
4687  Fed{done, body} = st
4688  match body:
4689    case Fail{e}:
4690      IO.pure(Fed, Fed{d <> done, Fail{e}})
4691    case Done{b}:
4692      do IO<Fed>:
4693        m : Zlib.Decoder & Result<&1, &1, U32 & String, Bytes.Bytes> <- Zlib.dec.push(d, 16777216, b)
4694        return stream.feed.received(done, m)
4695
4696def stream.feed.one(prev: IO(Fed), d: Zlib.Decoder) -> IO(Fed):
4697  do IO<Fed>:
4698    st : Fed <- prev
4699    stream.feed.step(st, d)
4700
4701def stream.feed.reverse(st: Fed) -> Fed:
4702  Fed{done, body} = st
4703  Fed{List.reverse(&1, Zlib.Decoder, done), body}
4704
4705def stream.feed.all(decs: List<&1, Zlib.Decoder>, b: Bytes.Bytes) -> IO(Fed):
4706  do IO<Fed>:
4707    st : Fed <- List.foldl(~&1, ~Zlib.Decoder, ~IO(Fed), ~stream.feed.one, decs,
4708      IO.pure(Fed, Fed{Nil{}, Done{b}}))
4709    return stream.feed.reverse(st)
4710
4711def stream.ready(empty: Bool, +rb: Rb, b: Bytes.Bytes) -> Po:
4712  match empty:
4713    case True{}:
4714      stream.after(rb)
4715    case False{}:
4716      PoReady{b}
4717
4718def stream.feed.got(+tls: Bool, s: Socket, +ms: U32, res: Res, +rb: Rb, back: Maybe<&2, String>, st: Fed) -> Stream:
4719  Fed{decs, body} = st
4720  match body:
4721    case Fail{e}:
4722      Stream{tls, s, ms, res, rb, PoBad{e}, decs, back}
4723    case Done{b}:
4724      Bytes.Bytes{+len, buf} = b
4725      Stream{tls, s, ms, res, rb, stream.ready(U32.is_zero(len), rb, Bytes.Bytes{len, buf}), decs, back}
4726
4727def stream.feed.st(st: Stream, b: Bytes.Bytes) -> IO(Stream):
4728  Stream{+tls, s, +ms, res, +rb, out, decs, back} = st
4729  match decs:
4730    case Nil{}:
4731      IO.pure(Stream, Stream{tls, s, ms, res, rb, PoReady{b}, Nil{}, back})
4732    case Con{d, rest}:
4733      do IO<Stream>:
4734        fed : Fed <- stream.feed.all(Con{d, rest}, b)
4735        return stream.feed.got(tls, s, ms, res, rb, back, fed)
4736
4737def stream.finish.ok(r: Result<&1, &1, U32 & String, Unit>) -> Bool:
4738  match r:
4739    case Fail{e}:
4740      False{}
4741    case Done{u}:
4742      True{}
4743
4744def stream.finish(decs: List<&1, Zlib.Decoder>) -> IO(Bool):
4745  match decs:
4746    case Nil{}:
4747      IO.pure(Bool, True{})
4748    case Con{d, rest}:
4749      do IO<Bool>:
4750        r : Result<&1, &1, U32 & String, Unit> <- Zlib.dec.finish(d)
4751        tail : Bool <- stream.finish(rest)
4752        return Bool.and(stream.finish.ok(r), tail)
4753
4754def stream.ended(good: Bool, +tls: Bool, s: Socket, +ms: U32, res: Res, +rb: Rb, back: Maybe<&2, String>) ->
4755  Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>:
4756  match good:
4757    case True{}:
4758      (Stream{tls, s, ms, res, rb, PoEnd{}, Nil{}, back}, Done{None{}})
4759    case False{}:
4760      (Stream{tls, s, ms, res, rb, PoBad{ErrBad{}}, Nil{}, back}, Fail{ErrBad{}})
4761
4762# The next piece of the body; None at the end. A piece is never empty.
4763@unsafe
4764def stream.read(st: Stream) -> IO(Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>):
4765  Stream{+tls, s, +ms, res, +rb, out, decs, back} = st
4766  match out:
4767    case PoPiece{b}:
4768      do IO<Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>>:
4769        next : Stream <- stream.feed.st(Stream{tls, s, ms, res, rb, PoNone{}, decs, back}, b)
4770        stream.read(next)
4771    case PoReady{b}:
4772      IO.pure(Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>,
4773        (Stream{tls, s, ms, res, rb, stream.after(rb), decs, back}, Done{Some{b}}))
4774    case PoEnd{}:
4775      do IO<Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>>:
4776        good : Bool <- stream.finish(decs)
4777        return stream.ended(good, tls, s, ms, res, rb, back)
4778    case PoBad{+e}:
4779      IO.pure(Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>,
4780        (Stream{tls, s, ms, res, rb, PoBad{e}, decs, back}, Fail{e}))
4781    case PoNone{}:
4782      do IO<Stream & Result<&1, &1, Err, Maybe<&1, Bytes.Bytes>>>:
4783        m : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <- io.recv.words(tls, s, 65536, ms)
4784        stream.read(stream.got(tls, ms, res, decs, back, rb, m))
4785
4786# Status and headers. Its body is empty: read the body with stream.read.
4787def stream.res(st: Stream) -> Stream & Res:
4788  Stream{tls, s, ms, res, rb, out, decs, back} = st
4789  Res{+status, +headers, body} = res
4790  (Stream{tls, s, ms, Res{status, headers, body}, rb, out, decs, back}, Res{status, headers, Bytes.new(0)})
4791
4792def stream.close(st: Stream) -> IO(Unit):
4793  Stream{tls, s, ms, res, rb, out, decs, back} = st
4794  do IO<Unit>:
4795    good : Bool <- stream.finish(decs)
4796    io.close(tls, s)
4797
4798def stream.po.end(out: Po) -> Bool:
4799  match out:
4800    case PoEnd{}:
4801      True{}
4802    case other:
4803      False{}
4804
4805def stream.rb.done(+rb: Rb) -> Bool:
4806  match rb:
4807    case RbDone{}:
4808      True{}
4809    case other:
4810      False{}
4811
4812def stream.give(whole: Bool, back: Maybe<&2, String>, p: Pool, +tls: Bool, s: Socket) -> IO(Pool):
4813  match whole:
4814    case True{}:
4815      match back:
4816        case Some{+key}:
4817          pool.give(p, key, Conn{tls, s})
4818        case None{}:
4819          do IO<Pool>:
4820            io.close(tls, s)
4821            return p
4822    case False{}:
4823      do IO<Pool>:
4824        io.close(tls, s)
4825        return p
4826
4827# stream.close, but a body read to its end and decoded cleanly gives its connection to the pool.
4828def pool.stream.close(p: Pool, st: Stream) -> IO(Pool):
4829  Stream{+tls, s, ms, res, +rb, out, decs, back} = st
4830  do IO<Pool>:
4831    good : Bool <- stream.finish(decs)
4832    stream.give(Bool.and(good, Bool.and(stream.po.end(out), stream.rb.done(rb))), back, p, tls, s)
4833
4834# Head read state: still scanning, cut short or bad, or the head with the bytes after it.
4835# none: no response byte arrived, so a reused socket may be stale.
4836type Sh is Type:
4837  ShMore{buf: Bytes.Bytes, k: U32}
4838  ShBad{err: Maybe<&2, Err>, none: Bool}
4839  ShRes{res: Res, rb: Rb, rest: Bytes.Bytes}
4840
4841def sh.of(hs: Hs) -> Sh:
4842  match hs:
4843    case HsWait{buf, k}:
4844      ShMore{buf, k}
4845    case HsBad{}:
4846      ShBad{None{}, False{}}
4847    case HsSeek{buf, from, k}:
4848      ShBad{None{}, False{}}
4849    case HsRes{res, rb, rest}:
4850      ShRes{res, rb, rest}
4851
4852def sh.none(buf: Bytes.Bytes, +k: U32) -> Bool:
4853  Bytes.Bytes{+len, b} = buf
4854  Bool.and(U32.is_zero(len), U32.is_zero(k))
4855
4856def sh.piece(+head: Bool, buf: Bytes.Bytes, +k: U32, p: U32 & Array<U32>) -> Sh:
4857  Bytes.Bytes{+ol, ob} = buf
4858  (+len, b) = p
4859  Bool.pick(Sh, U32.is_zero(len), ShBad{None{}, Bool.and(U32.is_zero(ol), U32.is_zero(k))}, sh.of(hs.more(head, k, Bytes.Bytes{ol, ob}, Bytes.Bytes{len, b})))
4860
4861def sh.next(+head: Bool, buf: Bytes.Bytes, +k: U32, m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> Socket & Sh:
4862  (s, r) = m
4863  match r:
4864    case Fail{(+code, why)}:
4865      (s, ShBad{Some{err.or_late(code, ErrRead{code, why})}, sh.none(buf, k)})
4866    case Done{p}:
4867      (s, sh.piece(head, buf, k, p))
4868
4869def fetch.fail.of(r: Result<&1, &1, Err, Res>) -> Result<&1, &1, Err, Stream>:
4870  match r:
4871    case Fail{e}:
4872      Fail{e}
4873    case Done{res}:
4874      Fail{ErrBad{}}
4875
4876# The connection can take another request only when the server did not say close and the body does not end at close.
4877def stream.back(back: Maybe<&2, String>, +rb: Rb, +headers: Map<&2, List<&2, String>>) -> Maybe<&2, String>:
4878  Bool.pick(Maybe<&2, String>, Bool.and(Bool.not(rb.eof(rb)), Bool.not(res.conn_close(headers))), back, None{})
4879
4880# Reads until the final head is in; the body bytes that came with it are the first piece.
4881@unsafe
4882def stream.head(+tls: Bool, +ms: U32, +head: Bool, back: Maybe<&2, String>, st: Socket & Sh) -> IO(Bool & Result<&1, &1, Err, Stream>):
4883  (s, sh) = st
4884  match sh:
4885    case ShMore{buf, +k}:
4886      do IO<Bool & Result<&1, &1, Err, Stream>>:
4887        m : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <- io.recv.words(tls, s, 65536, ms)
4888        stream.head(tls, ms, head, back, sh.next(head, buf, k, m))
4889    case ShBad{err, +none}:
4890      do IO<Bool & Result<&1, &1, Err, Stream>>:
4891        r : Result<&1, &1, Err, Res> <- fetch.bad(tls, s, err)
4892        return (none, fetch.fail.of(r))
4893    case ShRes{res, +rb, rest}:
4894      Res{+status, +headers, body} = res
4895      IO.pure(Bool & Result<&1, &1, Err, Stream>, (False{}, Done{stream.mk(tls, s, ms, Res{status, headers, body}, Nil{},
4896        stream.back(back, rb, headers), stream.pc(rb, rest))}))
4897
4898def stream.sent(+tls: Bool, +ms: U32, +head: Bool, back: Maybe<&2, String>, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Bool & Result<&1, &1, Err, Stream>):
4899  (s, r) = m
4900  match r:
4901    case Fail{(+code, why)}:
4902      do IO<Bool & Result<&1, &1, Err, Stream>>:
4903        io.close(tls, s)
4904        return (True{}, Fail{err.or_late(code, ErrWrite{code, why})})
4905    case Done{u}:
4906      stream.head(tls, ms, head, back, (s, ShMore{Bytes.new(0), 0}))
4907
4908# The bool is true when the open failed before any response byte.
4909def stream.conn(r: Result<&1, &1, Err, Conn>, +ms: U32, +head: Bool, back: Maybe<&2, String>, wire: Bytes.Bytes) -> IO(Bool & Result<&1, &1, Err, Stream>):
4910  match r:
4911    case Fail{e}:
4912      IO.pure(Bool & Result<&1, &1, Err, Stream>, (False{}, Fail{e}))
4913    case Done{c}:
4914      match c:
4915        case Conn{+tls, s}:
4916          do IO<Bool & Result<&1, &1, Err, Stream>>:
4917            sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, wire)
4918            stream.sent(tls, ms, head, back, sent)
4919        case ConnH2{s, client}:
4920          do IO<Bool & Result<&1, &1, Err, Stream>>:
4921            Wire.tls.close(s)
4922            return (False{}, Fail{ErrBad{}})
4923
4924def stream.opened(x: Bool & Result<&1, &1, Err, Stream>) -> Result<&1, &1, Err, Stream>:
4925  (none, r) = x
4926  r
4927
4928# A new connection, with no h2 offer.
4929def stream.fresh(+ms: U32, +head: Bool, back: Maybe<&2, String>, route: Route, wire: Bytes.Bytes) -> IO(Result<&1, &1, Err, Stream>):
4930  do IO<Result<&1, &1, Err, Stream>>:
4931    c : Result<&1, &1, Err, Conn> <- route.open(ms, False{}, route)
4932    x : Bool & Result<&1, &1, Err, Stream> <- stream.conn(c, ms, head, back, wire)
4933    return stream.opened(x)
4934
4935def stream.stale(again: Bool, p: Pool, +ms: U32, +head: Bool, +key: String, route: Route, spare: Bytes.Bytes, e: Err) ->
4936    IO(Pool & Result<&1, &1, Err, Stream>):
4937  match again:
4938    case True{}:
4939      do IO<Pool & Result<&1, &1, Err, Stream>>:
4940        got : Result<&1, &1, Err, Stream> <- stream.fresh(ms, head, Some{key}, route, spare)
4941        return (p, got)
4942    case False{}:
4943      IO.pure(Pool & Result<&1, &1, Err, Stream>, (p, Fail{e}))
4944
4945def stream.reused(p: Pool, +ms: U32, +head: Bool, +idem: Bool, +key: String, route: Route, spare: Bytes.Bytes,
4946    x: Bool & Result<&1, &1, Err, Stream>) -> IO(Pool & Result<&1, &1, Err, Stream>):
4947  (+none, r) = x
4948  match r:
4949    case Done{st}:
4950      IO.pure(Pool & Result<&1, &1, Err, Stream>, (p, Done{st}))
4951    case Fail{e}:
4952      stream.stale(pool.retry(none, idem), p, ms, head, key, route, spare, e)
4953
4954# An idle socket from the pool; an h2 session goes back, since a stream speaks HTTP/1.1.
4955def stream.idle(p: Pool, +ms: U32, +head: Bool, +idem: Bool, +key: String, route: Route,
4956    r: Bytes.Bytes & Bytes.Bytes, c: Conn) -> IO(Pool & Result<&1, &1, Err, Stream>):
4957  (wire, spare) = r
4958  match c:
4959    case Conn{tls, s}:
4960      do IO<Pool & Result<&1, &1, Err, Stream>>:
4961        x : Bool & Result<&1, &1, Err, Stream> <- stream.conn(Done{Conn{tls, s}}, ms, head, Some{key}, wire)
4962        stream.reused(p, ms, head, idem, key, route, spare, x)
4963    case ConnH2{s, client}:
4964      do IO<Pool & Result<&1, &1, Err, Stream>>:
4965        next : Pool <- pool.give(p, key, ConnH2{s, client})
4966        got : Result<&1, &1, Err, Stream> <- stream.fresh(ms, head, Some{key}, route, spare)
4967        return (next, got)
4968
4969# Copy the request only for a reused socket, for a stale-socket retry.
4970def stream.take(taken: Pool & Maybe<&1, Conn>, +ms: U32, +head: Bool, +idem: Bool, +key: String, route: Route,
4971    wire: Bytes.Bytes) -> IO(Pool & Result<&1, &1, Err, Stream>):
4972  (p, m) = taken
4973  match m:
4974    case None{}:
4975      do IO<Pool & Result<&1, &1, Err, Stream>>:
4976        got : Result<&1, &1, Err, Stream> <- stream.fresh(ms, head, Some{key}, route, wire)
4977        return (p, got)
4978    case Some{c}:
4979      stream.idle(p, ms, head, idem, key, route, Bytes.slice(wire, 0, 4294967295), c)
4980
4981# One HTTP/1.1 connection per stream: an idle one from the pool, else a new one. The request keeps it alive.
4982# A reused socket that fails before any response byte is tried once more on a new one, for an idempotent method.
4983def stream.route(p: Pool, +ms: U32, r: String & Send & Route) -> IO(Pool & Result<&1, &1, Err, Stream>):
4984  (+key, request, route) = r
4985  Send{+method, target, authority, headers, body} = request
4986  do IO<Pool & Result<&1, &1, Err, Stream>>:
4987    taken : Pool & Maybe<&1, Conn> <- pool.take.live(p, key)
4988    stream.take(taken, ms, String.eq(method, "HEAD"), pool.idempotent(method), key, route,
4989      encode_req.on(method, target, authority, headers, body, False{}))
4990
4991def stream.origin.choice(p: Pool, +method: String, +ms: U32,
4992    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, hf: String,
4993    scheme: String, host: String, port: U32, target: String,
4994    cert: Maybe<&2, Cert>, +ca: String, choice: Maybe<&2, Proxy>) ->
4995    IO(Pool & Result<&1, &1, Err, Stream>):
4996  match choice:
4997    case None{}:
4998      IO.pure(Pool & Result<&1, &1, Err, Stream>, (p, Fail{ErrUrl{}}))
4999    case Some{proxy}:
5000      stream.route(p, ms, origin.route(method, headers, body, hf, scheme, host,
5001        port, target, cert, ca, proxy))
5002
5003# cert and ca: the client identity and trusted CA file for this origin.
5004def stream.origin(p: Pool, +method: String, +ms: U32, headers: Map<&2, List<&2, String>>,
5005    body: Bytes.Bytes, hf: String, a: Url.Abs, known: Bool,
5006    cert: Maybe<&2, Cert>, +ca: String, choice: Maybe<&2, Proxy>) ->
5007    IO(Pool & Result<&1, &1, Err, Stream>):
5008  Url.Abs{scheme, host, port, target} = a
5009  match known:
5010    case False{}:
5011      IO.pure(Pool & Result<&1, &1, Err, Stream>, (p, Fail{ErrUrl{}}))
5012    case True{}:
5013      stream.origin.choice(p, method, ms, headers, body, hf, scheme, host, port,
5014        target, cert, ca, choice)
5015
5016def stream.one.back(h: Hop, pr: Pool & Result<&1, &1, Err, Stream>) -> Pool & Hop & Result<&1, &1, Err, Stream>:
5017  (p, r) = pr
5018  (p, h, r)
5019
5020# sent is headers plus any jar cookies; the hop keeps headers, so cookies never pile up across hops.
5021def stream.one.of(p: Pool, +method: String, +url: Url.Abs, headers: Map<&2, List<&2, String>>, sent: Map<&2, List<&2, String>>,
5022    +ms: U32, r: Bytes.Bytes & Bytes.Bytes, cert: Maybe<&2, Cert>, +ca: String) -> IO(Pool & Hop & Result<&1, &1, Err, Stream>):
5023  Url.Abs{+scheme, +host, +port, target} = url
5024  (body, wire) = r
5025  do IO<Pool & Hop & Result<&1, &1, Err, Stream>>:
5026    choice : Maybe<&2, Proxy> <- proxy.select(scheme, host, port)
5027    got : Pool & Result<&1, &1, Err, Stream> <-
5028      stream.origin(p, method, ms, sent, wire, Url.host_field(url), url,
5029        pool.scheme.ok(url), cert, ca, choice)
5030    return stream.one.back(Hop{method, url, headers, body}, got)
5031
5032# One request, on an idle socket to its origin when the pool has one. The hop comes back for the redirect.
5033def stream.one(p: Pool, h: Hop, +ms: U32, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>) -> IO(Pool & Hop & Result<&1, &1, Err, Stream>):
5034  Hop{+method, +url, +headers, body} = h
5035  stream.one.of(p, method, url, headers, jar.opt.apply(jar, url, headers), ms, Bytes.slice(body, 0, 4294967295), cert, ca)
5036
5037# Go: the next request. Drop: close this stream, then go on. Done: the answer.
5038type Sn is Type:
5039  SnGo{left: Nat, hop: Hop}
5040  SnDrop{st: Stream, next: Next}
5041  SnDone{r: Result<&1, &1, Err, Stream>}
5042
5043def sn.of(n: Next) -> Sn:
5044  match n:
5045    case NGo{l, h}:
5046      SnGo{l, h}
5047    case NStop{}:
5048      SnDone{Fail{ErrRedirect{}}}
5049    case NDone{m}:
5050      match m:
5051        case Fail{e}:
5052          SnDone{Fail{e}}
5053        case Done{r}:
5054          SnDone{Fail{ErrBad{}}}
5055
5056def stream.decide.done(st: Stream, m: Result<&1, &1, Err, Res>) -> Sn:
5057  match m:
5058    case Fail{e}:
5059      SnDrop{st, NDone{Fail{e}}}
5060    case Done{r}:
5061      SnDone{Done{st}}
5062
5063def stream.decide.n(st: Stream, n: Next) -> Sn:
5064  match n:
5065    case NDone{m}:
5066      stream.decide.done(st, m)
5067    case NGo{l, h}:
5068      SnDrop{st, NGo{l, h}}
5069    case NStop{}:
5070      SnDrop{st, NStop{}}
5071
5072# The redirect loop's state for a stream: what to do next, the client identity, the jar, and the CA file.
5073type StreamHops is Type:
5074  StreamHops{next: Sn, cert: Maybe<&2, Cert>, jar: Maybe<&2, Jar>, ca: String}
5075
5076# The certificate and CA file go on only to a hop on the same origin, as in pool.next.move.
5077def stream.trust(+old: String, cert: Maybe<&2, Cert>, +ca: String, n: Next) -> Next & Maybe<&2, Cert> & String:
5078  match n:
5079    case NGo{left, h}:
5080      Hop{method, +url, headers, body} = h
5081      +same = String.eq(old, redirect.origin(url))
5082      (NGo{left, Hop{method, url, headers, body}}, Bool.pick(Maybe<&2, Cert>, same, cert, None{}), pool.next.ca(same, ca))
5083    case NDone{r}:
5084      (NDone{r}, None{}, "")
5085    case NStop{}:
5086      (NStop{}, None{}, "")
5087
5088def stream.decide.trust(st: Stream, jar: Maybe<&2, Jar>, nt: Next & Maybe<&2, Cert> & String) -> StreamHops:
5089  (n, cert, ca) = nt
5090  StreamHops{stream.decide.n(st, n), cert, jar, ca}
5091
5092def stream.decide.jar(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, st: Stream, +old: String, hop: Hop,
5093    r: Maybe<&2, Jar> & Result<&1, &1, Err, Res>) -> StreamHops:
5094  (jar, m) = r
5095  stream.decide.trust(st, jar, stream.trust(old, cert, ca, fetch.policy(mode, left, hop, m)))
5096
5097# The jar takes the response's Set-Cookie fields first, redirect or not.
5098def stream.decide.of(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>, hop: Hop, sr: Stream & Res) -> StreamHops:
5099  Hop{method, +url, headers, body} = hop
5100  (st, res) = sr
5101  stream.decide.jar(mode, left, cert, ca, st, redirect.origin(url), Hop{method, url, headers, body}, jar.opt.store(jar, url, Done{res}))
5102
5103def stream.decide(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>, x: Hop & Result<&1, &1, Err, Stream>) -> StreamHops:
5104  (hop, r) = x
5105  match r:
5106    case Fail{e}:
5107      StreamHops{SnDone{Fail{e}}, cert, jar, ca}
5108    case Done{st}:
5109      stream.decide.of(mode, left, cert, ca, jar, hop, stream.res(st))
5110
5111def stream.step(+mode: Mode, left: Nat, cert: Maybe<&2, Cert>, +ca: String, jar: Maybe<&2, Jar>,
5112    x: Pool & Hop & Result<&1, &1, Err, Stream>) -> Pool & StreamHops:
5113  (p, hop, r) = x
5114  (p, stream.decide(mode, left, cert, ca, jar, (hop, r)))
5115
5116# mode says what a redirect does, as in pool.hops. A dropped redirect gives its connection to the pool
5117# when its body has ended. The pool and the jar come back beside the stream.
5118@unsafe
5119def stream.hops(+mode: Mode, +ms: U32, x: Pool & StreamHops) -> IO(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5120  (p, hs) = x
5121  StreamHops{sn, +cert, +jar, +ca} = hs
5122  match sn:
5123    case SnDone{r}:
5124      IO.pure(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>, (p, jar, r))
5125    case SnGo{left, hop}:
5126      do IO<Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5127        y : Pool & Hop & Result<&1, &1, Err, Stream> <- stream.one(p, hop, ms, cert, ca, jar)
5128        stream.hops(mode, ms, stream.step(mode, left, cert, ca, jar, y))
5129    case SnDrop{st, next}:
5130      do IO<Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5131        q : Pool <- pool.stream.close(p, st)
5132        stream.hops(mode, ms, (q, StreamHops{sn.of(next), cert, jar, ca}))
5133
5134type Opened is Type:
5135  Opened{decs: List<&1, Zlib.Decoder>, bad: Bool}
5136
5137def stream.open.got(st: Opened, r: Result<&1, &1, U32 & String, Zlib.Decoder>) -> Opened:
5138  Opened{decs, bad} = st
5139  match r:
5140    case Fail{e}:
5141      Opened{decs, True{}}
5142    case Done{d}:
5143      Opened{d <> decs, bad}
5144
5145def stream.open.add(st: Opened, m: IO(Result<&1, &1, U32 & String, Zlib.Decoder>)) -> IO(Opened):
5146  do IO<Opened>:
5147    r : Result<&1, &1, U32 & String, Zlib.Decoder> <- m
5148    return stream.open.got(st, r)
5149
5150def stream.open.zstd(zstd: Bool, st: Opened) -> IO(Opened):
5151  match zstd:
5152    case True{}:
5153      stream.open.add(st, Zlib.zstd.new())
5154    case False{}:
5155      IO.pure(Opened, st)
5156
5157def stream.open.br(br: Bool, +c: String, st: Opened) -> IO(Opened):
5158  match br:
5159    case True{}:
5160      stream.open.add(st, Zlib.brotli.new())
5161    case False{}:
5162      stream.open.zstd(String.eq(c, "zstd"), st)
5163
5164def stream.open.gz(gz: Bool, +c: String, st: Opened) -> IO(Opened):
5165  match gz:
5166    case True{}:
5167      stream.open.add(st, Zlib.inflate.new())
5168    case False{}:
5169      stream.open.br(String.eq(c, "br"), c, st)
5170
5171def stream.open.step(+c: String, st: Opened) -> IO(Opened):
5172  Opened{decs, bad} = st
5173  match bad:
5174    case True{}:
5175      IO.pure(Opened, Opened{decs, True{}})
5176    case False{}:
5177      stream.open.gz(Bool.or(String.eq(c, "gzip"), Bool.or(String.eq(c, "x-gzip"), String.eq(c, "deflate"))),
5178        c, Opened{decs, False{}})
5179
5180def stream.open.one(prev: IO(Opened), c: String) -> IO(Opened):
5181  do IO<Opened>:
5182    st : Opened <- prev
5183    stream.open.step(c, st)
5184
5185def stream.open.all(cs: List<&2, String>) -> IO(Opened):
5186  List.foldl(~&2, ~String, ~IO(Opened), ~stream.open.one, cs, IO.pure(Opened, Opened{Nil{}, False{}}))
5187
5188def stream.put.decs(st: Stream, decs: List<&1, Zlib.Decoder>) -> Stream:
5189  Stream{tls, s, ms, res, rb, out, old, back} = st
5190  Stream{tls, s, ms, res, rb, out, decs, back}
5191
5192def stream.init.opened(st: Stream, opened: Opened) -> IO(Result<&1, &1, Err, Stream>):
5193  Opened{decs, bad} = opened
5194  match bad:
5195    case True{}:
5196      do IO<Result<&1, &1, Err, Stream>>:
5197        stream.close(stream.put.decs(st, decs))
5198        return Fail{ErrBad{}}
5199    case False{}:
5200      IO.pure(Result<&1, &1, Err, Stream>, Done{stream.put.decs(st, List.reverse(&1, Zlib.Decoder, decs))})
5201
5202def stream.init.known(known: Bool, st: Stream, cs: List<&2, String>) -> IO(Result<&1, &1, Err, Stream>):
5203  match known:
5204    case False{}:
5205      IO.pure(Result<&1, &1, Err, Stream>, Done{st})
5206    case True{}:
5207      do IO<Result<&1, &1, Err, Stream>>:
5208        opened : Opened <- stream.open.all(cs)
5209        stream.init.opened(st, opened)
5210
5211def stream.init.st(+enc: String, st: Stream) -> IO(Result<&1, &1, Err, Stream>):
5212  Stream{tls, s, ms, res, rb, out, decs, back} = st
5213  Res{+status, +headers, body} = res
5214  +cs = List.reverse(&2, String, ce.split(fields(headers, "content-encoding")))
5215  stream.init.known(ce.known(cs, Con{"identity", Con{"x-gzip", ce.split(Con{enc, Nil{}})}}),
5216    Stream{tls, s, ms, Res{status, headers, body}, rb, out, decs, back}, cs)
5217
5218def stream.init(+enc: String, r: Result<&1, &1, Err, Stream>) -> IO(Result<&1, &1, Err, Stream>):
5219  match r:
5220    case Fail{e}:
5221      IO.pure(Result<&1, &1, Err, Stream>, Fail{e})
5222    case Done{st}:
5223      stream.init.st(enc, st)
5224
5225def stream.init.pool(+enc: String, x: Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>) -> IO(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5226  (p, jar, r) = x
5227  do IO<Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5228    s : Result<&1, &1, Err, Stream> <- stream.init(enc, r)
5229    return (p, jar, s)
5230
5231def stream.start(method: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, u: Maybe<&2, Url.Abs>) -> Sn:
5232  match u:
5233    case None{}:
5234      SnDone{Fail{ErrUrl{}}}
5235    case Some{a}:
5236      SnGo{20n, Hop{method, a, req.put(Map.to_list(&2, List<&2, String>, headers), empty()), body}}
5237
5238def stream.only(x: Maybe<&2, Jar> & Result<&1, &1, Err, Stream>) -> Result<&1, &1, Err, Stream>:
5239  (jar, r) = x
5240  r
5241
5242def pool.stream.only(x: Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>) -> Pool & Result<&1, &1, Err, Stream>:
5243  (p, jar, r) = x
5244  (p, r)
5245
5246# A one-off pool ends with the open; it closes any idle socket a redirect gave back.
5247def stream.unpooled(x: Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>) -> IO(Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5248  (p, jar, r) = x
5249  do IO<Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5250    pool.close(p)
5251    return (jar, r)
5252
5253# open.raw.how on a pool: each hop takes an idle socket to its origin when the pool has one.
5254def pool.open.raw.how(p: Pool, mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5255    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5256  stream.hops(mode, ms, (p, StreamHops{stream.start(method, headers, body, Url.absolute(url)), cert, jar, ca}))
5257
5258# A raw stream with every choice of fetch: mode for redirects, cert and ca for the first origin and each
5259# same-origin hop ("" trusts the default store), and a jar that gives cookies to every hop and takes them
5260# from every response. The jar comes back beside the stream. Body bytes and headers remain as sent.
5261def open.raw.how(mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5262    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5263  do IO<Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5264    x : Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <- pool.open.raw.how(pool.new(), mode, cert, ca, jar, method, url, headers, body, ms)
5265    stream.unpooled(x)
5266
5267# Open a raw stream. It follows redirects, with no client certificate and no jar.
5268def open.raw.with(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Result<&1, &1, Err, Stream>):
5269  do IO<Result<&1, &1, Err, Stream>>:
5270    x : Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <- open.raw.how(ModeFollow{}, None{}, "", None{}, method, url, headers, body, ms)
5271    return stream.only(x)
5272
5273def open.raw(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Result<&1, &1, Err, Stream>):
5274  open.raw.with(method, url, headers, body, 30000)
5275
5276def pool.open.how.enc(+enc: String, p: Pool, mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5277    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5278  do IO<Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5279    x : Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <-
5280      stream.hops(mode, ms, (p, StreamHops{stream.start(method, accept.enc(headers, enc), body, Url.absolute(url)), cert, jar, ca}))
5281    stream.init.pool(enc, x)
5282
5283# open.how on a pool. Give a stream back with pool.stream.close.
5284def pool.open.how(p: Pool, mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5285    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5286  do IO<Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5287    enc : String <- codings()
5288    pool.open.how.enc(enc, p, mode, cert, ca, jar, method, url, headers, body, ms)
5289
5290def pool.open.with(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Pool & Result<&1, &1, Err, Stream>):
5291  do IO<Pool & Result<&1, &1, Err, Stream>>:
5292    x : Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <- pool.open.how(p, ModeFollow{}, None{}, "", None{}, method, url, headers, body, ms)
5293    return pool.stream.only(x)
5294
5295# open on a pool: an idle socket to the origin when the pool has one, else a new one.
5296def pool.open(p: Pool, method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Pool & Result<&1, &1, Err, Stream>):
5297  pool.open.with(p, method, url, headers, body, 30000)
5298
5299def open.how.enc(+enc: String, mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5300    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5301  do IO<Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5302    x : Pool & Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <- pool.open.how.enc(enc, pool.new(), mode, cert, ca, jar, method, url, headers, body, ms)
5303    stream.unpooled(x)
5304
5305# open.raw.how, then each body piece decoded by Content-Encoding (see codings). Unknown codings stay raw.
5306def open.how(mode: Mode, cert: Maybe<&2, Cert>, ca: String, jar: Maybe<&2, Jar>, method: String, url: String,
5307    headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Maybe<&2, Jar> & Result<&1, &1, Err, Stream>):
5308  do IO<Maybe<&2, Jar> & Result<&1, &1, Err, Stream>>:
5309    enc : String <- codings()
5310    open.how.enc(enc, mode, cert, ca, jar, method, url, headers, body, ms)
5311
5312# Follow redirects, then decode each body piece by Content-Encoding. No client certificate and no jar.
5313def open.with(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes, +ms: U32) -> IO(Result<&1, &1, Err, Stream>):
5314  do IO<Result<&1, &1, Err, Stream>>:
5315    x : Maybe<&2, Jar> & Result<&1, &1, Err, Stream> <- open.how(ModeFollow{}, None{}, "", None{}, method, url, headers, body, ms)
5316    return stream.only(x)
5317
5318def open(method: String, url: String, headers: Map<&2, List<&2, String>>, body: Bytes.Bytes) -> IO(Result<&1, &1, Err, Stream>):
5319  open.with(method, url, headers, body, 30000)
5320
5321# Upload: the request body sent in chunks (RFC 9112 §7.1). err: the first failed write.
5322type Up is Type:
5323  Up{tls: Bool, s: Socket, ms: U32, head: Bool, err: Maybe<&2, Err>}
5324
5325def hex.digit(+n: U32) -> Char:
5326  Chr{Bool.pick(U32, U32.is_lt(n, 10), (48 + n : U32), (87 + n : U32))}
5327
5328def hex.go(k: Nat, +n: U32, acc: String) -> String:
5329  match k:
5330    case 0n:
5331      acc
5332    case 1n+p:
5333      hex.go(p, U32.shrn(n, 4n), SCon{hex.digit(U32.and(n, 15)), acc})
5334
5335def hex.trim(+s: String) -> String:
5336  match s:
5337    case SNil{}:
5338      SNil{}
5339    case SCon{Chr{+c}, +t}:
5340      Bool.pick(String, Bool.and(U32.is_eq(c, 48), Bool.not(String.is_empty(t))), hex.trim(t), SCon{Chr{c}, t})
5341
5342def hex(+n: U32) -> String:
5343  hex.trim(hex.go(8n, n, ""))
5344
5345def up.sent(+tls: Bool, +ms: U32, +head: Bool, m: Socket & Result<&1, &1, U32 & String, Unit>) -> Up:
5346  (s, r) = m
5347  match r:
5348    case Fail{(+code, why)}:
5349      Up{tls, s, ms, head, Some{err.or_late(code, ErrWrite{code, why})}}
5350    case Done{u}:
5351      Up{tls, s, ms, head, None{}}
5352
5353def up.conn(r: Result<&1, &1, Err, Conn>, +ms: U32, +head: Bool, wire: String) -> IO(Result<&1, &1, Err, Up>):
5354  match r:
5355    case Fail{e}:
5356      IO.pure(Result<&1, &1, Err, Up>, Fail{e})
5357    case Done{c}:
5358      match c:
5359        case Conn{+tls, s}:
5360          do IO<Result<&1, &1, Err, Up>>:
5361            sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, Bytes.from_string(wire))
5362            return Done{up.sent(tls, ms, head, sent)}
5363        case ConnH2{s, client}:
5364          do IO<Result<&1, &1, Err, Up>>:
5365            Wire.tls.close(s)
5366            return Fail{ErrBad{}}
5367
5368def up.head(+method: String, +target: String, +hf: String, +headers: Map<&2, List<&2, String>>) -> String:
5369  request(method, target, set(set(set(req.put(Map.to_list(&2, List<&2, String>, headers), empty()), "host", hf), "connection", "close"), "transfer-encoding", "chunked"), "")
5370
5371def up.route(+ms: U32, r: String & Send & Route) -> IO(Result<&1, &1, Err, Up>):
5372  (key, request, route) = r
5373  Send{+method, +target, +authority, +headers, body} = request
5374  do IO<Result<&1, &1, Err, Up>>:
5375    c : Result<&1, &1, Err, Conn> <- route.open(ms, False{}, route)
5376    up.conn(c, ms, String.eq(method, "HEAD"), up.head(method, target, authority, headers))
5377
5378def up.origin.choice(+method: String, +ms: U32,
5379    headers: Map<&2, List<&2, String>>, hf: String,
5380    scheme: String, host: String, port: U32, target: String,
5381    cert: Maybe<&2, Cert>, +ca: String, choice: Maybe<&2, Proxy>) ->
5382    IO(Result<&1, &1, Err, Up>):
5383  match choice:
5384    case None{}:
5385      IO.pure(Result<&1, &1, Err, Up>, Fail{ErrUrl{}})
5386    case Some{proxy}:
5387      up.route(ms, origin.route(method, headers, Bytes.new(0), hf, scheme, host,
5388        port, target, cert, ca, proxy))
5389
5390def up.origin(+method: String, +ms: U32, headers: Map<&2, List<&2, String>>,
5391    hf: String, a: Url.Abs, known: Bool, cert: Maybe<&2, Cert>, +ca: String) ->
5392    IO(Result<&1, &1, Err, Up>):
5393  Url.Abs{+scheme, +host, +port, target} = a
5394  match known:
5395    case False{}:
5396      IO.pure(Result<&1, &1, Err, Up>, Fail{ErrUrl{}})
5397    case True{}:
5398      do IO<Result<&1, &1, Err, Up>>:
5399        choice : Maybe<&2, Proxy> <- proxy.select(scheme, host, port)
5400        up.origin.choice(method, ms, headers, hf, scheme, host, port, target,
5401          cert, ca, choice)
5402
5403def up.url(+method: String, +ms: U32, headers: Map<&2, List<&2, String>>,
5404    u: Maybe<&2, Url.Abs>, cert: Maybe<&2, Cert>, +ca: String) ->
5405    IO(Result<&1, &1, Err, Up>):
5406  match u:
5407    case None{}:
5408      IO.pure(Result<&1, &1, Err, Up>, Fail{ErrUrl{}})
5409    case Some{+a}:
5410      up.origin(method, ms, headers, Url.host_field(a), a, pool.scheme.ok(a), cert, ca)
5411
5412# cert and ca as in open.how. A streamed body cannot be replayed, so redirects are not followed.
5413def upload.how(cert: Maybe<&2, Cert>, +ca: String, +method: String, url: String,
5414    headers: Map<&2, List<&2, String>>, +ms: U32) -> IO(Result<&1, &1, Err, Up>):
5415  up.url(method, ms, headers, Url.absolute(url), cert, ca)
5416
5417def upload.with(+method: String, url: String, headers: Map<&2, List<&2, String>>, +ms: U32) -> IO(Result<&1, &1, Err, Up>):
5418  upload.how(None{}, "", method, url, headers, ms)
5419
5420def upload(+method: String, url: String, headers: Map<&2, List<&2, String>>) -> IO(Result<&1, &1, Err, Up>):
5421  upload.with(method, url, headers, 30000)
5422
5423def up.wrote(+tls: Bool, +ms: U32, +head: Bool, m: Socket & Result<&1, &1, U32 & String, Unit>) -> Up & Result<&2, &2, Err, Unit>:
5424  (s, r) = m
5425  match r:
5426    case Fail{(+code, why)}:
5427      +e = err.or_late(code, ErrWrite{code, why})
5428      (Up{tls, s, ms, head, Some{e}}, Fail{e})
5429    case Done{u}:
5430      (Up{tls, s, ms, head, None{}}, Done{Unit{}})
5431
5432# One write per chunk, so the size line and the data do not wait on each other (Nagle).
5433def up.put(+tls: Bool, s: Socket, +ms: U32, +head: Bool, +len: U32, piece: Bytes.Bytes, skip: Bool) -> IO(Up & Result<&2, &2, Err, Unit>):
5434  match skip:
5435    case True{}:
5436      IO.pure(Up & Result<&2, &2, Err, Unit>, (Up{tls, s, ms, head, None{}}, Done{Unit{}}))
5437    case False{}:
5438      do IO<Up & Result<&2, &2, Err, Unit>>:
5439        sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, Bytes.concat(Con{Bytes.from_string(hex(len) ++ "\r\n"), Con{piece, Con{Bytes.from_string("\r\n"), Nil{}}}}))
5440        return up.wrote(tls, ms, head, sent)
5441
5442def up.chunk(+tls: Bool, s: Socket, +ms: U32, +head: Bool, piece: Bytes.Bytes) -> IO(Up & Result<&2, &2, Err, Unit>):
5443  Bytes.Bytes{+len, buf} = piece
5444  up.put(tls, s, ms, head, len, Bytes.Bytes{len, buf}, U32.is_zero(len))
5445
5446# One chunk. An empty piece sends nothing, since an empty chunk would end the body.
5447def upload.write(up: Up, piece: Bytes.Bytes) -> IO(Up & Result<&2, &2, Err, Unit>):
5448  Up{+tls, s, +ms, +head, err} = up
5449  match err:
5450    case Some{+e}:
5451      IO.pure(Up & Result<&2, &2, Err, Unit>, (Up{tls, s, ms, head, Some{e}}, Fail{e}))
5452    case None{}:
5453      up.chunk(tls, s, ms, head, piece)
5454
5455# Ends the body and returns the response as a stream.
5456def upload.finish(up: Up) -> IO(Result<&1, &1, Err, Stream>):
5457  Up{+tls, s, +ms, +head, err} = up
5458  match err:
5459    case Some{e}:
5460      do IO<Result<&1, &1, Err, Stream>>:
5461        io.close(tls, s)
5462        return Fail{e}
5463    case None{}:
5464      do IO<Result<&1, &1, Err, Stream>>:
5465        sent : Socket & Result<&1, &1, U32 & String, Unit> <- io.send.bytes(tls, s, Bytes.from_string("0\r\n\r\n"))
5466        x : Bool & Result<&1, &1, Err, Stream> <- stream.sent(tls, ms, head, None{}, sent)
5467        return stream.opened(x)
5468
5469type ServerConfig is Data:
5470  ServerConfig{host: String, port: U32, body_bytes: U32, header_bytes: U32,
5471    connections: U32, requests: U32, buffered: U32, header_ms: U32, body_ms: U32,
5472    idle_ms: U32, write_ms: U32}
5473
5474# Shared framing settings; all live readers reserve admission before allocation.
5475type Limits is Data:
5476  Limits{body: Nat, headers: U32, header_ms: U32, body_ms: U32, idle_ms: U32, write_ms: U32}
5477
5478# Bytes.words rounds with len+3 in U32. A retained cap plus the fixed 65536
5479# receive overshoot must still fit before that addition, including temporary copies.
5480def limits.representable() -> U32:
5481  4294901756
5482
5483def limits.validate.ok(valid: Bool) -> IO(Unit):
5484  match valid:
5485    case True{}:
5486      IO.pure(Unit, Unit{})
5487    case False{}:
5488      IO.die(Unit, 22, "request cap plus 65536-byte recv overshoot and 3-byte packed rounding must fit U32")
5489
5490def limits.validate(+max: Nat) -> IO(Unit):
5491  limits.validate.ok(Nat.is_gt(max, 0n) && Nat.is_le(max, U32.to_nat(limits.representable())))
5492
5493def limits(max: Nat) -> Limits:
5494  Limits{max, 65536, 5000, 30000, 30000, 30000}
5495
5496def limits.config(config: ServerConfig) -> Limits:
5497  ServerConfig{host, port, body, headers, connections, requests, buffered, header_ms, body_ms, idle_ms, write_ms} = config
5498  Limits{U32.to_nat(body), headers, header_ms, body_ms, idle_ms, write_ms}
5499
5500# One absolute deadline, never replenished by progress.
5501type Phase is Data:
5502  Phase{kind: U32, until: Time.Duration}
5503
5504def phase.new(+kind: U32, ms: U32, now: Time.Duration) -> Phase:
5505  Phase{kind, Time.Duration.add(now, Time.Duration.of_ms(ms))}
5506
5507def phase.remaining(+until: Time.Duration, +now: Time.Duration) -> U32:
5508  Bool.pick(U32, Cmp.is_lt(Time.Duration.cmp(now, until)),
5509    U32.max(1, Int.I64.to_u32(Time.Duration.to_ms(Time.Duration.sub(until, now)))), 0)
5510
5511def bounded.sent(m: Socket & (U32 & Result<&1, &1, U32 & String, Unit>)) -> Socket & Result<&1, &1, U32 & String, Unit>:
5512  (s, (accepted, result)) = m
5513  (s, result)
5514
5515def bounded.send.ready(expired: Bool, s: Socket, b: Bytes.Bytes, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, Unit>):
5516  match expired:
5517    case True{}:
5518      IO.pure(Socket & Result<&1, &1, U32 & String, Unit>, (s, Fail{(60, "write deadline expired")}))
5519    case False{}:
5520      Bytes.Bytes{len, words} = b
5521      do IO<Socket & Result<&1, &1, U32 & String, Unit>>:
5522        sent : Socket & (U32 & Result<&1, &1, U32 & String, Unit>) <- Wire.send.words.deadline(s, len, words, ms)
5523        return bounded.sent(sent)
5524
5525def bounded.send(s: Socket, b: Bytes.Bytes, until: Time.Duration) -> IO(Socket & Result<&1, &1, U32 & String, Unit>):
5526  do IO<Socket & Result<&1, &1, U32 & String, Unit>>:
5527    now : Time.Duration <- Time.mono()
5528    +ms : U32 = phase.remaining(until, now)
5529    bounded.send.ready(U32.is_zero(ms), s, b, ms)
5530
5531def reply.bytes(res: Res, head: Bool, close: Bool) -> Bytes.Bytes:
5532  Res{+status, +headers, body} = res
5533  encode.on(Res{status, Bool.pick(Map<&2, List<&2, String>>, close, set(headers, "connection", "close"), headers), body}, head)
5534
5535def reply.fail.bytes(+status: U32) -> Res:
5536  Res{status, set(empty(), "connection", "close"), Bytes.from_string(reason(status))}
5537
5538def reply.drain.of(m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> Socket & Bool:
5539  (s, r) = m
5540  match r:
5541    case Fail{e}:
5542      (s, False{})
5543    case Done{data}:
5544      (len, words) = data
5545      (s, Bool.not(U32.is_zero(len)))
5546
5547# ponytail: cap drain at 32 MiB and 2.55 s; use socket half-close if larger peers need guaranteed delivery.
5548def reply.drain(n: Nat, alive: Bool, +until: Time.Duration, st: Socket & Bool) -> IO(Unit):
5549  match n:
5550    case 0n:
5551      (s, more) = st
5552      Socket.close(s)
5553    case 1n+p:
5554      match alive:
5555        case False{}:
5556          (s, more) = st
5557          Socket.close(s)
5558        case True{}:
5559          (s, more) = st
5560          match more:
5561            case False{}:
5562              Socket.close(s)
5563            case True{}:
5564              do IO<Unit>:
5565                got : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <- Wire.recv.words(s, 65536, 50)
5566                now : Time.Duration <- Time.mono()
5567                reply.drain(p, Cmp.is_lt(Time.Duration.cmp(now, until)), until, reply.drain.of(got))
5568
5569def reply.sent.close(m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Unit):
5570  (s, r) = m
5571  match r:
5572    case Fail{e}:
5573      Socket.close(s)
5574    case Done{u}:
5575      do IO<Unit>:
5576        now : Time.Duration <- Time.mono()
5577        reply.drain(U32.to_nat(512), True{}, Time.Duration.add(now, Time.Duration.of_ms(2500)), (s, True{}))
5578
5579def reply.sent.observe(observer: Completion.Receipt, status: U32,
5580  sent: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Unit):
5581  (socket, result) = sent
5582  match result:
5583    case Fail{(code, message)}:
5584      do IO<Unit>:
5585        Socket.close(socket)
5586        Completion.report(observer, status, Completion.WriteFailed{code, message})
5587    case Done{unit}:
5588      do IO<Unit>:
5589        reply.sent.close((socket, Done{unit}))
5590        Completion.report(observer, status, Completion.HostAccepted{})
5591
5592
5593def reply_fail(observer: Completion.Receipt, s: Socket, +status: U32, ms: U32) -> IO(Unit):
5594  do IO<Unit>:
5595    now : Time.Duration <- Time.mono()
5596    sent : Socket & Result<&1, &1, U32 & String, Unit> <-
5597      bounded.send(s, encode(reply.fail.bytes(status)), Time.Duration.add(now, Time.Duration.of_ms(ms)))
5598    reply.sent.observe(observer, status, sent)
5599
5600
5601# The head is parsed once, when it is whole. Body pieces are kept as they come and joined once.
5602type Sv is Type:
5603  SvHead{buf: Bytes.Bytes}
5604  SvLen{req: Req, v11: Bool, left: U32, parts: List<&1, Bytes.Bytes>}
5605  SvChunk{req: Req, v11: Bool, n: U32, st: Dc, parts: List<&1, Bytes.Bytes>}
5606
5607type InputOperation is Data:
5608  ReadInput{}
5609  SendContinue{}
5610
5611# More: read on, first answering 100 Continue when cont is set.
5612type Turn is Type:
5613  TurnClose{}
5614  TurnInputFailed{operation: InputOperation, code: U32, message: String}
5615  TurnFail{status: U32}
5616  TurnReq{req: Req, rest: Bytes.Bytes, v11: Bool}
5617  TurnMore{sv: Sv, cont: Bool}
5618
5619def serve.max() -> Nat:
5620  fetch.max()
5621
5622def sv.none() -> Sv:
5623  SvHead{Bytes.new(0)}
5624
5625def sv.body(req: Req, +v11: Bool, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes) -> Turn:
5626  Req{method, path, headers, body} = req
5627  TurnReq{Req{method, path, headers, rb.body(parts)}, rest, v11}
5628
5629def sv.len.cut(req: Req, +v11: Bool, parts: List<&1, Bytes.Bytes>, +len: U32, +left: U32, r: Bytes.Bytes & Bytes.Bytes) -> Turn:
5630  (piece, last) = r
5631  sv.body(req, v11, Con{last, parts}, bytes.snd(Bytes.slice(piece, left, (len - left : U32))))
5632
5633def sv.len.have(short: Bool, req: Req, +v11: Bool, +left: U32, parts: List<&1, Bytes.Bytes>, +len: U32, piece: Bytes.Bytes) -> Turn:
5634  match short:
5635    case True{}:
5636      TurnMore{SvLen{req, v11, (left - len : U32), Con{piece, parts}}, False{}}
5637    case False{}:
5638      sv.len.cut(req, v11, parts, len, left, Bytes.slice(piece, 0, left))
5639
5640def sv.len(req: Req, +v11: Bool, +left: U32, parts: List<&1, Bytes.Bytes>, piece: Bytes.Bytes) -> Turn:
5641  Bytes.Bytes{+len, buf} = piece
5642  sv.len.have(U32.is_lt(len, left), req, v11, left, parts, len, Bytes.Bytes{len, buf})
5643
5644def sv.chunk.of(req: Req, +v11: Bool, +n: U32, c: Ck) -> Turn:
5645  Ck{st, parts, rest} = c
5646  match st:
5647    case DcDone{}:
5648      sv.body(req, v11, parts, rest)
5649    case DcBad{}:
5650      TurnFail{400}
5651    case DcSize{a, b}:
5652      TurnMore{SvChunk{req, v11, n, DcSize{a, b}, parts}, False{}}
5653    case DcExt{x}:
5654      TurnMore{SvChunk{req, v11, n, DcExt{x}, parts}, False{}}
5655    case DcSizeLf{x}:
5656      TurnMore{SvChunk{req, v11, n, DcSizeLf{x}, parts}, False{}}
5657    case DcData{k}:
5658      TurnMore{SvChunk{req, v11, n, DcData{k}, parts}, False{}}
5659    case DcDataCr{}:
5660      TurnMore{SvChunk{req, v11, n, DcDataCr{}, parts}, False{}}
5661    case DcDataLf{}:
5662      TurnMore{SvChunk{req, v11, n, DcDataLf{}, parts}, False{}}
5663    case DcTr{j}:
5664      TurnMore{SvChunk{req, v11, n, DcTr{j}, parts}, False{}}
5665
5666def sv.chunk.parts.cons(+len: U32, buf: Array<U32>, counted: List<&1, Bytes.Bytes> & Nat) -> List<&1, Bytes.Bytes> & Nat:
5667  (rest, n) = counted
5668  (Con{Bytes.Bytes{len, buf}, rest}, Nat.add(U32.to_nat(len), n))
5669
5670def sv.chunk.parts(xs: List<&1, Bytes.Bytes>, old: List<&1, Bytes.Bytes>) -> List<&1, Bytes.Bytes> & Nat:
5671  match xs:
5672    case Nil{}:
5673      (old, 0n)
5674    case Con{b, tail}:
5675      Bytes.Bytes{+len, buf} = b
5676      sv.chunk.parts.cons(len, buf, sv.chunk.parts(tail, old))
5677
5678def sv.chunk.cap(big: Bool, req: Req, +v11: Bool, n: U32, st: Dc, parts: List<&1, Bytes.Bytes>, rest: Bytes.Bytes) -> Turn:
5679  match big:
5680    case True{}:
5681      TurnFail{413}
5682    case False{}:
5683      sv.chunk.of(req, v11, n, Ck{st, parts, rest})
5684
5685def sv.chunk.counted(+max: Nat, req: Req, v11: Bool, n: U32, st: Dc, rest: Bytes.Bytes, counted: List<&1, Bytes.Bytes> & Nat) -> Turn:
5686  (parts, size) = counted
5687  +total = Nat.add(U32.to_nat(n), size)
5688  sv.chunk.cap(Nat.is_lt(max, total) || Nat.is_lt(U32.to_nat(4294967295), total), req, v11,
5689    U32.from_nat(total), st, parts, rest)
5690
5691def sv.chunk.decoded(max: Nat, req: Req, v11: Bool, n: U32, old: List<&1, Bytes.Bytes>, c: Ck) -> Turn:
5692  Ck{st, parts, rest} = c
5693  sv.chunk.counted(max, req, v11, n, st, rest, sv.chunk.parts(parts, old))
5694
5695def sv.chunk(+max: Nat, req: Req, +v11: Bool, +n: U32, st: Dc, parts: List<&1, Bytes.Bytes>, piece: Bytes.Bytes) -> Turn:
5696  sv.chunk.decoded(max, req, v11, n, parts, ck(piece, st, Nil{}))
5697
5698# RFC 9110 §10.1.1: answer 100 Continue only when the body is still to come.
5699def sv.cont(want: Bool, t: Turn) -> Turn:
5700  match t:
5701    case TurnMore{sv, c}:
5702      TurnMore{sv, want}
5703    case TurnClose{}:
5704      TurnClose{}
5705    case TurnInputFailed{operation, code, message}:
5706      TurnInputFailed{operation, code, message}
5707    case TurnFail{s}:
5708      TurnFail{s}
5709    case TurnReq{req, rest, v11}:
5710      TurnReq{req, rest, v11}
5711
5712def sv.cl.k(big: Bool, req: Req, +v11: Bool, +k: U32, rest: Bytes.Bytes) -> Turn:
5713  match big:
5714    case True{}:
5715      TurnFail{413}
5716    case False{}:
5717      sv.len(req, v11, k, Nil{}, rest)
5718
5719def sv.cl(+max: Nat, req: Req, +v11: Bool, rest: Bytes.Bytes, n: Maybe<&2, U32>) -> Turn:
5720  match n:
5721    case None{}:
5722      TurnFail{400}
5723    case Some{+k}:
5724      sv.cl.k(Nat.is_lt(max, U32.to_nat(k)), req, v11, k, rest)
5725
5726def sv.frame(+max: Nat, req: Req, +v11: Bool, rest: Bytes.Bytes, te: Bool) -> Turn:
5727  Req{method, path, +headers, body} = req
5728  match te:
5729    case True{}:
5730      sv.chunk(max, Req{method, path, headers, body}, v11, 0, dc.start(), Nil{}, rest)
5731    case False{}:
5732      sv.cl(max, Req{method, path, headers, body}, v11, rest, parse_u32(header(headers, "content-length")))
5733
5734def sv.expects(+v11: Bool, +headers: Map<&2, List<&2, String>>) -> Bool:
5735  Bool.and(v11, String.eq(String.to_lower(header(headers, "expect")), "100-continue"))
5736
5737def sv.got.head(+max: Nat, +v11: Bool, rest: Bytes.Bytes, req: Req) -> Turn:
5738  Req{method, path, +headers, body} = req
5739  sv.cont(sv.expects(v11, headers), sv.frame(max, Req{method, path, headers, body}, v11, rest, has_header(headers, "transfer-encoding")))
5740
5741def sv.parsed(+max: Nat, +v11: Bool, rest: Bytes.Bytes, g: Got) -> Turn:
5742  match g:
5743    case GotBad{}:
5744      TurnFail{400}
5745    case GotMore{}:
5746      TurnFail{400}
5747    case GotReq{req}:
5748      TurnReq{req, rest, v11}
5749    case GotHead{req}:
5750      sv.got.head(max, v11, rest, req)
5751
5752def sv.split(+max: Nat, +hlen: U32, +len: U32, r: Bytes.Bytes & Bytes.Bytes) -> Turn:
5753  (buf, hb) = r
5754  +head = Bytes.to_string(hb)
5755  sv.parsed(max, req.v11(head), bytes.snd(Bytes.slice(buf, hlen, (len - hlen : U32))), parse.got(head))
5756
5757def sv.hit.none(big: Bool, buf: Bytes.Bytes) -> Turn:
5758  match big:
5759    case True{}:
5760      TurnFail{431}
5761    case False{}:
5762      TurnMore{SvHead{buf}, False{}}
5763
5764def sv.hit.some(big: Bool, +max: Nat, +hlen: U32, +len: U32, buf: Bytes.Bytes) -> Turn:
5765  match big:
5766    case True{}:
5767      TurnFail{431}
5768    case False{}:
5769      sv.split(max, hlen, len, Bytes.slice(buf, 0, hlen))
5770
5771# RFC 6585 §5: refuse a head over the configured byte limit.
5772def sv.hit(+max: Nat, +headers: U32, +len: U32, buf: Bytes.Bytes, at: Maybe<&2, U32>) -> Turn:
5773  match at:
5774    case None{}:
5775      sv.hit.none(U32.is_lt(headers, len), buf)
5776    case Some{+i}:
5777      +hlen = (i + 4 : U32)
5778      sv.hit.some(U32.is_lt(headers, hlen), max, hlen, len, buf)
5779
5780def sv.seek.of(+max: Nat, +headers: U32, +len: U32, +from: U32, buf: Bytes.Bytes, r: Bytes.Bytes & Maybe<&2, U32>) -> Turn:
5781  (tail, m) = r
5782  sv.hit(max, headers, len, buf, found.at(from, m))
5783
5784def sv.seek(+max: Nat, +headers: U32, +len: U32, +from: U32, r: Bytes.Bytes & Bytes.Bytes) -> Turn:
5785  (buf, tail) = r
5786  sv.seek.of(max, headers, len, from, buf, Bytes.find(tail, "\r\n\r\n"))
5787
5788# Only the new bytes, and the 3 before them, can finish the blank line.
5789def sv.head.at(+max: Nat, +headers: U32, +from: U32, buf: Bytes.Bytes) -> Turn:
5790  Bytes.Bytes{+len, b} = buf
5791  sv.seek(max, headers, len, from, Bytes.slice(Bytes.Bytes{len, b}, from, (len - from : U32)))
5792
5793def sv.head(+max: Nat, +headers: U32, buf: Bytes.Bytes, piece: Bytes.Bytes) -> Turn:
5794  Bytes.Bytes{+ol, ob} = buf
5795  sv.head.at(max, headers, Bool.pick(U32, U32.is_lt(ol, 3), 0, (ol - 3 : U32)), Bytes.append(Bytes.Bytes{ol, ob}, piece))
5796
5797def sv.closed.pick(empty: Bool) -> Turn:
5798  match empty:
5799    case True{}:
5800      TurnClose{}
5801    case False{}:
5802      TurnFail{400}
5803
5804def sv.closed.head(buf: Bytes.Bytes) -> Turn:
5805  Bytes.Bytes{+len, b} = buf
5806  sv.closed.pick(U32.is_eq(len, 0))
5807
5808# A close mid-request is a request cut short (RFC 9112 §8).
5809def sv.step(closed: Bool, +max: Nat, +headers: U32, sv: Sv, piece: Bytes.Bytes) -> Turn:
5810  match closed:
5811    case True{}:
5812      match sv:
5813        case SvHead{buf}:
5814          sv.closed.head(buf)
5815        case SvLen{req, v11, left, parts}:
5816          TurnFail{400}
5817        case SvChunk{req, v11, n, st, parts}:
5818          TurnFail{400}
5819    case False{}:
5820      match sv:
5821        case SvHead{buf}:
5822          sv.head(max, headers, buf, piece)
5823        case SvLen{req, +v11, +left, parts}:
5824          sv.len(req, v11, left, parts, piece)
5825        case SvChunk{req, +v11, +n, st, parts}:
5826          sv.chunk(max, req, v11, n, st, parts, piece)
5827
5828def sv.frame.req(t: Turn) -> Maybe<&1, String & String>:
5829  match t:
5830    case TurnReq{req, rest, v11}:
5831      Req{method, path, headers, body} = req
5832      Some{(Bytes.to_string(body), Bytes.to_string(rest))}
5833    case TurnMore{sv, c}:
5834      None{}
5835    case TurnFail{s}:
5836      None{}
5837    case TurnClose{}:
5838      None{}
5839    case TurnInputFailed{operation, code, message}:
5840      None{}
5841
5842def sv.frame.go(xs: List<&2, String>, t: Turn) -> Maybe<&1, String & String>:
5843  match xs:
5844    case Nil{}:
5845      sv.frame.req(t)
5846    case Con{+x, rest}:
5847      match t:
5848        case TurnMore{sv, c}:
5849          sv.frame.go(rest, sv.step(False{}, serve.max(), 65536, sv, Bytes.from_string(x)))
5850        case TurnReq{req, r, v11}:
5851          sv.frame.req(TurnReq{req, r, v11})
5852        case TurnFail{s}:
5853          None{}
5854        case TurnClose{}:
5855          None{}
5856        case TurnInputFailed{operation, code, message}:
5857          None{}
5858
5859# The body and the bytes after it, once the pieces make a whole request; None otherwise.
5860def serve.frame(xs: List<&2, String>) -> Maybe<&1, String & String>:
5861  sv.frame.go(xs, TurnMore{sv.none(), False{}})
5862
5863def talk.piece(settings: Limits, sv: Sv, p: U32 & Array<U32>) -> Turn:
5864  Limits{max, headers, hm, bm, im, wm} = settings
5865  (+len, buf) = p
5866  sv.step(U32.is_eq(len, 0), max, headers, sv, Bytes.Bytes{len, buf})
5867
5868def talk.next(settings: Limits, sv: Sv, m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> Socket & Turn:
5869  (s, r) = m
5870  match r:
5871    case Fail{(+code, message)}:
5872      (s, Bool.pick(Turn, err.late(code), TurnFail{408}, TurnInputFailed{ReadInput{}, code, message}))
5873    case Done{p}:
5874      (s, talk.piece(settings, sv, p))
5875
5876def talk.after.pick(empty: Bool, settings: Limits, rest: Bytes.Bytes) -> Turn:
5877  match empty:
5878    case True{}:
5879      TurnMore{sv.none(), False{}}
5880    case False{}:
5881      Limits{max, headers, hm, bm, im, wm} = settings
5882      sv.step(False{}, max, headers, sv.none(), rest)
5883
5884def talk.after.rest(settings: Limits, rest: Bytes.Bytes, s: Socket) -> Socket & Turn:
5885  Bytes.Bytes{+len, buf} = rest
5886  (s, talk.after.pick(U32.is_eq(len, 0), settings, Bytes.Bytes{len, buf}))
5887
5888def talk.after.keep(keep: Bool, settings: Limits, rest: Bytes.Bytes, s: Socket) -> Socket & Turn:
5889  match keep:
5890    case False{}:
5891      (s, TurnClose{})
5892    case True{}:
5893      talk.after.rest(settings, rest, s)
5894
5895def talk.after(settings: Limits, keep: Bool, rest: Bytes.Bytes, m: Socket & Result<&1, &1, U32 & String, Unit>) -> (Socket & Turn) & Completion.Outcome:
5896  (s, r) = m
5897  match r:
5898    case Fail{(code, message)}:
5899      ((s, TurnClose{}), Completion.WriteFailed{code, message})
5900    case Done{u}:
5901      (talk.after.keep(keep, settings, rest, s), Completion.HostAccepted{})
5902
5903def talk.send(+settings: Limits, s: Socket, rest: Bytes.Bytes, +headers: Map<&2, List<&2, String>>, v11: Bool, +head: Bool, res: Res) -> IO((Socket & Turn) & Completion.Outcome):
5904  Limits{max, cap, hm, bm, im, wm} = settings
5905  Res{+status, +rh, body} = res
5906  +keep = serve.keep(headers, v11, status, rh)
5907  do IO<(Socket & Turn) & Completion.Outcome>:
5908    now : Time.Duration <- Time.mono()
5909    sent : Socket & Result<&1, &1, U32 & String, Unit> <-
5910      bounded.send(s, reply.bytes(Res{status, rh, body}, head, Bool.not(keep)), Time.Duration.add(now, Time.Duration.of_ms(wm)))
5911    return talk.after(settings, keep, rest, sent)
5912
5913def talk.completed.connection(outcome: Completion.Outcome, next: Socket & Turn) -> IO(Maybe<&1, Socket & Turn>):
5914  match outcome:
5915    case Completion.HostAccepted{}:
5916      (socket, turn) = next
5917      IO.pure(Maybe<&1, Socket & Turn>, Some{(socket, turn)})
5918    case Completion.WriteFailed{code, message}:
5919      (socket, turn) = next
5920      do IO<Maybe<&1, Socket & Turn>>:
5921        Socket.close(socket)
5922        return None{}
5923    case Completion.IncompleteResponse{}:
5924      (socket, turn) = next
5925      do IO<Maybe<&1, Socket & Turn>>:
5926        Socket.close(socket)
5927        return None{}
5928
5929def talk.completed(observer: Completion.Receipt, receipt: Completion.Receipt, +status: U32,
5930  sent: (Socket & Turn) & Completion.Outcome) -> IO(Maybe<&1, Socket & Turn>):
5931  (next, outcome) = sent
5932  +observed = {outcome : Completion.Outcome}
5933  do IO<Maybe<&1, Socket & Turn>>:
5934    remaining : Maybe<&1, Socket & Turn> <- talk.completed.connection(observed, next)
5935    Completion.report(observer, status, observed)
5936    Completion.report(receipt, status, observed)
5937    return remaining
5938
5939def talk.respond(observer: Completion.Receipt, +settings: Limits, s: Socket, rest: Bytes.Bytes,
5940  headers: Map<&2, List<&2, String>>, v11: Bool, head: Bool, reply: Reply) -> IO(Maybe<&1, Socket & Turn>):
5941  Reply{Res{+status, rh, body}, receipt} = reply
5942  do IO<Maybe<&1, Socket & Turn>>:
5943    sent : (Socket & Turn) & Completion.Outcome <- talk.send(settings, s, rest, headers, v11, head, Res{status, rh, body})
5944    talk.completed(observer, receipt, status, sent)
5945
5946def talk.cont(cont: Bool, s: Socket, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, Unit>):
5947  match cont:
5948    case False{}:
5949      IO.pure(Socket & Result<&1, &1, U32 & String, Unit>, (s, Done{Unit{}}))
5950    case True{}:
5951      do IO<Socket & Result<&1, &1, U32 & String, Unit>>:
5952        now : Time.Duration <- Time.mono()
5953        bounded.send(s, Bytes.from_string("HTTP/1.1 100 Continue\r\n\r\n"), Time.Duration.add(now, Time.Duration.of_ms(ms)))
5954
5955def phase.head(empty: Bool, settings: Limits, now: Time.Duration) -> Phase:
5956  match empty:
5957    case True{}:
5958      Limits{max, cap, hm, bm, im, wm} = settings
5959      phase.new(0, im, now)
5960    case False{}:
5961      Limits{max, cap, hm, bm, im, wm} = settings
5962      phase.new(1, hm, now)
5963
5964def phase.start(settings: Limits, sv: Sv, now: Time.Duration) -> Phase & Sv:
5965  Limits{max, cap, hm, bm, im, wm} = settings
5966  match sv:
5967    case SvHead{buf}:
5968      Bytes.Bytes{+len, words} = buf
5969      (phase.head(U32.is_zero(len), Limits{max, cap, hm, bm, im, wm}, now), SvHead{Bytes.Bytes{len, words}})
5970    case SvLen{req, v11, left, parts}:
5971      (phase.new(2, bm, now), SvLen{req, v11, left, parts})
5972    case SvChunk{req, v11, n, st, parts}:
5973      (phase.new(2, bm, now), SvChunk{req, v11, n, st, parts})
5974
5975def phase.select(settings: Limits, sv: Sv, now: Time.Duration, previous: Maybe<&2, Phase>) -> Phase & Sv:
5976  match previous:
5977    case None{}:
5978      phase.start(settings, sv, now)
5979    case Some{phase}:
5980      (phase, sv)
5981
5982# Buffered pipelined headers have already ended before upload callbacks run.
5983def phase.body(previous: Maybe<&2, Phase>, settings: Limits, now: Time.Duration) -> Phase:
5984  match previous:
5985    case Some{phase}:
5986      phase
5987    case None{}:
5988      Limits{max, cap, hm, bm, im, wm} = settings
5989      phase.new(2, bm, now)
5990
5991def phase.after(+settings: Limits, +now: Time.Duration, +phase: Phase, t: Turn) -> Turn & Phase:
5992  Limits{max, cap, hm, bm, im, wm} = settings
5993  Phase{+kind, until} = phase
5994  match t:
5995    case TurnMore{sv, cont}:
5996      match sv:
5997        case SvHead{buf}:
5998          (TurnMore{SvHead{buf}, cont}, Bool.pick(Phase, U32.is_zero(kind), phase.new(1, hm, now), phase))
5999        case SvLen{req, v11, left, parts}:
6000          (TurnMore{SvLen{req, v11, left, parts}, cont}, Bool.pick(Phase, U32.is_eq(kind, 2), phase, phase.new(2, bm, now)))
6001        case SvChunk{req, v11, n, st, parts}:
6002          (TurnMore{SvChunk{req, v11, n, st, parts}, cont}, Bool.pick(Phase, U32.is_eq(kind, 2), phase, phase.new(2, bm, now)))
6003    case TurnReq{req, rest, v11}:
6004      (TurnReq{req, rest, v11}, phase)
6005    case TurnFail{+status}:
6006      (Bool.pick(Turn, U32.is_zero(kind) && U32.is_eq(status, 408), TurnClose{}, TurnFail{status}), phase)
6007    case TurnClose{}:
6008      (TurnClose{}, phase)
6009    case TurnInputFailed{operation, code, message}:
6010      (TurnInputFailed{operation, code, message}, phase)
6011
6012def phase.expired(phase: Phase) -> Turn:
6013  Phase{kind, until} = phase
6014  Bool.pick(Turn, U32.is_zero(kind), TurnClose{}, TurnFail{408})
6015
6016def talk.received.state(s: Socket, next: Turn & Phase) -> (Socket & Turn) & Phase:
6017  (t, p) = next
6018  ((s, t), p)
6019
6020def talk.received.phase(settings: Limits, phase: Phase, now: Time.Duration, next: Socket & Turn) -> (Socket & Turn) & Phase:
6021  (s, t) = next
6022  talk.received.state(s, phase.after(settings, now, phase, t))
6023
6024def talk.received.late(expired: Bool, +settings: Limits, +phase: Phase, now: Time.Duration, sv: Sv, m: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>) -> (Socket & Turn) & Phase:
6025  match expired:
6026    case True{}:
6027      (s, r) = m
6028      ((s, phase.expired(phase)), phase)
6029    case False{}:
6030      talk.received.phase(settings, phase, now, talk.next(settings, sv, m))
6031
6032# A real host read slice returns its socket; no raced operation is abandoned.
6033# The original absolute phase survives slice timeouts and stop observations.
6034# Admitted streamed uploads retain their state and finish; unadmitted input closes.
6035def talk.read.running(held: Bool, inbox: Chan(Admission.Command)) -> IO(Bool):
6036  match held:
6037    case True{}: IO.pure(Bool, True{})
6038    case False{}: Admission.running(inbox)
6039
6040type ReadStep is Type:
6041  ReadChecking{socket: Socket, state: Sv, phase: Phase}
6042  ReadAllowed{running: Bool, socket: Socket, state: Sv, phase: Phase}
6043  ReadBudget{ms: U32, socket: Socket, state: Sv, phase: Phase}
6044  ReadReceived{state: Sv, phase: Phase, now: Time.Duration,
6045    result: Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>}
6046  ReadFailed{retry: Bool, socket: Socket, state: Sv, phase: Phase,
6047    now: Time.Duration, error: U32 & String}
6048
6049# One state machine owns the socket through each actual host return.
6050@unsafe
6051def talk.read.loop(+inbox: Chan(Admission.Command), +held: Bool, +settings: Limits,
6052  step: ReadStep) -> IO((Socket & Turn) & Phase):
6053  match step:
6054    case ReadChecking{s, sv, phase}:
6055      do IO<(Socket & Turn) & Phase>:
6056        running : Bool <- talk.read.running(held, inbox)
6057        talk.read.loop(inbox, held, settings, ReadAllowed{running, s, sv, phase})
6058    case ReadAllowed{running, s, sv, +phase}:
6059      match running:
6060        case False{}:
6061          IO.pure((Socket & Turn) & Phase, ((s, TurnClose{}), phase))
6062        case True{}:
6063          Phase{kind, until} = phase
6064          do IO<(Socket & Turn) & Phase>:
6065            now : Time.Duration <- Time.mono()
6066            +ms : U32 = phase.remaining(until, now)
6067            talk.read.loop(inbox, held, settings,
6068              ReadBudget{Bool.pick(U32, held, ms, U32.min(ms, 50)), s, sv, phase})
6069    case ReadBudget{ms, s, sv, +phase}:
6070      match ms:
6071        case 0:
6072          IO.pure((Socket & Turn) & Phase, ((s, phase.expired(phase)), phase))
6073        case _:
6074          do IO<(Socket & Turn) & Phase>:
6075            got : Socket & Result<&1, &1, U32 & String, U32 & Array<U32>> <- Wire.recv.words(s, 65536, ms)
6076            now : Time.Duration <- Time.mono()
6077            talk.read.loop(inbox, held, settings, ReadReceived{sv, phase, now, got})
6078    case ReadReceived{sv, +phase, +now, got}:
6079      Phase{kind, until} = phase
6080      (s, result) = got
6081      match result:
6082        case Fail{(+code, message)}:
6083          talk.read.loop(inbox, held, settings,
6084            ReadFailed{err.late(code) && Cmp.is_lt(Time.Duration.cmp(now, until)), s, sv, phase, now, (code, message)})
6085        case Done{packed}:
6086          IO.pure((Socket & Turn) & Phase,
6087            talk.received.late(Bool.not(Cmp.is_lt(Time.Duration.cmp(now, until))), settings, phase, now, sv, (s, Done{packed})))
6088    case ReadFailed{retry, s, sv, +phase, +now, error}:
6089      match retry:
6090        case True{}:
6091          talk.read.loop(inbox, held, settings, ReadChecking{s, sv, phase})
6092        case False{}:
6093          Phase{kind, until} = phase
6094          IO.pure((Socket & Turn) & Phase,
6095            talk.received.late(Bool.not(Cmp.is_lt(Time.Duration.cmp(now, until))), settings, phase, now, sv, (s, Fail{error})))
6096
6097def talk.read(+inbox: Chan(Admission.Command), held: Bool, +settings: Limits, sv: Sv, phase: Phase, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO((Socket & Turn) & Phase):
6098  (s, r) = m
6099  match r:
6100    case Fail{(code, message)}:
6101      IO.pure((Socket & Turn) & Phase, ((s, TurnInputFailed{SendContinue{}, code, message}), phase))
6102    case Done{u}:
6103      talk.read.loop(inbox, held, settings, ReadChecking{s, sv, phase})
6104
6105def talk.recv.selected(+inbox: Chan(Admission.Command), held: Bool, +settings: Limits, s: Socket, cont: Bool, selected: Phase & Sv) -> IO((Socket & Turn) & Phase):
6106  Limits{max, cap, hm, bm, im, wm} = settings
6107  (phase, state) = selected
6108  do IO<(Socket & Turn) & Phase>:
6109    sent : Socket & Result<&1, &1, U32 & String, Unit> <- talk.cont(cont, s, wm)
6110    talk.read(inbox, held, settings, state, phase, sent)
6111
6112def talk.recv(+inbox: Chan(Admission.Command), held: Bool, +settings: Limits, s: Socket, sv: Sv, cont: Bool, previous: Maybe<&2, Phase>) -> IO((Socket & Turn) & Phase):
6113  do IO<(Socket & Turn) & Phase>:
6114    now : Time.Duration <- Time.mono()
6115    talk.recv.selected(inbox, held, settings, s, cont, phase.select(settings, sv, now, previous))
6116
6117type Serving is Type:
6118  Serving{connection: Socket & Turn, phase: Maybe<&2, Phase>}
6119
6120def talk.state(received: (Socket & Turn) & Phase) -> Serving:
6121  (connection, phase) = received
6122  Serving{connection, Some{phase}}
6123
6124def serve.write.state(after: Maybe<&1, Socket & Turn>) -> Maybe<&1, Serving>:
6125  match after:
6126    case None{}:
6127      None{}
6128    case Some{connection}:
6129      Some{Serving{connection, None{}}}
6130
6131# A buffered-work unit owns one connection's incomplete request, decoded body,
6132# and pipelined remainder from before its first recv until the task really returns.
6133# ponytail: idle sockets also reserve buffer units; split idle/read leases if idle utilization matters.
6134def admission.done(+inbox: Chan(Admission.Command), operation: IO(Unit)) -> IO(Unit):
6135  do IO<Unit>:
6136    operation
6137    Admission.give(inbox, 0)
6138
6139def admission.head(req: Req) -> Bool:
6140  Req{method, path, headers, body} = req
6141  String.eq(method, "HEAD")
6142
6143def admission.turn.head(turn: Turn) -> Bool:
6144  match turn:
6145    case TurnReq{req, rest, v11}:
6146      admission.head(req)
6147    case TurnMore{sv, cont}:
6148      match sv:
6149        case SvHead{buf}:
6150          False{}
6151        case SvLen{req, v11, left, parts}:
6152          admission.head(req)
6153        case SvChunk{req, v11, n, state, parts}:
6154          admission.head(req)
6155    case TurnFail{status}:
6156      False{}
6157    case TurnClose{}:
6158      False{}
6159    case TurnInputFailed{operation, code, message}:
6160      False{}
6161
6162# This rejection has parsed metadata, so normal HEAD body suppression still applies.
6163def admission.reject(observer: Completion.Receipt, s: Socket, head: Bool, ms: U32) -> IO(Unit):
6164  do IO<Unit>:
6165    now : Time.Duration <- Time.mono()
6166    sent : Socket & Result<&1, &1, U32 & String, Unit> <-
6167      bounded.send(s, reply.bytes(reply.fail.bytes(503), head, True{}), Time.Duration.add(now, Time.Duration.of_ms(ms)))
6168    reply.sent.observe(observer, 503, sent)
6169
6170def admission.request(
6171  ~C: Data, ~h: C -> Req -> IO(Reply), ~observe: C -> U32 -> Completion.Outcome -> IO(Unit),
6172  admitted: Bool, +inbox: Chan(Admission.Command), +context: C, +settings: Limits, s: Socket, req: Req, rest: Bytes.Bytes, v11: Bool
6173) -> IO(Maybe<&1, Socket & Turn>):
6174  match admitted:
6175    case False{}:
6176      Limits{max, cap, hm, bm, im, wm} = settings
6177      do IO<Maybe<&1, Socket & Turn>>:
6178        admission.reject(Completion.Observed{status => outcome => observe(context, status, outcome)}, s, admission.head(req), wm)
6179        return None{}
6180    case True{}:
6181      Req{+method, path, +headers, body} = req
6182      do IO<Maybe<&1, Socket & Turn>>:
6183        res : Reply <- h(context, Req{method, path, headers, body})
6184        next : Maybe<&1, Socket & Turn> <- talk.respond(Completion.Observed{status => outcome => observe(context, status, outcome)}, settings, s, rest, headers, v11, String.eq(method, "HEAD"), res)
6185        Admission.give(inbox, 1)
6186        return next
6187
6188
6189@unsafe
6190def talk(~C: Data, ~h: C -> Req -> IO(Reply), ~observe: C -> U32 -> Completion.Outcome -> IO(Unit), +context: C, +settings: Limits, +inbox: Chan(Admission.Command), st: Maybe<&1, Serving>) -> IO(Unit):
6191  Limits{max, cap, hm, bm, im, wm} = settings
6192  match st:
6193    case None{}:
6194      IO.pure(Unit, Unit{})
6195    case Some{Serving{(s, t), phase}}:
6196      match t:
6197        case TurnMore{sv, cont}:
6198          do IO<Unit>:
6199            received : (Socket & Turn) & Phase <- talk.recv(inbox, False{}, settings, s, sv, cont, phase)
6200            talk(~C, ~h, ~observe, context, settings, inbox, Some{talk.state(received)})
6201        case TurnReq{req, rest, v11}:
6202          do IO<Unit>:
6203            admitted : Bool <- Admission.take(inbox, 1)
6204            next : Maybe<&1, Socket & Turn> <- admission.request(~C, ~h, ~observe, admitted, inbox, context, settings, s, req, rest, v11)
6205            talk(~C, ~h, ~observe, context, settings, inbox, serve.write.state(next))
6206        case TurnFail{status}:
6207          reply_fail(Completion.Observed{status => outcome => observe(context, status, outcome)}, s, status, wm)
6208        case TurnClose{}:
6209          Socket.close(s)
6210        case TurnInputFailed{operation, code, message}:
6211          Socket.close(s)
6212
6213def conn.admitted(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), admitted: Bool, +settings: Limits, +inbox: Chan(Admission.Command), l: Listener, s: Socket) -> IO(Listener):
6214  match admitted:
6215    case False{}:
6216      do IO<Listener>:
6217        Socket.close(s)
6218        return l
6219    case True{}:
6220      do IO<Listener>:
6221        IO.spawn(Unit, admission.done(inbox, talk(~Unit, ~(context => req => h(req)), ~(context => status => outcome => observe(status, outcome)), Unit{}, settings, inbox,
6222          Some{Serving{(s, TurnMore{sv.none(), False{}}), None{}}})))
6223        return l
6224
6225def conn(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), +settings: Limits, +inbox: Chan(Admission.Command), m: Listener & Result<&1, &1, U32 & String, Socket>) -> IO(Listener):
6226  (l, r) = m
6227  do IO<Listener>:
6228    s : Socket <- IO.pass(Socket, r)
6229    admitted : Bool <- Admission.take(inbox, 0)
6230    conn.admitted(~h, ~observe, admitted, settings, inbox, l, s)
6231
6232# Finite positive settings, not zero/unlimited sentinels.
6233type StartupError is Data:
6234  InvalidConfig{}
6235  BindError{code: U32, message: String}
6236
6237# Application-owned affine dependencies never enter this transport owner.
6238type Server<-C: Data> is Type:
6239  Server{listener: Listener, context: C, config: ServerConfig, admission: Chan(Admission.Command), drained: Chan(Admission.Counts)}
6240
6241def server.config(host: String, port: U32) -> ServerConfig:
6242  ServerConfig{host, port, 16777216, 65536, 128, 128, 128, 5000, 30000, 30000, 30000}
6243
6244def server.config.with(host: String, port: U32, max: Nat) -> ServerConfig:
6245  ServerConfig{host, port, U32.from_nat(max), 65536, 128, 128, 128, 5000, 30000, 30000, 30000}
6246
6247# Host deadline effects use signed 32-bit milliseconds.
6248def server.deadline.valid(+ms: U32) -> Bool:
6249  U32.is_gt(ms, 0) && U32.is_le(ms, 2147483647)
6250
6251def server.config.valid(config: ServerConfig) -> Bool:
6252  ServerConfig{host, port, +body, +headers, connections, requests, buffered, header_ms, body_ms, idle_ms, write_ms} = config
6253  U32.is_le(port, 65535) && U32.is_gt(body, 0) && U32.is_le(body, limits.representable()) &&
6254    U32.is_gt(headers, 0) && U32.is_le(headers, limits.representable()) &&
6255    U32.is_gt(connections, 0) && U32.is_gt(requests, 0) && U32.is_gt(buffered, 0) &&
6256    server.deadline.valid(header_ms) && server.deadline.valid(body_ms) &&
6257    server.deadline.valid(idle_ms) && server.deadline.valid(write_ms)
6258
6259def server.opened(~C: Data, ~O: Type, owner: O, listener: Listener, context: C,
6260  config: ServerConfig, opened: Chan(Admission.Command) & Chan(Admission.Counts)) -> IO(O & Result<&2, &1, StartupError, Server<C>>):
6261  (inbox, drained) = opened
6262  IO.pure(O & Result<&2, &1, StartupError, Server<C>>, (owner, Done{Server{listener, context, config, inbox, drained}}))
6263
6264def server.started(~C: Data, ~O: Type, context: C, owner: O, +config: ServerConfig, result: Result<&1, &1, U32 & String, Listener>) -> IO(O & Result<&2, &1, StartupError, Server<C>>):
6265  ServerConfig{host, port, body, headers, connections, requests, buffered, hm, bm, im, wm} = config
6266  match result:
6267    case Fail{error}:
6268      (code, message) = error
6269      IO.pure(O & Result<&2, &1, StartupError, Server<C>>, (owner, Fail{BindError{code, message}}))
6270    case Done{listener}:
6271      do IO<O & Result<&2, &1, StartupError, Server<C>>>:
6272        opened : Chan(Admission.Command) & Chan(Admission.Counts) <- Admission.open.owner(Admission.Caps{connections, requests, buffered})
6273        server.opened(~C, ~O, owner, listener, context, config, opened)
6274
6275def server.start.valid(~C: Data, ~O: Type, valid: Bool, context: C, owner: O, +config: ServerConfig) -> IO(O & Result<&2, &1, StartupError, Server<C>>):
6276  match valid:
6277    case False{}:
6278      IO.pure(O & Result<&2, &1, StartupError, Server<C>>, (owner, Fail{InvalidConfig{}}))
6279    case True{}:
6280      ServerConfig{host, port, body, headers, connections, requests, buffered, header_ms, body_ms, idle_ms, write_ms} = config
6281      do IO<O & Result<&2, &1, StartupError, Server<C>>>:
6282        result : Result<&1, &1, U32 & String, Listener> <- TCP.listen(host, port)
6283        server.started(~C, ~O, context, owner, config, result)
6284
6285# Done is the readiness signal: TCP.listen has installed an accepting socket.
6286# Both branches return the preopened application owner unchanged.
6287def server.start(~C: Data, ~O: Type, context: C, owner: O, +config: ServerConfig) -> IO(O & Result<&2, &1, StartupError, Server<C>>):
6288  server.start.valid(~C, ~O, server.config.valid(config), context, owner, config)
6289
6290
6291# Copyable read-only observations remain usable while server.run owns the listener.
6292type ServerStats is Data:
6293  ServerStats{inbox: Chan(Admission.Command)}
6294
6295type ServerCounts is Data:
6296  ServerCounts{connections: U32, active: U32, buffered: U32,
6297    connection_high: U32, active_high: U32, buffered_high: U32,
6298    connection_rejected: Nat, active_rejected: Nat, buffered_rejected: Nat}
6299
6300def server.observer(~C: Data, server: Server<C>) -> Server<C> & ServerStats:
6301  Server{listener, context, config, +inbox, drained} = server
6302  (Server{listener, context, config, inbox, drained}, ServerStats{inbox})
6303
6304def server.counts(counts: Maybe<&2, Admission.Counts>) -> Maybe<&2, ServerCounts>:
6305  match counts:
6306    case None{}:
6307      None{}
6308    case Some{Admission.Counts{c, a, b, ch, ah, bh, cr, ar, br}}:
6309      Some{ServerCounts{c, a, b, ch, ah, bh, cr, ar, br}}
6310
6311# None means accounting has closed after the listener and its retained operations.
6312# This is not a drain result or a per-response write outcome.
6313def server.stats(observer: ServerStats) -> IO(Maybe<&2, ServerCounts>):
6314  ServerStats{inbox} = observer
6315  do IO<Maybe<&2, ServerCounts>>:
6316    counts : Maybe<&2, Admission.Counts> <- Admission.snapshot(inbox)
6317    return server.counts(counts)
6318
6319type ServerControl is Data:
6320  ServerControl{inbox: Chan(Admission.Command)}
6321
6322# Stop linearizes in Admission. It requests quiescence, never attests drain.
6323def server.control(~C: Data, server: Server<C>) -> Server<C> & ServerControl:
6324  Server{listener, context, config, +inbox, drained} = server
6325  (Server{listener, context, config, inbox, drained}, ServerControl{inbox})
6326
6327def server.stop(control: ServerControl) -> IO(Bool):
6328  ServerControl{inbox} = control
6329  Admission.quiesce(inbox)
6330
6331def server.final.counts(counts: Admission.Counts) -> ServerCounts:
6332  Admission.Counts{c, a, b, ch, ah, bh, cr, ar, br} = counts
6333  ServerCounts{c, a, b, ch, ah, bh, cr, ar, br}
6334
6335# Only the listener owner closes it, after the current accept really returns.
6336# Joining the controller waits for every real operation, including callbacks.
6337def server.close(~C: Data, server: Server<C>) -> IO(ServerCounts):
6338  Server{listener, context, config, +inbox, drained} = server
6339  do IO<ServerCounts>:
6340    requested : Bool <- Admission.quiesce(inbox)
6341    Listener.close(listener)
6342    Admission.close(inbox)
6343    counts : Admission.Counts <- IO.join(Admission.Counts, drained)
6344    return server.final.counts(counts)
6345
6346type ServerExit is Data:
6347  ServerStopped{counts: ServerCounts}
6348  ServerAcceptFailed{code: U32, message: String, counts: ServerCounts}
6349
6350def server.finished(stopped: Bool, error: U32 & String, counts: ServerCounts) -> ServerExit:
6351  match stopped:
6352    case True{}:
6353      ServerStopped{counts}
6354    case False{}:
6355      (code, message) = error
6356      ServerAcceptFailed{code, message, counts}
6357
6358def server.finish(~C: Data, stopped: Bool, error: U32 & String, server: Server<C>) -> IO(ServerExit):
6359  do IO<ServerExit>:
6360    counts : ServerCounts <- server.close(~C, server)
6361    return server.finished(stopped, error, counts)
6362
6363def server.admitted(~C: Data, ~operation: C -> Limits -> Chan(Admission.Command) -> Socket -> IO(Unit), admitted: Bool, +context: C, +config: ServerConfig, +inbox: Chan(Admission.Command), drained: Chan(Admission.Counts), listener: Listener, socket: Socket) -> IO(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>):
6364  match admitted:
6365    case False{}:
6366      do IO<Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>>:
6367        Socket.close(socket)
6368        return (Server{listener, context, config, inbox, drained}, Fail{(11, "transport capacity exhausted")})
6369    case True{}:
6370      do IO<Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>>:
6371        finished : Chan(Unit) <- IO.fork(Unit, admission.done(inbox, operation(context, limits.config(config), inbox, socket)))
6372        return (Server{listener, context, config, inbox, drained}, Done{finished})
6373
6374def server.accepted(~C: Data, ~operation: C -> Limits -> Chan(Admission.Command) -> Socket -> IO(Unit), +context: C, +config: ServerConfig, +inbox: Chan(Admission.Command), drained: Chan(Admission.Counts), result: Listener & Result<&1, &1, U32 & String, Socket>) -> IO(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>):
6375  (listener, got) = result
6376  match got:
6377    case Fail{error}:
6378      IO.pure(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>, (Server{listener, context, config, inbox, drained}, Fail{error}))
6379    case Done{socket}:
6380      do IO<Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>>:
6381        admitted : Bool <- Admission.take(inbox, 0)
6382        server.admitted(~C, ~operation, admitted, context, config, inbox, drained, listener, socket)
6383
6384# One accepting step, so the caller retains explicit listener ownership.
6385type AcceptStep<-C: Data> is Type:
6386  AcceptChecking{server: Server<C>}
6387  AcceptAllowed{running: Bool, server: Server<C>}
6388  AcceptReceived{result: Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>}
6389  AcceptFailed{retry: Bool, server: Server<C>, error: U32 & String}
6390
6391@unsafe
6392def server.accept.loop(~C: Data, ~operation: C -> Limits -> Chan(Admission.Command) -> Socket -> IO(Unit),
6393  step: AcceptStep<C>) -> IO(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>):
6394  match step:
6395    case AcceptChecking{server}:
6396      Server{listener, context, config, +inbox, drained} = server
6397      do IO<Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>>:
6398        running : Bool <- Admission.running(inbox)
6399        server.accept.loop(~C, ~operation, AcceptAllowed{running, Server{listener, context, config, inbox, drained}})
6400    case AcceptAllowed{running, server}:
6401      match running:
6402        case False{}:
6403          IO.pure(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>, (server, Fail{(0, "server stopped")}))
6404        case True{}:
6405          Server{listener, context, config, inbox, drained} = server
6406          do IO<Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>>:
6407            result : Listener & Result<&1, &1, U32 & String, Socket> <- Wire.accept.deadline(listener, 50)
6408            accepted : Server<C> & Result<&1, &1, U32 & String, Chan(Unit)> <-
6409              server.accepted(~C, ~operation, context, config, inbox, drained, result)
6410            server.accept.loop(~C, ~operation, AcceptReceived{accepted})
6411    case AcceptReceived{accepted}:
6412      (server, result) = accepted
6413      match result:
6414        case Fail{(+code, message)}:
6415          server.accept.loop(~C, ~operation, AcceptFailed{err.late(code), server, (code, message)})
6416        case Done{finished}:
6417          IO.pure(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>, (server, Done{finished}))
6418    case AcceptFailed{retry, server, error}:
6419      match retry:
6420        case True{}: server.accept.loop(~C, ~operation, AcceptChecking{server})
6421        case False{}: IO.pure(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>, (server, Fail{error}))
6422
6423def server.accept.operation(~C: Data, ~operation: C -> Limits -> Chan(Admission.Command) -> Socket -> IO(Unit),
6424  server: Server<C>) -> IO(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>):
6425  server.accept.loop(~C, ~operation, AcceptChecking{server})
6426
6427def server.accept(~C: Data, ~h: C -> Req -> IO(Reply), ~observe: C -> U32 -> Completion.Outcome -> IO(Unit), server: Server<C>) -> IO(Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>):
6428  server.accept.operation(~C,
6429    ~(context => settings => inbox => socket => talk(~C, ~h, ~observe, context, settings, inbox,
6430      Some{Serving{(socket, TurnMore{sv.none(), False{}}), None{}}})), server)
6431
6432def error.stopped(error: U32 & String) -> Bool:
6433  (code, message) = error
6434  U32.is_zero(code) && String.eq(message, "server stopped")
6435
6436type ServerLoop<-C: Data> is Type:
6437  ServerListening{server: Server<C>}
6438  ServerAccepted{result: Server<C> & Result<&1, &1, U32 & String, Chan(Unit)>}
6439  ServerFailed{retry: Bool, server: Server<C>, error: U32 & String}
6440
6441def error.capacity(error: U32 & String) -> Bool:
6442  (code, message) = error
6443  U32.is_eq(code, 11) && String.eq(message, "transport capacity exhausted")
6444
6445
6446# A single recursive state machine avoids recursion hidden inside a template.
6447@unsafe
6448def server.run.loop(~C: Data, ~operation: C -> Limits -> Chan(Admission.Command) -> Socket -> IO(Unit), step: ServerLoop<C>) -> IO(ServerExit):
6449  match step:
6450    case ServerListening{server}:
6451      do IO<ServerExit>:
6452        result : Server<C> & Result<&1, &1, U32 & String, Chan(Unit)> <- server.accept.operation(~C, ~operation, server)
6453        server.run.loop(~C, ~operation, ServerAccepted{result})
6454    case ServerAccepted{accepted}:
6455      (server, result) = accepted
6456      match result:
6457        case Fail{+error}:
6458          server.run.loop(~C, ~operation, ServerFailed{error.capacity(error), server, error})
6459        case Done{finished}:
6460          do IO<ServerExit>:
6461            Chan.close(Unit, finished)
6462            server.run.loop(~C, ~operation, ServerListening{server})
6463    case ServerFailed{retry, server, +error}:
6464      match retry:
6465        case True{}:
6466          server.run.loop(~C, ~operation, ServerListening{server})
6467        case False{}:
6468          server.finish(~C, error.stopped(error), error, server)
6469
6470def server.run(~C: Data, ~h: C -> Req -> IO(Reply), ~observe: C -> U32 -> Completion.Outcome -> IO(Unit), server: Server<C>) -> IO(ServerExit):
6471  server.run.loop(~C,
6472    ~(context => settings => inbox => socket => talk(~C, ~h, ~observe, context, settings, inbox,
6473      Some{Serving{(socket, TurnMore{sv.none(), False{}}), None{}}})), ServerListening{server})
6474
6475# Convenience wrappers use the same owner and observe its actual exit/drain.
6476def server.exited(result: ServerExit) -> IO(Unit):
6477  match result:
6478    case ServerStopped{counts}: IO.pure(Unit, Unit{})
6479    case ServerAcceptFailed{code, message, counts}: IO.die(Unit, code, message)
6480
6481def server.start.failed(error: StartupError) -> IO(Unit):
6482  match error:
6483    case InvalidConfig{}: IO.die(Unit, 22, "invalid server configuration")
6484    case BindError{code, message}: IO.die(Unit, code, message)
6485
6486def serve.config.result(result: Result<&2, &2, StartupError, Unit>) -> IO(Unit):
6487  match result:
6488    case Fail{error}: server.start.failed(error)
6489    case Done{unit}: IO.pure(Unit, unit)
6490
6491def serve.stream.nonempty(~S: Type, ~piece: S -> Bytes.Bytes -> IO(S & Bool), empty: Bool, state: S, b: Bytes.Bytes) -> IO(S & Bool):
6492  match empty:
6493    case True{}:
6494      IO.pure(S & Bool, (state, False{}))
6495    case False{}:
6496      piece(state, b)
6497
6498# The piece callback returns True to discard the rest of this request body.
6499def serve.stream.one(~S: Type, ~piece: S -> Bytes.Bytes -> IO(S & Bool), drop: Bool, state: S, b: Bytes.Bytes) -> IO(S & Bool):
6500  match drop:
6501    case True{}:
6502      IO.pure(S & Bool, (state, True{}))
6503    case False{}:
6504      Bytes.Bytes{+len, buf} = b
6505      serve.stream.nonempty(~S, ~piece, U32.is_eq(len, 0), state, Bytes.Bytes{len, buf})
6506
6507def serve.stream.parts(~S: Type, ~piece: S -> Bytes.Bytes -> IO(S & Bool), xs: List<&1, Bytes.Bytes>, st: S & Bool) -> IO(S & Bool):
6508  match xs:
6509    case Nil{}:
6510      IO.pure(S & Bool, st)
6511    case Con{b, rest}:
6512      (state, drop) = st
6513      do IO<S & Bool>:
6514        next : S & Bool <- serve.stream.one(~S, ~piece, drop, state, b)
6515        serve.stream.parts(~S, ~piece, rest, next)
6516
6517def serve.stream.feed(~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool), req: Req, m: Maybe<&1, S & Bool>, parts: List<&1, Bytes.Bytes>) -> IO(S & Bool):
6518  match m:
6519    case None{}:
6520      do IO<S & Bool>:
6521        state : S <- start(req)
6522        serve.stream.parts(~S, ~piece, parts, (state, False{}))
6523    case Some{st}:
6524      serve.stream.parts(~S, ~piece, parts, st)
6525
6526def serve.stream.respond(~S: Type, ~finish: S -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), +settings: Limits, s: Socket, rest: Bytes.Bytes, +headers: Map<&2, List<&2, String>>, v11: Bool, +method: String, st: S & Bool) -> IO(Maybe<&1, Socket & Turn>):
6527  (state, dropped) = st
6528  do IO<Maybe<&1, Socket & Turn>>:
6529    res : Reply <- finish(state)
6530    talk.respond(Completion.Observed{status => outcome => observe(status, outcome)}, settings, s, rest, headers, v11, String.eq(method, "HEAD"), res)
6531
6532def admission.ready(t: Turn) -> Bool & Turn:
6533  match t:
6534    case TurnReq{req, rest, v11}:
6535      (True{}, TurnReq{req, rest, v11})
6536    case TurnMore{sv, cont}:
6537      match sv:
6538        case SvHead{buf}:
6539          (False{}, TurnMore{SvHead{buf}, cont})
6540        case SvLen{req, v11, left, parts}:
6541          (True{}, TurnMore{SvLen{req, v11, left, parts}, cont})
6542        case SvChunk{req, v11, n, state, parts}:
6543          (True{}, TurnMore{SvChunk{req, v11, n, state, parts}, cont})
6544    case TurnFail{status}:
6545      (False{}, TurnFail{status})
6546    case TurnClose{}:
6547      (False{}, TurnClose{})
6548    case TurnInputFailed{operation, code, message}:
6549      (False{}, TurnInputFailed{operation, code, message})
6550
6551def admission.give.held(held: Bool, inbox: Chan(Admission.Command)) -> IO(Unit):
6552  match held:
6553    case False{}:
6554      IO.pure(Unit, Unit{})
6555    case True{}:
6556      Admission.give(inbox, 1)
6557
6558type StreamServing<-S: Type> is Type:
6559  StreamCheck{held: Bool, connection: Serving, state: Maybe<&1, S & Bool>}
6560  StreamReady{held: Bool, ready: Bool, connection: Serving, state: Maybe<&1, S & Bool>}
6561  StreamChecked{held: Bool, admitted: Bool, connection: Serving, state: Maybe<&1, S & Bool>}
6562  StreamDone{}
6563
6564def serve.stream.after(~S: Type, next: Maybe<&1, Socket & Turn>) -> StreamServing<S>:
6565  match next:
6566    case None{}:
6567      StreamDone{}
6568    case Some{connection}:
6569      StreamCheck{False{}, Serving{connection, None{}}, None{}}
6570
6571def serve.stream.ready(~S: Type, held: Bool, socket: Socket, phase: Maybe<&2, Phase>,
6572  state: Maybe<&1, S & Bool>, result: Bool & Turn) -> StreamServing<S>:
6573  (ready, turn) = result
6574  StreamReady{held, ready, Serving{(socket, turn), phase}, state}
6575
6576def admission.stream.take(needed: Bool, inbox: Chan(Admission.Command)) -> IO(Bool):
6577  match needed:
6578    case False{}:
6579      IO.pure(Bool, True{})
6580    case True{}:
6581      Admission.take(inbox, 1)
6582
6583type StreamAbort is Data:
6584  StreamClosed{}
6585  StreamFailed{status: U32}
6586  StreamInputFailed{operation: InputOperation, code: U32, message: String}
6587
6588# The application consumes its real affine state on unsuccessful upload paths.
6589# An abort that does not return remains admitted work, not a fabricated drain.
6590def serve.stream.abort(~S: Type, ~abort: S -> StreamAbort -> IO(Unit),
6591  reason: StreamAbort, state: Maybe<&1, S & Bool>) -> IO(Unit):
6592  match state:
6593    case None{}: IO.pure(Unit, Unit{})
6594    case Some{pair}:
6595      (value, dropped) = pair
6596      abort(value, reason)
6597
6598# One recursive state machine keeps template callbacks out of mutual recursion.
6599@unsafe
6600def serve.stream.talk(
6601  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool),
6602  ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit), +settings: Limits, +inbox: Chan(Admission.Command), step: StreamServing<S>
6603) -> IO(Unit):
6604  Limits{max, cap, hm, bm, im, wm} = settings
6605  match step:
6606    case StreamDone{}:
6607      IO.pure(Unit, Unit{})
6608    case StreamCheck{held, connection, state}:
6609      Serving{(socket, turn), phase} = connection
6610      serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6611        serve.stream.ready(~S, held, socket, phase, state, admission.ready(turn)))
6612    case StreamReady{+held, +ready, connection, state}:
6613      do IO<Unit>:
6614        admitted : Bool <- admission.stream.take(Bool.not(held) && ready, inbox)
6615        serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6616          StreamChecked{held || ready, admitted, connection, state})
6617    case StreamChecked{+held, admitted, connection, m}:
6618      match admitted:
6619        case False{}:
6620          Serving{(socket, turn), phase} = connection
6621          do IO<Unit>:
6622            serve.stream.abort(~S, ~abort, StreamFailed{503}, m)
6623            admission.reject(Completion.Observed{status => outcome => observe(status, outcome)}, socket, admission.turn.head(turn), wm)
6624        case True{}:
6625          Serving{(s, t), phase} = connection
6626          match t:
6627            case TurnClose{}:
6628              do IO<Unit>:
6629                Socket.close(s)
6630                serve.stream.abort(~S, ~abort, StreamClosed{}, m)
6631                admission.give.held(held, inbox)
6632            case TurnInputFailed{operation, code, message}:
6633              do IO<Unit>:
6634                Socket.close(s)
6635                serve.stream.abort(~S, ~abort, StreamInputFailed{operation, code, message}, m)
6636                admission.give.held(held, inbox)
6637            case TurnFail{+status}:
6638              do IO<Unit>:
6639                serve.stream.abort(~S, ~abort, StreamFailed{status}, m)
6640                reply_fail(Completion.Observed{status => outcome => observe(status, outcome)}, s, status, wm)
6641                admission.give.held(held, inbox)
6642            case TurnReq{req, rest, v11}:
6643              Req{+method, path, +headers, body} = req
6644              do IO<Unit>:
6645                next : S & Bool <- serve.stream.feed(~S, ~start, ~piece,
6646                  Req{method, path, headers, Bytes.new(0)}, m, [body])
6647                after : Maybe<&1, Socket & Turn> <- serve.stream.respond(~S, ~finish, ~observe, settings, s, rest, headers, v11, method, next)
6648                Admission.give(inbox, 1)
6649                serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6650                  serve.stream.after(~S, after))
6651            case TurnMore{sv, cont}:
6652              match sv:
6653                case SvHead{buf}:
6654                  do IO<Unit>:
6655                    received : (Socket & Turn) & Phase <- talk.recv(inbox, held, settings, s, SvHead{buf}, cont, phase)
6656                    serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6657                      StreamCheck{held, talk.state(received), m})
6658                case SvLen{req, +v11, +left, parts}:
6659                  Req{+method, +path, +headers, body} = req
6660                  do IO<Unit>:
6661                    now : Time.Duration <- Time.mono()
6662                    reading : Phase = phase.body(phase, settings, now)
6663                    next : S & Bool <- serve.stream.feed(~S, ~start, ~piece,
6664                      Req{method, path, headers, body}, m, List.reverse(&1, Bytes.Bytes, parts))
6665                    received : (Socket & Turn) & Phase <- talk.recv(inbox, held, settings, s,
6666                      SvLen{Req{method, path, headers, Bytes.new(0)}, v11, left, Nil{}}, cont, Some{reading})
6667                    serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6668                      StreamCheck{held, talk.state(received), Some{next}})
6669                case SvChunk{req, +v11, +n, st, parts}:
6670                  Req{+method, +path, +headers, body} = req
6671                  do IO<Unit>:
6672                    now : Time.Duration <- Time.mono()
6673                    reading : Phase = phase.body(phase, settings, now)
6674                    next : S & Bool <- serve.stream.feed(~S, ~start, ~piece,
6675                      Req{method, path, headers, body}, m, List.reverse(&1, Bytes.Bytes, parts))
6676                    received : (Socket & Turn) & Phase <- talk.recv(inbox, held, settings, s,
6677                      SvChunk{Req{method, path, headers, Bytes.new(0)}, v11, n, st, Nil{}}, cont, Some{reading})
6678                    serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6679                      StreamCheck{held, talk.state(received), Some{next}})
6680
6681
6682def server.stream.run(
6683  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool),
6684  ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit), server: Server<Unit>
6685) -> IO(ServerExit):
6686  server.run.loop(~Unit,
6687    ~(context => settings => inbox => socket =>
6688      serve.stream.talk(~S, ~start, ~piece, ~finish, ~abort, ~observe, settings, inbox,
6689        StreamCheck{False{}, Serving{(socket, TurnMore{sv.none(), False{}}), None{}}, None{}})),
6690    ServerListening{server})
6691
6692def serve.stream.started(
6693  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool), ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
6694  result: Unit & Result<&2, &1, StartupError, Server<Unit>>
6695) -> IO(Result<&2, &2, StartupError, Unit>):
6696  (owner, got) = result
6697  match got:
6698    case Fail{error}:
6699      IO.pure(Result<&2, &2, StartupError, Unit>, Fail{error})
6700    case Done{server}:
6701      do IO<Result<&2, &2, StartupError, Unit>>:
6702        exited : ServerExit <- server.stream.run(~S, ~start, ~piece, ~finish, ~abort, ~observe, server)
6703        server.exited(exited)
6704        return Done{Unit{}}
6705
6706def serve.stream.config(
6707  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool), ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
6708  config: ServerConfig
6709) -> IO(Result<&2, &2, StartupError, Unit>):
6710  do IO<Result<&2, &2, StartupError, Unit>>:
6711    started : Unit & Result<&2, &1, StartupError, Server<Unit>> <- server.start(~Unit, ~Unit, Unit{}, Unit{}, config)
6712    serve.stream.started(~S, ~start, ~piece, ~finish, ~abort, ~observe, started)
6713
6714type WriterMode is Data:
6715  WriteKnown{left: U32}
6716  WriteChunk{}
6717  WriteNone{}
6718
6719type Writer is Type:
6720  Writer{s: Maybe<&1, Socket>, mode: WriterMode, until: Time.Duration, outcome: Completion.Outcome}
6721
6722type WriteHead<-S: Type> is Type:
6723  WriteHead{status: U32, headers: Map<&2, List<&2, String>>, length: Maybe<&2, U32>, state: S, receipt: Completion.Receipt}
6724
6725def writer.sent(+mode: WriterMode, +until: Time.Duration, m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Writer):
6726  (s, r) = m
6727  match r:
6728    case Fail{(code, message)}:
6729      do IO<Writer>:
6730        Socket.close(s)
6731        return Writer{None{}, mode, until, Completion.WriteFailed{code, message}}
6732    case Done{u}:
6733      IO.pure(Writer, Writer{Some{s}, mode, until, Completion.HostAccepted{}})
6734
6735def writer.raw(w: Writer, b: Bytes.Bytes) -> IO(Writer):
6736  Writer{socket, +mode, +until, outcome} = w
6737  match socket:
6738    case None{}:
6739      IO.pure(Writer, Writer{None{}, mode, until, outcome})
6740    case Some{s}:
6741      do IO<Writer>:
6742        sent : Socket & Result<&1, &1, U32 & String, Unit> <- bounded.send(s, b, until)
6743        writer.sent(mode, until, sent)
6744
6745def writer.chunk(s: Socket, b: Bytes.Bytes, +len: U32, until: Time.Duration) -> IO(Writer):
6746  do IO<Writer>:
6747    head : Writer <- writer.raw(Writer{Some{s}, WriteChunk{}, until, Completion.HostAccepted{}}, Bytes.from_string(hex(len) ++ "\r\n"))
6748    data : Writer <- writer.raw(head, b)
6749    writer.raw(data, Bytes.from_string("\r\n"))
6750
6751def writer.known(ok: Bool, s: Socket, +left: U32, len: U32, b: Bytes.Bytes, +until: Time.Duration) -> IO(Writer):
6752  match ok:
6753    case False{}:
6754      do IO<Writer>:
6755        Socket.close(s)
6756        return Writer{None{}, WriteKnown{left}, until, Completion.IncompleteResponse{}}
6757    case True{}:
6758      writer.raw(Writer{Some{s}, WriteKnown{(left - len : U32)}, until, Completion.HostAccepted{}}, b)
6759
6760def writer.chunk.go(empty: Bool, s: Socket, b: Bytes.Bytes, len: U32, until: Time.Duration) -> IO(Writer):
6761  match empty:
6762    case True{}:
6763      IO.pure(Writer, Writer{Some{s}, WriteChunk{}, until, Completion.HostAccepted{}})
6764    case False{}:
6765      writer.chunk(s, b, len, until)
6766
6767def writer.write.mode(s: Socket, mode: WriterMode, b: Bytes.Bytes, until: Time.Duration) -> IO(Writer):
6768  match mode:
6769    case WriteNone{}:
6770      IO.pure(Writer, Writer{Some{s}, WriteNone{}, until, Completion.HostAccepted{}})
6771    case WriteKnown{+left}:
6772      Bytes.Bytes{+len, buf} = b
6773      writer.known(U32.is_le(len, left), s, left, len, Bytes.Bytes{len, buf}, until)
6774    case WriteChunk{}:
6775      Bytes.Bytes{+len, buf} = b
6776      writer.chunk.go(U32.is_zero(len), s, Bytes.Bytes{len, buf}, len, until)
6777
6778def writer.write(w: Writer, b: Bytes.Bytes) -> IO(Writer):
6779  Writer{socket, mode, until, outcome} = w
6780  match socket:
6781    case None{}:
6782      IO.pure(Writer, Writer{None{}, mode, until, outcome})
6783    case Some{s}:
6784      writer.write.mode(s, mode, b, until)
6785
6786def writer.result(w: Writer) -> Maybe<&1, Socket> & Completion.Outcome:
6787  Writer{socket, mode, until, outcome} = w
6788  (socket, outcome)
6789
6790def writer.finish.known(complete: Bool, s: Socket) -> IO(Maybe<&1, Socket> & Completion.Outcome):
6791  match complete:
6792    case True{}:
6793      IO.pure(Maybe<&1, Socket> & Completion.Outcome, (Some{s}, Completion.HostAccepted{}))
6794    case False{}:
6795      do IO<Maybe<&1, Socket> & Completion.Outcome>:
6796        Socket.close(s)
6797        return (None{}, Completion.IncompleteResponse{})
6798
6799def writer.finish.mode(s: Socket, mode: WriterMode, until: Time.Duration) -> IO(Maybe<&1, Socket> & Completion.Outcome):
6800  match mode:
6801    case WriteNone{}:
6802      IO.pure(Maybe<&1, Socket> & Completion.Outcome, (Some{s}, Completion.HostAccepted{}))
6803    case WriteKnown{left}:
6804      writer.finish.known(U32.is_zero(left), s)
6805    case WriteChunk{}:
6806      do IO<Maybe<&1, Socket> & Completion.Outcome>:
6807        end : Writer <- writer.raw(Writer{Some{s}, WriteChunk{}, until, Completion.HostAccepted{}}, Bytes.from_string("0\r\n\r\n"))
6808        return writer.result(end)
6809
6810def writer.finish(w: Writer) -> IO(Maybe<&1, Socket> & Completion.Outcome):
6811  Writer{socket, mode, until, outcome} = w
6812  match socket:
6813    case None{}:
6814      IO.pure(Maybe<&1, Socket> & Completion.Outcome, (None{}, outcome))
6815    case Some{s}:
6816      writer.finish.mode(s, mode, until)
6817
6818def writer.header.one(m: Map<&2, List<&2, String>>, +key: String, vs: List<&2, String>, framing: Bool) -> Map<&2, List<&2, String>>:
6819  match framing:
6820    case True{}:
6821      m
6822    case False{}:
6823      Map.set(&2, List<&2, String>, m, key, vs)
6824
6825def writer.headers(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
6826  match xs:
6827    case Nil{}:
6828      m
6829    case (k, vs) <> rest:
6830      +key = String.to_lower(k)
6831      +framing = Bool.or(String.eq(key, "content-length"), String.eq(key, "transfer-encoding"))
6832      writer.headers(rest, writer.header.one(m, key, vs, framing))
6833
6834def writer.plan.body(headers: Map<&2, List<&2, String>>, length: Maybe<&2, U32>) -> WriterMode & Map<&2, List<&2, String>>:
6835  match length:
6836    case Some{+n}:
6837      (WriteKnown{n}, set(headers, "content-length", U32.show(n)))
6838    case None{}:
6839      (WriteChunk{}, set(headers, "transfer-encoding", "chunked"))
6840
6841def writer.plan(nobody: Bool, headers: Map<&2, List<&2, String>>, length: Maybe<&2, U32>) -> WriterMode & Map<&2, List<&2, String>>:
6842  match nobody:
6843    case True{}:
6844      (WriteNone{}, headers)
6845    case False{}:
6846      writer.plan.body(headers, length)
6847
6848def serve.write.after(settings: Limits, keep: Bool, rest: Bytes.Bytes, socket: Maybe<&1, Socket>) -> Maybe<&1, Socket & Turn>:
6849  match socket:
6850    case None{}:
6851      None{}
6852    case Some{s}:
6853      Some{talk.after.keep(keep, settings, rest, s)}
6854
6855def serve.write.completed(observer: Completion.Receipt, receipt: Completion.Receipt, +status: U32,
6856  settings: Limits, keep: Bool, rest: Bytes.Bytes, result: Maybe<&1, Socket> & Completion.Outcome) -> IO(Maybe<&1, Socket & Turn>):
6857  (socket, outcome) = result
6858  +observed = {outcome : Completion.Outcome}
6859  do IO<Maybe<&1, Socket & Turn>>:
6860    Completion.report(observer, status, observed)
6861    Completion.report(receipt, status, observed)
6862    return serve.write.after(settings, keep, rest, socket)
6863
6864def serve.write.returned(observer: Completion.Receipt, status: U32, settings: Limits, keep: Bool,
6865  rest: Bytes.Bytes, result: Writer & Completion.Receipt) -> IO(Maybe<&1, Socket & Turn>):
6866  (writer, receipt) = result
6867  do IO<Maybe<&1, Socket & Turn>>:
6868    done : Maybe<&1, Socket> & Completion.Outcome <- writer.finish(writer)
6869    serve.write.completed(observer, receipt, status, settings, keep, rest, done)
6870
6871def serve.write.finish(observer: Completion.Receipt, receipt: Completion.Receipt, status: U32,
6872  settings: Limits, keep: Bool, rest: Bytes.Bytes, w: Writer) -> IO(Maybe<&1, Socket & Turn>):
6873  serve.write.returned(observer, status, settings, keep, rest, (w, receipt))
6874
6875def serve.write.body(~S: Type, ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit),
6876  observer: Completion.Receipt, receipt: Completion.Receipt, status: U32, settings: Limits,
6877  keep: Bool, rest: Bytes.Bytes, state: S, s: Socket, mode: WriterMode, suppress: Bool, until: Time.Duration) -> IO(Maybe<&1, Socket & Turn>):
6878  match suppress:
6879    case True{}:
6880      do IO<Maybe<&1, Socket & Turn>>:
6881        dispose(state)
6882        serve.write.finish(observer, receipt, status, settings, keep, rest, Writer{Some{s}, WriteNone{}, until, Completion.HostAccepted{}})
6883    case False{}:
6884      do IO<Maybe<&1, Socket & Turn>>:
6885        result : Writer & Completion.Receipt <- write(state, Writer{Some{s}, mode, until, Completion.HostAccepted{}}, receipt)
6886        serve.write.returned(observer, status, settings, keep, rest, result)
6887
6888def serve.write.open(~S: Type, ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit),
6889  observer: Completion.Receipt, receipt: Completion.Receipt, +status: U32, settings: Limits,
6890  keep: Bool, rest: Bytes.Bytes, state: S, mode: WriterMode, suppress: Bool, until: Time.Duration,
6891  m: Socket & Result<&1, &1, U32 & String, Unit>) -> IO(Maybe<&1, Socket & Turn>):
6892  (s, r) = m
6893  match r:
6894    case Fail{(code, message)}:
6895      +failure = {Completion.WriteFailed{code, message} : Completion.Outcome}
6896      do IO<Maybe<&1, Socket & Turn>>:
6897        Socket.close(s)
6898        dispose(state)
6899        Completion.report(observer, status, failure)
6900        Completion.report(receipt, status, failure)
6901        return None{}
6902    case Done{u}:
6903      serve.write.body(~S, ~write, ~dispose, observer, receipt, status, settings, keep, rest, state, s, mode, suppress, until)
6904
6905def writer.connection(keep: Bool, headers: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>:
6906  match keep:
6907    case True{}:
6908      headers
6909    case False{}:
6910      set(headers, "connection", "close")
6911
6912def serve.write.send.plan(
6913  ~S: Type, ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit),
6914  observer: Completion.Receipt, receipt: Completion.Receipt, +settings: Limits, s: Socket, rest: Bytes.Bytes,
6915  +keep: Bool, +head: Bool, +status: U32, state: S, plan: WriterMode & Map<&2, List<&2, String>>
6916) -> IO(Maybe<&1, Socket & Turn>):
6917  Limits{max, cap, hm, bm, im, wm} = settings
6918  (mode, headers) = plan
6919  sent_headers = writer.connection(keep, headers)
6920  do IO<Maybe<&1, Socket & Turn>>:
6921    now : Time.Duration <- Time.mono()
6922    +until : Time.Duration = Time.Duration.add(now, Time.Duration.of_ms(wm))
6923    sent : Socket & Result<&1, &1, U32 & String, Unit> <-
6924      bounded.send(s, Bytes.from_string(response(status, sent_headers, "")), until)
6925    serve.write.open(~S, ~write, ~dispose, observer, receipt, status, settings, keep, rest, state, mode,
6926      frame.nobody(status, head), until, sent)
6927
6928def serve.write.send(
6929  ~S: Type, ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), observer: Completion.Receipt, settings: Limits, s: Socket, rest: Bytes.Bytes,
6930  +headers: Map<&2, List<&2, String>>, v11: Bool, +head: Bool, out: WriteHead<S>
6931) -> IO(Maybe<&1, Socket & Turn>):
6932  WriteHead{+status, rh, length, state, receipt} = out
6933  +normalized = writer.headers(Map.to_list(&2, List<&2, String>, rh), empty())
6934  +keep = serve.keep(headers, v11, status, normalized)
6935  serve.write.send.plan(~S, ~write, ~dispose, observer, receipt, settings, s, rest, keep, head, status, state,
6936    writer.plan(frame.nobody(status, False{}), normalized, length))
6937
6938def admission.write(
6939  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
6940  admitted: Bool, +inbox: Chan(Admission.Command), +settings: Limits,
6941  s: Socket, req: Req, rest: Bytes.Bytes, v11: Bool
6942) -> IO(Maybe<&1, Socket & Turn>):
6943  match admitted:
6944    case False{}:
6945      Limits{max, cap, hm, bm, im, wm} = settings
6946      do IO<Maybe<&1, Socket & Turn>>:
6947        admission.reject(Completion.Observed{status => outcome => observe(status, outcome)}, s, admission.head(req), wm)
6948        return None{}
6949    case True{}:
6950      Req{+method, path, +headers, body} = req
6951      do IO<Maybe<&1, Socket & Turn>>:
6952        out : WriteHead<S> <- start(Req{method, path, headers, body})
6953        after : Maybe<&1, Socket & Turn> <- serve.write.send(~S, ~write, ~dispose, Completion.Observed{status => outcome => observe(status, outcome)}, settings, s, rest, headers, v11, String.eq(method, "HEAD"), out)
6954        Admission.give(inbox, 1)
6955        return after
6956
6957@unsafe
6958def serve.write.talk(
6959  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
6960  +settings: Limits, +inbox: Chan(Admission.Command), st: Maybe<&1, Serving>
6961) -> IO(Unit):
6962  Limits{max, cap, hm, bm, im, wm} = settings
6963  match st:
6964    case None{}:
6965      IO.pure(Unit, Unit{})
6966    case Some{Serving{(s, t), phase}}:
6967      match t:
6968        case TurnMore{sv, cont}:
6969          do IO<Unit>:
6970            received : (Socket & Turn) & Phase <- talk.recv(inbox, False{}, settings, s, sv, cont, phase)
6971            serve.write.talk(~S, ~start, ~write, ~dispose, ~observe, settings, inbox, Some{talk.state(received)})
6972        case TurnReq{req, rest, v11}:
6973          do IO<Unit>:
6974            admitted : Bool <- Admission.take(inbox, 1)
6975            after : Maybe<&1, Socket & Turn> <- admission.write(~S, ~start, ~write, ~dispose, ~observe, admitted, inbox, settings, s, req, rest, v11)
6976            serve.write.talk(~S, ~start, ~write, ~dispose, ~observe, settings, inbox, serve.write.state(after))
6977        case TurnFail{status}:
6978          reply_fail(Completion.Observed{status => outcome => observe(status, outcome)}, s, status, wm)
6979        case TurnClose{}:
6980          Socket.close(s)
6981        case TurnInputFailed{operation, code, message}:
6982          Socket.close(s)
6983
6984def server.write.run(
6985  ~S: Type, ~start: Req -> IO(WriteHead<S>),
6986  ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt),
6987  ~dispose: S -> IO(Unit),
6988  ~observe: U32 -> Completion.Outcome -> IO(Unit), server: Server<Unit>
6989) -> IO(ServerExit):
6990  server.run.loop(~Unit,
6991    ~(context => settings => inbox => socket =>
6992      serve.write.talk(~S, ~start, ~write, ~dispose, ~observe, settings, inbox,
6993        Some{Serving{(socket, TurnMore{sv.none(), False{}}), None{}}})),
6994    ServerListening{server})
6995
6996def serve.write.started(
6997  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
6998  result: Unit & Result<&2, &1, StartupError, Server<Unit>>
6999) -> IO(Result<&2, &2, StartupError, Unit>):
7000  (owner, got) = result
7001  match got:
7002    case Fail{error}:
7003      IO.pure(Result<&2, &2, StartupError, Unit>, Fail{error})
7004    case Done{server}:
7005      do IO<Result<&2, &2, StartupError, Unit>>:
7006        exited : ServerExit <- server.write.run(~S, ~start, ~write, ~dispose, ~observe, server)
7007        server.exited(exited)
7008        return Done{Unit{}}
7009
7010def serve.write.config(
7011  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit), config: ServerConfig
7012) -> IO(Result<&2, &2, StartupError, Unit>):
7013  do IO<Result<&2, &2, StartupError, Unit>>:
7014    started : Unit & Result<&2, &1, StartupError, Server<Unit>> <- server.start(~Unit, ~Unit, Unit{}, Unit{}, config)
7015    serve.write.started(~S, ~start, ~write, ~dispose, ~observe, started)
7016
7017def serve.started(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit),
7018  started: Unit & Result<&2, &1, StartupError, Server<Unit>>) -> IO(Unit):
7019  (owner, result) = started
7020  match result:
7021    case Fail{error}: server.start.failed(error)
7022    case Done{server}:
7023      do IO<Unit>:
7024        exited : ServerExit <- server.run(~Unit, ~(context => req => h(req)),
7025          ~(context => status => outcome => observe(status, outcome)), server)
7026        server.exited(exited)
7027
7028def serve.on.with(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), host: String, +port: U32, +max: Nat) -> IO(Unit):
7029  do IO<Unit>:
7030    limits.validate(max)
7031    started : Unit & Result<&2, &1, StartupError, Server<Unit>> <-
7032      server.start(~Unit, ~Unit, Unit{}, Unit{}, server.config.with(host, port, max))
7033    serve.started(~h, ~observe, started)
7034
7035def serve.on(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), host: String, +port: U32) -> IO(Unit):
7036  serve.on.with(~h, ~observe, host, port, serve.max())
7037
7038def serve.with(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), +port: U32, +max: Nat) -> IO(Unit):
7039  serve.on.with(~h, ~observe, "0.0.0.0", port, max)
7040
7041def serve(~h: Req -> IO(Reply), ~observe: U32 -> Completion.Outcome -> IO(Unit), +port: U32) -> IO(Unit):
7042  serve.with(~h, ~observe, port, serve.max())
7043
7044def serve.stream.on.with(
7045  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool),
7046  ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit), host: String, +port: U32, +max: Nat
7047) -> IO(Unit):
7048  do IO<Unit>:
7049    limits.validate(max)
7050    result : Result<&2, &2, StartupError, Unit> <-
7051      serve.stream.config(~S, ~start, ~piece, ~finish, ~abort, ~observe, server.config.with(host, port, max))
7052    serve.config.result(result)
7053
7054def serve.stream.with(
7055  ~S: Type, ~start: Req -> IO(S), ~piece: S -> Bytes.Bytes -> IO(S & Bool),
7056  ~finish: S -> IO(Reply), ~abort: S -> StreamAbort -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit), +port: U32, +max: Nat
7057) -> IO(Unit):
7058  serve.stream.on.with(~S, ~start, ~piece, ~finish, ~abort, ~observe, "0.0.0.0", port, max)
7059
7060def serve.write.on.with(
7061  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
7062  host: String, +port: U32, +max: Nat
7063) -> IO(Unit):
7064  do IO<Unit>:
7065    limits.validate(max)
7066    result : Result<&2, &2, StartupError, Unit> <-
7067      serve.write.config(~S, ~start, ~write, ~dispose, ~observe, server.config.with(host, port, max))
7068    serve.config.result(result)
7069
7070def serve.write.with(
7071  ~S: Type, ~start: Req -> IO(WriteHead<S>), ~write: S -> Writer -> Completion.Receipt -> IO(Writer & Completion.Receipt), ~dispose: S -> IO(Unit), ~observe: U32 -> Completion.Outcome -> IO(Unit),
7072  +port: U32, +max: Nat
7073) -> IO(Unit):
7074  serve.write.on.with(~S, ~start, ~write, ~dispose, ~observe, "0.0.0.0", port, max)