Skip to main content

rill_osc/
osc.rs

1use crate::error::Error;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub struct TimeTag {
5    pub seconds: u64,
6    pub fractional: u64,
7}
8
9impl TimeTag {
10    pub fn immediate() -> Self {
11        TimeTag {
12            seconds: 0,
13            fractional: 0,
14        }
15    }
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum OscType {
20    Int(i32),
21    Float(f32),
22    String(String),
23    Blob(Vec<u8>),
24    Timetag(TimeTag),
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct OscMessage {
29    pub addr: String,
30    pub args: Vec<OscType>,
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub struct OscBundle {
35    pub timetag: TimeTag,
36    pub packets: Vec<OscPacket>,
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub enum OscPacket {
41    Message(OscMessage),
42    Bundle(OscBundle),
43}
44
45pub fn decode(buf: &[u8]) -> Result<OscPacket, Error> {
46    if buf.starts_with(b"/") {
47        decode_message(buf).map(OscPacket::Message)
48    } else if buf.starts_with(b"#bundle\0") {
49        decode_bundle(buf).map(OscPacket::Bundle)
50    } else {
51        Err(Error::InvalidPacket)
52    }
53}
54
55fn decode_message(buf: &[u8]) -> Result<OscMessage, Error> {
56    let (addr, rest) = read_string(buf)?;
57
58    if rest.is_empty() {
59        return Ok(OscMessage {
60            addr,
61            args: Vec::new(),
62        });
63    }
64
65    let (type_tags, rest) = read_string(rest)?;
66
67    if !type_tags.starts_with(',') {
68        return Err(Error::Parse("missing type tag prefix ','".into()));
69    }
70
71    let type_chars: Vec<char> = type_tags[1..].chars().collect();
72    let mut args = Vec::with_capacity(type_chars.len());
73    let mut ptr = rest;
74
75    for ch in type_chars {
76        let (arg, remaining) = match ch {
77            'i' => {
78                let (v, r) = read_i32(ptr)?;
79                (OscType::Int(v), r)
80            }
81            'f' => {
82                let (v, r) = read_f32(ptr)?;
83                (OscType::Float(v), r)
84            }
85            's' => {
86                let (v, r) = read_string(ptr)?;
87                (OscType::String(v), r)
88            }
89            'b' => {
90                let (v, r) = read_blob(ptr)?;
91                (OscType::Blob(v), r)
92            }
93            't' => {
94                let (v, r) = read_timetag(ptr)?;
95                (OscType::Timetag(v), r)
96            }
97            'T' => (OscType::Int(1), ptr),
98            'F' => (OscType::Int(0), ptr),
99            _ => return Err(Error::Parse(format!("unknown type tag: {}", ch))),
100        };
101        args.push(arg);
102        ptr = remaining;
103    }
104
105    Ok(OscMessage { addr, args })
106}
107
108fn decode_bundle(buf: &[u8]) -> Result<OscBundle, Error> {
109    let buf = &buf[8..];
110
111    let (timetag, mut rest) = read_timetag(buf)?;
112
113    let mut packets = Vec::new();
114
115    while !rest.is_empty() {
116        let (size, rest_after_size) = read_i32(rest)?;
117        let size = size as usize;
118
119        if size > rest_after_size.len() {
120            return Err(Error::Parse(
121                "bundle element size exceeds remaining data length".into(),
122            ));
123        }
124
125        let packet_data = &rest_after_size[..size];
126        let packet = decode(packet_data)?;
127        packets.push(packet);
128
129        rest = &rest_after_size[size..];
130    }
131
132    Ok(OscBundle { timetag, packets })
133}
134
135pub fn encode(packet: &OscPacket) -> Result<Vec<u8>, Error> {
136    match packet {
137        OscPacket::Message(msg) => encode_message(msg),
138        OscPacket::Bundle(bundle) => encode_bundle(bundle),
139    }
140}
141
142fn encode_message(msg: &OscMessage) -> Result<Vec<u8>, Error> {
143    let mut buf = Vec::new();
144
145    write_string(&mut buf, &msg.addr);
146
147    let mut type_str = String::from(",");
148    for arg in &msg.args {
149        match arg {
150            OscType::Int(_) => type_str.push('i'),
151            OscType::Float(_) => type_str.push('f'),
152            OscType::String(_) => type_str.push('s'),
153            OscType::Blob(_) => type_str.push('b'),
154            OscType::Timetag(_) => type_str.push('t'),
155        }
156    }
157
158    write_string(&mut buf, &type_str);
159
160    for arg in &msg.args {
161        match arg {
162            OscType::Int(v) => buf.extend_from_slice(&v.to_be_bytes()),
163            OscType::Float(v) => buf.extend_from_slice(&v.to_be_bytes()),
164            OscType::String(v) => write_string(&mut buf, v),
165            OscType::Blob(v) => write_blob(&mut buf, v),
166            OscType::Timetag(v) => write_timetag(&mut buf, *v),
167        }
168    }
169
170    Ok(buf)
171}
172
173fn encode_bundle(bundle: &OscBundle) -> Result<Vec<u8>, Error> {
174    let mut buf = Vec::new();
175
176    buf.extend_from_slice(b"#bundle\0");
177    write_timetag(&mut buf, bundle.timetag);
178
179    for packet in &bundle.packets {
180        let packet_bytes = encode(packet)?;
181        let size = packet_bytes.len() as i32;
182        buf.extend_from_slice(&size.to_be_bytes());
183        buf.extend_from_slice(&packet_bytes);
184    }
185
186    Ok(buf)
187}
188
189fn read_i32(buf: &[u8]) -> Result<(i32, &[u8]), Error> {
190    if buf.len() < 4 {
191        return Err(Error::Parse("buffer too short for int32".into()));
192    }
193    let bytes: [u8; 4] = [buf[0], buf[1], buf[2], buf[3]];
194    Ok((i32::from_be_bytes(bytes), &buf[4..]))
195}
196
197fn read_f32(buf: &[u8]) -> Result<(f32, &[u8]), Error> {
198    if buf.len() < 4 {
199        return Err(Error::Parse("buffer too short for float32".into()));
200    }
201    let bytes: [u8; 4] = [buf[0], buf[1], buf[2], buf[3]];
202    Ok((f32::from_be_bytes(bytes), &buf[4..]))
203}
204
205fn read_string(buf: &[u8]) -> Result<(String, &[u8]), Error> {
206    let null_pos = buf
207        .iter()
208        .position(|&b| b == 0)
209        .ok_or_else(|| Error::Parse("string without null terminator".into()))?;
210
211    let s = std::str::from_utf8(&buf[..null_pos])
212        .map_err(|e| Error::Parse(format!("invalid UTF-8: {}", e)))?
213        .to_string();
214
215    let padded_len = ((null_pos + 4) / 4) * 4;
216
217    if padded_len > buf.len() {
218        return Err(Error::Parse("buffer too short for padded string".into()));
219    }
220
221    Ok((s, &buf[padded_len..]))
222}
223
224fn read_blob(buf: &[u8]) -> Result<(Vec<u8>, &[u8]), Error> {
225    let (size, rest) = read_i32(buf)?;
226    let size = size as usize;
227
228    if size > rest.len() {
229        return Err(Error::Parse("blob size exceeds data length".into()));
230    }
231
232    let data = rest[..size].to_vec();
233
234    let padded_len = size.div_ceil(4) * 4;
235
236    if padded_len > rest.len() {
237        return Err(Error::Parse("buffer too short for padded blob".into()));
238    }
239
240    Ok((data, &rest[padded_len..]))
241}
242
243fn read_timetag(buf: &[u8]) -> Result<(TimeTag, &[u8]), Error> {
244    if buf.len() < 8 {
245        return Err(Error::Parse("buffer too short for timetag".into()));
246    }
247
248    let sec_bytes: [u8; 4] = [buf[0], buf[1], buf[2], buf[3]];
249    let frac_bytes: [u8; 4] = [buf[4], buf[5], buf[6], buf[7]];
250
251    let seconds = u32::from_be_bytes(sec_bytes) as u64;
252    let fractional = u32::from_be_bytes(frac_bytes) as u64;
253
254    Ok((
255        TimeTag {
256            seconds,
257            fractional,
258        },
259        &buf[8..],
260    ))
261}
262
263fn write_string(buf: &mut Vec<u8>, s: &str) {
264    let bytes = s.as_bytes();
265    buf.extend_from_slice(bytes);
266    buf.push(0);
267
268    let padded_len = (bytes.len() + 1).div_ceil(4) * 4;
269    let pad_len = padded_len - (bytes.len() + 1);
270    buf.extend(std::iter::repeat_n(0u8, pad_len));
271}
272
273fn write_blob(buf: &mut Vec<u8>, data: &[u8]) {
274    let size = data.len() as i32;
275    buf.extend_from_slice(&size.to_be_bytes());
276    buf.extend_from_slice(data);
277
278    let padded_len = data.len().div_ceil(4) * 4;
279    let pad_len = padded_len - data.len();
280    buf.extend(std::iter::repeat_n(0u8, pad_len));
281}
282
283fn write_timetag(buf: &mut Vec<u8>, tag: TimeTag) {
284    buf.extend_from_slice(&(tag.seconds as u32).to_be_bytes());
285    buf.extend_from_slice(&(tag.fractional as u32).to_be_bytes());
286}
287
288pub fn pattern_match(pattern: &str, addr: &str) -> bool {
289    if pattern == "*" {
290        return true;
291    }
292
293    let pat_parts: Vec<&str> = pattern.split('/').collect();
294    let addr_parts: Vec<&str> = addr.split('/').collect();
295
296    if pat_parts.len() != addr_parts.len() {
297        return false;
298    }
299
300    for (p, a) in pat_parts.iter().zip(addr_parts.iter()) {
301        if *p == "*" {
302            continue;
303        }
304        if p != a {
305            return false;
306        }
307    }
308
309    true
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn test_message_int_float() {
318        let msg = OscMessage {
319            addr: "/test/foo".into(),
320            args: vec![OscType::Int(42), OscType::Float(3.14)],
321        };
322        let packet = OscPacket::Message(msg.clone());
323        let bytes = encode(&packet).unwrap();
324        let decoded = decode(&bytes).unwrap();
325        assert_eq!(packet, decoded);
326    }
327
328    #[test]
329    fn test_message_string() {
330        let msg = OscMessage {
331            addr: "/hello".into(),
332            args: vec![OscType::String("world".into())],
333        };
334        let packet = OscPacket::Message(msg);
335        let bytes = encode(&packet).unwrap();
336        let decoded = decode(&bytes).unwrap();
337        assert_eq!(packet, decoded);
338    }
339
340    #[test]
341    fn test_message_blob() {
342        let msg = OscMessage {
343            addr: "/data".into(),
344            args: vec![OscType::Blob(vec![0x00, 0x01, 0x02, 0x03, 0x04])],
345        };
346        let packet = OscPacket::Message(msg);
347        let bytes = encode(&packet).unwrap();
348        let decoded = decode(&bytes).unwrap();
349        assert_eq!(packet, decoded);
350    }
351
352    #[test]
353    fn test_message_timetag() {
354        let msg = OscMessage {
355            addr: "/sync".into(),
356            args: vec![OscType::Timetag(TimeTag {
357                seconds: 100,
358                fractional: 500,
359            })],
360        };
361        let packet = OscPacket::Message(msg);
362        let bytes = encode(&packet).unwrap();
363        let decoded = decode(&bytes).unwrap();
364        assert_eq!(packet, decoded);
365    }
366
367    #[test]
368    fn test_message_mixed_args() {
369        let msg = OscMessage {
370            addr: "/mix".into(),
371            args: vec![
372                OscType::Int(-1),
373                OscType::Float(2.5),
374                OscType::String("test".into()),
375                OscType::Blob(vec![0xFF]),
376            ],
377        };
378        let packet = OscPacket::Message(msg);
379        let bytes = encode(&packet).unwrap();
380        let decoded = decode(&bytes).unwrap();
381        assert_eq!(packet, decoded);
382    }
383
384    #[test]
385    fn test_empty_message() {
386        let msg = OscMessage {
387            addr: "/test".into(),
388            args: vec![],
389        };
390        let bytes = encode(&OscPacket::Message(msg.clone())).unwrap();
391        let decoded = decode(&bytes).unwrap();
392        assert_eq!(OscPacket::Message(msg), decoded);
393    }
394
395    #[test]
396    fn test_bundle() {
397        let msg1 = OscMessage {
398            addr: "/a".into(),
399            args: vec![OscType::Int(1)],
400        };
401        let msg2 = OscMessage {
402            addr: "/b".into(),
403            args: vec![OscType::Float(2.0)],
404        };
405        let bundle = OscBundle {
406            timetag: TimeTag::immediate(),
407            packets: vec![OscPacket::Message(msg1), OscPacket::Message(msg2)],
408        };
409        let packet = OscPacket::Bundle(bundle);
410        let bytes = encode(&packet).unwrap();
411        let decoded = decode(&bytes).unwrap();
412        assert_eq!(packet, decoded);
413    }
414
415    #[test]
416    fn test_nested_bundle() {
417        let inner = OscBundle {
418            timetag: TimeTag::immediate(),
419            packets: vec![OscPacket::Message(OscMessage {
420                addr: "/inner".into(),
421                args: vec![OscType::Int(99)],
422            })],
423        };
424        let outer = OscBundle {
425            timetag: TimeTag {
426                seconds: 1,
427                fractional: 0,
428            },
429            packets: vec![OscPacket::Bundle(inner)],
430        };
431        let packet = OscPacket::Bundle(outer);
432        let bytes = encode(&packet).unwrap();
433        let decoded = decode(&bytes).unwrap();
434        assert_eq!(packet, decoded);
435    }
436
437    #[test]
438    fn test_decode_invalid() {
439        assert!(decode(b"garbage").is_err());
440        assert!(decode(b"").is_err());
441    }
442
443    #[test]
444    fn test_pattern_match_exact() {
445        assert!(pattern_match("/audio/volume", "/audio/volume"));
446        assert!(!pattern_match("/audio/volume", "/audio/pan"));
447    }
448
449    #[test]
450    fn test_pattern_match_wildcard() {
451        assert!(pattern_match("/audio/*", "/audio/volume"));
452        assert!(pattern_match("/audio/*", "/audio/pan"));
453        assert!(!pattern_match("/audio/*", "/mixer/volume"));
454        assert!(pattern_match("*", "/anything/works"));
455    }
456
457    #[test]
458    fn test_pattern_match_multi_segment() {
459        assert!(pattern_match("/*/volume", "/audio/volume"));
460        assert!(!pattern_match("/*/volume", "/audio/pan"));
461    }
462
463    #[test]
464    fn test_time_tag_immediate() {
465        let t = TimeTag::immediate();
466        assert_eq!(t.seconds, 0);
467        assert_eq!(t.fractional, 0);
468    }
469
470    #[test]
471    fn test_encode_empty_string() {
472        let msg = OscMessage {
473            addr: "/".into(),
474            args: vec![OscType::String(String::new())],
475        };
476        let packet = OscPacket::Message(msg);
477        let bytes = encode(&packet).unwrap();
478        let decoded = decode(&bytes).unwrap();
479        assert_eq!(packet, decoded);
480    }
481
482    #[test]
483    fn test_known_osc_packet() {
484        let bytes = b"/test\0\0\0,i\0\0\0\0\0\x2a";
485        let decoded = decode(bytes).unwrap();
486        match decoded {
487            OscPacket::Message(msg) => {
488                assert_eq!(msg.addr, "/test");
489                assert_eq!(msg.args.len(), 1);
490                assert_eq!(msg.args[0], OscType::Int(42));
491            }
492            _ => panic!("expected message"),
493        }
494    }
495
496    #[test]
497    fn test_empty_addr_no_args() {
498        let bytes = b"/\0\0\0";
499        let decoded = decode(bytes).unwrap();
500        match decoded {
501            OscPacket::Message(msg) => {
502                assert_eq!(msg.addr, "/");
503                assert!(msg.args.is_empty());
504            }
505            _ => panic!("expected message"),
506        }
507    }
508}