Skip to content

Commit

Permalink
Implement stream encoding
Browse files Browse the repository at this point in the history
This implements OSC packet stream encoding in encode_tcp.

Closes #47.
  • Loading branch information
klingtnet committed Jun 10, 2023
1 parent a4b802f commit 0865e66
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 4 deletions.
46 changes: 46 additions & 0 deletions src/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::convert::TryInto;

use crate::alloc::{string::String, vec::Vec};
use crate::types::{OscBundle, OscMessage, OscPacket, OscTime, OscType};

Expand Down Expand Up @@ -27,6 +29,50 @@ pub fn encode(packet: &OscPacket) -> crate::types::Result<Vec<u8>> {
Ok(bytes)
}

/// Takes a slice of OSC packet and returns a stream encoded byte vector on success.
/// If the packet was invalid an `OscError` is returned instead.
///
/// Note this prepends the size of each encoded packet as a 32-bit integer before the
/// encoded byte data of each OSC packet, as required by the [OSC packet specification].
/// For example, assume you want to send two OSC packets, then the message layout will
/// look like this: `[<size-of-packet-1><packet-1><size-of-packet-2><packet-2>...]`.
///
/// # Example
///
/// ```
/// use rosc::{OscPacket,OscMessage,OscType};
/// use rosc::encoder;
///
/// let packets = [
/// OscPacket::Message(OscMessage{
/// addr: "/osc1/freq".to_string(),
/// args: vec![OscType::Float(82.5)]
/// }),
/// OscPacket::Message(OscMessage{
/// addr: "/osc2/freq".to_string(),
/// args: vec![OscType::Float(144.0)]
/// }),
/// ];
/// assert!(encoder::encode_tcp(&packets).is_ok())
/// ```
///
/// [OSC specification]: https://opensoundcontrol.stanford.edu/spec-1_0.html#osc-packets
pub fn encode_tcp(packets: &[OscPacket]) -> crate::types::Result<Vec<u8>> {
// TODO(2023-05-14): Do we need to make the pre-allocation amount configurable?
let mut encoded_bytes = Vec::with_capacity(1024);
for packet in packets.iter() {
let bytes = encode(packet)?;
let size: u32 = bytes
.len()
.try_into()
.map_err(|_| crate::OscError::BadPacket(""))?;
encoded_bytes.extend(size.to_be_bytes());
encoded_bytes.extend(bytes);
}

Ok(encoded_bytes)
}

/// Takes a reference to an OSC packet and writes the
/// encoded bytes to the given output. On success, the
/// number of bytes written will be returned. If an error
Expand Down
182 changes: 178 additions & 4 deletions tests/decode_encode_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const GOLDEN_MESSAGE_WO_ARGS: &str = "2f736f6d652f6164647200002c000000";
const GOLDEN_MESSAGE_WITH_ALL_TYPES: &str = "2f616e6f746865722f616464726573732f3100002c69686664737362746346544e496d725b695b64645d735d0000000000000004000000000000002a40490fda400921fb54442eea54686973206973206120737472696e672e00000054686973206973206120737472696e6720746f6f2e00000000000003010203000000007b000001c80000006304292a81ffc02a0d0000002a3ff3ae147ae147ae4009ae147ae147ae59617900";
const GOLDEN_EMPTY_BUNDLE: &str = "2362756e646c65000000000400000002";
const GOLDEN_BUNDLE: &str = "2362756e646c6500000004d2000010e10000000c2f766965772f31002c000000000000202f6d697865722f6368616e6e656c2f312f616d70000000002c6600003f666666000000442362756e646c65000000162e0000223d000000142f6f73632f312f66726571002c690000000001b8000000182f6f73632f312f7068617365000000002c660000becccccd";
const GOLDEN_MULTI_PACKET: &str = "000000102f736f6d652f6164647200002c0000000000008c2362756e646c6500000004d2000010e10000000c2f766965772f31002c000000000000202f6d697865722f6368616e6e656c2f312f616d70000000002c6600003f666666000000442362756e646c65000000162e0000223d000000142f6f73632f312f66726571002c690000000001b8000000182f6f73632f312f7068617365000000002c660000becccccd";
const GOLDEN_MULTI_PACKET_BUNDLE_FIRST: &str = "0000008c2362756e646c6500000004d2000010e10000000c2f766965772f31002c000000000000202f6d697865722f6368616e6e656c2f312f616d70000000002c6600003f666666000000442362756e646c65000000162e0000223d000000142f6f73632f312f66726571002c690000000001b8000000182f6f73632f312f7068617365000000002c660000becccccd000000102f736f6d652f6164647200002c000000";

