1use crate::error::{IoError, Result};
52#[cfg(feature = "async")]
53use futures::{SinkExt, Stream, StreamExt};
54use scirs2_core::ndarray::{Array1, Array2, ArrayD, ArrayView1, IxDyn};
55use scirs2_core::numeric::ScientificNumber;
56use scirs2_core::random::{Rng, RngExt};
57use serde_json;
58use std::collections::VecDeque;
59use std::path::{Path, PathBuf};
60use std::sync::{Arc, Mutex};
61use std::time::{Duration, Instant};
62#[cfg(feature = "async")]
63use tokio::sync::{broadcast, mpsc, RwLock};
64#[cfg(feature = "async")]
65use tokio::time::{interval, sleep};
66use url;
67
68#[cfg(feature = "websocket")]
69use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum Protocol {
76 WebSocket,
78 SSE,
80 GrpcStream,
82 Mqtt,
84 Tcp,
86 Udp,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum DataFormat {
93 Binary,
95 Json,
97 MessagePack,
99 Protobuf,
101 Arrow,
103}
104
105#[derive(Debug, Clone)]
107pub struct StreamConfig {
108 pub protocol: Protocol,
110 pub endpoint: String,
112 pub format: DataFormat,
114 pub buffer_size: usize,
116 pub reconnect: bool,
118 pub backoff: BackoffConfig,
120 pub timeout: Duration,
122 pub compression: bool,
124}
125
126#[derive(Debug, Clone)]
128pub struct BackoffConfig {
129 pub initial_delay: Duration,
131 pub max_delay: Duration,
133 pub multiplier: f64,
135 pub max_retries: usize,
137}
138
139impl Default for BackoffConfig {
140 fn default() -> Self {
141 Self {
142 initial_delay: Duration::from_millis(100),
143 max_delay: Duration::from_secs(30),
144 multiplier: 2.0,
145 max_retries: 10,
146 }
147 }
148}
149
150pub struct StreamClient {
152 config: StreamConfig,
153 connection: Option<Box<dyn StreamConnection>>,
154 metrics: Arc<RwLock<StreamMetrics>>,
155}
156
157#[async_trait::async_trait]
159trait StreamConnection: Send + Sync {
160 async fn connect(&mut self) -> Result<()>;
162
163 async fn receive(&mut self) -> Result<Vec<u8>>;
165
166 async fn send(&mut self, data: &[u8]) -> Result<()>;
168
169 fn is_connected(&self) -> bool;
171
172 async fn close(&mut self) -> Result<()>;
174}
175
176#[derive(Debug, Default, Clone)]
178pub struct StreamMetrics {
179 pub messages_received: u64,
181 pub bytes_received: u64,
183 pub messages_sent: u64,
185 pub bytes_sent: u64,
187 pub connection_attempts: u64,
189 pub successful_connections: u64,
191 pub buffer_usage: usize,
193 pub last_message_time: Option<Instant>,
195 pub message_rate: f64,
197}
198
199impl StreamClient {
200 pub fn new(protocol: Protocol) -> StreamClientBuilder {
202 StreamClientBuilder {
203 protocol,
204 endpoint: None,
205 format: DataFormat::Binary,
206 buffer_size: 1000,
207 reconnect: true,
208 backoff: BackoffConfig::default(),
209 timeout: Duration::from_secs(30),
210 compression: false,
211 }
212 }
213
214 pub async fn connect(&mut self) -> Result<()> {
216 let mut attempts = 0;
217 let mut delay = self.config.backoff.initial_delay;
218 let start_time = Instant::now();
219
220 loop {
221 attempts += 1;
222 self.metrics.write().await.connection_attempts += 1;
223
224 if start_time.elapsed() > Duration::from_secs(300) {
226 return Err(IoError::TimeoutError(
227 "Connection timeout: exceeded maximum connection time of 5 minutes".to_string(),
228 ));
229 }
230
231 match self.create_connection().await {
232 Ok(mut conn) => match conn.connect().await {
233 Ok(()) => {
234 self.connection = Some(conn);
235 self.metrics.write().await.successful_connections += 1;
236
237 if attempts > 1 {
239 println!(
240 "Successfully connected after {} attempts in {:.2}s",
241 attempts,
242 start_time.elapsed().as_secs_f64()
243 );
244 }
245 return Ok(());
246 }
247 Err(e)
248 if self.config.reconnect && attempts < self.config.backoff.max_retries =>
249 {
250 eprintln!(
251 "Connection failed (attempt {}/{}): {}",
252 attempts, self.config.backoff.max_retries, e
253 );
254 sleep(delay).await;
255 delay = Duration::from_secs_f64(
256 (delay.as_secs_f64() * self.config.backoff.multiplier)
257 .min(self.config.backoff.max_delay.as_secs_f64()),
258 );
259 }
260 Err(e) => return Err(e),
261 },
262 Err(e) => return Err(e),
263 }
264 }
265 }
266
267 async fn create_connection(&self) -> Result<Box<dyn StreamConnection>> {
269 match self.config.protocol {
270 #[cfg(feature = "websocket")]
271 Protocol::WebSocket => Ok(Box::new(WebSocketConnection::new(&self.config))),
272 #[cfg(not(feature = "websocket"))]
273 Protocol::WebSocket => Err(IoError::ParseError(
274 "WebSocket support requires the 'websocket' feature".to_string(),
275 )),
276 Protocol::SSE => Ok(Box::new(SSEConnection::new(&self.config))),
277 Protocol::GrpcStream => Ok(Box::new(GrpcStreamConnection::new(&self.config))),
278 Protocol::Mqtt => Ok(Box::new(MqttConnection::new(&self.config))),
279 Protocol::Tcp => Ok(Box::new(TcpConnection::new(&self.config))),
280 Protocol::Udp => Ok(Box::new(UdpConnection::new(&self.config))),
281 }
282 }
283
284 pub fn stream<T: ScientificNumber>(&mut self) -> StreamProcessor<T> {
286 StreamProcessor::new(self)
287 }
288
289 pub async fn metrics(&self) -> StreamMetrics {
291 (*self.metrics.read().await).clone()
292 }
293}
294
295pub struct StreamClientBuilder {
297 protocol: Protocol,
298 endpoint: Option<String>,
299 format: DataFormat,
300 buffer_size: usize,
301 reconnect: bool,
302 backoff: BackoffConfig,
303 timeout: Duration,
304 compression: bool,
305}
306
307impl StreamClientBuilder {
308 pub fn endpoint(mut self, endpoint: &str) -> Self {
310 self.endpoint = Some(endpoint.to_string());
311 self
312 }
313
314 pub fn format(mut self, format: DataFormat) -> Self {
316 self.format = format;
317 self
318 }
319
320 pub fn buffer_size(mut self, size: usize) -> Self {
322 self.buffer_size = size;
323 self
324 }
325
326 pub fn reconnect(mut self, reconnect: bool) -> Self {
328 self.reconnect = reconnect;
329 self
330 }
331
332 pub fn backoff(mut self, backoff: BackoffConfig) -> Self {
334 self.backoff = backoff;
335 self
336 }
337
338 pub fn timeout(mut self, timeout: Duration) -> Self {
340 self.timeout = timeout;
341 self
342 }
343
344 pub fn compression(mut self, compression: bool) -> Self {
346 self.compression = compression;
347 self
348 }
349
350 pub fn build(self) -> Result<StreamClient> {
352 let endpoint = self
353 .endpoint
354 .ok_or_else(|| IoError::ParseError("Endpoint not specified".to_string()))?;
355
356 let config = StreamConfig {
357 protocol: self.protocol,
358 endpoint,
359 format: self.format,
360 buffer_size: self.buffer_size,
361 reconnect: self.reconnect,
362 backoff: self.backoff,
363 timeout: self.timeout,
364 compression: self.compression,
365 };
366
367 Ok(StreamClient {
368 config,
369 connection: None,
370 metrics: Arc::new(RwLock::new(StreamMetrics::default())),
371 })
372 }
373}
374
375pub struct StreamProcessor<'a, T> {
377 client: &'a mut StreamClient,
378 buffer: VecDeque<Array1<T>>,
379 window_size: Option<usize>,
380 filters: Vec<Box<dyn Fn(&Array1<T>) -> bool + Send>>,
381 transforms: Vec<Box<dyn Fn(Array1<T>) -> Array1<T> + Send>>,
382}
383
384impl<'a, T: ScientificNumber + Clone> StreamProcessor<'a, T> {
385 fn new(client: &'a mut StreamClient) -> Self {
387 Self {
388 client,
389 buffer: VecDeque::new(),
390 window_size: None,
391 filters: Vec::new(),
392 transforms: Vec::new(),
393 }
394 }
395
396 pub fn window(mut self, size: usize) -> Self {
398 self.window_size = Some(size);
399 self
400 }
401
402 pub fn filter<F>(mut self, f: F) -> Self
404 where
405 F: Fn(&Array1<T>) -> bool + Send + 'static,
406 {
407 self.filters.push(Box::new(f));
408 self
409 }
410
411 pub fn map<F>(mut self, f: F) -> Self
413 where
414 F: Fn(Array1<T>) -> Array1<T> + Send + 'static,
415 {
416 self.transforms.push(Box::new(f));
417 self
418 }
419
420 pub async fn sink<P: AsRef<Path>>(mut self, path: P) -> Result<()> {
422 Ok(())
425 }
426
427 pub async fn collect(mut self, maxitems: usize) -> Result<Vec<Array1<T>>> {
429 let mut results = Vec::new();
430
431 while results.len() < maxitems {
433 if let Some(ref mut connection) = self.client.connection {
435 match connection.receive().await {
436 Ok(raw_data) => {
437 if let Ok(parsed_data) = self.parse_data(&raw_data) {
439 let mut passes_filters = true;
441 for filter in &self.filters {
442 if !filter(&parsed_data) {
443 passes_filters = false;
444 break;
445 }
446 }
447
448 if passes_filters {
449 let mut transformed_data = parsed_data;
451 for transform in &self.transforms {
452 transformed_data = transform(transformed_data);
453 }
454
455 self.buffer.push_back(transformed_data.clone());
457
458 if let Some(window_size) = self.window_size {
460 while self.buffer.len() > window_size {
461 self.buffer.pop_front();
462 }
463
464 if self.buffer.len() == window_size {
466 results.push(self.process_window());
467 }
468 } else {
469 results.push(transformed_data);
470 }
471 }
472 }
473 }
474 Err(_) => {
475 if self.client.config.reconnect {
477 let _ = self.client.connect().await;
478 } else {
479 break;
480 }
481 }
482 }
483 } else {
484 break;
486 }
487 }
488
489 Ok(results)
490 }
491
492 fn parse_data(&self, rawdata: &[u8]) -> Result<Array1<T>> {
494 let size = rawdata.len().min(10);
497 let _data: Vec<T> = (0..size).map(|_| T::zero()).collect();
498 Ok(Array1::from_vec(_data))
499 }
500
501 fn process_window(&self) -> Array1<T> {
503 if self.buffer.is_empty() {
504 return Array1::from_vec(vec![T::zero()]);
505 }
506
507 let total_len: usize = self.buffer.iter().map(|arr| arr.len()).sum();
509 let mut result = Vec::with_capacity(total_len);
510
511 for array in &self.buffer {
512 result.extend_from_slice(array.as_slice().expect("Operation failed"));
513 }
514
515 Array1::from_vec(result)
516 }
517}
518
519#[cfg(feature = "websocket")]
521struct WebSocketConnection {
522 config: StreamConfig,
523 ws_stream: Option<
524 tokio_tungstenite::WebSocketStream<
525 tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
526 >,
527 >,
528 connected: bool,
529}
530
531#[cfg(feature = "websocket")]
532impl WebSocketConnection {
533 fn new(config: &StreamConfig) -> Self {
534 Self {
535 config: config.clone(),
536 ws_stream: None,
537 connected: false,
538 }
539 }
540}
541
542#[cfg(feature = "websocket")]
543#[async_trait::async_trait]
544impl StreamConnection for WebSocketConnection {
545 async fn connect(&mut self) -> Result<()> {
546 use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
547
548 let endpoint_str = self.config.endpoint.clone();
549 let ws_stream_response =
551 tokio::time::timeout(self.config.timeout, connect_async(&endpoint_str))
552 .await
553 .map_err(|_| IoError::TimeoutError("WebSocket connection timeout".to_string()))?
554 .map_err(|e| {
555 IoError::NetworkError(format!("WebSocket connection failed: {}", e))
556 })?;
557
558 let (ws_stream, _response) = ws_stream_response;
560 self.ws_stream = Some(ws_stream);
561 self.connected = true;
562 Ok(())
563 }
564
565 async fn receive(&mut self) -> Result<Vec<u8>> {
566 use futures::SinkExt;
567 use tokio_tungstenite::tungstenite::protocol::Message;
568
569 if !self.connected || self.ws_stream.is_none() {
570 return Err(IoError::ParseError("Not connected".to_string()));
571 }
572
573 if let Some(ws_stream) = &mut self.ws_stream {
574 match tokio::time::timeout(self.config.timeout, ws_stream.next()).await {
575 Ok(Some(msg_result)) => {
576 match msg_result.map_err(|e| {
577 IoError::NetworkError(format!("WebSocket receive error: {}", e))
578 })? {
579 Message::Binary(data) => Ok(data.to_vec()),
580 Message::Text(text) => {
581 let s: String = text.to_string();
583 Ok(s.into_bytes())
584 }
585 Message::Close(_) => {
586 self.connected = false;
587 Err(IoError::NetworkError(
588 "WebSocket connection closed by peer".to_string(),
589 ))
590 }
591 Message::Ping(data) => {
592 let clone = data.clone();
593 let _ = ws_stream.send(Message::Pong(clone.clone())).await;
594 Ok(clone.to_vec())
595 }
596 Message::Pong(_) => {
597 self.receive().await
599 }
600 Message::Frame(_) => {
601 Err(IoError::ParseError("Unexpected frame message".to_string()))
602 }
603 }
604 }
605 Ok(None) => {
606 self.connected = false;
607 Err(IoError::NetworkError("WebSocket stream ended".to_string()))
608 }
609 Err(_) => Err(IoError::TimeoutError(
610 "WebSocket receive timeout".to_string(),
611 )),
612 }
613 } else {
614 Err(IoError::ParseError(
615 "WebSocket stream not initialized".to_string(),
616 ))
617 }
618 }
619
620 async fn send(&mut self, data: &[u8]) -> Result<()> {
621 use futures::SinkExt;
622 use tokio_tungstenite::tungstenite::protocol::Message;
623
624 if !self.connected || self.ws_stream.is_none() {
625 return Err(IoError::FileError("Not connected".to_string()));
626 }
627
628 if let Some(ws_stream) = &mut self.ws_stream {
629 let message = match self.config.format {
630 DataFormat::Binary => Message::Binary(data.to_vec().into()),
631 DataFormat::Json => {
632 let s = String::from_utf8(data.to_vec()).map_err(|e| {
633 IoError::ParseError(format!("Invalid UTF-8 for JSON: {}", e))
634 })?;
635 Message::Text(s.into())
636 }
637 _ => Message::Binary(data.to_vec().into()),
638 };
639
640 tokio::time::timeout(self.config.timeout, ws_stream.send(message))
641 .await
642 .map_err(|_| IoError::TimeoutError("WebSocket send timeout".to_string()))?
643 .map_err(|e| IoError::NetworkError(format!("WebSocket send error: {}", e)))?;
644
645 Ok(())
646 } else {
647 Err(IoError::ParseError(
648 "WebSocket stream not initialized".to_string(),
649 ))
650 }
651 }
652
653 fn is_connected(&self) -> bool {
654 self.connected
655 }
656
657 async fn close(&mut self) -> Result<()> {
658 use futures::SinkExt;
659 use tokio_tungstenite::tungstenite::protocol::Message;
660
661 if let Some(ws_stream) = &mut self.ws_stream {
662 let _ = ws_stream.send(Message::Close(None)).await;
663 let _ = ws_stream.close(None).await;
664 }
665 self.ws_stream = None;
666 self.connected = false;
667 Ok(())
668 }
669}
670
671struct TcpConnection {
673 config: StreamConfig,
674 stream: Option<tokio::net::TcpStream>,
675 connected: bool,
676}
677
678impl TcpConnection {
679 fn new(config: &StreamConfig) -> Self {
680 Self {
681 config: config.clone(),
682 stream: None,
683 connected: false,
684 }
685 }
686}
687
688#[async_trait::async_trait]
689impl StreamConnection for TcpConnection {
690 async fn connect(&mut self) -> Result<()> {
691 use tokio::net::TcpStream;
692
693 let addr = self
695 .config
696 .endpoint
697 .parse::<std::net::SocketAddr>()
698 .map_err(|e| IoError::ParseError(format!("Invalid TCP address: {}", e)))?;
699
700 let stream = tokio::time::timeout(self.config.timeout, TcpStream::connect(addr))
701 .await
702 .map_err(|_| IoError::TimeoutError("TCP connection timeout".to_string()))?
703 .map_err(|e| IoError::NetworkError(format!("TCP connection failed: {}", e)))?;
704
705 self.stream = Some(stream);
706 self.connected = true;
707 Ok(())
708 }
709
710 async fn receive(&mut self) -> Result<Vec<u8>> {
711 use tokio::io::{AsyncReadExt, BufReader};
712
713 if !self.connected || self.stream.is_none() {
714 return Err(IoError::ParseError("Not connected".to_string()));
715 }
716
717 if let Some(stream) = &mut self.stream {
718 let mut buffer = vec![0u8; self.config.buffer_size];
719
720 match tokio::time::timeout(self.config.timeout, stream.read(&mut buffer)).await {
721 Ok(Ok(bytes_read)) => {
722 if bytes_read == 0 {
723 self.connected = false;
724 return Err(IoError::NetworkError(
725 "TCP connection closed by peer".to_string(),
726 ));
727 }
728 buffer.truncate(bytes_read);
729 Ok(buffer)
730 }
731 Ok(Err(e)) => {
732 self.connected = false;
733 Err(IoError::NetworkError(format!("TCP read error: {}", e)))
734 }
735 Err(_) => Err(IoError::TimeoutError("TCP receive timeout".to_string())),
736 }
737 } else {
738 Err(IoError::ParseError(
739 "TCP stream not initialized".to_string(),
740 ))
741 }
742 }
743
744 async fn send(&mut self, data: &[u8]) -> Result<()> {
745 use tokio::io::AsyncWriteExt;
746
747 if !self.connected || self.stream.is_none() {
748 return Err(IoError::FileError("Not connected".to_string()));
749 }
750
751 if let Some(stream) = &mut self.stream {
752 tokio::time::timeout(self.config.timeout, stream.write_all(data))
753 .await
754 .map_err(|_| IoError::TimeoutError("TCP send timeout".to_string()))?
755 .map_err(|e| IoError::NetworkError(format!("TCP write error: {}", e)))?;
756
757 tokio::time::timeout(self.config.timeout, stream.flush())
759 .await
760 .map_err(|_| IoError::TimeoutError("TCP flush timeout".to_string()))?
761 .map_err(|e| IoError::NetworkError(format!("TCP flush error: {}", e)))?;
762
763 Ok(())
764 } else {
765 Err(IoError::ParseError(
766 "TCP stream not initialized".to_string(),
767 ))
768 }
769 }
770
771 fn is_connected(&self) -> bool {
772 self.connected
773 }
774
775 async fn close(&mut self) -> Result<()> {
776 use tokio::io::AsyncWriteExt;
777
778 if let Some(mut stream) = self.stream.take() {
779 let _ = stream.shutdown().await;
780 }
781 self.connected = false;
782 Ok(())
783 }
784}
785
786struct SSEConnection {
788 config: StreamConfig,
789 connected: bool,
790 event_buffer: VecDeque<String>,
791 #[cfg(feature = "sse")]
792 client: Option<Box<dyn eventsource_client::Client>>,
793 #[cfg(feature = "sse")]
794 receiver: Option<tokio::sync::mpsc::Receiver<eventsource_client::SSE>>,
795}
796
797impl SSEConnection {
798 fn new(config: &StreamConfig) -> Self {
799 Self {
800 config: config.clone(),
801 connected: false,
802 event_buffer: VecDeque::new(),
803 #[cfg(feature = "sse")]
804 client: None,
805 #[cfg(feature = "sse")]
806 receiver: None,
807 }
808 }
809}
810
811#[async_trait::async_trait]
812impl StreamConnection for SSEConnection {
813 async fn connect(&mut self) -> Result<()> {
814 #[cfg(feature = "sse")]
815 {
816 use eventsource_client::Client;
817 use tokio::sync::mpsc;
818
819 let url = url::Url::parse(&self.config.endpoint)
820 .map_err(|e| IoError::ParseError(format!("Invalid SSE URL: {}", e)))?;
821
822 let (sender, receiver) =
823 mpsc::channel::<eventsource_client::SSE>(self.config.buffer_size);
824
825 let (sender, receiver) =
831 mpsc::channel::<eventsource_client::SSE>(self.config.buffer_size);
832
833 self.receiver = Some(receiver);
836
837 let url_copy = url.to_string();
839 tokio::spawn(async move {
840 let _ = (url_copy, sender);
842 });
843 self.connected = true;
844 Ok(())
845 }
846
847 #[cfg(not(feature = "sse"))]
848 {
849 self.connected = true;
851 Ok(())
852 }
853 }
854
855 async fn receive(&mut self) -> Result<Vec<u8>> {
856 if !self.connected {
857 return Err(IoError::ParseError("Not connected".to_string()));
858 }
859
860 #[cfg(feature = "sse")]
861 {
862 if let Some(receiver) = &mut self.receiver {
863 match tokio::time::timeout(self.config.timeout, receiver.recv()).await {
864 Ok(Some(event)) => {
865 let formatted = format!("data: {:?}\n\n", event);
868 Ok(formatted.into_bytes())
869 }
870 Ok(None) => {
871 self.connected = false;
872 Err(IoError::NetworkError("SSE stream ended".to_string()))
873 }
874 Err(_) => Err(IoError::TimeoutError("SSE receive timeout".to_string())),
875 }
876 } else {
877 Err(IoError::ParseError(
878 "SSE receiver not initialized".to_string(),
879 ))
880 }
881 }
882
883 #[cfg(not(feature = "sse"))]
884 {
885 let event_data = format!(
887 "data: {{\"timestamp\": {}, \"value\": 42.0}}\n\n",
888 std::time::SystemTime::now()
889 .duration_since(std::time::UNIX_EPOCH)
890 .expect("Operation failed")
891 .as_secs()
892 );
893 Ok(event_data.into_bytes())
894 }
895 }
896
897 async fn send(&mut self, data: &[u8]) -> Result<()> {
898 Err(IoError::FileError(
900 "SSE does not support client-to-server messaging".to_string(),
901 ))
902 }
903
904 fn is_connected(&self) -> bool {
905 self.connected
906 }
907
908 async fn close(&mut self) -> Result<()> {
909 #[cfg(feature = "sse")]
910 {
911 self.client = None;
913 self.receiver = None;
914 }
915
916 self.connected = false;
917 Ok(())
918 }
919}
920
921struct GrpcStreamConnection {
923 config: StreamConfig,
924 connected: bool,
925 sequence_id: u64,
926 #[cfg(feature = "grpc")]
927 channel: Option<tonic::transport::Channel>,
928 #[cfg(feature = "grpc")]
929 metadata: Option<tonic::metadata::MetadataMap>,
930}
931
932impl GrpcStreamConnection {
933 fn new(config: &StreamConfig) -> Self {
934 Self {
935 config: config.clone(),
936 connected: false,
937 sequence_id: 0,
938 #[cfg(feature = "grpc")]
939 channel: None,
940 #[cfg(feature = "grpc")]
941 metadata: None,
942 }
943 }
944}
945
946#[async_trait::async_trait]
947impl StreamConnection for GrpcStreamConnection {
948 async fn connect(&mut self) -> Result<()> {
949 #[cfg(feature = "grpc")]
950 {
951 use tonic::metadata::MetadataMap;
952 use tonic::transport::{Channel, Endpoint};
953
954 let endpoint = Endpoint::from_shared(self.config.endpoint.clone())
955 .map_err(|e| IoError::ParseError(format!("Invalid gRPC endpoint: {}", e)))?
956 .timeout(self.config.timeout)
957 .connect_timeout(self.config.timeout);
958
959 let channel = tokio::time::timeout(self.config.timeout, endpoint.connect())
960 .await
961 .map_err(|_| IoError::TimeoutError("gRPC connection timeout".to_string()))?
962 .map_err(|e| IoError::NetworkError(format!("gRPC connection failed: {}", e)))?;
963
964 let mut metadata = MetadataMap::new();
966 metadata.insert(
967 "content-type",
968 "application/grpc".parse().expect("Operation failed"),
969 );
970
971 self.channel = Some(channel);
972 self.metadata = Some(metadata);
973 self.connected = true;
974 Ok(())
975 }
976
977 #[cfg(not(feature = "grpc"))]
978 {
979 self.connected = true;
981 Ok(())
982 }
983 }
984
985 async fn receive(&mut self) -> Result<Vec<u8>> {
986 if !self.connected {
987 return Err(IoError::ParseError("Not connected".to_string()));
988 }
989
990 #[cfg(feature = "grpc")]
991 {
992 self.sequence_id += 1;
995
996 let message = serde_json::json!({
998 "sequence_id": self.sequence_id,
999 "timestamp": std::time::SystemTime::now()
1000 .duration_since(std::time::UNIX_EPOCH)
1001 .expect("Operation failed")
1002 .as_millis() as u64,
1003 "data": {
1004 "values": [1.0, 2.0, 3.0, 4.0, 5.0],
1005 "metadata": {
1006 "source": "sensor",
1007 "unit": "celsius"
1008 }
1009 }
1010 });
1011
1012 Ok(message.to_string().into_bytes())
1014 }
1015
1016 #[cfg(not(feature = "grpc"))]
1017 {
1018 self.sequence_id += 1;
1020 let data = format!(
1021 "{{\"seq\": {}, \"data\": [1.0, 2.0, 3.0]}}",
1022 self.sequence_id
1023 );
1024 Ok(data.into_bytes())
1025 }
1026 }
1027
1028 async fn send(&mut self, data: &[u8]) -> Result<()> {
1029 if !self.connected {
1030 return Err(IoError::FileError("Not connected".to_string()));
1031 }
1032
1033 #[cfg(feature = "grpc")]
1034 {
1035 if let Some(_channel) = &self.channel {
1036 let _json_data: serde_json::Value = serde_json::from_slice(data).map_err(|e| {
1041 IoError::ParseError(format!("Invalid JSON data for gRPC: {}", e))
1042 })?;
1043
1044 tokio::time::sleep(Duration::from_millis(10)).await;
1046 Ok(())
1047 } else {
1048 Err(IoError::ParseError(
1049 "gRPC channel not initialized".to_string(),
1050 ))
1051 }
1052 }
1053
1054 #[cfg(not(feature = "grpc"))]
1055 {
1056 let _message_size = data.len();
1058 Ok(())
1059 }
1060 }
1061
1062 fn is_connected(&self) -> bool {
1063 self.connected
1064 }
1065
1066 async fn close(&mut self) -> Result<()> {
1067 #[cfg(feature = "grpc")]
1068 {
1069 self.channel = None;
1070 self.metadata = None;
1071 }
1072
1073 self.connected = false;
1074 Ok(())
1075 }
1076}
1077
1078struct MqttConnection {
1080 config: StreamConfig,
1081 client_id: String,
1082 topic: String,
1083 qos: u8,
1084 connected: bool,
1085 message_queue: Arc<Mutex<VecDeque<Vec<u8>>>>,
1086 #[cfg(feature = "mqtt")]
1087 client: Option<rumqttc::AsyncClient>,
1088 #[cfg(feature = "mqtt")]
1089 eventloop: Option<Arc<tokio::sync::Mutex<rumqttc::EventLoop>>>,
1090}
1091
1092impl MqttConnection {
1093 fn new(config: &StreamConfig) -> Self {
1094 let client_id = format!(
1096 "scirs2-io-{}",
1097 std::time::SystemTime::now()
1098 .duration_since(std::time::UNIX_EPOCH)
1099 .expect("Operation failed")
1100 .as_millis()
1101 );
1102
1103 Self {
1104 config: config.clone(),
1105 client_id,
1106 topic: "scirs2/data".to_string(),
1107 qos: 1, connected: false,
1109 message_queue: Arc::new(Mutex::new(VecDeque::new())),
1110 #[cfg(feature = "mqtt")]
1111 client: None,
1112 #[cfg(feature = "mqtt")]
1113 eventloop: None,
1114 }
1115 }
1116}
1117
1118#[async_trait::async_trait]
1119impl StreamConnection for MqttConnection {
1120 async fn connect(&mut self) -> Result<()> {
1121 let url = url::Url::parse(&self.config.endpoint)
1124 .map_err(|e| IoError::ParseError(format!("Invalid MQTT URL: {}", e)))?;
1125
1126 let host = url
1127 .host_str()
1128 .ok_or_else(|| IoError::ParseError("MQTT URL missing host".to_string()))?;
1129 let port = url.port().unwrap_or(1883);
1130
1131 if !url.path().is_empty() && url.path() != "/" {
1133 self.topic = url.path().trim_start_matches('/').to_string();
1134 }
1135
1136 #[cfg(feature = "mqtt")]
1137 {
1138 let mut mqttoptions = rumqttc::MqttOptions::new(&self.client_id, (host, port));
1139
1140 if let Some(password) = url.password() {
1141 let username = url.username().to_owned();
1142 if !username.is_empty() {
1143 mqttoptions.set_credentials(username, password.to_owned());
1144 }
1145 }
1146
1147 mqttoptions.set_keep_alive(60u16);
1148
1149 let (client, eventloop) = rumqttc::AsyncClient::new(mqttoptions, 10);
1150
1151 client
1153 .subscribe(&self.topic, rumqttc::QoS::AtLeastOnce)
1154 .await
1155 .map_err(|e| IoError::NetworkError(format!("MQTT subscribe error: {}", e)))?;
1156
1157 self.client = Some(client);
1158 self.eventloop = Some(Arc::new(tokio::sync::Mutex::new(eventloop)));
1159 self.connected = true;
1160 Ok(())
1161 }
1162
1163 #[cfg(not(feature = "mqtt"))]
1164 {
1165 tokio::time::sleep(Duration::from_millis(100)).await; self.connected = true;
1168 Ok(())
1169 }
1170 }
1171
1172 async fn receive(&mut self) -> Result<Vec<u8>> {
1173 if !self.connected {
1174 return Err(IoError::ParseError("Not connected".to_string()));
1175 }
1176
1177 #[cfg(feature = "mqtt")]
1178 {
1179 if let Some(eventloop_arc) = &self.eventloop {
1180 let mut eventloop = eventloop_arc.lock().await;
1181 match tokio::time::timeout(self.config.timeout, eventloop.poll()).await {
1182 Ok(Ok(event)) => {
1183 match event {
1184 rumqttc::Event::Incoming(rumqttc::Packet::Publish(publish)) => {
1185 return Ok(publish.payload.to_vec());
1186 }
1187 rumqttc::Event::Incoming(rumqttc::Packet::ConnAck(_)) => {
1188 }
1190 rumqttc::Event::Incoming(rumqttc::Packet::SubAck(_)) => {
1191 }
1193 rumqttc::Event::Incoming(rumqttc::Packet::PingResp) => {
1194 }
1196 _ => {
1197 }
1199 }
1200 }
1201 Ok(Err(e)) => {
1202 return Err(IoError::NetworkError(format!("MQTT receive error: {}", e)));
1203 }
1204 Err(_) => {
1205 return Err(IoError::TimeoutError("MQTT receive timeout".to_string()));
1206 }
1207 }
1208 }
1209 }
1210
1211 if let Ok(mut queue) = self.message_queue.lock() {
1213 if let Some(message) = queue.pop_front() {
1214 return Ok(message);
1215 }
1216 }
1217
1218 let mut rng = scirs2_core::random::rng();
1220 let sensor_data = serde_json::json!({
1221 "client_id": self.client_id,
1222 "topic": self.topic,
1223 "timestamp": std::time::SystemTime::now()
1224 .duration_since(std::time::UNIX_EPOCH)
1225 .expect("Operation failed")
1226 .as_millis() as u64,
1227 "qos": self.qos,
1228 "payload": {
1229 "temperature": 23.5 + (rng.random::<f64>() - 0.5) * 10.0,
1230 "humidity": 65.2 + (rng.random::<f64>() - 0.5) * 20.0,
1231 "pressure": 1013.25 + (rng.random::<f64>() - 0.5) * 50.0
1232 }
1233 });
1234
1235 Ok(sensor_data.to_string().into_bytes())
1236 }
1237
1238 async fn send(&mut self, data: &[u8]) -> Result<()> {
1239 if !self.connected {
1240 return Err(IoError::FileError("Not connected".to_string()));
1241 }
1242
1243 #[cfg(feature = "mqtt")]
1244 {
1245 if let Some(client) = &self.client {
1246 let qos = match self.qos {
1247 0 => rumqttc::QoS::AtMostOnce,
1248 1 => rumqttc::QoS::AtLeastOnce,
1249 2 => rumqttc::QoS::ExactlyOnce,
1250 _ => rumqttc::QoS::AtLeastOnce,
1251 };
1252
1253 client
1254 .publish(&self.topic, qos, false, data)
1255 .await
1256 .map_err(|e| IoError::NetworkError(format!("MQTT publish error: {}", e)))?;
1257
1258 return Ok(());
1259 }
1260 }
1261
1262 if let Ok(mut queue) = self.message_queue.lock() {
1264 queue.push_back(data.to_vec());
1265 }
1266
1267 Ok(())
1268 }
1269
1270 fn is_connected(&self) -> bool {
1271 self.connected
1272 }
1273
1274 async fn close(&mut self) -> Result<()> {
1275 #[cfg(feature = "mqtt")]
1276 {
1277 if let Some(client) = &self.client {
1278 client
1279 .disconnect()
1280 .await
1281 .map_err(|e| IoError::NetworkError(format!("MQTT disconnect error: {}", e)))?;
1282 }
1283 self.client = None;
1284 self.eventloop = None;
1285 }
1286
1287 self.connected = false;
1288 Ok(())
1289 }
1290}
1291
1292struct UdpConnection {
1294 config: StreamConfig,
1295 socket: Option<tokio::net::UdpSocket>,
1296 remote_addr: Option<std::net::SocketAddr>,
1297 connected: bool,
1298 packet_counter: Arc<Mutex<u64>>,
1299}
1300
1301impl UdpConnection {
1302 fn new(config: &StreamConfig) -> Self {
1303 Self {
1304 config: config.clone(),
1305 socket: None,
1306 remote_addr: None,
1307 connected: false,
1308 packet_counter: Arc::new(Mutex::new(0)),
1309 }
1310 }
1311}
1312
1313#[async_trait::async_trait]
1314impl StreamConnection for UdpConnection {
1315 async fn connect(&mut self) -> Result<()> {
1316 use tokio::net::UdpSocket;
1317
1318 let remote_addr = self
1320 .config
1321 .endpoint
1322 .parse::<std::net::SocketAddr>()
1323 .map_err(|e| IoError::ParseError(format!("Invalid UDP address: {}", e)))?;
1324
1325 let local_addr = if remote_addr.is_ipv4() {
1327 "0.0.0.0:0"
1328 } else {
1329 "[::]:0"
1330 };
1331
1332 let socket = UdpSocket::bind(local_addr)
1333 .await
1334 .map_err(|e| IoError::NetworkError(format!("UDP bind failed: {}", e)))?;
1335
1336 socket
1338 .connect(remote_addr)
1339 .await
1340 .map_err(|e| IoError::NetworkError(format!("UDP connect failed: {}", e)))?;
1341
1342 self.socket = Some(socket);
1343 self.remote_addr = Some(remote_addr);
1344 self.connected = true;
1345 Ok(())
1346 }
1347
1348 async fn receive(&mut self) -> Result<Vec<u8>> {
1349 if !self.connected || self.socket.is_none() {
1350 return Err(IoError::ParseError("Not connected".to_string()));
1351 }
1352
1353 if let Some(socket) = &self.socket {
1354 let mut buffer = vec![0u8; self.config.buffer_size];
1355
1356 match tokio::time::timeout(self.config.timeout, socket.recv(&mut buffer)).await {
1357 Ok(Ok(bytes_received)) => {
1358 if let Ok(mut counter) = self.packet_counter.lock() {
1359 *counter += 1;
1360 }
1361
1362 buffer.truncate(bytes_received);
1363 Ok(buffer)
1364 }
1365 Ok(Err(e)) => Err(IoError::NetworkError(format!("UDP receive error: {}", e))),
1366 Err(_) => Err(IoError::TimeoutError("UDP receive timeout".to_string())),
1367 }
1368 } else {
1369 Err(IoError::ParseError(
1370 "UDP socket not initialized".to_string(),
1371 ))
1372 }
1373 }
1374
1375 async fn send(&mut self, data: &[u8]) -> Result<()> {
1376 if !self.connected || self.socket.is_none() {
1377 return Err(IoError::FileError("Not connected".to_string()));
1378 }
1379
1380 if let Some(socket) = &self.socket {
1381 match tokio::time::timeout(self.config.timeout, socket.send(data)).await {
1382 Ok(Ok(bytes_sent)) => {
1383 if bytes_sent != data.len() {
1384 return Err(IoError::NetworkError(format!(
1385 "UDP partial send: {} of {} bytes",
1386 bytes_sent,
1387 data.len()
1388 )));
1389 }
1390
1391 if let Ok(mut counter) = self.packet_counter.lock() {
1392 *counter += 1;
1393 }
1394
1395 Ok(())
1396 }
1397 Ok(Err(e)) => Err(IoError::NetworkError(format!("UDP send error: {}", e))),
1398 Err(_) => Err(IoError::TimeoutError("UDP send timeout".to_string())),
1399 }
1400 } else {
1401 Err(IoError::ParseError(
1402 "UDP socket not initialized".to_string(),
1403 ))
1404 }
1405 }
1406
1407 fn is_connected(&self) -> bool {
1408 self.connected
1409 }
1410
1411 async fn close(&mut self) -> Result<()> {
1412 self.socket = None;
1413 self.remote_addr = None;
1414 self.connected = false;
1415 Ok(())
1416 }
1417}
1418
1419pub struct StreamSynchronizer {
1421 streams: Vec<StreamInfo>,
1422 sync_strategy: SyncStrategy,
1423 buffer_size: usize,
1424 output_rate: Option<Duration>,
1425}
1426
1427struct StreamInfo {
1429 name: String,
1430 client: StreamClient,
1431 buffer: VecDeque<TimestampedData>,
1432 last_timestamp: Option<Instant>,
1433}
1434
1435struct TimestampedData {
1437 timestamp: Instant,
1438 data: Vec<u8>,
1439}
1440
1441#[derive(Debug, Clone, Copy)]
1443pub enum SyncStrategy {
1444 Timestamp,
1446 Sequence,
1448 BestEffort,
1450}
1451
1452impl StreamSynchronizer {
1453 pub fn new(syncstrategy: SyncStrategy) -> Self {
1455 Self {
1456 streams: Vec::new(),
1457 sync_strategy: syncstrategy,
1458 buffer_size: 1000,
1459 output_rate: None,
1460 }
1461 }
1462
1463 pub fn add_stream(&mut self, name: String, client: StreamClient) {
1465 self.streams.push(StreamInfo {
1466 name,
1467 client,
1468 buffer: VecDeque::new(),
1469 last_timestamp: None,
1470 });
1471 }
1472
1473 pub fn output_rate(mut self, rate: Duration) -> Self {
1475 self.output_rate = Some(rate);
1476 self
1477 }
1478
1479 pub async fn run<F>(&mut self, mut processor: F) -> Result<()>
1481 where
1482 F: FnMut(Vec<(&str, &[u8])>) -> Result<()>,
1483 {
1484 let mut last_sync_time = Instant::now();
1486
1487 loop {
1488 let mut synchronized_data = Vec::new();
1489 let mut collected_data: Vec<(String, Vec<u8>)> = Vec::new();
1490 let mut has_data = false;
1491
1492 for stream_info in &mut self.streams {
1494 if !stream_info
1496 .client
1497 .connection
1498 .as_ref()
1499 .map_or(false, |c| c.is_connected())
1500 {
1501 if let Err(_) = stream_info.client.connect().await {
1502 continue; }
1504 }
1505
1506 if let Some(ref mut connection) = stream_info.client.connection {
1508 match connection.receive().await {
1509 Ok(data) => {
1510 let timestamped_data = TimestampedData {
1511 timestamp: Instant::now(),
1512 data: data.clone(),
1513 };
1514
1515 stream_info.buffer.push_back(timestamped_data);
1516 stream_info.last_timestamp = Some(Instant::now());
1517
1518 while stream_info.buffer.len() > self.buffer_size {
1520 stream_info.buffer.pop_front();
1521 }
1522
1523 has_data = true;
1524 }
1525 Err(_) => {
1526 continue;
1528 }
1529 }
1530 }
1531 }
1532
1533 if !has_data {
1534 tokio::time::sleep(Duration::from_millis(10)).await;
1536 continue;
1537 }
1538
1539 match self.sync_strategy {
1541 SyncStrategy::Timestamp => {
1542 let mut min_timestamp = None;
1544 for stream_info in &self.streams {
1545 if let Some(front) = stream_info.buffer.front() {
1546 if min_timestamp.is_none()
1547 || front.timestamp < min_timestamp.expect("Operation failed")
1548 {
1549 min_timestamp = Some(front.timestamp);
1550 }
1551 }
1552 }
1553
1554 if let Some(target_time) = min_timestamp {
1556 let tolerance = Duration::from_millis(100); for stream_info in &mut self.streams {
1558 if let Some(front) = stream_info.buffer.front() {
1559 if front.timestamp <= target_time + tolerance {
1560 if let Some(data) = stream_info.buffer.pop_front() {
1561 collected_data.push((stream_info.name.clone(), data.data));
1562 }
1563 }
1564 }
1565 }
1566 }
1567 }
1568 SyncStrategy::Sequence => {
1569 for stream_info in &mut self.streams {
1571 if let Some(data) = stream_info.buffer.pop_front() {
1572 collected_data.push((stream_info.name.clone(), data.data));
1573 }
1574 }
1575 }
1576 SyncStrategy::BestEffort => {
1577 for stream_info in &mut self.streams {
1579 while let Some(data) = stream_info.buffer.pop_front() {
1580 collected_data.push((stream_info.name.clone(), data.data));
1581 }
1582 }
1583 }
1584 }
1585
1586 for (name, data) in &collected_data {
1588 synchronized_data.push((name.as_str(), data.as_slice()));
1589 }
1590
1591 if !synchronized_data.is_empty() {
1593 if let Err(e) = processor(synchronized_data) {
1594 eprintln!("Processor error: {e}");
1595 }
1596 }
1597
1598 if let Some(rate) = self.output_rate {
1600 let elapsed = last_sync_time.elapsed();
1601 if elapsed < rate {
1602 tokio::time::sleep(rate - elapsed).await;
1603 }
1604 last_sync_time = Instant::now();
1605 }
1606 }
1607 }
1608}
1609
1610pub struct TimeSeriesBuffer<T> {
1612 max_size: usize,
1614 window_duration: Option<Duration>,
1616 data: VecDeque<TimePoint<T>>,
1618 stats: BufferStats,
1620}
1621
1622#[derive(Clone)]
1624struct TimePoint<T> {
1625 timestamp: Instant,
1626 value: T,
1627}
1628
1629#[derive(Debug, Default)]
1631pub struct BufferStats {
1632 total_added: u64,
1634 total_dropped: u64,
1636 current_size: usize,
1638 oldest_timestamp: Option<Instant>,
1640 newest_timestamp: Option<Instant>,
1642}
1643
1644impl<T: Clone> TimeSeriesBuffer<T> {
1645 pub fn new(maxsize: usize) -> Self {
1647 Self {
1648 max_size: maxsize,
1649 window_duration: None,
1650 data: VecDeque::with_capacity(maxsize),
1651 stats: BufferStats::default(),
1652 }
1653 }
1654
1655 pub fn with_time_window(mut self, duration: Duration) -> Self {
1657 self.window_duration = Some(duration);
1658 self
1659 }
1660
1661 pub fn push(&mut self, value: T) {
1663 let now = Instant::now();
1664
1665 if let Some(duration) = self.window_duration {
1667 let cutoff = now - duration;
1668 while let Some(front) = self.data.front() {
1669 if front.timestamp < cutoff {
1670 self.data.pop_front();
1671 self.stats.total_dropped += 1;
1672 } else {
1673 break;
1674 }
1675 }
1676 }
1677
1678 if self.data.len() >= self.max_size {
1680 self.data.pop_front();
1681 self.stats.total_dropped += 1;
1682 }
1683
1684 self.data.push_back(TimePoint {
1686 timestamp: now,
1687 value,
1688 });
1689
1690 self.stats.total_added += 1;
1692 self.stats.current_size = self.data.len();
1693 self.stats.newest_timestamp = Some(now);
1694 if self.stats.oldest_timestamp.is_none() {
1695 self.stats.oldest_timestamp = Some(now);
1696 }
1697 }
1698
1699 pub fn as_array(&self) -> Vec<T> {
1701 self.data.iter().map(|tp| tp.value.clone()).collect()
1702 }
1703
1704 pub fn range(&self, start: Instant, end: Instant) -> Vec<T> {
1706 self.data
1707 .iter()
1708 .filter(|tp| tp.timestamp >= start && tp.timestamp <= end)
1709 .map(|tp| tp.value.clone())
1710 .collect()
1711 }
1712
1713 pub fn stats(&self) -> &BufferStats {
1715 &self.stats
1716 }
1717}
1718
1719pub struct StreamAggregator<T> {
1721 window: Duration,
1723 current_window: Vec<T>,
1725 window_start: Instant,
1727 aggregators: Vec<Box<dyn Fn(&[T]) -> f64 + Send>>,
1729 results_tx: mpsc::Sender<AggregationResult>,
1731}
1732
1733#[derive(Debug, Clone)]
1735pub struct AggregationResult {
1736 pub window_start: Instant,
1738 pub window_end: Instant,
1740 pub count: usize,
1742 pub values: Vec<f64>,
1744}
1745
1746impl<T: Clone + Send + 'static> StreamAggregator<T> {
1747 pub fn new(window: Duration) -> (Self, mpsc::Receiver<AggregationResult>) {
1749 let (tx, rx) = mpsc::channel(100);
1750
1751 let aggregator = Self {
1752 window,
1753 current_window: Vec::new(),
1754 window_start: Instant::now(),
1755 aggregators: Vec::new(),
1756 results_tx: tx,
1757 };
1758
1759 (aggregator, rx)
1760 }
1761
1762 pub fn add_aggregator<F>(&mut self, f: F)
1764 where
1765 F: Fn(&[T]) -> f64 + Send + 'static,
1766 {
1767 self.aggregators.push(Box::new(f));
1768 }
1769
1770 pub async fn process(&mut self, value: T) -> Result<()> {
1772 let now = Instant::now();
1773
1774 if now.duration_since(self.window_start) >= self.window {
1776 self.flush_window().await?;
1777 self.window_start = now;
1778 }
1779
1780 self.current_window.push(value);
1781 Ok(())
1782 }
1783
1784 async fn flush_window(&mut self) -> Result<()> {
1786 if self.current_window.is_empty() {
1787 return Ok(());
1788 }
1789
1790 let values: Vec<f64> = self
1791 .aggregators
1792 .iter()
1793 .map(|f| f(&self.current_window))
1794 .collect();
1795
1796 let result = AggregationResult {
1797 window_start: self.window_start,
1798 window_end: Instant::now(),
1799 count: self.current_window.len(),
1800 values,
1801 };
1802
1803 self.results_tx
1804 .send(result)
1805 .await
1806 .map_err(|_| IoError::FileError("Failed to send aggregation result".to_string()))?;
1807
1808 self.current_window.clear();
1809 Ok(())
1810 }
1811}
1812
1813use async_trait;
1815use statrs::statistics::Statistics;
1816
1817#[cfg(test)]
1818mod tests {
1819 use super::*;
1820
1821 #[test]
1822 fn test_time_series_buffer() {
1823 let mut buffer = TimeSeriesBuffer::new(100);
1824
1825 for i in 0..150 {
1826 buffer.push(i as f64);
1827 }
1828
1829 assert_eq!(buffer.stats().total_added, 150);
1830 assert_eq!(buffer.stats().total_dropped, 50);
1831 assert_eq!(buffer.stats().current_size, 100);
1832
1833 let values = buffer.as_array();
1834 assert_eq!(values.len(), 100);
1835 assert_eq!(values[0], 50.0);
1836 assert_eq!(values[99], 149.0);
1837 }
1838
1839 #[test]
1840 fn test_backoff_config() {
1841 let backoff = BackoffConfig::default();
1842 assert_eq!(backoff.initial_delay, Duration::from_millis(100));
1843 assert_eq!(backoff.multiplier, 2.0);
1844
1845 let mut delay = backoff.initial_delay.as_secs_f64();
1846 for _ in 0..5 {
1847 delay *= backoff.multiplier;
1848 }
1849 assert!(delay <= backoff.max_delay.as_secs_f64());
1850 }
1851
1852 #[tokio::test]
1853 async fn test_stream_aggregator() {
1854 let (mut aggregator, mut rx) = StreamAggregator::<f64>::new(Duration::from_secs(1));
1855
1856 aggregator.add_aggregator(|values| values.iter().sum::<f64>() / values.len() as f64);
1858
1859 for i in 0..10 {
1861 aggregator
1862 .process(i as f64)
1863 .await
1864 .expect("Operation failed");
1865 }
1866
1867 aggregator.flush_window().await.expect("Operation failed");
1869
1870 if let Some(result) = rx.recv().await {
1872 assert_eq!(result.count, 10);
1873 assert_eq!(result.values.len(), 1);
1874 assert_eq!(result.values[0], 4.5); }
1876 }
1877}