1use std::collections::{HashSet, VecDeque};
5use std::io::{Cursor, Read, Write};
6use std::net::TcpStream;
7use std::sync::{Arc, Mutex};
8
9use tungstenite::protocol::WebSocket;
10
11use crate::protocol::{self, CdpMessage, CdpResponse, SessionError};
12use crate::registry::SharedRegistry;
13use crate::{EventSender, RegistryDispatch};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum SessionState {
18 Created,
19 Active,
20 Closing,
21 Closed,
22}
23
24pub struct ReplayStream {
26 stream: TcpStream,
27 replay: Cursor<Vec<u8>>,
28}
29
30impl ReplayStream {
31 pub fn new(stream: TcpStream, peeked: Vec<u8>) -> Self {
32 ReplayStream {
33 stream,
34 replay: Cursor::new(peeked),
35 }
36 }
37}
38
39impl Read for ReplayStream {
40 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
41 if self.replay.position() < self.replay.get_ref().len() as u64 {
42 return self.replay.read(buf);
43 }
44 self.stream.read(buf)
45 }
46}
47
48impl Write for ReplayStream {
49 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
50 self.stream.write(buf)
51 }
52 fn flush(&mut self) -> std::io::Result<()> {
53 self.stream.flush()
54 }
55}
56
57#[derive(Debug, Clone)]
60pub struct OutboxEvent {
61 pub json: String,
63 pub domain: String,
65 pub browser_only: bool,
67}
68
69pub struct SessionHandle {
75 pub session: Mutex<CdpSession>,
76 pub outbox: Mutex<VecDeque<OutboxEvent>>,
77}
78
79impl SessionHandle {
80 pub fn new(session: CdpSession) -> Arc<Self> {
81 Arc::new(SessionHandle {
82 session: Mutex::new(session),
83 outbox: Mutex::new(VecDeque::new()),
84 })
85 }
86}
87
88pub struct CdpSession {
90 session_id: String,
91 target_id: String,
92 ws: WebSocket<ReplayStream>,
93 enabled_domains: HashSet<String>,
94 state: SessionState,
95 is_browser_session: bool,
96 first_domain_enabled: HashSet<String>,
97}
98
99impl CdpSession {
100 pub fn new(
101 session_id: String,
102 target_id: String,
103 ws: WebSocket<ReplayStream>,
104 is_browser_session: bool,
105 ) -> Self {
106 CdpSession {
107 session_id,
108 target_id,
109 ws,
110 enabled_domains: HashSet::new(),
111 state: SessionState::Created,
112 is_browser_session,
113 first_domain_enabled: HashSet::new(),
114 }
115 }
116
117 pub fn session_id(&self) -> &str {
118 &self.session_id
119 }
120
121 pub fn target_id(&self) -> &str {
122 &self.target_id
123 }
124
125 pub fn state(&self) -> SessionState {
126 self.state
127 }
128
129 pub fn is_browser_session(&self) -> bool {
130 self.is_browser_session
131 }
132
133 pub fn has_domain_enabled(&self, domain: &str) -> bool {
134 self.enabled_domains.contains(domain)
135 }
136
137 pub fn process(
139 &mut self,
140 registry: &SharedRegistry,
141 event_sender: &dyn EventSender,
142 ) -> Result<(), SessionError> {
143 let msg = match read_ws_message(&mut self.ws) {
144 Ok(Some(msg)) => msg,
145 Ok(None) => return Ok(()),
146 Err(e) => {
147 self.state = SessionState::Closing;
148 return Err(e);
149 }
150 };
151
152 let cdp_msg: CdpMessage = match protocol::parse_message(&msg) {
153 Some(m) => m,
154 None => {
155 let resp =
156 protocol::error_response(None, protocol::ERR_INVALID_REQUEST, "Invalid JSON");
157 let _ = self.send_text(&protocol::serialize_response(&resp));
158 return Ok(());
159 }
160 };
161 let flattened_session_id = cdp_msg.session_id.clone();
162
163 let response = self.route_command(cdp_msg, registry, event_sender);
164 let mut text = protocol::serialize_response(&response);
165 if let Some(sid) = &flattened_session_id {
169 if let Ok(mut v) = serde_json::from_str::<serde_json::Value>(&text) {
170 if v.is_object() {
171 v["sessionId"] = serde_json::Value::String(sid.clone());
172 text = v.to_string();
173 }
174 }
175 }
176 let _ = self.send_text(&text);
177 Ok(())
178 }
179
180 fn route_command(
187 &mut self,
188 msg: CdpMessage,
189 registry: &SharedRegistry,
190 event_sender: &dyn EventSender,
191 ) -> CdpResponse {
192 let parts: Vec<&str> = msg.method.splitn(2, '.').collect();
193 let domain = parts.first().copied().unwrap_or("");
194 let command = parts.get(1).copied().unwrap_or("");
195
196 match registry.dispatch_message(&msg, &self.target_id, event_sender) {
199 Some(Ok(result)) => {
200 match command {
201 "enable" => {
202 self.enabled_domains.insert(domain.to_string());
203 if self.state == SessionState::Created {
204 self.state = SessionState::Active;
205 }
206 if !self.first_domain_enabled.contains(domain) {
209 self.first_domain_enabled.insert(domain.to_string());
210 registry.notify_session_created(domain, &self.session_id);
211 }
212 }
213 "disable" => {
214 self.enabled_domains.remove(domain);
215 }
216 _ => {}
217 }
218 protocol::ok_response(msg.id, result)
219 }
220 Some(Err(err)) => CdpResponse {
221 id: msg.id,
222 result: None,
223 error: Some(err),
224 },
225 None => protocol::error_response(
226 msg.id,
227 protocol::ERR_METHOD_NOT_FOUND,
228 format!("'{}' wasn't found", msg.method),
229 ),
230 }
231 }
232
233 pub fn send_text(&mut self, data: &str) -> Result<(), SessionError> {
235 use tungstenite::Message;
236 self.ws
237 .send(Message::Text(data.into()))
238 .map_err(|_| SessionError::Io)
239 }
240
241 pub fn enabled_domains(&self) -> Vec<String> {
243 self.enabled_domains.iter().cloned().collect()
244 }
245
246 pub fn begin_close(&mut self) {
248 self.state = SessionState::Closing;
249 }
250
251 pub fn finalize(&mut self) {
253 self.state = SessionState::Closed;
254 }
255}
256
257fn read_ws_message(ws: &mut WebSocket<ReplayStream>) -> Result<Option<String>, SessionError> {
258 use tungstenite::Message;
259 match ws.read() {
260 Ok(Message::Text(text)) => Ok(Some(text.to_string())),
261 Ok(Message::Binary(data)) => Ok(Some(String::from_utf8_lossy(&data).into_owned())),
262 Ok(Message::Ping(_)) | Ok(Message::Pong(_)) => Ok(None),
263 Ok(Message::Close(_)) => Err(SessionError::Closed),
264 Ok(Message::Frame(_)) => Ok(None),
265 Err(_) => Err(SessionError::Io),
266 }
267}
268
269#[cfg(test)]
271mod tests {
272 use super::SessionState;
273
274 #[test]
275 fn session_state_equality_same_variant() {
276 assert_eq!(SessionState::Created, SessionState::Created);
277 }
278
279 #[test]
280 fn session_state_equality_different_variants() {
281 assert_ne!(SessionState::Created, SessionState::Closed);
282 }
283
284 #[test]
285 fn session_state_clone() {
286 let original = SessionState::Active;
287 let cloned = original.clone();
288 assert_eq!(original, cloned);
289 }
290
291 #[test]
292 fn session_state_copy() {
293 let original = SessionState::Closing;
294 let copied = original; assert_eq!(original, copied);
296 }
297
298 #[test]
299 fn session_state_debug_format() {
300 assert!(format!("{:?}", SessionState::Created).contains("Created"));
301 assert!(format!("{:?}", SessionState::Active).contains("Active"));
302 assert!(format!("{:?}", SessionState::Closing).contains("Closing"));
303 assert!(format!("{:?}", SessionState::Closed).contains("Closed"));
304 }
305
306 #[test]
307 fn session_state_all_variants_distinct() {
308 let variants = [
309 SessionState::Created,
310 SessionState::Active,
311 SessionState::Closing,
312 SessionState::Closed,
313 ];
314 for i in 0..variants.len() {
315 for j in (i + 1)..variants.len() {
316 assert_ne!(variants[i], variants[j]);
317 }
318 }
319 }
320
321 #[test]
322 fn session_state_send_sync() {
323 fn assert_send<T: Send>() {}
324 fn assert_sync<T: Sync>() {}
325 assert_send::<SessionState>();
326 assert_sync::<SessionState>();
327 }
328}