1 import Base 2 import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes 3 4 # State for the strict single-pass UTF-8 decoder. 5 type Utf8State is Data: 6 Utf8State{need: U32, lo: U32, hi: U32, cp: U32, out: String, valid: Bool} 7 8 def utf8.lead(+byte: U32, +out: String) -> Utf8State: 9 Utf8State{ 10 Bool.pick(U32, 11 U32.is_lt(byte, 128), 0, 12 Bool.pick(U32, U32.is_le(194, byte) && U32.is_le(byte, 223), 1, 13 Bool.pick(U32, U32.is_eq(byte, 224) || U32.is_le(225, byte) && U32.is_le(byte, 236) || U32.is_le(238, byte) && U32.is_le(byte, 239) || U32.is_eq(byte, 237), 2, 14 Bool.pick(U32, U32.is_le(240, byte) && U32.is_le(byte, 244), 3, 0)))), 15 Bool.pick(U32, U32.is_eq(byte, 224), 160, 16 Bool.pick(U32, U32.is_eq(byte, 240), 144, 128)), 17 Bool.pick(U32, U32.is_eq(byte, 237), 159, 18 Bool.pick(U32, U32.is_eq(byte, 244), 143, 191)), 19 Bool.pick(U32, 20 U32.is_lt(byte, 128), byte, 21 Bool.pick(U32, U32.is_le(194, byte) && U32.is_le(byte, 223), U32.and(byte, 31), 22 Bool.pick(U32, U32.is_le(224, byte) && U32.is_le(byte, 239), U32.and(byte, 15), 23 Bool.pick(U32, U32.is_le(240, byte) && U32.is_le(byte, 244), U32.and(byte, 7), 0)))), 24 Bool.pick(String, U32.is_lt(byte, 128), SCon{Chr{byte}, out}, out), 25 U32.is_lt(byte, 128) || U32.is_le(194, byte) && U32.is_le(byte, 244) 26 } 27 28 def utf8.cont(+need: U32, cp: U32, +out: String, ok: Bool, in_range: Bool, byte: U32) -> Utf8State: 29 match in_range: 30 case True{}: 31 +next_cp = (cp * 64 + U32.and(byte, 63) : U32) 32 Utf8State{(need - 1 : U32), 128, 191, next_cp, 33 Bool.pick(String, U32.is_eq(need, 1), SCon{Chr{next_cp}, out}, out), ok} 34 case False{}: 35 Utf8State{0, 128, 191, 0, out, False{}} 36 37 def utf8.step(state: Utf8State, +byte: U32) -> Utf8State: 38 match state: 39 case Utf8State{0, _, _, _, out, True{}}: 40 utf8.lead(byte, out) 41 case Utf8State{0, _, _, _, out, False{}}: 42 Utf8State{0, 128, 191, 0, out, False{}} 43 case Utf8State{need, lo, hi, cp, out, ok}: 44 utf8.cont(need, cp, out, ok, U32.is_le(lo, byte) && U32.is_le(byte, hi), byte) 45 46 def utf8.scan.fin.done(out: String, complete: Bool) -> Result<&1, &1, U32 & String, String>: 47 match complete: 48 case True{}: 49 Done{String.reverse(out)} 50 case False{}: 51 Fail{(1, "invalid UTF-8")} 52 53 def utf8.scan.fin(state: Utf8State) -> Result<&1, &1, U32 & String, String>: 54 match state: 55 case Utf8State{need, _, _, _, out, True{}}: 56 utf8.scan.fin.done(out, U32.is_eq(need, 0)) 57 case Utf8State{_, _, _, _, _, False{}}: 58 Fail{(1, "invalid UTF-8")} 59 60 def utf8.scan( 61 remaining: Nat, 62 +offset: U32, 63 pair: Array & U32, 64 state: Utf8State 65 ) -> Result<&1, &1, U32 & String, String>: 66 match remaining: 67 case 0n: 68 match pair: 69 case (_, _): 70 utf8.scan.fin(state) 71 case 1n+p: 72 match pair: 73 case (buf, b): 74 utf8.scan(p, (offset + 1 : U32), Bytes.peek(buf, (offset + 1 : U32)), utf8.step(state, b)) 75 76 # Return None rather than wrapping when U32 addition overflows. 77 def checked_add(+left: U32, +right: U32) -> Maybe<&2, U32>: 78 Bool.pick(Maybe<&2, U32>, U32.is_le(left, (4294967295 - right : U32)), Some{(left + right : U32)}, None{}) 79 80 # Normal target size for an encoded storage block or WAL frame. 81 def MAX_BLOCK_BYTES() -> U32: 82 1048576 83 84 # Size at which the SST encoder closes a block and starts the next one. 85 def TARGET_BLOCK_BYTES() -> U32: 86 4096 87 88 # Hard cap for an individual encoded key or value. 89 def MAX_RECORD_BYTES() -> U32: 90 1073741824 91 92 # Hard cap for the complete serialized Manifest. 93 def MAX_MANIFEST_BYTES() -> U32: 94 1048576 95 96 # Checks a record against the storage codec byte cap. 97 def record_bytes_allowed(+length: U32) -> Bool: 98 U32.is_le(length, MAX_RECORD_BYTES()) 99 100 def utf8.width(+scalar: U32) -> U32: 101 Bool.pick(U32, U32.is_le(scalar, 127), 1, 102 Bool.pick(U32, U32.is_le(scalar, 2047), 2, 103 Bool.pick(U32, U32.is_le(scalar, 65535), 3, 4))) 104 105 # Returns a string length as a storage byte count. 106 def string_size(+text: String) -> Nat: 107 match text: 108 case SNil{}: 109 0n 110 case SCon{Chr{scalar}, rest}: 111 Nat.add(U32.to_nat(utf8.width(scalar)), string_size(rest)) 112 113 def from_string.size(+text: String, +size: U32, fits: Bool) -> Maybe<&2, U32>: 114 match text fits: 115 case SNil{} _: 116 Some{size} 117 case SCon{Chr{_scalar}, SNil{}} False{}: 118 None{} 119 case SCon{Chr{scalar}, SNil{}} True{}: 120 Some{(size + utf8.width(scalar) : U32)} 121 case SCon{Chr{_scalar}, SCon{Chr{_next_scalar}, _rest}} False{}: 122 None{} 123 case SCon{Chr{scalar}, SCon{Chr{next_scalar}, rest}} True{}: 124 from_string.size(SCon{Chr{next_scalar}, rest}, (size + utf8.width(scalar) : U32), 125 U32.is_le((size + utf8.width(scalar) : U32), 126 (MAX_RECORD_BYTES() - utf8.width(next_scalar) : U32))) 127 128 def from_string.size.begin(+text: String) -> Maybe<&2, U32>: 129 match text: 130 case SNil{}: 131 Some{0} 132 case SCon{Chr{scalar}, _}: 133 from_string.size(text, 0, 134 U32.is_le(0, (MAX_RECORD_BYTES() - utf8.width(scalar) : U32))) 135 136 def utf8.put.result(pair: Bytes.Cursor & Bool) -> Bytes.Cursor: 137 match pair: 138 case (cursor, _): 139 cursor 140 141 def utf8.put.byte(cursor: Bytes.Cursor, +byte: U32) -> Bytes.Cursor: 142 utf8.put.result(Bytes.Cursor.put.u8(cursor, byte)) 143 144 def utf8.put.two(cursor: Bytes.Cursor, +scalar: U32) -> Bytes.Cursor: 145 utf8.put.byte(utf8.put.byte(cursor, (192 + U32.shrn(scalar, 6n) : U32)), 146 (128 + U32.and(scalar, 63) : U32)) 147 148 def utf8.put.three(cursor: Bytes.Cursor, +scalar: U32) -> Bytes.Cursor: 149 utf8.put.byte(utf8.put.byte( 150 utf8.put.byte(cursor, (224 + U32.shrn(scalar, 12n) : U32)), 151 (128 + U32.and(U32.shrn(scalar, 6n), 63) : U32)), 152 (128 + U32.and(scalar, 63) : U32)) 153 154 def utf8.put.four(cursor: Bytes.Cursor, +scalar: U32) -> Bytes.Cursor: 155 utf8.put.byte(utf8.put.byte( 156 utf8.put.byte(utf8.put.byte(cursor, (240 + U32.shrn(scalar, 18n) : U32)), 157 (128 + U32.and(U32.shrn(scalar, 12n), 63) : U32)), 158 (128 + U32.and(U32.shrn(scalar, 6n), 63) : U32)), 159 (128 + U32.and(scalar, 63) : U32)) 160 161 def utf8.put.scalar( 162 cursor: Bytes.Cursor, 163 +scalar: U32, 164 ascii: Bool, 165 two_byte: Bool, 166 three_byte: Bool 167 ) -> Bytes.Cursor: 168 match ascii two_byte three_byte: 169 case True{} _ _: 170 utf8.put.byte(cursor, scalar) 171 case False{} True{} _: 172 utf8.put.two(cursor, scalar) 173 case False{} False{} True{}: 174 utf8.put.three(cursor, scalar) 175 case False{} False{} False{}: 176 utf8.put.four(cursor, scalar) 177 178 def utf8.encode.go(text: String, cursor: Bytes.Cursor) -> Bytes.Cursor: 179 match text: 180 case SNil{}: 181 cursor 182 case SCon{Chr{+scalar}, tail}: 183 utf8.encode.go(tail, utf8.put.scalar(cursor, scalar, 184 U32.is_le(scalar, 127), U32.is_le(scalar, 2047), U32.is_le(scalar, 65535))) 185 186 def from_string.output.pair(result: Bytes.Bytes & U32) -> Bytes.Bytes: 187 match result: 188 case (bytes, _): 189 bytes 190 191 def from_string.output(+text: String, +len: U32) -> Bytes.Bytes: 192 from_string.output.pair(Bytes.Cursor.finish(utf8.encode.go(text, 193 Bytes.Cursor.new(Bytes.Bytes{len, Bytes.alloc(len)})))) 194 195 def from_string.preflight(+text: String, size: Maybe<&2, U32>) -> Result<&1, &1, U32 & String, Bytes.Bytes>: 196 match size: 197 case None{}: 198 Fail{(2, "record too large")} 199 case Some{len}: 200 Done{from_string.output(text, len)} 201 202 # Encode public text at the packed storage boundary. 203 def from_string(+text: String) -> Result<&1, &1, U32 & String, Bytes.Bytes>: 204 from_string.preflight(text, from_string.size.begin(text)) 205 206 # Decode bytes after strict RFC 3629 validation. 207 def decode_checked(bytes: Bytes.Bytes) -> Result<&1, &1, U32 & String, String>: 208 match bytes: 209 case Bytes.Bytes{len, buf}: 210 utf8.scan(U32.to_nat(len), 0, Bytes.peek(buf, 0), Utf8State{0, 128, 191, 0, SNil{}, True{}}) 211 212 # Decode a packed buffer, rejecting malformed UTF-8. 213 def to_string_strict(bytes: Bytes.Bytes) -> Result<&1, &1, U32 & String, String>: 214 decode_checked(bytes) 215 216 # Read one bounded big-endian u32 through a package cursor. 217 def read_u32be(cursor: Bytes.Cursor) -> Bytes.Cursor & Maybe<&2, U32>: 218 Bytes.Cursor.u32be(cursor) 219 220 # Write one bounded big-endian u32 through a package cursor. 221 def write_u32be(cursor: Bytes.Cursor, value: U32) -> Bytes.Cursor & Bool: 222 Bytes.Cursor.put.u32be(cursor, value)