monocoque/zmq/router.rs
1//! ROUTER socket implementation.
2
3use super::common::channel_to_io_error;
4use bytes::Bytes;
5use monocoque_core::monitor::{SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::options::SocketOptions;
7use monocoque_core::rt::{TcpListener, TcpStream};
8use monocoque_zmtp::SocketType;
9use monocoque_zmtp::router::RouterSocket as InternalRouter;
10use std::io;
11
12/// A ROUTER socket for identity-based routing.
13///
14/// ROUTER sockets prefix incoming messages with the sender's identity,
15/// and route outgoing messages based on the first frame (identity).
16/// They're used for:
17///
18/// - Async request-reply servers
19/// - Brokers and proxies
20/// - Stateful connection tracking
21///
22/// ## ZeroMQ Compatibility
23///
24/// Compatible with `zmq::ROUTER` and `zmq::DEALER` sockets from libzmq.
25///
26/// ## Message Format
27///
28/// **Incoming**: `[identity, delimiter, ...user_frames]`\
29/// **Outgoing**: `[identity, delimiter, ...user_frames]` (routes to peer with that identity)
30///
31/// ## Example
32///
33/// ```rust,no_run
34/// use monocoque::zmq::RouterSocket;
35///
36/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37/// // Bind and accept first connection
38/// let (listener, mut socket) = RouterSocket::bind("127.0.0.1:5555").await?;
39///
40/// // Echo server
41/// while let Ok(Some(msg)) = socket.recv().await {
42/// // msg[0] = identity, msg[1] = delimiter, msg[2+] = payload
43/// socket.send(msg).await?; // Echo back to sender
44/// }
45/// # Ok(())
46/// # }
47/// ```
48pub struct RouterSocket<S = TcpStream>
49where
50 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
51{
52 inner: InternalRouter<S>,
53 monitor: Option<SocketEventSender>,
54}
55
56impl RouterSocket {
57 /// Bind to an address and accept the first connection.
58 ///
59 /// This is the recommended way to create a server-side ROUTER socket.
60 /// It handles TCP binding, accepting the first connection, and ZMTP handshake.
61 ///
62 /// # Returns
63 ///
64 /// A tuple of `(listener, socket)` where:
65 /// - `listener` can be used to accept additional connections
66 /// - `socket` is ready to send/receive with the first peer
67 ///
68 /// # Errors
69 ///
70 /// Returns an error if:
71 /// - The address is already in use
72 /// - Permission denied (e.g., binding to privileged port without root)
73 /// - Invalid address format
74 ///
75 /// # Example
76 ///
77 /// ```rust,no_run
78 /// use monocoque::zmq::RouterSocket;
79 ///
80 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
81 /// let (listener, socket) = RouterSocket::bind("127.0.0.1:5555").await?;
82 ///
83 /// // Use socket for first connection
84 /// // Accept more connections from listener if needed:
85 /// // let (stream, _) = listener.accept().await?;
86 /// // let socket2 = RouterSocket::from_stream(stream).await;
87 /// # Ok(())
88 /// # }
89 /// ```
90 pub async fn bind(
91 addr: impl monocoque_core::rt::ToSocketAddrs,
92 ) -> io::Result<(TcpListener, Self)> {
93 let listener = TcpListener::bind(addr).await?;
94 let (stream, _) = listener.accept().await?;
95 let socket = Self::from_tcp(stream).await?;
96 Ok((listener, socket))
97 }
98
99 /// Create a ROUTER socket from an existing TCP stream.
100 ///
101 /// **Deprecated**: Use [`RouterSocket::from_tcp()`] instead to enable TCP_NODELAY for optimal latency.
102 ///
103 /// Use this for advanced scenarios or when accepting multiple connections
104 /// from a listener.
105 ///
106 /// # Example
107 ///
108 /// ```rust,no_run
109 /// use monocoque::zmq::RouterSocket;
110 /// use monocoque_core::rt::TcpListener;
111 ///
112 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
113 /// let listener = TcpListener::bind("127.0.0.1:5555").await?;
114 ///
115 /// loop {
116 /// let (stream, addr) = listener.accept().await?;
117 /// println!("New connection from {}", addr);
118 /// // Prefer this:
119 /// let socket = RouterSocket::from_tcp(stream).await?;
120 /// // Over this:
121 /// // let socket = RouterSocket::from_stream(stream).await;
122 /// // Handle socket (e.g., spawn task)
123 /// }
124 /// # Ok(())
125 /// # }
126 /// ```
127 #[deprecated(
128 since = "0.1.0",
129 note = "Use `from_tcp()` instead to enable TCP_NODELAY"
130 )]
131 pub async fn from_stream(stream: TcpStream) -> io::Result<Self> {
132 Ok(Self {
133 inner: InternalRouter::new(stream).await?,
134 monitor: None,
135 })
136 }
137
138 /// Create a ROUTER socket from an existing TCP stream with TCP_NODELAY enabled.
139 pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
140 Ok(Self {
141 inner: InternalRouter::from_tcp(stream).await?,
142 monitor: None,
143 })
144 }
145
146 /// Create a ROUTER socket from a TCP stream with custom options.
147 pub async fn from_tcp_with_options(
148 stream: TcpStream,
149 options: monocoque_core::options::SocketOptions,
150 ) -> io::Result<Self> {
151 Ok(Self {
152 inner: InternalRouter::from_tcp_with_options(stream, options).await?,
153 monitor: None,
154 })
155 }
156
157 /// Create a ROUTER socket from any stream with custom options.
158 pub async fn with_options<Stream>(
159 stream: Stream,
160 options: monocoque_core::options::SocketOptions,
161 ) -> io::Result<RouterSocket<Stream>>
162 where
163 Stream: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
164 {
165 Ok(RouterSocket {
166 inner: InternalRouter::with_options(stream, options).await?,
167 monitor: None,
168 })
169 }
170}
171
172// Generic impl - works with any stream type
173impl<S> RouterSocket<S>
174where
175 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
176{
177 /// Enable monitoring for this socket.
178 ///
179 /// Returns a receiver for socket lifecycle events. Once enabled, the socket
180 /// will emit events like Accepted, Disconnected, etc.
181 ///
182 /// # Example
183 ///
184 /// ```rust,no_run
185 /// use monocoque::zmq::RouterSocket;
186 ///
187 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
188 /// let (_listener, mut socket) = RouterSocket::bind("127.0.0.1:5555").await?;
189 /// let monitor = socket.monitor();
190 ///
191 /// // Spawn task to handle events
192 /// monocoque::rt::spawn_detached(async move {
193 /// while let Ok(event) = monitor.recv_async().await {
194 /// println!("Socket event: {}", event);
195 /// }
196 /// });
197 /// # Ok(())
198 /// # }
199 /// ```
200 pub fn monitor(&mut self) -> SocketMonitor {
201 let (sender, receiver) = create_monitor();
202 self.monitor = Some(sender);
203 receiver
204 }
205
206 /// Get a mutable reference to this socket's options.
207 #[inline]
208 pub fn options_mut(&mut self) -> &mut SocketOptions {
209 self.inner.options_mut()
210 }
211
212 /// Send a multipart message.
213 ///
214 /// The first frame must be the peer identity to route to.
215 /// Messages are sent asynchronously.
216 ///
217 /// # Errors
218 ///
219 /// Returns an error if the underlying connection is closed or broken.
220 ///
221 /// # Example
222 ///
223 /// ```rust,no_run
224 /// # use monocoque::zmq::RouterSocket;
225 /// # use bytes::Bytes;
226 /// # async fn example(mut socket: RouterSocket, identity: Bytes) -> Result<(), Box<dyn std::error::Error>> {
227 /// socket.send(vec![
228 /// identity, // Route to this peer
229 /// Bytes::new(), // Delimiter
230 /// Bytes::from("reply"), // Payload
231 /// ]).await?;
232 /// # Ok(())
233 /// # }
234 /// ```
235 pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
236 channel_to_io_error(self.inner.send(msg).await)
237 }
238
239 /// Send a message to the internal buffer without flushing.
240 ///
241 /// Use this for batching multiple messages before a single flush.
242 pub fn send_buffered(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
243 channel_to_io_error(self.inner.send_buffered(msg))
244 }
245
246 /// Flush all buffered messages to the network.
247 pub async fn flush(&mut self) -> io::Result<()> {
248 channel_to_io_error(self.inner.flush().await)
249 }
250
251 /// Send multiple messages in a single batch.
252 pub async fn send_batch(&mut self, messages: &[Vec<Bytes>]) -> io::Result<()> {
253 channel_to_io_error(self.inner.send_batch(messages).await)
254 }
255
256 /// Get the number of bytes currently buffered.
257 #[inline]
258 pub fn buffered_bytes(&self) -> usize {
259 self.inner.buffered_bytes()
260 }
261
262 /// Get the socket type.
263 ///
264 /// # ZeroMQ Compatibility
265 ///
266 /// Corresponds to `ZMQ_TYPE` (16) option.
267 #[inline]
268 pub const fn socket_type() -> SocketType {
269 SocketType::Router
270 }
271
272 /// Get the endpoint this socket is connected/bound to, if available.
273 ///
274 /// Returns `None` if the socket was created from a raw stream.
275 ///
276 /// # ZeroMQ Compatibility
277 ///
278 /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) option.
279 #[inline]
280 pub fn last_endpoint(&self) -> Option<&monocoque_core::endpoint::Endpoint> {
281 self.inner.last_endpoint()
282 }
283
284 /// Check if the last received message has more frames coming.
285 ///
286 /// Returns `true` if there are more frames in the current multipart message.
287 ///
288 /// # ZeroMQ Compatibility
289 ///
290 /// Corresponds to `ZMQ_RCVMORE` (13) option.
291 #[inline]
292 pub fn has_more(&self) -> bool {
293 self.inner.has_more()
294 }
295
296 /// Get the event state of the socket.
297 ///
298 /// Returns a bitmask indicating ready-to-receive and ready-to-send states.
299 ///
300 /// # Returns
301 ///
302 /// - `1` (POLLIN) - Socket is ready to receive
303 /// - `2` (POLLOUT) - Socket is ready to send
304 /// - `3` (POLLIN | POLLOUT) - Socket is ready for both
305 ///
306 /// # ZeroMQ Compatibility
307 ///
308 /// Corresponds to `ZMQ_EVENTS` (15) option.
309 #[inline]
310 pub fn events(&self) -> u32 {
311 self.inner.events()
312 }
313
314 /// Set the routing identity for the next accepted connection.
315 ///
316 /// This identity will be used for the next peer that connects to this ROUTER.
317 /// The option is consumed after the connection and must be set again for
318 /// subsequent connections.
319 ///
320 /// # Arguments
321 ///
322 /// * `id` - The identity to assign (1-255 bytes, cannot start with null byte)
323 ///
324 /// # Errors
325 ///
326 /// Returns an error if the identity is invalid (empty, too long, or starts
327 /// with null byte).
328 ///
329 /// # ZeroMQ Compatibility
330 ///
331 /// Corresponds to `ZMQ_CONNECT_ROUTING_ID` (62).
332 ///
333 /// # Example
334 ///
335 /// ```rust,no_run
336 /// use monocoque::zmq::RouterSocket;
337 /// use bytes::Bytes;
338 ///
339 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
340 /// let (_listener, mut router) = RouterSocket::bind("tcp://0.0.0.0:5555").await?;
341 ///
342 /// // Assign explicit identity to next connection
343 /// router.set_connect_routing_id(b"worker-001".to_vec())?;
344 ///
345 /// // When a peer connects, it will be identified as "worker-001"
346 /// # Ok(())
347 /// # }
348 /// ```
349 pub fn set_connect_routing_id(&mut self, id: Vec<u8>) -> io::Result<()> {
350 // Validate identity for ROUTER socket
351 monocoque_core::options::SocketOptions::validate_router_identity(&id)?;
352 self.inner.options_mut().connect_routing_id = Some(Bytes::from(id));
353 Ok(())
354 }
355
356 /// Enable or disable ROUTER_MANDATORY mode.
357 ///
358 /// When enabled, sending to an unknown identity returns an error.
359 /// When disabled (default), messages to unknown identities are silently dropped.
360 ///
361 /// **Note**: The current single-peer ROUTER implementation doesn't have a
362 /// routing table yet, so this option affects future multi-peer support.
363 ///
364 /// # ZeroMQ Compatibility
365 ///
366 /// Corresponds to `ZMQ_ROUTER_MANDATORY` (33).
367 ///
368 /// # Example
369 ///
370 /// ```rust,no_run
371 /// # use monocoque::zmq::RouterSocket;
372 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
373 /// let (_listener, mut router) = RouterSocket::bind("tcp://0.0.0.0:5555").await?;
374 ///
375 /// // Fail fast if routing to unknown peer
376 /// router.set_router_mandatory(true);
377 /// # Ok(())
378 /// # }
379 /// ```
380 pub fn set_router_mandatory(&mut self, enabled: bool) {
381 self.inner.options_mut().router_mandatory = enabled;
382 }
383
384 /// Enable or disable ROUTER_HANDOVER mode.
385 ///
386 /// When enabled, a new connection with an existing identity will take over
387 /// that identity, closing the old connection.
388 /// When disabled (default), duplicate identities are rejected.
389 ///
390 /// **Note**: The current single-peer ROUTER implementation doesn't have a
391 /// routing table yet, so this option affects future multi-peer support.
392 ///
393 /// # ZeroMQ Compatibility
394 ///
395 /// Corresponds to `ZMQ_ROUTER_HANDOVER` (56).
396 ///
397 /// # Example
398 ///
399 /// ```rust,no_run
400 /// # use monocoque::zmq::RouterSocket;
401 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
402 /// let (_listener, mut router) = RouterSocket::bind("tcp://0.0.0.0:5555").await?;
403 ///
404 /// // Allow identity takeover for reconnecting clients
405 /// router.set_router_handover(true);
406 /// # Ok(())
407 /// # }
408 /// ```
409 pub fn set_router_handover(&mut self, enabled: bool) {
410 self.inner.options_mut().router_handover = enabled;
411 }
412
413 /// Get the peer identity for this connection.
414 ///
415 /// Returns the identity of the connected peer. This is either:
416 /// - The identity set via `set_connect_routing_id()`
417 /// - The peer's self-reported identity from the handshake
418 /// - An auto-generated identity
419 ///
420 /// # Example
421 ///
422 /// ```rust,no_run
423 /// # use monocoque::zmq::RouterSocket;
424 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
425 /// let (_listener, router) = RouterSocket::bind("tcp://0.0.0.0:5555").await?;
426 ///
427 /// let identity = router.peer_identity();
428 /// println!("Peer identity: {:?}", identity);
429 /// # Ok(())
430 /// # }
431 /// ```
432 pub const fn peer_identity(&self) -> &Bytes {
433 self.inner.peer_identity()
434 }
435
436 /// Receive a multipart message.
437 ///
438 /// The returned message will have the sender's identity as the first frame,
439 /// followed by a delimiter, then the payload frames.
440 ///
441 /// Returns `None` if the connection is closed.
442 ///
443 /// # Example
444 ///
445 /// ```rust,no_run
446 /// # use monocoque::zmq::RouterSocket;
447 /// # async fn example(mut socket: RouterSocket) -> Result<(), Box<dyn std::error::Error>> {
448 /// while let Ok(Some(msg)) = socket.recv().await {
449 /// let identity = &msg[0];
450 /// let payload = &msg[2..]; // Skip identity and delimiter
451 /// println!("From {:?}: {:?}", identity, payload);
452 /// }
453 /// # Ok(())
454 /// # }
455 /// ```
456 pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
457 self.inner.recv().await
458 }
459}
460
461// Unix-specific impl for IPC support
462#[cfg(unix)]
463impl RouterSocket<monocoque_core::rt::UnixStream> {
464 /// Create a ROUTER socket from an existing Unix domain socket stream (IPC).
465 pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
466 Ok(Self {
467 inner: InternalRouter::new(stream).await?,
468 monitor: None,
469 })
470 }
471
472 /// Create a ROUTER socket from an existing Unix stream with custom options.
473 ///
474 /// This method provides full control over socket behavior through SocketOptions.
475 pub async fn from_unix_stream_with_options(
476 stream: monocoque_core::rt::UnixStream,
477 options: monocoque_core::options::SocketOptions,
478 ) -> io::Result<Self> {
479 Ok(Self {
480 inner: InternalRouter::with_options(stream, options).await?,
481 monitor: None,
482 })
483 }
484}
485
486// Implement ProxySocket for the high-level RouterSocket wrapper
487impl monocoque_zmtp::proxy::ProxySocket for RouterSocket<TcpStream> {
488 fn recv_multipart<'life0, 'async_trait>(
489 &'life0 mut self,
490 ) -> ::core::pin::Pin<
491 Box<dyn ::core::future::Future<Output = io::Result<Option<Vec<Bytes>>>> + 'async_trait>,
492 >
493 where
494 'life0: 'async_trait,
495 Self: 'async_trait,
496 {
497 Box::pin(async move { self.recv().await })
498 }
499
500 fn send_multipart<'life0, 'async_trait>(
501 &'life0 mut self,
502 msg: Vec<Bytes>,
503 ) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = io::Result<()>> + 'async_trait>>
504 where
505 'life0: 'async_trait,
506 Self: 'async_trait,
507 {
508 Box::pin(async move { self.send(msg).await })
509 }
510
511 fn socket_desc(&self) -> &'static str {
512 "ROUTER"
513 }
514}