1use async_trait::async_trait;
9use ciborium::Value as CborValue;
10use futures_util::stream::{SplitSink, SplitStream};
11use futures_util::{SinkExt, StreamExt};
12use std::collections::HashMap;
13use std::sync::Arc;
14use std::sync::atomic::{AtomicU64, Ordering::SeqCst};
15use tokio::net::TcpStream;
16use tokio::sync::{Mutex, mpsc, oneshot};
17use tokio_tungstenite::MaybeTlsStream;
18use tokio_tungstenite::tungstenite::client::IntoClientRequest;
19use tokio_tungstenite::tungstenite::http::HeaderValue;
20use tokio_tungstenite::tungstenite::http::header::SEC_WEBSOCKET_PROTOCOL;
21use tokio_tungstenite::{WebSocketStream, connect_async, tungstenite::Message};
22use tracing::{Instrument as _, warn};
23
24use crate::SurrealConnection;
25use crate::live::{Action, Notification};
26use crate::{
27 engine::Engine,
28 error::{Result, SurrealError},
29};
30
31type LiveSubscribers = Arc<Mutex<HashMap<String, mpsc::UnboundedSender<Notification>>>>;
33
34fn cbor_id_key(v: &CborValue) -> Option<String> {
41 match v {
42 CborValue::Text(t) => Some(t.clone()),
43 CborValue::Integer(i) => {
44 let n: i128 = (*i).into();
45 Some(n.to_string())
46 }
47 CborValue::Tag(_, inner) => cbor_id_key(inner),
48 CborValue::Bytes(b) if b.len() == 16 => Some(
49 uuid::Uuid::from_slice(b)
50 .map(|u| u.to_string())
51 .unwrap_or_else(|_| hex::encode(b)),
52 ),
53 CborValue::Bytes(b) => Some(hex::encode(b)),
54 _ => None,
55 }
56}
57
58fn parse_notification(inner: &[(CborValue, CborValue)]) -> Option<Notification> {
65 let mut action = None;
66 let mut query_id = None;
67 let mut record_id = None;
68 let mut data = None;
69 for (k, v) in inner {
70 if let CborValue::Text(k) = k {
71 match k.as_str() {
72 "action" => {
73 if let CborValue::Text(a) = v {
74 action = Action::parse(a);
75 }
76 }
77 "id" => query_id = cbor_id_key(v),
78 "record" => record_id = Some(v.clone()),
79 "result" => data = Some(v.clone()),
80 _ => {}
81 }
82 }
83 }
84 Some(Notification {
85 query_id: query_id?,
86 action: action?,
87 record_id: record_id.unwrap_or(CborValue::Null),
88 data: data.unwrap_or(CborValue::Null),
89 })
90}
91
92#[derive(Debug, Clone)]
94struct RouterRequest {
95 id: String,
96 method: String,
97 params: Option<CborValue>,
98}
99
100type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
101
102pub struct WsCborEngine {
108 sink: Arc<Mutex<SplitSink<WsStream, Message>>>,
109 stream: Arc<Mutex<SplitStream<WsStream>>>,
110 msg_id: AtomicU64,
111 pending_requests: Arc<Mutex<HashMap<String, oneshot::Sender<CborValue>>>>,
112 live_subscribers: LiveSubscribers,
113 task_handle: Option<tokio::task::JoinHandle<()>>,
114}
115
116impl WsCborEngine {
117 pub async fn from_connection(connect: &SurrealConnection) -> Result<Self> {
118 static TLS_PROVIDER: std::sync::Once = std::sync::Once::new();
121 TLS_PROVIDER.call_once(|| {
122 let _ = rustls::crypto::ring::default_provider().install_default();
123 });
124
125 let base_url = connect
126 .url
127 .as_ref()
128 .ok_or_else(|| SurrealError::Connection("URL is required to connect".to_string()))?;
129
130 let mut ws_url = if let Some(rest) = base_url.strip_prefix("cbor://") {
131 format!("ws://{}", rest)
132 } else {
133 base_url.clone()
134 };
135 if !ws_url.ends_with("/rpc") {
136 if ws_url.ends_with('/') {
137 ws_url.push_str("rpc");
138 } else {
139 ws_url.push_str("/rpc");
140 }
141 }
142
143 let mut request = ws_url
144 .as_str()
145 .into_client_request()
146 .map_err(|e| SurrealError::Connection(format!("Invalid WebSocket URL: {}", e)))?;
147
148 request
149 .headers_mut()
150 .insert(SEC_WEBSOCKET_PROTOCOL, HeaderValue::from_static("cbor"));
151
152 let (stream, _response) = connect_async(request).await.map_err(|e| {
153 SurrealError::Connection(format!("Failed to connect to WebSocket: {}", e))
154 })?;
155
156 let (sink, stream) = stream.split();
157
158 let mut engine = Self {
159 sink: Arc::new(Mutex::new(sink)),
160 stream: Arc::new(Mutex::new(stream)),
161 msg_id: AtomicU64::new(0),
162 pending_requests: Arc::new(Mutex::new(HashMap::new())),
163 live_subscribers: Arc::new(Mutex::new(HashMap::new())),
164 task_handle: None,
165 };
166
167 let task_handle = engine.handle_messages();
168 engine.task_handle = Some(task_handle);
169
170 connect.init_engine(&mut engine).await?;
171
172 Ok(engine)
173 }
174
175 fn handle_messages(&self) -> tokio::task::JoinHandle<()> {
176 let stream = Arc::clone(&self.stream);
177 let pending_requests = Arc::clone(&self.pending_requests);
178 let live_subscribers = Arc::clone(&self.live_subscribers);
179
180 tokio::spawn(
181 async move {
182 loop {
183 let msg = {
184 let mut stream_guard = stream.lock().await;
185 stream_guard.next().await
186 };
187
188 let msg = match msg {
189 None => break,
190 Some(Err(e)) => {
191 warn!(error = %e, "CBOR ws receive error");
192 break;
193 }
194 Some(Ok(msg)) => msg,
195 };
196
197 match msg {
198 Message::Text(_text) => {
199 }
201 Message::Binary(binary) => {
202 match ciborium::from_reader::<CborValue, _>(binary.as_ref()) {
206 Ok(CborValue::Map(map)) => {
207 let mut top_id = None;
208 let mut result = None;
209 let mut error = None;
210
211 for (key, value) in &map {
212 if let CborValue::Text(k) = key {
213 match k.as_str() {
214 "id" => top_id = Some(value.clone()),
215 "result" => result = Some(value.clone()),
216 "error" => error = Some(value.clone()),
217 _ => {}
218 }
219 }
220 }
221
222 if top_id.is_none()
226 && let Some(CborValue::Map(inner)) = &result
227 && let Some(n) = parse_notification(inner)
228 {
229 let tx = {
230 let subs = live_subscribers.lock().await;
231 subs.get(&n.query_id).cloned()
232 };
233 if let Some(tx) = tx {
234 let _ = tx.send(n);
235 }
236 continue;
237 }
238
239 if let Some(id) = top_id.as_ref().and_then(cbor_id_key) {
241 let tx = {
242 let mut pending = pending_requests.lock().await;
243 pending.remove(&id)
244 };
245
246 if let Some(tx) = tx {
247 if let Some(err) = error {
248 let _ = tx.send(CborValue::Map(vec![(
249 CborValue::Text("error".to_string()),
250 err,
251 )]));
252 } else if let Some(res) = result {
253 let _ = tx.send(res);
254 } else {
255 let _ = tx.send(CborValue::Null);
256 }
257 }
258 }
259 }
260 Ok(_) => {}
261 Err(e) => {
262 warn!(error = %e, bytes = binary.len(), "CBOR parse failed");
263 }
264 }
265 }
266 Message::Ping(_) => {}
267 Message::Pong(_) => {}
268 Message::Close(_) => break,
269 _ => {}
270 }
271 }
272 }
273 .in_current_span(),
274 )
275 }
276}
277
278#[async_trait]
279impl Engine for WsCborEngine {
280 async fn send_message_cbor(&mut self, method: &str, params: CborValue) -> Result<CborValue> {
281 let (tx, rx) = oneshot::channel();
282 let id = self.msg_id.fetch_add(1, SeqCst).to_string();
283
284 {
285 let mut pending = self.pending_requests.lock().await;
286 pending.insert(id.clone(), tx);
287 }
288
289 let request = RouterRequest {
290 id: id.clone(),
291 method: method.to_string(),
292 params: Some(params),
293 };
294
295 let mut request_map = vec![
296 (
297 CborValue::Text("id".to_string()),
298 CborValue::Text(request.id),
299 ),
300 (
301 CborValue::Text("method".to_string()),
302 CborValue::Text(request.method),
303 ),
304 ];
305
306 if let Some(params) = request.params {
307 request_map.push((CborValue::Text("params".to_string()), params));
308 }
309
310 let rpc_message = CborValue::Map(request_map);
311
312 let mut payload = Vec::new();
313 ciborium::into_writer(&rpc_message, &mut payload)
314 .map_err(|e| SurrealError::Protocol(format!("CBOR encoding failed: {}", e)))?;
315
316 {
317 let mut sink = self.sink.lock().await;
318 sink.send(Message::Binary(payload.into()))
319 .await
320 .map_err(|e| SurrealError::Connection(format!("WS send failed: {}", e)))?;
321 }
322
323 let response = rx
324 .await
325 .map_err(|_| SurrealError::Protocol("Response channel closed".to_string()))?;
326
327 if let CborValue::Map(map) = &response {
328 for (key, value) in map {
329 if let CborValue::Text(k) = key
330 && k == "error"
331 {
332 if let CborValue::Map(error_map) = value {
333 let mut code = -1;
334 let mut message = String::new();
335
336 for (error_key, error_value) in error_map {
337 if let CborValue::Text(error_k) = error_key {
338 match error_k.as_str() {
339 "code" => {
340 if let CborValue::Integer(c) = error_value {
341 code = (*c).try_into().unwrap_or(-1);
342 }
343 }
344 "message" => {
345 if let CborValue::Text(m) = error_value {
346 message = m.clone();
347 }
348 }
349 _ => {}
350 }
351 }
352 }
353
354 if !message.is_empty() {
355 return Err(SurrealError::ServerError { code, message });
356 }
357 }
358
359 return Err(SurrealError::Protocol(format!("Server error: {:?}", value)));
360 }
361 }
362 }
363
364 Ok(response)
365 }
366
367 async fn register_live(
368 &mut self,
369 query_id: &str,
370 ) -> Result<mpsc::UnboundedReceiver<Notification>> {
371 let (tx, rx) = mpsc::unbounded_channel();
372 let mut subs = self.live_subscribers.lock().await;
373 subs.insert(query_id.to_string(), tx);
374 Ok(rx)
375 }
376
377 async fn unregister_live(&mut self, query_id: &str) {
378 let mut subs = self.live_subscribers.lock().await;
379 subs.remove(query_id);
380 }
381}
382
383impl Drop for WsCborEngine {
384 fn drop(&mut self) {
385 if let Some(handle) = self.task_handle.take() {
386 handle.abort();
387 }
388 }
389}