fudi_rs/
lib.rs

1//! This crate enables the communication between Rust programs
2//! and Pure Data over a network using the FUDI protocol.
3//!
4//! # Examples
5//! Create and send a bang to a Pure Data instance with a netreceive object listening
6//! on 127.0.0.1:5678 for UDP traffic.
7//! ```rust
8//! let netsend = fudi_rs::NetSendUdp::new("127.0.0.1:5678");
9//! let msg = fudi_rs::PdMessage::Bang;
10//! netsend.send(&msg).expect("sending message failed");
11//! ```
12//!
13//! # References
14//! * [Pure Data](http://puredata.info/)
15//! * [FUDI specification](https://web.archive.org/web/20120304071510/http://wiki.puredata.info/en/FUDI) (via archive.org)
16//! * [wikipedia: FUDI](https://en.wikipedia.org/wiki/FUDI)
17//! * [FLOSS Manuals: Pure Data - messages](http://write.flossmanuals.net/pure-data/messages/)
18//! * [FLOSS manuals: Pure Data - send and receive](http://write.flossmanuals.net/pure-data/send-and-receive/)
19//! * [Pure Data message implementation notes](https://puredata.info/dev/PdMessages)
20//! * [undocumented internal messages](https://puredata.info/docs/tutorials/TipsAndTricks#undocumented-pd-internal-messages)
21
22use std::io::{Error, ErrorKind, Result};
23use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
24use std::str::FromStr;
25
26#[macro_use]
27extern crate nom;
28
29mod parser;
30
31/// An implementation of the most generic Pure Data message type.
32#[derive(Debug)]
33pub struct GenericMessage {
34    selector: String,
35    atoms: Vec<String>,
36}
37
38/// An implementation of the (generic) atom data type.
39#[derive(Debug)]
40pub struct Atom {
41    number: Option<f32>,
42    word: Option<String>,
43}
44
45/// An incomplete implementation of Pure Data message types.
46///
47/// # implemented
48/// * Float messages
49/// * Symbol messages (based on strings)
50/// * Bang messages
51/// * custom/generic message
52///
53/// # not implemented
54/// * list
55/// * pointer
56///
57/// # Examples
58/// Create a message to send a (floating point) number.
59/// ```rust
60/// let msg = fudi_rs::PdMessage::Float(23.42);
61/// ```
62///
63/// # references
64/// * [FLOSS Manuals: Pure Data - messages](http://write.flossmanuals.net/pure-data/messages/)
65/// * [puredata.info: PdMessages](https://puredata.info/dev/PdMessages)
66#[derive(Debug)]
67pub enum PdMessage {
68    Float(f32),
69    Symbol(String),
70    Bang,
71    List(Vec<Atom>),
72    Generic(GenericMessage),
73}
74
75impl PdMessage {
76    /// Generate a message string for the (given) message type.
77    /// # note
78    /// A message needs a trailing newline (i.e. '\n') according to the Java example in the [old wiki page](https://web.archive.org/web/20120304071510/http://wiki.puredata.info/en/FUDI). This is not explicitly mentioned in the FUDI specification.
79    fn to_text(&self) -> String {
80        let mut payload: String;
81        match &self {
82            PdMessage::Float(f) => payload = format!("float {}", f),
83            PdMessage::Symbol(word) => payload = format!("symbol {}", word),
84            PdMessage::Bang => payload = String::from("bang"),
85            PdMessage::List(items) => {
86                payload = String::from("list");
87                for atom in items.iter() {}
88            }
89            PdMessage::Generic(msg) => {
90                payload = msg.selector.clone();
91                for atom in msg.atoms.iter() {
92                    payload = payload + " " + atom;
93                }
94            }
95        }
96        payload = format!("{};\n", payload); // newline not in spec, but in vanilla pd
97        payload
98    }
99}
100
101#[cfg(test)]
102mod test_pdmessage {
103    use super::*;
104
105    #[test]
106    fn generate_float_message() {
107        let msg = PdMessage::Float(2.974);
108        assert_eq!(String::from("float 2.974;\n"), msg.to_text());
109    }
110
111    #[test]
112    fn generate_symbol_message() {
113        let msg = PdMessage::Symbol(String::from("foobar"));
114        assert_eq!(String::from("symbol foobar;\n"), msg.to_text());
115    }
116
117    #[test]
118    fn generate_bang_message() {
119        let msg = PdMessage::Bang;
120        assert_eq!(String::from("bang;\n"), msg.to_text());
121    }
122
123    #[test]
124    fn generate_generic_message() {
125        let msg = PdMessage::Generic(GenericMessage {
126            selector: String::from("selector"),
127            atoms: vec!["one".to_string(), "two".to_string(), "17.9".to_string()],
128        });
129        assert_eq!(String::from("selector one two 17.9;\n"), msg.to_text());
130    }
131}
132
133/// Encapsulate sending Pure Date messages via FUDI over UDP.
134/// This is the library equivalent of the netsend-object for UDP.
135///
136/// # references
137/// * [FLOSS manuals: Pure Data - send and receive](http://write.flossmanuals.net/pure-data/send-and-receive/)
138pub struct NetSendUdp {
139    target: SocketAddr,
140    socket: UdpSocket,
141}
142
143impl NetSendUdp {
144    /// Create a new instance and set target address.
145    ///
146    /// # Arguments
147    /// * `target` - target host (& port) to send messages to
148    pub fn new(target: &str) -> crate::NetSendUdp {
149        NetSendUdp {
150            target: SocketAddr::from_str(target).expect("failed to parse target address"),
151            socket: UdpSocket::bind("0.0.0.0:0").expect("failed to bind host socket"),
152        }
153    }
154
155    /// Send a message to the target and return the number of bytes sent.
156    ///
157    /// # Arguments
158    /// * `msg` - message to send to the target
159    pub fn send(&self, msg: &PdMessage) -> Result<usize> {
160        self.socket.send_to(msg.to_text().as_bytes(), self.target)
161    }
162}
163
164#[cfg(test)]
165mod test_netsendudp {
166    use super::*;
167
168    #[test]
169    fn create_udp_netsend_test_target() {
170        let target = "127.0.0.1:8989";
171        let ns = NetSendUdp::new(&String::from(target));
172
173        assert_eq!(ns.target.is_ipv4(), true);
174        assert_eq!(ns.target.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
175        assert_eq!(ns.target.port(), 8989);
176    }
177
178    #[test]
179    fn send_bang_into_ether() {
180        let msg = PdMessage::Bang;
181        let target = "127.0.0.1:8989";
182        let ns = NetSendUdp::new(&String::from(target));
183        let res = ns.send(&msg);
184        match res {
185            Ok(bsend) => assert_eq!(bsend, 6),
186            Err(fail) => panic!(fail),
187        }
188    }
189
190    #[test]
191    fn send_float_into_ether() {
192        let msg = PdMessage::Float(432.0);
193        let target = "127.0.0.1:8989";
194        let ns = NetSendUdp::new(&String::from(target));
195        let res = ns.send(&msg);
196        match res {
197            Ok(bsend) => assert_eq!(bsend, 11),
198            Err(fail) => panic!(fail),
199        }
200    }
201}
202
203/// Encapsulate receiving Pure Date messages via FUDI over UDP.
204/// This is the library equivalent of the netreceive-object for UDP.
205///
206/// # references
207/// * [FLOSS manuals: Pure Data - send and receive](http://write.flossmanuals.net/pure-data/send-and-receive/)
208pub struct NetReceiveUdp {
209    socket: UdpSocket,
210}
211
212impl NetReceiveUdp {
213    /// Create a new instance and set address to listen on.
214    ///
215    /// # Arguments
216    /// * `addr` - host (& port) to listen for messages
217    pub fn new(addr: &str) -> crate::NetReceiveUdp {
218        let laddr = SocketAddr::from_str(addr).expect("failed to parse target address");
219        NetReceiveUdp {
220            socket: UdpSocket::bind(laddr).expect("failed to bind socket to host"),
221        }
222    }
223
224    /// Receive binary data via UDP.
225    ///
226    /// *note*: This function panics upon errors.
227    pub fn receive_binary(&self) -> Vec<u8> {
228        // max 65,507 bytes (65,535 − 8 byte UDP header − 20 byte IP header)
229        let mut buffer = [0; 65535 - 8 - 20];
230        let recv_result = self.socket.recv_from(&mut buffer);
231        let mut data;
232        match recv_result {
233            Ok((amount, _)) => data = Vec::from(&buffer[..amount]),
234            Err(e) => panic!("receiving data failed: {:?}", e),
235        }
236        data
237    }
238
239    /// Receive Pure Data messages via UDP.
240    pub fn receive(&self) -> Result<PdMessage> {
241        let payload = self.receive_binary();
242        let res = parser::get_message(payload.as_slice());
243        match res {
244            Ok(msg) => Ok(msg),
245            Err(msg) => {
246                let err = Error::new(ErrorKind::InvalidData, msg);
247                Err(err)
248            }
249        }
250    }
251}
252
253#[cfg(test)]
254mod test_netreceiveudp {
255    use super::*;
256
257    #[test]
258    fn create_udp_netreceiveudp_test_target() {
259        // create netreceive
260        let target = "127.0.0.1:8989";
261        let nr = NetReceiveUdp::new(&String::from(target));
262
263        // extract socket from netreceive
264        let nr_socket = nr
265            .socket
266            .local_addr()
267            .expect("could not retrieve socket address");
268
269        // test properties
270        assert_eq!(nr_socket.is_ipv4(), true);
271        assert_eq!(nr_socket.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
272        assert_eq!(nr_socket.port(), 8989);
273    }
274}