1mod control;
8mod info;
9mod schedule;
10
11#[cfg(test)]
12mod control_tests;
13#[cfg(test)]
14mod tests;
15
16#[cfg(all(test, target_os = "linux"))]
19mod socket;
20
21use std::io;
22use std::net::Ipv4Addr;
23use std::path::PathBuf;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::{Arc, Mutex, MutexGuard};
26use std::thread::JoinHandle;
27use std::time::{Duration, Instant};
28
29use crate::event::{Event, EventHandler};
30use crate::rx::process_frame;
31use crate::state::{Device, State};
32use crate::types::*;
33use crate::wire::{
34 build_hello, op, Addressee, BayUid, Conn, DeviceFeature, DeviceUid, FirmwareType, Opcode,
35 SendError, Tx, MULTICAST_IP, MULTICAST_PORT, PROTOCOL_VERSION, VERSION,
36};
37
38pub use control::ControlError;
39pub use info::{BayInfo, DeviceInfo};
40use schedule::Schedule;
41
42const DEFAULT_NAME: &str = "MXR Rust";
44
45const CLIENT_SERIAL: &str = "P9SN00000000";
50
51const UID_FILE: &str = ".mxr-uid";
55
56const RECV_BUFFER: usize = 65535;
58
59const PROBE_TICK: Duration = Duration::from_secs(1);
61
62const SHUTDOWN_POLL: Duration = Duration::from_millis(50);
64
65const DISCOVER_INTERVAL: Duration = Duration::from_secs(5);
67
68const CONFIG_GRACE: Duration = Duration::from_secs(15);
71
72#[derive(Clone, Debug, Default)]
78#[non_exhaustive]
79pub struct Config {
80 pub target_ip: Option<Ipv4Addr>,
83 pub port: Option<u16>,
85 pub local_ip: Option<Ipv4Addr>,
94 pub interface: Option<String>,
100 pub broadcast: bool,
102 pub name: Option<String>,
104 pub uid: Option<DeviceUid>,
107 pub uid_path: Option<PathBuf>,
110}
111
112struct Shared {
114 uid: DeviceUid,
115 name: String,
116 handler: Arc<dyn EventHandler>,
117 state: Mutex<State>,
118 tx: Mutex<Tx>,
119 schedule: Mutex<Schedule>,
120 network: Mutex<Network>,
121 closing: AtomicBool,
122}
123
124#[derive(Clone, Debug)]
126struct Network {
127 target_ip: Option<Ipv4Addr>,
128 port: Option<u16>,
129 local_ip: Option<Ipv4Addr>,
130 interface: Option<String>,
131 broadcast: bool,
132}
133
134impl Network {
135 fn target(&self) -> io::Result<Ipv4Addr> {
139 if let Some(ip) = self.target_ip {
140 return Ok(ip);
141 }
142 if !self.broadcast {
143 return Ok(MULTICAST_IP);
144 }
145 Ok(crate::wire::broadcast_address(self.local_ip).unwrap_or(MULTICAST_IP))
146 }
147
148 fn port(&self) -> u16 {
149 self.port.unwrap_or(if self.broadcast {
150 crate::wire::BROADCAST_PORT
151 } else {
152 MULTICAST_PORT
153 })
154 }
155
156 fn open(&self) -> io::Result<Conn> {
157 Conn::open(
158 self.target()?,
159 self.port(),
160 self.local_ip,
161 self.interface.as_deref(),
162 )
163 }
164}
165
166pub struct Remote {
172 shared: Arc<Shared>,
173 workers: Mutex<Vec<JoinHandle<()>>>,
174}
175
176impl Remote {
177 pub fn new(config: Config, handler: Arc<dyn EventHandler>) -> io::Result<Self> {
181 let uid = match config.uid {
182 Some(uid) => uid,
183 None => load_uid(config.uid_path.clone())?,
184 };
185 let name = config.name.unwrap_or_else(|| DEFAULT_NAME.to_owned());
186 Ok(Self {
187 shared: Arc::new(Shared {
188 uid,
189 name,
190 handler,
191 state: Mutex::new(State::new(uid)),
192 tx: Mutex::new(Tx::default()),
193 schedule: Mutex::new(Schedule::new()),
194 network: Mutex::new(Network {
195 target_ip: config.target_ip,
196 port: config.port,
197 local_ip: config.local_ip,
198 interface: config.interface,
199 broadcast: config.broadcast,
200 }),
201 closing: AtomicBool::new(false),
202 }),
203 workers: Mutex::new(Vec::new()),
204 })
205 }
206
207 pub fn start(&self) -> io::Result<()> {
212 let conn = lock(&self.shared.network).open()?;
213 lock(&self.shared.tx).set_conn(Some(conn));
214 self.shared.closing.store(false, Ordering::SeqCst);
215 self.spawn_workers()?;
216 self.shared.announce();
217 let _ = self.shared.discover();
218 Ok(())
219 }
220
221 pub fn close(&self) {
225 self.shared.closing.store(true, Ordering::SeqCst);
226 for worker in std::mem::take(&mut *lock(&self.workers)) {
227 let _ = worker.join();
228 }
229 lock(&self.shared.tx).set_conn(None);
233 }
234
235 fn spawn_workers(&self) -> io::Result<()> {
236 let mut workers = lock(&self.workers);
237 if !workers.is_empty() {
238 return Ok(());
239 }
240 for (name, body) in [
241 ("mxr-rx", Shared::receive_loop as fn(&Shared)),
242 ("mxr-probe", Shared::probe_loop as fn(&Shared)),
243 ] {
244 let shared = Arc::clone(&self.shared);
245 workers.push(
246 std::thread::Builder::new()
247 .name(name.to_owned())
248 .spawn(move || body(&shared))?,
249 );
250 }
251 Ok(())
252 }
253
254 pub fn uid(&self) -> DeviceUid {
258 self.shared.uid
259 }
260
261 pub fn name(&self) -> &str {
263 &self.shared.name
264 }
265
266 pub fn target(&self) -> Option<std::net::SocketAddrV4> {
268 lock(&self.shared.tx).conn().map(|conn| conn.target())
269 }
270
271 pub fn devices(&self) -> Vec<DeviceUid> {
275 self.shared
276 .read(|state| state.devices.keys().copied().collect())
277 }
278
279 pub fn device(&self, uid: DeviceUid) -> Option<DeviceInfo> {
281 let now = Instant::now();
282 self.shared
283 .read(|state| state.device(uid).map(|d| DeviceInfo::of(d, now)))
284 }
285
286 pub fn device_by_serial(&self, serial: &str) -> Option<DeviceUid> {
288 self.shared
289 .read(|state| state.device_by_serial(serial).map(|d| d.uid))
290 }
291
292 pub fn resolve_device(&self, name: &str) -> Option<DeviceUid> {
295 if let Ok(uid) = name.parse::<DeviceUid>() {
296 if self.shared.read(|state| state.device(uid).is_some()) {
297 return Some(uid);
298 }
299 }
300 self.device_by_serial(name)
301 }
302
303 pub fn bay(&self, uid: BayUid) -> Option<BayInfo> {
305 self.shared
306 .read(|state| state.bay(uid).map(|bay| BayInfo::of(state, bay)))
307 }
308
309 pub fn bay_by_name(&self, device: DeviceUid, port_name: &str) -> Option<BayUid> {
311 self.shared.read(|state| {
312 state
313 .device(device)?
314 .bay_by_name(port_name)
315 .map(crate::state::Bay::uid)
316 })
317 }
318
319 pub fn bay_by_stream_ip(&self, ip: Ipv4Addr, audio: bool) -> Option<BayUid> {
322 self.shared.read(|state| state.bay_by_stream_ip(ip, audio))
323 }
324
325 pub fn v2ip_sources(&self, uid: DeviceUid) -> Option<Vec<V2ipStreamSources>> {
327 self.shared
328 .read(|state| state.device(uid)?.v2ip_sources.clone())
329 }
330
331 pub fn v2ip_details(&self, uid: DeviceUid) -> Option<DeviceV2ipDetails> {
333 self.shared.read(|state| state.device(uid)?.v2ip_details)
334 }
335
336 pub fn v2ip_sink(&self, uid: DeviceUid) -> Option<DeviceV2ipSink> {
338 self.shared.read(|state| state.device(uid)?.v2ip_sink)
339 }
340
341 pub fn v2ip_stats(&self, uid: DeviceUid) -> Option<V2ipDeviceStats> {
343 self.shared.read(|state| state.device(uid)?.v2ip_stats)
344 }
345
346 pub fn v2ip_tiling(&self, uid: DeviceUid) -> Option<V2ipTilingConfig> {
348 self.shared.read(|state| state.device(uid)?.tiling)
349 }
350
351 pub fn audio_endpoints(&self, uid: DeviceUid) -> Option<AudioEndpoints> {
353 self.shared.read(|state| state.device(uid)?.audio.clone())
354 }
355
356 pub fn multiviewer_status(&self, uid: DeviceUid) -> Option<MultiviewerStatus> {
358 self.shared
359 .read(|state| state.device(uid)?.multiviewer.clone())
360 }
361
362 pub fn dolby_settings(&self, uid: DeviceUid) -> Option<AmpDolbySettings> {
364 self.shared.read(|state| state.device(uid)?.dolby_settings)
365 }
366
367 pub fn rc_settings(&self, uid: DeviceUid) -> Option<RcSettings> {
369 self.shared
370 .read(|state| state.device(uid)?.rc_settings.clone())
371 }
372
373 pub fn network_status(&self, uid: DeviceUid) -> Vec<NetworkPortStatus> {
375 self.shared.read(|state| {
376 state
377 .device(uid)
378 .map(|d| d.network.values().cloned().collect())
379 .unwrap_or_default()
380 })
381 }
382
383 pub fn topology(&self, uid: DeviceUid) -> Vec<TopologyEntry> {
385 self.shared.read(|state| {
386 state
387 .device(uid)
388 .map(|d| d.topology.clone())
389 .unwrap_or_default()
390 })
391 }
392
393 pub fn edid(&self, uid: DeviceUid, output: bool) -> Option<Vec<u8>> {
399 self.shared
400 .read(|state| state.device(uid)?.edid(output).map(<[u8]>::to_vec))
401 }
402
403 pub fn frames_received(&self) -> u64 {
413 self.shared.read(|state| state.frames_received)
414 }
415
416 pub fn firmware(&self, uid: DeviceUid) -> Vec<(FirmwareType, FirmwareVersion)> {
418 self.shared.read(|state| {
419 state
420 .device(uid)
421 .map(|d| d.firmware.iter().map(|(k, v)| (*k, v.clone())).collect())
422 .unwrap_or_default()
423 })
424 }
425
426 pub fn update_config(&self, local_ip: Option<Ipv4Addr>, broadcast: bool) -> io::Result<()> {
431 let network = {
432 let mut network = lock(&self.shared.network);
433 if network.local_ip == local_ip && network.broadcast == broadcast {
434 return Ok(());
435 }
436 network.local_ip = local_ip;
437 network.broadcast = broadcast;
438 network.clone()
439 };
440 let conn = network.open()?;
443 lock(&self.shared.tx).set_conn(Some(conn));
444 self.shared.announce();
445 let _ = self.shared.discover();
446 Ok(())
447 }
448
449 pub fn discover(&self) -> Result<(), SendError> {
451 self.shared.discover()
452 }
453}
454
455impl Drop for Remote {
456 fn drop(&mut self) {
457 self.close();
458 }
459}
460
461impl Shared {
462 fn read<R>(&self, f: impl FnOnce(&State) -> R) -> R {
464 f(&lock(&self.state))
465 }
466
467 fn mutate<R>(&self, f: impl FnOnce(&mut State, &mut Vec<Event>) -> R) -> R {
472 let mut events = Vec::new();
473 let result = f(&mut lock(&self.state), &mut events);
474 self.dispatch(events);
475 result
476 }
477
478 fn dispatch(&self, events: Vec<Event>) {
479 for event in events {
480 event.dispatch(&*self.handler);
481 }
482 }
483
484 fn process_datagram(&self, data: &[u8], from: Ipv4Addr) {
491 let events = process_frame(&mut lock(&self.state), data, Some(from), Instant::now());
492 self.dispatch(events);
493 }
494
495 fn send(&self, to: &Addressee, opcode: Opcode, payload: &[u8]) -> Result<usize, SendError> {
497 lock(&self.tx).send(to, self.uid, opcode, payload)
498 }
499
500 fn discover(&self) -> Result<(), SendError> {
501 lock(&self.schedule).discovered(Instant::now());
502 self.send(&Addressee::Broadcast, op::SYS_DISCOVER, &[])?;
503 Ok(())
504 }
505
506 fn announce(&self) {
515 let payload = build_hello(
516 PROTOCOL_VERSION,
517 &self.name,
518 CLIENT_SERIAL,
519 VERSION,
520 DeviceFeature::MANAGER.bits(),
521 );
522 match self.send(&Addressee::Broadcast, op::SYS_HELLO, &payload) {
523 Ok(n) if n > 0 => lock(&self.schedule).announced(Instant::now()),
524 _ => {}
525 }
526 }
527
528 fn sleep_until_next_tick(&self) -> bool {
533 let deadline = Instant::now() + PROBE_TICK;
534 while Instant::now() < deadline {
535 if self.closing.load(Ordering::SeqCst) {
536 return false;
537 }
538 std::thread::sleep(SHUTDOWN_POLL);
539 }
540 !self.closing.load(Ordering::SeqCst)
541 }
542
543 fn announce_due(&self, now: Instant) -> bool {
550 !self.closing.load(Ordering::SeqCst) && lock(&self.schedule).announce_due(now)
551 }
552
553 fn receive_loop(&self) {
555 let mut buf = vec![0u8; RECV_BUFFER];
556 while !self.closing.load(Ordering::SeqCst) {
557 let Some(conn) = lock(&self.tx).conn() else {
558 break;
559 };
560 match conn.recv(&mut buf) {
561 Ok(Some((data, from))) => self.process_datagram(data, from),
562 Ok(None) => {}
563 Err(_) => break,
564 }
565 }
566 }
567
568 fn probe_loop(&self) {
571 while self.sleep_until_next_tick() {
572 let now = Instant::now();
573 let want_discover = self.mutate(|state, ev| {
574 let mut incomplete = false;
575 let mut any_complete = false;
576 for device in state.devices.values_mut() {
577 device.check_online(now, ev);
578 if device.configuration_complete() {
579 any_complete = true;
580 } else if now.saturating_duration_since(device.hello_received) > CONFIG_GRACE {
581 incomplete = true;
584 }
585 }
586 incomplete || !any_complete
589 });
590
591 let discover_due = lock(&self.schedule).discover_due(now);
592 if self.announce_due(now) {
593 self.announce();
594 }
595 if want_discover && discover_due {
596 let _ = self.discover();
597 }
598 }
599 }
600}
601
602fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
609 m.lock().unwrap_or_else(|e| e.into_inner())
610}
611
612fn load_uid(path: Option<PathBuf>) -> io::Result<DeviceUid> {
618 let path = path.or_else(|| {
619 std::env::var_os("HOME")
620 .or_else(|| std::env::var_os("USERPROFILE"))
621 .map(|home| PathBuf::from(home).join(UID_FILE))
622 });
623 if let Some(path) = &path {
624 if let Ok(bytes) = std::fs::read(path) {
625 if let Ok(array) = <[u8; 16]>::try_from(bytes.get(..16).unwrap_or_default()) {
626 return Ok(DeviceUid::from_array(array));
627 }
628 }
629 }
630 let mut bytes = [0u8; 16];
631 getrandom::getrandom(&mut bytes).map_err(|e| io::Error::other(e.to_string()))?;
632 if let Some(path) = &path {
633 let _ = std::fs::write(path, bytes);
634 }
635 Ok(DeviceUid::from_array(bytes))
636}
637
638impl crate::wire::ProtocolTarget for Device {
640 fn serial(&self) -> &str {
641 Device::serial(self)
642 }
643
644 fn supported_protocol(&self) -> u16 {
645 self.hello.supported_protocol
646 }
647}