# Postgres frontend/backend protocol 3.0 messages over Bytes, and an incremental reader of backend messages. import Base import bend-kit-bytes@0.3.1.0/bytes.bend as Bytes # ---- messages # A StartupMessage parameter, such as ("user", name) or ("database", db). type Param is Type: Param{name: Bytes.Bytes, value: Bytes.Bytes} # Frontend messages. Describe and CloseMsg take kind 83 ('S', statement) or 80 ('P', portal). # Bind params hold one Maybe per parameter, None for SQL NULL; empty formats mean text. type Front is Type: Startup{params: List<&1, Param>} SSLRequest{} Password{text: Bytes.Bytes} SASLInitial{mech: Bytes.Bytes, data: Bytes.Bytes} SASLResponse{data: Bytes.Bytes} Parse{name: Bytes.Bytes, query: Bytes.Bytes, oids: List<&2, U32>} Bind{portal: Bytes.Bytes, stmt: Bytes.Bytes, formats: List<&2, U32>, params: List<&1, Maybe<&1, Bytes.Bytes>>, results: List<&2, U32>} Describe{kind: U32, name: Bytes.Bytes} Execute{portal: Bytes.Bytes, max: U32} Sync{} CloseMsg{kind: U32, name: Bytes.Bytes} Terminate{} # A RowDescription column. size and modifier are the raw int16 and int32 words. type Field is Type: Field{name: Bytes.Bytes, table: U32, column: U32, oid: U32, size: U32, modifier: U32, format: U32} # An ErrorResponse or NoticeResponse field: code is its type byte, such as 67 ('C', SQLSTATE) or 77 ('M', message). type Notice is Type: Notice{code: U32, value: Bytes.Bytes} # Backend messages. AuthOther holds any other authentication code (MD5 is 5) and its payload. # ReadyForQuery status is 73 ('I'), 84 ('T') or 69 ('E'). Other holds a well-framed message of any other tag. type Back is Type: AuthOk{} AuthCleartext{} AuthSASL{mechs: List<&1, Bytes.Bytes>} AuthSASLContinue{data: Bytes.Bytes} AuthSASLFinal{data: Bytes.Bytes} AuthOther{code: U32, data: Bytes.Bytes} ParameterStatus{name: Bytes.Bytes, value: Bytes.Bytes} BackendKeyData{pid: U32, key: U32} ReadyForQuery{status: U32} RowDescription{fields: List<&1, Field>} DataRow{cols: List<&1, Maybe<&1, Bytes.Bytes>>} CommandComplete{tag: Bytes.Bytes} ErrorResponse{fields: List<&1, Notice>} NoticeResponse{fields: List<&1, Notice>} ParseComplete{} BindComplete{} CloseComplete{} NoData{} EmptyQuery{} PortalSuspended{} Other{tag: U32, body: Bytes.Bytes} # The first field whose code is code. def notice.get(+code: U32, fs: List<&1, Notice>) -> Maybe<&1, Bytes.Bytes>: match fs: case Nil{}: None{} case Con{Notice{+k, v}, t}: Bool.pick(Maybe<&1, Bytes.Bytes>, U32.is_eq(k, code), Some{v}, notice.get(code, t)) # ---- encoder def byte1(+c: U32) -> Bytes.Bytes: Bytes.set(Bytes.new(1), 0, c) def be16(+v: U32) -> Bytes.Bytes: Bytes.set.u16be(Bytes.new(2), 0, v) def be32(+v: U32) -> Bytes.Bytes: Bytes.set.u32be(Bytes.new(4), 0, v) # ponytail: a NUL inside a cstr field ends it early; the message length still frames it, so the server rejects that message only. def cstr.enc(b: Bytes.Bytes) -> Bytes.Bytes: Bytes.append(b, Bytes.new(1)) def frame.of(+tag: U32, r: Bytes.Bytes & U32) -> Bytes.Bytes: (body, +n) = r Bytes.concat([byte1(tag), be32((n + 4 : U32)), body]) # A tagged message: the tag byte, the int32 length (counting itself), then the body. def frame(+tag: U32, body: Bytes.Bytes) -> Bytes.Bytes: frame.of(tag, Bytes.length(body)) def packet.of(r: Bytes.Bytes & U32) -> Bytes.Bytes: (body, +n) = r Bytes.append(be32((n + 4 : U32)), body) # An untagged startup-phase packet. def packet(body: Bytes.Bytes) -> Bytes.Bytes: packet.of(Bytes.length(body)) def n16.con(h: Bytes.Bytes, r: List<&1, Bytes.Bytes> & U32) -> List<&1, Bytes.Bytes> & U32: (t, +n) = r (Con{h, t}, (n + 1 : U32)) def n16.count(xs: List<&1, Bytes.Bytes>) -> List<&1, Bytes.Bytes> & U32: match xs: case Nil{}: (Nil{}, 0) case Con{h, t}: n16.con(h, n16.count(t)) def n16.of(r: List<&1, Bytes.Bytes> & U32) -> Bytes.Bytes: (xs, +n) = r Bytes.concat(Con{be16(n), xs}) # An int16 count, then the pieces. def n16(xs: List<&1, Bytes.Bytes>) -> Bytes.Bytes: n16.of(n16.count(xs)) def be16s(xs: List<&2, U32>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: Nil{} case Con{h, t}: Con{be16(h), be16s(t)} def be32s(xs: List<&2, U32>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: Nil{} case Con{h, t}: Con{be32(h), be32s(t)} def value.of(r: Bytes.Bytes & U32) -> Bytes.Bytes: (b, +n) = r Bytes.append(be32(n), b) # An int32 length then the bytes; -1 and nothing for NULL. def value(m: Maybe<&1, Bytes.Bytes>) -> Bytes.Bytes: match m: case None{}: be32(4294967295) case Some{b}: value.of(Bytes.length(b)) def values(xs: List<&1, Maybe<&1, Bytes.Bytes>>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: Nil{} case Con{h, t}: Con{value(h), values(t)} def params.enc(xs: List<&1, Param>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: [byte1(0)] case Con{Param{n, v}, t}: Con{Bytes.append(cstr.enc(n), cstr.enc(v)), params.enc(t)} def names.enc(xs: List<&1, Bytes.Bytes>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: [byte1(0)] case Con{h, t}: Con{cstr.enc(h), names.enc(t)} def notices.enc(xs: List<&1, Notice>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: [byte1(0)] case Con{Notice{+k, v}, t}: Con{Bytes.append(byte1(k), cstr.enc(v)), notices.enc(t)} def fields.enc(xs: List<&1, Field>) -> List<&1, Bytes.Bytes>: match xs: case Nil{}: Nil{} case Con{Field{n, +tb, +col, +oid, +sz, +md, +fmt}, t}: Con{Bytes.concat([cstr.enc(n), be32(tb), be16(col), be32(oid), be16(sz), be32(md), be16(fmt)]), fields.enc(t)} # The wire bytes of a frontend message. def encode(m: Front) -> Bytes.Bytes: match m: case Startup{ps}: packet(Bytes.concat(Con{be32(196608), params.enc(ps)})) case SSLRequest{}: packet(be32(80877103)) case Password{t}: frame(112, cstr.enc(t)) case SASLInitial{mech, d}: frame(112, Bytes.append(cstr.enc(mech), value(Some{d}))) case SASLResponse{d}: frame(112, d) case Parse{n, q, oids}: frame(80, Bytes.concat([cstr.enc(n), cstr.enc(q), n16(be32s(oids))])) case Bind{p, s, fs, ps, rs}: frame(66, Bytes.concat([cstr.enc(p), cstr.enc(s), n16(be16s(fs)), n16(values(ps)), n16(be16s(rs))])) case Describe{+k, n}: frame(68, Bytes.append(byte1(k), cstr.enc(n))) case Execute{p, +max}: frame(69, Bytes.append(cstr.enc(p), be32(max))) case Sync{}: frame(83, Bytes.new(0)) case CloseMsg{+k, n}: frame(67, Bytes.append(byte1(k), cstr.enc(n))) case Terminate{}: frame(88, Bytes.new(0)) def auth.enc(+code: U32, payload: Bytes.Bytes) -> Bytes.Bytes: frame(82, Bytes.append(be32(code), payload)) # The wire bytes of a backend message, as a server sends it. def encode.back(m: Back) -> Bytes.Bytes: match m: case AuthOk{}: auth.enc(0, Bytes.new(0)) case AuthCleartext{}: auth.enc(3, Bytes.new(0)) case AuthSASL{ms}: auth.enc(10, Bytes.concat(names.enc(ms))) case AuthSASLContinue{d}: auth.enc(11, d) case AuthSASLFinal{d}: auth.enc(12, d) case AuthOther{+code, d}: auth.enc(code, d) case ParameterStatus{n, v}: frame(83, Bytes.append(cstr.enc(n), cstr.enc(v))) case BackendKeyData{+pid, +key}: frame(75, Bytes.append(be32(pid), be32(key))) case ReadyForQuery{+s}: frame(90, byte1(s)) case RowDescription{fs}: frame(84, n16(fields.enc(fs))) case DataRow{cs}: frame(68, n16(values(cs))) case CommandComplete{t}: frame(67, cstr.enc(t)) case ErrorResponse{fs}: frame(69, Bytes.concat(notices.enc(fs))) case NoticeResponse{fs}: frame(78, Bytes.concat(notices.enc(fs))) case ParseComplete{}: frame(49, Bytes.new(0)) case BindComplete{}: frame(50, Bytes.new(0)) case CloseComplete{}: frame(51, Bytes.new(0)) case NoData{}: frame(110, Bytes.new(0)) case EmptyQuery{}: frame(73, Bytes.new(0)) case PortalSuspended{}: frame(115, Bytes.new(0)) case Other{+tag, b}: frame(tag, b) # ---- body cursor # The buffer, the next byte i, the end e of the message body (e <= len), and whether every read so far fit. # A read that does not fit clears ok and leaves i; the message is Bad unless ok holds and i reaches e. type Cur is Type: Cur{b: Bytes.Bytes, i: U32, e: U32, ok: Bool} def fail(c: Cur) -> Cur: Cur{b, +i, +e, ok} = c Cur{b, i, e, False{}} def need(ok: Bool, c: Cur) -> Cur: match ok: case True{}: c case False{}: fail(c) def left(c: Cur) -> Cur & Nat: Cur{b, +i, +e, +ok} = c (Cur{b, i, e, ok}, U32.to_nat((e - i : U32))) def uint.got(+i: U32, +n: U32, +e: U32, +ok: Bool, r: Bytes.Bytes & Maybe<&2, U32>) -> Cur & U32: (b, m) = r match m: case Some{+v}: (Cur{b, (i + n : U32), e, ok}, v) case None{}: (Cur{b, i, e, False{}}, 0) def uint.at(fit: Bool, b: Bytes.Bytes, +i: U32, +n: U32, +e: U32, +ok: Bool) -> Cur & U32: match fit: case True{}: uint.got(i, n, e, ok, Bytes.uint(b, i, n, True{})) case False{}: (Cur{b, i, e, False{}}, 0) # A big-endian unsigned integer of n bytes (1, 2 or 4). def uint(c: Cur, +n: U32) -> Cur & U32: Cur{b, +i, +e, +ok} = c uint.at(Bytes.fits(e, i, n), b, i, n, e, ok) def take.got(+i: U32, +e: U32, +ok: Bool, r: Bytes.Bytes & Bytes.Bytes) -> Cur & Bytes.Bytes: (b, s) = r (Cur{b, i, e, ok}, s) def take.at(fit: Bool, b: Bytes.Bytes, +i: U32, +n: U32, +e: U32, +ok: Bool) -> Cur & Bytes.Bytes: match fit: case True{}: take.got((i + n : U32), e, ok, Bytes.slice(b, i, n)) case False{}: (Cur{b, i, e, False{}}, Bytes.new(0)) # The next n bytes, copied out. def take(c: Cur, +n: U32) -> Cur & Bytes.Bytes: Cur{b, +i, +e, +ok} = c take.at(Bytes.fits(e, i, n), b, i, n, e, ok) def rest(c: Cur) -> Cur & Bytes.Bytes: Cur{b, +i, +e, +ok} = c take(Cur{b, i, e, ok}, (e - i : U32)) def nul() -> String: SCon{Chr{0}, SNil{}} def cstr.in(inside: Bool, b: Bytes.Bytes, +i: U32, +j: U32, +e: U32, +ok: Bool) -> Cur & Bytes.Bytes: match inside: case True{}: take.got((j + 1 : U32), e, ok, Bytes.slice(b, i, (j - i : U32))) case False{}: (Cur{b, i, e, False{}}, Bytes.new(0)) def cstr.at(+i: U32, +e: U32, +ok: Bool, r: Bytes.Bytes & Maybe<&2, U32>) -> Cur & Bytes.Bytes: (b, m) = r match m: case Some{+j}: cstr.in(U32.is_lt(j, e), b, i, j, e, ok) case None{}: (Cur{b, i, e, False{}}, Bytes.new(0)) # The bytes before the next NUL inside the body, and the cursor past it. def cstr(c: Cur) -> Cur & Bytes.Bytes: Cur{b, +i, +e, +ok} = c cstr.at(i, e, ok, Bytes.find.from(b, nul(), i)) def u16s(c: Cur) -> Cur & U32: uint(c, 2) def u32s(c: Cur) -> Cur & U32: uint(c, 4) # n more items after the one in r, oldest last in acc. def many(~A: Type, ~item: Cur -> Cur & A, n: Nat, r: Cur & A, acc: List<&1, A>) -> Cur & List<&1, A>: match n: case 0n: (c, v) = r (c, List.reverse(&1, A, Con{v, acc})) case 1n+p: (c, v) = r many(~A, ~item, p, item(c), Con{v, acc}) def n16.go(~A: Type, ~item: Cur -> Cur & A, zero: Bool, +k: U32, c: Cur) -> Cur & List<&1, A>: match zero: case True{}: (c, Nil{}) case False{}: many(~A, ~item, U32.to_nat((k - 1 : U32)), item(c), Nil{}) # An int16 count, then that many items. def n16.dec(~A: Type, ~item: Cur -> Cur & A, r: Cur & U32) -> Cur & List<&1, A>: (c, +k) = r n16.go(~A, ~item, U32.is_eq(k, 0), k, c) def list16(~A: Type, ~item: Cur -> Cur & A, c: Cur) -> Cur & List<&1, A>: n16.dec(~A, ~item, uint(c, 2)) def dup(xs: List<&1, U32>) -> List<&2, U32>: match xs: case Nil{}: Nil{} case Con{+h, t}: Con{h, dup(t)} def col.some(r: Cur & Bytes.Bytes) -> Cur & Maybe<&1, Bytes.Bytes>: (c, b) = r (c, Some{b}) def col.of(null: Bool, +n: U32, c: Cur) -> Cur & Maybe<&1, Bytes.Bytes>: match null: case True{}: (c, None{}) case False{}: col.some(take(c, n)) def col.len(r: Cur & U32) -> Cur & Maybe<&1, Bytes.Bytes>: (c, +n) = r col.of(U32.is_eq(n, 4294967295), n, c) # An int32 length then the bytes; -1 is NULL, and any other negative length does not fit. def col(c: Cur) -> Cur & Maybe<&1, Bytes.Bytes>: col.len(uint(c, 4)) # A loop up to a terminator: Go holds an item still to push, Stop the end. acc is reversed. type Loop<-A: Type, -B: Type> is Type: Go{c: Cur, x: A, acc: List<&1, B>} Stop{c: Cur, acc: List<&1, B>} # Out of fuel before the terminator is Bad. def loop.end(-A: Type, -B: Type, s: Loop) -> Cur & List<&1, B>: match s: case Go{c, x, acc}: (fail(c), List.reverse(&1, B, acc)) case Stop{c, acc}: (c, List.reverse(&1, B, acc)) def loop.pick(-A: Type, -B: Type, stop: Bool, c: Cur, x: A, acc: List<&1, B>) -> Loop: match stop: case True{}: Stop{c, acc} case False{}: Go{c, x, acc} def strs.len(c: Cur, acc: List<&1, Bytes.Bytes>, r: Bytes.Bytes & U32) -> Loop: (m, +n) = r loop.pick(Bytes.Bytes, Bytes.Bytes, U32.is_eq(n, 0), c, m, acc) def strs.read(acc: List<&1, Bytes.Bytes>, r: Cur & Bytes.Bytes) -> Loop: (c, m) = r strs.len(c, acc, Bytes.length(m)) # Each string takes a byte, so fuel of the bytes left is enough. def strs(f: Nat, s: Loop) -> Cur & List<&1, Bytes.Bytes>: match f: case 0n: loop.end(Bytes.Bytes, Bytes.Bytes, s) case 1n+p: match s: case Go{c, m, acc}: strs(p, strs.read(Con{m, acc}, cstr(c))) case Stop{c, acc}: (c, List.reverse(&1, Bytes.Bytes, acc)) def strs.start(r: Cur & Nat) -> Cur & List<&1, Bytes.Bytes>: (c, f) = r strs(f, strs.read(Nil{}, cstr(c))) # NUL-terminated strings up to an empty one. def names.dec(c: Cur) -> Cur & List<&1, Bytes.Bytes>: strs.start(left(c)) def pr.len(c: Cur, acc: List<&1, Param>, r: Bytes.Bytes & U32) -> Loop: (m, +n) = r loop.pick(Bytes.Bytes, Param, U32.is_eq(n, 0), c, m, acc) def pr.read(acc: List<&1, Param>, r: Cur & Bytes.Bytes) -> Loop: (c, m) = r pr.len(c, acc, Bytes.length(m)) def pr.val(name: Bytes.Bytes, acc: List<&1, Param>, r: Cur & Bytes.Bytes) -> Loop: (c, v) = r pr.read(Con{Param{name, v}, acc}, cstr(c)) # Name and value pairs up to an empty name; a value may be empty. Each pair takes two bytes. def prs(f: Nat, s: Loop) -> Cur & List<&1, Param>: match f: case 0n: loop.end(Bytes.Bytes, Param, s) case 1n+p: match s: case Go{c, name, acc}: prs(p, pr.val(name, acc, cstr(c))) case Stop{c, acc}: (c, List.reverse(&1, Param, acc)) def prs.start(r: Cur & Nat) -> Cur & List<&1, Param>: (c, f) = r prs(f, pr.read(Nil{}, cstr(c))) def params.dec(c: Cur) -> Cur & List<&1, Param>: prs.start(left(c)) def notice.code(acc: List<&1, Notice>, r: Cur & U32) -> Loop: (c, +k) = r loop.pick(U32, Notice, U32.is_eq(k, 0), c, k, acc) def notice.push(+code: U32, acc: List<&1, Notice>, r: Cur & Bytes.Bytes) -> Loop: (c, v) = r notice.code(Con{Notice{code, v}, acc}, uint(c, 1)) # Fields up to a zero code byte; each takes two bytes, so the bytes left are enough fuel. def notices(f: Nat, s: Loop) -> Cur & List<&1, Notice>: match f: case 0n: loop.end(U32, Notice, s) case 1n+p: match s: case Go{c, +code, acc}: notices(p, notice.push(code, acc, cstr(c))) case Stop{c, acc}: (c, List.reverse(&1, Notice, acc)) def notices.start(r: Cur & Nat) -> Cur & List<&1, Notice>: (c, f) = r notices(f, notice.code(Nil{}, uint(c, 1))) def notices.dec(c: Cur) -> Cur & List<&1, Notice>: notices.start(left(c)) def fd.g(name: Bytes.Bytes, +tb: U32, +col: U32, +oid: U32, +sz: U32, +md: U32, r: Cur & U32) -> Cur & Field: (c, +fmt) = r (c, Field{name, tb, col, oid, sz, md, fmt}) def fd.f(name: Bytes.Bytes, +tb: U32, +col: U32, +oid: U32, +sz: U32, r: Cur & U32) -> Cur & Field: (c, +md) = r fd.g(name, tb, col, oid, sz, md, uint(c, 2)) def fd.e(name: Bytes.Bytes, +tb: U32, +col: U32, +oid: U32, r: Cur & U32) -> Cur & Field: (c, +sz) = r fd.f(name, tb, col, oid, sz, uint(c, 4)) def fd.d(name: Bytes.Bytes, +tb: U32, +col: U32, r: Cur & U32) -> Cur & Field: (c, +oid) = r fd.e(name, tb, col, oid, uint(c, 2)) def fd.c(name: Bytes.Bytes, +tb: U32, r: Cur & U32) -> Cur & Field: (c, +col) = r fd.d(name, tb, col, uint(c, 4)) def fd.b(name: Bytes.Bytes, r: Cur & U32) -> Cur & Field: (c, +tb) = r fd.c(name, tb, uint(c, 2)) def fd.a(r: Cur & Bytes.Bytes) -> Cur & Field: (c, name) = r fd.b(name, uint(c, 4)) def field(c: Cur) -> Cur & Field: fd.a(cstr(c)) # ---- backend bodies def one(-A: Type, f: A -> Back, r: Cur & A) -> Cur & Back: (c, x) = r (c, f(x)) def ps.b(name: Bytes.Bytes, r: Cur & Bytes.Bytes) -> Cur & Back: (c, v) = r (c, ParameterStatus{name, v}) def ps.a(r: Cur & Bytes.Bytes) -> Cur & Back: (c, n) = r ps.b(n, cstr(c)) def bkd.b(+pid: U32, r: Cur & U32) -> Cur & Back: (c, +k) = r (c, BackendKeyData{pid, k}) def bkd.a(r: Cur & U32) -> Cur & Back: (c, +p) = r bkd.b(p, uint(c, 4)) def rfq(r: Cur & U32) -> Cur & Back: (c, +s) = r (need(Bool.or(U32.is_eq(s, 73), Bool.or(U32.is_eq(s, 84), U32.is_eq(s, 69))), c), ReadyForQuery{s}) def auth.data(k: U32, +code: U32, r: Cur & Bytes.Bytes) -> Cur & Back: match k: case 11: (c, d) = r (c, AuthSASLContinue{d}) case 12: (c, d) = r (c, AuthSASLFinal{d}) case _: (c, d) = r (c, AuthOther{code, d}) def auth.of(k: U32, +code: U32, c: Cur) -> Cur & Back: match k: case 0: (c, AuthOk{}) case 3: (c, AuthCleartext{}) case 10: one(List<&1, Bytes.Bytes>, xs => AuthSASL{xs}, names.dec(c)) case _: auth.data(code, code, rest(c)) def auth(r: Cur & U32) -> Cur & Back: (c, +code) = r auth.of(code, code, c) def back(t: U32, +tag: U32, c: Cur) -> Cur & Back: match t: case 82: auth(uint(c, 4)) case 83: ps.a(cstr(c)) case 75: bkd.a(uint(c, 4)) case 90: rfq(uint(c, 1)) case 84: one(List<&1, Field>, xs => RowDescription{xs}, list16(~Field, ~field, c)) case 68: one(List<&1, Maybe<&1, Bytes.Bytes>>, xs => DataRow{xs}, list16(~Maybe<&1, Bytes.Bytes>, ~col, c)) case 67: one(Bytes.Bytes, x => CommandComplete{x}, cstr(c)) case 69: one(List<&1, Notice>, xs => ErrorResponse{xs}, notices.dec(c)) case 78: one(List<&1, Notice>, xs => NoticeResponse{xs}, notices.dec(c)) case 49: (c, ParseComplete{}) case 50: (c, BindComplete{}) case 51: (c, CloseComplete{}) case 110: (c, NoData{}) case 73: (c, EmptyQuery{}) case 115: (c, PortalSuspended{}) case _: one(Bytes.Bytes, b => Other{tag, b}, rest(c)) # ---- incremental reader # Bytes read from the server, and the index of the first byte not yet decoded. type Reader is Type: Reader{buf: Bytes.Bytes, pos: U32} def reader() -> Reader: Reader{Bytes.new(0), 0} def feed.at(zero: Bool, buf: Bytes.Bytes, +pos: U32, chunk: Bytes.Bytes) -> Reader: match zero: case True{}: Reader{Bytes.append(buf, chunk), 0} case False{}: Reader{Bytes.append(Bytes.snd(Bytes.slice(buf, pos, 4294967295)), chunk), 0} # Drops the decoded bytes, then adds chunk. def feed(r: Reader, chunk: Bytes.Bytes) -> Reader: Reader{buf, +pos} = r feed.at(U32.is_zero(pos), buf, pos, chunk) # One message; Want until it has all arrived; Bad for a length below 4 or above 1 GiB, # or a body its tag does not fill exactly. After Bad the stream has lost its framing. type Next is Type: Got{m: Back} Want{} Bad{} def next.done(ok: Bool, +pos: U32, +e: U32, buf: Bytes.Bytes, m: Back) -> Reader & Next: match ok: case True{}: (Reader{buf, e}, Got{m}) case False{}: (Reader{buf, pos}, Bad{}) def next.body(+pos: U32, r: Cur & Back) -> Reader & Next: (c, m) = r Cur{buf, +i, +e, +ok} = c next.done(Bool.and(ok, U32.is_eq(i, e)), pos, e, buf, m) def next.whole(ok: Bool, +t: U32, +pos: U32, +n: U32, buf: Bytes.Bytes) -> Reader & Next: match ok: case True{}: next.body(pos, back(t, t, Cur{buf, (pos + 5 : U32), (pos + 1 + n : U32), True{}})) case False{}: (Reader{buf, pos}, Want{}) def next.size(ok: Bool, +t: U32, +pos: U32, +len: U32, +n: U32, buf: Bytes.Bytes) -> Reader & Next: match ok: case True{}: next.whole(Bytes.fits(len, (pos + 1 : U32), n), t, pos, n, buf) case False{}: (Reader{buf, pos}, Bad{}) def next.n(+t: U32, +pos: U32, +len: U32, r: Cur & U32) -> Reader & Next: (c, +n) = r Cur{buf, i, e, ok} = c next.size(Bool.and(U32.is_le(4, n), U32.is_le(n, 1073741824)), t, pos, len, n, buf) def next.tag(+pos: U32, +len: U32, r: Cur & U32) -> Reader & Next: (c, +t) = r next.n(t, pos, len, uint(c, 4)) def next.room(ok: Bool, +pos: U32, +len: U32, buf: Bytes.Bytes) -> Reader & Next: match ok: case True{}: next.tag(pos, len, uint(Cur{buf, pos, len, True{}}, 1)) case False{}: (Reader{buf, pos}, Want{}) def next.len(+pos: U32, r: Bytes.Bytes & U32) -> Reader & Next: (buf, +len) = r next.room(Bytes.fits(len, pos, 5), pos, len, buf) # The next backend message. def next(r: Reader) -> Reader & Next: Reader{buf, +pos} = r next.len(pos, Bytes.length(buf)) def pending.of(+pos: U32, r: Bytes.Bytes & U32) -> Reader & U32: (b, +len) = r (Reader{b, pos}, (len - pos : U32)) # The count of bytes read but not yet decoded. def pending(r: Reader) -> Reader & U32: Reader{buf, +pos} = r pending.of(pos, Bytes.length(buf)) # ---- frontend decoder, for tests and mock servers def one.f(-A: Type, f: A -> Front, r: Cur & A) -> Cur & Front: (c, x) = r (c, f(x)) def fe.sasl(mech: Bytes.Bytes, r: Cur & Maybe<&1, Bytes.Bytes>) -> Cur & Front: (c, m) = r match m: case Some{d}: (c, SASLInitial{mech, d}) case None{}: (fail(c), SASLInitial{mech, Bytes.new(0)}) def fe.sasl.a(r: Cur & Bytes.Bytes) -> Cur & Front: (c, mech) = r fe.sasl(mech, col(c)) def fe.pass(k: U32, c: Cur) -> Cur & Front: match k: case 1: one.f(Bytes.Bytes, t => Password{t}, cstr(c)) case 2: fe.sasl.a(cstr(c)) case _: one.f(Bytes.Bytes, d => SASLResponse{d}, rest(c)) def fe.parse.c(name: Bytes.Bytes, q: Bytes.Bytes, r: Cur & List<&1, U32>) -> Cur & Front: (c, oids) = r (c, Parse{name, q, dup(oids)}) def fe.parse.b(name: Bytes.Bytes, r: Cur & Bytes.Bytes) -> Cur & Front: (c, q) = r fe.parse.c(name, q, list16(~U32, ~u32s, c)) def fe.parse.a(r: Cur & Bytes.Bytes) -> Cur & Front: (c, name) = r fe.parse.b(name, cstr(c)) def fe.bind.e(p: Bytes.Bytes, s: Bytes.Bytes, fs: List<&1, U32>, ps: List<&1, Maybe<&1, Bytes.Bytes>>, r: Cur & List<&1, U32>) -> Cur & Front: (c, rs) = r (c, Bind{p, s, dup(fs), ps, dup(rs)}) def fe.bind.d(p: Bytes.Bytes, s: Bytes.Bytes, fs: List<&1, U32>, r: Cur & List<&1, Maybe<&1, Bytes.Bytes>>) -> Cur & Front: (c, ps) = r fe.bind.e(p, s, fs, ps, list16(~U32, ~u16s, c)) def fe.bind.c(p: Bytes.Bytes, s: Bytes.Bytes, r: Cur & List<&1, U32>) -> Cur & Front: (c, fs) = r fe.bind.d(p, s, fs, list16(~Maybe<&1, Bytes.Bytes>, ~col, c)) def fe.bind.b(p: Bytes.Bytes, r: Cur & Bytes.Bytes) -> Cur & Front: (c, s) = r fe.bind.c(p, s, list16(~U32, ~u16s, c)) def fe.bind.a(r: Cur & Bytes.Bytes) -> Cur & Front: (c, p) = r fe.bind.b(p, cstr(c)) def fe.kind(close: Bool, +k: U32, r: Cur & Bytes.Bytes) -> Cur & Front: match close: case True{}: (c, n) = r (need(Bool.or(U32.is_eq(k, 83), U32.is_eq(k, 80)), c), CloseMsg{k, n}) case False{}: (c, n) = r (need(Bool.or(U32.is_eq(k, 83), U32.is_eq(k, 80)), c), Describe{k, n}) def fe.kind.a(+close: Bool, r: Cur & U32) -> Cur & Front: (c, +k) = r fe.kind(close, k, cstr(c)) def fe.exec(p: Bytes.Bytes, r: Cur & U32) -> Cur & Front: (c, +max) = r (c, Execute{p, max}) def fe.exec.a(r: Cur & Bytes.Bytes) -> Cur & Front: (c, p) = r fe.exec(p, uint(c, 4)) def fe.tagged(t: U32, +mode: U32, c: Cur) -> Cur & Front: match t: case 112: fe.pass(mode, c) case 80: fe.parse.a(cstr(c)) case 66: fe.bind.a(cstr(c)) case 68: fe.kind.a(False{}, uint(c, 1)) case 69: fe.exec.a(cstr(c)) case 83: (c, Sync{}) case 67: fe.kind.a(True{}, uint(c, 1)) case 88: (c, Terminate{}) case _: (fail(c), Sync{}) def fe.len(+t: U32, +mode: U32, r: Cur & U32) -> Cur & Front: (c, +n) = r Cur{b, +i, +e, +ok} = c fe.tagged(t, mode, Cur{b, i, e, Bool.and(ok, U32.is_eq((n + 1 : U32), e))}) def fe.tag(+mode: U32, r: Cur & U32) -> Cur & Front: (c, +t) = r fe.len(t, mode, uint(c, 4)) def fe.pkt(k: U32, c: Cur) -> Cur & Front: match k: case 80877103: (c, SSLRequest{}) case 196608: one.f(List<&1, Param>, ps => Startup{ps}, params.dec(c)) case _: (fail(c), SSLRequest{}) def fe.code(r: Cur & U32) -> Cur & Front: (c, +k) = r fe.pkt(k, c) def fe.plen(r: Cur & U32) -> Cur & Front: (c, +n) = r Cur{b, +i, +e, +ok} = c fe.code(uint(Cur{b, i, e, Bool.and(ok, U32.is_eq(n, e))}, 4)) def fe.start(k: U32, +mode: U32, c: Cur) -> Cur & Front: match k: case 0: fe.plen(uint(c, 4)) case _: fe.tag(mode, uint(c, 1)) def fe.fin(r: Cur & Front) -> Maybe<&1, Front>: (c, m) = r Cur{b, +i, +e, ok} = c match ok: case True{}: Bool.pick(Maybe<&1, Front>, U32.is_eq(i, e), Some{m}, None{}) case False{}: None{} # One frontend message that fills b exactly, or None. The protocol leaves the phase to context: # mode 0 reads an untagged startup packet, and 'p' reads as Password (1), SASLInitial (2) or SASLResponse (3). def decode.front(+mode: U32, b: Bytes.Bytes) -> Maybe<&1, Front>: Bytes.Bytes{+len, buf} = b fe.fin(fe.start(mode, mode, Cur{Bytes.Bytes{len, buf}, 0, len, True{}}))