1use std::io::{ErrorKind, Read as _, Write as _};
8use std::os::unix::net::{UnixListener, UnixStream};
9use std::path::{Path, PathBuf};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::{Arc, Mutex};
12use std::thread::JoinHandle;
13use std::time::Duration;
14
15use crate::frame::Frame;
16
17pub const MAX_FRAME_LEN: usize = 64 * 1024 * 1024;
20
21const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
25
26const ACCEPT_POLL: Duration = Duration::from_secs(1);
30
31const READ_POLL: Duration = Duration::from_millis(200);
33
34const STALL: Duration = Duration::from_secs(5);
37
38enum Filled {
40 Complete,
42 Quiet,
44 Closed,
46}
47
48fn read_full(
57 stream: &mut UnixStream,
58 buf: &mut [u8],
59 may_be_quiet: bool,
60) -> Result<Filled, String> {
61 let mut filled = 0;
62 let mut stalled = Duration::ZERO;
63 while filled < buf.len() {
64 match stream.read(&mut buf[filled..]) {
65 Ok(0) => return Ok(Filled::Closed),
66 Ok(read) => {
67 filled += read;
68 stalled = Duration::ZERO;
69 }
70 Err(why) if matches!(why.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {
71 if may_be_quiet && filled == 0 {
72 return Ok(Filled::Quiet);
73 }
74 stalled += READ_POLL;
75 if stalled >= STALL {
76 return Err("a frame stalled mid-read".to_owned());
77 }
78 }
79 Err(why) if why.kind() == ErrorKind::Interrupted => {}
80 Err(why) => return Err(format!("the frame was not read: {why}")),
81 }
82 }
83 Ok(Filled::Complete)
84}
85
86pub fn socket_path(profile: &Path) -> PathBuf {
93 profile.join("frames.sock")
94}
95
96pub fn private_dir(profile: &Path) -> Result<PathBuf, String> {
103 use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _};
104
105 let uid = std::fs::metadata(profile)
106 .map_err(|why| {
107 format!("the profile directory {} cannot be read: {why}", profile.display())
108 })?
109 .uid();
110 let dir = Path::new("/tmp").join(format!("forge-{uid}"));
111 std::fs::create_dir_all(&dir)
112 .map_err(|why| format!("the socket directory {} cannot be made: {why}", dir.display()))?;
113 std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).map_err(|why| {
114 format!("the socket directory {} cannot be made private: {why}", dir.display())
115 })?;
116 let meta = std::fs::metadata(&dir)
117 .map_err(|why| format!("the socket directory {} cannot be read: {why}", dir.display()))?;
118 if meta.uid() != uid || meta.permissions().mode() & 0o777 != 0o700 {
119 return Err(format!(
120 "the socket directory {} is not this user's private directory",
121 dir.display()
122 ));
123 }
124 Ok(dir)
125}
126
127pub fn frame_socket_path(profile: &Path, id: u16) -> Result<PathBuf, String> {
130 Ok(private_dir(profile)?.join(format!("frames-{}-{id}.sock", std::process::id())))
131}
132
133pub fn connect(path: &Path) -> Result<UnixStream, String> {
135 let stream = UnixStream::connect(path)
136 .map_err(|why| format!("the frame socket {} did not accept: {why}", path.display()))?;
137 stream
138 .set_write_timeout(Some(WRITE_TIMEOUT))
139 .map_err(|why| format!("the frame socket's write timeout cannot be set: {why}"))?;
140 Ok(stream)
141}
142
143pub const KIND_FRAME: u8 = 1;
145pub const KIND_AUDIO: u8 = 2;
146
147#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct Audio {
153 pub profile: u16,
155 pub tab: u16,
157 pub seq: u32,
159 pub sample_rate: u32,
160 pub channels: u8,
161 pub samples: Vec<i16>,
163}
164
165impl Audio {
166 pub const HEADER_LEN: usize = 13;
168
169 pub fn encode(&self) -> Vec<u8> {
170 let mut out = Vec::with_capacity(Self::HEADER_LEN + self.samples.len() * 2);
171 out.extend_from_slice(&self.profile.to_le_bytes());
172 out.extend_from_slice(&self.tab.to_le_bytes());
173 out.extend_from_slice(&self.seq.to_le_bytes());
174 out.extend_from_slice(&self.sample_rate.to_le_bytes());
175 out.push(self.channels);
176 for sample in &self.samples {
177 out.extend_from_slice(&sample.to_le_bytes());
178 }
179 out
180 }
181
182 pub fn decode(bytes: &[u8]) -> Result<Self, String> {
183 if bytes.len() < Self::HEADER_LEN {
184 return Err(format!(
185 "an audio packet is at least {} bytes, got {}",
186 Self::HEADER_LEN,
187 bytes.len()
188 ));
189 }
190 let profile = u16::from_le_bytes([bytes[0], bytes[1]]);
191 let tab = u16::from_le_bytes([bytes[2], bytes[3]]);
192 let seq = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
193 let sample_rate = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
194 let channels = bytes[12];
195 let rest = &bytes[Self::HEADER_LEN..];
196 if !rest.len().is_multiple_of(2) {
197 return Err("an audio packet carries a whole number of samples".to_owned());
198 }
199 let samples =
200 rest.as_chunks::<2>().0.iter().map(|pair| i16::from_le_bytes(*pair)).collect();
201 Ok(Self { profile, tab, seq, sample_rate, channels, samples })
202 }
203}
204
205fn write_message(stream: &mut UnixStream, kind: u8, body: &[u8]) -> Result<(), String> {
212 let len = u32::try_from(body.len() + 1)
213 .map_err(|why| format!("a message past the channel's length: {why}"))?;
214 let mut bytes = Vec::with_capacity(5 + body.len());
215 bytes.extend_from_slice(&len.to_le_bytes());
216 bytes.push(kind);
217 bytes.extend_from_slice(body);
218 let deadline = std::time::Instant::now() + WRITE_TIMEOUT;
219 while !bytes.is_empty() {
220 match stream.write(&bytes) {
221 Ok(0) => return Err("the frame socket closed mid-message".to_owned()),
222 Ok(written) => {
223 bytes.drain(..written);
224 }
225 Err(why) if why.kind() == ErrorKind::Interrupted => {}
226 Err(why) if matches!(why.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {
227 if std::time::Instant::now() >= deadline {
228 return Err("the message was not written within its bound".to_owned());
229 }
230 }
231 Err(why) => return Err(format!("the message was not written: {why}")),
232 }
233 if std::time::Instant::now() >= deadline {
234 return Err("the message was not written within its bound".to_owned());
235 }
236 }
237 Ok(())
238}
239
240pub fn write(stream: &mut UnixStream, frame: &Frame) -> Result<(), String> {
242 write_message(stream, KIND_FRAME, &frame.encode())
243}
244
245pub fn write_audio(stream: &mut UnixStream, audio: &Audio) -> Result<(), String> {
247 write_message(stream, KIND_AUDIO, &audio.encode())
248}
249
250#[derive(Debug)]
252pub enum Reception {
253 Frame(Frame),
255 Audio(Audio),
257 Quiet,
259 Closed,
261}
262
263#[derive(Debug)]
265pub enum ChannelMessage {
266 Frame(Frame),
267 Audio(Audio),
268}
269
270pub fn read(stream: &mut UnixStream) -> Result<Reception, String> {
272 let mut length = [0_u8; 4];
273 match read_full(stream, &mut length, true)? {
274 Filled::Complete => {}
275 Filled::Quiet => return Ok(Reception::Quiet),
276 Filled::Closed => return Ok(Reception::Closed),
277 }
278 let len = usize::try_from(u32::from_le_bytes(length)).unwrap_or(usize::MAX);
279 if !(1..=MAX_FRAME_LEN).contains(&len) {
280 return Err(format!("a message length of {len} is not a message"));
281 }
282 let mut body = vec![0_u8; len];
283 match read_full(stream, &mut body, false)? {
284 Filled::Complete => {}
285 Filled::Quiet => return Ok(Reception::Quiet),
287 Filled::Closed => return Ok(Reception::Closed),
288 }
289 let (kind, payload) = body.split_first().ok_or_else(|| "an empty message".to_owned())?;
290 match *kind {
291 KIND_FRAME => Frame::decode(payload).map(Reception::Frame),
292 KIND_AUDIO => Audio::decode(payload).map(Reception::Audio),
293 other => Err(format!("a message kind of {other} is not one this channel knows")),
294 }
295}
296
297pub fn serve<F>(path: &Path, sink: F) -> Result<Reader, String>
307where
308 F: FnMut(ChannelMessage) + Send + 'static,
309{
310 let _ = std::fs::remove_file(path);
311 let listener = UnixListener::bind(path)
312 .map_err(|why| format!("the frame socket {} cannot be bound: {why}", path.display()))?;
313 listener
314 .set_nonblocking(true)
315 .map_err(|why| format!("the frame socket cannot be made non-blocking: {why}"))?;
316 let stop = Arc::new(AtomicBool::new(false));
317 let own_stop = Arc::clone(&stop);
318 let own_path = path.to_path_buf();
319 let wake = path.to_path_buf();
320 let sink = Arc::new(Mutex::new(sink));
321 let thread = std::thread::spawn(move || {
322 let mut connections: Vec<JoinHandle<()>> = Vec::new();
323 while !own_stop.load(Ordering::Acquire) {
324 match listener.accept() {
325 Ok((stream, _)) => {
326 let sink = Arc::clone(&sink);
327 let stop = Arc::clone(&own_stop);
328 let path = own_path.clone();
329 connections.push(std::thread::spawn(move || {
330 read_connection(stream, &path, &stop, &sink);
331 }));
332 }
333 Err(why) if why.kind() == ErrorKind::WouldBlock => {
334 std::thread::sleep(ACCEPT_POLL);
335 }
336 Err(why) => {
337 eprintln!(
338 "cef host: the frame socket accept failed (event_name \
339 cef_frames_accept_failed, {}): {why}",
340 own_path.display()
341 );
342 break;
343 }
344 }
345 }
346 for connection in connections {
347 let _ = connection.join();
348 }
349 let _ = std::fs::remove_file(&own_path);
350 });
351 Ok(Reader { stop, wake, path: path.to_path_buf(), thread: Some(thread) })
352}
353
354fn read_connection<F>(mut stream: UnixStream, path: &Path, stop: &AtomicBool, sink: &Mutex<F>)
356where
357 F: FnMut(ChannelMessage),
358{
359 if let Err(why) = stream.set_read_timeout(Some(READ_POLL)) {
360 eprintln!("cef host: the frame socket's read timeout was refused: {why}");
361 }
362 if let Err(why) = stream.set_nonblocking(false) {
366 eprintln!("cef host: the frame socket cannot be made blocking: {why}");
367 }
368 loop {
369 if stop.load(Ordering::Acquire) {
370 return;
371 }
372 match read(&mut stream) {
373 Ok(Reception::Frame(frame)) => {
374 (sink.lock().unwrap_or_else(std::sync::PoisonError::into_inner))(
375 ChannelMessage::Frame(frame),
376 );
377 }
378 Ok(Reception::Audio(audio)) => {
379 (sink.lock().unwrap_or_else(std::sync::PoisonError::into_inner))(
380 ChannelMessage::Audio(audio),
381 );
382 }
383 Ok(Reception::Quiet) => {}
384 Ok(Reception::Closed) => break,
385 Err(why) => {
386 eprintln!(
387 "cef host: the frame stream ended (event_name cef_frames_read_failed, {}): \
388 {why}",
389 path.display()
390 );
391 break;
392 }
393 }
394 }
395}
396
397pub struct Reader {
399 stop: Arc<AtomicBool>,
400 wake: PathBuf,
401 path: PathBuf,
402 thread: Option<JoinHandle<()>>,
403}
404
405impl Reader {
406 pub fn stop(mut self) {
409 self.end();
410 }
411
412 fn end(&mut self) {
413 self.stop.store(true, Ordering::Release);
414 let _ = UnixStream::connect(&self.wake);
415 if let Some(thread) = self.thread.take() {
416 let _ = thread.join();
417 }
418 let _ = std::fs::remove_file(&self.path);
419 }
420}
421
422impl Drop for Reader {
423 fn drop(&mut self) {
424 self.end();
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::frame_socket_path;
431
432 #[test]
438 fn a_full_length_profile_name_still_leaves_a_short_socket() {
439 let dir = tempfile::tempdir().expect("a temp dir");
440 let profile = dir.path().join("p".repeat(64)).join("cef");
441 std::fs::create_dir_all(&profile).expect("the profile dir");
442 let path = frame_socket_path(&profile, 1).expect("the frame socket path");
443 assert!(
444 path.as_os_str().len() < 104,
445 "{} is {} bytes, past a Unix socket's address",
446 path.display(),
447 path.as_os_str().len(),
448 );
449 assert!(
450 path.starts_with(std::path::Path::new("/tmp")),
451 "the short path is outside the profile tree: {}",
452 path.display(),
453 );
454 }
455}