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 pdu_state(&self, uid: DeviceUid) -> Option<PduState> {
369 self.shared.read(|state| state.device(uid)?.pdu_state)
370 }
371
372 pub fn rc_settings(&self, uid: DeviceUid) -> Option<RcSettings> {
374 self.shared
375 .read(|state| state.device(uid)?.rc_settings.clone())
376 }
377
378 pub fn network_status(&self, uid: DeviceUid) -> Vec<NetworkPortStatus> {
380 self.shared.read(|state| {
381 state
382 .device(uid)
383 .map(|d| d.network.values().cloned().collect())
384 .unwrap_or_default()
385 })
386 }
387
388 pub fn topology(&self, uid: DeviceUid) -> Vec<TopologyEntry> {
390 self.shared.read(|state| {
391 state
392 .device(uid)
393 .map(|d| d.topology.clone())
394 .unwrap_or_default()
395 })
396 }
397
398 pub fn firmware(&self, uid: DeviceUid) -> Vec<(FirmwareType, FirmwareVersion)> {
400 self.shared.read(|state| {
401 state
402 .device(uid)
403 .map(|d| d.firmware.iter().map(|(k, v)| (*k, v.clone())).collect())
404 .unwrap_or_default()
405 })
406 }
407
408 pub fn update_config(&self, local_ip: Option<Ipv4Addr>, broadcast: bool) -> io::Result<()> {
413 let network = {
414 let mut network = lock(&self.shared.network);
415 if network.local_ip == local_ip && network.broadcast == broadcast {
416 return Ok(());
417 }
418 network.local_ip = local_ip;
419 network.broadcast = broadcast;
420 network.clone()
421 };
422 let conn = network.open()?;
425 lock(&self.shared.tx).set_conn(Some(conn));
426 self.shared.announce();
427 let _ = self.shared.discover();
428 Ok(())
429 }
430
431 pub fn discover(&self) -> Result<(), SendError> {
433 self.shared.discover()
434 }
435}
436
437impl Drop for Remote {
438 fn drop(&mut self) {
439 self.close();
440 }
441}
442
443impl Shared {
444 fn read<R>(&self, f: impl FnOnce(&State) -> R) -> R {
446 f(&lock(&self.state))
447 }
448
449 fn mutate<R>(&self, f: impl FnOnce(&mut State, &mut Vec<Event>) -> R) -> R {
454 let mut events = Vec::new();
455 let result = f(&mut lock(&self.state), &mut events);
456 self.dispatch(events);
457 result
458 }
459
460 fn dispatch(&self, events: Vec<Event>) {
461 for event in events {
462 event.dispatch(&*self.handler);
463 }
464 }
465
466 fn process_datagram(&self, data: &[u8], from: Ipv4Addr) {
473 let events = process_frame(&mut lock(&self.state), data, Some(from), Instant::now());
474 self.dispatch(events);
475 }
476
477 fn send(&self, to: &Addressee, opcode: Opcode, payload: &[u8]) -> Result<usize, SendError> {
479 lock(&self.tx).send(to, self.uid, opcode, payload)
480 }
481
482 fn discover(&self) -> Result<(), SendError> {
483 lock(&self.schedule).discovered(Instant::now());
484 self.send(&Addressee::Broadcast, op::SYS_DISCOVER, &[])?;
485 Ok(())
486 }
487
488 fn announce(&self) {
497 let payload = build_hello(
498 PROTOCOL_VERSION,
499 &self.name,
500 CLIENT_SERIAL,
501 VERSION,
502 DeviceFeature::MANAGER.bits(),
503 );
504 match self.send(&Addressee::Broadcast, op::SYS_HELLO, &payload) {
505 Ok(n) if n > 0 => lock(&self.schedule).announced(Instant::now()),
506 _ => {}
507 }
508 }
509
510 fn sleep_until_next_tick(&self) -> bool {
515 let deadline = Instant::now() + PROBE_TICK;
516 while Instant::now() < deadline {
517 if self.closing.load(Ordering::SeqCst) {
518 return false;
519 }
520 std::thread::sleep(SHUTDOWN_POLL);
521 }
522 !self.closing.load(Ordering::SeqCst)
523 }
524
525 fn announce_due(&self, now: Instant) -> bool {
532 !self.closing.load(Ordering::SeqCst) && lock(&self.schedule).announce_due(now)
533 }
534
535 fn receive_loop(&self) {
537 let mut buf = vec![0u8; RECV_BUFFER];
538 while !self.closing.load(Ordering::SeqCst) {
539 let Some(conn) = lock(&self.tx).conn() else {
540 break;
541 };
542 match conn.recv(&mut buf) {
543 Ok(Some((data, from))) => self.process_datagram(data, from),
544 Ok(None) => {}
545 Err(_) => break,
546 }
547 }
548 }
549
550 fn probe_loop(&self) {
553 while self.sleep_until_next_tick() {
554 let now = Instant::now();
555 let want_discover = self.mutate(|state, ev| {
556 let mut incomplete = false;
557 let mut any_complete = false;
558 for device in state.devices.values_mut() {
559 device.check_online(now, ev);
560 if device.configuration_complete() {
561 any_complete = true;
562 } else if now.saturating_duration_since(device.hello_received) > CONFIG_GRACE {
563 incomplete = true;
566 }
567 }
568 incomplete || !any_complete
571 });
572
573 let discover_due = lock(&self.schedule).discover_due(now);
574 if self.announce_due(now) {
575 self.announce();
576 }
577 if want_discover && discover_due {
578 let _ = self.discover();
579 }
580 }
581 }
582}
583
584fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
591 m.lock().unwrap_or_else(|e| e.into_inner())
592}
593
594fn load_uid(path: Option<PathBuf>) -> io::Result<DeviceUid> {
600 let path = path.or_else(|| {
601 std::env::var_os("HOME")
602 .or_else(|| std::env::var_os("USERPROFILE"))
603 .map(|home| PathBuf::from(home).join(UID_FILE))
604 });
605 if let Some(path) = &path {
606 if let Ok(bytes) = std::fs::read(path) {
607 if let Ok(array) = <[u8; 16]>::try_from(bytes.get(..16).unwrap_or_default()) {
608 return Ok(DeviceUid::from_array(array));
609 }
610 }
611 }
612 let mut bytes = [0u8; 16];
613 getrandom::getrandom(&mut bytes).map_err(|e| io::Error::other(e.to_string()))?;
614 if let Some(path) = &path {
615 let _ = std::fs::write(path, bytes);
616 }
617 Ok(DeviceUid::from_array(bytes))
618}
619
620impl crate::wire::ProtocolTarget for Device {
622 fn serial(&self) -> &str {
623 Device::serial(self)
624 }
625
626 fn supported_protocol(&self) -> u16 {
627 self.hello.supported_protocol
628 }
629}