1pub mod handler;
4
5use std::ffi::{CStr, CString};
6use std::marker::PhantomData;
7use std::net::IpAddr;
8use std::os::raw::{c_char, c_int, c_void};
9use std::ptr;
10use std::sync::Mutex;
11
12pub use handler::Handler;
13use libjuice_sys as sys;
14
15use crate::error::Error;
16use crate::log::ensure_logging;
17use crate::Result;
18
19fn raw_retcode_to_result(retcode: c_int) -> Result<()> {
21 match retcode {
22 0 => Ok(()),
23 sys::JUICE_ERR_INVALID => Err(Error::InvalidArgument),
24 sys::JUICE_ERR_FAILED => Err(Error::Failed),
25 sys::JUICE_ERR_NOT_AVAIL => Err(Error::NotAvailable),
26 _ => unreachable!(),
27 }
28}
29
30pub struct Builder {
32 stun_server: Option<StunServer>,
33 port_range: Option<(u16, u16)>,
34 bind_address: Option<CString>,
35 turn_servers: Vec<TurnServer>,
36 handler: Handler,
37}
38
39impl Builder {
40 fn new(handler: Handler) -> Self {
42 Builder {
43 stun_server: None,
44 port_range: None,
45 bind_address: None,
46 turn_servers: vec![],
47 handler,
48 }
49 }
50
51 pub fn with_stun(mut self, host: String, port: u16) -> Self {
53 self.stun_server = Some(StunServer::new(host, port).unwrap());
54 self
55 }
56
57 pub fn with_port_range(mut self, begin: u16, end: u16) -> Self {
59 self.port_range = Some((begin, end));
60 self
61 }
62
63 pub fn with_bind_address(mut self, addr: &IpAddr) -> Self {
65 self.bind_address = Some(CString::new(addr.to_string()).unwrap()); self
67 }
68
69 pub fn add_turn_server<T>(mut self, host: T, port: u16, user: T, pass: T) -> Result<Self>
71 where
72 T: Into<Vec<u8>>,
73 {
74 let server = TurnServer {
75 host: CString::new(host).map_err(|_| Error::InvalidArgument)?,
76 port,
77 username: CString::new(user).map_err(|_| Error::InvalidArgument)?,
78 password: CString::new(pass).map_err(|_| Error::InvalidArgument)?,
79 };
80 self.turn_servers.push(server);
81
82 Ok(self)
83 }
84
85 pub fn build(self) -> crate::Result<Agent> {
87 ensure_logging();
88
89 let mut holder = Box::new(Holder {
90 agent: ptr::null_mut(),
91 handler: Mutex::new(self.handler),
92 _marker: PhantomData::default(),
93 });
94
95 let port_range = self.port_range.unwrap_or((0, 0));
97 let stun_server = self.stun_server.unwrap_or_default();
99 let bind_address = self
100 .bind_address
101 .as_ref()
102 .map(|v| v.as_ptr())
103 .unwrap_or(ptr::null());
104
105 let servers = self
106 .turn_servers
107 .iter()
108 .map(|turn| sys::juice_turn_server {
109 host: turn.host.as_ptr(),
110 port: turn.port,
111 username: turn.username.as_ptr(),
112 password: turn.password.as_ptr(),
113 })
114 .collect::<Vec<_>>();
115
116 let turn_servers = if servers.is_empty() {
117 (ptr::null(), 0)
118 } else {
119 (servers.as_ptr(), servers.len() as _)
120 };
121
122 let config = &sys::juice_config {
123 stun_server_host: stun_server.0.as_ptr(),
124 stun_server_port: stun_server.1,
125 turn_servers: turn_servers.0 as _,
126 turn_servers_count: turn_servers.1,
127 bind_address,
128 local_port_range_begin: port_range.0,
129 local_port_range_end: port_range.1,
130 cb_state_changed: Some(on_state_changed),
131 cb_candidate: Some(on_candidate),
132 cb_gathering_done: Some(on_gathering_done),
133 cb_recv: Some(on_recv),
134 user_ptr: holder.as_mut() as *mut Holder as _,
135 };
136
137 let ptr = unsafe { sys::juice_create(config as _) };
138 if ptr.is_null() {
139 Err(Error::Failed)
140 } else {
141 holder.agent = ptr;
142 Ok(Agent { holder })
143 }
144 }
145}
146
147pub struct Agent {
149 holder: Box<Holder>,
150}
151
152impl Agent {
153 pub fn builder(h: Handler) -> Builder {
155 Builder::new(h)
156 }
157
158 pub fn get_state(&self) -> State {
160 unsafe {
161 sys::juice_get_state(self.holder.agent)
162 .try_into()
163 .expect("failed to convert state")
164 }
165 }
166
167 pub fn get_local_description(&self) -> crate::Result<String> {
169 let mut buf = vec![0; sys::JUICE_MAX_SDP_STRING_LEN as _];
170 let res = unsafe {
171 let res = sys::juice_get_local_description(
172 self.holder.agent,
173 buf.as_mut_ptr(),
174 buf.len() as _,
175 );
176 let _ = raw_retcode_to_result(res)?;
177 let s = CStr::from_ptr(buf.as_mut_ptr());
178 String::from_utf8_lossy(s.to_bytes())
179 };
180 Ok(res.to_string())
181 }
182
183 pub fn gather_candidates(&self) -> crate::Result<()> {
185 let ret = unsafe { sys::juice_gather_candidates(self.holder.agent) };
186 raw_retcode_to_result(ret)
187 }
188
189 pub fn set_remote_description(&self, sdp: String) -> crate::Result<()> {
191 let s = CString::new(sdp).map_err(|_| Error::InvalidArgument)?;
192 let ret = unsafe { sys::juice_set_remote_description(self.holder.agent, s.as_ptr()) };
193 raw_retcode_to_result(ret)
194 }
195
196 pub fn add_remote_candidate(&self, sdp: String) -> crate::Result<()> {
198 let s = CString::new(sdp).map_err(|_| Error::InvalidArgument)?;
199 let ret = unsafe { sys::juice_add_remote_candidate(self.holder.agent, s.as_ptr()) };
200 raw_retcode_to_result(ret)
201 }
202
203 pub fn set_remote_gathering_done(&self) -> crate::Result<()> {
205 let ret = unsafe { sys::juice_set_remote_gathering_done(self.holder.agent) };
206 raw_retcode_to_result(ret)
207 }
208
209 pub fn send(&self, data: &[u8]) -> crate::Result<()> {
211 let ret =
212 unsafe { sys::juice_send(self.holder.agent, data.as_ptr() as _, data.len() as _) };
213 raw_retcode_to_result(ret)
214 }
215
216 pub fn get_selected_candidates(&self) -> crate::Result<(String, String)> {
218 let mut local = vec![0; sys::JUICE_MAX_SDP_STRING_LEN as _];
219 let mut remote = vec![0; sys::JUICE_MAX_SDP_STRING_LEN as _];
220 let ret = unsafe {
221 let res = sys::juice_get_selected_candidates(
222 self.holder.agent,
223 local.as_mut_ptr() as _,
224 local.len() as _,
225 remote.as_mut_ptr() as _,
226 remote.len() as _,
227 );
228 let _ = raw_retcode_to_result(res)?;
229 let l = CStr::from_ptr(local.as_mut_ptr());
230 let r = CStr::from_ptr(remote.as_mut_ptr());
231 (
232 String::from_utf8_lossy(l.to_bytes()).to_string(),
233 String::from_utf8_lossy(r.to_bytes()).to_string(),
234 )
235 };
236 Ok(ret)
237 }
238
239 pub fn get_selected_addresses(&self) -> crate::Result<(String, String)> {
240 let mut local = vec![0; sys::JUICE_MAX_SDP_STRING_LEN as _];
241 let mut remote = vec![0; sys::JUICE_MAX_SDP_STRING_LEN as _];
242 let ret = unsafe {
243 let res = sys::juice_get_selected_addresses(
244 self.holder.agent,
245 local.as_mut_ptr() as _,
246 local.len() as _,
247 remote.as_mut_ptr() as _,
248 remote.len() as _,
249 );
250 let _ = raw_retcode_to_result(res)?;
251 let l = CStr::from_ptr(local.as_mut_ptr());
252 let r = CStr::from_ptr(remote.as_mut_ptr());
253 (
254 String::from_utf8_lossy(l.to_bytes()).to_string(),
255 String::from_utf8_lossy(r.to_bytes()).to_string(),
256 )
257 };
258 Ok(ret)
259 }
260}
261
262pub(crate) struct Holder {
263 agent: *mut sys::juice_agent_t,
264 handler: Mutex<Handler>,
265 _marker: PhantomData<(sys::juice_agent, std::marker::PhantomPinned)>,
266}
267
268impl Drop for Holder {
269 fn drop(&mut self) {
270 unsafe { sys::juice_destroy(self.agent) }
271 }
272}
273
274unsafe impl Sync for Holder {}
276
277unsafe impl Send for Holder {}
278
279impl Holder {
280 pub(crate) fn on_state_changed(&self, state: State) {
281 let mut h = self.handler.lock().unwrap();
282 h.on_state_changed(state)
283 }
284
285 pub(crate) fn on_candidate(&self, candidate: String) {
286 let mut h = self.handler.lock().unwrap();
287 h.on_candidate(candidate)
288 }
289
290 pub(crate) fn on_gathering_done(&self) {
291 let mut h = self.handler.lock().unwrap();
292 h.on_gathering_done()
293 }
294
295 pub(crate) fn on_recv(&self, packet: &[u8]) {
296 let mut h = self.handler.lock().unwrap();
297 h.on_recv(packet)
298 }
299}
300
301#[derive(Debug, Copy, Clone, PartialEq)]
302pub enum State {
303 Disconnected,
304 Gathering,
305 Connecting,
306 Connected,
307 Completed,
308 Failed,
309}
310
311impl TryFrom<sys::juice_state> for State {
312 type Error = ();
313
314 fn try_from(value: sys::juice_state) -> std::result::Result<Self, Self::Error> {
315 Ok(match value {
316 sys::juice_state_JUICE_STATE_DISCONNECTED => State::Disconnected,
317 sys::juice_state_JUICE_STATE_GATHERING => State::Gathering,
318 sys::juice_state_JUICE_STATE_CONNECTING => State::Connecting,
319 sys::juice_state_JUICE_STATE_CONNECTED => State::Connected,
320 sys::juice_state_JUICE_STATE_COMPLETED => State::Completed,
321 sys::juice_state_JUICE_STATE_FAILED => State::Failed,
322 _ => return Err(()),
323 })
324 }
325}
326
327struct StunServer(CString, u16);
329
330impl Default for StunServer {
331 fn default() -> Self {
332 Self(CString::new("stun.l.google.com").unwrap(), 19302)
333 }
334}
335
336impl StunServer {
337 fn new<T: Into<Vec<u8>>>(host: T, port: u16) -> Result<Self> {
339 Ok(Self(
340 CString::new(host).map_err(|_| Error::InvalidArgument)?,
341 port,
342 ))
343 }
344}
345
346struct TurnServer {
348 pub host: CString,
349 pub username: CString,
350 pub password: CString,
351 pub port: u16,
352}
353
354unsafe extern "C" fn on_state_changed(
355 _: *mut sys::juice_agent_t,
356 state: sys::juice_state_t,
357 user_ptr: *mut c_void,
358) {
359 let agent: &Holder = &*(user_ptr as *const _);
360
361 if let Err(e) = state.try_into().map(|s| agent.on_state_changed(s)) {
362 log::error!("failed to map state {:?}", e)
363 }
364}
365
366unsafe extern "C" fn on_candidate(
367 _: *mut sys::juice_agent_t,
368 sdp: *const c_char,
369 user_ptr: *mut c_void,
370) {
371 let agent: &Holder = &*(user_ptr as *const _);
372 let candidate = {
373 let s = CStr::from_ptr(sdp);
374 String::from_utf8_lossy(s.to_bytes())
375 };
376 agent.on_candidate(candidate.to_string())
377}
378
379unsafe extern "C" fn on_gathering_done(_: *mut sys::juice_agent_t, user_ptr: *mut c_void) {
380 let agent: &Holder = &*(user_ptr as *const _);
381 agent.on_gathering_done()
382}
383
384unsafe extern "C" fn on_recv(
385 _: *mut sys::juice_agent_t,
386 data: *const c_char,
387 len: sys::size_t,
388 user_ptr: *mut c_void,
389) {
390 let agent: &Holder = &*(user_ptr as *const _);
391 let packet = core::slice::from_raw_parts(data as _, len as _);
392 agent.on_recv(packet)
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::Handler;
399 use std::sync::{Arc, Barrier};
400
401 #[test]
402 fn build() {
403 crate::test_util::logger_init();
404
405 let handler = Handler::default();
406 let agent = Agent::builder(handler).build().unwrap();
407
408 assert_eq!(agent.get_state(), State::Disconnected);
409 log::debug!(
410 "local description \n\"{}\"",
411 agent.get_local_description().unwrap()
412 );
413 }
414
415 #[test]
416 fn gather() {
417 crate::test_util::logger_init();
418
419 let gathering_barrier = Arc::new(Barrier::new(2));
420
421 let handler = Handler::default()
422 .state_handler(|state| log::debug!("State changed to: {:?}", state))
423 .gathering_done_handler({
424 let barrier = gathering_barrier.clone();
425 move || {
426 log::debug!("Gathering finished");
427 barrier.wait();
428 }
429 })
430 .candidate_handler(|candidate| log::debug!("Local candidate: \"{}\"", candidate));
431
432 let agent = Agent::builder(handler).build().unwrap();
433
434 assert_eq!(agent.get_state(), State::Disconnected);
435 log::debug!(
436 "local description \n\"{}\"",
437 agent.get_local_description().unwrap()
438 );
439
440 agent.gather_candidates().unwrap();
441 assert_eq!(agent.get_state(), State::Gathering);
442
443 let _ = gathering_barrier.wait();
444
445 log::debug!(
446 "local description \n\"{}\"",
447 agent.get_local_description().unwrap()
448 );
449 }
450}