1use super::tcp::stream::validate_stream_record;
9use super::{
10 ConnectionState, DiscoveredPeer, PacketBuffer, PacketTx, ReceivedPacket, Transport,
11 TransportAddr, TransportError, TransportId, TransportState, TransportType,
12};
13use crate::Identity;
14use crate::config::WebSocketConfig;
15use crate::dataplane::validate_direct_fsp_transport_fragment;
16use crate::discovery::local_udp::LocalKeyHint;
17use futures::{SinkExt, StreamExt};
18use rand::RngExt;
19use secp256k1::XOnlyPublicKey;
20use serde::Serialize;
21use std::collections::{HashMap, VecDeque};
22use std::net::SocketAddr;
23use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
24use std::sync::{Arc, Mutex as StdMutex};
25use std::time::{Duration, SystemTime, UNIX_EPOCH};
26use tokio::net::{TcpListener, TcpStream};
27use tokio::sync::{Mutex, Semaphore, mpsc};
28use tokio::task::JoinHandle;
29use tokio_tungstenite::tungstenite::handshake::server::{ErrorResponse, Request, Response};
30use tokio_tungstenite::tungstenite::protocol::WebSocketConfig as TungsteniteConfig;
31use tokio_tungstenite::tungstenite::{Bytes, Message};
32use tokio_tungstenite::{WebSocketStream, accept_hdr_async_with_config, connect_async_with_config};
33use tracing::{debug, info};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36enum Direction {
37 Inbound,
38 Outbound,
39}
40
41struct Connection {
42 generation: u64,
43 tx: mpsc::Sender<Vec<u8>>,
44}
45
46type ConnectionPool = Arc<Mutex<HashMap<TransportAddr, Connection>>>;
47type ConnectionStates = Arc<StdMutex<HashMap<TransportAddr, ConnectionState>>>;
48type DiscoveryQueue = Arc<StdMutex<VecDeque<DiscoveredPeer>>>;
49
50#[derive(Debug, Default)]
51struct WebSocketStats {
52 connections_opened: AtomicU64,
53 connections_closed: AtomicU64,
54 connections_rejected: AtomicU64,
55 reconnect_attempts: AtomicU64,
56 frames_sent: AtomicU64,
57 frames_received: AtomicU64,
58 bytes_sent: AtomicU64,
59 bytes_received: AtomicU64,
60 invalid_frames: AtomicU64,
61 send_queue_full: AtomicU64,
62}
63
64#[derive(Debug, Serialize)]
65pub struct WebSocketStatsSnapshot {
66 pub connections_opened: u64,
67 pub connections_closed: u64,
68 pub connections_rejected: u64,
69 pub reconnect_attempts: u64,
70 pub frames_sent: u64,
71 pub frames_received: u64,
72 pub bytes_sent: u64,
73 pub bytes_received: u64,
74 pub invalid_frames: u64,
75 pub send_queue_full: u64,
76}
77
78impl WebSocketStats {
79 fn snapshot(&self) -> WebSocketStatsSnapshot {
80 let load = |value: &AtomicU64| value.load(Ordering::Relaxed);
81 WebSocketStatsSnapshot {
82 connections_opened: load(&self.connections_opened),
83 connections_closed: load(&self.connections_closed),
84 connections_rejected: load(&self.connections_rejected),
85 reconnect_attempts: load(&self.reconnect_attempts),
86 frames_sent: load(&self.frames_sent),
87 frames_received: load(&self.frames_received),
88 bytes_sent: load(&self.bytes_sent),
89 bytes_received: load(&self.bytes_received),
90 invalid_frames: load(&self.invalid_frames),
91 send_queue_full: load(&self.send_queue_full),
92 }
93 }
94}
95
96#[derive(Clone)]
97struct Runtime {
98 transport_id: TransportId,
99 config: WebSocketConfig,
100 local_pubkey: [u8; 32],
101 packet_tx: PacketTx,
102 pool: ConnectionPool,
103 states: ConnectionStates,
104 discoveries: DiscoveryQueue,
105 running: Arc<AtomicBool>,
106 total_slots: Arc<Semaphore>,
107 inbound_slots: Arc<Semaphore>,
108 generation: Arc<AtomicU64>,
109 stats: Arc<WebSocketStats>,
110}
111
112impl Runtime {
113 fn websocket_config(&self) -> TungsteniteConfig {
114 let mut config = TungsteniteConfig::default();
115 config.max_message_size = Some(self.config.max_frame_bytes());
116 config.max_frame_size = Some(self.config.max_frame_bytes());
117 config.max_write_buffer_size = self.config.max_frame_bytes().saturating_mul(2);
118 config.write_buffer_size = 0;
119 config
120 }
121
122 fn next_generation(&self) -> u64 {
123 self.generation.fetch_add(1, Ordering::Relaxed)
124 }
125
126 fn set_state(&self, addr: &TransportAddr, state: ConnectionState) {
127 self.states
128 .lock()
129 .unwrap_or_else(|error| error.into_inner())
130 .insert(addr.clone(), state);
131 }
132
133 fn clear_state_if(&self, addr: &TransportAddr, generation: u64) {
134 let connected_generation = self
135 .pool
136 .try_lock()
137 .ok()
138 .and_then(|pool| pool.get(addr).map(|connection| connection.generation));
139 if connected_generation != Some(generation) {
140 self.states
141 .lock()
142 .unwrap_or_else(|error| error.into_inner())
143 .remove(addr);
144 }
145 }
146}
147
148pub struct WebSocketTransport {
150 transport_id: TransportId,
151 name: Option<String>,
152 config: WebSocketConfig,
153 state: TransportState,
154 local_addr: Option<SocketAddr>,
155 runtime: Runtime,
156 tasks: Arc<StdMutex<Vec<JoinHandle<()>>>>,
157}
158
159impl WebSocketTransport {
160 pub fn new(
161 transport_id: TransportId,
162 name: Option<String>,
163 config: WebSocketConfig,
164 packet_tx: PacketTx,
165 identity: &Identity,
166 ) -> Self {
167 let max_connections = config.max_connections();
168 let max_inbound = config.max_inbound_connections();
169 let runtime = Runtime {
170 transport_id,
171 config: config.clone(),
172 local_pubkey: identity.pubkey().serialize(),
173 packet_tx,
174 pool: Arc::new(Mutex::new(HashMap::new())),
175 states: Arc::new(StdMutex::new(HashMap::new())),
176 discoveries: Arc::new(StdMutex::new(VecDeque::new())),
177 running: Arc::new(AtomicBool::new(false)),
178 total_slots: Arc::new(Semaphore::new(max_connections)),
179 inbound_slots: Arc::new(Semaphore::new(max_inbound)),
180 generation: Arc::new(AtomicU64::new(1)),
181 stats: Arc::new(WebSocketStats::default()),
182 };
183 Self {
184 transport_id,
185 name,
186 config,
187 state: TransportState::Configured,
188 local_addr: None,
189 runtime,
190 tasks: Arc::new(StdMutex::new(Vec::new())),
191 }
192 }
193
194 pub fn name(&self) -> Option<&str> {
195 self.name.as_deref()
196 }
197
198 pub fn local_addr(&self) -> Option<SocketAddr> {
199 self.local_addr
200 }
201
202 pub fn public_url(&self) -> Option<&str> {
203 self.config.public_url.as_deref()
204 }
205
206 pub(crate) fn is_configured_seed_addr(&self, addr: &TransportAddr) -> bool {
207 addr.as_str().is_some_and(|candidate| {
208 self.config
209 .seed_urls
210 .iter()
211 .any(|seed_url| seed_url == candidate)
212 })
213 }
214
215 pub(crate) fn is_configured_adjacency(
216 &self,
217 addr: &TransportAddr,
218 handshake_is_initiator: bool,
219 ) -> bool {
220 if self.is_configured_seed_addr(addr) {
221 true
225 } else if !handshake_is_initiator {
226 self.config.bind_addr.is_some()
230 } else {
231 false
232 }
233 }
234
235 pub fn stats(&self) -> WebSocketStatsSnapshot {
236 self.runtime.stats.snapshot()
237 }
238
239 fn push_task(&self, task: JoinHandle<()>) {
240 self.tasks
241 .lock()
242 .unwrap_or_else(|error| error.into_inner())
243 .push(task);
244 }
245
246 pub async fn start_async(&mut self) -> Result<(), TransportError> {
247 if !self.state.can_start() {
248 return Err(TransportError::AlreadyStarted);
249 }
250 self.config
251 .validate()
252 .map_err(TransportError::StartFailed)?;
253 self.state = TransportState::Starting;
254 self.runtime.running.store(true, Ordering::Release);
255
256 if let Some(bind_addr) = self.config.bind_addr.as_deref() {
257 let bind_addr = bind_addr
258 .parse::<SocketAddr>()
259 .map_err(|error| TransportError::StartFailed(error.to_string()))?;
260 let listener = TcpListener::bind(bind_addr)
261 .await
262 .map_err(|error| TransportError::bind_failed(bind_addr, error))?;
263 self.local_addr = Some(
264 listener
265 .local_addr()
266 .map_err(|error| TransportError::StartFailed(error.to_string()))?,
267 );
268 self.push_task(tokio::spawn(run_accept_loop(
269 self.runtime.clone(),
270 listener,
271 )));
272 }
273
274 for seed_url in self.config.seed_urls.clone() {
275 let addr = TransportAddr::from_string(&seed_url);
276 self.runtime.set_state(&addr, ConnectionState::Connecting);
277 self.push_task(tokio::spawn(run_seed_dialer(self.runtime.clone(), addr)));
278 }
279
280 self.state = TransportState::Up;
281 info!(
282 transport_id = %self.transport_id,
283 local_addr = ?self.local_addr,
284 seeds = self.config.seed_urls.len(),
285 "WebSocket transport started"
286 );
287 Ok(())
288 }
289
290 pub async fn stop_async(&mut self) -> Result<(), TransportError> {
291 if !self.state.is_operational() {
292 return Err(TransportError::NotStarted);
293 }
294 self.runtime.running.store(false, Ordering::Release);
295 let tasks = {
296 let mut tasks = self.tasks.lock().unwrap_or_else(|error| error.into_inner());
297 std::mem::take(&mut *tasks)
298 };
299 for task in &tasks {
300 task.abort();
301 }
302 for task in tasks {
303 let _ = task.await;
304 }
305 self.runtime.pool.lock().await.clear();
306 self.runtime
307 .states
308 .lock()
309 .unwrap_or_else(|error| error.into_inner())
310 .clear();
311 self.local_addr = None;
312 self.state = TransportState::Down;
313 Ok(())
314 }
315
316 pub async fn send_async(
317 &self,
318 addr: &TransportAddr,
319 data: &[u8],
320 ) -> Result<usize, TransportError> {
321 if !self.state.is_operational() {
322 return Err(TransportError::NotStarted);
323 }
324 if data.len() > self.config.max_frame_bytes() {
325 return Err(TransportError::MtuExceeded {
326 packet_size: data.len(),
327 mtu: self.config.max_frame_bytes().min(u16::MAX as usize) as u16,
328 });
329 }
330 validate_websocket_record(data).map_err(TransportError::SendFailed)?;
331 let tx = self
332 .runtime
333 .pool
334 .lock()
335 .await
336 .get(addr)
337 .map(|connection| connection.tx.clone())
338 .ok_or(TransportError::NotStarted)?;
339 tx.try_send(data.to_vec()).map_err(|error| match error {
340 mpsc::error::TrySendError::Full(_) => {
341 self.runtime
342 .stats
343 .send_queue_full
344 .fetch_add(1, Ordering::Relaxed);
345 TransportError::SendFailed("WebSocket send queue full".into())
346 }
347 mpsc::error::TrySendError::Closed(_) => {
348 TransportError::SendFailed("WebSocket connection closed".into())
349 }
350 })?;
351 Ok(data.len())
352 }
353
354 pub async fn connect_async(&self, addr: &TransportAddr) -> Result<(), TransportError> {
355 if !self.state.is_operational() {
356 return Err(TransportError::NotStarted);
357 }
358 let raw = addr
359 .as_str()
360 .ok_or_else(|| TransportError::InvalidAddress(addr.to_string()))?;
361 let candidate = WebSocketConfig {
362 seed_urls: vec![raw.to_owned()],
363 ..self.config.clone()
364 };
365 candidate
366 .validate()
367 .map_err(TransportError::InvalidAddress)?;
368 if self.connection_state_sync(addr) == ConnectionState::Connected {
369 return Ok(());
370 }
371 {
372 let mut states = self
373 .runtime
374 .states
375 .lock()
376 .unwrap_or_else(|error| error.into_inner());
377 if matches!(states.get(addr), Some(ConnectionState::Connecting)) {
378 return Ok(());
379 }
380 states.insert(addr.clone(), ConnectionState::Connecting);
381 }
382 self.push_task(tokio::spawn(run_one_shot_dial(
383 self.runtime.clone(),
384 addr.clone(),
385 )));
386 Ok(())
387 }
388
389 pub fn connection_state_sync(&self, addr: &TransportAddr) -> ConnectionState {
390 if let Ok(pool) = self.runtime.pool.try_lock()
391 && pool.contains_key(addr)
392 {
393 return ConnectionState::Connected;
394 }
395 self.runtime
396 .states
397 .lock()
398 .unwrap_or_else(|error| error.into_inner())
399 .get(addr)
400 .cloned()
401 .unwrap_or(ConnectionState::None)
402 }
403
404 pub async fn close_connection_async(&self, addr: &TransportAddr) {
405 self.runtime.pool.lock().await.remove(addr);
406 self.runtime
407 .states
408 .lock()
409 .unwrap_or_else(|error| error.into_inner())
410 .remove(addr);
411 }
412
413 pub(crate) async fn restart_after_network_change(&mut self) -> Result<bool, TransportError> {
420 if !(self.state.is_operational() || self.state.can_start()) {
421 return Ok(false);
422 }
423 if self.state.is_operational() {
424 self.runtime.pool.lock().await.clear();
425 self.runtime
426 .states
427 .lock()
428 .unwrap_or_else(|error| error.into_inner())
429 .clear();
430 return Ok(true);
431 }
432 let previous_state = self.state;
433 let previous_local_addr = self.local_addr;
434 match self.start_async().await {
435 Ok(()) => Ok(true),
436 Err(error) => {
437 self.runtime.running.store(false, Ordering::Release);
438 self.local_addr = previous_local_addr;
439 self.state = previous_state;
440 Err(error)
441 }
442 }
443 }
444
445 pub(crate) async fn rollback_network_change_start(
446 &mut self,
447 previous_state: TransportState,
448 ) -> Result<(), TransportError> {
449 if self.state.is_operational() {
450 self.stop_async().await?;
451 }
452 self.state = previous_state;
453 Ok(())
454 }
455}
456
457impl Transport for WebSocketTransport {
458 fn transport_id(&self) -> TransportId {
459 self.transport_id
460 }
461
462 fn transport_type(&self) -> &TransportType {
463 &TransportType::WEBSOCKET
464 }
465
466 fn state(&self) -> TransportState {
467 self.state
468 }
469
470 fn mtu(&self) -> u16 {
471 self.config.mtu()
472 }
473
474 fn start(&mut self) -> Result<(), TransportError> {
475 Err(TransportError::NotSupported(
476 "use start_async() for WebSocket transport".into(),
477 ))
478 }
479
480 fn stop(&mut self) -> Result<(), TransportError> {
481 Err(TransportError::NotSupported(
482 "use stop_async() for WebSocket transport".into(),
483 ))
484 }
485
486 fn send(&self, _addr: &TransportAddr, _data: &[u8]) -> Result<(), TransportError> {
487 Err(TransportError::NotSupported(
488 "use send_async() for WebSocket transport".into(),
489 ))
490 }
491
492 fn discover(&self) -> Result<Vec<DiscoveredPeer>, TransportError> {
493 Ok(self
494 .runtime
495 .discoveries
496 .lock()
497 .unwrap_or_else(|error| error.into_inner())
498 .drain(..)
499 .collect())
500 }
501
502 fn auto_connect(&self) -> bool {
503 true
504 }
505
506 fn accept_connections(&self) -> bool {
507 self.config.accept_connections()
508 }
509}
510
511async fn run_accept_loop(runtime: Runtime, listener: TcpListener) {
512 while runtime.running.load(Ordering::Acquire) {
513 let Ok((stream, peer_addr)) = listener.accept().await else {
514 continue;
515 };
516 let Ok(inbound_permit) = runtime.inbound_slots.clone().try_acquire_owned() else {
517 runtime
518 .stats
519 .connections_rejected
520 .fetch_add(1, Ordering::Relaxed);
521 continue;
522 };
523 let Ok(total_permit) = runtime.total_slots.clone().try_acquire_owned() else {
524 runtime
525 .stats
526 .connections_rejected
527 .fetch_add(1, Ordering::Relaxed);
528 continue;
529 };
530 let accepted_runtime = runtime.clone();
531 tokio::spawn(async move {
532 let _inbound_permit = inbound_permit;
533 let _total_permit = total_permit;
534 if let Err(error) = accept_connection(accepted_runtime, stream, peer_addr).await {
535 debug!(%peer_addr, %error, "WebSocket inbound connection ended");
536 }
537 });
538 }
539}
540
541#[allow(clippy::result_large_err)]
542async fn accept_connection(
543 runtime: Runtime,
544 stream: TcpStream,
545 peer_addr: SocketAddr,
546) -> Result<(), TransportError> {
547 let path = runtime.config.path().to_owned();
548 let callback = move |request: &Request, response: Response| {
549 if request.uri().path() == path {
550 Ok(response)
551 } else {
552 let mut error = ErrorResponse::new(Some("not found".into()));
553 *error.status_mut() = tokio_tungstenite::tungstenite::http::StatusCode::NOT_FOUND;
554 Err(error)
555 }
556 };
557 let websocket =
558 accept_hdr_async_with_config(stream, callback, Some(runtime.websocket_config()))
559 .await
560 .map_err(|_| TransportError::ConnectionRefused)?;
561 let generation = runtime.next_generation();
562 let addr = TransportAddr::from_string(&format!("ws-peer://{peer_addr}/{generation}"));
563 run_connection(
564 runtime,
565 addr,
566 websocket,
567 generation,
568 Direction::Inbound,
569 false,
570 )
571 .await
572}
573
574async fn run_seed_dialer(runtime: Runtime, addr: TransportAddr) {
575 let mut delay_ms = runtime.config.reconnect_initial_ms();
576 while runtime.running.load(Ordering::Acquire) {
577 runtime.set_state(&addr, ConnectionState::Connecting);
578 runtime
579 .stats
580 .reconnect_attempts
581 .fetch_add(1, Ordering::Relaxed);
582 let result = dial_and_run(runtime.clone(), addr.clone()).await;
583 if !runtime.running.load(Ordering::Acquire) {
584 break;
585 }
586 match result {
587 Ok(()) => {
588 delay_ms = runtime.config.reconnect_initial_ms();
589 }
590 Err(error) => {
591 runtime.set_state(&addr, ConnectionState::Failed(error.to_string()));
592 debug!(remote_addr = %addr, %error, "WebSocket seed connection failed");
593 delay_ms = delay_ms
594 .saturating_mul(2)
595 .min(runtime.config.reconnect_max_ms());
596 }
597 }
598 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
599 }
600}
601
602async fn run_one_shot_dial(runtime: Runtime, addr: TransportAddr) {
603 let result = dial_and_run(runtime.clone(), addr.clone()).await;
604 if let Err(error) = result {
605 runtime.set_state(&addr, ConnectionState::Failed(error.to_string()));
606 }
607}
608
609async fn dial_and_run(runtime: Runtime, addr: TransportAddr) -> Result<(), TransportError> {
610 let _slot = runtime
611 .total_slots
612 .clone()
613 .try_acquire_owned()
614 .map_err(|_| TransportError::ConnectionRefused)?;
615 let url = addr
616 .as_str()
617 .ok_or_else(|| TransportError::InvalidAddress(addr.to_string()))?;
618 let connect = connect_async_with_config(url, Some(runtime.websocket_config()), false);
619 let (websocket, _) = tokio::time::timeout(
620 Duration::from_millis(runtime.config.connect_timeout_ms()),
621 connect,
622 )
623 .await
624 .map_err(|_| TransportError::Timeout)?
625 .map_err(|error| TransportError::StartFailed(error.to_string()))?;
626 let generation = runtime.next_generation();
627 run_connection(
628 runtime,
629 addr,
630 websocket,
631 generation,
632 Direction::Outbound,
633 true,
634 )
635 .await
636}
637
638async fn run_connection<S>(
639 runtime: Runtime,
640 addr: TransportAddr,
641 websocket: WebSocketStream<S>,
642 generation: u64,
643 direction: Direction,
644 request_key_hint: bool,
645) -> Result<(), TransportError>
646where
647 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
648{
649 let (mut sink, mut stream) = websocket.split();
650 let (tx, mut rx) = mpsc::channel::<Vec<u8>>(runtime.config.max_send_queue());
651 {
652 let mut pool = runtime.pool.lock().await;
653 if pool.contains_key(&addr) {
654 return Err(TransportError::AlreadyStarted);
655 }
656 pool.insert(addr.clone(), Connection { generation, tx });
657 }
658 runtime.set_state(&addr, ConnectionState::Connected);
659 runtime
660 .stats
661 .connections_opened
662 .fetch_add(1, Ordering::Relaxed);
663
664 let mut pending_nonce = request_key_hint.then(|| rand::rng().random::<u64>());
665 if let Some(nonce) = pending_nonce {
666 sink.send(Message::Binary(
667 LocalKeyHint::Request { nonce }.encode().into(),
668 ))
669 .await
670 .map_err(|error| TransportError::SendFailed(error.to_string()))?;
671 }
672
673 let started = tokio::time::Instant::now();
674 let mut last_received = started;
675 let ping_secs = runtime.config.ping_interval_secs();
676 let idle_secs = runtime.config.idle_timeout_secs();
677 let mut ping = tokio::time::interval(if ping_secs == 0 {
678 Duration::from_secs(24 * 60 * 60)
679 } else {
680 Duration::from_secs(ping_secs)
681 });
682 ping.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
683 let mut check = tokio::time::interval(Duration::from_secs(1));
684 check.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
685
686 enum Event {
687 Outbound(Option<Vec<u8>>),
688 Inbound(Option<Result<Message, tokio_tungstenite::tungstenite::Error>>),
689 Ping,
690 Check,
691 }
692
693 let outcome = loop {
694 let event = tokio::select! {
695 outbound = rx.recv() => Event::Outbound(outbound),
696 inbound = stream.next() => Event::Inbound(inbound),
697 _ = ping.tick(), if ping_secs > 0 => Event::Ping,
698 _ = check.tick() => Event::Check,
699 };
700 match event {
701 Event::Outbound(Some(data)) => {
702 let len = data.len();
703 if let Err(error) = sink.send(Message::Binary(data.into())).await {
704 break Err(TransportError::SendFailed(error.to_string()));
705 }
706 runtime.stats.frames_sent.fetch_add(1, Ordering::Relaxed);
707 runtime
708 .stats
709 .bytes_sent
710 .fetch_add(len as u64, Ordering::Relaxed);
711 }
712 Event::Outbound(None) => break Ok(()),
713 Event::Inbound(Some(Ok(Message::Binary(data)))) => {
714 last_received = tokio::time::Instant::now();
715 if let Some(hint) = LocalKeyHint::decode(&data) {
716 match hint {
717 LocalKeyHint::Request { nonce } => {
718 let reply = LocalKeyHint::Response {
719 nonce,
720 pubkey: runtime.local_pubkey,
721 };
722 if let Err(error) =
723 sink.send(Message::Binary(reply.encode().into())).await
724 {
725 break Err(TransportError::SendFailed(error.to_string()));
726 }
727 }
728 LocalKeyHint::Response { nonce, pubkey }
729 if pending_nonce == Some(nonce) =>
730 {
731 pending_nonce = None;
732 if pubkey != runtime.local_pubkey
733 && let Ok(pubkey) = XOnlyPublicKey::from_slice(&pubkey)
734 {
735 runtime
736 .discoveries
737 .lock()
738 .unwrap_or_else(|error| error.into_inner())
739 .push_back(DiscoveredPeer::with_hint(
740 runtime.transport_id,
741 addr.clone(),
742 pubkey,
743 ));
744 }
745 }
746 LocalKeyHint::Response { .. } => {}
747 }
748 continue;
749 }
750 if data.len() > runtime.config.max_frame_bytes()
751 || validate_websocket_record(&data).is_err()
752 {
753 runtime.stats.invalid_frames.fetch_add(1, Ordering::Relaxed);
754 break Err(TransportError::RecvFailed(
755 "invalid WebSocket FIPS physical record".into(),
756 ));
757 }
758 let len = data.len();
759 let packet = ReceivedPacket::with_timestamp(
760 runtime.transport_id,
761 addr.clone(),
762 PacketBuffer::new(data.to_vec()),
763 now_ms(),
764 );
765 if runtime.packet_tx.send(packet).is_err() {
766 break Err(TransportError::RecvFailed(
767 "node packet channel closed".into(),
768 ));
769 }
770 runtime
771 .stats
772 .frames_received
773 .fetch_add(1, Ordering::Relaxed);
774 runtime
775 .stats
776 .bytes_received
777 .fetch_add(len as u64, Ordering::Relaxed);
778 }
779 Event::Inbound(Some(Ok(Message::Ping(payload)))) => {
780 last_received = tokio::time::Instant::now();
781 if let Err(error) = sink.send(Message::Pong(payload)).await {
782 break Err(TransportError::SendFailed(error.to_string()));
783 }
784 }
785 Event::Inbound(Some(Ok(Message::Pong(_)))) => {
786 last_received = tokio::time::Instant::now();
787 }
788 Event::Inbound(Some(Ok(Message::Close(_))) | None) => break Ok(()),
789 Event::Inbound(Some(Ok(Message::Text(_) | Message::Frame(_)))) => {
790 runtime.stats.invalid_frames.fetch_add(1, Ordering::Relaxed);
791 break Err(TransportError::RecvFailed(
792 "WebSocket transport accepts binary messages only".into(),
793 ));
794 }
795 Event::Inbound(Some(Err(error))) => {
796 break Err(TransportError::RecvFailed(error.to_string()));
797 }
798 Event::Ping => {
799 if let Err(error) = sink.send(Message::Ping(Bytes::new())).await {
800 break Err(TransportError::SendFailed(error.to_string()));
801 }
802 }
803 Event::Check => {
804 if pending_nonce.is_some()
805 && started.elapsed()
806 >= Duration::from_millis(runtime.config.key_hint_timeout_ms())
807 {
808 break Err(TransportError::Timeout);
809 }
810 if idle_secs > 0 && last_received.elapsed() >= Duration::from_secs(idle_secs) {
811 break Err(TransportError::Timeout);
812 }
813 }
814 }
815 };
816
817 {
818 let mut pool = runtime.pool.lock().await;
819 if pool
820 .get(&addr)
821 .is_some_and(|connection| connection.generation == generation)
822 {
823 pool.remove(&addr);
824 }
825 }
826 runtime.clear_state_if(&addr, generation);
827 runtime
828 .stats
829 .connections_closed
830 .fetch_add(1, Ordering::Relaxed);
831 debug!(
832 transport_id = %runtime.transport_id,
833 remote_addr = %addr,
834 ?direction,
835 "WebSocket physical connection closed"
836 );
837 outcome
838}
839
840fn now_ms() -> u64 {
841 SystemTime::now()
842 .duration_since(UNIX_EPOCH)
843 .unwrap_or_default()
844 .as_millis() as u64
845}
846
847fn validate_websocket_record(data: &[u8]) -> Result<(), String> {
848 if validate_direct_fsp_transport_fragment(data) {
849 return Ok(());
850 }
851 validate_stream_record(data).map_err(|error| format!("invalid FIPS physical record: {error}"))
852}
853
854#[cfg(test)]
855mod tests;