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::{fmt, 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(Clone)]
30pub struct SocketOptions {
31 /// Read buffer size (bytes)
32 ///
33 /// Size of the read-slab 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 /// Bind listeners with `SO_REUSEPORT` (Unix only).
256 ///
257 /// When `true`, a socket that creates its own listener binds it with
258 /// `SO_REUSEPORT` so multiple acceptors can share one port with in-kernel
259 /// load balancing (scaling accept for high-connection ROUTER/PULL/XPUB).
260 /// - `false` (default): a single listener per address.
261 pub reuse_port: bool,
262
263 /// Multicast TTL (`ZMQ_MULTICAST_HOPS`)
264 ///
265 /// Time-to-live for multicast packets.
266 /// - Default: 1 (local network only)
267 pub multicast_hops: i32,
268
269 /// IP Type of Service (`ZMQ_TOS`)
270 ///
271 /// Sets the `ToS` field in IP headers for `QoS`.
272 /// - Default: 0 (normal service)
273 pub tos: i32,
274
275 /// Maximum multicast transmission unit (`ZMQ_MULTICAST_MAXTPDU`)
276 ///
277 /// Maximum transport data unit for multicast.
278 /// - Default: 1500 bytes
279 pub multicast_maxtpdu: i32,
280
281 /// IPv6 support (`ZMQ_IPV6`)
282 ///
283 /// Enable IPv6 on socket.
284 /// - `false` (default): IPv4 only
285 /// - `true`: IPv6 support enabled
286 pub ipv6: bool,
287
288 /// Bind to device (`ZMQ_BINDTODEVICE`)
289 ///
290 /// Bind socket to specific network interface (Linux only).
291 /// - Default: None (bind to all interfaces)
292 pub bind_to_device: Option<String>,
293
294 // --- Security Options ---
295 /// PLAIN server mode (`ZMQ_PLAIN_SERVER`)
296 ///
297 /// Enable PLAIN authentication as server.
298 /// - `false` (default): Client mode
299 /// - `true`: Server mode (validate credentials)
300 pub plain_server: bool,
301
302 /// PLAIN username (`ZMQ_PLAIN_USERNAME`)
303 ///
304 /// Username for PLAIN authentication (client side).
305 /// - Default: None (no authentication)
306 pub plain_username: Option<String>,
307
308 /// PLAIN password (`ZMQ_PLAIN_PASSWORD`)
309 ///
310 /// Password for PLAIN authentication (client side).
311 /// - Default: None (no authentication)
312 pub plain_password: Option<String>,
313
314 /// CURVE server mode (`ZMQ_CURVE_SERVER`)
315 ///
316 /// Enable CURVE encryption as server.
317 /// - `false` (default): Client mode
318 /// - `true`: Server mode (provide server key)
319 pub curve_server: bool,
320
321 /// CURVE public key (`ZMQ_CURVE_PUBLICKEY`)
322 ///
323 /// Local public key for CURVE (32 bytes).
324 /// - Default: None (no encryption)
325 pub curve_publickey: Option<[u8; 32]>,
326
327 /// CURVE secret key (`ZMQ_CURVE_SECRETKEY`)
328 ///
329 /// Local secret key for CURVE (32 bytes).
330 /// - Default: None (no encryption)
331 pub curve_secretkey: Option<[u8; 32]>,
332
333 /// CURVE server key (`ZMQ_CURVE_SERVERKEY`)
334 ///
335 /// Server's public key for CURVE client (32 bytes).
336 /// - Default: None (no encryption)
337 /// - Client must set this to verify server identity
338 pub curve_serverkey: Option<[u8; 32]>,
339
340 /// ZAP domain (`ZMQ_ZAP_DOMAIN`)
341 ///
342 /// Security domain for ZAP authentication.
343 /// - Default: "" (global domain)
344 pub zap_domain: String,
345
346 /// Subscriptions (`ZMQ_SUBSCRIBE`)
347 ///
348 /// Subscription filters for SUB/XSUB sockets.
349 /// - Empty vec: No subscriptions (default) - won't receive any messages
350 /// - vec![b""] or vec![`Bytes::new()`]: Subscribe to all messages
351 /// - vec![b"topic1", b"topic2"]: Subscribe to specific topics
352 ///
353 /// Note: SUB sockets MUST subscribe to at least one topic to receive messages.
354 pub subscriptions: Vec<bytes::Bytes>,
355
356 /// Unsubscriptions (`ZMQ_UNSUBSCRIBE`)
357 ///
358 /// Subscription filters to remove for SUB/XSUB sockets.
359 /// Applied after subscriptions during socket configuration.
360 pub unsubscriptions: Vec<bytes::Bytes>,
361
362 /// Maximum reconnection attempts (`ZMQ_RECONNECT_STOP`)
363 ///
364 /// Maximum number of times to attempt reconnection after a disconnect.
365 /// - `None`: Retry indefinitely (default, matches libzmq behaviour)
366 /// - `Some(n)`: Give up and return `NotConnected` after n attempts
367 pub max_reconnect_attempts: Option<u32>,
368
369 /// ZMTP heartbeat interval (`ZMQ_HEARTBEAT_IVL` = 75)
370 ///
371 /// How often to send PING heartbeat commands on an otherwise idle connection.
372 /// - `None`: Disabled (default)
373 /// - `Some(dur)`: Send PING every `dur` of inactivity
374 pub heartbeat_ivl: Option<Duration>,
375
376 /// ZMTP heartbeat TTL (`ZMQ_HEARTBEAT_TTL` = 76)
377 ///
378 /// Time-to-live for the remote peer's heartbeat (sent in PING command).
379 /// The remote will disconnect if it doesn't receive a heartbeat within this interval.
380 /// - `None`: Use `heartbeat_ivl` (default)
381 /// - `Some(dur)`: Override TTL sent to peer
382 pub heartbeat_ttl: Option<Duration>,
383
384 /// ZMTP heartbeat timeout (`ZMQ_HEARTBEAT_TIMEOUT` = 77)
385 ///
386 /// How long to wait for a PONG reply before considering the connection dead.
387 /// - `None`: Use `heartbeat_ivl` (default)
388 /// - `Some(dur)`: Custom timeout (recommended: 2-5x `heartbeat_ivl`)
389 pub heartbeat_timeout: Option<Duration>,
390
391 /// ROUTER raw mode (`ZMQ_ROUTER_RAW` = 41)
392 ///
393 /// Put ROUTER socket into raw mode (no ZMTP handshake, acts like STREAM).
394 /// - `false` (default): Normal ZMTP routing
395 /// - `true`: Raw TCP bridging mode
396 pub router_raw: bool,
397
398 /// STREAM connect/disconnect notifications (`ZMQ_STREAM_NOTIFY` = 73)
399 ///
400 /// Send empty notification frames on connect and disconnect.
401 /// - `true` (default): Send notification frames
402 /// - `false`: Suppress notification frames
403 pub stream_notify: bool,
404
405 /// XPUB no-drop mode (`ZMQ_XPUB_NODROP` = 69)
406 ///
407 /// - `false` (default): Drop messages silently when HWM is reached
408 /// - `true`: Return error (`EAGAIN`) instead of dropping
409 pub xpub_nodrop: bool,
410
411 /// Invert topic matching (`ZMQ_INVERT_MATCHING` = 74)
412 ///
413 /// Invert the subscription filter logic for PUB/SUB and XPUB/XSUB.
414 /// - `false` (default): Deliver messages matching subscriptions
415 /// - `true`: Deliver messages NOT matching any subscription
416 pub invert_matching: bool,
417
418 /// Write coalescing: batch multiple `send()` calls before writing to the kernel.
419 ///
420 /// When enabled, `send()` accumulates encoded messages in an internal buffer
421 /// and only flushes to the kernel when `write_coalesce_threshold` bytes have
422 /// accumulated or when `flush()` is called explicitly.
423 ///
424 /// - `false` (default): each `send()` writes immediately (lowest latency)
425 /// - `true`: messages are batched (higher throughput for small messages)
426 ///
427 /// Always call `flush()` after the last `send()` in a burst to ensure
428 /// all buffered data reaches the peer.
429 pub write_coalescing: bool,
430
431 /// Byte threshold at which the coalesce buffer is flushed automatically.
432 ///
433 /// Only relevant when `write_coalescing` is enabled. The internal send
434 /// buffer is written to the kernel as a single syscall once it reaches
435 /// this many bytes.
436 ///
437 /// - Default: 65536 (64 KB) - one typical TCP segment on loopback
438 pub write_coalesce_threshold: usize,
439
440 /// Frame-body size at or above which the send path switches to a vectored
441 /// write (`writev`) instead of copying the body into the userspace send
442 /// buffer.
443 ///
444 /// For large frames the body copy into the coalescing buffer is the
445 /// dominant per-byte cost on the core. Above this threshold the frame header
446 /// and the refcounted `Bytes` body are written as an iovec, so the body is
447 /// handed straight to the kernel with no intermediate copy. Small frames
448 /// stay on the copy path, where a single `write` of one contiguous buffer
449 /// beats the per-iovec bookkeeping.
450 ///
451 /// Only applies in eager mode (write coalescing disabled) and when the
452 /// connection is not CURVE-encrypted (encryption must transform the body
453 /// into a fresh buffer regardless).
454 ///
455 /// - Default: 32768 (32 KB) - the measured crossover on loopback below which
456 /// copying the body into one contiguous buffer beats a two-segment
457 /// `writev`; tune for your hardware and message sizes
458 pub vectored_write_threshold: usize,
459}
460
461impl fmt::Debug for SocketOptions {
462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463 f.debug_struct("SocketOptions")
464 .field("read_buffer_size", &self.read_buffer_size())
465 .field("write_buffer_size", &self.write_buffer_size)
466 .field("recv_timeout", &self.recv_timeout)
467 .field("send_timeout", &self.send_timeout)
468 .field("handshake_timeout", &self.handshake_timeout)
469 .field("linger", &self.linger)
470 .field("reconnect_ivl", &self.reconnect_ivl)
471 .field("reconnect_ivl_max", &self.reconnect_ivl_max)
472 .field("connect_timeout", &self.connect_timeout)
473 .field("recv_hwm", &self.recv_hwm)
474 .field("send_hwm", &self.send_hwm)
475 .field("immediate", &self.immediate)
476 .field("max_msg_size", &self.max_msg_size)
477 .field("routing_id", &self.routing_id)
478 .field("connect_routing_id", &self.connect_routing_id)
479 .field("router_mandatory", &self.router_mandatory)
480 .field("router_handover", &self.router_handover)
481 .field("probe_router", &self.probe_router)
482 .field("xpub_verbose", &self.xpub_verbose)
483 .field("xpub_manual", &self.xpub_manual)
484 .field("xpub_welcome_msg", &self.xpub_welcome_msg)
485 .field("xsub_verbose_unsubs", &self.xsub_verbose_unsubs)
486 .field("conflate", &self.conflate)
487 .field("tcp_keepalive", &self.tcp_keepalive)
488 .field("tcp_keepalive_cnt", &self.tcp_keepalive_cnt)
489 .field("tcp_keepalive_idle", &self.tcp_keepalive_idle)
490 .field("tcp_keepalive_intvl", &self.tcp_keepalive_intvl)
491 .field("req_correlate", &self.req_correlate)
492 .field("req_relaxed", &self.req_relaxed)
493 .field("rate", &self.rate)
494 .field("recovery_ivl", &self.recovery_ivl)
495 .field("sndbuf", &self.sndbuf)
496 .field("rcvbuf", &self.rcvbuf)
497 .field("reuse_port", &self.reuse_port)
498 .field("multicast_hops", &self.multicast_hops)
499 .field("tos", &self.tos)
500 .field("multicast_maxtpdu", &self.multicast_maxtpdu)
501 .field("ipv6", &self.ipv6)
502 .field("bind_to_device", &self.bind_to_device)
503 .field("plain_server", &self.plain_server)
504 .field("plain_username", &self.plain_username)
505 .field(
506 "plain_password",
507 &self.plain_password.as_ref().map(|_| "[REDACTED]"),
508 )
509 .field("curve_server", &self.curve_server)
510 .field("curve_publickey", &self.curve_publickey)
511 .field(
512 "curve_secretkey",
513 &self.curve_secretkey.as_ref().map(|_| "[REDACTED]"),
514 )
515 .field("curve_serverkey", &self.curve_serverkey)
516 .field("zap_domain", &self.zap_domain)
517 .field("subscriptions", &self.subscriptions)
518 .field("unsubscriptions", &self.unsubscriptions)
519 .field("max_reconnect_attempts", &self.max_reconnect_attempts)
520 .field("heartbeat_ivl", &self.heartbeat_ivl)
521 .field("heartbeat_ttl", &self.heartbeat_ttl)
522 .field("heartbeat_timeout", &self.heartbeat_timeout)
523 .field("router_raw", &self.router_raw)
524 .field("stream_notify", &self.stream_notify)
525 .field("xpub_nodrop", &self.xpub_nodrop)
526 .field("invert_matching", &self.invert_matching)
527 .field("write_coalescing", &self.write_coalescing)
528 .field("write_coalesce_threshold", &self.write_coalesce_threshold)
529 .field("vectored_write_threshold", &self.vectored_write_threshold)
530 .finish()
531 }
532}
533
534impl Default for SocketOptions {
535 fn default() -> Self {
536 Self {
537 recv_timeout: None, // Block indefinitely
538 send_timeout: None, // Block indefinitely
539 handshake_timeout: Duration::from_secs(30),
540 linger: Some(Duration::from_secs(30)), // Wait 30s for pending messages
541 reconnect_ivl: Duration::from_millis(100),
542 reconnect_ivl_max: Duration::ZERO, // No maximum
543 connect_timeout: Duration::ZERO, // Use OS default
544 recv_hwm: 1000,
545 send_hwm: 1000,
546 immediate: false,
547 max_msg_size: None, // No limit
548 read_buffer_size: 8192, // 8KB - balanced default
549 write_buffer_size: 8192, // 8KB - balanced default
550 routing_id: None,
551 connect_routing_id: None,
552 router_mandatory: false,
553 router_handover: false,
554 probe_router: false,
555 xpub_verbose: false,
556 xpub_manual: false,
557 xpub_welcome_msg: None,
558 xsub_verbose_unsubs: false,
559 conflate: false,
560 tcp_keepalive: -1, // OS default
561 tcp_keepalive_cnt: -1, // OS default
562 tcp_keepalive_idle: -1, // OS default
563 tcp_keepalive_intvl: -1, // OS default
564 req_correlate: false,
565 req_relaxed: false,
566 rate: 100, // 100 kbps
567 recovery_ivl: Duration::from_secs(10),
568 sndbuf: 0, // OS default
569 rcvbuf: 0, // OS default
570 reuse_port: false,
571 multicast_hops: 1, // Local network only
572 tos: 0, // Normal service
573 multicast_maxtpdu: 1500, // Standard MTU
574 ipv6: false, // IPv4 only
575 bind_to_device: None, // All interfaces
576 // Security
577 plain_server: false,
578 plain_username: None,
579 plain_password: None,
580 curve_server: false,
581 curve_publickey: None,
582 curve_secretkey: None,
583 curve_serverkey: None,
584 zap_domain: String::new(), // Global domain
585 subscriptions: Vec::new(), // No subscriptions
586 unsubscriptions: Vec::new(), // No unsubscriptions
587 max_reconnect_attempts: None, // Retry indefinitely
588 heartbeat_ivl: None,
589 heartbeat_ttl: None,
590 heartbeat_timeout: None,
591 router_raw: false,
592 stream_notify: true,
593 xpub_nodrop: false,
594 invert_matching: false,
595 write_coalescing: false,
596 write_coalesce_threshold: 65536,
597 vectored_write_threshold: 32768,
598 }
599 }
600}
601
602impl SocketOptions {
603 /// Create new socket options with default values (8KB buffers).
604 #[must_use]
605 pub fn new() -> Self {
606 Self::default()
607 }
608
609 /// Create socket options optimized for small messages (< 1KB).
610 ///
611 /// Sets 4KB buffers, suitable for low-latency request-reply patterns.
612 ///
613 /// # Examples
614 ///
615 /// ```
616 /// use monocoque_core::options::SocketOptions;
617 ///
618 /// let opts = SocketOptions::small(); // 4KB buffers for REQ/REP
619 /// ```
620 #[must_use]
621 pub fn small() -> Self {
622 Self {
623 read_buffer_size: 4096,
624 write_buffer_size: 4096,
625 ..Self::default()
626 }
627 }
628
629 /// Create socket options optimized for large messages (> 8KB).
630 ///
631 /// Sets 16KB buffers, suitable for high-throughput async patterns.
632 ///
633 /// # Examples
634 ///
635 /// ```
636 /// use monocoque_core::options::SocketOptions;
637 ///
638 /// let opts = SocketOptions::large(); // 16KB buffers for DEALER/ROUTER
639 /// ```
640 #[must_use]
641 pub fn large() -> Self {
642 Self {
643 read_buffer_size: 16384,
644 write_buffer_size: 16384,
645 ..Self::default()
646 }
647 }
648
649 /// Set receive timeout.
650 ///
651 /// # Examples
652 ///
653 /// ```
654 /// use monocoque_core::options::SocketOptions;
655 /// use std::time::Duration;
656 ///
657 /// // Non-blocking receive
658 /// let opts = SocketOptions::new().with_recv_timeout(Duration::ZERO);
659 ///
660 /// // 5 second timeout
661 /// let opts = SocketOptions::new().with_recv_timeout(Duration::from_secs(5));
662 /// ```
663 pub const fn with_recv_timeout(mut self, timeout: Duration) -> Self {
664 self.recv_timeout = Some(timeout);
665 self
666 }
667
668 /// Set send timeout.
669 pub const fn with_send_timeout(mut self, timeout: Duration) -> Self {
670 self.send_timeout = Some(timeout);
671 self
672 }
673
674 /// Set handshake timeout.
675 pub const fn with_handshake_timeout(mut self, timeout: Duration) -> Self {
676 self.handshake_timeout = timeout;
677 self
678 }
679
680 /// Set linger timeout.
681 pub const fn with_linger(mut self, linger: Option<Duration>) -> Self {
682 self.linger = linger;
683 self
684 }
685
686 /// Set reconnection interval.
687 pub const fn with_reconnect_ivl(mut self, ivl: Duration) -> Self {
688 self.reconnect_ivl = ivl;
689 self
690 }
691
692 /// Set maximum reconnection interval for exponential backoff.
693 pub const fn with_reconnect_ivl_max(mut self, max: Duration) -> Self {
694 self.reconnect_ivl_max = max;
695 self
696 }
697
698 /// Set maximum number of reconnection attempts.
699 ///
700 /// `None` retries indefinitely (default); `Some(n)` gives up after n attempts.
701 pub const fn with_max_reconnect_attempts(mut self, max: Option<u32>) -> Self {
702 self.max_reconnect_attempts = max;
703 self
704 }
705
706 /// Set connection timeout.
707 pub const fn with_connect_timeout(mut self, timeout: Duration) -> Self {
708 self.connect_timeout = timeout;
709 self
710 }
711
712 /// Set heartbeat interval (`ZMQ_HEARTBEAT_IVL`).
713 pub const fn with_heartbeat_ivl(mut self, ivl: Duration) -> Self {
714 self.heartbeat_ivl = Some(ivl);
715 self
716 }
717
718 /// Set heartbeat TTL (`ZMQ_HEARTBEAT_TTL`).
719 pub const fn with_heartbeat_ttl(mut self, ttl: Duration) -> Self {
720 self.heartbeat_ttl = Some(ttl);
721 self
722 }
723
724 /// Set heartbeat timeout (`ZMQ_HEARTBEAT_TIMEOUT`).
725 pub const fn with_heartbeat_timeout(mut self, timeout: Duration) -> Self {
726 self.heartbeat_timeout = Some(timeout);
727 self
728 }
729
730 /// Enable or disable ROUTER raw mode (`ZMQ_ROUTER_RAW`).
731 pub const fn with_router_raw(mut self, raw: bool) -> Self {
732 self.router_raw = raw;
733 self
734 }
735
736 /// Enable or disable STREAM connect/disconnect notifications (`ZMQ_STREAM_NOTIFY`).
737 pub const fn with_stream_notify(mut self, notify: bool) -> Self {
738 self.stream_notify = notify;
739 self
740 }
741
742 /// Enable XPUB no-drop mode (`ZMQ_XPUB_NODROP`).
743 pub const fn with_xpub_nodrop(mut self, nodrop: bool) -> Self {
744 self.xpub_nodrop = nodrop;
745 self
746 }
747
748 /// Enable inverted topic matching (`ZMQ_INVERT_MATCHING`).
749 pub const fn with_invert_matching(mut self, invert: bool) -> Self {
750 self.invert_matching = invert;
751 self
752 }
753
754 /// Enable or disable write coalescing.
755 ///
756 /// When enabled, consecutive `send()` calls accumulate in an internal buffer
757 /// and are written to the kernel in one syscall, reducing per-message overhead
758 /// for small-message workloads. Call `flush()` after the last send in a burst.
759 pub const fn with_write_coalescing(mut self, enabled: bool) -> Self {
760 self.write_coalescing = enabled;
761 self
762 }
763
764 /// Set the byte threshold at which the coalesce buffer flushes automatically.
765 pub const fn with_write_coalesce_threshold(mut self, threshold: usize) -> Self {
766 self.write_coalesce_threshold = threshold;
767 self
768 }
769
770 /// Set the frame-body size at or above which the eager send path uses a
771 /// vectored write (`writev`) instead of copying the body into the send
772 /// buffer. See [`SocketOptions::vectored_write_threshold`]. Set to
773 /// `usize::MAX` to disable vectored writes entirely.
774 pub const fn with_vectored_write_threshold(mut self, threshold: usize) -> Self {
775 self.vectored_write_threshold = threshold;
776 self
777 }
778
779 /// Get the configured PLAIN password, if any.
780 pub fn plain_password(&self) -> Option<&str> {
781 self.plain_password.as_deref()
782 }
783
784 /// Get the configured CURVE secret key, if any.
785 pub const fn curve_secretkey(&self) -> Option<&[u8; 32]> {
786 self.curve_secretkey.as_ref()
787 }
788
789 /// Get the configured read buffer size after applying the read-slab cap.
790 pub const fn read_buffer_size(&self) -> usize {
791 if self.read_buffer_size > crate::io::READ_SLAB_SIZE {
792 crate::io::READ_SLAB_SIZE
793 } else {
794 self.read_buffer_size
795 }
796 }
797
798 /// Set receive high water mark.
799 pub const fn with_recv_hwm(mut self, hwm: usize) -> Self {
800 self.recv_hwm = hwm;
801 self
802 }
803
804 /// Set send high water mark.
805 pub const fn with_send_hwm(mut self, hwm: usize) -> Self {
806 self.send_hwm = hwm;
807 self
808 }
809
810 /// Enable or disable immediate mode.
811 pub const fn with_immediate(mut self, immediate: bool) -> Self {
812 self.immediate = immediate;
813 self
814 }
815
816 /// Set maximum message size.
817 pub const fn with_max_msg_size(mut self, size: Option<usize>) -> Self {
818 self.max_msg_size = size;
819 self
820 }
821
822 /// Set read buffer size.
823 ///
824 /// # Examples
825 ///
826 /// ```
827 /// use monocoque_core::options::SocketOptions;
828 ///
829 /// // Small buffers for low latency
830 /// let opts = SocketOptions::new().with_read_buffer_size(4096);
831 ///
832 /// // Large buffers for throughput
833 /// let opts = SocketOptions::new().with_read_buffer_size(16384);
834 /// ```
835 pub const fn with_read_buffer_size(mut self, size: usize) -> Self {
836 self.read_buffer_size = if size > crate::io::READ_SLAB_SIZE {
837 crate::io::READ_SLAB_SIZE
838 } else {
839 size
840 };
841 self
842 }
843
844 /// Set write buffer size.
845 pub const fn with_write_buffer_size(mut self, size: usize) -> Self {
846 self.write_buffer_size = size;
847 self
848 }
849
850 /// Set both read and write buffer sizes (convenience method).
851 ///
852 /// # Examples
853 ///
854 /// ```
855 /// use monocoque_core::options::SocketOptions;
856 ///
857 /// // Small buffers for both
858 /// let opts = SocketOptions::new().with_buffer_sizes(4096, 4096);
859 /// ```
860 pub const fn with_buffer_sizes(mut self, read_size: usize, write_size: usize) -> Self {
861 self.read_buffer_size = if read_size > crate::io::READ_SLAB_SIZE {
862 crate::io::READ_SLAB_SIZE
863 } else {
864 read_size
865 };
866 self.write_buffer_size = write_size;
867 self
868 }
869
870 /// Set socket routing ID / identity.
871 ///
872 /// # Examples
873 ///
874 /// ```
875 /// use monocoque_core::options::SocketOptions;
876 /// use bytes::Bytes;
877 ///
878 /// let opts = SocketOptions::new()
879 /// .with_routing_id(Bytes::from_static(b"worker-01"));
880 /// ```
881 pub fn with_routing_id(mut self, id: bytes::Bytes) -> Self {
882 self.routing_id = Some(id);
883 self
884 }
885
886 /// Set connect routing ID for the next connection.
887 ///
888 /// This option is consumed after each connect operation and must be set
889 /// again for subsequent connections.
890 ///
891 /// # Examples
892 ///
893 /// ```
894 /// use monocoque_core::options::SocketOptions;
895 /// use bytes::Bytes;
896 ///
897 /// let opts = SocketOptions::new()
898 /// .with_connect_routing_id(Bytes::from_static(b"client-001"));
899 /// ```
900 pub fn with_connect_routing_id(mut self, id: bytes::Bytes) -> Self {
901 self.connect_routing_id = Some(id);
902 self
903 }
904
905 /// Enable ROUTER mandatory mode.
906 pub const fn with_router_mandatory(mut self, enabled: bool) -> Self {
907 self.router_mandatory = enabled;
908 self
909 }
910
911 /// Enable ROUTER handover mode.
912 pub const fn with_router_handover(mut self, enabled: bool) -> Self {
913 self.router_handover = enabled;
914 self
915 }
916
917 /// Enable ROUTER probe on connect.
918 pub const fn with_probe_router(mut self, enabled: bool) -> Self {
919 self.probe_router = enabled;
920 self
921 }
922
923 /// Enable XPUB verbose mode.
924 pub const fn with_xpub_verbose(mut self, enabled: bool) -> Self {
925 self.xpub_verbose = enabled;
926 self
927 }
928
929 /// Enable XPUB manual mode.
930 pub const fn with_xpub_manual(mut self, enabled: bool) -> Self {
931 self.xpub_manual = enabled;
932 self
933 }
934
935 /// Set XPUB welcome message.
936 pub fn with_xpub_welcome_msg(mut self, msg: bytes::Bytes) -> Self {
937 self.xpub_welcome_msg = Some(msg);
938 self
939 }
940
941 /// Enable XSUB verbose unsubscribe.
942 pub const fn with_xsub_verbose_unsubs(mut self, enabled: bool) -> Self {
943 self.xsub_verbose_unsubs = enabled;
944 self
945 }
946
947 /// Enable message conflation (keep only last message).
948 pub const fn with_conflate(mut self, enabled: bool) -> Self {
949 self.conflate = enabled;
950 self
951 }
952
953 /// Set TCP keepalive mode.
954 ///
955 /// # Arguments
956 ///
957 /// * `mode` - `-1` for OS default, `0` to disable, `1` to enable
958 pub const fn with_tcp_keepalive(mut self, mode: i32) -> Self {
959 self.tcp_keepalive = mode;
960 self
961 }
962
963 /// Set TCP keepalive count (number of probes before timeout).
964 ///
965 /// # Arguments
966 ///
967 /// * `count` - `-1` for OS default, `> 0` for specific count
968 pub const fn with_tcp_keepalive_cnt(mut self, count: i32) -> Self {
969 self.tcp_keepalive_cnt = count;
970 self
971 }
972
973 /// Set TCP keepalive idle time (seconds before first probe).
974 ///
975 /// # Arguments
976 ///
977 /// * `seconds` - `-1` for OS default, `> 0` for specific idle time
978 pub const fn with_tcp_keepalive_idle(mut self, seconds: i32) -> Self {
979 self.tcp_keepalive_idle = seconds;
980 self
981 }
982
983 /// Set TCP keepalive interval (seconds between probes).
984 ///
985 /// # Arguments
986 ///
987 /// * `seconds` - `-1` for OS default, `> 0` for specific interval
988 pub const fn with_tcp_keepalive_intvl(mut self, seconds: i32) -> Self {
989 self.tcp_keepalive_intvl = seconds;
990 self
991 }
992
993 /// Enable REQ correlation mode (match replies to requests).
994 pub const fn with_req_correlate(mut self, enabled: bool) -> Self {
995 self.req_correlate = enabled;
996 self
997 }
998
999 /// Enable REQ relaxed mode (allow multiple outstanding requests).
1000 pub const fn with_req_relaxed(mut self, enabled: bool) -> Self {
1001 self.req_relaxed = enabled;
1002 self
1003 }
1004
1005 /// Set multicast rate (`ZMQ_RATE`).
1006 pub const fn with_rate(mut self, rate: i32) -> Self {
1007 self.rate = rate;
1008 self
1009 }
1010
1011 /// Set multicast recovery interval (`ZMQ_RECOVERY_IVL`).
1012 pub const fn with_recovery_ivl(mut self, interval: Duration) -> Self {
1013 self.recovery_ivl = interval;
1014 self
1015 }
1016
1017 /// Set OS send buffer size (`ZMQ_SNDBUF`).
1018 pub const fn with_sndbuf(mut self, size: i32) -> Self {
1019 self.sndbuf = size;
1020 self
1021 }
1022
1023 /// Set OS receive buffer size (`ZMQ_RCVBUF`).
1024 pub const fn with_rcvbuf(mut self, size: i32) -> Self {
1025 self.rcvbuf = size;
1026 self
1027 }
1028
1029 /// Bind listeners with `SO_REUSEPORT` (Unix only) so multiple acceptors can
1030 /// share one port. See [`SocketOptions::reuse_port`].
1031 pub const fn with_reuse_port(mut self, enabled: bool) -> Self {
1032 self.reuse_port = enabled;
1033 self
1034 }
1035
1036 /// Set multicast TTL/hops (`ZMQ_MULTICAST_HOPS`).
1037 pub const fn with_multicast_hops(mut self, hops: i32) -> Self {
1038 self.multicast_hops = hops;
1039 self
1040 }
1041
1042 /// Set IP Type of Service (`ZMQ_TOS`).
1043 pub const fn with_tos(mut self, tos: i32) -> Self {
1044 self.tos = tos;
1045 self
1046 }
1047
1048 /// Set multicast maximum TPU (`ZMQ_MULTICAST_MAXTPDU`).
1049 pub const fn with_multicast_maxtpdu(mut self, mtu: i32) -> Self {
1050 self.multicast_maxtpdu = mtu;
1051 self
1052 }
1053
1054 /// Enable IPv6 support (`ZMQ_IPV6`).
1055 pub const fn with_ipv6(mut self, enabled: bool) -> Self {
1056 self.ipv6 = enabled;
1057 self
1058 }
1059
1060 /// Bind to specific device (`ZMQ_BINDTODEVICE`) - Linux only.
1061 pub fn with_bind_to_device(mut self, device: impl Into<String>) -> Self {
1062 self.bind_to_device = Some(device.into());
1063 self
1064 }
1065
1066 // --- Security Options ---
1067
1068 /// Enable PLAIN server mode.
1069 ///
1070 /// # Examples
1071 ///
1072 /// ```
1073 /// use monocoque_core::options::SocketOptions;
1074 ///
1075 /// let opts = SocketOptions::new().with_plain_server(true);
1076 /// ```
1077 pub const fn with_plain_server(mut self, enabled: bool) -> Self {
1078 self.plain_server = enabled;
1079 self
1080 }
1081
1082 /// Set PLAIN client credentials.
1083 ///
1084 /// # Examples
1085 ///
1086 /// ```
1087 /// use monocoque_core::options::SocketOptions;
1088 ///
1089 /// let opts = SocketOptions::new()
1090 /// .with_plain_credentials("admin", "secret123");
1091 /// ```
1092 pub fn with_plain_credentials(
1093 mut self,
1094 username: impl Into<String>,
1095 password: impl Into<String>,
1096 ) -> Self {
1097 self.plain_username = Some(username.into());
1098 self.plain_password = Some(password.into());
1099 self
1100 }
1101
1102 /// Enable CURVE server mode.
1103 ///
1104 /// # Examples
1105 ///
1106 /// ```
1107 /// use monocoque_core::options::SocketOptions;
1108 ///
1109 /// let opts = SocketOptions::new().with_curve_server(true);
1110 /// ```
1111 pub const fn with_curve_server(mut self, enabled: bool) -> Self {
1112 self.curve_server = enabled;
1113 self
1114 }
1115
1116 /// Set CURVE client keys (public + secret).
1117 ///
1118 /// # Examples
1119 ///
1120 /// ```
1121 /// use monocoque_core::options::SocketOptions;
1122 ///
1123 /// let public = [0u8; 32]; // Replace with actual key
1124 /// let secret = [0u8; 32]; // Replace with actual key
1125 /// let opts = SocketOptions::new().with_curve_keypair(public, secret);
1126 /// ```
1127 pub const fn with_curve_keypair(mut self, publickey: [u8; 32], secretkey: [u8; 32]) -> Self {
1128 self.curve_publickey = Some(publickey);
1129 self.curve_secretkey = Some(secretkey);
1130 self
1131 }
1132
1133 /// Set CURVE server public key (for client).
1134 ///
1135 /// # Examples
1136 ///
1137 /// ```
1138 /// use monocoque_core::options::SocketOptions;
1139 ///
1140 /// let server_key = [0u8; 32]; // Server's public key
1141 /// let opts = SocketOptions::new().with_curve_serverkey(server_key);
1142 /// ```
1143 pub const fn with_curve_serverkey(mut self, serverkey: [u8; 32]) -> Self {
1144 self.curve_serverkey = Some(serverkey);
1145 self
1146 }
1147
1148 /// Set ZAP domain for authentication.
1149 ///
1150 /// # Examples
1151 ///
1152 /// ```
1153 /// use monocoque_core::options::SocketOptions;
1154 ///
1155 /// let opts = SocketOptions::new().with_zap_domain("production");
1156 /// ```
1157 pub fn with_zap_domain(mut self, domain: impl Into<String>) -> Self {
1158 self.zap_domain = domain.into();
1159 self
1160 }
1161
1162 /// Add a subscription filter for SUB/XSUB sockets (`ZMQ_SUBSCRIBE`).
1163 ///
1164 /// SUB sockets MUST subscribe to at least one topic to receive messages.
1165 /// An empty filter (b"" or `Bytes::new()`) subscribes to all messages.
1166 ///
1167 /// # Examples
1168 ///
1169 /// ```
1170 /// use monocoque_core::options::SocketOptions;
1171 /// use bytes::Bytes;
1172 ///
1173 /// // Subscribe to all messages
1174 /// let opts = SocketOptions::new().with_subscribe(Bytes::new());
1175 ///
1176 /// // Subscribe to specific topics
1177 /// let opts = SocketOptions::new()
1178 /// .with_subscribe(Bytes::from("weather."))
1179 /// .with_subscribe(Bytes::from("stocks."));
1180 /// ```
1181 pub fn with_subscribe(mut self, filter: bytes::Bytes) -> Self {
1182 self.subscriptions.push(filter);
1183 self
1184 }
1185
1186 /// Add multiple subscription filters for SUB/XSUB sockets.
1187 ///
1188 /// Convenience method to subscribe to multiple topics at once.
1189 ///
1190 /// # Examples
1191 ///
1192 /// ```
1193 /// use monocoque_core::options::SocketOptions;
1194 /// use bytes::Bytes;
1195 ///
1196 /// let opts = SocketOptions::new()
1197 /// .with_subscriptions(vec![
1198 /// Bytes::from("weather."),
1199 /// Bytes::from("stocks."),
1200 /// ]);
1201 /// ```
1202 pub fn with_subscriptions(mut self, filters: Vec<bytes::Bytes>) -> Self {
1203 self.subscriptions.extend(filters);
1204 self
1205 }
1206
1207 /// Add an unsubscription filter for SUB/XSUB sockets (`ZMQ_UNSUBSCRIBE`).
1208 ///
1209 /// Removes a previously added subscription filter.
1210 ///
1211 /// # Examples
1212 ///
1213 /// ```
1214 /// use monocoque_core::options::SocketOptions;
1215 /// use bytes::Bytes;
1216 ///
1217 /// let opts = SocketOptions::new()
1218 /// .with_subscribe(Bytes::new()) // Subscribe to all
1219 /// .with_unsubscribe(Bytes::from("admin.")); // Except admin topics
1220 /// ```
1221 pub fn with_unsubscribe(mut self, filter: bytes::Bytes) -> Self {
1222 self.unsubscriptions.push(filter);
1223 self
1224 }
1225
1226 // --- Query Methods ---
1227
1228 /// Check if receive operation should be non-blocking.
1229 pub const fn is_recv_nonblocking(&self) -> bool {
1230 matches!(self.recv_timeout, Some(d) if d.is_zero())
1231 }
1232
1233 /// Check if send operation should be non-blocking.
1234 pub const fn is_send_nonblocking(&self) -> bool {
1235 matches!(self.send_timeout, Some(d) if d.is_zero())
1236 }
1237
1238 /// Validate routing ID for use with ROUTER sockets.
1239 ///
1240 /// ROUTER socket identities must:
1241 /// - Be 1-255 bytes long
1242 /// - Not start with null byte (0x00) which is reserved for auto-generated IDs
1243 pub fn validate_router_identity(id: &[u8]) -> std::io::Result<()> {
1244 if id.is_empty() {
1245 return Err(std::io::Error::new(
1246 std::io::ErrorKind::InvalidInput,
1247 "routing ID cannot be empty",
1248 ));
1249 }
1250
1251 if id.len() > 255 {
1252 return Err(std::io::Error::new(
1253 std::io::ErrorKind::InvalidInput,
1254 format!("routing ID cannot exceed 255 bytes (got {})", id.len()),
1255 ));
1256 }
1257
1258 if id[0] == 0x00 {
1259 return Err(std::io::Error::new(
1260 std::io::ErrorKind::InvalidInput,
1261 "routing ID cannot start with null byte (reserved for auto-generated IDs)",
1262 ));
1263 }
1264
1265 Ok(())
1266 }
1267
1268 /// Validate general routing ID (for DEALER, REQ, REP).
1269 ///
1270 /// Less strict than ROUTER identities - allows null prefix.
1271 pub fn validate_routing_id(id: &[u8]) -> std::io::Result<()> {
1272 if id.len() > 255 {
1273 return Err(std::io::Error::new(
1274 std::io::ErrorKind::InvalidInput,
1275 format!("routing ID cannot exceed 255 bytes (got {})", id.len()),
1276 ));
1277 }
1278 Ok(())
1279 }
1280
1281 /// Get the current reconnection interval with exponential backoff.
1282 ///
1283 /// Returns the interval to use, considering exponential backoff
1284 /// and the maximum interval setting.
1285 pub fn next_reconnect_ivl(&self, attempt: u32) -> Duration {
1286 if self.reconnect_ivl_max.is_zero() {
1287 // No exponential backoff, always use base interval
1288 return self.reconnect_ivl;
1289 }
1290
1291 // Calculate exponential backoff: base * 2^attempt
1292 let backoff = self
1293 .reconnect_ivl
1294 .saturating_mul(2u32.saturating_pow(attempt));
1295
1296 // Cap at maximum interval
1297 backoff.min(self.reconnect_ivl_max)
1298 }
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use super::*;
1304
1305 #[test]
1306 fn test_default_options() {
1307 let opts = SocketOptions::default();
1308 assert!(opts.recv_timeout.is_none());
1309 assert!(opts.send_timeout.is_none());
1310 assert_eq!(opts.handshake_timeout, Duration::from_secs(30));
1311 assert_eq!(opts.reconnect_ivl, Duration::from_millis(100));
1312 assert_eq!(opts.recv_hwm, 1000);
1313 assert_eq!(opts.send_hwm, 1000);
1314 }
1315
1316 #[test]
1317 fn test_builder_pattern() {
1318 let opts = SocketOptions::new()
1319 .with_recv_timeout(Duration::from_secs(5))
1320 .with_send_timeout(Duration::from_secs(10))
1321 .with_recv_hwm(2000);
1322
1323 assert_eq!(opts.recv_timeout, Some(Duration::from_secs(5)));
1324 assert_eq!(opts.send_timeout, Some(Duration::from_secs(10)));
1325 assert_eq!(opts.recv_hwm, 2000);
1326 }
1327
1328 #[test]
1329 fn test_nonblocking_checks() {
1330 let blocking = SocketOptions::new();
1331 assert!(!blocking.is_recv_nonblocking());
1332 assert!(!blocking.is_send_nonblocking());
1333
1334 let nonblocking = SocketOptions::new()
1335 .with_recv_timeout(Duration::ZERO)
1336 .with_send_timeout(Duration::ZERO);
1337 assert!(nonblocking.is_recv_nonblocking());
1338 assert!(nonblocking.is_send_nonblocking());
1339 }
1340
1341 #[test]
1342 fn test_exponential_backoff() {
1343 let opts = SocketOptions::new()
1344 .with_reconnect_ivl(Duration::from_millis(100))
1345 .with_reconnect_ivl_max(Duration::from_secs(10));
1346
1347 // First attempt: 100ms
1348 assert_eq!(opts.next_reconnect_ivl(0), Duration::from_millis(100));
1349
1350 // Second attempt: 200ms
1351 assert_eq!(opts.next_reconnect_ivl(1), Duration::from_millis(200));
1352
1353 // Third attempt: 400ms
1354 assert_eq!(opts.next_reconnect_ivl(2), Duration::from_millis(400));
1355
1356 // Eventually caps at 10s
1357 assert_eq!(opts.next_reconnect_ivl(10), Duration::from_secs(10));
1358 }
1359
1360 #[test]
1361 fn test_no_exponential_backoff() {
1362 let opts = SocketOptions::new().with_reconnect_ivl(Duration::from_millis(100));
1363 // reconnect_ivl_max is 0 by default
1364
1365 // Always returns base interval
1366 assert_eq!(opts.next_reconnect_ivl(0), Duration::from_millis(100));
1367 assert_eq!(opts.next_reconnect_ivl(1), Duration::from_millis(100));
1368 assert_eq!(opts.next_reconnect_ivl(10), Duration::from_millis(100));
1369 }
1370
1371 #[test]
1372 fn test_routing_id_validation() {
1373 // Valid ROUTER identities
1374 assert!(SocketOptions::validate_router_identity(b"client-001").is_ok());
1375 assert!(SocketOptions::validate_router_identity(&[0x01; 255]).is_ok());
1376
1377 // Invalid: empty
1378 assert!(SocketOptions::validate_router_identity(b"").is_err());
1379
1380 // Invalid: too long
1381 assert!(SocketOptions::validate_router_identity(&[0x01; 256]).is_err());
1382
1383 // Invalid: starts with null byte
1384 assert!(SocketOptions::validate_router_identity(b"\x00client").is_err());
1385 }
1386
1387 #[test]
1388 fn test_general_routing_id_validation() {
1389 // Valid
1390 assert!(SocketOptions::validate_routing_id(b"").is_ok()); // Empty allowed
1391 assert!(SocketOptions::validate_routing_id(b"\x00client").is_ok()); // Null prefix allowed
1392 assert!(SocketOptions::validate_routing_id(&[0x00; 255]).is_ok());
1393
1394 // Invalid: too long
1395 assert!(SocketOptions::validate_routing_id(&[0x01; 256]).is_err());
1396 }
1397
1398 #[test]
1399 fn test_connect_routing_id() {
1400 let opts =
1401 SocketOptions::new().with_connect_routing_id(bytes::Bytes::from_static(b"peer-123"));
1402
1403 assert_eq!(
1404 opts.connect_routing_id,
1405 Some(bytes::Bytes::from_static(b"peer-123"))
1406 );
1407 }
1408
1409 #[test]
1410 fn debug_output_redacts_security_options() {
1411 let opts = SocketOptions::new()
1412 .with_plain_credentials("alice", "super-secret-password")
1413 .with_curve_keypair([1u8; 32], [7u8; 32]);
1414
1415 let debug = format!("{opts:?}");
1416
1417 assert!(
1418 !debug.contains("super-secret-password"),
1419 "SocketOptions Debug output exposes the PLAIN password"
1420 );
1421 assert!(
1422 !debug.contains("curve_secretkey: Some([7, 7, 7"),
1423 "SocketOptions Debug output exposes the CURVE secret key"
1424 );
1425 }
1426
1427 #[test]
1428 fn read_buffer_size_cannot_exceed_read_slab_size() {
1429 let opts = SocketOptions::new().with_read_buffer_size(crate::io::READ_SLAB_SIZE + 1);
1430
1431 assert!(
1432 opts.read_buffer_size <= crate::io::READ_SLAB_SIZE,
1433 "SocketOptions allowed a read buffer size larger than the read slab"
1434 );
1435 }
1436
1437 #[test]
1438 fn test_router_options() {
1439 let opts = SocketOptions::new()
1440 .with_router_mandatory(true)
1441 .with_router_handover(true);
1442
1443 assert!(opts.router_mandatory);
1444 assert!(opts.router_handover);
1445 }
1446
1447 #[test]
1448 fn test_subscription_options() {
1449 // Test with_subscribe
1450 let opts = SocketOptions::new()
1451 .with_subscribe(bytes::Bytes::new()) // Subscribe to all
1452 .with_subscribe(bytes::Bytes::from("weather."))
1453 .with_subscribe(bytes::Bytes::from("stocks."));
1454
1455 assert_eq!(opts.subscriptions.len(), 3);
1456 assert_eq!(opts.subscriptions[0], bytes::Bytes::new());
1457 assert_eq!(opts.subscriptions[1], bytes::Bytes::from("weather."));
1458 assert_eq!(opts.subscriptions[2], bytes::Bytes::from("stocks."));
1459
1460 // Test with_subscriptions
1461 let opts2 = SocketOptions::new().with_subscriptions(vec![
1462 bytes::Bytes::from("topic1"),
1463 bytes::Bytes::from("topic2"),
1464 ]);
1465
1466 assert_eq!(opts2.subscriptions.len(), 2);
1467
1468 // Test with_unsubscribe
1469 let opts3 = opts.with_unsubscribe(bytes::Bytes::from("admin."));
1470 assert_eq!(opts3.unsubscriptions.len(), 1);
1471 assert_eq!(opts3.unsubscriptions[0], bytes::Bytes::from("admin."));
1472 }
1473}