sockudo_ws/
lib.rs

1//! # Sockudo-WS: Ultra-low latency WebSocket library
2//!
3//! A high-performance WebSocket library designed for HFT applications,
4//! fully compatible with Tokio and Axum.
5//!
6//! ## Performance Features
7//!
8//! - **SIMD Acceleration**: AVX2/AVX-512/NEON for frame masking and UTF-8 validation
9//! - **Zero-Copy Parsing**: Direct buffer access without intermediate copies
10//! - **Write Batching (Corking)**: Minimizes syscalls via vectored I/O
11//! - **Cache-Line Alignment**: Prevents false sharing in concurrent scenarios
12//! - **Lock-Free Queues**: SPSC/MPMC for cross-task communication
13//! - **Optional mimalloc**: High-performance allocator for reduced latency
14//!
15//! ## Example with Axum
16//!
17//! ```ignore
18//! use axum::{Router, routing::get};
19//! use sockudo_ws::axum::WebSocketUpgrade;
20//!
21//! async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
22//!     ws.on_upgrade(|socket| async move {
23//!         // Handle WebSocket connection
24//!     })
25//! }
26//!
27//! let app = Router::new().route("/ws", get(ws_handler));
28//! ```
29//!
30//! ## HTTP/2 and HTTP/3 WebSocket Support
31//!
32//! ```ignore
33//! use sockudo_ws::{WebSocketServer, WebSocketClient, Http2, Http3, Config};
34//!
35//! // HTTP/2 server
36//! let server = WebSocketServer::<Http2>::new(Config::default());
37//! server.serve(tls_stream, |ws, req| async move {
38//!     // handle connection
39//! }).await?;
40//!
41//! // HTTP/3 server
42//! let server = WebSocketServer::<Http3>::bind(addr, tls_config, Config::default()).await?;
43//! server.serve(|ws, req| async move {
44//!     // handle connection
45//! }).await?;
46//! ```
47
48#![allow(dead_code)]
49#![allow(clippy::missing_safety_doc)]
50
51// Use mimalloc as the global allocator when the feature is enabled
52#[cfg(feature = "mimalloc")]
53#[global_allocator]
54static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
55
56pub mod cork;
57pub mod error;
58pub mod frame;
59pub mod handshake;
60pub mod mask;
61pub mod protocol;
62pub mod pubsub;
63pub mod queue;
64pub mod simd;
65pub mod stream;
66pub mod utf8;
67
68// Transport and Extended CONNECT modules
69#[cfg(any(feature = "http2", feature = "http3"))]
70pub mod extended_connect;
71pub mod transport;
72
73pub mod server;
74
75pub mod client;
76
77#[cfg(any(feature = "http2", feature = "http3"))]
78pub mod multiplex;
79
80#[cfg(feature = "permessage-deflate")]
81pub mod deflate;
82
83#[cfg(feature = "permessage-deflate")]
84pub mod compression;
85
86#[cfg(feature = "axum-integration")]
87pub mod axum_integration;
88
89#[cfg(feature = "http2")]
90pub mod http2;
91
92#[cfg(feature = "http3")]
93pub mod http3;
94
95#[cfg(all(feature = "io-uring", target_os = "linux"))]
96pub mod io_uring;
97
98// Core re-exports
99pub use error::{Error, Result};
100pub use frame::{Frame, OpCode};
101pub use handshake::HandshakeResult;
102pub use protocol::{Message, Role};
103pub use pubsub::{PubSub, PubSubState, PublishResult, SubscriberId};
104pub use stream::{SplitReader, SplitWriter, Stream, WebSocketStream};
105
106#[cfg(feature = "permessage-deflate")]
107pub use stream::CompressedWebSocketStream;
108
109// Transport re-exports
110pub use transport::{Http1, Http2, Http3, Transport};
111
112// Extended CONNECT re-exports (for HTTP/2 and HTTP/3)
113#[cfg(any(feature = "http2", feature = "http3"))]
114pub use extended_connect::{
115    ExtendedConnectConfig, ExtendedConnectRequest, ExtendedConnectResponse,
116};
117#[cfg(any(feature = "http2", feature = "http3"))]
118pub use extended_connect::{build_extended_connect_error, build_extended_connect_response};
119
120// Server/Client re-exports
121#[cfg(any(feature = "http2", feature = "http3"))]
122pub use server::WebSocketServer;
123
124#[cfg(any(feature = "http2", feature = "http3"))]
125pub use client::WebSocketClient;
126
127#[cfg(any(feature = "http2", feature = "http3"))]
128pub use multiplex::MultiplexedConnection;
129
130// Re-export config types at top level for convenience
131
132#[cfg(feature = "permessage-deflate")]
133pub use compression::{CompressionContext, SharedCompressorPool, global_shared_pool};
134#[cfg(feature = "permessage-deflate")]
135pub use deflate::{DeflateConfig, DeflateContext};
136#[cfg(feature = "permessage-deflate")]
137pub use protocol::CompressedProtocol;
138
139/// Cache line size for modern CPUs (64 bytes for x86_64, ARM64)
140pub const CACHE_LINE_SIZE: usize = 64;
141
142/// Default cork buffer size (16KB like uWebSockets)
143pub const CORK_BUFFER_SIZE: usize = 16 * 1024;
144
145/// Default receive buffer size (64KB for high throughput)
146pub const RECV_BUFFER_SIZE: usize = 64 * 1024;
147
148/// Maximum WebSocket frame header size (2 + 8 + 4 = 14 bytes)
149pub const MAX_FRAME_HEADER_SIZE: usize = 14;
150
151/// Small message threshold for fast-path optimization (< 126 bytes uses 2-byte header)
152pub const SMALL_MESSAGE_THRESHOLD: usize = 125;
153
154/// Medium message threshold (< 64KB uses 4-byte header)
155pub const MEDIUM_MESSAGE_THRESHOLD: usize = 65535;
156
157/// WebSocket GUID for handshake
158pub const WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
159
160// ============================================================================
161// Transport-specific configurations
162// ============================================================================
163
164/// HTTP/2 configuration (RFC 8441)
165#[cfg(feature = "http2")]
166#[derive(Debug, Clone)]
167pub struct Http2Config {
168    /// Initial stream-level flow control window size (default: 1MB)
169    pub initial_stream_window_size: u32,
170    /// Initial connection-level flow control window size (default: 2MB)
171    pub initial_connection_window_size: u32,
172    /// Maximum concurrent streams per connection (default: 100)
173    pub max_concurrent_streams: u32,
174    /// Enable Extended CONNECT protocol for WebSocket (default: true)
175    pub enable_connect_protocol: bool,
176}
177
178#[cfg(feature = "http2")]
179impl Default for Http2Config {
180    fn default() -> Self {
181        Self {
182            initial_stream_window_size: 1024 * 1024,         // 1MB
183            initial_connection_window_size: 2 * 1024 * 1024, // 2MB
184            max_concurrent_streams: 100,
185            enable_connect_protocol: true,
186        }
187    }
188}
189
190/// HTTP/3 configuration (RFC 9220)
191#[cfg(feature = "http3")]
192#[derive(Debug, Clone)]
193pub struct Http3Config {
194    /// Maximum idle timeout for QUIC connection in milliseconds (default: 30000)
195    pub max_idle_timeout_ms: u64,
196    /// Initial stream-level flow control window size (default: 1MB)
197    pub initial_stream_window_size: u64,
198    /// Enable 0-RTT for faster reconnection (default: false)
199    pub enable_0rtt: bool,
200    /// Enable Extended CONNECT protocol for WebSocket (default: true)
201    pub enable_connect_protocol: bool,
202    /// Maximum UDP payload size (default: 1350)
203    pub max_udp_payload_size: u16,
204}
205
206#[cfg(feature = "http3")]
207impl Default for Http3Config {
208    fn default() -> Self {
209        Self {
210            max_idle_timeout_ms: 30_000,
211            initial_stream_window_size: 1024 * 1024, // 1MB
212            enable_0rtt: false,
213            enable_connect_protocol: true,
214            max_udp_payload_size: 1350,
215        }
216    }
217}
218
219/// io_uring configuration (Linux only)
220#[cfg(all(feature = "io-uring", target_os = "linux"))]
221#[derive(Debug, Clone)]
222pub struct IoUringConfig {
223    /// Number of registered buffers for zero-copy I/O (default: 64)
224    pub registered_buffer_count: usize,
225    /// Size of each registered buffer in bytes (default: 64KB)
226    pub registered_buffer_size: usize,
227    /// Enable SQPOLL mode for reduced syscalls (default: false)
228    pub sqpoll: bool,
229    /// Number of submission queue entries (default: 256)
230    pub sq_entries: u32,
231}
232
233#[cfg(all(feature = "io-uring", target_os = "linux"))]
234impl Default for IoUringConfig {
235    fn default() -> Self {
236        Self {
237            registered_buffer_count: 64,
238            registered_buffer_size: 64 * 1024, // 64KB
239            sqpoll: false,
240            sq_entries: 256,
241        }
242    }
243}
244
245// ============================================================================
246// Compression
247// ============================================================================
248
249/// Compression mode for WebSocket connections (RFC 7692 permessage-deflate)
250///
251/// This enum controls how compression is handled for WebSocket connections.
252///
253/// Per RFC 7692, the LZ77 sliding window size is limited to 8-15 bits
254/// (256 bytes to 32KB). Larger windows provide better compression but
255/// use more memory per connection.
256///
257/// # Memory Usage per Connection
258///
259/// | Mode | Description | Window Bits | Window Size |
260/// |------|-------------|-------------|-------------|
261/// | `Disabled` | No compression | - | - |
262/// | `Dedicated` | Per-connection compressor | 15 | 32KB |
263/// | `Shared` | Shared compressor pool | 15 | 32KB |
264/// | `Window256B` | Minimal memory | 8 | 256B |
265/// | `Window1KB` | 1KB sliding window | 10 | 1KB |
266/// | `Window2KB` | 2KB sliding window | 11 | 2KB |
267/// | `Window4KB` | 4KB sliding window | 12 | 4KB |
268/// | `Window8KB` | 8KB sliding window | 13 | 8KB |
269/// | `Window16KB` | 16KB sliding window | 14 | 16KB |
270/// | `Window32KB` | 32KB sliding window (max) | 15 | 32KB |
271#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
272pub enum Compression {
273    /// No compression
274    #[default]
275    Disabled,
276    /// Dedicated compressor per connection (32KB window, best compression)
277    Dedicated,
278    /// Shared compressor pool (32KB window, good for many connections)
279    Shared,
280    /// 256 byte sliding window (window_bits=8, minimal memory)
281    Window256B,
282    /// 1KB sliding window (window_bits=10)
283    Window1KB,
284    /// 2KB sliding window (window_bits=11)
285    Window2KB,
286    /// 4KB sliding window (window_bits=12)
287    Window4KB,
288    /// 8KB sliding window (window_bits=13)
289    Window8KB,
290    /// 16KB sliding window (window_bits=14)
291    Window16KB,
292    /// 32KB sliding window (window_bits=15, maximum per RFC 7692)
293    Window32KB,
294}
295
296impl Compression {
297    /// Returns true if compression is enabled
298    #[inline]
299    pub fn is_enabled(&self) -> bool {
300        !matches!(self, Compression::Disabled)
301    }
302
303    /// Returns true if this mode uses shared compression
304    #[inline]
305    pub fn is_shared(&self) -> bool {
306        matches!(self, Compression::Shared)
307    }
308
309    /// Returns true if this mode uses dedicated per-connection compression
310    #[inline]
311    pub fn is_dedicated(&self) -> bool {
312        !matches!(self, Compression::Disabled | Compression::Shared)
313    }
314
315    /// Get the window bits for this compression mode
316    ///
317    /// Returns the LZ77 window bits (8-15) for RFC 7692 compliance.
318    #[inline]
319    pub fn window_bits(&self) -> u8 {
320        match self {
321            Compression::Disabled => 0,
322            Compression::Window256B => 8,
323            Compression::Window1KB => 10,
324            Compression::Window2KB => 11,
325            Compression::Window4KB => 12,
326            Compression::Window8KB => 13,
327            Compression::Window16KB => 14,
328            Compression::Dedicated | Compression::Shared | Compression::Window32KB => 15,
329        }
330    }
331
332    /// Get the compression threshold for this mode
333    ///
334    /// Messages smaller than this threshold won't be compressed.
335    /// Larger window modes benefit more from compressing smaller messages.
336    #[inline]
337    pub fn compression_threshold(&self) -> usize {
338        match self {
339            Compression::Disabled => usize::MAX,
340            Compression::Window256B => 128,
341            Compression::Window1KB => 64,
342            Compression::Window2KB => 48,
343            Compression::Window4KB => 40,
344            Compression::Window8KB => 32,
345            Compression::Window16KB | Compression::Window32KB => 24,
346            Compression::Dedicated | Compression::Shared => 16,
347        }
348    }
349
350    /// Whether to use context takeover (preserve compression dictionary between messages)
351    ///
352    /// Smaller window modes disable context takeover to reduce memory.
353    /// Shared mode also disables context takeover since the encoder pool
354    /// cannot maintain context across different connections.
355    /// Larger dedicated modes enable it for better compression across messages.
356    #[inline]
357    pub fn context_takeover(&self) -> bool {
358        !matches!(
359            self,
360            Compression::Disabled
361                | Compression::Shared
362                | Compression::Window256B
363                | Compression::Window1KB
364                | Compression::Window2KB
365        )
366    }
367
368    /// Convert to DeflateConfig
369    #[cfg(feature = "permessage-deflate")]
370    pub fn to_deflate_config(&self) -> Option<crate::deflate::DeflateConfig> {
371        if !self.is_enabled() {
372            return None;
373        }
374
375        let window_bits = self.window_bits();
376        let no_context_takeover = !self.context_takeover();
377
378        Some(crate::deflate::DeflateConfig {
379            server_max_window_bits: window_bits,
380            client_max_window_bits: window_bits,
381            server_no_context_takeover: no_context_takeover,
382            client_no_context_takeover: no_context_takeover,
383            compression_level: match self {
384                Compression::Window256B | Compression::Window1KB => 1, // Fast for small windows
385                Compression::Window2KB | Compression::Window4KB => 3,  // Balanced
386                Compression::Window8KB | Compression::Window16KB => 5, // Good compression
387                _ => 6,                                                // Best for 32KB
388            },
389            compression_threshold: self.compression_threshold(),
390        })
391    }
392}
393
394/// Configuration for WebSocket connections
395///
396/// Mirrors uWebSockets configuration options for familiarity.
397///
398/// # Example
399///
400/// ```
401/// use sockudo_ws::{Config, Compression};
402///
403/// let config = Config::builder()
404///     .compression(Compression::Shared)
405///     .max_payload_length(16 * 1024)
406///     .idle_timeout(10)
407///     .max_backpressure(1024 * 1024)
408///     .build();
409/// ```
410#[derive(Debug, Clone)]
411pub struct Config {
412    /// Maximum message size (default: 64MB)
413    /// Equivalent to uWS maxPayloadLength
414    pub max_message_size: usize,
415    /// Maximum frame size (default: 16MB)
416    pub max_frame_size: usize,
417    /// Write buffer size for corking (default: 16KB)
418    pub write_buffer_size: usize,
419    /// Compression mode (default: Disabled)
420    pub compression: Compression,
421    /// Idle timeout in seconds (default: 120, 0 = disabled)
422    /// Connection is closed if no data received within this time
423    pub idle_timeout: u32,
424    /// Maximum backpressure in bytes before dropping connection (default: 1MB)
425    /// If write buffer exceeds this, connection is closed
426    pub max_backpressure: usize,
427    /// Send pings automatically to keep connection alive (default: true)
428    pub auto_ping: bool,
429    /// Ping interval in seconds (default: 30)
430    pub ping_interval: u32,
431    /// Per-message deflate configuration (requires `permessage-deflate` feature)
432    #[cfg(feature = "permessage-deflate")]
433    pub deflate: Option<crate::deflate::DeflateConfig>,
434
435    // Transport-specific configurations
436    /// HTTP/2 configuration (requires `http2` feature)
437    #[cfg(feature = "http2")]
438    pub http2: Http2Config,
439    /// HTTP/3 configuration (requires `http3` feature)
440    #[cfg(feature = "http3")]
441    pub http3: Http3Config,
442    /// io_uring configuration (requires `io-uring` feature, Linux only)
443    #[cfg(all(feature = "io-uring", target_os = "linux"))]
444    pub io_uring: IoUringConfig,
445}
446
447impl Default for Config {
448    fn default() -> Self {
449        Self {
450            max_message_size: 64 * 1024 * 1024,
451            max_frame_size: 16 * 1024 * 1024,
452            write_buffer_size: CORK_BUFFER_SIZE,
453            compression: Compression::Disabled,
454            idle_timeout: 120,
455            max_backpressure: 1024 * 1024,
456            auto_ping: true,
457            ping_interval: 30,
458            #[cfg(feature = "permessage-deflate")]
459            deflate: None,
460            #[cfg(feature = "http2")]
461            http2: Http2Config::default(),
462            #[cfg(feature = "http3")]
463            http3: Http3Config::default(),
464            #[cfg(all(feature = "io-uring", target_os = "linux"))]
465            io_uring: IoUringConfig::default(),
466        }
467    }
468}
469
470impl Config {
471    /// Create a new config builder
472    pub fn builder() -> ConfigBuilder {
473        ConfigBuilder::new()
474    }
475
476    /// Create config with uWebSockets-style defaults
477    pub fn uws_defaults() -> Self {
478        Self {
479            max_message_size: 16 * 1024,
480            max_frame_size: 16 * 1024,
481            write_buffer_size: CORK_BUFFER_SIZE,
482            compression: Compression::Shared,
483            idle_timeout: 10,
484            max_backpressure: 1024 * 1024,
485            auto_ping: true,
486            ping_interval: 30,
487            #[cfg(feature = "permessage-deflate")]
488            deflate: None,
489            #[cfg(feature = "http2")]
490            http2: Http2Config::default(),
491            #[cfg(feature = "http3")]
492            http3: Http3Config::default(),
493            #[cfg(all(feature = "io-uring", target_os = "linux"))]
494            io_uring: IoUringConfig::default(),
495        }
496    }
497}
498
499/// Builder for WebSocket configuration
500#[derive(Debug, Clone)]
501pub struct ConfigBuilder {
502    config: Config,
503}
504
505impl ConfigBuilder {
506    /// Create a new builder with default values
507    pub fn new() -> Self {
508        Self {
509            config: Config::default(),
510        }
511    }
512
513    /// Set compression mode
514    pub fn compression(mut self, compression: Compression) -> Self {
515        self.config.compression = compression;
516        self
517    }
518
519    /// Set maximum payload/message length (uWS: maxPayloadLength)
520    pub fn max_payload_length(mut self, size: usize) -> Self {
521        self.config.max_message_size = size;
522        self.config.max_frame_size = size;
523        self
524    }
525
526    /// Set maximum message size
527    pub fn max_message_size(mut self, size: usize) -> Self {
528        self.config.max_message_size = size;
529        self
530    }
531
532    /// Set maximum frame size
533    pub fn max_frame_size(mut self, size: usize) -> Self {
534        self.config.max_frame_size = size;
535        self
536    }
537
538    /// Set idle timeout in seconds (uWS: idleTimeout)
539    /// Set to 0 to disable
540    pub fn idle_timeout(mut self, seconds: u32) -> Self {
541        self.config.idle_timeout = seconds;
542        self
543    }
544
545    /// Set maximum backpressure before dropping connection (uWS: maxBackpressure)
546    pub fn max_backpressure(mut self, bytes: usize) -> Self {
547        self.config.max_backpressure = bytes;
548        self
549    }
550
551    /// Set write buffer size for corking
552    pub fn write_buffer_size(mut self, size: usize) -> Self {
553        self.config.write_buffer_size = size;
554        self
555    }
556
557    /// Enable or disable auto ping
558    pub fn auto_ping(mut self, enabled: bool) -> Self {
559        self.config.auto_ping = enabled;
560        self
561    }
562
563    /// Set ping interval in seconds
564    pub fn ping_interval(mut self, seconds: u32) -> Self {
565        self.config.ping_interval = seconds;
566        self
567    }
568
569    // ========================================================================
570    // Per-Message Deflate Configuration Methods
571    // ========================================================================
572
573    /// Enable per-message deflate compression with default configuration
574    #[cfg(feature = "permessage-deflate")]
575    pub fn enable_deflate(mut self) -> Self {
576        self.config.deflate = Some(crate::deflate::DeflateConfig::default());
577        self
578    }
579
580    /// Set per-message deflate configuration
581    #[cfg(feature = "permessage-deflate")]
582    pub fn deflate_config(mut self, config: crate::deflate::DeflateConfig) -> Self {
583        self.config.deflate = Some(config);
584        self
585    }
586
587    // ========================================================================
588    // HTTP/2 Configuration Methods
589    // ========================================================================
590
591    /// Set HTTP/2 initial stream window size
592    #[cfg(feature = "http2")]
593    pub fn http2_stream_window_size(mut self, size: u32) -> Self {
594        self.config.http2.initial_stream_window_size = size;
595        self
596    }
597
598    /// Set HTTP/2 initial connection window size
599    #[cfg(feature = "http2")]
600    pub fn http2_connection_window_size(mut self, size: u32) -> Self {
601        self.config.http2.initial_connection_window_size = size;
602        self
603    }
604
605    /// Set HTTP/2 maximum concurrent streams
606    #[cfg(feature = "http2")]
607    pub fn http2_max_streams(mut self, count: u32) -> Self {
608        self.config.http2.max_concurrent_streams = count;
609        self
610    }
611
612    /// Enable or disable HTTP/2 Extended CONNECT protocol (RFC 8441)
613    #[cfg(feature = "http2")]
614    pub fn http2_enable_connect_protocol(mut self, enabled: bool) -> Self {
615        self.config.http2.enable_connect_protocol = enabled;
616        self
617    }
618
619    // ========================================================================
620    // HTTP/3 Configuration Methods
621    // ========================================================================
622
623    /// Set HTTP/3 maximum idle timeout in milliseconds
624    #[cfg(feature = "http3")]
625    pub fn http3_idle_timeout(mut self, ms: u64) -> Self {
626        self.config.http3.max_idle_timeout_ms = ms;
627        self
628    }
629
630    /// Set HTTP/3 initial stream window size
631    #[cfg(feature = "http3")]
632    pub fn http3_stream_window_size(mut self, size: u64) -> Self {
633        self.config.http3.initial_stream_window_size = size;
634        self
635    }
636
637    /// Enable or disable HTTP/3 0-RTT
638    #[cfg(feature = "http3")]
639    pub fn http3_enable_0rtt(mut self, enabled: bool) -> Self {
640        self.config.http3.enable_0rtt = enabled;
641        self
642    }
643
644    /// Enable or disable HTTP/3 Extended CONNECT protocol (RFC 9220)
645    #[cfg(feature = "http3")]
646    pub fn http3_enable_connect_protocol(mut self, enabled: bool) -> Self {
647        self.config.http3.enable_connect_protocol = enabled;
648        self
649    }
650
651    /// Set HTTP/3 maximum UDP payload size
652    #[cfg(feature = "http3")]
653    pub fn http3_max_udp_payload_size(mut self, size: u16) -> Self {
654        self.config.http3.max_udp_payload_size = size;
655        self
656    }
657
658    // ========================================================================
659    // io_uring Configuration Methods
660    // ========================================================================
661
662    /// Set io_uring registered buffer count and size
663    #[cfg(all(feature = "io-uring", target_os = "linux"))]
664    pub fn io_uring_buffers(mut self, count: usize, size: usize) -> Self {
665        self.config.io_uring.registered_buffer_count = count;
666        self.config.io_uring.registered_buffer_size = size;
667        self
668    }
669
670    /// Enable or disable io_uring SQPOLL mode
671    #[cfg(all(feature = "io-uring", target_os = "linux"))]
672    pub fn io_uring_sqpoll(mut self, enabled: bool) -> Self {
673        self.config.io_uring.sqpoll = enabled;
674        self
675    }
676
677    /// Set io_uring submission queue entries
678    #[cfg(all(feature = "io-uring", target_os = "linux"))]
679    pub fn io_uring_sq_entries(mut self, entries: u32) -> Self {
680        self.config.io_uring.sq_entries = entries;
681        self
682    }
683
684    /// Build the configuration
685    pub fn build(self) -> Config {
686        self.config
687    }
688}
689
690impl Default for ConfigBuilder {
691    fn default() -> Self {
692        Self::new()
693    }
694}
695
696/// Prelude module for convenient imports
697pub mod prelude {
698    pub use crate::Config;
699    pub use crate::error::{Error, Result};
700    pub use crate::frame::{Frame, OpCode};
701    pub use crate::protocol::{Message, Role};
702    pub use crate::pubsub::{PubSub, PublishResult, SubscriberId};
703    pub use crate::stream::WebSocketStream;
704    pub use crate::transport::{Http1, Http2, Http3, Transport};
705
706    #[cfg(any(feature = "http2", feature = "http3"))]
707    pub use crate::extended_connect::ExtendedConnectRequest;
708
709    #[cfg(any(feature = "http2", feature = "http3"))]
710    pub use crate::server::WebSocketServer;
711
712    #[cfg(any(feature = "http2", feature = "http3"))]
713    pub use crate::client::WebSocketClient;
714
715    #[cfg(any(feature = "http2", feature = "http3"))]
716    pub use crate::multiplex::MultiplexedConnection;
717}