Skip to main content

monocoque_core/
options.rs

1//! Socket configuration options
2//!
3//! This module provides configuration options for `ZeroMQ` sockets, similar to
4//! libzmq's socket options (`zmq_setsockopt/zmq_getsockopt`).
5
6use std::time::Duration;
7
8/// Socket configuration options.
9///
10/// These options control socket behavior including timeouts, buffer sizes,
11/// and reliability features. This struct consolidates all socket configuration
12/// in one place, following the `MongoDB` Rust driver pattern.
13///
14/// # Examples
15///
16/// ```
17/// use monocoque_core::options::SocketOptions;
18/// use std::time::Duration;
19///
20/// // Simple case: use defaults
21/// let opts = SocketOptions::default();
22///
23/// // Customize timeouts and buffers
24/// let opts = SocketOptions::default()
25///     .with_recv_timeout(Duration::from_secs(5))
26///     .with_send_timeout(Duration::from_secs(5))
27///     .with_buffer_sizes(16384, 16384);  // 16KB buffers for high-throughput
28/// ```
29#[derive(Debug, Clone)]
30pub struct SocketOptions {
31    /// Read buffer size (bytes)
32    ///
33    /// Size of arena-allocated buffer for receiving data.
34    /// - Default: 8192 (8KB) - balanced for most workloads
35    /// - Small (4KB): Low-latency with small messages (< 1KB)
36    /// - Large (16KB): High-throughput with large messages (> 8KB)
37    pub read_buffer_size: usize,
38
39    /// Write buffer size (bytes)
40    ///
41    /// Initial capacity of `BytesMut` buffer for sending data.
42    /// - Default: 8192 (8KB) - balanced for most workloads
43    /// - Small (4KB): Low-latency with small messages
44    /// - Large (16KB): High-throughput with large messages
45    pub write_buffer_size: usize,
46
47    /// Receive timeout (`ZMQ_RCVTIMEO`)
48    ///
49    /// Maximum time to wait for a receive operation.
50    /// - `None`: Block indefinitely (default)
51    /// - `Some(Duration::ZERO)`: Non-blocking (return immediately with EAGAIN)
52    /// - `Some(duration)`: Wait up to duration before returning EAGAIN
53    pub recv_timeout: Option<Duration>,
54
55    /// Send timeout (`ZMQ_SNDTIMEO`)
56    ///
57    /// Maximum time to wait for a send operation.
58    /// - `None`: Block indefinitely (default)
59    /// - `Some(Duration::ZERO)`: Non-blocking (return immediately with EAGAIN)
60    /// - `Some(duration)`: Wait up to duration before returning EAGAIN
61    pub send_timeout: Option<Duration>,
62
63    /// Handshake timeout (`ZMQ_HANDSHAKE_IVL`)
64    ///
65    /// Maximum time to complete ZMTP handshake after connection.
66    /// - Default: 30 seconds
67    /// - Set to `Duration::ZERO` to disable timeout
68    pub handshake_timeout: Duration,
69
70    /// Linger timeout (`ZMQ_LINGER`)
71    ///
72    /// Time to wait for pending messages to be sent before closing socket.
73    /// - `None`: Close immediately, discard pending messages
74    /// - `Some(Duration::ZERO)`: Same as None
75    /// - `Some(duration)`: Wait up to duration for messages to be sent
76    pub linger: Option<Duration>,
77
78    /// Reconnect interval (`ZMQ_RECONNECT_IVL`)
79    ///
80    /// Initial reconnection delay after connection loss.
81    /// - Default: 100ms
82    /// - Use with `reconnect_ivl_max` for exponential backoff
83    pub reconnect_ivl: Duration,
84
85    /// Maximum reconnect interval (`ZMQ_RECONNECT_IVL_MAX`)
86    ///
87    /// Maximum reconnection delay for exponential backoff.
88    /// - Default: 0 (no maximum, use `reconnect_ivl` always)
89    /// - When > 0: Doubles `reconnect_ivl` up to this value
90    pub reconnect_ivl_max: Duration,
91
92    /// Connection timeout (`ZMQ_CONNECT_TIMEOUT`)
93    ///
94    /// Maximum time to wait for TCP connection to complete.
95    /// - Default: 0 (use OS default)
96    pub connect_timeout: Duration,
97
98    /// High water mark for receiving (`ZMQ_RCVHWM`)
99    ///
100    /// Maximum number of messages to queue for receiving.
101    /// When reached, socket will block or drop messages depending on socket type.
102    /// - Default: 1000 messages
103    pub recv_hwm: usize,
104
105    /// High water mark for sending (`ZMQ_SNDHWM`)
106    ///
107    /// Maximum number of messages to queue for sending.
108    /// When reached, socket will block or drop messages depending on socket type.
109    /// - Default: 1000 messages
110    pub send_hwm: usize,
111
112    /// Enable immediate connect mode (`ZMQ_IMMEDIATE`)
113    ///
114    /// - `false` (default): Queue messages while connecting
115    /// - `true`: Report error if no connection established
116    pub immediate: bool,
117
118    /// Maximum message size (`ZMQ_MAXMSGSIZE`)
119    ///
120    /// Maximum size of a single message in bytes.
121    /// - `None`: No limit (default)
122    /// - `Some(size)`: Reject messages larger than size
123    pub max_msg_size: Option<usize>,
124
125    /// Socket identity / routing ID (`ZMQ_ROUTING_ID` / `ZMQ_IDENTITY`)
126    ///
127    /// Identity for ROUTER addressing. If None, a random UUID is generated.
128    /// - Default: None (auto-generate)
129    /// - Custom: Set for stable identity across reconnections
130    pub routing_id: Option<bytes::Bytes>,
131
132    /// Connect routing ID (`ZMQ_CONNECT_ROUTING_ID`)
133    ///
134    /// Identity to assign to the next outgoing connection.
135    /// Used by ROUTER sockets to assign a specific identity to a peer.
136    /// - Default: None (auto-generate)
137    /// - Custom: Assign explicit identity to next connection
138    /// - Consumed after each connect operation
139    pub connect_routing_id: Option<bytes::Bytes>,
140
141    /// ROUTER mandatory mode (`ZMQ_ROUTER_MANDATORY`)
142    ///
143    /// - `false` (default): Silently drop messages to unknown peers
144    /// - `true`: Return error when sending to unknown peer
145    pub router_mandatory: bool,
146
147    /// ROUTER handover mode (`ZMQ_ROUTER_HANDOVER`)
148    ///
149    /// - `false` (default): Disconnect old peer when new peer with same identity connects
150    /// - `true`: Hand over pending messages to new peer with same identity
151    pub router_handover: bool,
152
153    /// Probe ROUTER on connect (`ZMQ_PROBE_ROUTER`)
154    ///
155    /// - `false` (default): Normal operation
156    /// - `true`: Send empty message on connect to probe ROUTER identity
157    pub probe_router: bool,
158
159    /// XPUB verbose mode (`ZMQ_XPUB_VERBOSE`)
160    ///
161    /// - `false` (default): Only report new subscriptions
162    /// - `true`: Report all subscription messages (including duplicates)
163    pub xpub_verbose: bool,
164
165    /// XPUB manual mode (`ZMQ_XPUB_MANUAL`)
166    ///
167    /// - `false` (default): Automatic subscription management
168    /// - `true`: Manual subscription control via `send()`
169    pub xpub_manual: bool,
170
171    /// XPUB welcome message (`ZMQ_XPUB_WELCOME_MSG`)
172    ///
173    /// Message to send to new subscribers on connection.
174    /// Useful for last value cache (LVC) patterns.
175    pub xpub_welcome_msg: Option<bytes::Bytes>,
176
177    /// XSUB verbose unsubscribe (`ZMQ_XSUB_VERBOSE_UNSUBSCRIBE`)
178    ///
179    /// - `false` (default): Don't send explicit unsubscribe messages
180    /// - `true`: Send unsubscribe messages upstream
181    pub xsub_verbose_unsubs: bool,
182
183    /// Conflate messages (`ZMQ_CONFLATE`)
184    ///
185    /// - `false` (default): Queue all messages
186    /// - `true`: Keep only last message (overwrite queue)
187    pub conflate: bool,
188
189    /// TCP keepalive (`ZMQ_TCP_KEEPALIVE`)
190    ///
191    /// - `-1` (default): Use OS default
192    /// - `0`: Disable TCP keepalive
193    /// - `1`: Enable TCP keepalive
194    pub tcp_keepalive: i32,
195
196    /// TCP keepalive count (`ZMQ_TCP_KEEPALIVE_CNT`)
197    ///
198    /// Number of keepalive probes before considering connection dead.
199    /// - `-1` (default): Use OS default
200    /// - `> 0`: Number of probes
201    pub tcp_keepalive_cnt: i32,
202
203    /// TCP keepalive idle (`ZMQ_TCP_KEEPALIVE_IDLE`)
204    ///
205    /// Time in seconds before starting keepalive probes.
206    /// - `-1` (default): Use OS default
207    /// - `> 0`: Idle time in seconds
208    pub tcp_keepalive_idle: i32,
209
210    /// TCP keepalive interval (`ZMQ_TCP_KEEPALIVE_INTVL`)
211    ///
212    /// Time in seconds between keepalive probes.
213    /// - `-1` (default): Use OS default
214    /// - `> 0`: Interval in seconds
215    pub tcp_keepalive_intvl: i32,
216
217    /// REQ correlate mode (`ZMQ_REQ_CORRELATE`)
218    ///
219    /// Match replies to requests using message envelope.
220    /// - `false` (default): Accept any reply
221    /// - `true`: Match reply envelope to request
222    pub req_correlate: bool,
223
224    /// REQ relaxed mode (`ZMQ_REQ_RELAXED`)
225    ///
226    /// Allow multiple outstanding requests without strict alternation.
227    /// - `false` (default): Strict send-recv-send-recv pattern
228    /// - `true`: Allow send-send-recv-recv pattern
229    pub req_relaxed: bool,
230
231    /// Multicast rate in kilobits per second (`ZMQ_RATE`)
232    ///
233    /// Maximum send or receive data rate for multicast transports (PGM/EPGM).
234    /// - Default: 100 kbps
235    pub rate: i32,
236
237    /// Multicast recovery interval (`ZMQ_RECOVERY_IVL`)
238    ///
239    /// Maximum time to recover lost messages on multicast transports.
240    /// - Default: 10 seconds
241    pub recovery_ivl: Duration,
242
243    /// OS-level send buffer size (`ZMQ_SNDBUF`)
244    ///
245    /// Size of kernel send buffer. 0 = OS default.
246    /// - Default: 0 (use OS default)
247    pub sndbuf: i32,
248
249    /// OS-level receive buffer size (`ZMQ_RCVBUF`)
250    ///
251    /// Size of kernel receive buffer. 0 = OS default.
252    /// - Default: 0 (use OS default)
253    pub rcvbuf: i32,
254
255    /// Multicast TTL (`ZMQ_MULTICAST_HOPS`)
256    ///
257    /// Time-to-live for multicast packets.
258    /// - Default: 1 (local network only)
259    pub multicast_hops: i32,
260
261    /// IP Type of Service (`ZMQ_TOS`)
262    ///
263    /// Sets the `ToS` field in IP headers for `QoS`.
264    /// - Default: 0 (normal service)
265    pub tos: i32,
266
267    /// Maximum multicast transmission unit (`ZMQ_MULTICAST_MAXTPDU`)
268    ///
269    /// Maximum transport data unit for multicast.
270    /// - Default: 1500 bytes
271    pub multicast_maxtpdu: i32,
272
273    /// IPv6 support (`ZMQ_IPV6`)
274    ///
275    /// Enable IPv6 on socket.
276    /// - `false` (default): IPv4 only
277    /// - `true`: IPv6 support enabled
278    pub ipv6: bool,
279
280    /// Bind to device (`ZMQ_BINDTODEVICE`)
281    ///
282    /// Bind socket to specific network interface (Linux only).
283    /// - Default: None (bind to all interfaces)
284    pub bind_to_device: Option<String>,
285
286    // --- Security Options ---
287    /// PLAIN server mode (`ZMQ_PLAIN_SERVER`)
288    ///
289    /// Enable PLAIN authentication as server.
290    /// - `false` (default): Client mode
291    /// - `true`: Server mode (validate credentials)
292    pub plain_server: bool,
293
294    /// PLAIN username (`ZMQ_PLAIN_USERNAME`)
295    ///
296    /// Username for PLAIN authentication (client side).
297    /// - Default: None (no authentication)
298    pub plain_username: Option<String>,
299
300    /// PLAIN password (`ZMQ_PLAIN_PASSWORD`)
301    ///
302    /// Password for PLAIN authentication (client side).
303    /// - Default: None (no authentication)
304    pub plain_password: Option<String>,
305
306    /// CURVE server mode (`ZMQ_CURVE_SERVER`)
307    ///
308    /// Enable CURVE encryption as server.
309    /// - `false` (default): Client mode
310    /// - `true`: Server mode (provide server key)
311    pub curve_server: bool,
312
313    /// CURVE public key (`ZMQ_CURVE_PUBLICKEY`)
314    ///
315    /// Local public key for CURVE (32 bytes).
316    /// - Default: None (no encryption)
317    pub curve_publickey: Option<[u8; 32]>,
318
319    /// CURVE secret key (`ZMQ_CURVE_SECRETKEY`)
320    ///
321    /// Local secret key for CURVE (32 bytes).
322    /// - Default: None (no encryption)
323    pub curve_secretkey: Option<[u8; 32]>,
324
325    /// CURVE server key (`ZMQ_CURVE_SERVERKEY`)
326    ///
327    /// Server's public key for CURVE client (32 bytes).
328    /// - Default: None (no encryption)
329    /// - Client must set this to verify server identity
330    pub curve_serverkey: Option<[u8; 32]>,
331
332    /// ZAP domain (`ZMQ_ZAP_DOMAIN`)
333    ///
334    /// Security domain for ZAP authentication.
335    /// - Default: "" (global domain)
336    pub zap_domain: String,
337
338    /// Subscriptions (`ZMQ_SUBSCRIBE`)
339    ///
340    /// Subscription filters for SUB/XSUB sockets.
341    /// - Empty vec: No subscriptions (default) - won't receive any messages
342    /// - vec![b""] or vec![`Bytes::new()`]: Subscribe to all messages
343    /// - vec![b"topic1", b"topic2"]: Subscribe to specific topics
344    ///
345    /// Note: SUB sockets MUST subscribe to at least one topic to receive messages.
346    pub subscriptions: Vec<bytes::Bytes>,
347
348    /// Unsubscriptions (`ZMQ_UNSUBSCRIBE`)
349    ///
350    /// Subscription filters to remove for SUB/XSUB sockets.
351    /// Applied after subscriptions during socket configuration.
352    pub unsubscriptions: Vec<bytes::Bytes>,
353
354    /// Maximum reconnection attempts (`ZMQ_RECONNECT_STOP`)
355    ///
356    /// Maximum number of times to attempt reconnection after a disconnect.
357    /// - `None`: Retry indefinitely (default, matches libzmq behaviour)
358    /// - `Some(n)`: Give up and return `NotConnected` after n attempts
359    pub max_reconnect_attempts: Option<u32>,
360
361    /// ZMTP heartbeat interval (`ZMQ_HEARTBEAT_IVL` = 75)
362    ///
363    /// How often to send PING heartbeat commands on an otherwise idle connection.
364    /// - `None`: Disabled (default)
365    /// - `Some(dur)`: Send PING every `dur` of inactivity
366    pub heartbeat_ivl: Option<Duration>,
367
368    /// ZMTP heartbeat TTL (`ZMQ_HEARTBEAT_TTL` = 76)
369    ///
370    /// Time-to-live for the remote peer's heartbeat (sent in PING command).
371    /// The remote will disconnect if it doesn't receive a heartbeat within this interval.
372    /// - `None`: Use `heartbeat_ivl` (default)
373    /// - `Some(dur)`: Override TTL sent to peer
374    pub heartbeat_ttl: Option<Duration>,
375
376    /// ZMTP heartbeat timeout (`ZMQ_HEARTBEAT_TIMEOUT` = 77)
377    ///
378    /// How long to wait for a PONG reply before considering the connection dead.
379    /// - `None`: Use `heartbeat_ivl` (default)
380    /// - `Some(dur)`: Custom timeout (recommended: 2-5x `heartbeat_ivl`)
381    pub heartbeat_timeout: Option<Duration>,
382
383    /// ROUTER raw mode (`ZMQ_ROUTER_RAW` = 41)
384    ///
385    /// Put ROUTER socket into raw mode (no ZMTP handshake, acts like STREAM).
386    /// - `false` (default): Normal ZMTP routing
387    /// - `true`: Raw TCP bridging mode
388    pub router_raw: bool,
389
390    /// STREAM connect/disconnect notifications (`ZMQ_STREAM_NOTIFY` = 73)
391    ///
392    /// Send empty notification frames on connect and disconnect.
393    /// - `true` (default): Send notification frames
394    /// - `false`: Suppress notification frames
395    pub stream_notify: bool,
396
397    /// XPUB no-drop mode (`ZMQ_XPUB_NODROP` = 69)
398    ///
399    /// - `false` (default): Drop messages silently when HWM is reached
400    /// - `true`: Return error (`EAGAIN`) instead of dropping
401    pub xpub_nodrop: bool,
402
403    /// Invert topic matching (`ZMQ_INVERT_MATCHING` = 74)
404    ///
405    /// Invert the subscription filter logic for PUB/SUB and XPUB/XSUB.
406    /// - `false` (default): Deliver messages matching subscriptions
407    /// - `true`: Deliver messages NOT matching any subscription
408    pub invert_matching: bool,
409
410    /// Write coalescing: batch multiple `send()` calls before writing to the kernel.
411    ///
412    /// When enabled, `send()` accumulates encoded messages in an internal buffer
413    /// and only flushes to the kernel when `write_coalesce_threshold` bytes have
414    /// accumulated or when `flush()` is called explicitly.
415    ///
416    /// - `false` (default): each `send()` writes immediately (lowest latency)
417    /// - `true`: messages are batched (higher throughput for small messages)
418    ///
419    /// Always call `flush()` after the last `send()` in a burst to ensure
420    /// all buffered data reaches the peer.
421    pub write_coalescing: bool,
422
423    /// Byte threshold at which the coalesce buffer is flushed automatically.
424    ///
425    /// Only relevant when `write_coalescing` is enabled. The internal send
426    /// buffer is written to the kernel as a single syscall once it reaches
427    /// this many bytes.
428    ///
429    /// - Default: 65536 (64 KB) - one typical TCP segment on loopback
430    pub write_coalesce_threshold: usize,
431
432    /// Frame-body size at or above which the send path switches to a vectored
433    /// write (`writev`) instead of copying the body into the userspace send
434    /// buffer.
435    ///
436    /// For large frames the body copy into the coalescing buffer is the
437    /// dominant per-byte cost on the core. Above this threshold the frame header
438    /// and the refcounted `Bytes` body are written as an iovec, so the body is
439    /// handed straight to the kernel with no intermediate copy. Small frames
440    /// stay on the copy path, where a single `write` of one contiguous buffer
441    /// beats the per-iovec bookkeeping.
442    ///
443    /// Only applies in eager mode (write coalescing disabled) and when the
444    /// connection is not CURVE-encrypted (encryption must transform the body
445    /// into a fresh buffer regardless).
446    ///
447    /// - Default: 32768 (32 KB) - the measured crossover on loopback below which
448    ///   copying the body into one contiguous buffer beats a two-segment
449    ///   `writev`; tune for your hardware and message sizes
450    pub vectored_write_threshold: usize,
451}
452
453impl Default for SocketOptions {
454    fn default() -> Self {
455        Self {
456            recv_timeout: None, // Block indefinitely
457            send_timeout: None, // Block indefinitely
458            handshake_timeout: Duration::from_secs(30),
459            linger: Some(Duration::from_secs(30)), // Wait 30s for pending messages
460            reconnect_ivl: Duration::from_millis(100),
461            reconnect_ivl_max: Duration::ZERO, // No maximum
462            connect_timeout: Duration::ZERO,   // Use OS default
463            recv_hwm: 1000,
464            send_hwm: 1000,
465            immediate: false,
466            max_msg_size: None,      // No limit
467            read_buffer_size: 8192,  // 8KB - balanced default
468            write_buffer_size: 8192, // 8KB - balanced default
469            routing_id: None,
470            connect_routing_id: None,
471            router_mandatory: false,
472            router_handover: false,
473            probe_router: false,
474            xpub_verbose: false,
475            xpub_manual: false,
476            xpub_welcome_msg: None,
477            xsub_verbose_unsubs: false,
478            conflate: false,
479            tcp_keepalive: -1,       // OS default
480            tcp_keepalive_cnt: -1,   // OS default
481            tcp_keepalive_idle: -1,  // OS default
482            tcp_keepalive_intvl: -1, // OS default
483            req_correlate: false,
484            req_relaxed: false,
485            rate: 100, // 100 kbps
486            recovery_ivl: Duration::from_secs(10),
487            sndbuf: 0,               // OS default
488            rcvbuf: 0,               // OS default
489            multicast_hops: 1,       // Local network only
490            tos: 0,                  // Normal service
491            multicast_maxtpdu: 1500, // Standard MTU
492            ipv6: false,             // IPv4 only
493            bind_to_device: None,    // All interfaces
494            // Security
495            plain_server: false,
496            plain_username: None,
497            plain_password: None,
498            curve_server: false,
499            curve_publickey: None,
500            curve_secretkey: None,
501            curve_serverkey: None,
502            zap_domain: String::new(),    // Global domain
503            subscriptions: Vec::new(),    // No subscriptions
504            unsubscriptions: Vec::new(),  // No unsubscriptions
505            max_reconnect_attempts: None, // Retry indefinitely
506            heartbeat_ivl: None,
507            heartbeat_ttl: None,
508            heartbeat_timeout: None,
509            router_raw: false,
510            stream_notify: true,
511            xpub_nodrop: false,
512            invert_matching: false,
513            write_coalescing: false,
514            write_coalesce_threshold: 65536,
515            vectored_write_threshold: 32768,
516        }
517    }
518}
519
520impl SocketOptions {
521    /// Create new socket options with default values (8KB buffers).
522    #[must_use]
523    pub fn new() -> Self {
524        Self::default()
525    }
526
527    /// Create socket options optimized for small messages (< 1KB).
528    ///
529    /// Sets 4KB buffers, suitable for low-latency request-reply patterns.
530    ///
531    /// # Examples
532    ///
533    /// ```
534    /// use monocoque_core::options::SocketOptions;
535    ///
536    /// let opts = SocketOptions::small();  // 4KB buffers for REQ/REP
537    /// ```
538    #[must_use]
539    pub fn small() -> Self {
540        Self {
541            read_buffer_size: 4096,
542            write_buffer_size: 4096,
543            ..Self::default()
544        }
545    }
546
547    /// Create socket options optimized for large messages (> 8KB).
548    ///
549    /// Sets 16KB buffers, suitable for high-throughput async patterns.
550    ///
551    /// # Examples
552    ///
553    /// ```
554    /// use monocoque_core::options::SocketOptions;
555    ///
556    /// let opts = SocketOptions::large();  // 16KB buffers for DEALER/ROUTER
557    /// ```
558    #[must_use]
559    pub fn large() -> Self {
560        Self {
561            read_buffer_size: 16384,
562            write_buffer_size: 16384,
563            ..Self::default()
564        }
565    }
566
567    /// Set receive timeout.
568    ///
569    /// # Examples
570    ///
571    /// ```
572    /// use monocoque_core::options::SocketOptions;
573    /// use std::time::Duration;
574    ///
575    /// // Non-blocking receive
576    /// let opts = SocketOptions::new().with_recv_timeout(Duration::ZERO);
577    ///
578    /// // 5 second timeout
579    /// let opts = SocketOptions::new().with_recv_timeout(Duration::from_secs(5));
580    /// ```
581    pub const fn with_recv_timeout(mut self, timeout: Duration) -> Self {
582        self.recv_timeout = Some(timeout);
583        self
584    }
585
586    /// Set send timeout.
587    pub const fn with_send_timeout(mut self, timeout: Duration) -> Self {
588        self.send_timeout = Some(timeout);
589        self
590    }
591
592    /// Set handshake timeout.
593    pub const fn with_handshake_timeout(mut self, timeout: Duration) -> Self {
594        self.handshake_timeout = timeout;
595        self
596    }
597
598    /// Set linger timeout.
599    pub const fn with_linger(mut self, linger: Option<Duration>) -> Self {
600        self.linger = linger;
601        self
602    }
603
604    /// Set reconnection interval.
605    pub const fn with_reconnect_ivl(mut self, ivl: Duration) -> Self {
606        self.reconnect_ivl = ivl;
607        self
608    }
609
610    /// Set maximum reconnection interval for exponential backoff.
611    pub const fn with_reconnect_ivl_max(mut self, max: Duration) -> Self {
612        self.reconnect_ivl_max = max;
613        self
614    }
615
616    /// Set maximum number of reconnection attempts.
617    ///
618    /// `None` retries indefinitely (default); `Some(n)` gives up after n attempts.
619    pub const fn with_max_reconnect_attempts(mut self, max: Option<u32>) -> Self {
620        self.max_reconnect_attempts = max;
621        self
622    }
623
624    /// Set connection timeout.
625    pub const fn with_connect_timeout(mut self, timeout: Duration) -> Self {
626        self.connect_timeout = timeout;
627        self
628    }
629
630    /// Set heartbeat interval (`ZMQ_HEARTBEAT_IVL`).
631    pub const fn with_heartbeat_ivl(mut self, ivl: Duration) -> Self {
632        self.heartbeat_ivl = Some(ivl);
633        self
634    }
635
636    /// Set heartbeat TTL (`ZMQ_HEARTBEAT_TTL`).
637    pub const fn with_heartbeat_ttl(mut self, ttl: Duration) -> Self {
638        self.heartbeat_ttl = Some(ttl);
639        self
640    }
641
642    /// Set heartbeat timeout (`ZMQ_HEARTBEAT_TIMEOUT`).
643    pub const fn with_heartbeat_timeout(mut self, timeout: Duration) -> Self {
644        self.heartbeat_timeout = Some(timeout);
645        self
646    }
647
648    /// Enable or disable ROUTER raw mode (`ZMQ_ROUTER_RAW`).
649    pub const fn with_router_raw(mut self, raw: bool) -> Self {
650        self.router_raw = raw;
651        self
652    }
653
654    /// Enable or disable STREAM connect/disconnect notifications (`ZMQ_STREAM_NOTIFY`).
655    pub const fn with_stream_notify(mut self, notify: bool) -> Self {
656        self.stream_notify = notify;
657        self
658    }
659
660    /// Enable XPUB no-drop mode (`ZMQ_XPUB_NODROP`).
661    pub const fn with_xpub_nodrop(mut self, nodrop: bool) -> Self {
662        self.xpub_nodrop = nodrop;
663        self
664    }
665
666    /// Enable inverted topic matching (`ZMQ_INVERT_MATCHING`).
667    pub const fn with_invert_matching(mut self, invert: bool) -> Self {
668        self.invert_matching = invert;
669        self
670    }
671
672    /// Enable or disable write coalescing.
673    ///
674    /// When enabled, consecutive `send()` calls accumulate in an internal buffer
675    /// and are written to the kernel in one syscall, reducing per-message overhead
676    /// for small-message workloads.  Call `flush()` after the last send in a burst.
677    pub const fn with_write_coalescing(mut self, enabled: bool) -> Self {
678        self.write_coalescing = enabled;
679        self
680    }
681
682    /// Set the byte threshold at which the coalesce buffer flushes automatically.
683    pub const fn with_write_coalesce_threshold(mut self, threshold: usize) -> Self {
684        self.write_coalesce_threshold = threshold;
685        self
686    }
687
688    /// Set the frame-body size at or above which the eager send path uses a
689    /// vectored write (`writev`) instead of copying the body into the send
690    /// buffer. See [`SocketOptions::vectored_write_threshold`]. Set to
691    /// `usize::MAX` to disable vectored writes entirely.
692    pub const fn with_vectored_write_threshold(mut self, threshold: usize) -> Self {
693        self.vectored_write_threshold = threshold;
694        self
695    }
696
697    /// Set receive high water mark.
698    pub const fn with_recv_hwm(mut self, hwm: usize) -> Self {
699        self.recv_hwm = hwm;
700        self
701    }
702
703    /// Set send high water mark.
704    pub const fn with_send_hwm(mut self, hwm: usize) -> Self {
705        self.send_hwm = hwm;
706        self
707    }
708
709    /// Enable or disable immediate mode.
710    pub const fn with_immediate(mut self, immediate: bool) -> Self {
711        self.immediate = immediate;
712        self
713    }
714
715    /// Set maximum message size.
716    pub const fn with_max_msg_size(mut self, size: Option<usize>) -> Self {
717        self.max_msg_size = size;
718        self
719    }
720
721    /// Set read buffer size.
722    ///
723    /// # Examples
724    ///
725    /// ```
726    /// use monocoque_core::options::SocketOptions;
727    ///
728    /// // Small buffers for low latency
729    /// let opts = SocketOptions::new().with_read_buffer_size(4096);
730    ///
731    /// // Large buffers for throughput
732    /// let opts = SocketOptions::new().with_read_buffer_size(16384);
733    /// ```
734    pub const fn with_read_buffer_size(mut self, size: usize) -> Self {
735        self.read_buffer_size = size;
736        self
737    }
738
739    /// Set write buffer size.
740    pub const fn with_write_buffer_size(mut self, size: usize) -> Self {
741        self.write_buffer_size = size;
742        self
743    }
744
745    /// Set both read and write buffer sizes (convenience method).
746    ///
747    /// # Examples
748    ///
749    /// ```
750    /// use monocoque_core::options::SocketOptions;
751    ///
752    /// // Small buffers for both
753    /// let opts = SocketOptions::new().with_buffer_sizes(4096, 4096);
754    /// ```
755    pub const fn with_buffer_sizes(mut self, read_size: usize, write_size: usize) -> Self {
756        self.read_buffer_size = read_size;
757        self.write_buffer_size = write_size;
758        self
759    }
760
761    /// Set socket routing ID / identity.
762    ///
763    /// # Examples
764    ///
765    /// ```
766    /// use monocoque_core::options::SocketOptions;
767    /// use bytes::Bytes;
768    ///
769    /// let opts = SocketOptions::new()
770    ///     .with_routing_id(Bytes::from_static(b"worker-01"));
771    /// ```
772    pub fn with_routing_id(mut self, id: bytes::Bytes) -> Self {
773        self.routing_id = Some(id);
774        self
775    }
776
777    /// Set connect routing ID for the next connection.
778    ///
779    /// This option is consumed after each connect operation and must be set
780    /// again for subsequent connections.
781    ///
782    /// # Examples
783    ///
784    /// ```
785    /// use monocoque_core::options::SocketOptions;
786    /// use bytes::Bytes;
787    ///
788    /// let opts = SocketOptions::new()
789    ///     .with_connect_routing_id(Bytes::from_static(b"client-001"));
790    /// ```
791    pub fn with_connect_routing_id(mut self, id: bytes::Bytes) -> Self {
792        self.connect_routing_id = Some(id);
793        self
794    }
795
796    /// Enable ROUTER mandatory mode.
797    pub const fn with_router_mandatory(mut self, enabled: bool) -> Self {
798        self.router_mandatory = enabled;
799        self
800    }
801
802    /// Enable ROUTER handover mode.
803    pub const fn with_router_handover(mut self, enabled: bool) -> Self {
804        self.router_handover = enabled;
805        self
806    }
807
808    /// Enable ROUTER probe on connect.
809    pub const fn with_probe_router(mut self, enabled: bool) -> Self {
810        self.probe_router = enabled;
811        self
812    }
813
814    /// Enable XPUB verbose mode.
815    pub const fn with_xpub_verbose(mut self, enabled: bool) -> Self {
816        self.xpub_verbose = enabled;
817        self
818    }
819
820    /// Enable XPUB manual mode.
821    pub const fn with_xpub_manual(mut self, enabled: bool) -> Self {
822        self.xpub_manual = enabled;
823        self
824    }
825
826    /// Set XPUB welcome message.
827    pub fn with_xpub_welcome_msg(mut self, msg: bytes::Bytes) -> Self {
828        self.xpub_welcome_msg = Some(msg);
829        self
830    }
831
832    /// Enable XSUB verbose unsubscribe.
833    pub const fn with_xsub_verbose_unsubs(mut self, enabled: bool) -> Self {
834        self.xsub_verbose_unsubs = enabled;
835        self
836    }
837
838    /// Enable message conflation (keep only last message).
839    pub const fn with_conflate(mut self, enabled: bool) -> Self {
840        self.conflate = enabled;
841        self
842    }
843
844    /// Set TCP keepalive mode.
845    ///
846    /// # Arguments
847    ///
848    /// * `mode` - `-1` for OS default, `0` to disable, `1` to enable
849    pub const fn with_tcp_keepalive(mut self, mode: i32) -> Self {
850        self.tcp_keepalive = mode;
851        self
852    }
853
854    /// Set TCP keepalive count (number of probes before timeout).
855    ///
856    /// # Arguments
857    ///
858    /// * `count` - `-1` for OS default, `> 0` for specific count
859    pub const fn with_tcp_keepalive_cnt(mut self, count: i32) -> Self {
860        self.tcp_keepalive_cnt = count;
861        self
862    }
863
864    /// Set TCP keepalive idle time (seconds before first probe).
865    ///
866    /// # Arguments
867    ///
868    /// * `seconds` - `-1` for OS default, `> 0` for specific idle time
869    pub const fn with_tcp_keepalive_idle(mut self, seconds: i32) -> Self {
870        self.tcp_keepalive_idle = seconds;
871        self
872    }
873
874    /// Set TCP keepalive interval (seconds between probes).
875    ///
876    /// # Arguments
877    ///
878    /// * `seconds` - `-1` for OS default, `> 0` for specific interval
879    pub const fn with_tcp_keepalive_intvl(mut self, seconds: i32) -> Self {
880        self.tcp_keepalive_intvl = seconds;
881        self
882    }
883
884    /// Enable REQ correlation mode (match replies to requests).
885    pub const fn with_req_correlate(mut self, enabled: bool) -> Self {
886        self.req_correlate = enabled;
887        self
888    }
889
890    /// Enable REQ relaxed mode (allow multiple outstanding requests).
891    pub const fn with_req_relaxed(mut self, enabled: bool) -> Self {
892        self.req_relaxed = enabled;
893        self
894    }
895
896    /// Set multicast rate (`ZMQ_RATE`).
897    pub const fn with_rate(mut self, rate: i32) -> Self {
898        self.rate = rate;
899        self
900    }
901
902    /// Set multicast recovery interval (`ZMQ_RECOVERY_IVL`).
903    pub const fn with_recovery_ivl(mut self, interval: Duration) -> Self {
904        self.recovery_ivl = interval;
905        self
906    }
907
908    /// Set OS send buffer size (`ZMQ_SNDBUF`).
909    pub const fn with_sndbuf(mut self, size: i32) -> Self {
910        self.sndbuf = size;
911        self
912    }
913
914    /// Set OS receive buffer size (`ZMQ_RCVBUF`).
915    pub const fn with_rcvbuf(mut self, size: i32) -> Self {
916        self.rcvbuf = size;
917        self
918    }
919
920    /// Set multicast TTL/hops (`ZMQ_MULTICAST_HOPS`).
921    pub const fn with_multicast_hops(mut self, hops: i32) -> Self {
922        self.multicast_hops = hops;
923        self
924    }
925
926    /// Set IP Type of Service (`ZMQ_TOS`).
927    pub const fn with_tos(mut self, tos: i32) -> Self {
928        self.tos = tos;
929        self
930    }
931
932    /// Set multicast maximum TPU (`ZMQ_MULTICAST_MAXTPDU`).
933    pub const fn with_multicast_maxtpdu(mut self, mtu: i32) -> Self {
934        self.multicast_maxtpdu = mtu;
935        self
936    }
937
938    /// Enable IPv6 support (`ZMQ_IPV6`).
939    pub const fn with_ipv6(mut self, enabled: bool) -> Self {
940        self.ipv6 = enabled;
941        self
942    }
943
944    /// Bind to specific device (`ZMQ_BINDTODEVICE`) - Linux only.
945    pub fn with_bind_to_device(mut self, device: impl Into<String>) -> Self {
946        self.bind_to_device = Some(device.into());
947        self
948    }
949
950    // --- Security Options ---
951
952    /// Enable PLAIN server mode.
953    ///
954    /// # Examples
955    ///
956    /// ```
957    /// use monocoque_core::options::SocketOptions;
958    ///
959    /// let opts = SocketOptions::new().with_plain_server(true);
960    /// ```
961    pub const fn with_plain_server(mut self, enabled: bool) -> Self {
962        self.plain_server = enabled;
963        self
964    }
965
966    /// Set PLAIN client credentials.
967    ///
968    /// # Examples
969    ///
970    /// ```
971    /// use monocoque_core::options::SocketOptions;
972    ///
973    /// let opts = SocketOptions::new()
974    ///     .with_plain_credentials("admin", "secret123");
975    /// ```
976    pub fn with_plain_credentials(
977        mut self,
978        username: impl Into<String>,
979        password: impl Into<String>,
980    ) -> Self {
981        self.plain_username = Some(username.into());
982        self.plain_password = Some(password.into());
983        self
984    }
985
986    /// Enable CURVE server mode.
987    ///
988    /// # Examples
989    ///
990    /// ```
991    /// use monocoque_core::options::SocketOptions;
992    ///
993    /// let opts = SocketOptions::new().with_curve_server(true);
994    /// ```
995    pub const fn with_curve_server(mut self, enabled: bool) -> Self {
996        self.curve_server = enabled;
997        self
998    }
999
1000    /// Set CURVE client keys (public + secret).
1001    ///
1002    /// # Examples
1003    ///
1004    /// ```
1005    /// use monocoque_core::options::SocketOptions;
1006    ///
1007    /// let public = [0u8; 32];  // Replace with actual key
1008    /// let secret = [0u8; 32];  // Replace with actual key
1009    /// let opts = SocketOptions::new().with_curve_keypair(public, secret);
1010    /// ```
1011    pub const fn with_curve_keypair(mut self, publickey: [u8; 32], secretkey: [u8; 32]) -> Self {
1012        self.curve_publickey = Some(publickey);
1013        self.curve_secretkey = Some(secretkey);
1014        self
1015    }
1016
1017    /// Set CURVE server public key (for client).
1018    ///
1019    /// # Examples
1020    ///
1021    /// ```
1022    /// use monocoque_core::options::SocketOptions;
1023    ///
1024    /// let server_key = [0u8; 32];  // Server's public key
1025    /// let opts = SocketOptions::new().with_curve_serverkey(server_key);
1026    /// ```
1027    pub const fn with_curve_serverkey(mut self, serverkey: [u8; 32]) -> Self {
1028        self.curve_serverkey = Some(serverkey);
1029        self
1030    }
1031
1032    /// Set ZAP domain for authentication.
1033    ///
1034    /// # Examples
1035    ///
1036    /// ```
1037    /// use monocoque_core::options::SocketOptions;
1038    ///
1039    /// let opts = SocketOptions::new().with_zap_domain("production");
1040    /// ```
1041    pub fn with_zap_domain(mut self, domain: impl Into<String>) -> Self {
1042        self.zap_domain = domain.into();
1043        self
1044    }
1045
1046    /// Add a subscription filter for SUB/XSUB sockets (`ZMQ_SUBSCRIBE`).
1047    ///
1048    /// SUB sockets MUST subscribe to at least one topic to receive messages.
1049    /// An empty filter (b"" or `Bytes::new()`) subscribes to all messages.
1050    ///
1051    /// # Examples
1052    ///
1053    /// ```
1054    /// use monocoque_core::options::SocketOptions;
1055    /// use bytes::Bytes;
1056    ///
1057    /// // Subscribe to all messages
1058    /// let opts = SocketOptions::new().with_subscribe(Bytes::new());
1059    ///
1060    /// // Subscribe to specific topics
1061    /// let opts = SocketOptions::new()
1062    ///     .with_subscribe(Bytes::from("weather."))
1063    ///     .with_subscribe(Bytes::from("stocks."));
1064    /// ```
1065    pub fn with_subscribe(mut self, filter: bytes::Bytes) -> Self {
1066        self.subscriptions.push(filter);
1067        self
1068    }
1069
1070    /// Add multiple subscription filters for SUB/XSUB sockets.
1071    ///
1072    /// Convenience method to subscribe to multiple topics at once.
1073    ///
1074    /// # Examples
1075    ///
1076    /// ```
1077    /// use monocoque_core::options::SocketOptions;
1078    /// use bytes::Bytes;
1079    ///
1080    /// let opts = SocketOptions::new()
1081    ///     .with_subscriptions(vec![
1082    ///         Bytes::from("weather."),
1083    ///         Bytes::from("stocks."),
1084    ///     ]);
1085    /// ```
1086    pub fn with_subscriptions(mut self, filters: Vec<bytes::Bytes>) -> Self {
1087        self.subscriptions.extend(filters);
1088        self
1089    }
1090
1091    /// Add an unsubscription filter for SUB/XSUB sockets (`ZMQ_UNSUBSCRIBE`).
1092    ///
1093    /// Removes a previously added subscription filter.
1094    ///
1095    /// # Examples
1096    ///
1097    /// ```
1098    /// use monocoque_core::options::SocketOptions;
1099    /// use bytes::Bytes;
1100    ///
1101    /// let opts = SocketOptions::new()
1102    ///     .with_subscribe(Bytes::new())  // Subscribe to all
1103    ///     .with_unsubscribe(Bytes::from("admin.")); // Except admin topics
1104    /// ```
1105    pub fn with_unsubscribe(mut self, filter: bytes::Bytes) -> Self {
1106        self.unsubscriptions.push(filter);
1107        self
1108    }
1109
1110    // --- Query Methods ---
1111
1112    /// Check if receive operation should be non-blocking.
1113    pub const fn is_recv_nonblocking(&self) -> bool {
1114        matches!(self.recv_timeout, Some(d) if d.is_zero())
1115    }
1116
1117    /// Check if send operation should be non-blocking.
1118    pub const fn is_send_nonblocking(&self) -> bool {
1119        matches!(self.send_timeout, Some(d) if d.is_zero())
1120    }
1121
1122    /// Validate routing ID for use with ROUTER sockets.
1123    ///
1124    /// ROUTER socket identities must:
1125    /// - Be 1-255 bytes long
1126    /// - Not start with null byte (0x00) which is reserved for auto-generated IDs
1127    pub fn validate_router_identity(id: &[u8]) -> std::io::Result<()> {
1128        if id.is_empty() {
1129            return Err(std::io::Error::new(
1130                std::io::ErrorKind::InvalidInput,
1131                "routing ID cannot be empty",
1132            ));
1133        }
1134
1135        if id.len() > 255 {
1136            return Err(std::io::Error::new(
1137                std::io::ErrorKind::InvalidInput,
1138                format!("routing ID cannot exceed 255 bytes (got {})", id.len()),
1139            ));
1140        }
1141
1142        if id[0] == 0x00 {
1143            return Err(std::io::Error::new(
1144                std::io::ErrorKind::InvalidInput,
1145                "routing ID cannot start with null byte (reserved for auto-generated IDs)",
1146            ));
1147        }
1148
1149        Ok(())
1150    }
1151
1152    /// Validate general routing ID (for DEALER, REQ, REP).
1153    ///
1154    /// Less strict than ROUTER identities - allows null prefix.
1155    pub fn validate_routing_id(id: &[u8]) -> std::io::Result<()> {
1156        if id.len() > 255 {
1157            return Err(std::io::Error::new(
1158                std::io::ErrorKind::InvalidInput,
1159                format!("routing ID cannot exceed 255 bytes (got {})", id.len()),
1160            ));
1161        }
1162        Ok(())
1163    }
1164
1165    /// Get the current reconnection interval with exponential backoff.
1166    ///
1167    /// Returns the interval to use, considering exponential backoff
1168    /// and the maximum interval setting.
1169    pub fn next_reconnect_ivl(&self, attempt: u32) -> Duration {
1170        if self.reconnect_ivl_max.is_zero() {
1171            // No exponential backoff, always use base interval
1172            return self.reconnect_ivl;
1173        }
1174
1175        // Calculate exponential backoff: base * 2^attempt
1176        let backoff = self
1177            .reconnect_ivl
1178            .saturating_mul(2u32.saturating_pow(attempt));
1179
1180        // Cap at maximum interval
1181        backoff.min(self.reconnect_ivl_max)
1182    }
1183}
1184
1185#[cfg(test)]
1186mod tests {
1187    use super::*;
1188
1189    #[test]
1190    fn test_default_options() {
1191        let opts = SocketOptions::default();
1192        assert!(opts.recv_timeout.is_none());
1193        assert!(opts.send_timeout.is_none());
1194        assert_eq!(opts.handshake_timeout, Duration::from_secs(30));
1195        assert_eq!(opts.reconnect_ivl, Duration::from_millis(100));
1196        assert_eq!(opts.recv_hwm, 1000);
1197        assert_eq!(opts.send_hwm, 1000);
1198    }
1199
1200    #[test]
1201    fn test_builder_pattern() {
1202        let opts = SocketOptions::new()
1203            .with_recv_timeout(Duration::from_secs(5))
1204            .with_send_timeout(Duration::from_secs(10))
1205            .with_recv_hwm(2000);
1206
1207        assert_eq!(opts.recv_timeout, Some(Duration::from_secs(5)));
1208        assert_eq!(opts.send_timeout, Some(Duration::from_secs(10)));
1209        assert_eq!(opts.recv_hwm, 2000);
1210    }
1211
1212    #[test]
1213    fn test_nonblocking_checks() {
1214        let blocking = SocketOptions::new();
1215        assert!(!blocking.is_recv_nonblocking());
1216        assert!(!blocking.is_send_nonblocking());
1217
1218        let nonblocking = SocketOptions::new()
1219            .with_recv_timeout(Duration::ZERO)
1220            .with_send_timeout(Duration::ZERO);
1221        assert!(nonblocking.is_recv_nonblocking());
1222        assert!(nonblocking.is_send_nonblocking());
1223    }
1224
1225    #[test]
1226    fn test_exponential_backoff() {
1227        let opts = SocketOptions::new()
1228            .with_reconnect_ivl(Duration::from_millis(100))
1229            .with_reconnect_ivl_max(Duration::from_secs(10));
1230
1231        // First attempt: 100ms
1232        assert_eq!(opts.next_reconnect_ivl(0), Duration::from_millis(100));
1233
1234        // Second attempt: 200ms
1235        assert_eq!(opts.next_reconnect_ivl(1), Duration::from_millis(200));
1236
1237        // Third attempt: 400ms
1238        assert_eq!(opts.next_reconnect_ivl(2), Duration::from_millis(400));
1239
1240        // Eventually caps at 10s
1241        assert_eq!(opts.next_reconnect_ivl(10), Duration::from_secs(10));
1242    }
1243
1244    #[test]
1245    fn test_no_exponential_backoff() {
1246        let opts = SocketOptions::new().with_reconnect_ivl(Duration::from_millis(100));
1247        // reconnect_ivl_max is 0 by default
1248
1249        // Always returns base interval
1250        assert_eq!(opts.next_reconnect_ivl(0), Duration::from_millis(100));
1251        assert_eq!(opts.next_reconnect_ivl(1), Duration::from_millis(100));
1252        assert_eq!(opts.next_reconnect_ivl(10), Duration::from_millis(100));
1253    }
1254
1255    #[test]
1256    fn test_routing_id_validation() {
1257        // Valid ROUTER identities
1258        assert!(SocketOptions::validate_router_identity(b"client-001").is_ok());
1259        assert!(SocketOptions::validate_router_identity(&[0x01; 255]).is_ok());
1260
1261        // Invalid: empty
1262        assert!(SocketOptions::validate_router_identity(b"").is_err());
1263
1264        // Invalid: too long
1265        assert!(SocketOptions::validate_router_identity(&[0x01; 256]).is_err());
1266
1267        // Invalid: starts with null byte
1268        assert!(SocketOptions::validate_router_identity(b"\x00client").is_err());
1269    }
1270
1271    #[test]
1272    fn test_general_routing_id_validation() {
1273        // Valid
1274        assert!(SocketOptions::validate_routing_id(b"").is_ok()); // Empty allowed
1275        assert!(SocketOptions::validate_routing_id(b"\x00client").is_ok()); // Null prefix allowed
1276        assert!(SocketOptions::validate_routing_id(&[0x00; 255]).is_ok());
1277
1278        // Invalid: too long
1279        assert!(SocketOptions::validate_routing_id(&[0x01; 256]).is_err());
1280    }
1281
1282    #[test]
1283    fn test_connect_routing_id() {
1284        let opts =
1285            SocketOptions::new().with_connect_routing_id(bytes::Bytes::from_static(b"peer-123"));
1286
1287        assert_eq!(
1288            opts.connect_routing_id,
1289            Some(bytes::Bytes::from_static(b"peer-123"))
1290        );
1291    }
1292
1293    #[test]
1294    fn test_router_options() {
1295        let opts = SocketOptions::new()
1296            .with_router_mandatory(true)
1297            .with_router_handover(true);
1298
1299        assert!(opts.router_mandatory);
1300        assert!(opts.router_handover);
1301    }
1302
1303    #[test]
1304    fn test_subscription_options() {
1305        // Test with_subscribe
1306        let opts = SocketOptions::new()
1307            .with_subscribe(bytes::Bytes::new()) // Subscribe to all
1308            .with_subscribe(bytes::Bytes::from("weather."))
1309            .with_subscribe(bytes::Bytes::from("stocks."));
1310
1311        assert_eq!(opts.subscriptions.len(), 3);
1312        assert_eq!(opts.subscriptions[0], bytes::Bytes::new());
1313        assert_eq!(opts.subscriptions[1], bytes::Bytes::from("weather."));
1314        assert_eq!(opts.subscriptions[2], bytes::Bytes::from("stocks."));
1315
1316        // Test with_subscriptions
1317        let opts2 = SocketOptions::new().with_subscriptions(vec![
1318            bytes::Bytes::from("topic1"),
1319            bytes::Bytes::from("topic2"),
1320        ]);
1321
1322        assert_eq!(opts2.subscriptions.len(), 2);
1323
1324        // Test with_unsubscribe
1325        let opts3 = opts.with_unsubscribe(bytes::Bytes::from("admin."));
1326        assert_eq!(opts3.unsubscriptions.len(), 1);
1327        assert_eq!(opts3.unsubscriptions[0], bytes::Bytes::from("admin."));
1328    }
1329}