1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::Mutex;
3
4use tokio::sync::mpsc;
5
6use crate::error::{EnigmaRtcError, RtcResult};
7use crate::signaling::decode_signaling;
8use crate::types::{RtcEvent, SignalingMessage};
9use crate::webrtc::RtcEngine;
10
11pub struct MockWebRtcEngine {
12 next_offer: Mutex<String>,
13 next_answer: Mutex<String>,
14 applied_answers: Mutex<Vec<String>>,
15 remote_candidates: Mutex<Vec<String>>,
16 event_sender: Mutex<Option<mpsc::UnboundedSender<RtcEvent>>>,
17 microphone_enabled: AtomicBool,
18 camera_enabled: AtomicBool,
19}
20
21impl MockWebRtcEngine {
22 pub fn new() -> Self {
23 Self {
24 next_offer: Mutex::new("mock-offer".to_string()),
25 next_answer: Mutex::new("mock-answer".to_string()),
26 applied_answers: Mutex::new(Vec::new()),
27 remote_candidates: Mutex::new(Vec::new()),
28 event_sender: Mutex::new(None),
29 microphone_enabled: AtomicBool::new(true),
30 camera_enabled: AtomicBool::new(true),
31 }
32 }
33
34 pub fn set_offer(&self, sdp: impl Into<String>) {
35 if let Ok(mut guard) = self.next_offer.lock() {
36 *guard = sdp.into();
37 }
38 }
39
40 pub fn set_answer(&self, sdp: impl Into<String>) {
41 if let Ok(mut guard) = self.next_answer.lock() {
42 *guard = sdp.into();
43 }
44 }
45
46 pub fn emit_candidate(&self, candidate: &str) -> RtcResult<()> {
47 let sender = self
48 .event_sender
49 .lock()
50 .map_err(|_| EnigmaRtcError::ChannelClosed)?
51 .clone();
52 if let Some(tx) = sender {
53 let message = SignalingMessage::IceCandidate {
54 candidate: candidate.to_string(),
55 sdp_mid: Some("0".to_string()),
56 sdp_mline_index: Some(0),
57 };
58 tx.send(RtcEvent::LocalIceCandidate(message))
59 .map_err(|_| EnigmaRtcError::ChannelClosed)?;
60 }
61 Ok(())
62 }
63
64 pub fn last_applied_answer(&self) -> Option<String> {
65 self.applied_answers
66 .lock()
67 .ok()
68 .and_then(|v| v.last().cloned())
69 }
70
71 pub fn candidates(&self) -> Vec<String> {
72 self.remote_candidates
73 .lock()
74 .map(|v| v.clone())
75 .unwrap_or_default()
76 }
77
78 pub fn microphone_enabled(&self) -> bool {
79 self.microphone_enabled.load(Ordering::SeqCst)
80 }
81
82 pub fn camera_enabled(&self) -> bool {
83 self.camera_enabled.load(Ordering::SeqCst)
84 }
85}
86
87impl RtcEngine for MockWebRtcEngine {
88 fn set_event_sender(&self, sender: mpsc::UnboundedSender<RtcEvent>) -> RtcResult<()> {
89 let mut slot = self
90 .event_sender
91 .lock()
92 .map_err(|_| EnigmaRtcError::ChannelClosed)?;
93 *slot = Some(sender);
94 Ok(())
95 }
96
97 fn create_offer(&self) -> RtcResult<String> {
98 self.next_offer
99 .lock()
100 .map(|s| s.clone())
101 .map_err(|_| EnigmaRtcError::InvalidState)
102 }
103
104 fn create_answer(&self, offer_sdp: &str) -> RtcResult<String> {
105 if offer_sdp.trim().is_empty() {
106 return Err(EnigmaRtcError::InvalidSdp);
107 }
108 self.next_answer
109 .lock()
110 .map(|s| s.clone())
111 .map_err(|_| EnigmaRtcError::InvalidState)
112 }
113
114 fn apply_answer(&self, answer_sdp: &str) -> RtcResult<()> {
115 if answer_sdp.trim().is_empty() {
116 return Err(EnigmaRtcError::InvalidSdp);
117 }
118 if let Ok(mut guard) = self.applied_answers.lock() {
119 guard.push(answer_sdp.to_string());
120 return Ok(());
121 }
122 Err(EnigmaRtcError::InvalidState)
123 }
124
125 fn add_remote_candidate(&self, candidate_json: &str) -> RtcResult<()> {
126 match decode_signaling(candidate_json)? {
127 SignalingMessage::IceCandidate { candidate, .. } => {
128 if let Ok(mut guard) = self.remote_candidates.lock() {
129 guard.push(candidate);
130 return Ok(());
131 }
132 Err(EnigmaRtcError::InvalidState)
133 }
134 _ => Err(EnigmaRtcError::InvalidCandidate),
135 }
136 }
137
138 fn set_microphone_enabled(&self, enabled: bool) -> RtcResult<()> {
139 self.microphone_enabled.store(enabled, Ordering::SeqCst);
140 Ok(())
141 }
142
143 fn set_camera_enabled(&self, enabled: bool) -> RtcResult<()> {
144 self.camera_enabled.store(enabled, Ordering::SeqCst);
145 Ok(())
146 }
147}