Skip to main content

open_sound_control/
message.rs

1use crate::argument::OscArgument;
2use crate::helpers;
3use crate::helpers::OscParseError;
4
5/// Represents an OSC message
6pub struct OscMessage {
7    pub address: String,
8    pub arguments: Vec<OscArgument>
9}
10
11impl OscMessage {
12
13  /// Constructs a new OscMessage with a given address and zero arguments
14  pub fn new(address: impl Into<String>) -> Self {
15        Self { 
16          address: address.into(),
17          arguments: Vec::new()
18        }
19  }
20
21  /// Adds an argument to the OscMessage and returns the modified message
22  pub fn with_arg(mut self, arg: OscArgument) -> Self {
23        self.arguments.push(arg);
24        self
25  }
26
27  /// Construct an OscMessage from a sequence of bytes
28  pub fn from_bytes(bytes: &[u8]) -> Result<Self, OscParseError> {
29
30    if bytes.is_empty() {
31      return Err(OscParseError::NotEnoughData);
32    }
33
34    // Extract Address Pattern
35    let address_pattern_null_pos = bytes.iter().position(|&b| b == 0).ok_or(OscParseError::InvalidFormat)?;
36    let address_pattern_bytes = &bytes[0..address_pattern_null_pos];
37    let address_pattern = String::from_utf8(address_pattern_bytes.to_vec()).map_err(|_| OscParseError::InvalidString)?;
38    
39    // check address pattern starts with '/'
40    if ! address_pattern.starts_with("/") {
41      return Err(OscParseError::InvalidString);
42    }
43
44    // Extract Type Tag String
45    let comma_pos = address_pattern_null_pos + bytes[address_pattern_null_pos..].iter().position(|&b| b == 0x2c).ok_or(OscParseError::InvalidFormat)?;
46    let type_tag_null_pos = comma_pos + bytes[comma_pos..].iter().position(|&b| b == 0).ok_or(OscParseError::InvalidFormat)?;
47    let type_tag_bytes = &bytes[comma_pos..type_tag_null_pos];
48    let type_tag_string = String::from_utf8(type_tag_bytes.to_vec()).map_err(|_| OscParseError::InvalidString)?;
49
50    let mut arguments = Vec::<OscArgument>::new();
51
52    // Process arguments
53    let mut arguments_index: usize = (type_tag_null_pos + 1 + 3) & !3;
54
55    for tag in type_tag_string[1..].chars() {
56      let argument = OscArgument::from_bytes(bytes, &mut arguments_index, tag).map_err(|_| OscParseError::CouldNotParseArguments)?;
57      arguments.push (argument);
58    }
59
60    Ok(OscMessage { address: address_pattern, arguments })
61  }
62
63  /// Construct an OscMessage from a sequence of bytes
64  pub fn to_string(&self) -> String {
65    let mut result = String::new();
66    result.push_str(&self.address);
67    result.push_str(" ");
68
69    for (i, arg) in self.arguments.iter().enumerate() {
70      result.push_str(&arg.to_string());
71      result.push_str( if i < self.arguments.len() - 1 { ", " } else { "" } );
72    }
73    result
74  }
75
76  /// Convert an OscMessage to a vector of bytes
77  pub fn to_bytes(&self) -> Vec<u8> {
78    [
79      self.address_pattern_in_bytes(),
80      self.type_tag_string_in_bytes(),
81      self.arguments_in_bytes()
82    ].concat()
83  }
84
85  fn address_pattern_in_bytes(&self) -> Vec<u8> {
86    let mut bytes: Vec<u8> = self.address.as_bytes().to_vec();
87    bytes.push(b'\0');
88    helpers::pad_to_multiple_of_4_bytes(&mut bytes);
89    bytes
90  }
91
92  fn type_tag_string_in_bytes(&self) -> Vec<u8> {
93    let mut bytes: Vec<u8> = vec![b','];
94    bytes.extend(self.arguments.iter().map(|a| a.type_tag() as u8));
95    bytes.push(b'\0');
96    helpers::pad_to_multiple_of_4_bytes(&mut bytes);
97    bytes
98  }
99
100  fn arguments_in_bytes(&self) -> Vec<u8> {
101    self.arguments.iter().flat_map(|a| a.to_bytes()).collect()
102  }
103}
104
105//==================================================================
106// Tests
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    //------------------------------------------------------------------
112    #[test]
113    fn test_construct_message1() {
114      let m1 = OscMessage {
115        address: String::from("/new/message"),
116        arguments: vec![
117          OscArgument::Int32(3),
118          OscArgument::Float32(0.4)
119        ]
120      };
121
122      assert_eq!(m1.address, "/new/message");
123      assert_eq!(m1.arguments.len(), 2);
124      assert_eq!(m1.arguments.get(0), Some(&OscArgument::Int32(3)));
125      assert_eq!(m1.arguments.get(1), Some(&OscArgument::Float32(0.4)));
126      assert_eq!(m1.arguments.get(2), None);
127    }
128
129    //------------------------------------------------------------------
130    #[test]
131    fn test_construct_message2() {
132      let m2 = OscMessage::new("/nice");
133
134      assert_eq!(m2.address, "/nice");
135      assert_eq!(m2.arguments.len(), 0);
136    }
137
138    //------------------------------------------------------------------
139    #[test]
140    fn test_convert_message_to_bytes1() {
141      let m1 = OscMessage {
142        address: String::from("/oscillator/4/frequency"),
143        arguments: vec![
144          OscArgument::Float32(440.0)
145        ]
146      };
147
148      let bytes = m1.to_bytes();
149
150      let expected_bytes: Vec<u8> = vec![
151          0x2f, 0x6f, 0x73, 0x63,
152          0x69, 0x6c, 0x6c, 0x61,
153          0x74, 0x6f, 0x72, 0x2f,
154          0x34, 0x2f, 0x66, 0x72,
155          0x65, 0x71, 0x75, 0x65,
156          0x6e, 0x63, 0x79, 0x00,
157          0x2c, 0x66, 0x00, 0x00,
158          0x43, 0xdc, 0x00, 0x00,
159      ];
160
161      assert_eq!(bytes, expected_bytes);
162
163      let result = OscMessage::from_bytes(&bytes);
164      assert!(result.is_ok(), "Expected OscMessage ok, got: {:?}", result.err());
165
166      let m2 = result.unwrap();
167      assert_eq!(m2.address, "/oscillator/4/frequency");
168      assert_eq!(m2.arguments.len(), 1);
169      assert_eq!(m2.arguments.get(0), Some(&OscArgument::Float32(440.0)));
170    }
171
172    //------------------------------------------------------------------
173    #[test]
174    fn test_convert_message_to_bytes2() {
175      let m1 = OscMessage {
176        address: String::from("/foo"),
177        arguments: vec![
178          OscArgument::Int32(1000),
179          OscArgument::Int32(-1),
180          OscArgument::String("hello".to_string()),
181          OscArgument::Float32(1.234),
182          OscArgument::Float32(5.678)
183        ]
184      };
185
186      let bytes = m1.to_bytes();
187
188      let expected_bytes: Vec<u8> = vec![
189          0x2f, 0x66, 0x6f, 0x6f,
190          0x00, 0x00, 0x00, 0x00,
191          0x2c, 0x69, 0x69, 0x73,
192          0x66, 0x66, 0x00, 0x00,
193          0x00, 0x00, 0x03, 0xe8,
194          0xff, 0xff, 0xff, 0xff,
195          0x68, 0x65, 0x6c, 0x6c,
196          0x6f, 0x00, 0x00, 0x00,
197          0x3f, 0x9d, 0xf3, 0xb6,
198          0x40, 0xb5, 0xb2, 0x2d,
199      ];
200
201      assert_eq!(bytes, expected_bytes);
202
203      let result = OscMessage::from_bytes(&bytes);
204      assert!(result.is_ok(), "Expected OscMessage ok, got: {:?}", result.err());
205
206      let m2 = result.unwrap();
207      assert_eq!(m2.address, "/foo");
208      assert_eq!(m2.arguments.len(), 5);
209      assert_eq!(m2.arguments.get(0), Some(&OscArgument::Int32(1000)));
210      assert_eq!(m2.arguments.get(1), Some(&OscArgument::Int32(-1)));
211      assert_eq!(m2.arguments.get(2), Some(&OscArgument::String("hello".to_string())));
212      assert_eq!(m2.arguments.get(3), Some(&OscArgument::Float32(1.234)));
213      assert_eq!(m2.arguments.get(4), Some(&OscArgument::Float32(5.678)));
214    }
215
216    #[test]
217    fn test_empty_arguments() {
218        let msg = OscMessage::new("/test");
219        let bytes = msg.to_bytes();
220        let decoded = OscMessage::from_bytes(&bytes).unwrap();
221        assert_eq!(decoded.address, "/test");
222        assert_eq!(decoded.arguments.len(), 0);
223    }
224
225    #[test]
226    fn test_invalid_address_no_slash() {
227        let bytes = b"invalid\0\0\0\0,\0\0\0";
228        assert!(matches!(
229            OscMessage::from_bytes(bytes),
230            Err(OscParseError::InvalidString)
231        ));
232    }
233
234    #[test]
235    fn test_truncated_message() {
236        let bytes = b"/test\0\0\0,i"; // incomplete
237        assert!(OscMessage::from_bytes(bytes).is_err());
238    }
239
240    #[test]
241    fn test_to_string() {
242        let msg = OscMessage {
243            address: "/example".to_string(),
244            arguments: vec![
245                OscArgument::Int32(42),
246                OscArgument::Float32(3.14),
247                OscArgument::String("hello".to_string()),
248                OscArgument::Blob(vec![0x01, 0x02, 0x03])
249            ],
250        };
251        let msg_str = msg.to_string();
252        assert_eq!(msg_str, "/example 42, 3.14, \"hello\", Blob(3 bytes)");
253    }
254}