mod control;
mod info;
mod schedule;
#[cfg(test)]
mod control_tests;
#[cfg(test)]
mod tests;
#[cfg(all(test, target_os = "linux"))]
mod socket;
use std::io;
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use crate::event::{Event, EventHandler};
use crate::rx::process_frame;
use crate::state::{Device, State};
use crate::types::*;
use crate::wire::{
build_hello, op, Addressee, BayUid, Conn, DeviceFeature, DeviceUid, FirmwareType, Opcode,
SendError, Tx, MULTICAST_IP, MULTICAST_PORT, PROTOCOL_VERSION, VERSION,
};
pub use control::ControlError;
pub use info::{BayInfo, DeviceInfo};
use schedule::Schedule;
const DEFAULT_NAME: &str = "MXR Rust";
const CLIENT_SERIAL: &str = "P9SN00000000";
const UID_FILE: &str = ".mxr-uid";
const RECV_BUFFER: usize = 65535;
const PROBE_TICK: Duration = Duration::from_secs(1);
const SHUTDOWN_POLL: Duration = Duration::from_millis(50);
const DISCOVER_INTERVAL: Duration = Duration::from_secs(5);
const CONFIG_GRACE: Duration = Duration::from_secs(15);
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct Config {
pub target_ip: Option<Ipv4Addr>,
pub port: Option<u16>,
pub local_ip: Option<Ipv4Addr>,
pub interface: Option<String>,
pub broadcast: bool,
pub name: Option<String>,
pub uid: Option<DeviceUid>,
pub uid_path: Option<PathBuf>,
}
struct Shared {
uid: DeviceUid,
name: String,
handler: Arc<dyn EventHandler>,
state: Mutex<State>,
tx: Mutex<Tx>,
schedule: Mutex<Schedule>,
network: Mutex<Network>,
closing: AtomicBool,
}
#[derive(Clone, Debug)]
struct Network {
target_ip: Option<Ipv4Addr>,
port: Option<u16>,
local_ip: Option<Ipv4Addr>,
interface: Option<String>,
broadcast: bool,
}
impl Network {
fn target(&self) -> io::Result<Ipv4Addr> {
if let Some(ip) = self.target_ip {
return Ok(ip);
}
if !self.broadcast {
return Ok(MULTICAST_IP);
}
Ok(crate::wire::broadcast_address(self.local_ip).unwrap_or(MULTICAST_IP))
}
fn port(&self) -> u16 {
self.port.unwrap_or(if self.broadcast {
crate::wire::BROADCAST_PORT
} else {
MULTICAST_PORT
})
}
fn open(&self) -> io::Result<Conn> {
Conn::open(
self.target()?,
self.port(),
self.local_ip,
self.interface.as_deref(),
)
}
}
pub struct Remote {
shared: Arc<Shared>,
workers: Mutex<Vec<JoinHandle<()>>>,
}
impl Remote {
pub fn new(config: Config, handler: Arc<dyn EventHandler>) -> io::Result<Self> {
let uid = match config.uid {
Some(uid) => uid,
None => load_uid(config.uid_path.clone())?,
};
let name = config.name.unwrap_or_else(|| DEFAULT_NAME.to_owned());
Ok(Self {
shared: Arc::new(Shared {
uid,
name,
handler,
state: Mutex::new(State::new(uid)),
tx: Mutex::new(Tx::default()),
schedule: Mutex::new(Schedule::new()),
network: Mutex::new(Network {
target_ip: config.target_ip,
port: config.port,
local_ip: config.local_ip,
interface: config.interface,
broadcast: config.broadcast,
}),
closing: AtomicBool::new(false),
}),
workers: Mutex::new(Vec::new()),
})
}
pub fn start(&self) -> io::Result<()> {
let conn = lock(&self.shared.network).open()?;
lock(&self.shared.tx).set_conn(Some(conn));
self.shared.closing.store(false, Ordering::SeqCst);
self.spawn_workers()?;
self.shared.announce();
let _ = self.shared.discover();
Ok(())
}
pub fn close(&self) {
self.shared.closing.store(true, Ordering::SeqCst);
for worker in std::mem::take(&mut *lock(&self.workers)) {
let _ = worker.join();
}
lock(&self.shared.tx).set_conn(None);
}
fn spawn_workers(&self) -> io::Result<()> {
let mut workers = lock(&self.workers);
if !workers.is_empty() {
return Ok(());
}
for (name, body) in [
("mxr-rx", Shared::receive_loop as fn(&Shared)),
("mxr-probe", Shared::probe_loop as fn(&Shared)),
] {
let shared = Arc::clone(&self.shared);
workers.push(
std::thread::Builder::new()
.name(name.to_owned())
.spawn(move || body(&shared))?,
);
}
Ok(())
}
pub fn uid(&self) -> DeviceUid {
self.shared.uid
}
pub fn name(&self) -> &str {
&self.shared.name
}
pub fn target(&self) -> Option<std::net::SocketAddrV4> {
lock(&self.shared.tx).conn().map(|conn| conn.target())
}
pub fn devices(&self) -> Vec<DeviceUid> {
self.shared
.read(|state| state.devices.keys().copied().collect())
}
pub fn device(&self, uid: DeviceUid) -> Option<DeviceInfo> {
let now = Instant::now();
self.shared
.read(|state| state.device(uid).map(|d| DeviceInfo::of(d, now)))
}
pub fn device_by_serial(&self, serial: &str) -> Option<DeviceUid> {
self.shared
.read(|state| state.device_by_serial(serial).map(|d| d.uid))
}
pub fn resolve_device(&self, name: &str) -> Option<DeviceUid> {
if let Ok(uid) = name.parse::<DeviceUid>() {
if self.shared.read(|state| state.device(uid).is_some()) {
return Some(uid);
}
}
self.device_by_serial(name)
}
pub fn bay(&self, uid: BayUid) -> Option<BayInfo> {
self.shared
.read(|state| state.bay(uid).map(|bay| BayInfo::of(state, bay)))
}
pub fn bay_by_name(&self, device: DeviceUid, port_name: &str) -> Option<BayUid> {
self.shared.read(|state| {
state
.device(device)?
.bay_by_name(port_name)
.map(crate::state::Bay::uid)
})
}
pub fn bay_by_stream_ip(&self, ip: Ipv4Addr, audio: bool) -> Option<BayUid> {
self.shared.read(|state| state.bay_by_stream_ip(ip, audio))
}
pub fn v2ip_sources(&self, uid: DeviceUid) -> Option<Vec<V2ipStreamSources>> {
self.shared
.read(|state| state.device(uid)?.v2ip_sources.clone())
}
pub fn v2ip_details(&self, uid: DeviceUid) -> Option<DeviceV2ipDetails> {
self.shared.read(|state| state.device(uid)?.v2ip_details)
}
pub fn v2ip_sink(&self, uid: DeviceUid) -> Option<DeviceV2ipSink> {
self.shared.read(|state| state.device(uid)?.v2ip_sink)
}
pub fn v2ip_stats(&self, uid: DeviceUid) -> Option<V2ipDeviceStats> {
self.shared.read(|state| state.device(uid)?.v2ip_stats)
}
pub fn v2ip_tiling(&self, uid: DeviceUid) -> Option<V2ipTilingConfig> {
self.shared.read(|state| state.device(uid)?.tiling)
}
pub fn audio_endpoints(&self, uid: DeviceUid) -> Option<AudioEndpoints> {
self.shared.read(|state| state.device(uid)?.audio.clone())
}
pub fn multiviewer_status(&self, uid: DeviceUid) -> Option<MultiviewerStatus> {
self.shared
.read(|state| state.device(uid)?.multiviewer.clone())
}
pub fn dolby_settings(&self, uid: DeviceUid) -> Option<AmpDolbySettings> {
self.shared.read(|state| state.device(uid)?.dolby_settings)
}
pub fn rc_settings(&self, uid: DeviceUid) -> Option<RcSettings> {
self.shared
.read(|state| state.device(uid)?.rc_settings.clone())
}
pub fn network_status(&self, uid: DeviceUid) -> Vec<NetworkPortStatus> {
self.shared.read(|state| {
state
.device(uid)
.map(|d| d.network.values().cloned().collect())
.unwrap_or_default()
})
}
pub fn topology(&self, uid: DeviceUid) -> Vec<TopologyEntry> {
self.shared.read(|state| {
state
.device(uid)
.map(|d| d.topology.clone())
.unwrap_or_default()
})
}
pub fn edid(&self, uid: DeviceUid, output: bool) -> Option<Vec<u8>> {
self.shared
.read(|state| state.device(uid)?.edid(output).map(<[u8]>::to_vec))
}
pub fn frames_received(&self) -> u64 {
self.shared.read(|state| state.frames_received)
}
pub fn firmware(&self, uid: DeviceUid) -> Vec<(FirmwareType, FirmwareVersion)> {
self.shared.read(|state| {
state
.device(uid)
.map(|d| d.firmware.iter().map(|(k, v)| (*k, v.clone())).collect())
.unwrap_or_default()
})
}
pub fn update_config(&self, local_ip: Option<Ipv4Addr>, broadcast: bool) -> io::Result<()> {
let network = {
let mut network = lock(&self.shared.network);
if network.local_ip == local_ip && network.broadcast == broadcast {
return Ok(());
}
network.local_ip = local_ip;
network.broadcast = broadcast;
network.clone()
};
let conn = network.open()?;
lock(&self.shared.tx).set_conn(Some(conn));
self.shared.announce();
let _ = self.shared.discover();
Ok(())
}
pub fn discover(&self) -> Result<(), SendError> {
self.shared.discover()
}
}
impl Drop for Remote {
fn drop(&mut self) {
self.close();
}
}
impl Shared {
fn read<R>(&self, f: impl FnOnce(&State) -> R) -> R {
f(&lock(&self.state))
}
fn mutate<R>(&self, f: impl FnOnce(&mut State, &mut Vec<Event>) -> R) -> R {
let mut events = Vec::new();
let result = f(&mut lock(&self.state), &mut events);
self.dispatch(events);
result
}
fn dispatch(&self, events: Vec<Event>) {
for event in events {
event.dispatch(&*self.handler);
}
}
fn process_datagram(&self, data: &[u8], from: Ipv4Addr) {
let events = process_frame(&mut lock(&self.state), data, Some(from), Instant::now());
self.dispatch(events);
}
fn send(&self, to: &Addressee, opcode: Opcode, payload: &[u8]) -> Result<usize, SendError> {
lock(&self.tx).send(to, self.uid, opcode, payload)
}
fn discover(&self) -> Result<(), SendError> {
lock(&self.schedule).discovered(Instant::now());
self.send(&Addressee::Broadcast, op::SYS_DISCOVER, &[])?;
Ok(())
}
fn announce(&self) {
let payload = build_hello(
PROTOCOL_VERSION,
&self.name,
CLIENT_SERIAL,
VERSION,
DeviceFeature::MANAGER.bits(),
);
match self.send(&Addressee::Broadcast, op::SYS_HELLO, &payload) {
Ok(n) if n > 0 => lock(&self.schedule).announced(Instant::now()),
_ => {}
}
}
fn sleep_until_next_tick(&self) -> bool {
let deadline = Instant::now() + PROBE_TICK;
while Instant::now() < deadline {
if self.closing.load(Ordering::SeqCst) {
return false;
}
std::thread::sleep(SHUTDOWN_POLL);
}
!self.closing.load(Ordering::SeqCst)
}
fn announce_due(&self, now: Instant) -> bool {
!self.closing.load(Ordering::SeqCst) && lock(&self.schedule).announce_due(now)
}
fn receive_loop(&self) {
let mut buf = vec![0u8; RECV_BUFFER];
while !self.closing.load(Ordering::SeqCst) {
let Some(conn) = lock(&self.tx).conn() else {
break;
};
match conn.recv(&mut buf) {
Ok(Some((data, from))) => self.process_datagram(data, from),
Ok(None) => {}
Err(_) => break,
}
}
}
fn probe_loop(&self) {
while self.sleep_until_next_tick() {
let now = Instant::now();
let want_discover = self.mutate(|state, ev| {
let mut incomplete = false;
let mut any_complete = false;
for device in state.devices.values_mut() {
device.check_online(now, ev);
if device.configuration_complete() {
any_complete = true;
} else if now.saturating_duration_since(device.hello_received) > CONFIG_GRACE {
incomplete = true;
}
}
incomplete || !any_complete
});
let discover_due = lock(&self.schedule).discover_due(now);
if self.announce_due(now) {
self.announce();
}
if want_discover && discover_due {
let _ = self.discover();
}
}
}
}
fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
fn load_uid(path: Option<PathBuf>) -> io::Result<DeviceUid> {
let path = path.or_else(|| {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(|home| PathBuf::from(home).join(UID_FILE))
});
if let Some(path) = &path {
if let Ok(bytes) = std::fs::read(path) {
if let Ok(array) = <[u8; 16]>::try_from(bytes.get(..16).unwrap_or_default()) {
return Ok(DeviceUid::from_array(array));
}
}
}
let mut bytes = [0u8; 16];
getrandom::getrandom(&mut bytes).map_err(|e| io::Error::other(e.to_string()))?;
if let Some(path) = &path {
let _ = std::fs::write(path, bytes);
}
Ok(DeviceUid::from_array(bytes))
}
impl crate::wire::ProtocolTarget for Device {
fn serial(&self) -> &str {
Device::serial(self)
}
fn supported_protocol(&self) -> u16 {
self.hello.supported_protocol
}
}