1use std::net::SocketAddrV4;
15use std::path::PathBuf;
16use std::time::Duration;
17
18use anyhow::{Context as _, Result, anyhow, bail};
19use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream};
20use tokio::sync::mpsc;
21use umsh::tokio_support::LanConfig;
22use umsh_ulcp_simdev::host::HostPlatform;
23use umsh_ulcp_simdev::lan::{self, HostEvent, StateLock};
24use umsh_ulcp_simdev::soft::SoftDevice;
25
26const READ_CHUNK: usize = 4096;
29
30const LOCK_PATIENCE: Duration = Duration::from_secs(2);
33
34fn device_dir() -> Result<PathBuf> {
36 crate::connection::state_dir()
37 .map(|dir| dir.join("lan-device"))
38 .ok_or_else(|| anyhow!("no HOME directory to keep the LAN device's state in"))
39}
40
41pub async fn open(group: Option<SocketAddrV4>) -> Result<DuplexStream> {
48 let dir = device_dir()?;
49 let platform =
50 HostPlatform::new(&dir).with_context(|| format!("creating {}", dir.display()))?;
51 let deadline = tokio::time::Instant::now() + LOCK_PATIENCE;
52 let lock = loop {
53 if let Some(lock) =
54 StateLock::try_acquire(&dir).with_context(|| format!("locking {}", dir.display()))?
55 {
56 break lock;
57 }
58 if tokio::time::Instant::now() >= deadline {
59 bail!(
60 "another umshctl is already the LAN virtual radio ({} is locked)",
61 dir.display()
62 );
63 }
64 tokio::time::sleep(Duration::from_millis(50)).await;
65 };
66 let (host, device_end) = tokio::io::duplex(READ_CHUNK);
67 let mut device = SoftDevice::new(
68 lan::session_config(concat!("umshctl-lan/", env!("CARGO_PKG_VERSION"))),
69 platform,
70 );
71 if !device.session().settings().enabled {
75 let _ = device.session_mut().toggle_radio(&mut |_| {});
76 }
77 if let Some(group) = group {
78 device
79 .session_mut()
80 .set_lan_bearer_group(Some(umsh::ulcp_wire::lan::Group {
81 address: group.ip().octets(),
82 port: group.port(),
83 }));
84 }
85 let (events, host_events) = mpsc::unbounded_channel();
86 let (wire_tx, wire_rx) = mpsc::unbounded_channel();
87 let _ = events.send(HostEvent::Attach(Box::new(move |wire| {
88 let _ = wire_tx.send(wire);
89 })));
90 tokio::task::spawn_local(async move {
91 lan::run(device, LanConfig::lan(), host_events, |_| {}).await;
94 drop(lock);
95 });
96 tokio::task::spawn_local(relay(device_end, events, wire_rx));
97 Ok(host)
98}
99
100async fn relay(
103 mut pipe: DuplexStream,
104 events: mpsc::UnboundedSender<HostEvent>,
105 mut wire: mpsc::UnboundedReceiver<Vec<u8>>,
106) {
107 let mut buf = vec![0u8; READ_CHUNK];
108 loop {
109 tokio::select! {
110 read = pipe.read(&mut buf) => match read {
111 Ok(0) | Err(_) => break,
112 Ok(len) => {
113 if events.send(HostEvent::Bytes(buf[..len].to_vec())).is_err() {
114 break;
115 }
116 }
117 },
118 frame = wire.recv() => match frame {
119 Some(frame) if pipe.write_all(&frame).await.is_ok() => {}
120 _ => break,
121 },
122 }
123 }
124}