Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement stream encoding #49

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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