1#[cfg(feature = "chromecast")]
122pub mod chromecast;
123#[cfg(any(feature = "http-file-server", any_protocol))]
124pub mod context;
125#[cfg(all(any_protocol, feature = "discovery"))]
126pub mod discovery;
127#[cfg(feature = "fcast")]
128pub mod fcast;
129#[cfg(feature = "chromecast")]
130pub(crate) mod googlecast_protocol;
131#[cfg(feature = "http-file-server")]
132pub(crate) mod http;
133pub(crate) mod utils;
134
135#[cfg(feature = "http-file-server")]
136pub mod file_server;
137
138#[cfg(all(any_protocol, feature = "discovery_types"))]
140#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
141pub trait DeviceDiscovererEventHandler: Send + Sync {
142 fn device_available(&self, device_info: device::DeviceInfo);
144 fn device_removed(&self, device_name: String);
146 fn device_changed(&self, device_info: device::DeviceInfo);
151}
152
153#[cfg(all(feature = "discovery", any_protocol))]
154use std::future::Future;
155
156#[cfg(any(feature = "http-file-server", any_protocol))]
157use tokio::runtime;
158#[cfg(any_protocol)]
159pub mod device;
160#[cfg(any_protocol)]
161use std::str::FromStr;
162
163#[cfg(feature = "uniffi")]
164uniffi::setup_scaffolding!();
165
166#[cfg(any(feature = "discovery", feature = "http-file-server", any_protocol))]
167#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
168#[cfg_attr(feature = "uniffi", uniffi(flat_error))]
169#[derive(thiserror::Error, Debug)]
170pub enum AsyncRuntimeError {
171 #[error("failed to build")]
172 FailedToBuild(#[from] std::io::Error),
173}
174
175#[cfg(any(feature = "http-file-server", any_protocol))]
176pub(crate) enum AsyncRuntime {
177 Handle(runtime::Handle),
178 Runtime(runtime::Runtime),
179}
180
181#[cfg(any(feature = "http-file-server", any_protocol))]
182impl AsyncRuntime {
183 pub fn new(threads: Option<usize>, name: &str) -> Result<Self, AsyncRuntimeError> {
184 Ok(match runtime::Handle::try_current() {
185 Ok(handle) => Self::Handle(handle),
186 Err(_) => Self::Runtime({
187 if let Some(threads) = threads {
188 runtime::Builder::new_multi_thread()
189 .worker_threads(threads)
190 .enable_all()
191 .thread_name(name)
192 .build()?
193 } else {
194 runtime::Builder::new_multi_thread()
195 .enable_all()
196 .thread_name(name)
197 .build()?
198 }
199 }),
200 })
201 }
202
203 #[cfg(all(feature = "discovery", any_protocol))]
204 pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
205 where
206 F: Future + Send + 'static,
207 F::Output: Send + 'static,
208 {
209 match self {
210 AsyncRuntime::Handle(h) => h.spawn(future),
211 AsyncRuntime::Runtime(rt) => rt.spawn(future),
212 }
213 }
214
215 pub fn handle(&self) -> runtime::Handle {
216 match self {
217 AsyncRuntime::Handle(handle) => handle.clone(),
218 AsyncRuntime::Runtime(runtime) => runtime.handle().clone(),
219 }
220 }
221}
222
223#[cfg(any_protocol)]
225#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
226#[derive(Copy, Clone, Debug, PartialEq, Eq)]
227pub enum IpAddr {
228 V4 {
229 o1: u8,
230 o2: u8,
231 o3: u8,
232 o4: u8,
233 },
234 V6 {
235 o1: u8,
237 o2: u8,
238 o3: u8,
239 o4: u8,
240 o5: u8,
241 o6: u8,
242 o7: u8,
243 o8: u8,
244 o9: u8,
245 o10: u8,
246 o11: u8,
247 o12: u8,
248 o13: u8,
249 o14: u8,
250 o15: u8,
251 o16: u8,
252 scope_id: u32,
253 },
254}
255
256#[cfg(any_protocol)]
257impl IpAddr {
258 pub fn v4(o1: u8, o2: u8, o3: u8, o4: u8) -> Self {
259 Self::V4 { o1, o2, o3, o4 }
260 }
261}
262
263#[cfg(any_protocol)]
264#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
265#[cfg_attr(feature = "uniffi", uniffi(flat_error))]
266#[derive(thiserror::Error, Debug)]
267pub enum ParseIpAddrError {
268 #[error("failed to parse address")]
269 FailedToParse(#[from] std::net::AddrParseError),
270}
271
272#[allow(dead_code)]
273#[cfg(any_protocol)]
274#[cfg_attr(feature = "uniffi", uniffi::export)]
275fn try_ip_addr_from_str(s: &str) -> Result<IpAddr, ParseIpAddrError> {
276 Ok(IpAddr::from(&std::net::IpAddr::from_str(
277 s.trim_matches(['[', ']']),
278 )?))
279}
280
281#[allow(dead_code)]
282#[cfg(any_protocol)]
283#[cfg_attr(feature = "uniffi", uniffi::export)]
284pub fn url_format_ip_addr(addr: &IpAddr) -> String {
285 match addr {
286 IpAddr::V4 { o1, o2, o3, o4 } => format!("{o1}.{o2}.{o3}.{o4}"),
287 IpAddr::V6 {
288 o1,
289 o2,
290 o3,
291 o4,
292 o5,
293 o6,
294 o7,
295 o8,
296 o9,
297 o10,
298 o11,
299 o12,
300 o13,
301 o14,
302 o15,
303 o16,
304 scope_id,
305 } => {
306 let addr = std::net::Ipv6Addr::from_bits(u128::from_be_bytes([
307 *o1, *o2, *o3, *o4, *o5, *o6, *o7, *o8, *o9, *o10, *o11, *o12, *o13, *o14, *o15,
308 *o16,
309 ]));
310 format!("[{addr}%{scope_id}]")
311 }
312 }
313}
314
315#[allow(dead_code)]
316#[cfg(any_protocol)]
317#[cfg_attr(feature = "uniffi", uniffi::export)]
318fn octets_from_ip_addr(addr: &IpAddr) -> Vec<u8> {
319 match addr {
320 IpAddr::V4 { o1, o2, o3, o4 } => vec![*o1, *o2, *o3, *o4],
321 IpAddr::V6 {
322 o1,
323 o2,
324 o3,
325 o4,
326 o5,
327 o6,
328 o7,
329 o8,
330 o9,
331 o10,
332 o11,
333 o12,
334 o13,
335 o14,
336 o15,
337 o16,
338 ..
339 } => {
340 vec![
341 *o1, *o2, *o3, *o4, *o5, *o6, *o7, *o8, *o9, *o10, *o11, *o12, *o13, *o14, *o15,
342 *o16,
343 ]
344 }
345 }
346}
347
348#[cfg(any_protocol)]
349impl From<&std::net::IpAddr> for IpAddr {
350 fn from(value: &std::net::IpAddr) -> Self {
351 match value {
352 std::net::IpAddr::V4(v4) => {
353 let octets = v4.octets();
354 Self::V4 {
355 o1: octets[0],
356 o2: octets[1],
357 o3: octets[2],
358 o4: octets[3],
359 }
360 }
361 std::net::IpAddr::V6(v6) => {
362 let octets = v6.octets();
363 Self::V6 {
364 o1: octets[0],
365 o2: octets[1],
366 o3: octets[2],
367 o4: octets[3],
368 o5: octets[4],
369 o6: octets[5],
370 o7: octets[6],
371 o8: octets[7],
372 o9: octets[8],
373 o10: octets[9],
374 o11: octets[10],
375 o12: octets[11],
376 o13: octets[12],
377 o14: octets[13],
378 o15: octets[14],
379 o16: octets[15],
380 scope_id: 0,
381 }
382 }
383 }
384 }
385}
386
387#[cfg(any_protocol)]
388impl From<&IpAddr> for std::net::IpAddr {
389 fn from(value: &IpAddr) -> Self {
390 match value {
391 IpAddr::V4 { o1, o2, o3, o4 } => {
392 std::net::IpAddr::V4(std::net::Ipv4Addr::new(*o1, *o2, *o3, *o4))
393 }
394 IpAddr::V6 {
395 o1,
396 o2,
397 o3,
398 o4,
399 o5,
400 o6,
401 o7,
402 o8,
403 o9,
404 o10,
405 o11,
406 o12,
407 o13,
408 o14,
409 o15,
410 o16,
411 ..
412 } => std::net::IpAddr::V6(std::net::Ipv6Addr::from_bits(u128::from_be_bytes([
413 *o1, *o2, *o3, *o4, *o5, *o6, *o7, *o8, *o9, *o10, *o11, *o12, *o13, *o14, *o15,
414 *o16,
415 ]))),
416 }
417 }
418}
419
420#[cfg(any_protocol)]
421impl From<std::net::IpAddr> for IpAddr {
422 fn from(value: std::net::IpAddr) -> Self {
423 Self::from(&value)
424 }
425}
426
427#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
428#[cfg(all(
429 any(target_os = "android", target_os = "ios", feature = "_uniffi_csharp"),
430 feature = "logging"
431))]
432#[derive(Clone, Debug, PartialEq, Eq)]
433pub enum LogLevelFilter {
434 Debug,
435 Info,
436}
437
438#[cfg(all(
439 any(target_os = "android", target_os = "ios", feature = "_uniffi_csharp"),
440 feature = "logging"
441))]
442impl LogLevelFilter {
443 pub fn to_log_compat(&self) -> log::LevelFilter {
444 match self {
445 LogLevelFilter::Debug => log::LevelFilter::Debug,
446 LogLevelFilter::Info => log::LevelFilter::Debug,
447 }
448 }
449}
450
451#[cfg(all(target_os = "android", feature = "logging"))]
452#[cfg_attr(feature = "uniffi", uniffi::export)]
453pub fn init_logger(level_filter: LogLevelFilter) {
454 log_panics::init();
455 android_logger::init_once(
456 android_logger::Config::default().with_max_level(level_filter.to_log_compat()),
457 );
458}
459
460#[cfg(all(target_os = "ios", feature = "logging"))]
461#[cfg_attr(feature = "uniffi", uniffi::export)]
462pub fn init_logger(level_filter: LogLevelFilter) {
463 env_logger::Builder::new()
464 .filter(None, level_filter.to_log_compat())
465 .init();
466}
467
468#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
469#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
470pub enum LogLevel {
471 Error,
472 Warn,
473 Info,
474 Debug,
475 Trace,
476}
477
478#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
479#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
480pub trait LogHandler: Send + Sync {
481 fn log(&self, level: LogLevel, tag: String, message: String);
482}
483
484#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
485pub struct CustomLogger {
486 handler: std::sync::Arc<dyn LogHandler>,
487}
488
489#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
490impl CustomLogger {
491 pub fn init(handler: std::sync::Arc<dyn LogHandler>) -> anyhow::Result<()> {
492 log::set_max_level(log::LevelFilter::Debug);
493 Ok(log::set_boxed_logger(Box::new(Self { handler }))?)
494 }
495}
496
497#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
498impl log::Log for CustomLogger {
499 fn enabled(&self, _metadata: &log::Metadata) -> bool {
500 true
501 }
502
503 fn log(&self, record: &log::Record<'_>) {
504 self.handler.log(
505 match record.level() {
506 log::Level::Error => LogLevel::Error,
507 log::Level::Warn => LogLevel::Warn,
508 log::Level::Info => LogLevel::Info,
509 log::Level::Debug => LogLevel::Debug,
510 log::Level::Trace => LogLevel::Trace,
511 },
512 record.module_path().unwrap_or("n/a").to_string(),
513 record.args().to_string(),
514 );
515 }
516
517 fn flush(&self) {}
518}
519
520#[cfg(all(feature = "_uniffi_csharp", feature = "logging"))]
521#[cfg_attr(feature = "uniffi", uniffi::export)]
522pub fn init_custom_logger(handler: std::sync::Arc<dyn LogHandler>) {
523 let _ = CustomLogger::init(handler);
524}
525
526#[cfg(test)]
527mod tests {
528 use crate::AsyncRuntime;
529
530 #[tokio::test]
531 async fn async_runtime_spawn() {
532 let rt = AsyncRuntime::new(Some(1), "test-runtime").unwrap();
533 let jh = rt.spawn(async {
534 async fn test() -> u8 {
535 0
536 }
537 test().await
538 });
539 assert_eq!(jh.await.unwrap(), 0u8);
540 }
541}