///|
fn hex_digit(code : Int32) -> Int32? {
if code >= 48 && code <= 57 {
Some(code - 48)
} else if code >= 65 && code <= 70 {
Some(code - 55)
} else if code >= 97 && code <= 102 {
Some(code - 87)
} else {
None
}
}
///|
pub fn hex_decode(
text : String,
max_bytes~ : Int32 = 1048576,
) -> Bytes raise OscError {
if max_bytes < 0 || max_bytes > 16777216 {
raise Limit("hex budget")
}
if text.length() % 2 != 0 {
raise Invalid("odd hex length", text.length())
}
if text.length() / 2 > max_bytes {
raise Limit("hex decoded bytes")
}
let out : Array[Byte] = []
let mut i = 0
while i < text.length() {
let hi = match hex_digit(text[i].to_int()) {
Some(n) => n
None => raise Invalid("invalid hex digit", i)
}
let lo = match hex_digit(text[i + 1].to_int()) {
Some(n) => n
None => raise Invalid("invalid hex digit", i + 1)
}
out.push((hi * 16 + lo).to_byte())
i += 2
}
Bytes::from_array(out)
}
///|
pub fn hex_encode(
bytes : Bytes,
max_bytes~ : Int32 = 1048576,
) -> String raise OscError {
if max_bytes < 0 || max_bytes > 16777216 || bytes.length() > max_bytes {
raise Limit("hex input bytes")
}
let out = StringBuilder()
let digits = "0123456789abcdef"
for b in bytes {
out.write_char(digits[b.to_int() / 16].to_int().to_char().unwrap())
out.write_char(digits[b.to_int() % 16].to_int().to_char().unwrap())
}
out.to_string()
}
///|
pub(all) struct PacketSummary {
wire_bytes : Int32
messages : Int32
bundles : Int32
arguments : Int32
array_arguments : Int32
blob_bytes : Int32
max_depth : Int32
} derive(Eq, Debug)
///|
pub extend PacketSummary with Eq::{equal, not_equal}
///|
pub extend PacketSummary with @debug.Debug::{to_repr}
///|
priv struct SummaryBuilder {
mut messages : Int32
mut bundles : Int32
mut arguments : Int32
mut arrays : Int32
mut blobs : Int32
mut depth : Int32
}
///|
fn SummaryBuilder::argument(self : SummaryBuilder, arg : Argument) -> Unit {
self.arguments += 1
match arg {
Array(items) => {
self.arrays += 1
for item in items {
self.argument(item)
}
}
Blob(bytes) => self.blobs += bytes.length()
_ => ()
}
}
///|
fn SummaryBuilder::packet(
self : SummaryBuilder,
packet : Packet,
depth : Int32,
) -> Unit {
if depth > self.depth {
self.depth = depth
}
match packet {
Message(m) => {
self.messages += 1
for a in m.arguments {
self.argument(a)
}
}
Bundle(_, children) => {
self.bundles += 1
for child in children {
self.packet(child, depth + 1)
}
}
}
}
///|
/// First validates all budgets; never inspect untrusted models recursively first.
pub fn summarize(
packet : Packet,
limits~ : Limits = Limits::default(),
) -> PacketSummary raise OscError {
let wire = encode(packet, limits~)
let owned = decode(wire, limits~)
let builder = SummaryBuilder::{
messages: 0,
bundles: 0,
arguments: 0,
arrays: 0,
blobs: 0,
depth: 0,
}
builder.packet(owned, 1)
{
wire_bytes: wire.length(),
messages: builder.messages,
bundles: builder.bundles,
arguments: builder.arguments,
array_arguments: builder.arrays,
blob_bytes: builder.blobs,
max_depth: builder.depth,
}
}