fn prefix_with_size(size: u32, golden: &str) -> String {
[hex::encode(size.to_be_bytes()), golden.into()].concat()
}

#[test]
fn test_message_wo_args() {
Expand All @@ -17,12 +23,24 @@ fn test_message_wo_args() {
args: vec![],
});

// Datagram encoding and decoding.
let bytes = encoder::encode(&packet).expect("encode failed");
assert_eq!(hex::decode(GOLDEN_MESSAGE_WO_ARGS).unwrap(), bytes);

let (tail, decoded_packet) = decoder::decode_udp(&bytes).expect("decode failed");
assert_eq!(0, tail.len());
assert_eq!(packet, decoded_packet)
assert_eq!(packet, decoded_packet);

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&[packet.clone()]).expect("stream encode failed");
assert_eq!(
prefix_with_size(16, GOLDEN_MESSAGE_WO_ARGS),
hex::encode(&bytes)
);

let (tail, decoded_packet) = decoder::decode_tcp(&bytes).expect("stream decode failed");
assert_eq!(0, tail.len());
assert_eq!(Some(packet), decoded_packet);
}

#[test]
Expand Down Expand Up @@ -71,12 +89,24 @@ fn test_encode_message_with_all_types() {
],
});

// Datagram encoding and decoding.
let bytes = encoder::encode(&packet).expect("encode failed");
assert_eq!(hex::decode(GOLDEN_MESSAGE_WITH_ALL_TYPES).unwrap(), bytes);

let (tail, decoded_packet) = decoder::decode_udp(&bytes).expect("decode failed");
assert_eq!(0, tail.len());
assert_eq!(packet, decoded_packet)
assert_eq!(packet, decoded_packet);

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&[packet.clone()]).expect("stream encode failed");
assert_eq!(
prefix_with_size(168, GOLDEN_MESSAGE_WITH_ALL_TYPES),
hex::encode(&bytes)
);

let (tail, decoded_packet) = decoder::decode_tcp(&bytes).expect("stream decode failed");
assert_eq!(0, tail.len());
assert_eq!(Some(packet), decoded_packet);
}

#[test]
Expand All @@ -86,12 +116,24 @@ fn test_empty_bundle() {
content: vec![],
});

// Datagram encoding and decoding.
let bytes = encoder::encode(&packet).expect("encode failed");
assert_eq!(hex::decode(GOLDEN_EMPTY_BUNDLE).unwrap(), bytes);

let (tail, decoded_packet) = decoder::decode_udp(&bytes).expect("decode failed");
assert_eq!(0, tail.len());
assert_eq!(packet, decoded_packet)
assert_eq!(packet, decoded_packet);

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&[packet.clone()]).expect("stream encode failed");
assert_eq!(
prefix_with_size(16, GOLDEN_EMPTY_BUNDLE),
hex::encode(&bytes)
);

let (tail, decoded_packet) = decoder::decode_tcp(&bytes).expect("stream decode failed");
assert_eq!(0, tail.len());
assert_eq!(Some(packet), decoded_packet);
}

#[test]
Expand Down Expand Up @@ -123,12 +165,144 @@ fn test_bundle() {
],
});

// Datagram encoding and decoding.
let bytes = encoder::encode(&packet).expect("encode failed");
assert_eq!(hex::decode(GOLDEN_BUNDLE).unwrap(), bytes);

