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}