#![allow(dead_code)]
#[derive(Debug, Clone)]
pub enum OscArg {
Int(i32),
Float(f32),
String(String),
Bool(bool),
}
impl OscArg {
pub fn type_tag(&self) -> char {
match self {
OscArg::Int(_) => 'i',
OscArg::Float(_) => 'f',
OscArg::String(_) => 's',
OscArg::Bool(true) => 'T',
OscArg::Bool(false) => 'F',
}
}
}
#[derive(Debug, Clone)]
pub struct OscMessage {
pub address: String,
pub args: Vec<OscArg>,
}
impl OscMessage {
pub fn new(address: impl Into<String>, args: Vec<OscArg>) -> Self {
Self {
address: address.into(),
args,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct OscBundle {
pub timestamp_secs: f64,
pub messages: Vec<OscMessage>,
}
impl OscBundle {
pub fn new(timestamp_secs: f64) -> Self {
Self {
timestamp_secs,
messages: Vec::new(),
}
}
pub fn add_message(&mut self, msg: OscMessage) {
self.messages.push(msg);
}
}
pub fn pad_to_4(buf: &mut Vec<u8>) {
while !buf.len().is_multiple_of(4) {
buf.push(0);
}
}
pub fn encode_osc_string(s: &str) -> Vec<u8> {
let mut buf: Vec<u8> = s.as_bytes().to_vec();
buf.push(0);
pad_to_4(&mut buf);
buf
}
pub fn encode_osc_message(msg: &OscMessage) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&encode_osc_string(&msg.address));
let tags: String = std::iter::once(',')
.chain(msg.args.iter().map(|a| a.type_tag()))
.collect();
buf.extend_from_slice(&encode_osc_string(&tags));
for arg in &msg.args {
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) => buf.extend_from_slice(&encode_osc_string(s)),
OscArg::Bool(_) => { }
}
}
buf
}
pub fn encode_osc_bundle(bundle: &OscBundle) -> Vec<u8> {
let mut buf = encode_osc_string("#bundle");
let secs_since_1900 = (bundle.timestamp_secs + 2_208_988_800.0) as u32;
buf.extend_from_slice(&secs_since_1900.to_be_bytes());
buf.extend_from_slice(&0u32.to_be_bytes());
for msg in &bundle.messages {
let encoded = encode_osc_message(msg);
buf.extend_from_slice(&(encoded.len() as u32).to_be_bytes());
buf.extend_from_slice(&encoded);
}
buf
}
pub fn is_osc_bundle(data: &[u8]) -> bool {
data.len() >= 8 && &data[0..7] == b"#bundle"
}
pub fn count_osc_messages(bundle: &OscBundle) -> usize {
bundle.messages.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_osc_arg_type_tags() {
assert_eq!(OscArg::Int(0).type_tag(), 'i' );
assert_eq!(OscArg::Float(0.0).type_tag(), 'f' );
assert_eq!(
OscArg::String("x".into()).type_tag(),
's'
);
assert_eq!(OscArg::Bool(true).type_tag(), 'T' );
assert_eq!(OscArg::Bool(false).type_tag(), 'F' );
}
#[test]
fn test_pad_to_4() {
let mut buf = vec![1u8, 2, 3];
pad_to_4(&mut buf);
assert_eq!(buf.len() % 4, 0 );
}
#[test]
fn test_encode_osc_string_multiple_of_4() {
let s = encode_osc_string("/test");
assert_eq!(s.len() % 4, 0 );
}
#[test]
fn test_encode_osc_string_null_terminated() {
let s = encode_osc_string("hi");
assert!(s.contains(&0u8) );
}
#[test]
fn test_encode_osc_message_non_empty() {
let msg = OscMessage::new("/test", vec![OscArg::Int(42)]);
let bytes = encode_osc_message(&msg);
assert!(!bytes.is_empty() );
}
#[test]
fn test_encode_osc_bundle_magic() {
let bundle = OscBundle::new(0.0);
let bytes = encode_osc_bundle(&bundle);
assert!(is_osc_bundle(&bytes) );
}
#[test]
fn test_count_messages() {
let mut bundle = OscBundle::new(0.0);
bundle.add_message(OscMessage::new("/a", vec![]));
bundle.add_message(OscMessage::new("/b", vec![]));
assert_eq!(count_osc_messages(&bundle), 2 );
}
#[test]
fn test_bundle_with_float_arg() {
let mut bundle = OscBundle::new(1.0);
bundle.add_message(OscMessage::new("/freq", vec![OscArg::Float(440.0)]));
let bytes = encode_osc_bundle(&bundle);
assert!(bytes.len() > 16 );
}
#[test]
fn test_is_osc_bundle_rejects_random() {
assert!(!is_osc_bundle(b"randomdata") );
}
}