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