1 import 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 10 type B64 is Data: 11 B64{acc: U32, bits: U32, ok: Bool} 12 13 def 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. 25 def 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 44 def 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. 59 def 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 64 def 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). 73 def 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 78 def 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 85 def 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. 93 def B64.decode(s: String) -> Maybe<&2, List<&2, U32>>: 94 B64.decode.fin(B64.go(s, B64{0, 0, True{}}, Nil{})) 95 96 def 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 109 def 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. 113 def 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)}}}}