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, CONFIG_GRACE};
32use crate::types::*;
33use crate::wire::{
34 build_hello, op, Addressee, BayUid, Conn, DeviceFeature, DeviceUid, FirmwareType, Opcode,
35 SendError, Tx, V2ipFpgaFeature, 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
68#[derive(Clone, Debug, Default)]
74#[non_exhaustive]
75pub struct Config {
76 pub target_ip: Option<Ipv4Addr>,
79 pub port: Option<u16>,
81 pub local_ip: Option<Ipv4Addr>,
90 pub interface: Option<String>,
96 pub broadcast: bool,
98 pub name: Option<String>,
100 pub uid: Option<DeviceUid>,
103 pub uid_path: Option<PathBuf>,
106}
107
108struct Shared {
110 uid: DeviceUid,
111 name: String,
112 handler: Arc<dyn EventHandler>,
113 state: Mutex<State>,
114 tx: Mutex<Tx>,
115 schedule: Mutex<Schedule>,
116 network: Mutex<Network>,
117 closing: AtomicBool,
118}
119
120#[derive(Clone, Debug)]
122struct Network {
123 target_ip: Option<Ipv4Addr>,
124 port: Option<u16>,
125 local_ip: Option<Ipv4Addr>,
126 interface: Option<String>,
127 broadcast: bool,
128}
129
130impl Network {
131 fn target(&self) -> io::Result<Ipv4Addr> {
135 if let Some(ip) = self.target_ip {
136 return Ok(ip);
137 }
138 if !self.broadcast {
139 return Ok(MULTICAST_IP);
140 }
141 Ok(crate::wire::broadcast_address(self.local_ip).unwrap_or(MULTICAST_IP))
142 }
143
144 fn port(&self) -> u16 {
145 self.port.unwrap_or(if self.broadcast {
146 crate::wire::BROADCAST_PORT
147 } else {
148 MULTICAST_PORT
149 })
150 }
151
152 fn open(&self) -> io::Result<Conn> {
153 Conn::open(
154 self.target()?,
155 self.port(),
156 self.local_ip,
157 self.interface.as_deref(),
158 )
159 }
160}
161
162pub struct Remote {
168 shared: Arc<Shared>,
169 workers: Mutex<Vec<JoinHandle<()>>>,
170}
171
172impl Remote {
173 pub fn new(config: Config, handler: Arc<dyn EventHandler>) -> io::Result<Self> {
177 let uid = match config.uid {
178 Some(uid) => uid,
179 None => load_uid(config.uid_path.clone())?,
180 };
181 let name = config.name.unwrap_or_else(|| DEFAULT_NAME.to_owned());
182 Ok(Self {
183 shared: Arc::new(Shared {
184 uid,
185 name,
186 handler,
187 state: Mutex::new(State::new(uid)),
188 tx: Mutex::new(Tx::default()),
189 schedule: Mutex::new(Schedule::new()),
190 network: Mutex::new(Network {
191 target_ip: config.target_ip,
192 port: config.port,
193 local_ip: config.local_ip,
194 interface: config.interface,
195 broadcast: config.broadcast,
196 }),
197 closing: AtomicBool::new(false),
198 }),
199 workers: Mutex::new(Vec::new()),
200 })
201 }
202
203 pub fn start(&self) -> io::Result<()> {
208 let conn = lock(&self.shared.network).open()?;
209 lock(&self.shared.tx).set_conn(Some(conn));
210 self.shared.closing.store(false, Ordering::SeqCst);
211 self.spawn_workers()?;
212 self.shared.announce();
213 let _ = self.shared.discover();
214 Ok(())
215 }
216
217 pub fn close(&self) {
221 self.shared.closing.store(true, Ordering::SeqCst);
222 for worker in std::mem::take(&mut *lock(&self.workers)) {
223 let _ = worker.join();
224 }
225 lock(&self.shared.tx).set_conn(None);
229 }
230
231 fn spawn_workers(&self) -> io::Result<()> {
232 let mut workers = lock(&self.workers);
233 if !workers.is_empty() {
234 return Ok(());
235 }
236 for (name, body) in [
237 ("mxr-rx", Shared::receive_loop as fn(&Shared)),
238 ("mxr-probe", Shared::probe_loop as fn(&Shared)),
239 ] {
240 let shared = Arc::clone(&self.shared);
241 workers.push(
242 std::thread::Builder::new()
243 .name(name.to_owned())
244 .spawn(move || body(&shared))?,
245 );
246 }
247 Ok(())
248 }
249
250 pub fn uid(&self) -> DeviceUid {
254 self.shared.uid
255 }
256
257 pub fn name(&self) -> &str {
259 &self.shared.name
260 }
261
262 pub fn target(&self) -> Option<std::net::SocketAddrV4> {
264 lock(&self.shared.tx).conn().map(|conn| conn.target())
265 }
266
267 pub fn devices(&self) -> Vec<DeviceUid> {
271 self.shared
272 .read(|state| state.devices.keys().copied().collect())
273 }
274
275 pub fn device(&self, uid: DeviceUid) -> Option<DeviceInfo> {
277 let now = Instant::now();
278 self.shared
279 .read(|state| state.device(uid).map(|d| DeviceInfo::of(d, now)))
280 }
281
282 pub fn device_by_serial(&self, serial: &str) -> Option<DeviceUid> {
284 self.shared
285 .read(|state| state.device_by_serial(serial).map(|d| d.uid))
286 }
287
288 pub fn resolve_device(&self, name: &str) -> Option<DeviceUid> {
291 if let Ok(uid) = name.parse::<DeviceUid>() {
292 if self.shared.read(|state| state.device(uid).is_some()) {
293 return Some(uid);
294 }
295 }
296 self.device_by_serial(name)
297 }
298
299 pub fn bay(&self, uid: BayUid) -> Option<BayInfo> {
301 self.shared
302 .read(|state| state.bay(uid).map(|bay| BayInfo::of(state, bay)))
303 }
304
305 pub fn bay_by_name(&self, device: DeviceUid, port_name: &str) -> Option<BayUid> {
307 self.shared.read(|state| {
308 state
309 .device(device)?
310 .bay_by_name(port_name)
311 .map(crate::state::Bay::uid)
312 })
313 }
314
315 pub fn bay_by_stream_ip(&self, ip: Ipv4Addr, audio: bool) -> Option<BayUid> {
318 self.shared.read(|state| state.bay_by_stream_ip(ip, audio))
319 }
320
321 pub fn v2ip_sources(&self, uid: DeviceUid) -> Option<Vec<V2ipStreamSources>> {
323 self.shared
324 .read(|state| state.device(uid)?.v2ip_sources.clone())
325 }
326
327 pub fn v2ip_details(&self, uid: DeviceUid) -> Option<DeviceV2ipDetails> {
329 self.shared.read(|state| state.device(uid)?.v2ip_details)
330 }
331
332 pub fn v2ip_sink(&self, uid: DeviceUid) -> Option<DeviceV2ipSink> {
334 self.shared.read(|state| state.device(uid)?.v2ip_sink)
335 }
336
337 pub fn v2ip_stats(&self, uid: DeviceUid) -> Option<V2ipDeviceStats> {
339 self.shared.read(|state| state.device(uid)?.v2ip_stats)
340 }
341
342 pub fn v2ip_features(&self, uid: DeviceUid) -> Option<V2ipFpgaFeature> {
350 self.shared.read(|state| state.device(uid)?.v2ip_features)
351 }
352
353 pub fn v2ip_device_settings(&self, uid: DeviceUid) -> Option<V2ipDeviceSettings> {
359 self.shared.read(|state| state.device(uid)?.v2ip_settings)
360 }
361
362 pub fn v2ip_tiling(&self, uid: DeviceUid) -> Option<V2ipTilingConfig> {
364 self.shared.read(|state| state.device(uid)?.tiling)
365 }
366
367 pub fn audio_endpoints(&self, uid: DeviceUid) -> Option<AudioEndpoints> {
369 self.shared.read(|state| state.device(uid)?.audio.clone())
370 }
371
372 pub fn multiviewer_status(&self, uid: DeviceUid) -> Option<MultiviewerStatus> {
374 self.shared
375 .read(|state| state.device(uid)?.multiviewer.clone())
376 }
377
378 pub fn dolby_settings(&self, uid: DeviceUid) -> Option<AmpDolbySettings> {
380 self.shared.read(|state| state.device(uid)?.dolby_settings)
381 }
382
383 pub fn rc_settings(&self, uid: DeviceUid) -> Option<RcSettings> {
385 self.shared
386 .read(|state| state.device(uid)?.rc_settings.clone())
387 }
388
389 pub fn network_status(&self, uid: DeviceUid) -> Vec<NetworkPortStatus> {
391 self.shared.read(|state| {
392 state
393 .device(uid)
394 .map(|d| d.network.values().cloned().collect())
395 .unwrap_or_default()
396 })
397 }
398
399 pub fn topology(&self, uid: DeviceUid) -> Vec<TopologyEntry> {
401 self.shared.read(|state| {
402 state
403 .device(uid)
404 .map(|d| d.topology.clone())
405 .unwrap_or_default()
406 })
407 }
408
409 pub fn edid(&self, uid: DeviceUid, output: bool) -> Option<Vec<u8>> {
415 self.shared
416 .read(|state| state.device(uid)?.edid(output).map(<[u8]>::to_vec))
417 }
418
419 pub fn frames_received(&self) -> u64 {
429 self.shared.read(|state| state.frames_received)
430 }
431
432 pub fn firmware(&self, uid: DeviceUid) -> Vec<(FirmwareType, FirmwareVersion)> {
434 self.shared.read(|state| {
435 state
436 .device(uid)
437 .map(|d| d.firmware.iter().map(|(k, v)| (*k, v.clone())).collect())
438 .unwrap_or_default()
439 })
440 }
441
442 pub fn update_config(&self, local_ip: Option<Ipv4Addr>, broadcast: bool) -> io::Result<()> {
447 let network = {
448 let mut network = lock(&self.shared.network);
449 if network.local_ip == local_ip && network.broadcast == broadcast {
450 return Ok(());
451 }
452 network.local_ip = local_ip;
453 network.broadcast = broadcast;
454 network.clone()
455 };
456 let conn = network.open()?;
459 lock(&self.shared.tx).set_conn(Some(conn));
460 self.shared.announce();
461 let _ = self.shared.discover();
462 Ok(())
463 }
464
465 pub fn discover(&self) -> Result<(), SendError> {
467 self.shared.discover()
468 }
469}
470
471impl Drop for Remote {
472 fn drop(&mut self) {
473 self.close();
474 }
475}
476
477impl Shared {
478 fn read<R>(&self, f: impl FnOnce(&State) -> R) -> R {
480 f(&lock(&self.state))
481 }
482
483 fn mutate<R>(&self, f: impl FnOnce(&mut State, &mut Vec<Event>) -> R) -> R {
488 let mut events = Vec::new();
489 let result = f(&mut lock(&self.state), &mut events);
490 self.dispatch(events);
491 result
492 }
493
494 fn dispatch(&self, events: Vec<Event>) {
495 for event in events {
496 event.dispatch(&*self.handler);
497 }
498 }
499
500 fn process_datagram(&self, data: &[u8], from: Ipv4Addr) {
507 let events = process_frame(&mut lock(&self.state), data, Some(from), Instant::now());
508 self.dispatch(events);
509 }
510
511 fn send(&self, to: &Addressee, opcode: Opcode, payload: &[u8]) -> Result<usize, SendError> {
513 lock(&self.tx).send(to, self.uid, opcode, payload)
514 }
515
516 fn discover(&self) -> Result<(), SendError> {
517 lock(&self.schedule).discovered(Instant::now());
518 self.send(&Addressee::Broadcast, op::SYS_DISCOVER, &[])?;
519 Ok(())
520 }
521
522 fn announce(&self) {
531 let payload = build_hello(
532 PROTOCOL_VERSION,
533 &self.name,
534 CLIENT_SERIAL,
535 VERSION,
536 DeviceFeature::MANAGER.bits(),
537 );
538 match self.send(&Addressee::Broadcast, op::SYS_HELLO, &payload) {
539 Ok(n) if n > 0 => lock(&self.schedule).announced(Instant::now()),
540 _ => {}
541 }
542 }
543
544 fn sleep_until_next_tick(&self) -> bool {
549 let deadline = Instant::now() + PROBE_TICK;
550 while Instant::now() < deadline {
551 if self.closing.load(Ordering::SeqCst) {
552 return false;
553 }
554 std::thread::sleep(SHUTDOWN_POLL);
555 }
556 !self.closing.load(Ordering::SeqCst)
557 }
558
559 fn announce_due(&self, now: Instant) -> bool {
566 !self.closing.load(Ordering::SeqCst) && lock(&self.schedule).announce_due(now)
567 }
568
569 fn receive_loop(&self) {
571 let mut buf = vec![0u8; RECV_BUFFER];
572 while !self.closing.load(Ordering::SeqCst) {
573 let Some(conn) = lock(&self.tx).conn() else {
574 break;
575 };
576 match conn.recv(&mut buf) {
577 Ok(Some((data, from))) => self.process_datagram(data, from),
578 Ok(None) => {}
579 Err(_) => break,
580 }
581 }
582 }
583
584 fn probe_loop(&self) {
586 while self.sleep_until_next_tick() {
587 self.probe_once(Instant::now());
588 }
589 }
590
591 pub(super) fn probe_once(&self, now: Instant) {
593 let want_discover = self.mutate(|state, ev| {
594 let mut incomplete = false;
595 let mut any_complete = false;
596 for device in state.devices.values_mut() {
597 device.check_online(now, ev);
598 device.check_config_complete(now, ev);
602 if device.configuration_complete(now) {
603 any_complete = true;
604 } else if now.saturating_duration_since(device.first_seen) > CONFIG_GRACE {
605 incomplete = true;
608 }
609 }
610 incomplete || !any_complete
613 });
614
615 let discover_due = lock(&self.schedule).discover_due(now);
616 if self.announce_due(now) {
617 self.announce();
618 }
619 if want_discover && discover_due {
620 let _ = self.discover();
621 }
622 }
623}
624
625fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
632 m.lock().unwrap_or_else(|e| e.into_inner())
633}
634
635fn load_uid(path: Option<PathBuf>) -> io::Result<DeviceUid> {
641 let path = path.or_else(|| {
642 std::env::var_os("HOME")
643 .or_else(|| std::env::var_os("USERPROFILE"))
644 .map(|home| PathBuf::from(home).join(UID_FILE))
645 });
646 if let Some(path) = &path {
647 if let Ok(bytes) = std::fs::read(path) {
648 if let Ok(array) = <[u8; 16]>::try_from(bytes.get(..16).unwrap_or_default()) {
649 return Ok(DeviceUid::from_array(array));
650 }
651 }
652 }
653 let mut bytes = [0u8; 16];
654 getrandom::getrandom(&mut bytes).map_err(|e| io::Error::other(e.to_string()))?;
655 if let Some(path) = &path {
656 let _ = std::fs::write(path, bytes);
657 }
658 Ok(DeviceUid::from_array(bytes))
659}
660
661impl crate::wire::ProtocolTarget for Device {
663 fn serial(&self) -> &str {
664 Device::serial(self)
665 }
666
667 fn supported_protocol(&self) -> u16 {
668 self.hello.supported_protocol
669 }
670}