offline_protocol_transport/
internet.rs1use crate::constants::{
18 INTERNET_DEFAULT_SERVER_ADDRESS, INTERNET_PENDING_CONFIRMATION_TIMEOUT_SECS,
19};
20use crate::{Result, Transport, TransportMetrics, TransportStatus, TransportType};
21use offline_protocol_core::{Message, MutexExt};
22use std::collections::{HashMap, VecDeque};
23use std::sync::{Arc, Mutex};
24use std::time::{Duration, Instant};
25
26use crate::common::recalculate_delivery_ratios;
27
28#[derive(Debug, Clone)]
34pub struct InternetConfig {
35 pub server_address: String,
37}
38
39impl Default for InternetConfig {
40 fn default() -> Self {
41 Self {
42 server_address: INTERNET_DEFAULT_SERVER_ADDRESS.to_string(),
43 }
44 }
45}
46
47pub struct InternetTransport {
66 device_id: String,
68 config: InternetConfig,
70 status: Arc<Mutex<TransportStatus>>,
72 receive_queue: Arc<Mutex<VecDeque<Message>>>,
74 send_queue: Arc<Mutex<VecDeque<Message>>>,
76 pending_confirmation: Arc<Mutex<HashMap<String, Instant>>>,
86 metrics: Arc<Mutex<TransportMetrics>>,
88}
89
90impl InternetTransport {
91 pub fn new(device_id: impl Into<String>) -> Self {
93 Self::with_config(device_id, InternetConfig::default())
94 }
95
96 pub fn with_config(device_id: impl Into<String>, config: InternetConfig) -> Self {
98 Self {
99 device_id: device_id.into(),
100 config,
101 status: Arc::new(Mutex::new(TransportStatus::Unavailable)),
102 receive_queue: Arc::new(Mutex::new(VecDeque::new())),
103 send_queue: Arc::new(Mutex::new(VecDeque::new())),
104 pending_confirmation: Arc::new(Mutex::new(HashMap::new())),
105 metrics: Arc::new(Mutex::new(TransportMetrics::default())),
106 }
107 }
108
109 pub fn device_id(&self) -> &str {
111 &self.device_id
112 }
113
114 pub fn config(&self) -> &InternetConfig {
116 &self.config
117 }
118
119 pub fn on_message_received(&self, message: Message) {
121 crate::common::on_message_received(&self.receive_queue, message);
122 }
123
124 pub fn on_message_received_from(&self, message: Message, peer_id: String) {
138 crate::common::on_message_received_from(&self.receive_queue, message, peer_id);
139 }
140
141 pub fn serialize_message(&self, message: &Message) -> Result<Vec<u8>> {
143 crate::common::serialize_message(message)
144 }
145
146 fn drain_expired_pending(&self) {
149 let timeout = Duration::from_secs(INTERNET_PENDING_CONFIRMATION_TIMEOUT_SECS);
150 let now = Instant::now();
151 let mut expired_count: u32 = 0;
152
153 {
154 let mut pending = self.pending_confirmation.lock_or_recover();
155 pending.retain(|_id, enqueued_at| {
156 if now.duration_since(*enqueued_at) >= timeout {
157 expired_count += 1;
158 false
159 } else {
160 true
161 }
162 });
163 }
164
165 if expired_count > 0 {
166 tracing::warn!(
167 expired_count = expired_count,
168 "Pending confirmations expired, recorded as failures"
169 );
170 self.record_bulk_failures(expired_count);
171 }
172 }
173
174 fn fail_all_pending(&self) {
180 let count = {
181 let mut pending = self.pending_confirmation.lock_or_recover();
182 let count = u32::try_from(pending.len()).unwrap_or(u32::MAX);
183 pending.clear();
184 count
185 };
186
187 if count > 0 {
188 tracing::warn!(
189 count = count,
190 "Failing all pending confirmations due to transport disconnect"
191 );
192 self.record_bulk_failures(count);
193 }
194 }
195
196 fn record_bulk_failures(&self, count: u32) {
198 let mut metrics = self.metrics.lock_or_recover();
199 metrics.failure_count = metrics.failure_count.saturating_add(count);
200 recalculate_delivery_ratios(&mut metrics);
201 }
202
203 pub fn pending_confirmation_count(&self) -> usize {
205 self.pending_confirmation.lock_or_recover().len()
206 }
207
208 pub fn update_metrics(&self, incoming: TransportMetrics) {
216 let mut current = self.metrics.lock_or_recover();
217 current.rssi = incoming.rssi;
218 current.latency_ms = incoming.latency_ms;
219 current.bandwidth_bps = incoming.bandwidth_bps;
220 current.congestion = incoming.congestion;
221 current.queue_depth = incoming.queue_depth;
222 current.battery_level = incoming.battery_level;
223 current.is_charging = incoming.is_charging;
224 current.relay_connection_count = incoming.relay_connection_count;
225 current.is_active_relay = incoming.is_active_relay;
226 current.average_hop_count = incoming.average_hop_count;
227 current.energy_cost = incoming.energy_cost;
228 }
229}
230
231impl Transport for InternetTransport {
232 fn as_any(&self) -> &dyn std::any::Any {
233 self
234 }
235
236 fn transport_type(&self) -> TransportType {
237 TransportType::Internet
238 }
239
240 fn status(&self) -> TransportStatus {
241 *self.status.lock_or_recover()
242 }
243
244 fn metrics(&self) -> TransportMetrics {
245 self.metrics.lock_or_recover().clone()
246 }
247
248 fn send(&self, message: &Message) -> Result<()> {
249 if self.status() != TransportStatus::Available {
251 return Err(crate::Error::TransportNotAvailable(
252 "Internet transport is not available".to_string(),
253 ));
254 }
255
256 let mut queue = self.send_queue.lock_or_recover();
258 queue.push_back(message.clone());
259
260 let mut metrics = self.metrics.lock_or_recover();
262 metrics.queue_depth = queue.len();
263 metrics.congestion = ((metrics.queue_depth as f32) / 25.0).clamp(0.0, 1.0);
264
265 Ok(())
266 }
267
268 fn receive(&self) -> Result<Option<Message>> {
269 let mut queue = self.receive_queue.lock_or_recover();
270 Ok(queue.pop_front())
271 }
272
273 fn start(&self) -> Result<()> {
274 Ok(())
277 }
278
279 fn stop(&self) -> Result<()> {
280 self.fail_all_pending();
281 *self.status.lock_or_recover() = TransportStatus::Disconnected;
282 Ok(())
283 }
284
285 fn on_status_changed(&self, status: TransportStatus) {
291 let previous_status = {
292 let mut guard = self.status.lock_or_recover();
293 let prev = *guard;
294 *guard = status;
295 prev
296 };
297
298 if status == TransportStatus::Available {
299 let queue_len = self.send_queue.lock_or_recover().len();
300
301 if queue_len > 0 {
302 tracing::info!(
303 pending_messages = queue_len,
304 "Internet transport available, {} messages pending in queue",
305 queue_len
306 );
307 }
308 } else if previous_status == TransportStatus::Available
309 && status != TransportStatus::Available
310 {
311 self.fail_all_pending();
314
315 let queue_len = self.send_queue.lock_or_recover().len();
316 if queue_len > 0 {
317 tracing::warn!(
318 pending_messages = queue_len,
319 new_status = ?status,
320 "Internet transport disconnected with {} messages in queue (will retry)",
321 queue_len
322 );
323 }
324 }
325 }
326
327 fn on_data_received(&self, data: Vec<u8>) -> Result<()> {
331 crate::common::on_data_received(&self.receive_queue, data)
332 }
333
334 fn on_data_received_from(&self, data: Vec<u8>, peer_id: String) -> Result<()> {
352 crate::common::on_data_received_from(&self.receive_queue, data, peer_id)
353 }
354
355 fn get_next_message(&self) -> Result<Option<(String, Vec<u8>)>> {
364 self.drain_expired_pending();
368
369 let (message, queue_depth) = {
370 let mut queue = self.send_queue.lock_or_recover();
371 match queue.pop_front() {
372 Some(m) => {
373 let depth = queue.len();
374 (m, depth)
375 }
376 None => return Ok(None),
377 }
378 };
379
380 let message_id = message.id.as_str().to_string();
381
382 let data = self.serialize_message(&message)?;
386
387 {
388 let mut pending = self.pending_confirmation.lock_or_recover();
389 pending.insert(message_id.clone(), Instant::now());
390 }
391
392 {
393 let mut metrics = self.metrics.lock_or_recover();
394 metrics.queue_depth = queue_depth;
395 }
396
397 Ok(Some((message_id, data)))
398 }
399
400 fn confirm_sent(&self, message_id: &str) {
405 let was_pending = {
406 let mut pending = self.pending_confirmation.lock_or_recover();
407 pending.remove(message_id).is_some()
408 };
409
410 if was_pending {
411 let mut metrics = self.metrics.lock_or_recover();
412 metrics.success_count = metrics.success_count.saturating_add(1);
413 recalculate_delivery_ratios(&mut metrics);
414 tracing::trace!(
415 message_id = message_id,
416 success_count = metrics.success_count,
417 "confirm_sent: recorded success"
418 );
419 } else {
420 tracing::debug!(
421 message_id = message_id,
422 "confirm_sent: unknown or already-resolved message"
423 );
424 }
425 }
426
427 fn report_send_failure(&self, message_id: &str) {
429 let was_pending = {
430 let mut pending = self.pending_confirmation.lock_or_recover();
431 pending.remove(message_id).is_some()
432 };
433
434 if was_pending {
435 let mut metrics = self.metrics.lock_or_recover();
436 metrics.failure_count = metrics.failure_count.saturating_add(1);
437 recalculate_delivery_ratios(&mut metrics);
438 tracing::trace!(
439 message_id = message_id,
440 failure_count = metrics.failure_count,
441 "report_send_failure: recorded failure"
442 );
443 } else {
444 tracing::debug!(
445 message_id = message_id,
446 "report_send_failure: unknown or already-resolved message"
447 );
448 }
449 }
450}
451
452pub struct InternetTransportBuilder {
454 device_id: String,
455 config: InternetConfig,
456}
457
458impl InternetTransportBuilder {
459 pub fn new(device_id: impl Into<String>) -> Self {
461 Self {
462 device_id: device_id.into(),
463 config: InternetConfig::default(),
464 }
465 }
466
467 pub fn server_address(mut self, address: impl Into<String>) -> Self {
469 self.config.server_address = address.into();
470 self
471 }
472
473 pub fn build(self) -> InternetTransport {
475 InternetTransport::with_config(self.device_id, self.config)
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::constants::DEFAULT_MAX_MESSAGE_SIZE;
483 use offline_protocol_core::{AppId, UserId, WireCodec};
484
485 fn create_test_message() -> Message {
486 Message::new(
487 UserId::new("alice").unwrap(),
488 UserId::new("bob").unwrap(),
489 AppId::new("test").unwrap(),
490 "Test message",
491 )
492 }
493
494 #[test]
501 fn serialize_stays_json_even_when_message_stamped_binary() {
502 let transport = InternetTransport::new("test-device");
503 let mut message = create_test_message();
504 message.set_wire_codec(WireCodec::BinaryV1);
505
506 let data = transport.serialize_message(&message).unwrap();
507
508 assert_eq!(
509 data.first(),
510 Some(&b'{'),
511 "internet transport must emit JSON regardless of the wire-codec stamp"
512 );
513 assert_eq!(transport.deserialize_message(&data).unwrap().id, message.id);
515 }
516
517 #[test]
518 fn test_internet_transport_creation() {
519 let transport = InternetTransport::new("test-device");
520 assert_eq!(transport.device_id(), "test-device");
521 assert_eq!(transport.transport_type(), TransportType::Internet);
522 assert_eq!(transport.status(), TransportStatus::Unavailable);
523 }
524
525 #[test]
526 fn test_builder() {
527 let transport = InternetTransportBuilder::new("test-device")
528 .server_address("ws://example.com:8080")
529 .build();
530
531 assert_eq!(transport.config().server_address, "ws://example.com:8080");
532 }
533
534 #[test]
535 fn test_send_receive() {
536 let transport = InternetTransport::new("test-device");
537
538 transport.on_status_changed(TransportStatus::Available);
540
541 let message = create_test_message();
543 let message_id = message.id.as_str().to_string();
544 assert!(transport.send(&message).is_ok());
545
546 let next = transport.get_next_message().unwrap();
548 assert!(next.is_some());
549 let (returned_id, data) = next.unwrap();
550 assert_eq!(returned_id, message_id);
551 assert!(!data.is_empty());
552
553 assert_eq!(transport.pending_confirmation_count(), 1);
555
556 transport.confirm_sent(&returned_id);
558 assert_eq!(transport.pending_confirmation_count(), 0);
559 let metrics = transport.metrics();
560 assert_eq!(metrics.success_count, 1);
561 }
562
563 #[test]
564 fn test_send_failure_tracking() {
565 let transport = InternetTransport::new("test-device");
566 transport.on_status_changed(TransportStatus::Available);
567
568 let message = create_test_message();
569 assert!(transport.send(&message).is_ok());
570
571 let (msg_id, _data) = transport.get_next_message().unwrap().unwrap();
572 assert_eq!(transport.pending_confirmation_count(), 1);
573
574 transport.report_send_failure(&msg_id);
576 assert_eq!(transport.pending_confirmation_count(), 0);
577 let metrics = transport.metrics();
578 assert_eq!(metrics.failure_count, 1);
579 assert_eq!(metrics.success_count, 0);
580 }
581
582 #[test]
583 fn test_pending_expiry_on_drain() {
584 let transport = InternetTransport::new("test-device");
585 transport.on_status_changed(TransportStatus::Available);
586
587 let message = create_test_message();
588 assert!(transport.send(&message).is_ok());
589
590 let (msg_id, _data) = transport.get_next_message().unwrap().unwrap();
591 assert_eq!(transport.pending_confirmation_count(), 1);
592
593 {
595 let mut pending = transport.pending_confirmation.lock().unwrap();
596 let expired_time = Instant::now()
597 - Duration::from_secs(INTERNET_PENDING_CONFIRMATION_TIMEOUT_SECS + 1);
598 pending.insert(msg_id.clone(), expired_time);
599 }
600
601 let next = transport.get_next_message().unwrap();
603 assert!(next.is_none());
604
605 assert_eq!(transport.pending_confirmation_count(), 0);
607 let metrics = transport.metrics();
608 assert_eq!(metrics.failure_count, 1);
609 assert_eq!(metrics.success_count, 0);
610 }
611
612 #[test]
613 fn test_fail_all_pending_on_disconnect() {
614 let transport = InternetTransport::new("test-device");
615 transport.on_status_changed(TransportStatus::Available);
616
617 let msg1 = create_test_message();
619 let msg2 = create_test_message();
620 assert!(transport.send(&msg1).is_ok());
621 assert!(transport.send(&msg2).is_ok());
622
623 let _ = transport.get_next_message().unwrap().unwrap();
624 let _ = transport.get_next_message().unwrap().unwrap();
625 assert_eq!(transport.pending_confirmation_count(), 2);
626
627 transport.on_status_changed(TransportStatus::Disconnected);
629 assert_eq!(transport.pending_confirmation_count(), 0);
630 let metrics = transport.metrics();
631 assert_eq!(metrics.failure_count, 2);
632 assert_eq!(metrics.success_count, 0);
633 }
634
635 #[test]
636 fn test_serialization() {
637 let transport = InternetTransport::new("test-device");
638 let message = create_test_message();
639
640 let data = transport.serialize_message(&message).unwrap();
642 assert!(!data.is_empty());
643
644 let deserialized = transport.deserialize_message(&data).unwrap();
646 assert_eq!(deserialized.id, message.id);
647 }
648
649 #[test]
650 fn test_stop_fails_pending() {
651 let transport = InternetTransport::new("test-device");
652 transport.on_status_changed(TransportStatus::Available);
653
654 let msg = create_test_message();
655 assert!(transport.send(&msg).is_ok());
656 let _ = transport.get_next_message().unwrap().unwrap();
657 assert_eq!(transport.pending_confirmation_count(), 1);
658
659 transport.stop().unwrap();
660 assert_eq!(transport.pending_confirmation_count(), 0);
661 assert_eq!(transport.metrics().failure_count, 1);
662 }
663
664 #[test]
671 fn update_metrics_covers_all_fields() {
672 let m = TransportMetrics::default();
673 let TransportMetrics {
680 rssi: _,
681 latency_ms: _,
682 bandwidth_bps: _,
683 congestion: _,
684 queue_depth: _,
685 success_count: _,
686 failure_count: _,
687 battery_level: _,
688 is_charging: _,
689 relay_connection_count: _,
690 is_active_relay: _,
691 delivery_ratio: _,
692 drop_rate: _,
693 average_hop_count: _,
694 energy_cost: _,
695 } = m;
696 }
697
698 #[test]
699 fn test_send_when_unavailable_fails() {
700 let transport = InternetTransport::new("test-device");
701 let message = create_test_message();
702 let result = transport.send(&message);
703 assert!(result.is_err());
704 assert!(matches!(
705 result.unwrap_err(),
706 crate::Error::TransportNotAvailable(_)
707 ));
708 }
709
710 #[test]
711 fn test_receive_when_empty_returns_none() {
712 let transport = InternetTransport::new("test-device");
713 let received = transport.receive().unwrap();
714 assert!(received.is_none());
715 }
716
717 #[test]
718 fn test_on_data_received_invalid_json_drops_ok() {
719 let transport = InternetTransport::new("test-device");
720 let result = transport.on_data_received(b"not valid json".to_vec());
721 assert!(result.is_ok());
722 assert!(transport.receive().unwrap().is_none());
723 }
724
725 #[test]
726 fn test_on_data_received_valid_json_queues_message() {
727 let transport = InternetTransport::new("test-device");
728 let message = create_test_message();
729 let data = transport.serialize_message(&message).unwrap();
730 transport.on_data_received(data).unwrap();
731 let received = transport.receive().unwrap();
732 assert!(received.is_some());
733 assert_eq!(received.unwrap().id, message.id);
734 }
735
736 #[test]
737 fn test_update_metrics_preserves_confirmation_counts() {
738 let transport = InternetTransport::new("test-device");
739 transport.on_status_changed(TransportStatus::Available);
740 let msg = create_test_message();
741 transport.send(&msg).unwrap();
742 let (id, _) = transport.get_next_message().unwrap().unwrap();
743 transport.confirm_sent(&id);
744 assert_eq!(transport.metrics().success_count, 1);
745
746 let mut incoming = TransportMetrics::default();
747 incoming.rssi = Some(-70);
748 incoming.latency_ms = Some(100);
749 transport.update_metrics(incoming);
750
751 let m = transport.metrics();
752 assert_eq!(m.rssi, Some(-70));
753 assert_eq!(m.latency_ms, Some(100));
754 assert_eq!(m.success_count, 1);
755 }
756
757 #[test]
758 fn test_confirm_sent_unknown_id_no_panic() {
759 let transport = InternetTransport::new("test-device");
760 transport.confirm_sent("nonexistent-id");
761 assert_eq!(transport.metrics().success_count, 0);
762 }
763
764 #[test]
765 fn test_report_send_failure_unknown_id_no_panic() {
766 let transport = InternetTransport::new("test-device");
767 transport.report_send_failure("nonexistent-id");
768 assert_eq!(transport.metrics().failure_count, 0);
769 }
770
771 #[test]
772 fn test_start_does_not_require_connection() {
773 let transport = InternetTransport::new("test-device");
774 assert!(transport.start().is_ok());
775 assert_eq!(transport.status(), TransportStatus::Unavailable);
776 }
777
778 #[test]
779 fn test_on_data_received_rejects_oversized_payload() {
780 let transport = InternetTransport::new("test-device");
781 let oversized = vec![0u8; DEFAULT_MAX_MESSAGE_SIZE + 1];
782 let result = transport.on_data_received(oversized);
783 assert!(result.is_err());
784 assert!(matches!(
785 result.unwrap_err(),
786 crate::Error::MessageTooLarge(_, _)
787 ));
788 }
789
790 #[test]
791 fn test_on_data_received_from_rejects_oversized_payload() {
792 let transport = InternetTransport::new("test-device");
793 let oversized = vec![0u8; DEFAULT_MAX_MESSAGE_SIZE + 1];
794 let result = transport.on_data_received_from(oversized, "peer1".to_string());
795 assert!(result.is_err());
796 assert!(matches!(
797 result.unwrap_err(),
798 crate::Error::MessageTooLarge(_, _)
799 ));
800 }
801}