let (tail, decoded_packet) = decoder::decode_udp(&bytes).expect("decode failed");
assert_eq!(0, tail.len());
assert_eq!(packet, decoded_packet)
assert_eq!(packet, decoded_packet);

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&[packet.clone()]).expect("stream encode failed");
assert_eq!(prefix_with_size(140, GOLDEN_BUNDLE), hex::encode(&bytes));

let (tail, decoded_packet) = decoder::decode_tcp(&bytes).expect("stream decode failed");
assert_eq!(0, tail.len());
assert_eq!(Some(packet), decoded_packet);
}

#[test]
fn test_multi_packet_message() {
let packets = vec![
OscPacket::Message(OscMessage {
addr: "/some/addr".to_string(),
args: vec![],
}),
OscPacket::Bundle(OscBundle {
timetag: (1234, 4321).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/view/1".to_string(),
args: vec![],
}),
OscPacket::Message(OscMessage {
addr: "/mixer/channel/1/amp".to_string(),
args: vec![0.9f32.into()],
}),
OscPacket::Bundle(OscBundle {
timetag: (5678, 8765).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/osc/1/freq".to_string(),
args: vec![440i32.into()],
}),
OscPacket::Message(OscMessage {
addr: "/osc/1/phase".to_string(),
args: vec![(-0.4f32).into()],
}),
],
}),
],
}),
];

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&packets).expect("stream encode failed");
assert_eq!(GOLDEN_MULTI_PACKET, hex::encode(&bytes));

let (remainder, decoded_packets) =
decoder::decode_tcp_vec(&bytes).expect("stream decode failed");
assert!(remainder.is_empty());
assert_eq!(packets, decoded_packets);
}

#[test]
fn test_multi_packet_message_bundle_first() {
let packets = vec![
OscPacket::Bundle(OscBundle {
timetag: (1234, 4321).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/view/1".to_string(),
args: vec![],
}),
OscPacket::Message(OscMessage {
addr: "/mixer/channel/1/amp".to_string(),
args: vec![0.9f32.into()],
}),
OscPacket::Bundle(OscBundle {
timetag: (5678, 8765).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/osc/1/freq".to_string(),
args: vec![440i32.into()],
}),
OscPacket::Message(OscMessage {
addr: "/osc/1/phase".to_string(),
args: vec![(-0.4f32).into()],
}),
],
}),
],
}),
OscPacket::Message(OscMessage {
addr: "/some/addr".to_string(),
args: vec![],
}),
];

// Stream encoding and decoding.
let bytes = encoder::encode_tcp(&packets).expect("stream encode failed");
assert_eq!(GOLDEN_MULTI_PACKET_BUNDLE_FIRST, hex::encode(&bytes));

let (remainder, decoded_packets) =
decoder::decode_tcp_vec(&bytes).expect("stream decode failed");
assert!(remainder.is_empty());
// Note that OSC packets following an OSC bundle get included into the OSC bundles contents.
assert_eq!(
vec![OscPacket::Bundle(OscBundle {
timetag: (1234, 4321).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/view/1".to_string(),
args: vec![],
}),
OscPacket::Message(OscMessage {
addr: "/mixer/channel/1/amp".to_string(),
args: vec![0.9f32.into()],
}),
OscPacket::Bundle(OscBundle {
timetag: (5678, 8765).into(),
content: vec![
OscPacket::Message(OscMessage {
addr: "/osc/1/freq".to_string(),
args: vec![440i32.into()],
}),
OscPacket::Message(OscMessage {
addr: "/osc/1/phase".to_string(),
args: vec![(-0.4f32).into()],
}),
],
}),
OscPacket::Message(OscMessage {
addr: "/some/addr".to_string(),
args: vec![],
}),
],
}),],
decoded_packets
);
}

#[cfg(feature = "std")]
Expand Down

0 comments on commit 0865e66

Please sign in to comment.