1use super::{
16 AnyStreamInfo, ByteStreamInfo, StreamError, StreamProgress, StreamResult, TextStreamInfo,
17};
18use bytes::{Bytes, BytesMut};
19use futures_util::{Stream, StreamExt};
20use livekit_common::EncryptionType;
21use livekit_protocol::data_stream as proto;
22use parking_lot::Mutex;
23use std::{
24 collections::HashMap,
25 fmt::Debug,
26 pin::Pin,
27 sync::Arc,
28 task::{Context, Poll},
29};
30use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
31
32pub trait StreamReader: Stream<Item = StreamResult<Self::Output>> {
38 type Output;
40
41 type Info;
43
44 fn info(&self) -> &Self::Info;
46
47 fn read_all(self) -> impl std::future::Future<Output = StreamResult<Self::Output>> + Send;
53}
54
55pub struct ByteStreamReader {
57 info: ByteStreamInfo,
58 chunk_rx: UnboundedReceiver<StreamResult<Bytes>>,
59}
60
61pub struct TextStreamReader {
63 info: TextStreamInfo,
64 chunk_rx: UnboundedReceiver<StreamResult<Bytes>>,
65}
66
67impl StreamReader for ByteStreamReader {
68 type Output = Bytes;
69 type Info = ByteStreamInfo;
70
71 fn info(&self) -> &ByteStreamInfo {
72 &self.info
73 }
74
75 async fn read_all(mut self) -> StreamResult<Bytes> {
76 let mut buffer = BytesMut::new();
77 while let Some(result) = self.next().await {
78 match result {
79 Ok(bytes) => buffer.extend_from_slice(&bytes),
80 Err(e) => return Err(e),
81 }
82 }
83 Ok(buffer.freeze())
84 }
85}
86
87impl ByteStreamReader {
88 pub async fn write_to_file(
97 mut self,
98 directory: Option<impl AsRef<std::path::Path>>,
99 name_override: Option<&str>,
100 ) -> StreamResult<std::path::PathBuf> {
101 let directory =
102 directory.map(|d| d.as_ref().to_path_buf()).unwrap_or_else(|| std::env::temp_dir());
103 let name = name_override.unwrap_or_else(|| &self.info.name);
104 let file_path = directory.join(name);
105
106 let mut file = tokio::fs::File::create(&file_path).await.map_err(StreamError::Io)?;
107
108 while let Some(result) = self.next().await {
109 let bytes = result?;
110 tokio::io::AsyncWriteExt::write_all(&mut file, &bytes)
111 .await
112 .map_err(StreamError::Io)?;
113 }
114 tokio::io::AsyncWriteExt::flush(&mut file).await.map_err(StreamError::Io)?;
115
116 Ok(file_path)
117 }
118}
119
120impl Stream for ByteStreamReader {
121 type Item = StreamResult<Bytes>;
122
123 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
124 let this = self.get_mut();
125 match Pin::new(&mut this.chunk_rx).poll_recv(cx) {
126 Poll::Ready(Some(Ok(chunk))) => Poll::Ready(Some(Ok(chunk))),
127 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
128 Poll::Ready(None) => Poll::Ready(None),
129 Poll::Pending => Poll::Pending,
130 }
131 }
132}
133
134#[cfg(feature = "test-utils")]
135impl TextStreamReader {
136 pub fn new_for_test(
138 info: TextStreamInfo,
139 chunk_rx: UnboundedReceiver<StreamResult<Bytes>>,
140 ) -> Self {
141 Self { info, chunk_rx }
142 }
143}
144
145impl StreamReader for TextStreamReader {
146 type Output = String;
147 type Info = TextStreamInfo;
148
149 fn info(&self) -> &TextStreamInfo {
150 &self.info
151 }
152
153 async fn read_all(mut self) -> StreamResult<String> {
154 let mut result = String::new();
155 while let Some(chunk) = self.next().await {
156 match chunk {
157 Ok(text) => result.push_str(&text),
158 Err(e) => return Err(e),
159 }
160 }
161 Ok(result)
162 }
163}
164
165impl Stream for TextStreamReader {
166 type Item = StreamResult<String>;
167
168 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
169 let this = self.get_mut();
170 match Pin::new(&mut this.chunk_rx).poll_recv(cx) {
171 Poll::Ready(Some(Ok(chunk))) => match String::from_utf8(chunk.into()) {
172 Ok(content) => Poll::Ready(Some(Ok(content))),
173 Err(e) => {
174 this.chunk_rx.close();
175 Poll::Ready(Some(Err(StreamError::from(e))))
176 }
177 },
178 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
179 Poll::Ready(None) => Poll::Ready(None),
180 Poll::Pending => Poll::Pending,
181 }
182 }
183}
184
185impl Debug for ByteStreamReader {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.debug_struct("ByteStreamReader")
188 .field("id", &self.info.id())
189 .field("topic", &self.info.topic)
190 .finish()
191 }
192}
193
194impl Debug for TextStreamReader {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("TextStreamReader")
197 .field("id", &self.info.id())
198 .field("topic", &self.info.topic)
199 .finish()
200 }
201}
202
203pub enum AnyStreamReader {
204 Byte(ByteStreamReader),
205 Text(TextStreamReader),
206}
207
208impl AnyStreamReader {
209 pub(super) fn from(info: AnyStreamInfo) -> (Self, UnboundedSender<StreamResult<Bytes>>) {
211 let (chunk_tx, chunk_rx) = mpsc::unbounded_channel();
212 let reader = match info {
213 AnyStreamInfo::Byte(info) => Self::Byte(ByteStreamReader { info, chunk_rx }),
214 AnyStreamInfo::Text(info) => Self::Text(TextStreamReader { info, chunk_rx }),
215 };
216 return (reader, chunk_tx);
217 }
218}
219struct Descriptor {
220 progress: StreamProgress,
221 chunk_tx: UnboundedSender<StreamResult<Bytes>>,
222 encryption_type: EncryptionType,
223 is_internal: bool,
224 }
226
227#[derive(Clone)]
228pub struct IncomingStreamManager {
229 inner: Arc<Mutex<ManagerInner>>,
230 open_tx: UnboundedSender<(AnyStreamReader, String)>,
231 reserved_topics: Arc<[String]>,
234}
235
236#[derive(Default)]
237struct ManagerInner {
238 open_streams: HashMap<String, Descriptor>,
239}
240
241impl IncomingStreamManager {
242 pub fn new(
243 reserved_topics: Vec<String>,
244 ) -> (Self, UnboundedReceiver<(AnyStreamReader, String)>) {
245 let (open_tx, open_rx) = mpsc::unbounded_channel();
246 (
247 Self {
248 inner: Arc::new(Mutex::new(Default::default())),
249 open_tx,
250 reserved_topics: reserved_topics.into(),
251 },
252 open_rx,
253 )
254 }
255
256 pub fn handle_header(
258 &self,
259 header: proto::Header,
260 identity: String,
261 encryption_type: livekit_protocol::encryption::Type,
262 ) {
263 let is_internal = self.reserved_topics.iter().any(|t| t == &header.topic);
264 let Ok(info) = AnyStreamInfo::try_from_with_encryption(header, encryption_type.into())
265 .inspect_err(|e| log::error!("Invalid header: {}", e))
266 else {
267 return;
268 };
269
270 let id = info.id().to_owned();
271 let bytes_total = info.total_length();
272 let stream_encryption_type = info.encryption_type();
273
274 let mut inner = self.inner.lock();
275 if inner.open_streams.contains_key(&id) {
276 log::error!("Stream '{}' already open", id);
277 return;
278 }
279
280 let (reader, chunk_tx) = AnyStreamReader::from(info);
281 let _ = self.open_tx.send((reader, identity));
282
283 let descriptor = Descriptor {
284 progress: StreamProgress { bytes_total, ..Default::default() },
285 chunk_tx,
286 encryption_type: stream_encryption_type,
287 is_internal,
288 };
289 inner.open_streams.insert(id, descriptor);
290 }
291
292 pub fn is_internal(&self, stream_id: &str) -> bool {
296 self.inner.lock().open_streams.get(stream_id).is_some_and(|d| d.is_internal)
297 }
298
299 pub fn handle_chunk(
301 &self,
302 chunk: proto::Chunk,
303 encryption_type: livekit_protocol::encryption::Type,
304 ) {
305 let id = chunk.stream_id;
306 let mut inner = self.inner.lock();
307 let Some(descriptor) = inner.open_streams.get_mut(&id) else {
308 return;
309 };
310
311 if descriptor.encryption_type != encryption_type.into() {
312 inner.close_stream_with_error(&id, StreamError::EncryptionTypeMismatch);
313 return;
314 }
315
316 if descriptor.progress.chunk_index != chunk.chunk_index {
317 inner.close_stream_with_error(&id, StreamError::MissedChunk);
318 return;
319 }
320
321 descriptor.progress.chunk_index += 1;
322 descriptor.progress.bytes_processed += chunk.content.len() as u64;
323
324 if match descriptor.progress.bytes_total {
325 Some(total) => descriptor.progress.bytes_processed > total as u64,
326 None => false,
327 } {
328 inner.close_stream_with_error(&id, StreamError::LengthExceeded);
329 return;
330 }
331 inner.yield_chunk(&id, Bytes::from(chunk.content));
332 }
334
335 pub fn handle_trailer(&self, trailer: proto::Trailer) {
337 let id = trailer.stream_id;
338 let mut inner = self.inner.lock();
339 let Some(descriptor) = inner.open_streams.get_mut(&id) else {
340 return;
341 };
342
343 if !match descriptor.progress.bytes_total {
344 Some(total) => descriptor.progress.bytes_processed >= total as u64,
345 None => true,
346 } {
347 inner.close_stream_with_error(&id, StreamError::Incomplete);
348 return;
349 }
350 if !trailer.reason.is_empty() {
351 inner.close_stream_with_error(&id, StreamError::AbnormalEnd(trailer.reason));
352 return;
353 }
354 inner.close_stream(&id);
355 }
356}
357
358impl ManagerInner {
359 fn yield_chunk(&mut self, id: &str, chunk: Bytes) {
360 let Some(descriptor) = self.open_streams.get_mut(id) else {
361 return;
362 };
363 if descriptor.chunk_tx.send(Ok(chunk)).is_err() {
364 self.close_stream(id);
366 }
367 }
368
369 fn close_stream(&mut self, id: &str) {
370 self.open_streams.remove(id);
372 }
373
374 fn close_stream_with_error(&mut self, id: &str, error: StreamError) {
375 if let Some(descriptor) = self.open_streams.remove(id) {
376 let _ = descriptor.chunk_tx.send(Err(error));
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use livekit_protocol::encryption::Type as EncType;
385 use std::collections::HashMap;
386
387 const SENDER: &str = "alice";
388
389 fn attrs(pairs: &[(&str, &str)]) -> HashMap<String, String> {
390 pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
391 }
392
393 fn text_header(
394 id: &str,
395 total_length: Option<u64>,
396 attributes: HashMap<String, String>,
397 inline_content: Option<Vec<u8>>,
398 compression: proto::CompressionType,
399 ) -> proto::Header {
400 proto::Header {
401 stream_id: id.to_string(),
402 timestamp: 0,
403 topic: "topic".to_string(),
404 mime_type: "text/plain".to_string(),
405 total_length,
406 encryption_type: 0,
407 attributes,
408 content_header: Some(proto::header::ContentHeader::TextHeader(
409 proto::TextHeader::default(),
410 )),
411 inline_content,
412 compression: compression as i32,
413 }
414 }
415
416 fn byte_header(
417 id: &str,
418 total_length: Option<u64>,
419 inline_content: Option<Vec<u8>>,
420 compression: proto::CompressionType,
421 ) -> proto::Header {
422 proto::Header {
423 stream_id: id.to_string(),
424 timestamp: 0,
425 topic: "topic".to_string(),
426 mime_type: "application/octet-stream".to_string(),
427 total_length,
428 encryption_type: 0,
429 attributes: HashMap::new(),
430 content_header: Some(proto::header::ContentHeader::ByteHeader(proto::ByteHeader {
431 name: "file".to_string(),
432 })),
433 inline_content,
434 compression: compression as i32,
435 }
436 }
437
438 fn chunk(id: &str, index: u64, content: Vec<u8>) -> proto::Chunk {
439 proto::Chunk {
440 stream_id: id.to_string(),
441 chunk_index: index,
442 content,
443 ..Default::default()
444 }
445 }
446
447 fn trailer(id: &str) -> proto::Trailer {
448 proto::Trailer { stream_id: id.to_string(), ..Default::default() }
449 }
450
451 fn trailer_with_attrs(id: &str, attributes: HashMap<String, String>) -> proto::Trailer {
452 proto::Trailer { stream_id: id.to_string(), reason: String::new(), attributes }
453 }
454
455 async fn read_text(reader: AnyStreamReader) -> StreamResult<String> {
456 match reader {
457 AnyStreamReader::Text(r) => r.read_all().await,
458 _ => panic!("expected a text reader"),
459 }
460 }
461
462 async fn read_bytes(reader: AnyStreamReader) -> StreamResult<Bytes> {
463 match reader {
464 AnyStreamReader::Byte(r) => r.read_all().await,
465 _ => panic!("expected a byte reader"),
466 }
467 }
468
469 fn text_info(reader: &AnyStreamReader) -> &TextStreamInfo {
470 match reader {
471 AnyStreamReader::Text(r) => r.info(),
472 _ => panic!("expected a text reader"),
473 }
474 }
475
476 #[tokio::test]
477 async fn text_stream_round_trips() {
478 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
479 let text = "hello world";
480 mgr.handle_header(
481 text_header(
482 "s1",
483 Some(text.len() as u64),
484 attrs(&[("foo", "bar")]),
485 None,
486 proto::CompressionType::None,
487 ),
488 SENDER.to_string(),
489 EncType::None,
490 );
491 let (reader, identity) = rx.recv().await.expect("a reader should be dispatched");
492 assert_eq!(identity, SENDER);
493 assert_eq!(text_info(&reader).attributes.get("foo"), Some(&"bar".to_string()));
494 mgr.handle_chunk(chunk("s1", 0, text.as_bytes().to_vec()), EncType::None);
495 mgr.handle_trailer(trailer("s1"));
496 assert_eq!(read_text(reader).await.unwrap(), text);
497 }
498
499 #[tokio::test]
500 async fn byte_stream_round_trips() {
501 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
502 mgr.handle_header(
503 byte_header("s1", Some(4), None, proto::CompressionType::None),
504 SENDER.to_string(),
505 EncType::None,
506 );
507 let (reader, _) = rx.recv().await.expect("a reader should be dispatched");
508 mgr.handle_chunk(chunk("s1", 0, vec![1, 2, 3, 4]), EncType::None);
509 mgr.handle_trailer(trailer("s1"));
510 assert_eq!(read_bytes(reader).await.unwrap(), Bytes::from(vec![1u8, 2, 3, 4]));
511 }
512
513 #[tokio::test]
514 async fn merges_trailer_attributes() {
515 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
516 let text = "hi";
517 mgr.handle_header(
518 text_header(
519 "s1",
520 Some(text.len() as u64),
521 attrs(&[("foo", "bar"), ("baz", "quux")]),
522 None,
523 proto::CompressionType::None,
524 ),
525 SENDER.to_string(),
526 EncType::None,
527 );
528 let (reader, _) = rx.recv().await.expect("a reader should be dispatched");
529 mgr.handle_chunk(chunk("s1", 0, text.as_bytes().to_vec()), EncType::None);
530 mgr.handle_trailer(trailer_with_attrs(
531 "s1",
532 attrs(&[("hello", "world"), ("foo", "updated")]),
533 ));
534 let info_attrs = text_info(&reader).attributes.clone();
536 assert_eq!(read_text(reader).await.unwrap(), text);
537 assert_eq!(info_attrs.get("baz"), Some(&"quux".to_string()));
539 }
540
541 #[tokio::test]
542 async fn errors_when_too_few_bytes() {
543 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
544 mgr.handle_header(
545 text_header("s1", Some(5), HashMap::new(), None, proto::CompressionType::None),
546 SENDER.to_string(),
547 EncType::None,
548 );
549 let (reader, _) = rx.recv().await.expect("a reader should be dispatched");
550 mgr.handle_chunk(chunk("s1", 0, vec![b'x']), EncType::None);
551 mgr.handle_trailer(trailer("s1"));
552 assert!(matches!(read_text(reader).await, Err(StreamError::Incomplete)));
553 }
554
555 #[tokio::test]
556 async fn errors_when_too_many_bytes() {
557 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
558 mgr.handle_header(
559 byte_header("s1", Some(3), None, proto::CompressionType::None),
560 SENDER.to_string(),
561 EncType::None,
562 );
563 let (reader, _) = rx.recv().await.expect("a reader should be dispatched");
564 mgr.handle_chunk(chunk("s1", 0, vec![1, 2, 3, 4, 5]), EncType::None);
565 mgr.handle_trailer(trailer("s1"));
566 assert!(matches!(read_bytes(reader).await, Err(StreamError::LengthExceeded)));
567 }
568
569 #[tokio::test]
570 async fn drops_on_encryption_type_mismatch() {
571 let (mgr, mut rx) = IncomingStreamManager::new(vec![]);
572 mgr.handle_header(
573 text_header("s1", Some(2), HashMap::new(), None, proto::CompressionType::None),
574 SENDER.to_string(),
575 EncType::None,
576 );
577 let (reader, _) = rx.recv().await.expect("a reader should be dispatched");
578 mgr.handle_chunk(chunk("s1", 0, vec![b'h', b'i']), EncType::Gcm);
579 assert!(matches!(read_text(reader).await, Err(StreamError::EncryptionTypeMismatch)));
580 }
581}