Skip to main content

rill_osc/
server.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use tokio::net::UdpSocket;
5
6use crate::error::Error;
7use crate::osc::{self, OscMessage, OscPacket};
8
9type Handler = Arc<dyn Fn(OscMessage, SocketAddr) + Send + Sync + 'static>;
10
11/// A UDP OSC server that listens for packets and dispatches them to
12/// registered pattern handlers.
13pub struct OscServer {
14    socket: Arc<UdpSocket>,
15    handlers: Vec<(String, Handler)>,
16    buffer_size: usize,
17}
18
19impl OscServer {
20    /// Bind the server to the given UDP socket address.
21    pub async fn bind(addr: impl tokio::net::ToSocketAddrs) -> Result<Self, Error> {
22        let socket = UdpSocket::bind(addr).await?;
23        Ok(Self {
24            socket: Arc::new(socket),
25            handlers: Vec::new(),
26            buffer_size: 65536,
27        })
28    }
29
30    /// Register a handler for OSC messages matching the given pattern.
31    pub fn handle<F>(&mut self, pattern: impl Into<String>, f: F)
32    where
33        F: Fn(OscMessage, SocketAddr) + Send + Sync + 'static,
34    {
35        self.handlers.push((pattern.into(), Arc::new(f)));
36    }
37
38    /// Set the receive buffer size in bytes (default 65536).
39    pub fn set_buffer_size(&mut self, size: usize) {
40        self.buffer_size = size;
41    }
42
43    /// Get the local socket address the server is bound to.
44    pub fn local_addr(&self) -> Result<SocketAddr, Error> {
45        Ok(self.socket.local_addr()?)
46    }
47
48    /// Run the OSC server loop, receiving and dispatching packets forever.
49    pub async fn run(&self) -> Result<(), Error> {
50        let mut buf = vec![0u8; self.buffer_size];
51        loop {
52            let (n, src) = self.socket.recv_from(&mut buf).await?;
53            let data = &buf[..n];
54            match osc::decode(data) {
55                Ok(packet) => {
56                    if let Err(e) = self.dispatch(packet, src) {
57                        log::warn!("dispatch error: {}", e);
58                    }
59                }
60                Err(e) => {
61                    log::warn!("failed to decode OSC packet from {}: {}", src, e);
62                }
63            }
64        }
65    }
66
67    fn dispatch(&self, packet: OscPacket, src: SocketAddr) -> Result<(), Error> {
68        match packet {
69            OscPacket::Message(msg) => {
70                for (pattern, handler) in &self.handlers {
71                    if osc::pattern_match(pattern, &msg.addr) {
72                        handler(msg.clone(), src);
73                    }
74                }
75                Ok(())
76            }
77            OscPacket::Bundle(bundle) => {
78                for p in bundle.packets {
79                    self.dispatch(p, src)?;
80                }
81                Ok(())
82            }
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::osc::{OscBundle, OscMessage, OscPacket, OscType, TimeTag};
91    use std::sync::atomic::{AtomicBool, Ordering};
92    use std::sync::Arc;
93
94    #[test]
95    fn test_pattern_registration() {
96        let server = OscServer::bind("127.0.0.1:0");
97        let rt = tokio::runtime::Builder::new_current_thread()
98            .enable_io()
99            .build()
100            .unwrap();
101        let mut server = rt.block_on(server).unwrap();
102
103        let called = Arc::new(AtomicBool::new(false));
104        let called_clone = called.clone();
105
106        server.handle("/test", move |_msg, _src| {
107            called_clone.store(true, Ordering::SeqCst);
108        });
109
110        assert_eq!(server.handlers.len(), 1);
111
112        let msg = OscMessage {
113            addr: "/test".into(),
114            args: vec![OscType::Int(42)],
115        };
116
117        server
118            .dispatch(OscPacket::Message(msg), "127.0.0.1:0".parse().unwrap())
119            .unwrap();
120
121        assert!(called.load(Ordering::SeqCst));
122    }
123
124    #[test]
125    fn test_dispatch_bundle_unwraps() {
126        let server = OscServer::bind("127.0.0.1:0");
127        let rt = tokio::runtime::Builder::new_current_thread()
128            .enable_io()
129            .build()
130            .unwrap();
131        let mut server = rt.block_on(server).unwrap();
132
133        let count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
134        let count_clone = count.clone();
135
136        server.handle("/*", move |_msg, _src| {
137            count_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
138        });
139
140        let bundle = OscBundle {
141            timetag: TimeTag::immediate(),
142            packets: vec![
143                OscPacket::Message(OscMessage {
144                    addr: "/a".into(),
145                    args: vec![],
146                }),
147                OscPacket::Message(OscMessage {
148                    addr: "/b".into(),
149                    args: vec![],
150                }),
151            ],
152        };
153
154        server
155            .dispatch(OscPacket::Bundle(bundle), "127.0.0.1:0".parse().unwrap())
156            .unwrap();
157
158        assert_eq!(count.load(std::sync::atomic::Ordering::SeqCst), 2);
159    }
160
161    #[test]
162    fn test_no_handler_match() {
163        let server = OscServer::bind("127.0.0.1:0");
164        let rt = tokio::runtime::Builder::new_current_thread()
165            .enable_io()
166            .build()
167            .unwrap();
168        let mut server = rt.block_on(server).unwrap();
169
170        server.handle("/audio/volume", |_msg, _src| {});
171
172        let msg = OscMessage {
173            addr: "/mixer/pan".into(),
174            args: vec![],
175        };
176
177        let result = server.dispatch(OscPacket::Message(msg), "127.0.0.1:0".parse().unwrap());
178        assert!(result.is_ok());
179    }
180
181    #[test]
182    fn test_set_buffer_size() {
183        let server = OscServer::bind("127.0.0.1:0");
184        let rt = tokio::runtime::Builder::new_current_thread()
185            .enable_io()
186            .build()
187            .unwrap();
188        let mut server = rt.block_on(server).unwrap();
189        server.set_buffer_size(1024);
190        assert_eq!(server.buffer_size, 1024);
191    }
192
193    #[test]
194    fn test_local_addr() {
195        let server = OscServer::bind("127.0.0.1:0");
196        let rt = tokio::runtime::Builder::new_current_thread()
197            .enable_io()
198            .build()
199            .unwrap();
200        let server = rt.block_on(server).unwrap();
201        let addr = server.local_addr().unwrap();
202        assert!(addr.port() > 0);
203    }
204}