///|
pub(all) struct DecodeSelection {
paths : Array[String]
inline_array_items : Int
scan_items : Int
} derive(Eq, @debug.Debug)
///|
pub fn decode_selection() -> DecodeSelection {
{ paths: [], inline_array_items: 1024, scan_items: 16 * 1024 * 1024, }
}
///|
pub(all) struct BinaryField {
path : String
offset : Int
byte_length : Int
elements : Int
element_type : String
stride : Int
little_endian : Bool
aligned : Bool
} derive(Eq, @debug.Debug)
///|
pub struct PayloadView {
source : Bytes
value : Json
binary : Map[String, BinaryField]
visited : Int
}
///|
pub fn PayloadView::json(self : PayloadView) -> Json {
self.value
}
///|
pub fn PayloadView::binary_fields(self : PayloadView) -> Array[BinaryField] {
let keys = self.binary.keys().to_array()
keys.sort()
keys.map(fn(key) { self.binary[key] })
}
///|
pub fn PayloadView::info(self : PayloadView) -> Json {
{
"wire_bytes": self.source.length(),
"visited_items": self.visited,
"binary_fields": self.binary.length(),
}
}
///|
/// Offsets are relative to a field's raw payload bytes, excluding array count.
pub fn PayloadView::read_binary(
self : PayloadView,
path : String,
offset : Int,
length : Int,
max_bytes? : Int = 65536,
) -> Bytes raise McapError {
guard self.binary.get(path) is Some(field) else {
raise Invalid("binary view field not found")
}
if offset < 0 ||
length < 0 ||
max_bytes < 0 ||
length > max_bytes ||
offset > field.byte_length ||
length > field.byte_length - offset {
raise Invalid("binary view read exceeds field or output budget")
}
self.source[field.offset + offset:field.offset + offset + length].to_owned()
}
///|
pub fn PayloadView::write_binary(
self : PayloadView,
path : String,
sink : WriteSink,
block_bytes? : Int = 65536,
cancelled? : () -> Bool = fn() { false },
) -> UInt64 raise McapError {
if block_bytes < 1 || block_bytes > 1024 * 1024 {
raise Invalid("invalid binary export block size")
}
guard self.binary.get(path) is Some(field) else {
raise Invalid("binary view field not found")
}
let mut position = 0
while position < field.byte_length {
if cancelled() {
raise Invalid("binary export cancelled")
}
let count = block_bytes.min(field.byte_length - position)
sink.write(self.read_binary(path, position, count, max_bytes=block_bytes))
position += count
}
field.byte_length.to_uint64()
}
///|
pub fn PayloadView::read_elements(
self : PayloadView,
path : String,
start : Int,
count : Int,
max_elements? : Int = 1024,
) -> Json raise McapError {
guard self.binary.get(path) is Some(field) && field.stride > 0 else {
raise Invalid("element windows require a fixed-width primitive array")
}
if start < 0 ||
count < 0 ||
max_elements < 0 ||
max_elements > 100000 ||
count > max_elements ||
start > field.elements ||
count > field.elements - start {
raise Invalid("array window exceeds field or item budget")
}
let decoder = cdr_decoder("moonmcap/View", b"uint8 unused")
let wire : CdrWire = {
bytes: self.source,
little: field.little_endian,
position: field.offset + start * field.stride,
budget: [count],
aligned: field.aligned,
}
let model : CdrField = {
name: "value",
base: field.element_type,
array: None,
bound: None,
string_bound: None,
}
let values = []
for _i = 0; _i < count; _i = _i + 1 {
values.push(decoder.value(wire, model, 1))
}
Json::array(values)
}
///|
fn JsonCondition::collect_paths(
self : JsonCondition,
result : Array[String],
) -> Unit {
match self {
Compare(path, _, _) => {
let p = view_path(path)
if !result.contains(p) {
result.push(p)
}
}
All(items) | Any(items) =>
for item in items {
item.collect_paths(result)
}
Not(item) => item.collect_paths(result)
_ => ()
}
}
///|
pub fn JsonFilter::required_paths(self : JsonFilter) -> Array[String] {
let result = []
self.condition.collect_paths(result)
result
}
///|
pub fn JsonFilter::selected_paths(self : JsonFilter) -> Array[String] {
self.projections.map(fn(item) { item.0 })
}
///|
fn contains_binary_view(value : Json) -> Bool {
match value {
Object(fields) =>
fields.contains("$binary") || fields.values().any(contains_binary_view)
Array(items) => items.any(contains_binary_view)
_ => false
}
}
///|
/// Fail explicitly when an exact condition would inspect an opaque array.
pub fn PayloadDecoder::require_filter_fields(
self : PayloadDecoder,
filter : JsonFilter,
) -> PayloadDecoder raise McapError {
let paths = filter.required_paths().map(fn(p) { json_pointer(p) })
payload_decoder(fn(schema, channel, bytes) {
let value = (self.decode_payload)(schema, channel, bytes)
if channel.message_encoding != "cdr" && channel.message_encoding != "ros1" {
return value
}
for path in paths {
if pointer_value(value, path) is Some(v) && contains_binary_view(v) {
raise Invalid(
"exact filter requires an opaque binary field; raise inline limit or use bounded array reads",
)
}
}
value
})
}
///|
/// Preserve JSON nesting while retaining requested paths and their ancestors.
fn selected_json(
value : Json,
pointers : Array[Array[String]],
path : Array[String],
) -> Json {
if pointers.is_empty() ||
pointers.any(fn(p) {
p.length() <= path.length() && p == path[0:p.length()].to_array()
}) {
return value
}
match value {
Object(fields) => {
let out : Map[String, Json] = Map([])
for (key, value) in fields {
let child = path + [key]
if selection_wants(pointers, child) {
out[key] = selected_json(value, pointers, child)
}
}
Json::object(out)
}
Array(items) =>
Json::array(
items.mapi(fn(i, value) {
let child = path + [i.to_string()]
if selection_wants(pointers, child) {
selected_json(value, pointers, child)
} else {
Json::null()
}
}),
)
_ => value
}
}
///|
fn selection_paths(
selection : DecodeSelection,
) -> Array[Array[String]] raise McapError {
if selection.paths.length() > 256 ||
selection.inline_array_items < 0 ||
selection.inline_array_items > 100000 ||
selection.scan_items < 1 {
raise Invalid("invalid decode selection limits")
}
selection.paths.map(fn(p) { json_pointer(p) })
}
///|
fn selection_wants(paths : Array[Array[String]], path : Array[String]) -> Bool {
paths.is_empty() ||
paths.any(fn(p) {
let common = p.length().min(path.length())
for i = 0; i < common; i = i + 1 {
if p[i] != path[i] {
return false
}
}
true
})
}
///|
fn view_path(parts : Array[String]) -> String {
parts.fold(init="", fn(path, part) {
path +
"/" +
part.replace_all(old="~", new="~0").replace_all(old="/", new="~1")
})
}
///|
fn BinaryField::json(self : BinaryField) -> Json {
{
"$binary": self.path,
"byte_length": self.byte_length,
"elements": self.elements,
"element_type": self.element_type,
"stride": self.stride,
"little_endian": self.little_endian,
}
}
///|
fn cdr_width(base : String) -> Int {
match base {
"bool" | "byte" | "char" | "uint8" | "int8" => 1
"uint16" | "int16" => 2
"uint32" | "int32" | "float32" => 4
"uint64" | "int64" | "float64" => 8
_ => 0
}
}
///|
fn CdrWire::skip(self : CdrWire, length : Int) -> Unit raise McapError {
if length < 0 || length > self.bytes.length() - self.position {
raise Invalid("truncated projected payload")
}
self.position += length
}
///|
struct CdrProjection {
decoder : CdrDecoder
paths : Array[Array[String]]
selection : DecodeSelection
binary : Map[String, BinaryField]
}
///|
fn CdrProjection::message(
self : CdrProjection,
wire : CdrWire,
name : String,
path : Array[String],
depth : Int,
emit : Bool,
) -> Json raise McapError {
if depth > self.decoder.options.depth {
raise Invalid("projected CDR depth exceeded")
}
let result : Map[String, Json] = Map([])
let fields = self.decoder.definitions[name]
if fields.is_empty() && !self.decoder.ros1 {
wire.item()
if wire.unsigned(1) != 0UL {
raise Invalid("invalid empty ROS2 message")
}
}
for field in fields {
let child = path + [field.name]
let wanted = emit && selection_wants(self.paths, child)
let scalar = fn(
emit : Bool,
item_path : Array[String],
) -> Json raise McapError {
if cdr_primitive(field.base) {
self.decoder.value(wire, field, depth)
} else {
wire.item()
self.message(wire, field.base, item_path, depth + 1, emit)
}
}
let value = match field.array {
None => scalar(wanted, child)
Some(array) => {
wire.item()
let count = if array < 0 {
bounded_length(wire.unsigned(4), wire.budget[0], "projected array")
} else {
array
}
if count > wire.budget[0] ||
(field.bound is Some(bound) && count > bound) {
raise Invalid("projected array item or declared limit")
}
let width = cdr_width(field.base)
if wanted &&
count > self.selection.inline_array_items &&
self.paths.any(fn(p) {
p.length() > child.length() &&
p[0:child.length()].to_array() == child
}) {
raise Invalid(
"array element selection exceeds inline limit; use bounded read_elements",
)
}
if width > 0 && (!wanted || count > self.selection.inline_array_items) {
if count > 0 && wire.aligned {
wire.skip((width - (wire.position - 4) % width) % width)
}
let start = wire.position
if count > (wire.bytes.length() - start) / width {
raise Invalid("truncated projected primitive array")
}
if field.base == "bool" {
for i = 0; i < count; i = i + 1 {
if wire.bytes[start + i] > 0x01 {
raise Invalid("invalid projected boolean")
}
}
}
wire.skip(count * width)
wire.budget[0] -= count
if wanted {
let binary : BinaryField = {
path: view_path(child),
offset: start,
byte_length: count * width,
elements: count,
element_type: field.base,
stride: width,
little_endian: wire.little,
aligned: wire.aligned,
}
self.binary[binary.path] = binary
binary.json()
} else {
Json::null()
}
} else {
let start = wire.position
let inline = wanted && count <= self.selection.inline_array_items
let values = []
for i = 0; i < count; i = i + 1 {
let value = scalar(inline, child + [i.to_string()])
if inline {
values.push(value)
}
}
if inline {
Json::array(values)
} else if wanted {
let binary : BinaryField = {
path: view_path(child),
offset: start,
byte_length: wire.position - start,
elements: count,
element_type: field.base,
stride: 0,
little_endian: wire.little,
aligned: wire.aligned,
}
self.binary[binary.path] = binary
binary.json()
} else {
Json::null()
}
}
}
}
if self.decoder.ros1 &&
(name == "moonmcap_ros1/Time" || name == "moonmcap_ros1/Duration") &&
field.name == "nanosec" {
validate_ros1_time(name, Map::from_array([("nanosec", value)]))
}
if wanted {
result[field.name] = value
}
}
Json::object(result)
}
///|
pub fn CdrDecoder::view(
self : CdrDecoder,
bytes : Bytes,
selection? : DecodeSelection = decode_selection(),
) -> PayloadView raise McapError {
let paths = selection_paths(selection)
if bytes.length() > self.options.message_bytes {
raise Invalid("projected payload exceeds byte budget")
}
if !self.ros1 &&
(
bytes.length() < 4 ||
bytes[0] != 0x00 ||
(bytes[1] != 0x00 && bytes[1] != 0x01) ||
bytes[2] != 0x00 ||
bytes[3] != 0x00
) {
raise Invalid("invalid projected XCDR1 encapsulation or size")
}
let wire : CdrWire = {
bytes,
little: if self.ros1 {
true
} else {
bytes[1] == 0x01
},
position: if self.ros1 {
0
} else {
4
},
budget: [selection.scan_items],
aligned: !self.ros1,
}
let projection : CdrProjection = {
decoder: self,
paths,
selection,
binary: Map([]),
}
let value = projection.message(wire, self.name, [], 1, true)
if (self.ros1 && bytes.length() != wire.position) ||
bytes.length() - wire.position > 3 ||
bytes[wire.position:].iter().any(fn(b) { b != 0x00 }) {
raise Invalid("trailing projected CDR bytes")
}
{
source: bytes,
value,
binary: projection.binary,
visited: selection.scan_items - wire.budget[0],
}
}