1use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
5
6use dspk_core::RecordingInfo;
7use dspk_view::{Envelope, View};
8
9use crate::error::{StreamError, StreamResult};
10use crate::protocol::{
11 decode_signal, envelope_from_frame, info_from_header, read_message, request_from_view, wire, write_message, DEFAULT_MAX_MESSAGE_BYTES, PROTOCOL_VERSION,
12};
13
14pub struct Session {
16 _endpoint: quinn::Endpoint,
18 connection: quinn::Connection,
19 control: quinn::SendStream,
20 _control_recv: quinn::RecvStream,
22 server: wire::ServerInfo,
23 recording: Option<RecordingInfo>,
24 max_message_bytes: usize,
25}
26
27impl Session {
28 pub async fn connect(addr: SocketAddr, server_name: &str, config: quinn::ClientConfig) -> StreamResult<Self> {
31 let local: SocketAddr = if addr.is_ipv6() { (Ipv6Addr::UNSPECIFIED, 0).into() } else { (Ipv4Addr::UNSPECIFIED, 0).into() };
32 let mut endpoint = quinn::Endpoint::client(local)?;
33 endpoint.set_default_client_config(config);
34 let connection = endpoint.connect(addr, server_name)?.await?;
35 let (mut control, mut control_recv) = connection.open_bi().await?;
36 write_message(&mut control, &wire::ClientHello { protocol_version: PROTOCOL_VERSION }).await?;
37 let server: wire::ServerInfo = read_message(&mut control_recv, DEFAULT_MAX_MESSAGE_BYTES).await?.ok_or_else(|| StreamError::protocol("no server info"))?;
38 if server.protocol_version != PROTOCOL_VERSION {
39 return Err(StreamError::protocol(format!("server speaks protocol {}, this client {PROTOCOL_VERSION}", server.protocol_version)));
40 }
41 let recording = server.recording.as_ref().map(info_from_header).transpose()?;
42 Ok(Self { _endpoint: endpoint, connection, control, _control_recv: control_recv, server, recording, max_message_bytes: DEFAULT_MAX_MESSAGE_BYTES })
43 }
44
45 pub fn with_max_message_bytes(mut self, bytes: usize) -> Self {
47 self.max_message_bytes = bytes;
48 self
49 }
50
51 pub fn server(&self) -> &wire::ServerInfo {
53 &self.server
54 }
55
56 pub fn recording(&self) -> Option<&RecordingInfo> {
59 self.recording.as_ref()
60 }
61
62 pub async fn subscribe(&mut self, first_sample: u64) -> StreamResult<SignalReceiver> {
64 let info = self.recording.clone().ok_or_else(|| StreamError::protocol("no recording is open: open one first"))?;
65 write_message(&mut self.control, &wire::Subscribe { first_sample }).await?;
66 let recv = self.connection.accept_uni().await?;
67 Ok(SignalReceiver { recv, info, max_message_bytes: self.max_message_bytes })
68 }
69
70 async fn request(&self, kind: wire::request::Kind) -> StreamResult<wire::response::Kind> {
73 let (mut send, mut recv) = self.connection.open_bi().await?;
74 write_message(&mut send, &wire::Request { kind: Some(kind) }).await?;
75 send.finish().map_err(StreamError::protocol)?;
76 let response: wire::Response = read_message(&mut recv, self.max_message_bytes).await?.ok_or_else(|| StreamError::protocol("no response"))?;
77 match response.kind {
78 Some(wire::response::Kind::Error(message)) => Err(StreamError::Server(message)),
79 Some(kind) => Ok(kind),
80 None => Err(StreamError::protocol("empty response")),
81 }
82 }
83
84 pub async fn view(&self, view: &View) -> StreamResult<Envelope> {
86 match self.request(wire::request::Kind::View(request_from_view(view)?)).await? {
87 wire::response::Kind::Envelope(frame) => envelope_from_frame(frame),
88 _ => Err(StreamError::protocol("unexpected answer to a view")),
89 }
90 }
91
92 pub async fn list_files(&self, dir: &str) -> StreamResult<wire::FileList> {
94 match self.request(wire::request::Kind::ListFiles(wire::ListFiles { dir: dir.into() })).await? {
95 wire::response::Kind::Files(list) => Ok(list),
96 _ => Err(StreamError::protocol("unexpected answer to a listing")),
97 }
98 }
99
100 async fn status_request(&self, kind: wire::request::Kind) -> StreamResult<wire::Status> {
101 match self.request(kind).await? {
102 wire::response::Kind::Status(status) => Ok(status),
103 _ => Err(StreamError::protocol("unexpected answer to a job request")),
104 }
105 }
106
107 pub async fn configure(&self, job: wire::Configure) -> StreamResult<wire::Status> {
109 self.status_request(wire::request::Kind::Configure(job)).await
110 }
111
112 pub async fn start(&self) -> StreamResult<wire::Status> {
114 self.status_request(wire::request::Kind::Start(wire::Start {})).await
115 }
116
117 pub async fn stop(&self) -> StreamResult<wire::Status> {
119 self.status_request(wire::request::Kind::Stop(wire::Stop {})).await
120 }
121
122 pub async fn status(&self) -> StreamResult<wire::Status> {
124 self.status_request(wire::request::Kind::Status(wire::GetStatus {})).await
125 }
126
127 pub async fn logs(&self, lines: u32) -> StreamResult<Vec<String>> {
129 match self.request(wire::request::Kind::Logs(wire::GetLogs { lines })).await? {
130 wire::response::Kind::Logs(logs) => Ok(logs.lines),
131 _ => Err(StreamError::protocol("unexpected answer to logs")),
132 }
133 }
134
135 pub async fn fetch_file(&self, path: &str, dest: &std::path::Path) -> StreamResult<u64> {
138 use tokio::io::AsyncWriteExt as _;
139 let (mut send, mut recv) = self.connection.open_bi().await?;
140 write_message(&mut send, &wire::Request { kind: Some(wire::request::Kind::Fetch(wire::Fetch { path: path.into() })) }).await?;
141 send.finish().map_err(StreamError::protocol)?;
142 let response: wire::Response = read_message(&mut recv, self.max_message_bytes).await?.ok_or_else(|| StreamError::protocol("no response"))?;
143 let header = match response.kind {
144 Some(wire::response::Kind::File(header)) => header,
145 Some(wire::response::Kind::Error(message)) => return Err(StreamError::Server(message)),
146 _ => return Err(StreamError::protocol("unexpected answer to fetch")),
147 };
148 let mut out = tokio::fs::OpenOptions::new().write(true).create_new(true).open(dest).await?;
149 let mut written = 0u64;
150 while let Some(chunk) = read_message::<wire::FileChunk>(&mut recv, self.max_message_bytes).await? {
151 written += chunk.data.len() as u64;
152 if written > header.size {
153 return Err(StreamError::protocol("the server sent more than the file's size"));
154 }
155 out.write_all(&chunk.data).await?;
156 }
157 out.flush().await?;
158 if written != header.size {
159 return Err(StreamError::Truncated);
160 }
161 Ok(written)
162 }
163
164 pub async fn fetch(&self, path: &str, dest: &std::path::Path) -> StreamResult<(usize, u64)> {
168 let parent = path.rsplit_once('/').map_or("", |(dir, _)| dir);
169 let name = path.rsplit('/').next().unwrap_or(path);
170 let listing = self.list_files(parent).await?;
171 let entry = listing.entries.iter().find(|e| e.name == name).ok_or_else(|| StreamError::Server(format!("{path:?} not found")))?;
172 if !entry.directory {
173 return Ok((1, self.fetch_file(path, dest).await?));
174 }
175 if dest.symlink_metadata().is_ok() {
176 return Err(StreamError::Io(std::io::Error::new(std::io::ErrorKind::AlreadyExists, format!("{} exists (never overwritten)", dest.display()))));
177 }
178 tokio::fs::create_dir(dest).await?;
179 let (mut files, mut bytes) = (0usize, 0u64);
180 let mut stack = vec![(path.to_string(), dest.to_path_buf())];
182 while let Some((remote, local)) = stack.pop() {
183 for e in self.list_files(&remote).await?.entries {
184 let plain = !e.name.is_empty() && !e.name.starts_with('.') && !e.name.contains(['/', '\\']) && e.name != "..";
185 if !plain {
186 return Err(StreamError::protocol(format!("the server listed {:?}: not a plain name", e.name)));
187 }
188 let (r, l) = (format!("{remote}/{}", e.name), local.join(&e.name));
189 if e.directory {
190 tokio::fs::create_dir(&l).await?;
191 stack.push((r, l));
192 } else {
193 bytes += self.fetch_file(&r, &l).await?;
194 files += 1;
195 }
196 }
197 }
198 Ok((files, bytes))
199 }
200
201 pub async fn open(&mut self, path: &str, source: Option<&str>) -> StreamResult<&RecordingInfo> {
204 let open = wire::Open { path: path.into(), source: source.unwrap_or_default().into() };
205 match self.request(wire::request::Kind::Open(open)).await? {
206 wire::response::Kind::Header(header) => {
207 self.recording = Some(info_from_header(&header)?);
208 Ok(self.recording.as_ref().expect("just set"))
209 }
210 _ => Err(StreamError::protocol("unexpected answer to open")),
211 }
212 }
213
214 pub async fn close(mut self) {
216 let _ = self.control.finish();
217 self.connection.close(quinn::VarInt::from_u32(0), b"client closed");
218 self._endpoint.wait_idle().await;
219 }
220}
221
222pub struct SignalReceiver {
224 recv: quinn::RecvStream,
225 info: RecordingInfo,
226 max_message_bytes: usize,
227}
228
229impl SignalReceiver {
230 pub async fn next_frame(&mut self) -> StreamResult<Option<wire::SignalFrame>> {
232 read_message(&mut self.recv, self.max_message_bytes).await
233 }
234
235 pub async fn next_values(&mut self) -> StreamResult<Option<(u64, Vec<f32>)>> {
237 match self.next_frame().await? {
238 Some(frame) => Ok(Some((frame.first_sample, decode_signal(&frame, &self.info)?))),
239 None => Ok(None),
240 }
241 }
242}