Skip to main content

monocoque_core/
options.rs

1//! Socket configuration options
2//!
3//! This module provides configuration options for `ZeroMQ` sockets, similar to
4//! libzmq's socket options (`zmq_setsockopt/zmq_getsockopt`).
5
6use std::{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}