use alloc::{string::String, vec::Vec};
use crate::{OscArg, OscBundle, OscMessage, OscPacket, OscTimeTag};
pub fn encode(packet: &OscPacket) -> Vec<u8> {
let mut buf = Vec::new();
encode_packet(packet, &mut buf);
buf
}
fn encode_packet(packet: &OscPacket, buf: &mut Vec<u8>) {
match packet {
OscPacket::Message(msg) => encode_message(msg, buf),
OscPacket::Bundle(bundle) => encode_bundle(bundle, buf),
}
}
fn encode_message(msg: &OscMessage, buf: &mut Vec<u8>) {
write_str(&msg.address, buf);
let mut type_tags = String::from(",");
collect_type_tags(&msg.args, &mut type_tags);
write_str(&type_tags, buf);
write_args(&msg.args, buf);
}
fn collect_type_tags(args: &[OscArg], tags: &mut String) {
for arg in args {
match arg {
OscArg::Array(inner) => {
tags.push('[');
collect_type_tags(inner, tags);
tags.push(']');
}
other => tags.push(other.type_tag()),
}
}
}
fn write_args(args: &[OscArg], buf: &mut Vec<u8>) {
for arg in args {
write_arg(arg, buf);
}
}
fn write_arg(arg: &OscArg, buf: &mut Vec<u8>) {
match arg {
OscArg::Int(v) => buf.extend_from_slice(&v.to_be_bytes()),
OscArg::Float(v) => buf.extend_from_slice(&v.to_be_bytes()),
OscArg::String(s) => write_str(s, buf),
OscArg::Blob(b) => write_blob(b, buf),
OscArg::Long(v) => buf.extend_from_slice(&v.to_be_bytes()),
OscArg::Double(v) => buf.extend_from_slice(&v.to_be_bytes()),
OscArg::TimeTag(t) => write_timetag(t, buf),
OscArg::Char(c) => {
buf.extend_from_slice(&(*c as u32).to_be_bytes());
}
OscArg::Color(r, g, b, a) => buf.extend_from_slice(&[*r, *g, *b, *a]),
OscArg::Midi(m) => buf.extend_from_slice(m),
OscArg::Bool(_) | OscArg::Nil | OscArg::Impulse => {}
OscArg::Array(inner) => {
write_args(inner, buf);
}
}
}
fn encode_bundle(bundle: &OscBundle, buf: &mut Vec<u8>) {
buf.extend_from_slice(b"#bundle\0");
write_timetag(&bundle.time, buf);
for element in &bundle.elements {
let start = buf.len();
buf.extend_from_slice(&[0u8; 4]); encode_packet(element, buf);
let element_len = (buf.len() - start - 4) as u32;
buf[start..start + 4].copy_from_slice(&element_len.to_be_bytes());
}
}
fn write_timetag(t: &OscTimeTag, buf: &mut Vec<u8>) {
buf.extend_from_slice(&t.seconds.to_be_bytes());
buf.extend_from_slice(&t.fractional.to_be_bytes());
}
fn write_str(s: &str, buf: &mut Vec<u8>) {
buf.extend_from_slice(s.as_bytes());
buf.push(0); pad4(buf);
}
fn write_blob(b: &[u8], buf: &mut Vec<u8>) {
buf.extend_from_slice(&(b.len() as u32).to_be_bytes());
buf.extend_from_slice(b);
pad4(buf);
}
fn pad4(buf: &mut Vec<u8>) {
while !buf.len().is_multiple_of(4) {
buf.push(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{OscArg, OscMessage, OscPacket, OscTimeTag, decode};
fn msg(address: &str, args: Vec<OscArg>) -> OscPacket {
OscPacket::Message(OscMessage {
address: address.to_string(),
args,
})
}
#[test]
fn encode_simple_message() {
let packet = msg(
"/test",
vec![
OscArg::Int(42),
OscArg::Float(core::f32::consts::PI),
OscArg::String("hello".to_string()),
],
);
let bytes = encode(&packet);
let decoded = decode(&bytes).expect("round-trip decode failed");
let bytes2 = encode(&decoded);
assert_eq!(bytes, bytes2);
}
#[test]
fn encode_empty_message() {
let packet = msg("/empty", vec![]);
let bytes = encode(&packet);
let decoded = decode(&bytes).expect("decode failed");
assert_eq!(decoded, packet);
}
#[test]
fn encode_bool_nil_impulse() {
let packet = msg(
"/flags",
vec![
OscArg::Bool(true),
OscArg::Bool(false),
OscArg::Nil,
OscArg::Impulse,
],
);
let bytes = encode(&packet);
let decoded = decode(&bytes).expect("decode failed");
assert_eq!(decoded, packet);
}
#[test]
fn encode_blob_non_multiple_of_4() {
let blob_data = vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE];
let packet = msg("/blob", vec![OscArg::Blob(blob_data.clone())]);
let bytes = encode(&packet);
let decoded = decode(&bytes).expect("decode failed");
assert_eq!(decoded, packet);
}
#[test]
fn encode_array_nested() {
let packet = msg(
"/array",
vec![OscArg::Array(vec![
OscArg::Int(1),
OscArg::Int(2),
OscArg::Array(vec![OscArg::Float(3.0)]),
])],
);
let bytes = encode(&packet);
let decoded = decode(&bytes).expect("decode failed");
assert_eq!(decoded, packet);
}
#[test]
fn vlq_padding_string_lengths() {
for len in 1usize..=4 {
let s: alloc::string::String = "x".repeat(len);
let mut buf = Vec::new();
write_str(&s, &mut buf);
assert_eq!(
buf.len() % 4,
0,
"string of len {} produced unaligned buffer (len {})",
len,
buf.len()
);
let expected = (len + 1).div_ceil(4) * 4;
assert_eq!(buf.len(), expected);
}
}
#[test]
fn type_tag_chars() {
assert_eq!(OscArg::Int(0).type_tag(), 'i');
assert_eq!(OscArg::Float(0.0).type_tag(), 'f');
assert_eq!(OscArg::String(alloc::string::String::new()).type_tag(), 's');
assert_eq!(OscArg::Blob(alloc::vec![]).type_tag(), 'b');
assert_eq!(OscArg::Long(0).type_tag(), 'h');
assert_eq!(OscArg::Double(0.0).type_tag(), 'd');
assert_eq!(OscArg::TimeTag(OscTimeTag::IMMEDIATE).type_tag(), 't');
assert_eq!(OscArg::Char('A').type_tag(), 'c');
assert_eq!(OscArg::Color(0, 0, 0, 0).type_tag(), 'r');
assert_eq!(OscArg::Midi([0; 4]).type_tag(), 'm');
assert_eq!(OscArg::Bool(true).type_tag(), 'T');
assert_eq!(OscArg::Bool(false).type_tag(), 'F');
assert_eq!(OscArg::Nil.type_tag(), 'N');
assert_eq!(OscArg::Impulse.type_tag(), 'I');
assert_eq!(OscArg::Array(alloc::vec![]).type_tag(), '[');
}
}