1use std::io;
7use std::pin::Pin;
8use std::sync::Arc;
9use std::path::Path;
10
11use async_trait::async_trait;
12use futures::{Stream, StreamExt};
13use tokio::io::{AsyncReadExt, AsyncWriteExt};
14
15use crate::error::{Error, Result, SandboxError, ResourceKind};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum StreamDirection {
20 HostToGuest,
22
23 GuestToHost,
25
26 Bidirectional,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum StreamType {
33 Bytes,
35
36 Text,
38
39 Json,
41
42 MessagePack,
44}
45
46#[derive(Debug, Clone)]
48pub struct StreamingChannelConfig {
49 pub direction: StreamDirection,
51
52 pub stream_type: StreamType,
54
55 pub buffer_size: usize,
57
58 pub max_chunk_size: usize,
60
61 pub validate_utf8: bool,
63}
64
65impl Default for StreamingChannelConfig {
66 fn default() -> Self {
67 Self {
68 direction: StreamDirection::Bidirectional,
69 stream_type: StreamType::Bytes,
70 buffer_size: 64 * 1024, max_chunk_size: 16 * 1024, validate_utf8: true,
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct StreamingStats {
80 pub bytes_sent: u64,
82
83 pub bytes_received: u64,
85
86 pub chunks_sent: u64,
88
89 pub chunks_received: u64,
91
92 pub average_chunk_size: f64,
94
95 pub max_chunk_size_seen: usize,
97
98 pub error_count: u64,
100}
101
102pub trait StreamingChannel: Send + Sync {
104 fn id(&self) -> &str;
106
107 fn config(&self) -> &StreamingChannelConfig;
109
110 fn stats(&self) -> StreamingStats;
112
113 fn is_open(&self) -> bool;
115
116 fn close(&self) -> Result<()>;
118}
119
120#[derive(Debug, Clone)]
122pub struct StreamChunk {
123 pub data: Vec<u8>,
125
126 pub is_final: bool,
128
129 pub sequence: u64,
131
132 pub metadata: Option<serde_json::Value>,
134}
135
136#[async_trait]
138pub trait StreamingInput: StreamingChannel {
139 async fn send_chunk(&self, chunk: StreamChunk) -> Result<()>;
141
142 async fn send_bytes(&self, data: &[u8], is_final: bool) -> Result<()>;
144
145 async fn send_stream<S>(&self, stream: &mut S) -> Result<u64>
147 where
148 S: Stream<Item = Result<StreamChunk>> + Unpin + Send;
149}
150
151#[async_trait]
153pub trait StreamingOutput: StreamingChannel {
154 async fn receive_chunk(&self) -> Result<StreamChunk>;
156
157 async fn receive_bytes(&self, max_size: Option<usize>) -> Result<Vec<u8>>;
159
160 fn receive_stream(&self) -> Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>;
162}
163
164pub trait StreamingChannel2Way: StreamingInput + StreamingOutput {}
166
167#[derive(Clone)]
169pub enum StreamingChannel2WayImpl {
170 Memory(MemoryStreamingChannel),
171 File(FileStreamingChannel),
172}
173
174impl StreamingChannel for StreamingChannel2WayImpl {
175 fn id(&self) -> &str {
176 match self {
177 StreamingChannel2WayImpl::Memory(ch) => ch.id(),
178 StreamingChannel2WayImpl::File(ch) => ch.id(),
179 }
180 }
181
182 fn config(&self) -> &StreamingChannelConfig {
183 match self {
184 StreamingChannel2WayImpl::Memory(ch) => ch.config(),
185 StreamingChannel2WayImpl::File(ch) => ch.config(),
186 }
187 }
188
189 fn stats(&self) -> StreamingStats {
190 match self {
191 StreamingChannel2WayImpl::Memory(ch) => ch.stats(),
192 StreamingChannel2WayImpl::File(ch) => ch.stats(),
193 }
194 }
195
196 fn is_open(&self) -> bool {
197 match self {
198 StreamingChannel2WayImpl::Memory(ch) => ch.is_open(),
199 StreamingChannel2WayImpl::File(ch) => ch.is_open(),
200 }
201 }
202
203 fn close(&self) -> Result<()> {
204 match self {
205 StreamingChannel2WayImpl::Memory(ch) => ch.close(),
206 StreamingChannel2WayImpl::File(ch) => ch.close(),
207 }
208 }
209}
210
211#[async_trait]
212impl StreamingInput for StreamingChannel2WayImpl {
213 async fn send_chunk(&self, chunk: StreamChunk) -> Result<()> {
214 match self {
215 StreamingChannel2WayImpl::Memory(ch) => ch.send_chunk(chunk).await,
216 StreamingChannel2WayImpl::File(ch) => ch.send_chunk(chunk).await,
217 }
218 }
219
220 async fn send_bytes(&self, data: &[u8], is_final: bool) -> Result<()> {
221 match self {
222 StreamingChannel2WayImpl::Memory(ch) => ch.send_bytes(data, is_final).await,
223 StreamingChannel2WayImpl::File(ch) => ch.send_bytes(data, is_final).await,
224 }
225 }
226
227 async fn send_stream<S>(&self, stream: &mut S) -> Result<u64>
228 where
229 S: Stream<Item = Result<StreamChunk>> + Unpin + Send
230 {
231 match self {
232 StreamingChannel2WayImpl::Memory(ch) => ch.send_stream(stream).await,
233 StreamingChannel2WayImpl::File(ch) => ch.send_stream(stream).await,
234 }
235 }
236}
237
238#[async_trait]
239impl StreamingOutput for StreamingChannel2WayImpl {
240 async fn receive_chunk(&self) -> Result<StreamChunk> {
241 match self {
242 StreamingChannel2WayImpl::Memory(ch) => ch.receive_chunk().await,
243 StreamingChannel2WayImpl::File(ch) => ch.receive_chunk().await,
244 }
245 }
246
247 async fn receive_bytes(&self, max_size: Option<usize>) -> Result<Vec<u8>> {
248 match self {
249 StreamingChannel2WayImpl::Memory(ch) => ch.receive_bytes(max_size).await,
250 StreamingChannel2WayImpl::File(ch) => ch.receive_bytes(max_size).await,
251 }
252 }
253
254 fn receive_stream(&self) -> Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>> {
255 match self {
256 StreamingChannel2WayImpl::Memory(ch) => ch.receive_stream(),
257 StreamingChannel2WayImpl::File(ch) => ch.receive_stream(),
258 }
259 }
260}
261
262impl StreamingChannel2Way for StreamingChannel2WayImpl {}
263
264#[derive(Clone)]
266pub enum StreamingChannelType {
267 Memory(Arc<MemoryStreamingChannel>),
268 File(Arc<FileStreamingChannel>),
269}
270
271impl StreamingChannelType {
272 pub fn id(&self) -> &str {
273 match self {
274 StreamingChannelType::Memory(channel) => &channel.id,
275 StreamingChannelType::File(channel) => &channel.id,
276 }
277 }
278
279 pub async fn send_chunk(&self, chunk: StreamChunk) -> Result<()> {
280 match self {
281 StreamingChannelType::Memory(channel) => channel.send_chunk(chunk).await,
282 StreamingChannelType::File(channel) => channel.send_chunk(chunk).await,
283 }
284 }
285
286 pub async fn receive_chunk(&self) -> Result<StreamChunk> {
287 match self {
288 StreamingChannelType::Memory(channel) => channel.receive_chunk().await,
289 StreamingChannelType::File(channel) => channel.receive_chunk().await,
290 }
291 }
292
293 pub async fn close(&self) -> Result<()> {
294 match self {
295 StreamingChannelType::Memory(channel) => channel.close(),
296 StreamingChannelType::File(channel) => channel.close(),
297 }
298 }
299}
300
301#[derive(Clone)]
303pub struct MemoryStreamingChannel {
304 id: String,
306
307 config: StreamingChannelConfig,
309
310 state: Arc<tokio::sync::RwLock<MemoryStreamingState>>,
312}
313
314struct MemoryStreamingState {
316 is_open: bool,
318
319 h2g_queue: Vec<StreamChunk>,
321
322 g2h_queue: Vec<StreamChunk>,
324
325 stats: StreamingStats,
327}
328
329impl MemoryStreamingChannel {
330 pub fn new(id: impl Into<String>, config: StreamingChannelConfig) -> Self {
332 Self {
333 id: id.into(),
334 config,
335 state: Arc::new(tokio::sync::RwLock::new(MemoryStreamingState {
336 is_open: true,
337 h2g_queue: Vec::new(),
338 g2h_queue: Vec::new(),
339 stats: StreamingStats {
340 bytes_sent: 0,
341 bytes_received: 0,
342 chunks_sent: 0,
343 chunks_received: 0,
344 average_chunk_size: 0.0,
345 max_chunk_size_seen: 0,
346 error_count: 0,
347 },
348 })),
349 }
350 }
351}
352
353impl StreamingChannel for MemoryStreamingChannel {
354 fn id(&self) -> &str {
355 &self.id
356 }
357
358 fn config(&self) -> &StreamingChannelConfig {
359 &self.config
360 }
361
362 fn stats(&self) -> StreamingStats {
363 tokio::task::block_in_place(|| {
364 tokio::runtime::Handle::current().block_on(async {
365 self.state.read().await.stats.clone()
366 })
367 })
368 }
369
370 fn is_open(&self) -> bool {
371 tokio::task::block_in_place(|| {
372 tokio::runtime::Handle::current().block_on(async {
373 self.state.read().await.is_open
374 })
375 })
376 }
377
378 fn close(&self) -> Result<()> {
379 tokio::task::block_in_place(|| {
380 tokio::runtime::Handle::current().block_on(async {
381 let mut state = self.state.write().await;
382 state.is_open = false;
383 Ok(())
384 })
385 })
386 }
387}
388
389#[async_trait]
390impl StreamingInput for MemoryStreamingChannel {
391 async fn send_chunk(&self, chunk: StreamChunk) -> Result<()> {
392 let mut state = self.state.write().await;
393
394 if !state.is_open {
395 return Err(Error::Communication {
396 channel: "memory_streaming".to_string(),
397 reason: "Streaming channel is closed".to_string(),
398 instance_id: None,
399 });
400 }
401
402 if chunk.data.len() > self.config.max_chunk_size {
403 state.stats.error_count += 1;
404 return Err(Error::ResourceExhausted {
405 kind: ResourceKind::Memory,
406 limit: self.config.max_chunk_size as u64,
407 used: chunk.data.len() as u64,
408 instance_id: None,
409 suggestion: Some(format!("Consider reducing chunk size to {} bytes or less", self.config.max_chunk_size)),
410 });
411 }
412
413 state.stats.bytes_sent += chunk.data.len() as u64;
415 state.stats.chunks_sent += 1;
416
417 let new_avg = ((state.stats.average_chunk_size * (state.stats.chunks_sent - 1) as f64)
419 + chunk.data.len() as f64) / state.stats.chunks_sent as f64;
420 state.stats.average_chunk_size = new_avg;
421
422 state.stats.max_chunk_size_seen = state.stats.max_chunk_size_seen.max(chunk.data.len());
424
425 state.h2g_queue.push(chunk);
427
428 Ok(())
429 }
430
431 async fn send_bytes(&self, data: &[u8], is_final: bool) -> Result<()> {
432 let chunk = StreamChunk {
434 data: data.to_vec(),
435 is_final,
436 sequence: 0, metadata: None,
438 };
439
440 self.send_chunk(chunk).await
441 }
442
443 async fn send_stream<S>(&self, stream: &mut S) -> Result<u64>
444 where
445 S: Stream<Item = Result<StreamChunk>> + Unpin + Send,
446 {
447 let mut bytes_sent = 0;
448
449 while let Some(chunk_result) = stream.next().await {
450 let chunk = chunk_result?;
451 bytes_sent += chunk.data.len() as u64;
452 self.send_chunk(chunk).await?;
453 }
454
455 Ok(bytes_sent)
456 }
457}
458
459#[async_trait]
460impl StreamingOutput for MemoryStreamingChannel {
461 async fn receive_chunk(&self) -> Result<StreamChunk> {
462 let mut state = self.state.write().await;
463
464 if !state.is_open {
465 return Err(Error::Communication {
466 channel: "memory_streaming".to_string(),
467 reason: "Streaming channel is closed".to_string(),
468 instance_id: None,
469 });
470 }
471
472 if state.g2h_queue.is_empty() {
473 return Err(Error::NotFound {
474 resource_type: "stream_data".to_string(),
475 identifier: "No data available in streaming channel".to_string(),
476 });
477 }
478
479 let chunk = state.g2h_queue.remove(0);
481
482 state.stats.bytes_received += chunk.data.len() as u64;
484 state.stats.chunks_received += 1;
485
486 Ok(chunk)
487 }
488
489 async fn receive_bytes(&self, max_size: Option<usize>) -> Result<Vec<u8>> {
490 let chunk = self.receive_chunk().await?;
491
492 if let Some(max) = max_size {
494 if chunk.data.len() > max {
495 return Err(Error::ResourceExhausted {
496 kind: ResourceKind::Memory,
497 limit: max as u64,
498 used: chunk.data.len() as u64,
499 instance_id: None,
500 suggestion: Some(format!("Consider increasing max_size to {} bytes or more", chunk.data.len())),
501 });
502 }
503 }
504
505 Ok(chunk.data)
506 }
507
508 fn receive_stream(&self) -> Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>> {
509 let state = self.state.clone();
511
512 let stream = async_stream::stream! {
514 let mut last_seen_index = 0;
515
516 loop {
517 let state_guard = state.read().await;
519
520 if !state_guard.is_open {
522 break;
523 }
524
525 let new_chunks: Vec<StreamChunk> = state_guard.g2h_queue
527 .iter()
528 .skip(last_seen_index)
529 .cloned()
530 .collect();
531
532 last_seen_index = state_guard.g2h_queue.len();
534
535 drop(state_guard);
537
538 for chunk in new_chunks {
540 let is_final = chunk.is_final;
541 yield Ok(chunk);
542
543 if is_final {
545 return;
546 }
547 }
548
549 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
551 }
552
553 yield Err(Error::Communication {
555 channel: "memory_streaming".to_string(),
556 reason: "Streaming channel was closed".to_string(),
557 instance_id: None,
558 });
559 };
560
561 Box::pin(stream)
562 }
563}
564
565impl StreamingChannel2Way for MemoryStreamingChannel {}
566
567#[derive(Clone)]
569pub struct FileStreamingChannel {
570 id: String,
572
573 config: StreamingChannelConfig,
575
576 input_path: Option<std::path::PathBuf>,
578
579 output_path: Option<std::path::PathBuf>,
581
582 state: Arc<tokio::sync::RwLock<FileStreamingState>>,
584}
585
586struct FileStreamingState {
588 is_open: bool,
590
591 stats: StreamingStats,
593}
594
595impl FileStreamingChannel {
596 pub fn new(
598 id: impl Into<String>,
599 config: StreamingChannelConfig,
600 input_path: Option<impl AsRef<Path>>,
601 output_path: Option<impl AsRef<Path>>,
602 ) -> Self {
603 Self {
604 id: id.into(),
605 config,
606 input_path: input_path.map(|p| p.as_ref().to_path_buf()),
607 output_path: output_path.map(|p| p.as_ref().to_path_buf()),
608 state: Arc::new(tokio::sync::RwLock::new(FileStreamingState {
609 is_open: true,
610 stats: StreamingStats {
611 bytes_sent: 0,
612 bytes_received: 0,
613 chunks_sent: 0,
614 chunks_received: 0,
615 average_chunk_size: 0.0,
616 max_chunk_size_seen: 0,
617 error_count: 0,
618 },
619 })),
620 }
621 }
622}
623
624impl StreamingChannel for FileStreamingChannel {
625 fn id(&self) -> &str {
626 &self.id
627 }
628
629 fn config(&self) -> &StreamingChannelConfig {
630 &self.config
631 }
632
633 fn stats(&self) -> StreamingStats {
634 tokio::task::block_in_place(|| {
635 tokio::runtime::Handle::current().block_on(async {
636 self.state.read().await.stats.clone()
637 })
638 })
639 }
640
641 fn is_open(&self) -> bool {
642 tokio::task::block_in_place(|| {
643 tokio::runtime::Handle::current().block_on(async {
644 self.state.read().await.is_open
645 })
646 })
647 }
648
649 fn close(&self) -> Result<()> {
650 tokio::task::block_in_place(|| {
651 tokio::runtime::Handle::current().block_on(async {
652 let mut state = self.state.write().await;
653 state.is_open = false;
654 Ok(())
655 })
656 })
657 }
658}
659
660#[async_trait]
661impl StreamingInput for FileStreamingChannel {
662 async fn send_chunk(&self, chunk: StreamChunk) -> Result<()> {
663 let mut state = self.state.write().await;
664
665 if !state.is_open {
666 return Err(Error::Communication {
667 channel: "file_streaming".to_string(),
668 reason: "Streaming channel is closed".to_string(),
669 instance_id: None,
670 });
671 }
672
673 let output_path = match &self.output_path {
674 Some(path) => path,
675 None => return Err(Error::Configuration {
676 message: "No output file configured".to_string(),
677 suggestion: Some("Configure an output file path using with_output_file()".to_string()),
678 field: Some("output_path".to_string()),
679 }),
680 };
681
682 let mut file = tokio::fs::OpenOptions::new()
684 .write(true)
685 .create(true)
686 .append(true)
687 .open(output_path)
688 .await
689 .map_err(|e| Error::Filesystem {
690 operation: "open".to_string(),
691 path: output_path.clone(),
692 reason: format!("Failed to open output file: {}", e),
693 })?;
694
695 let size = chunk.data.len() as u32;
697 let size_bytes = size.to_le_bytes();
698
699 file.write_all(size_bytes.as_slice()).await
700 .map_err(|e| Error::Filesystem {
701 operation: "write".to_string(),
702 path: output_path.clone(),
703 reason: format!("Failed to write chunk size: {}", e),
704 })?;
705
706 file.write_all(&chunk.data).await
707 .map_err(|e| Error::Filesystem {
708 operation: "write".to_string(),
709 path: output_path.clone(),
710 reason: format!("Failed to write chunk data: {}", e),
711 })?;
712
713 let final_flag = if chunk.is_final { 1u8 } else { 0u8 };
715 file.write_all([final_flag].as_slice()).await
716 .map_err(|e| Error::Filesystem {
717 operation: "write".to_string(),
718 path: output_path.clone(),
719 reason: format!("Failed to write final flag: {}", e),
720 })?;
721
722 state.stats.bytes_sent += chunk.data.len() as u64;
724 state.stats.chunks_sent += 1;
725
726 let new_avg = ((state.stats.average_chunk_size * (state.stats.chunks_sent - 1) as f64)
728 + chunk.data.len() as f64) / state.stats.chunks_sent as f64;
729 state.stats.average_chunk_size = new_avg;
730
731 state.stats.max_chunk_size_seen = state.stats.max_chunk_size_seen.max(chunk.data.len());
733
734 Ok(())
735 }
736
737 async fn send_bytes(&self, data: &[u8], is_final: bool) -> Result<()> {
738 let chunk = StreamChunk {
740 data: data.to_vec(),
741 is_final,
742 sequence: 0,
743 metadata: None,
744 };
745
746 self.send_chunk(chunk).await
747 }
748
749 async fn send_stream<S>(&self, stream: &mut S) -> Result<u64>
750 where
751 S: Stream<Item = Result<StreamChunk>> + Unpin + Send,
752 {
753 let mut bytes_sent = 0;
754
755 while let Some(chunk_result) = stream.next().await {
756 let chunk = chunk_result?;
757 bytes_sent += chunk.data.len() as u64;
758 self.send_chunk(chunk).await?;
759 }
760
761 Ok(bytes_sent)
762 }
763}
764
765#[async_trait]
766impl StreamingOutput for FileStreamingChannel {
767 async fn receive_chunk(&self) -> Result<StreamChunk> {
768 let mut state = self.state.write().await;
769
770 if !state.is_open {
771 return Err(Error::Communication {
772 channel: "file_streaming".to_string(),
773 reason: "Streaming channel is closed".to_string(),
774 instance_id: None,
775 });
776 }
777
778 let input_path = match &self.input_path {
779 Some(path) => path,
780 None => return Err(Error::Configuration {
781 message: "No input file configured".to_string(),
782 suggestion: Some("Configure an input file path using with_input_file()".to_string()),
783 field: Some("input_path".to_string()),
784 }),
785 };
786
787 if !tokio::fs::try_exists(input_path).await.unwrap_or(false) {
789 return Err(Error::NotFound {
790 resource_type: "input_file".to_string(),
791 identifier: "Input file does not exist".to_string(),
792 });
793 }
794
795 let mut file = tokio::fs::File::open(input_path).await
797 .map_err(|e| Error::Filesystem {
798 operation: "open".to_string(),
799 path: input_path.clone(),
800 reason: format!("Failed to open input file: {}", e),
801 })?;
802
803 let metadata = file.metadata().await
805 .map_err(|e| Error::Filesystem {
806 operation: "metadata".to_string(),
807 path: input_path.clone(),
808 reason: format!("Failed to get file metadata: {}", e),
809 })?;
810
811 if metadata.len() == 0 {
813 return Err(Error::NotFound {
814 resource_type: "stream_data".to_string(),
815 identifier: "Input file is empty".to_string(),
816 });
817 }
818
819 let mut size_bytes = [0u8; 4];
821 file.read_exact(&mut size_bytes).await
822 .map_err(|e| Error::Filesystem {
823 operation: "read".to_string(),
824 path: input_path.clone(),
825 reason: format!("Failed to read chunk size: {}", e),
826 })?;
827
828 let size = u32::from_le_bytes(size_bytes) as usize;
829
830 let mut data = vec![0u8; size];
832 file.read_exact(&mut data).await
833 .map_err(|e| Error::Filesystem {
834 operation: "read".to_string(),
835 path: input_path.clone(),
836 reason: format!("Failed to read chunk data: {}", e),
837 })?;
838
839 let mut final_flag = [0u8; 1];
841 file.read_exact(&mut final_flag).await
842 .map_err(|e| Error::Filesystem {
843 operation: "read".to_string(),
844 path: input_path.clone(),
845 reason: format!("Failed to read final flag: {}", e),
846 })?;
847
848 let is_final = final_flag[0] != 0;
849
850 let chunk = StreamChunk {
852 data,
853 is_final,
854 sequence: state.stats.chunks_received,
855 metadata: None,
856 };
857
858 state.stats.bytes_received += chunk.data.len() as u64;
860 state.stats.chunks_received += 1;
861
862 Ok(chunk)
863 }
864
865 async fn receive_bytes(&self, max_size: Option<usize>) -> Result<Vec<u8>> {
866 let chunk = self.receive_chunk().await?;
867
868 if let Some(max) = max_size {
870 if chunk.data.len() > max {
871 return Err(Error::ResourceExhausted {
872 kind: ResourceKind::Memory,
873 limit: max as u64,
874 used: chunk.data.len() as u64,
875 instance_id: None,
876 suggestion: Some(format!("Consider increasing max_size to {} bytes or more", chunk.data.len())),
877 });
878 }
879 }
880
881 Ok(chunk.data)
882 }
883
884 fn receive_stream(&self) -> Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>> {
885 let state = self.state.clone();
887 let input_path = self.input_path.clone();
888
889 let stream = async_stream::stream! {
891 let input_path = match &input_path {
893 Some(path) => path.clone(),
894 None => {
895 yield Err(Error::Configuration {
896 message: "No input file configured".to_string(),
897 suggestion: Some("Configure an input file path using with_input_file()".to_string()),
898 field: Some("input_path".to_string()),
899 });
900 return;
901 }
902 };
903
904 let file = match tokio::fs::File::open(&input_path).await {
906 Ok(file) => file,
907 Err(e) => {
908 yield Err(Error::Filesystem {
909 operation: "open".to_string(),
910 path: input_path.clone(),
911 reason: format!("Failed to open input file: {}", e),
912 });
913 return;
914 }
915 };
916
917 let mut reader = tokio::io::BufReader::new(file);
919 let mut sequence = 0;
920
921 loop {
922 {
924 let state_guard = state.read().await;
925 if !state_guard.is_open {
926 yield Err(Error::Communication {
927 channel: "file_streaming".to_string(),
928 reason: "Streaming channel was closed".to_string(),
929 instance_id: None,
930 });
931 return;
932 }
933 }
934
935 let mut size_bytes = [0u8; 4];
937 match reader.read_exact(&mut size_bytes).await {
938 Ok(_) => {}
939 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
940 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
942 continue;
943 }
944 Err(e) => {
945 yield Err(Error::Filesystem {
946 operation: "read".to_string(),
947 path: input_path.clone(),
948 reason: format!("Failed to read chunk size: {}", e),
949 });
950 return;
951 }
952 }
953
954 let size = u32::from_le_bytes(size_bytes) as usize;
955
956 let mut data = vec![0u8; size];
958 if let Err(e) = reader.read_exact(&mut data).await {
959 yield Err(Error::Filesystem {
960 operation: "read".to_string(),
961 path: input_path.clone(),
962 reason: format!("Failed to read chunk data: {}", e),
963 });
964 return;
965 }
966
967 let mut final_flag = [0u8; 1];
969 if let Err(e) = reader.read_exact(&mut final_flag).await {
970 yield Err(Error::Filesystem {
971 operation: "read".to_string(),
972 path: input_path.clone(),
973 reason: format!("Failed to read final flag: {}", e),
974 });
975 return;
976 }
977
978 let is_final = final_flag[0] != 0;
979
980 let chunk = StreamChunk {
982 data,
983 is_final,
984 sequence,
985 metadata: None,
986 };
987
988 {
990 let mut state_guard = state.write().await;
991 state_guard.stats.bytes_received += chunk.data.len() as u64;
992 state_guard.stats.chunks_received += 1;
993 }
994
995 yield Ok(chunk);
997 sequence += 1;
998
999 if is_final {
1001 break;
1002 }
1003 }
1004 };
1005
1006 Box::pin(stream)
1007 }
1008}
1009
1010impl StreamingChannel2Way for FileStreamingChannel {}
1011
1012pub struct StreamingFactory;
1014
1015impl StreamingFactory {
1016 pub fn create_memory_channel(
1018 id: impl Into<String>,
1019 config: Option<StreamingChannelConfig>,
1020 ) -> StreamingChannelType {
1021 let config = config.unwrap_or_default();
1022 StreamingChannelType::Memory(Arc::new(MemoryStreamingChannel::new(id, config)))
1023 }
1024
1025 pub fn create_file_channel(
1027 id: impl Into<String>,
1028 config: Option<StreamingChannelConfig>,
1029 input_path: Option<impl AsRef<Path>>,
1030 output_path: Option<impl AsRef<Path>>,
1031 ) -> StreamingChannelType {
1032 let config = config.unwrap_or_default();
1033 StreamingChannelType::File(Arc::new(FileStreamingChannel::new(id, config, input_path, output_path)))
1034 }
1035}
1036
1037pub struct StreamingFunction<T, R> {
1039 processor: Box<dyn Fn(T, Arc<StreamingChannel2WayImpl>) -> Result<R> + Send + Sync>,
1041
1042 input_stream: Arc<StreamingChannel2WayImpl>,
1044}
1045
1046impl<T, R> StreamingFunction<T, R>
1047where
1048 T: serde::de::DeserializeOwned + Send + 'static,
1049 R: serde::Serialize + Send + 'static,
1050{
1051 pub fn new<F>(processor: F, input_stream: Arc<StreamingChannel2WayImpl>) -> Self
1053 where
1054 F: Fn(T, Arc<StreamingChannel2WayImpl>) -> Result<R> + Send + Sync + 'static,
1055 {
1056 Self {
1057 processor: Box::new(processor),
1058 input_stream,
1059 }
1060 }
1061
1062 pub fn process(&self, request: T) -> Result<R> {
1064 (self.processor)(request, self.input_stream.clone())
1065 }
1066}
1067
1068#[async_trait]
1070pub trait StreamingInstanceExt {
1071 async fn call_streaming_function<Params, Return>(
1073 &self,
1074 function_name: &str,
1075 params: &Params,
1076 stream: Arc<StreamingChannel2WayImpl>,
1077 ) -> Result<Return>
1078 where
1079 Params: serde::Serialize + ?Sized + Send + Sync,
1080 Return: serde::de::DeserializeOwned + 'static;
1081}
1082
1083pub struct StreamingManager {
1085 channels: tokio::sync::RwLock<std::collections::HashMap<String, StreamingChannelType>>,
1087}
1088
1089impl StreamingManager {
1090 pub fn new() -> Self {
1092 Self {
1093 channels: tokio::sync::RwLock::new(std::collections::HashMap::new()),
1094 }
1095 }
1096
1097 pub async fn create_channel(
1099 &self,
1100 id: Option<String>,
1101 config: Option<StreamingChannelConfig>,
1102 ) -> Result<StreamingChannelType> {
1103 let id = id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1104 let channel = StreamingFactory::create_memory_channel(id.clone(), config);
1105
1106 let mut channels = self.channels.write().await;
1108 channels.insert(id, channel.clone());
1109
1110 Ok(channel)
1111 }
1112
1113 pub async fn get_channel(&self, id: &str) -> Option<StreamingChannelType> {
1115 let channels = self.channels.read().await;
1116 channels.get(id).cloned()
1117 }
1118
1119 pub async fn close_channel(&self, id: &str) -> Result<()> {
1121 let mut channels = self.channels.write().await;
1122
1123 if let Some(channel) = channels.remove(id) {
1124 channel.close().await?;
1125 Ok(())
1126 } else {
1127 Err(SandboxError::NotFound {
1128 resource_type: "streaming_channel".to_string(),
1129 identifier: id.to_string()
1130 })
1131 }
1132 }
1133
1134 pub async fn list_channels(&self) -> Vec<String> {
1136 let channels = self.channels.read().await;
1137 channels.keys().cloned().collect()
1138 }
1139}
1140
1141impl Default for StreamingManager {
1142 fn default() -> Self {
1143 Self::new()
1144 }
1145}
1146
1147pub struct StreamTransformer;
1149
1150impl StreamTransformer {
1151 pub fn bytes_to_text_stream<S>(
1153 stream: S,
1154 ) -> impl Stream<Item = Result<String>>
1155 where
1156 S: Stream<Item = Result<StreamChunk>> + Send,
1157 {
1158 stream.map(|chunk_result| {
1159 let chunk = chunk_result?;
1160 let text = String::from_utf8(chunk.data)
1161 .map_err(|e| SandboxError::Serialization {
1162 format: "utf-8".to_string(),
1163 operation: "decode".to_string(),
1164 reason: e.to_string(),
1165 })?;
1166 Ok(text)
1167 })
1168 }
1169
1170 pub fn bytes_to_json_stream<S, T>(
1172 stream: S,
1173 ) -> impl Stream<Item = Result<T>>
1174 where
1175 S: Stream<Item = Result<StreamChunk>> + Send,
1176 T: serde::de::DeserializeOwned,
1177 {
1178 stream.map(|chunk_result| {
1179 let chunk = chunk_result?;
1180 let value = serde_json::from_slice(&chunk.data)
1181 .map_err(|e| SandboxError::Serialization {
1182 format: "json".to_string(),
1183 operation: "decode".to_string(),
1184 reason: e.to_string(),
1185 })?;
1186 Ok(value)
1187 })
1188 }
1189
1190 pub fn objects_to_json_byte_stream<S, T>(
1192 stream: S,
1193 ) -> impl Stream<Item = Result<StreamChunk>>
1194 where
1195 S: Stream<Item = Result<T>> + Send,
1196 T: serde::Serialize,
1197 {
1198 stream.map(|item_result| {
1199 let item = item_result?;
1200 let json = serde_json::to_vec(&item)
1201 .map_err(|e| SandboxError::Serialization {
1202 format: "json".to_string(),
1203 operation: "encode".to_string(),
1204 reason: e.to_string(),
1205 })?;
1206
1207 Ok(StreamChunk {
1208 data: json,
1209 is_final: false, sequence: 0, metadata: None,
1212 })
1213 })
1214 }
1215}