src/base64.bend

bend-kube-runtime@0.2.0.0 · 125 lines · 13 definitions

1import Base
2
3# Base64
4# ======
5#
6# The standard alphabet (RFC 4648 section 4), as kubeconfig's *-data fields
7# and Secret data use it. Decoding skips whitespace and padding; a char
8# outside the alphabet fails the whole decode.
9
10type B64 is Data:
11  B64{acc: U32, bits: U32, ok: Bool}
12
13def B64.val.go(+x: U32, up: Bool, low: Bool, dig: Bool) -> U32:
14  match up low dig:
15    case True{} _ _:
16      (x - 65 : U32)
17    case False{} True{} _:
18      (x - 71 : U32)
19    case False{} False{} True{}:
20      (x + 4 : U32)
21    case _ _ _:
22      65
23
24# The 6-bit value of a char: 64 for skipped chars, 65 for bad ones.
25def B64.val(+c: Char) -> U32:
26  match c:
27    case '+':
28      62
29    case '/':
30      63
31    case '=':
32      64
33    case ' ':
34      64
35    case '\n':
36      64
37    case '\r':
38      64
39    case '\t':
40      64
41    case Chr{+x}:
42      B64.val.go(x, Char.is_upper(Chr{x}), Char.is_lower(Chr{x}), Char.is_digit(Chr{x}))
43
44def B64.next.go(+acc: U32, +bits: U32, ok: Bool, +v: U32, skip: Bool, bad: Bool, full: Bool) ->
45  B64:
46  match skip bad full:
47    case True{} _ _:
48      B64{acc, bits, ok}
49    case False{} True{} _:
50      B64{acc, bits, False{}}
51    case False{} False{} True{}:
52      # 8 bits are ready: the byte leaves, bits - 2 stay
53      B64{(((acc << 6n) .|. v) .&. ((1 << U32.to_nat((bits - 2 : U32))) - 1) : U32),
54        (bits - 2 : U32), ok}
55    case False{} False{} False{}:
56      B64{((acc << 6n) .|. v : U32), (bits + 6 : U32), ok}
57
58# The state after a char's value v.
59def B64.next(st: B64, +v: U32) -> B64:
60  match st:
61    case B64{+acc, +bits, ok}:
62      B64.next.go(acc, bits, ok, v, U32.is_eq(v, 64), U32.is_eq(v, 65), U32.is_ge(bits, 2))
63
64def B64.emit.go(+acc: U32, +bits: U32, +v: U32, out: List<&2, U32>, skip: Bool, full: Bool) ->
65  List<&2, U32>:
66  match skip full:
67    case False{} True{}:
68      ((((acc << 6n) .|. v) >> U32.to_nat((bits - 2 : U32))) .&. 255 : U32) <> out
69    case _ _:
70      out
71
72# The byte a char completes, pushed onto out (reversed).
73def B64.emit(st: B64, +v: U32, out: List<&2, U32>) -> List<&2, U32>:
74  match st:
75    case B64{+acc, +bits, ok}:
76      B64.emit.go(acc, bits, v, out, U32.is_ge(v, 64), U32.is_ge(bits, 2))
77
78def B64.go(s: String, +st: B64, out: List<&2, U32>) -> B64 & List<&2, U32>:
79  match s:
80    case SNil{}:
81      (st, out)
82    case SCon{+c, t}:
83      B64.go(t, B64.next(st, B64.val(c)), B64.emit(st, B64.val(c), out))
84
85def B64.decode.fin(r: B64 & List<&2, U32>) -> Maybe<&2, List<&2, U32>>:
86  match r:
87    case (B64{acc, bits, True{}}, out):
88      Some{List.reverse(&2, U32, out)}
89    case _:
90      None{}
91
92# The bytes a base64 text spells; None if a char is outside the alphabet.
93def B64.decode(s: String) -> Maybe<&2, List<&2, U32>>:
94  B64.decode.fin(B64.go(s, B64{0, 0, True{}}, Nil{}))
95
96def B64.chr.go(+v: U32, up: Bool, low: Bool, dig: Bool, plus: Bool) -> Char:
97  match up low dig plus:
98    case True{} _ _ _:
99      Chr{(v + 65 : U32)}
100    case False{} True{} _ _:
101      Chr{(v + 71 : U32)}
102    case False{} False{} True{} _:
103      Chr{(v - 4 : U32)}
104    case False{} False{} False{} True{}:
105      '+'
106    case _ _ _ _:
107      '/'
108
109def B64.chr(+v: U32) -> Char:
110  B64.chr.go(v, U32.is_lt(v, 26), U32.is_lt(v, 52), U32.is_lt(v, 62), U32.is_eq(v, 62))
111
112# Encodes bytes, three at a time, with '=' padding.
113def B64.encode(bs: List<&2, U32>) -> String:
114  match bs:
115    case Nil{}:
116      ""
117    case Con{+a, Nil{}}:
118      SCon{B64.chr((a >> 2n : U32)), SCon{B64.chr(((a .&. 3) << 4n : U32)), "=="}}
119    case Con{+a, Con{+b, Nil{}}}:
120      SCon{B64.chr((a >> 2n : U32)), SCon{B64.chr((((a .&. 3) << 4n) .|. (b >> 4n) : U32)),
121        SCon{B64.chr(((b .&. 15) << 2n : U32)), "="}}}
122    case Con{+a, Con{+b, Con{+c, t}}}:
123      SCon{B64.chr((a >> 2n : U32)), SCon{B64.chr((((a .&. 3) << 4n) .|. (b >> 4n) : U32)),
124        SCon{B64.chr((((b .&. 15) << 2n) .|. (c >> 6n) : U32)), SCon{B64.chr((c .&. 63 : U32)),
125          B64.encode(t)}}}}