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 edid(&self, uid: DeviceUid, output: bool) -> Option<Vec<u8>> {
404 self.shared
405 .read(|state| state.device(uid)?.edid(output).map(<[u8]>::to_vec))
406 }
407
408 pub fn frames_received(&self) -> u64 {
418 self.shared.read(|state| state.frames_received)
419 }
420
421 pub fn firmware(&self, uid: DeviceUid) -> Vec<(FirmwareType, FirmwareVersion)> {
423 self.shared.read(|state| {
424 state
425 .device(uid)
426 .map(|d| d.firmware.iter().map(|(k, v)| (*k, v.clone())).collect())
427 .unwrap_or_default()
428 })
429 }
430
431 pub fn update_config(&self, local_ip: Option<Ipv4Addr>, broadcast: bool) -> io::Result<()> {
436 let network = {
437 let mut network = lock(&self.shared.network);
438 if network.local_ip == local_ip && network.broadcast == broadcast {
439 return Ok(());
440 }
441 network.local_ip = local_ip;
442 network.broadcast = broadcast;
443 network.clone()
444 };
445 let conn = network.open()?;
448 lock(&self.shared.tx).set_conn(Some(conn));
449 self.shared.announce();
450 let _ = self.shared.discover();
451 Ok(())
452 }
453
454 pub fn discover(&self) -> Result<(), SendError> {
456 self.shared.discover()
457 }
458}
459
460impl Drop for Remote {
461 fn drop(&mut self) {
462 self.close();
463 }
464}
465
466impl Shared {
467 fn read<R>(&self, f: impl FnOnce(&State) -> R) -> R {
469 f(&lock(&self.state))
470 }
471
472 fn mutate<R>(&self, f: impl FnOnce(&mut State, &mut Vec<Event>) -> R) -> R {
477 let mut events = Vec::new();
478 let result = f(&mut lock(&self.state), &mut events);
479 self.dispatch(events);
480 result
481 }
482
483 fn dispatch(&self, events: Vec<Event>) {
484 for event in events {
485 event.dispatch(&*self.handler);
486 }
487 }
488
489 fn process_datagram(&self, data: &[u8], from: Ipv4Addr) {
496 let events = process_frame(&mut lock(&self.state), data, Some(from), Instant::now());
497 self.dispatch(events);
498 }
499
500 fn send(&self, to: &Addressee, opcode: Opcode, payload: &[u8]) -> Result<usize, SendError> {
502 lock(&self.tx).send(to, self.uid, opcode, payload)
503 }
504
505 fn discover(&self) -> Result<(), SendError> {
506 lock(&self.schedule).discovered(Instant::now());
507 self.send(&Addressee::Broadcast, op::SYS_DISCOVER, &[])?;
508 Ok(())
509 }
510
511 fn announce(&self) {
520 let payload = build_hello(
521 PROTOCOL_VERSION,
522 &self.name,
523 CLIENT_SERIAL,
524 VERSION,
525 DeviceFeature::MANAGER.bits(),
526 );
527 match self.send(&Addressee::Broadcast, op::SYS_HELLO, &payload) {
528 Ok(n) if n > 0 => lock(&self.schedule).announced(Instant::now()),
529 _ => {}
530 }
531 }
532
533 fn sleep_until_next_tick(&self) -> bool {
538 let deadline = Instant::now() + PROBE_TICK;
539 while Instant::now() < deadline {
540 if self.closing.load(Ordering::SeqCst) {
541 return false;
542 }
543 std::thread::sleep(SHUTDOWN_POLL);
544 }
545 !self.closing.load(Ordering::SeqCst)
546 }
547
548 fn announce_due(&self, now: Instant) -> bool {
555 !self.closing.load(Ordering::SeqCst) && lock(&self.schedule).announce_due(now)
556 }
557
558 fn receive_loop(&self) {
560 let mut buf = vec![0u8; RECV_BUFFER];
561 while !self.closing.load(Ordering::SeqCst) {
562 let Some(conn) = lock(&self.tx).conn() else {
563 break;
564 };
565 match conn.recv(&mut buf) {
566 Ok(Some((data, from))) => self.process_datagram(data, from),
567 Ok(None) => {}
568 Err(_) => break,
569 }
570 }
571 }
572
573 fn probe_loop(&self) {
576 while self.sleep_until_next_tick() {
577 let now = Instant::now();
578 let want_discover = self.mutate(|state, ev| {
579 let mut incomplete = false;
580 let mut any_complete = false;
581 for device in state.devices.values_mut() {
582 device.check_online(now, ev);
583 if device.configuration_complete() {
584 any_complete = true;
585 } else if now.saturating_duration_since(device.hello_received) > CONFIG_GRACE {
586 incomplete = true;
589 }
590 }
591 incomplete || !any_complete
594 });
595
596 let discover_due = lock(&self.schedule).discover_due(now);
597 if self.announce_due(now) {
598 self.announce();
599 }
600 if want_discover && discover_due {
601 let _ = self.discover();
602 }
603 }
604 }
605}
606
607fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
614 m.lock().unwrap_or_else(|e| e.into_inner())
615}
616
617fn load_uid(path: Option<PathBuf>) -> io::Result<DeviceUid> {
623 let path = path.or_else(|| {
624 std::env::var_os("HOME")
625 .or_else(|| std::env::var_os("USERPROFILE"))
626 .map(|home| PathBuf::from(home).join(UID_FILE))
627 });
628 if let Some(path) = &path {
629 if let Ok(bytes) = std::fs::read(path) {
630 if let Ok(array) = <[u8; 16]>::try_from(bytes.get(..16).unwrap_or_default()) {
631 return Ok(DeviceUid::from_array(array));
632 }
633 }
634 }
635 let mut bytes = [0u8; 16];
636 getrandom::getrandom(&mut bytes).map_err(|e| io::Error::other(e.to_string()))?;
637 if let Some(path) = &path {
638 let _ = std::fs::write(path, bytes);
639 }
640 Ok(DeviceUid::from_array(bytes))
641}
642
643impl crate::wire::ProtocolTarget for Device {
645 fn serial(&self) -> &str {
646 Device::serial(self)
647 }
648
649 fn supported_protocol(&self) -> u16 {
650 self.hello.supported_protocol
651 }
652}