netget/server/webtransport/
session.rs1use super::actions::{validate, DATAGRAM_EVENT, STREAM_EVENT, STREAM_REPLY_EVENT};
5use crate::client::command_support;
6use crate::logging::emit::Log;
7use crate::protocol::Event;
8use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
9use anyhow::{bail, ensure, Context, Result};
10use futures::future::BoxFuture;
11use futures::{stream::FuturesUnordered, StreamExt};
12use serde_json::{json, Value};
13use std::sync::Arc;
14use std::time::Duration;
15use tokio::sync::mpsc;
16use wtransport::{Connection, RecvStream, SendStream, VarInt};
17
18pub const MAX_STREAM_BYTES: usize = 1024 * 1024;
20pub const STREAM_TIMEOUT: Duration = Duration::from_secs(30);
22pub const MAX_IN_FLIGHT: usize = 32;
24pub const MAX_FOLLOWUP_DEPTH: usize = 4;
26pub const CODE_REFUSED: u32 = 0x1;
28pub const CODE_FAILED: u32 = 0x2;
29
30pub type AskFn = Arc<dyn Fn(Event) -> BoxFuture<'static, Result<Vec<Value>>> + Send + Sync>;
31pub type TrafficFn = Arc<dyn Fn(u64, u64) -> BoxFuture<'static, ()> + Send + Sync>;
32
33pub struct Session {
34 pub conn: Connection,
35 ask: AskFn,
36 traffic: TrafficFn,
37 status_tx: mpsc::UnboundedSender<String>,
38 label: &'static str,
39 turn: tokio::sync::Mutex<()>,
40}
41
42pub fn payload(v: &Value) -> Result<Vec<u8>> {
44 let data = v["data"].as_str().context("data is text")?;
45 let bytes = match v["encoding"].as_str().unwrap_or("utf8") {
46 "utf8" => data.as_bytes().to_vec(),
47 "hex" => hex::decode(data).context("data is not hex")?,
48 other => bail!("encoding {other:?} is utf8 or hex"),
49 };
50 ensure!(
51 bytes.len() <= MAX_STREAM_BYTES,
52 "data exceeds {MAX_STREAM_BYTES} bytes"
53 );
54 Ok(bytes)
55}
56
57pub fn describe(bytes: &[u8]) -> (String, &'static str) {
59 match std::str::from_utf8(bytes) {
60 Ok(text) => (text.to_owned(), "utf8"),
61 Err(_) => (hex::encode(bytes), "hex"),
62 }
63}
64
65pub async fn read_all(mut recv: RecvStream) -> Result<Vec<u8>> {
67 let read = async {
68 let mut data = Vec::new();
69 let mut buf = vec![0u8; 16 * 1024];
70 while let Some(n) = recv.read(&mut buf).await? {
71 if n > MAX_STREAM_BYTES - data.len() {
72 return Ok(None);
73 }
74 data.extend_from_slice(&buf[..n]);
75 }
76 Ok::<_, anyhow::Error>(Some(data))
77 };
78 match tokio::time::timeout(STREAM_TIMEOUT, read).await {
79 Ok(Ok(Some(data))) => Ok(data),
80 Ok(Ok(None)) => {
81 recv.stop(VarInt::from_u32(CODE_REFUSED));
82 bail!("the stream exceeds {MAX_STREAM_BYTES} bytes")
83 }
84 Ok(Err(e)) => Err(e),
85 Err(_) => {
86 recv.stop(VarInt::from_u32(CODE_REFUSED));
87 bail!("the stream did not finish within {STREAM_TIMEOUT:?}")
88 }
89 }
90}
91
92async fn write_and_finish(send: &mut SendStream, bytes: &[u8]) -> Result<()> {
93 tokio::time::timeout(STREAM_TIMEOUT, async {
94 send.write_all(bytes).await?;
95 send.finish().await?;
96 Ok::<_, anyhow::Error>(())
97 })
98 .await
99 .context("the peer did not take the data in time")?
100}
101
102impl Session {
103 pub fn new(
104 conn: Connection,
105 ask: AskFn,
106 traffic: TrafficFn,
107 status_tx: mpsc::UnboundedSender<String>,
108 label: &'static str,
109 ) -> Arc<Self> {
110 Arc::new(Self {
111 conn,
112 ask,
113 traffic,
114 status_tx,
115 label,
116 turn: tokio::sync::Mutex::new(()),
117 })
118 }
119
120 fn log(&self) -> Log<'_> {
121 Log::new(Some(&self.status_tx))
122 }
123
124 pub async fn ask(&self, event: Event) -> Result<Vec<Value>> {
126 let _turn = self.turn.lock().await;
127 (self.ask)(event).await
128 }
129
130 pub async fn run(
132 self: Arc<Self>,
133 initial: Vec<Value>,
134 first: Option<Event>,
135 mut commands: mpsc::Receiver<ClientCommand>,
136 ) -> String {
137 let mut work: FuturesUnordered<BoxFuture<'static, ()>> = FuturesUnordered::new();
138 if !initial.is_empty() {
139 work.push(self.clone().execute(initial, None, 0));
140 }
141 if let Some(event) = first {
142 let s = self.clone();
143 work.push(Box::pin(async move {
144 match s.ask(event).await {
145 Ok(actions) => s.clone().execute(actions, None, 0).await,
146 Err(e) => s.log().error(format!(
147 "{} decision=fail_closed_llm_error; nothing sent: {e}",
148 s.label
149 )),
150 }
151 }));
152 }
153 let mut commands_open = true;
154 loop {
155 let room = work.len() < MAX_IN_FLIGHT;
156 tokio::select! {
157 r = self.conn.accept_bi(), if room => match r {
158 Ok((send, recv)) => work.push(Box::pin(self.clone().incoming(Some(send), recv))),
159 Err(e) => return e.to_string(),
160 },
161 r = self.conn.accept_uni(), if room => match r {
162 Ok(recv) => work.push(Box::pin(self.clone().incoming(None, recv))),
163 Err(e) => return e.to_string(),
164 },
165 r = self.conn.receive_datagram(), if room => match r {
166 Ok(d) => work.push(Box::pin(self.clone().datagram(d.payload().to_vec()))),
167 Err(e) => return e.to_string(),
168 },
169 c = commands.recv(), if commands_open && room => match c {
170 Some(c) => {
171 let checked = match c.action["type"].as_str() {
172 Some("webtransport_send_datagram" | "webtransport_open_uni" | "webtransport_open_bi" | "webtransport_close") => validate(&c.action),
173 _ => Err(anyhow::anyhow!("a live session takes webtransport_send_datagram, webtransport_open_uni, webtransport_open_bi or webtransport_close")),
174 };
175 match checked {
176 Ok(()) => {
177 let bytes_sent = payload(&c.action).map(|p| p.len()).unwrap_or(0);
178 work.push(self.clone().execute(vec![c.action.clone()], None, 0));
179 command_support::reply(c, Ok(ClientSendOutcome::Sent { bytes_sent }));
180 }
181 Err(e) => command_support::reply(c, Ok(ClientSendOutcome::Rejected { error: e.to_string() })),
182 }
183 }
184 None => commands_open = false,
185 },
186 _ = work.next(), if !work.is_empty() => {}
187 e = self.conn.closed() => return e.to_string(),
188 }
189 }
190 }
191
192 async fn incoming(self: Arc<Self>, send: Option<SendStream>, recv: RecvStream) {
193 let stream_id = recv.id().into_u64();
194 let data = match read_all(recv).await {
195 Ok(data) => data,
196 Err(e) => {
197 self.log().warn(format!(
198 "{} decision=protocol_refusal stream={stream_id}: {e}",
199 self.label
200 ));
201 if let Some(mut send) = send {
202 let _ = send.reset(VarInt::from_u32(CODE_REFUSED));
203 }
204 return;
205 }
206 };
207 (self.traffic)(data.len() as u64, 0).await;
208 let (text, encoding) = describe(&data);
209 let direction = if send.is_some() {
210 "bidirectional"
211 } else {
212 "unidirectional"
213 };
214 self.log().debug(format!(
215 "{} {direction} stream {stream_id}: {} bytes",
216 self.label,
217 data.len()
218 ));
219 let event = Event::new(
220 &STREAM_EVENT,
221 json!({"stream_id": stream_id, "direction": direction, "data": text, "encoding": encoding}),
222 );
223 match self.ask(event).await {
224 Ok(actions) => self.clone().execute(actions, send, 0).await,
225 Err(e) => {
226 self.log().error(format!(
227 "{} decision=fail_closed_llm_error stream={stream_id}; resetting it: {e}",
228 self.label
229 ));
230 if let Some(mut send) = send {
231 let _ = send.reset(VarInt::from_u32(CODE_FAILED));
232 }
233 }
234 }
235 }
236
237 async fn datagram(self: Arc<Self>, data: Vec<u8>) {
238 (self.traffic)(data.len() as u64, 0).await;
239 let (text, encoding) = describe(&data);
240 let event = Event::new(&DATAGRAM_EVENT, json!({"data": text, "encoding": encoding}));
241 match self.ask(event).await {
242 Ok(actions) => self.clone().execute(actions, None, 0).await,
243 Err(e) => self.log().error(format!(
244 "{} decision=fail_closed_llm_error datagram; nothing sent: {e}",
245 self.label
246 )),
247 }
248 }
249
250 pub fn execute(
253 self: Arc<Self>,
254 actions: Vec<Value>,
255 mut reply: Option<SendStream>,
256 depth: usize,
257 ) -> BoxFuture<'static, ()> {
258 Box::pin(async move {
259 for a in actions {
260 let kind = a["type"].as_str().unwrap_or_default().to_owned();
261 if !kind.starts_with("webtransport_") {
262 continue;
263 }
264 if let Err(e) = validate(&a) {
265 self.log().warn(format!(
266 "{} decision=fail_closed_invalid_reply {kind}: {e}",
267 self.label
268 ));
269 continue;
270 }
271 if let Err(e) = self.clone().act(&kind, &a, &mut reply, depth).await {
272 self.log()
273 .warn(format!("{} {kind} failed: {e:#}", self.label));
274 }
275 if kind == "webtransport_close" {
276 return;
277 }
278 }
279 if let Some(mut send) = reply {
280 self.log().info(format!(
281 "{} decision=model_silent; finishing the stream empty",
282 self.label
283 ));
284 let _ = send.finish().await;
285 }
286 })
287 }
288
289 async fn act(
290 self: Arc<Self>,
291 kind: &str,
292 a: &Value,
293 reply: &mut Option<SendStream>,
294 depth: usize,
295 ) -> Result<()> {
296 match kind {
297 "webtransport_reply" => {
298 let mut send = reply
299 .take()
300 .context("webtransport_reply answers a bidirectional stream, and there is none to answer")?;
301 let bytes = payload(a)?;
302 self.log().info(format!(
303 "{} decision=model_answer; replying {} bytes",
304 self.label,
305 bytes.len()
306 ));
307 write_and_finish(&mut send, &bytes).await?;
308 (self.traffic)(0, bytes.len() as u64).await;
309 }
310 "webtransport_send_datagram" => {
311 let bytes = payload(a)?;
312 self.conn.send_datagram(&bytes)?;
313 (self.traffic)(0, bytes.len() as u64).await;
314 }
315 "webtransport_open_uni" => {
316 let bytes = payload(a)?;
317 let mut send = self.conn.open_uni().await?.await?;
318 write_and_finish(&mut send, &bytes).await?;
319 (self.traffic)(0, bytes.len() as u64).await;
320 }
321 "webtransport_open_bi" => {
322 let bytes = payload(a)?;
323 let (mut send, recv) = self.conn.open_bi().await?.await?;
324 let stream_id = send.id().into_u64();
325 write_and_finish(&mut send, &bytes).await?;
326 (self.traffic)(0, bytes.len() as u64).await;
327 let answer = read_all(recv).await?;
328 (self.traffic)(answer.len() as u64, 0).await;
329 if depth + 1 >= MAX_FOLLOWUP_DEPTH {
330 self.log().warn(format!(
331 "{} follow-up depth {MAX_FOLLOWUP_DEPTH} reached; the reply on stream {stream_id} is not raised",
332 self.label
333 ));
334 return Ok(());
335 }
336 let (text, encoding) = describe(&answer);
337 let event = Event::new(
338 &STREAM_REPLY_EVENT,
339 json!({"stream_id": stream_id, "request": a["data"], "data": text, "encoding": encoding}),
340 );
341 let actions = self.ask(event).await?;
342 self.clone().execute(actions, None, depth + 1).await;
343 }
344 "webtransport_close" => {
345 let code = a["code"].as_u64().unwrap_or(0) as u32;
346 let reason = a["reason"].as_str().unwrap_or_default();
347 self.log().info(format!(
348 "{} closing the session: code={code} reason={reason:?}",
349 self.label
350 ));
351 self.conn.close(VarInt::from_u32(code), reason.as_bytes());
352 }
353 "webtransport_accept" | "webtransport_reject" => {}
355 other => bail!("unknown action {other}"),
356 }
357 Ok(())
358 }
359}