1use std::net::SocketAddr;
4use std::path::Path;
5use std::sync::{Arc, Mutex};
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8use dspk_core::{RecordingInfo, RecordingSource, SampleFormat};
9use dspk_view::{Envelope, Pyramid};
10use tokio::task::JoinHandle;
11use tokio::time::{interval, MissedTickBehavior};
12
13use super::auth::{Authority, Peer, Permission};
14use super::files::{kind_of, DataRoot};
15use super::jobs::Jobs;
16use crate::error::{StreamError, StreamResult};
17use crate::protocol::{frame_from_envelope, header_from_info, read_message, view_from_request, wire, write_message, PROTOCOL_VERSION};
18use dspk_view::View;
19
20pub const DEFAULT_FRAME_SEC: f64 = 0.02;
22pub const DEFAULT_QUEUE_FRAMES: usize = 8;
24pub const DEFAULT_MAX_REQUEST_BYTES: usize = 64 << 10;
27pub const DEFAULT_MAX_CONNECTIONS: usize = 32;
29pub const MAX_VIEW_VALUES: u64 = 8 << 20;
32pub const MAX_VIEW_COLUMNS: u32 = 1 << 16;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Pacing {
38 RealTime,
40 Unpaced,
42}
43
44#[derive(Debug, Clone)]
46pub struct ServeOptions {
47 pub frame_sec: f64,
49 pub pacing: Pacing,
50 pub loop_playback: bool,
52 pub queue_frames: usize,
54 pub max_message_bytes: usize,
56}
57
58impl Default for ServeOptions {
59 fn default() -> Self {
60 Self { frame_sec: DEFAULT_FRAME_SEC, pacing: Pacing::RealTime, loop_playback: false, queue_frames: DEFAULT_QUEUE_FRAMES, max_message_bytes: DEFAULT_MAX_REQUEST_BYTES }
61 }
62}
63
64pub struct Server {
66 endpoint: quinn::Endpoint,
67 max_connections: usize,
68 authority: Option<Arc<Authority>>,
69}
70
71pub const REFUSED_CLIENT: u32 = 1;
73
74impl Server {
75 pub fn bind(addr: SocketAddr, config: quinn::ServerConfig) -> StreamResult<Self> {
77 Ok(Self { endpoint: quinn::Endpoint::server(config, addr)?, max_connections: DEFAULT_MAX_CONNECTIONS, authority: None })
78 }
79
80 pub fn with_authority(mut self, authority: Arc<Authority>) -> Self {
84 self.authority = Some(authority);
85 self
86 }
87
88 pub fn with_max_connections(mut self, n: usize) -> Self {
90 self.max_connections = n.max(1);
91 self
92 }
93
94 pub fn local_addr(&self) -> StreamResult<SocketAddr> {
95 Ok(self.endpoint.local_addr()?)
96 }
97
98 pub async fn accept(&self) -> Option<(quinn::Connection, Peer)> {
105 loop {
106 let incoming = self.endpoint.accept().await?;
107 if !incoming.remote_address_validated() && incoming.may_retry() {
108 if let Err(e) = incoming.retry() {
109 tracing::debug!("QUIC retry failed: {e}");
110 }
111 continue;
112 }
113 if self.endpoint.open_connections() >= self.max_connections {
114 tracing::warn!("refused {}: {} clients connected (the limit)", incoming.remote_address(), self.max_connections);
115 incoming.refuse();
116 continue;
117 }
118 let connection = match incoming.await {
119 Ok(connection) => connection,
120 Err(e) => {
121 tracing::warn!("QUIC handshake failed: {e}");
122 continue;
123 }
124 };
125 match self.peer(&connection) {
126 Some(peer) => return Some((connection, peer)),
127 None => {
128 tracing::warn!("refused {}: its certificate is not enrolled or is revoked", connection.remote_address());
129 connection.close(quinn::VarInt::from_u32(REFUSED_CLIENT), b"client not enrolled or revoked");
130 }
131 }
132 }
133 }
134
135 fn peer(&self, connection: &quinn::Connection) -> Option<Peer> {
138 let Some(authority) = &self.authority else { return Some(Peer::anonymous()) };
139 let certificates = connection.peer_identity().and_then(|id| id.downcast::<Vec<rustls::pki_types::CertificateDer<'static>>>().ok());
140 match certificates.as_deref().and_then(|c| c.first()) {
141 Some(certificate) => authority.authorize(certificate),
142 None => Some(Peer::anonymous()),
143 }
144 }
145
146 pub fn close(&self) {
148 self.endpoint.close(quinn::VarInt::from_u32(0), b"server closed");
149 }
150}
151
152fn streamed_info(source: &dyn RecordingSource) -> (RecordingInfo, bool) {
155 let info = source.info().clone();
156 let all: Vec<usize> = (0..info.channel_count()).collect();
157 let mut probe = vec![0u8; all.len() * info.format.bytes()];
159 let stored = info.samples == 0 || source.read_stored(&all, 0..1, &mut probe).is_ok();
160 if stored {
161 return (info, true);
162 }
163 let mut scaled = info;
164 scaled.format = SampleFormat::F32;
165 for c in &mut scaled.channels {
166 (c.gain, c.offset) = (1.0, 0.0);
167 }
168 (scaled, false)
169}
170
171pub struct OpenRecording {
173 source: Arc<dyn RecordingSource>,
174 pyramid: Option<Arc<Pyramid>>,
175 info: RecordingInfo,
176 stored: bool,
177}
178
179impl OpenRecording {
180 pub fn new(source: Arc<dyn RecordingSource>, pyramid: Option<Arc<Pyramid>>) -> Self {
181 let (info, stored) = streamed_info(source.as_ref());
182 Self { source, pyramid, info, stored }
183 }
184
185 pub fn info(&self) -> &RecordingInfo {
187 &self.info
188 }
189}
190
191pub trait RecordingOpener: Send + Sync {
194 fn open(&self, path: &Path, source: Option<&str>) -> StreamResult<OpenRecording>;
196}
197
198#[derive(Clone)]
200pub struct Device {
201 pub name: String,
203 pub version: String,
204 pub files: Option<(DataRoot, Arc<dyn RecordingOpener>)>,
206 pub recording: Option<Arc<OpenRecording>>,
208 pub jobs: Option<Arc<Jobs>>,
210 pub runtime: String,
212 pub options: ServeOptions,
213}
214
215type Current = Arc<Mutex<Option<Arc<OpenRecording>>>>;
217
218fn current_recording(current: &Current) -> Option<Arc<OpenRecording>> {
219 current.lock().unwrap_or_else(|e| e.into_inner()).clone()
220}
221
222pub async fn serve_recording(connection: quinn::Connection, source: Arc<dyn RecordingSource>, pyramid: Option<Arc<Pyramid>>, options: ServeOptions) -> StreamResult<()> {
224 let device = Device { name: "dspk".into(), version: String::new(), files: None, recording: Some(Arc::new(OpenRecording::new(source, pyramid))), jobs: None, runtime: String::new(), options };
225 serve_session(connection, Peer::anonymous(), Arc::new(device)).await
226}
227
228pub async fn serve_session(connection: quinn::Connection, peer: Peer, device: Arc<Device>) -> StreamResult<()> {
232 let options = device.options.clone();
233 let (mut control_send, mut control_recv) = connection.accept_bi().await?;
234 let hello: wire::ClientHello = read_message(&mut control_recv, options.max_message_bytes).await?.ok_or_else(|| StreamError::protocol("no hello"))?;
235 let info = wire::ServerInfo {
236 protocol_version: PROTOCOL_VERSION,
237 server_name: device.name.clone(),
238 server_version: device.version.clone(),
239 peer_name: peer.name.clone(),
240 permission: wire_permission(peer.permission) as i32,
241 state: device.jobs.as_ref().map_or(wire::DeviceState::Stopped, |j| j.state()) as i32,
242 has_files: device.files.is_some(),
243 recording: device.recording.as_ref().map(|r| header_from_info(r.info())),
244 runtime: device.runtime.clone(),
245 };
246 write_message(&mut control_send, &info).await?;
247 if hello.protocol_version != PROTOCOL_VERSION {
248 return Err(StreamError::protocol(format!("client speaks protocol {}, this server {PROTOCOL_VERSION}", hello.protocol_version)));
249 }
250
251 let current: Current = Arc::new(Mutex::new(device.recording.clone()));
252 let requests = tokio::spawn(answer_requests(connection.clone(), device.clone(), current.clone(), Arc::new(peer)));
253 let mut signal: Option<JoinHandle<StreamResult<()>>> = None;
254 let result = loop {
255 match read_message::<wire::Subscribe>(&mut control_recv, options.max_message_bytes).await {
256 Ok(Some(subscribe)) => {
257 let Some(recording) = current_recording(¤t) else { break Err(StreamError::protocol("subscribe before any recording is open")) };
258 if let Some(old) = signal.take() {
259 old.abort();
260 }
261 let task = stream_signal(connection.clone(), recording, subscribe.first_sample, options.clone());
262 signal = Some(tokio::spawn(task));
263 }
264 Ok(None) => break Ok(()),
265 Err(e) if e.is_closed_by_peer() => break Ok(()),
266 Err(e) => break Err(e),
267 }
268 };
269 if let Some(task) = signal {
270 task.abort();
271 }
272 requests.abort();
273 result
274}
275
276fn wire_permission(p: Permission) -> wire::Permission {
277 match p {
278 Permission::Read => wire::Permission::Read,
279 Permission::Control => wire::Permission::Control,
280 }
281}
282
283async fn answer_requests(connection: quinn::Connection, device: Arc<Device>, current: Current, peer: Arc<Peer>) {
285 let max = device.options.max_message_bytes;
286 while let Ok((mut send, mut recv)) = connection.accept_bi().await {
287 let (device, current, peer) = (device.clone(), current.clone(), peer.clone());
288 tokio::spawn(async move {
289 let answer = async {
290 let request: wire::Request = read_message(&mut recv, max).await?.ok_or_else(|| StreamError::protocol("empty request stream"))?;
291 if let Some(wire::request::Kind::Fetch(fetch)) = &request.kind {
292 return send_file(&mut send, &device, &fetch.path).await;
293 }
294 let kind = match answer(request, &device, ¤t, &peer).await {
295 Ok(kind) => kind,
296 Err(e) => wire::response::Kind::Error(e.reason()),
297 };
298 write_message(&mut send, &wire::Response { kind: Some(kind) }).await?;
299 send.finish().map_err(StreamError::protocol)?;
300 StreamResult::Ok(())
301 };
302 if let Err(e) = answer.await {
303 tracing::debug!("request stream closed: {e}");
304 }
305 });
306 }
307}
308
309async fn answer(request: wire::Request, device: &Device, current: &Current, peer: &Peer) -> StreamResult<wire::response::Kind> {
311 use wire::request::Kind as Req;
312 use wire::response::Kind as Resp;
313 match request.kind.ok_or_else(|| StreamError::protocol("empty request"))? {
314 Req::View(view) => {
315 let recording = current_recording(current).ok_or_else(|| StreamError::protocol("no recording is open"))?;
316 let view = checked_view(&view, recording.info())?;
317 let envelope = tokio::task::spawn_blocking(move || -> StreamResult<Envelope> { Ok(view.read(recording.source.as_ref(), recording.pyramid.as_deref())?) })
318 .await
319 .map_err(StreamError::protocol)?;
320 Ok(Resp::Envelope(frame_from_envelope(envelope)))
321 }
322 Req::ListFiles(list) => {
323 let (root, _) = device.files.as_ref().ok_or_else(|| StreamError::protocol("this server serves no files"))?;
324 let root = root.clone();
325 let files = tokio::task::spawn_blocking(move || root.list(&list.dir)).await.map_err(StreamError::protocol)??;
326 Ok(Resp::Files(files))
327 }
328 Req::Open(open) => {
329 let (root, opener) = device.files.clone().ok_or_else(|| StreamError::protocol("this server serves no files"))?;
330 let recording = tokio::task::spawn_blocking(move || -> StreamResult<OpenRecording> {
331 let path = root.resolve(&open.path)?;
332 if kind_of(&path, path.is_dir()) != wire::FileKind::Recording {
333 return Err(StreamError::Protocol(format!("{:?} is not a recording", open.path)));
334 }
335 opener.open(&path, Some(open.source.as_str()).filter(|s| !s.is_empty()))
336 })
337 .await
338 .map_err(StreamError::protocol)??;
339 let header = header_from_info(recording.info());
340 *current.lock().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(recording));
341 Ok(Resp::Header(header))
342 }
343 Req::Configure(configure) => {
344 let jobs = jobs_of(device)?;
345 let peer = peer.clone();
346 Ok(Resp::Status(tokio::task::spawn_blocking(move || jobs.configure(&peer, configure)).await.map_err(StreamError::protocol)??))
347 }
348 Req::Start(_) => Ok(Resp::Status(jobs_of(device)?.start(peer)?)),
349 Req::Stop(_) => Ok(Resp::Status(jobs_of(device)?.stop(peer)?)),
350 Req::Status(_) => Ok(Resp::Status(jobs_of(device)?.status())),
351 Req::Logs(logs) => Ok(Resp::Logs(wire::LogLines { lines: jobs_of(device)?.logs(logs.lines as usize) })),
352 Req::Fetch(_) => Err(StreamError::protocol("fetch is answered by send_file")),
353 }
354}
355
356pub const FILE_CHUNK_BYTES: usize = 1 << 20;
358
359async fn send_file(send: &mut quinn::SendStream, device: &Device, path: &str) -> StreamResult<()> {
361 use tokio::io::AsyncReadExt as _;
362 let opened = async {
363 let (root, _) = device.files.as_ref().ok_or_else(|| StreamError::Server("this server serves no files".into()))?;
364 let full = root.resolve(path)?;
365 if !full.is_file() {
366 return Err(StreamError::Server(format!("{path:?} is not a file (fetch a directory file by file)")));
367 }
368 let file = tokio::fs::File::open(&full).await?;
369 let size = file.metadata().await?.len();
370 Ok((file, size, root.relative(&full)))
371 };
372 let (mut file, size, relative) = match opened.await {
373 Ok(o) => o,
374 Err(e) => {
375 write_message(send, &wire::Response { kind: Some(wire::response::Kind::Error(e.reason())) }).await?;
376 return send.finish().map_err(StreamError::protocol);
377 }
378 };
379 write_message(send, &wire::Response { kind: Some(wire::response::Kind::File(wire::FileHeader { path: relative, size })) }).await?;
380 let mut buf = vec![0u8; FILE_CHUNK_BYTES];
381 loop {
382 let n = file.read(&mut buf).await?;
383 if n == 0 {
384 break;
385 }
386 write_message(send, &wire::FileChunk { data: buf[..n].to_vec() }).await?;
387 }
388 send.finish().map_err(StreamError::protocol)
389}
390
391fn jobs_of(device: &Device) -> StreamResult<Arc<Jobs>> {
392 device.jobs.clone().ok_or_else(|| StreamError::Server("this server runs no jobs".into()))
393}
394
395pub fn checked_view(request: &wire::ViewRequest, info: &RecordingInfo) -> StreamResult<View> {
399 let view = view_from_request(request);
400 view.validate(info.channel_count(), info.samples).map_err(|e| StreamError::protocol(e.to_string()))?;
401 let mut seen = vec![false; info.channel_count()];
402 for &c in &view.channels {
403 if std::mem::replace(&mut seen[c], true) {
404 return Err(StreamError::protocol(format!("channel {c} asked for twice")));
405 }
406 }
407 if request.width > MAX_VIEW_COLUMNS {
408 return Err(StreamError::protocol(format!("{} columns asked for: at most {MAX_VIEW_COLUMNS}", request.width)));
409 }
410 let values = view.channels.len() as u64 * (view.end - view.start).min(u64::from(request.width));
411 if values > MAX_VIEW_VALUES {
412 return Err(StreamError::protocol(format!("a view of {values} values: at most {MAX_VIEW_VALUES} (fewer channels or columns)")));
413 }
414 Ok(view)
415}
416
417async fn stream_signal(connection: quinn::Connection, recording: Arc<OpenRecording>, first_sample: u64, options: ServeOptions) -> StreamResult<()> {
421 let (source, info, stored) = (recording.source.clone(), recording.info.clone(), recording.stored);
422 let total = info.samples;
423 let frame = ((options.frame_sec * info.sample_rate_hz()).round() as u64).max(1);
424 let channels: Vec<usize> = (0..info.channel_count()).collect();
425 let bytes_per_value = info.format.bytes();
426 let (tx, mut rx) = tokio::sync::mpsc::channel::<StreamResult<(u64, u32, Vec<u8>)>>(options.queue_frames.max(1));
427 let loop_playback = options.loop_playback;
428 std::thread::spawn(move || {
429 let mut start = first_sample.min(total);
430 loop {
431 if start >= total {
432 if !loop_playback || total == 0 {
433 return;
434 }
435 start = 0;
436 }
437 let end = (start + frame).min(total);
438 let n = (end - start) as usize;
439 let mut bytes = vec![0u8; channels.len() * n * bytes_per_value];
440 let read = if stored {
441 source.read_stored(&channels, start..end, &mut bytes)
442 } else {
443 let mut values = vec![0.0f32; channels.len() * n];
444 source.read(&channels, start..end, &mut values).map(|()| {
445 for (dst, v) in bytes.chunks_exact_mut(bytes_per_value).zip(values) {
446 dst.copy_from_slice(&v.to_le_bytes());
447 }
448 })
449 };
450 let message = read.map(|()| (start, n as u32, bytes)).map_err(StreamError::from);
451 let failed = message.is_err();
452 if tx.blocking_send(message).is_err() || failed {
453 return;
454 }
455 start = end;
456 }
457 });
458
459 let mut send = connection.open_uni().await?;
460 let mut ticker = interval(Duration::from_secs_f64(frame as f64 / info.sample_rate_hz()));
461 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
462 let mut sequence = 0u64;
463 async {
464 while let Some(chunk) = rx.recv().await {
465 let (first_sample, samples, data) = chunk?;
466 if options.pacing == Pacing::RealTime {
467 ticker.tick().await;
468 }
469 let sent_unix_ns = SystemTime::now().duration_since(UNIX_EPOCH).map_or(0, |d| d.as_nanos() as u64);
470 write_message(&mut send, &wire::SignalFrame { sequence, first_sample, samples, data, sent_unix_ns }).await?;
471 sequence += 1;
472 }
473 send.finish().map_err(StreamError::protocol)?;
474 StreamResult::Ok(())
475 }
476 .await
477}