///|
const TAG_NULL : Int = 0x00
///|
const TAG_BOOL : Int = 0x01
///|
const TAG_INT : Int = 0x02
///|
const TAG_DOUBLE : Int = 0x03
///|
const TAG_STRING : Int = 0x04
///|
const TAG_BYTES : Int = 0x05
///|
const TAG_ARRAY : Int = 0x06
///|
const TAG_OBJECT : Int = 0x07
///|
/// Resource limits applied while decoding untrusted input.
pub(all) struct DecodeLimits {
max_depth : Int
max_collection_items : Int
max_value_bytes : Int
} derive(Eq, Debug)
///|
/// Returns conservative default limits for one decoded value.
pub fn default_decode_limits() -> DecodeLimits {
{ max_depth: 64, max_collection_items: 1000000, max_value_bytes: 16777216 }
}
///|
fn write_tag(writer : ByteWriter, tag : Int) -> Unit {
ignore(writer.write_u8(tag))
}
///|
fn write_length(writer : ByteWriter, length : Int) -> Unit {
writer.write_u32(length.reinterpret_as_uint())
}
///|
fn encode_into(writer : ByteWriter, value : BinValue) -> Unit {
match value {
Null => write_tag(writer, TAG_NULL)
Bool(value) => {
write_tag(writer, TAG_BOOL)
ignore(writer.write_u8(if value { 1 } else { 0 }))
}
Int(value) => {
write_tag(writer, TAG_INT)
writer.write_i64(value.to_int64())
}
Double(value) => {
write_tag(writer, TAG_DOUBLE)
writer.write_f64(value)
}
String(value) => {
write_tag(writer, TAG_STRING)
let bytes = @utf8.encode(value[:])
write_length(writer, bytes.length())
writer.write_bytes(bytes)
}
Bytes(value) => {
write_tag(writer, TAG_BYTES)
write_length(writer, value.length())
writer.write_bytes(value)
}
Array(values) => {
write_tag(writer, TAG_ARRAY)
write_length(writer, values.length())
for value in values {
encode_into(writer, value)
}
}
Object(entries) => {
write_tag(writer, TAG_OBJECT)
write_length(writer, entries.length())
for entry in entries {
let (key, value) = entry
let key_bytes = @utf8.encode(key[:])
write_length(writer, key_bytes.length())
writer.write_bytes(key_bytes)
encode_into(writer, value)
}
}
}
}
///|
/// Encodes one `BinValue` into the moonbin v1 wire format.
pub fn encode(value : BinValue) -> Bytes {
let writer = ByteWriter::new()
encode_into(writer, value)
writer.to_bytes()
}
///|
fn read_length(reader : ByteReader) -> Result[Int, DecodeError] {
match reader.read_u32() {
Err(err) => Err(err)
Ok(value) =>
if value > 0x7FFFFFFFU {
Err(DecodeError::InvalidLength("length exceeds MoonBit Int range"))
} else {
Ok(value.reinterpret_as_int())
}
}
}
///|
fn decode_utf8(bytes : Bytes) -> Result[String, DecodeError] {
Ok(@utf8.decode(bytes[:])) catch {
_ => Err(DecodeError::InvalidType("valid UTF-8", "malformed bytes"))
}
}
///|
fn decode_from(
reader : ByteReader,
limits : DecodeLimits,
depth : Int,
) -> Result[BinValue, DecodeError] {
if depth > limits.max_depth {
return Err(DecodeError::LimitExceeded("maximum nesting depth"))
}
let tag = match reader.read_u8() {
Ok(value) => value
Err(err) => return Err(err)
}
match tag {
TAG_NULL => Ok(BinValue::Null)
TAG_BOOL =>
match reader.read_u8() {
Ok(0) => Ok(BinValue::Bool(false))
Ok(1) => Ok(BinValue::Bool(true))
Ok(_) =>
Err(DecodeError::InvalidType("bool payload 0 or 1", "invalid byte"))
Err(err) => Err(err)
}
TAG_INT =>
match reader.read_i64() {
Err(err) => Err(err)
Ok(raw) => {
let value = raw.to_int()
if value.to_int64() == raw {
Ok(BinValue::Int(value))
} else {
Err(
DecodeError::InvalidType("MoonBit Int range", "i64 value \{raw}"),
)
}
}
}
TAG_DOUBLE =>
match reader.read_f64() {
Ok(value) => Ok(BinValue::Double(value))
Err(err) => Err(err)
}
TAG_STRING => {
let length = match read_length(reader) {
Ok(value) => value
Err(err) => return Err(err)
}
if length > limits.max_value_bytes {
return Err(DecodeError::LimitExceeded("string byte length"))
}
let bytes = match reader.read_bytes(length) {
Ok(value) => value
Err(err) => return Err(err)
}
match decode_utf8(bytes) {
Ok(value) => Ok(BinValue::String(value))
Err(err) => Err(err)
}
}
TAG_BYTES => {
let length = match read_length(reader) {
Ok(value) => value
Err(err) => return Err(err)
}
if length > limits.max_value_bytes {
return Err(DecodeError::LimitExceeded("byte value length"))
}
match reader.read_bytes(length) {
Ok(value) => Ok(BinValue::Bytes(value))
Err(err) => Err(err)
}
}
TAG_ARRAY => {
let count = match read_length(reader) {
Ok(value) => value
Err(err) => return Err(err)
}
if count > limits.max_collection_items {
return Err(DecodeError::LimitExceeded("array item count"))
}
if count > reader.remaining() {
return Err(
DecodeError::InvalidLength("array count exceeds remaining bytes"),
)
}
let values : Array[BinValue] = []
for _ in 0.. values.push(value)
Err(err) => return Err(err)
}
}
Ok(BinValue::Array(values))
}
TAG_OBJECT => {
let count = match read_length(reader) {
Ok(value) => value
Err(err) => return Err(err)
}
if count > limits.max_collection_items {
return Err(DecodeError::LimitExceeded("object entry count"))
}
if count > reader.remaining() / 5 {
return Err(
DecodeError::InvalidLength("object count exceeds remaining bytes"),
)
}
let entries : Array[(String, BinValue)] = []
for _ in 0.. value
Err(err) => return Err(err)
}
if key_length > limits.max_value_bytes {
return Err(DecodeError::LimitExceeded("object key byte length"))
}
let key_bytes = match reader.read_bytes(key_length) {
Ok(value) => value
Err(err) => return Err(err)
}
let key = match decode_utf8(key_bytes) {
Ok(value) => value
Err(err) => return Err(err)
}
let value = match decode_from(reader, limits, depth + 1) {
Ok(value) => value
Err(err) => return Err(err)
}
entries.push((key, value))
}
Ok(BinValue::Object(entries))
}
_ => Err(DecodeError::InvalidTag(tag))
}
}
///|
/// Decodes exactly one moonbin v1 value using explicit resource limits.
///
/// The function rejects unread trailing bytes after the top-level value.
pub fn decode_with_limits(
bytes : Bytes,
limits : DecodeLimits,
) -> Result[BinValue, DecodeError] {
if limits.max_depth < 0 ||
limits.max_collection_items < 0 ||
limits.max_value_bytes < 0 {
return Err(DecodeError::InvalidLength("decode limits must be non-negative"))
}
let reader = ByteReader::new(bytes)
match decode_from(reader, limits, 0) {
Err(err) => Err(err)
Ok(value) =>
if reader.remaining() == 0 {
Ok(value)
} else {
Err(DecodeError::TrailingBytes(reader.remaining()))
}
}
}
///|
/// Decodes exactly one moonbin v1 value using conservative default limits.
pub fn decode(bytes : Bytes) -> Result[BinValue, DecodeError] {
decode_with_limits(bytes, default_decode_limits())
}