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}