///|
// Keep the wire primitive independent of removed standard-library methods.
fn UInt::mcap_le_bytes(self : UInt) -> Bytes {
[
self.to_byte(),
(self >> 8).to_byte(),
(self >> 16).to_byte(),
(self >> 24).to_byte(),
]
}
///|
struct Cursor {
data : Bytes
mut pos : Int
limits : Limits
}
///|
fn cursor(data : Bytes, limits : Limits) -> Cursor {
{ data, pos: 0, limits, }
}
///|
fn Cursor::take(self : Cursor, count : Int) -> Bytes raise McapError {
if count < 0 || count > self.data.length() - self.pos {
raise Invalid("truncated field at byte \{self.pos}: requested \{count}")
}
let result = self.data[self.pos:self.pos + count].to_owned()
self.pos += count
result
}
///|
fn Cursor::u16(self : Cursor) -> Int raise McapError {
let bytes = self.take(2)
bytes[0].to_int() | (bytes[1].to_int() << 8)
}
///|
fn Cursor::u32(self : Cursor) -> UInt raise McapError {
let bytes = self.take(4)
let mut result = 0U
for i = 0; i < 4; i = i + 1 {
result = result | (bytes[i].to_uint() << (i * 8))
}
result
}
///|
fn Cursor::u64(self : Cursor) -> UInt64 raise McapError {
let bytes = self.take(8)
let mut result = 0UL
for i = 0; i < 8; i = i + 1 {
result = result | (bytes[i].to_uint64() << (i * 8))
}
result
}
///|
fn bounded_length(
value : UInt64,
maximum : Int,
label : String,
) -> Int raise McapError {
if maximum < 0 || value > maximum.to_uint64() {
raise Invalid("\{label} length exceeds configured limit")
}
value.to_int()
}
///|
fn Cursor::text(self : Cursor) -> String raise McapError {
let size = bounded_length(
self.u32().to_uint64(),
self.limits.max_string_bytes,
"string",
)
let bytes = self.take(size)
@utf8.decode(bytes[:], ignore_bom=true) catch {
_ => raise Invalid("malformed UTF-8 at byte \{self.pos - size}")
}
}
///|
fn Cursor::blob(self : Cursor) -> Bytes raise McapError {
let size = bounded_length(
self.u32().to_uint64(),
self.limits.max_record_bytes,
"blob",
)
self.take(size)
}
///|
fn Cursor::pairs(self : Cursor) -> Array[(String, String)] raise McapError {
let size = bounded_length(
self.u32().to_uint64(),
self.limits.max_record_bytes,
"map",
)
let nested = cursor(self.take(size), self.limits)
let pairs = []
let seen : Map[String, Bool] = {}
while nested.pos < nested.data.length() {
let key = nested.text()
let value = nested.text()
if seen.contains(key) {
raise Invalid("duplicate map key: \{key}")
}
seen[key] = true
pairs.push((key, value))
}
pairs
}
///|
fn Cursor::finish(self : Cursor) -> Unit raise McapError {
if self.pos != self.data.length() {
raise Invalid("unexpected trailing bytes at \{self.pos}")
}
}
///|
fn put_u16(out : @buffer.Buffer, value : Int) -> Unit raise McapError {
if value < 0 || value > 65535 {
raise Invalid("identifier outside uint16")
}
out.write_uint16_le(value.to_uint16())
}
///|
fn put_text(out : @buffer.Buffer, value : String) -> Unit {
let bytes = @utf8.encode(value[:])
out.write_uint_le(bytes.length().to_uint())
out.write_bytes(bytes[:])
}
///|
fn put_blob(out : @buffer.Buffer, value : Bytes) -> Unit {
out.write_uint_le(value.length().to_uint())
out.write_bytes(value[:])
}
///|
fn put_pairs(
out : @buffer.Buffer,
pairs : Array[(String, String)],
) -> Unit raise McapError {
let nested = @buffer.Buffer::Buffer()
let seen : Map[String, Bool] = {}
for pair in pairs {
if seen.contains(pair.0) {
raise Invalid("duplicate map key: \{pair.0}")
}
seen[pair.0] = true
put_text(nested, pair.0)
put_text(nested, pair.1)
}
put_blob(out, nested.to_bytes())
}
///|
pub fn frame(opcode : Byte, body : Bytes) -> Bytes raise McapError {
if opcode == 0 {
raise Invalid("opcode zero is reserved")
}
let out = @buffer.Buffer::Buffer()
out.write_byte(opcode)
out.write_uint64_le(body.length().to_uint64())
out.write_bytes(body[:])
out.to_bytes()
}
///|
pub fn decode_records(
bytes : Bytes,
limits? : Limits = default_limits(),
base? : Int = 0,
) -> Array[Record] raise McapError {
if limits.max_records < 0 || base < 0 || base > 2147483647 - bytes.length() {
raise Invalid("invalid scan limits or base")
}
let input = cursor(bytes, limits)
let result = []
while input.pos < bytes.length() {
if result.length() >= limits.max_records {
raise Invalid("record count exceeds configured limit")
}
let offset = input.pos
let opcode = input.take(1)[0]
if opcode == 0 {
raise Invalid("opcode zero at byte \{offset + base}")
}
let size = bounded_length(input.u64(), limits.max_record_bytes, "record")
let body = input.take(size)
result.push({ opcode, offset: offset + base, body, })
}
result
}