Skip to main content

rill_osc/
osc.rs

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