///|
/// Binary codec primitives: varint integers, length-prefixed UTF-8 strings,
/// doubles, and CRC-32 (IEEE 802.3, same polynomial as zlib).
/// Little-endian fixed-width where noted. All pure MoonBit, portable to
/// every backend including wasm-gc and js.
///|
/// Append an unsigned varint (LEB128) to the buffer.
/// Every byte except the last has the continuation bit (0x80) set.
pub fn write_varint(buf : Array[Byte], value : Int32) -> Unit {
let mut v = value
while v >= 0x80 {
buf.push(((v & 0x7F) | 0x80).to_byte())
v = v.lsr(7)
}
buf.push(v.to_byte())
}
///|
/// Cursor over a byte slice for decoding.
pub struct Reader {
data : Bytes
mut pos : Int32
}
///|
pub fn Reader::new(data : Bytes) -> Reader {
{ data, pos: 0, }
}
///|
pub fn Reader::remaining(self : Reader) -> Int32 {
self.data.length() - self.pos
}
///|
/// Decoding errors (truncated or malformed data).
pub(all) suberror CodecError {
UnexpectedEof
VarintOverflow
InvalidUtf8
BadMagic
BadVersion(Int32)
CrcMismatch(UInt32, UInt32)
} derive(Eq, Debug)
///|
pub fn Reader::read_byte(self : Reader) -> Byte raise CodecError {
match self.data.get(self.pos) {
Some(b) => {
self.pos += 1
b
}
None => raise CodecError::UnexpectedEof
}
}
///|
pub fn Reader::read_varint(self : Reader) -> Int32 raise CodecError {
let mut result = 0
let mut shift = 0
while true {
let b = self.read_byte().to_int()
result = result | ((b & 0x7F) << shift)
if b < 0x80 {
break result
}
shift += 7
if shift >= 63 {
raise CodecError::VarintOverflow
}
} nobreak {
result
}
}
///|
pub fn write_string(buf : Array[Byte], s : String) -> Unit {
let bytes = @utf8.encode(s)
write_varint(buf, bytes.length())
for b in bytes.iter() {
buf.push(b)
}
}
///|
pub fn Reader::read_string(self : Reader) -> String raise CodecError {
let len = self.read_varint()
if len > self.remaining() {
raise CodecError::UnexpectedEof
}
let view = match self.data.get_view(start=self.pos, end=self.pos + len) {
Some(v) => v
None => raise CodecError::UnexpectedEof
}
self.pos += len
// Validate UTF-8 with the strict decoder.
let s = @utf8.decode(view) catch { _ => raise CodecError::InvalidUtf8 }
s
}
///|
pub fn write_double(buf : Array[Byte], value : Double) -> Unit {
let bits = value.reinterpret_as_uint64()
for i in 0..<8 {
buf.push(((bits >> (i * 8)) & 0xFFUL).to_int().to_byte())
}
}
///|
pub fn Reader::read_double(self : Reader) -> Double raise CodecError {
let mut bits : UInt64 = 0UL
for i in 0..<8 {
let b = self.read_byte().to_uint64()
bits = bits | (b << (i * 8))
}
bits.reinterpret_as_double()
}
///|
let crc32_table : FixedArray[UInt32] = {
let table = FixedArray::make(256, 0U)
for i in 0..<256 {
let mut c = i.to_uint()
for _ in 0..<8 {
c = if (c & 1U) == 1U { 0xEDB88320U ^ c.lsr(1) } else { c.lsr(1) }
}
table[i] = c
}
table
}
///|
/// CRC-32 (IEEE) of a byte slice.
pub fn crc32(data : Bytes) -> UInt32 {
let mut crc = 0xFFFFFFFFU
for b in data.iter() {
let idx = ((crc ^ b.to_int().to_uint()) & 0xFFU).to_int()
crc = crc32_table[idx] ^ crc.lsr(8)
}
crc ^ 0xFFFFFFFFU
}
///|
/// Append a little-endian uint32 (used for the trailing checksum).
pub fn write_u32_le(buf : Array[Byte], value : UInt32) -> Unit {
for i in 0..<4 {
buf.push(((value >> (i * 8)) & 0xFFU).to_byte())
}
}
///|
pub fn Reader::read_u32_le(self : Reader) -> UInt32 raise CodecError {
let mut result = 0U
for i in 0..<4 {
let b = self.read_byte().to_int().to_uint()
result = result | (b << (i * 8))
}
result
}