Skip to main content

monocoque_zmtp/
pair.rs

1//! PAIR socket implementation
2//!
3//! PAIR sockets are exclusive peer-to-peer sockets that connect exactly two endpoints.
4//! They provide bidirectional communication without routing or filtering.
5//!
6//! # Characteristics
7//!
8//! - **Exclusive**: Only connects to one peer at a time
9//! - **Bidirectional**: Can both send and receive messages
10//! - **No routing**: Messages go directly between the pair
11//! - **No filtering**: All messages are delivered
12//!
13//! # Use Cases
14//!
15//! - Connecting two threads in a process
16//! - Exclusive communication between two services
17//! - Testing and prototyping
18
19use crate::base::SocketBase;
20use crate::inproc_stream::InprocStream;
21use crate::{handshake::perform_handshake_with_options, session::SocketType};
22use bytes::Bytes;
23use compio_io::{AsyncRead, AsyncWrite};
24use monocoque_core::endpoint::Endpoint;
25use monocoque_core::options::SocketOptions;
26use monocoque_core::rt::TcpStream;
27use smallvec::SmallVec;
28use std::io;
29use tracing::{debug, trace};
30
31/// PAIR socket for exclusive peer-to-peer communication.
32///
33/// PAIR sockets connect exactly two endpoints and provide bidirectional
34/// message passing without any routing or filtering logic.
35pub struct PairSocket<S = TcpStream>
36where
37    S: AsyncRead + AsyncWrite + Unpin,
38{
39    /// Base socket infrastructure (stream, buffers, options)
40    base: SocketBase<S>,
41    /// Accumulated frames for current multipart message
42    frames: SmallVec<[Bytes; 4]>,
43}
44
45impl<S> PairSocket<S>
46where
47    S: AsyncRead + AsyncWrite + Unpin,
48{
49    /// Create a new PAIR socket from a stream with default buffer configuration.
50    pub async fn new(stream: S) -> io::Result<Self> {
51        Self::with_options(stream, SocketOptions::default()).await
52    }
53
54    /// Create a new PAIR socket with custom buffer configuration and socket options.
55    pub async fn with_options(mut stream: S, options: SocketOptions) -> io::Result<Self> {
56        debug!("[PAIR] Creating new PAIR socket");
57
58        // Perform ZMTP handshake
59        debug!("[PAIR] Performing ZMTP handshake...");
60        let handshake_result = perform_handshake_with_options(
61            &mut stream,
62            SocketType::Pair,
63            options.routing_id.as_deref(),
64            Some(options.handshake_timeout),
65            &options,
66        )
67        .await
68        .map_err(|e| io::Error::other(format!("Handshake failed: {}", e)))?;
69
70        debug!(
71            peer_identity = ?handshake_result.peer_identity,
72            peer_socket_type = ?handshake_result.peer_socket_type,
73            "[PAIR] Handshake complete"
74        );
75
76        debug!("[PAIR] Socket initialized");
77
78        let mut base = SocketBase::new(stream, SocketType::Pair, options);
79        base.curve_cipher = handshake_result.curve_cipher;
80        Ok(Self {
81            base,
82            frames: SmallVec::new(),
83        })
84    }
85
86    /// Send a message to the paired socket.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if the socket is poisoned, disconnected, or if the write fails.
91    pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
92        trace!("[PAIR] Sending {} frames", msg.len());
93
94        // Coalesce / vector / copy-and-write as appropriate.
95        self.base.send_message(&msg).await?;
96
97        trace!("[PAIR] Message sent successfully");
98        Ok(())
99    }
100
101    /// Receive a message from the paired socket.
102    ///
103    /// Returns `Ok(Some(msg))` if a message was received, `Ok(None)` if the
104    /// connection was closed, or an error.
105    pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
106        trace!("[PAIR] Waiting for message");
107
108        // Read from stream until we have a complete message
109        loop {
110            // Try to decode frames from buffer
111            loop {
112                match self.base.process_frame()? {
113                    crate::base::FrameResult::NeedMore => break,
114                    crate::base::FrameResult::CommandHandled => {
115                        if !self.base.send_buffer.is_empty() {
116                            self.base.flush_send_buffer().await?;
117                        }
118                    }
119                    crate::base::FrameResult::Data(more, payload) => {
120                        self.frames.push(payload);
121                        if !more {
122                            let msg: Vec<Bytes> = self.frames.drain(..).collect();
123                            trace!("[PAIR] Received {} frames", msg.len());
124                            return Ok(Some(msg));
125                        }
126                    }
127                }
128            }
129
130            // Need more data - read raw bytes from stream
131            let n = self.base.read_raw().await?;
132            if n == 0 {
133                // EOF - connection closed
134                trace!("[PAIR] Connection closed");
135                return Ok(None);
136            }
137            if self.base.check_heartbeat()? {
138                self.base.flush_send_buffer().await?;
139            }
140            // Continue decoding with new data
141        }
142    }
143
144    /// Receive a message into a caller-provided buffer, reusing its allocation.
145    ///
146    /// Allocation-free counterpart to [`recv`](Self::recv): frames go into `out`
147    /// (cleared on entry) instead of a fresh `Vec`. Returns `Ok(true)` on a
148    /// complete message, `Ok(false)` on EOF.
149    pub async fn recv_into(&mut self, out: &mut Vec<Bytes>) -> io::Result<bool> {
150        out.clear();
151        loop {
152            loop {
153                match self.base.process_frame()? {
154                    crate::base::FrameResult::NeedMore => break,
155                    crate::base::FrameResult::CommandHandled => {
156                        if !self.base.send_buffer.is_empty() {
157                            self.base.flush_send_buffer().await?;
158                        }
159                    }
160                    crate::base::FrameResult::Data(more, payload) => {
161                        if !more && self.frames.is_empty() {
162                            out.push(payload);
163                            return Ok(true);
164                        }
165                        self.frames.push(payload);
166                        if !more {
167                            out.extend(self.frames.drain(..));
168                            return Ok(true);
169                        }
170                    }
171                }
172            }
173
174            let n = self.base.read_raw().await?;
175            if n == 0 {
176                return Ok(false);
177            }
178            if self.base.check_heartbeat()? {
179                self.base.flush_send_buffer().await?;
180            }
181        }
182    }
183
184    /// Try to receive a message into `out` without a kernel read.
185    ///
186    /// Returns `Ok(true)` with a complete message moved into `out`, or
187    /// `Ok(false)` leaving `out` untouched when nothing complete is buffered.
188    pub fn try_recv_into(&mut self, out: &mut Vec<Bytes>) -> io::Result<bool> {
189        loop {
190            match self.base.process_frame()? {
191                crate::base::FrameResult::NeedMore => return Ok(false),
192                crate::base::FrameResult::CommandHandled => {}
193                crate::base::FrameResult::Data(more, payload) => {
194                    if !more && self.frames.is_empty() {
195                        out.clear();
196                        out.push(payload);
197                        return Ok(true);
198                    }
199                    self.frames.push(payload);
200                    if !more {
201                        out.clear();
202                        out.extend(self.frames.drain(..));
203                        return Ok(true);
204                    }
205                }
206            }
207        }
208    }
209
210    /// Receive a single-frame message, returning just its frame.
211    ///
212    /// Returns `Ok(None)` on EOF, and an error if the message is multipart.
213    pub async fn recv_one(&mut self) -> io::Result<Option<Bytes>> {
214        loop {
215            loop {
216                match self.base.process_frame()? {
217                    crate::base::FrameResult::NeedMore => break,
218                    crate::base::FrameResult::CommandHandled => {
219                        if !self.base.send_buffer.is_empty() {
220                            self.base.flush_send_buffer().await?;
221                        }
222                    }
223                    crate::base::FrameResult::Data(more, payload) => {
224                        if more || !self.frames.is_empty() {
225                            return Err(io::Error::new(
226                                io::ErrorKind::InvalidData,
227                                "recv_one received a multipart message",
228                            ));
229                        }
230                        return Ok(Some(payload));
231                    }
232                }
233            }
234
235            let n = self.base.read_raw().await?;
236            if n == 0 {
237                return Ok(None);
238            }
239            if self.base.check_heartbeat()? {
240                self.base.flush_send_buffer().await?;
241            }
242        }
243    }
244
245    /// Close the socket gracefully by shutting down the underlying stream.
246    pub async fn close(mut self) -> io::Result<()> {
247        trace!("[PAIR] Closing socket");
248        self.base.close().await
249    }
250
251    /// Get a reference to the socket options.
252    #[inline]
253    pub const fn options(&self) -> &SocketOptions {
254        &self.base.options
255    }
256
257    /// Get a mutable reference to the socket options.
258    #[inline]
259    pub fn options_mut(&mut self) -> &mut SocketOptions {
260        &mut self.base.options
261    }
262
263    /// Set socket options (builder-style).
264    #[inline]
265    pub fn set_options(&mut self, options: SocketOptions) {
266        self.base.set_options(options);
267    }
268
269    /// Get the socket type.
270    ///
271    /// # ZeroMQ Compatibility
272    ///
273    /// Corresponds to `ZMQ_TYPE` (16) option.
274    #[inline]
275    pub const fn socket_type(&self) -> SocketType {
276        SocketType::Pair
277    }
278
279    /// Get the endpoint this socket is connected/bound to, if available.
280    ///
281    /// Returns `None` if the socket was created from a raw stream.
282    ///
283    /// # ZeroMQ Compatibility
284    ///
285    /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) option.
286    #[inline]
287    pub fn last_endpoint(&self) -> Option<&Endpoint> {
288        self.base.last_endpoint()
289    }
290
291    /// Check if the last received message has more frames coming.
292    ///
293    /// Returns `true` if there are more frames in the current multipart message.
294    ///
295    /// # ZeroMQ Compatibility
296    ///
297    /// Corresponds to `ZMQ_RCVMORE` (13) option.
298    #[inline]
299    pub fn has_more(&self) -> bool {
300        self.base.has_more()
301    }
302
303    /// Get the event state of the socket.
304    ///
305    /// Returns a bitmask indicating ready-to-receive and ready-to-send states.
306    ///
307    /// # Returns
308    ///
309    /// - `1` (POLLIN) - Socket is ready to receive
310    /// - `2` (POLLOUT) - Socket is ready to send
311    /// - `3` (POLLIN | POLLOUT) - Socket is ready for both
312    ///
313    /// # ZeroMQ Compatibility
314    ///
315    /// Corresponds to `ZMQ_EVENTS` (15) option.
316    #[inline]
317    pub fn events(&self) -> u32 {
318        self.base.events()
319    }
320}
321
322// Specialized implementation for TCP streams to enable TCP_NODELAY
323impl PairSocket<TcpStream> {
324    /// Bind to an address and accept the first connection.
325    ///
326    /// PAIR sockets form an exclusive pair with exactly one peer.
327    ///
328    /// # Returns
329    ///
330    /// A tuple of `(listener, socket)` where:
331    /// - `listener` can be used to accept additional connections if needed
332    /// - `socket` is ready to send/receive with the first peer
333    ///
334    /// # Example
335    ///
336    /// ```no_run
337    /// use monocoque_zmtp::pair::PairSocket;
338    ///
339    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
340    /// let (listener, mut socket) = PairSocket::bind("127.0.0.1:5555").await?;
341    /// # Ok(())
342    /// # }
343    /// ```
344    pub async fn bind(
345        addr: impl monocoque_core::rt::ToSocketAddrs,
346    ) -> io::Result<(monocoque_core::rt::TcpListener, Self)> {
347        let listener = monocoque_core::rt::TcpListener::bind(addr).await?;
348        let (stream, _) = listener.accept().await?;
349        let socket = Self::from_tcp(stream).await?;
350        Ok((listener, socket))
351    }
352
353    /// Connect to a remote PAIR socket, storing the endpoint for automatic reconnection.
354    ///
355    /// # Example
356    ///
357    /// ```no_run
358    /// use monocoque_zmtp::pair::PairSocket;
359    ///
360    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
361    /// let mut socket = PairSocket::connect("127.0.0.1:5555").await?;
362    /// # Ok(())
363    /// # }
364    /// ```
365    pub async fn connect(addr: impl monocoque_core::rt::ToSocketAddrs) -> io::Result<Self> {
366        Self::connect_with_options(addr, SocketOptions::default()).await
367    }
368
369    /// Connect with custom options, storing the endpoint for reconnection.
370    pub async fn connect_with_options(
371        addr: impl monocoque_core::rt::ToSocketAddrs,
372        options: SocketOptions,
373    ) -> io::Result<Self> {
374        let stream = TcpStream::connect(addr).await?;
375        let peer_addr = stream.peer_addr()?;
376        crate::utils::configure_tcp_stream(&stream, &options, "PAIR")?;
377
378        let mut stream = stream;
379        let handshake_result = perform_handshake_with_options(
380            &mut stream,
381            SocketType::Pair,
382            options.routing_id.as_deref(),
383            Some(options.handshake_timeout),
384            &options,
385        )
386        .await
387        .map_err(|e| io::Error::other(format!("Handshake failed: {}", e)))?;
388
389        debug!(
390            peer_identity = ?handshake_result.peer_identity,
391            peer_socket_type = ?handshake_result.peer_socket_type,
392            "[PAIR] Connected to {} (endpoint stored for reconnection)",
393            peer_addr
394        );
395
396        let endpoint = monocoque_core::endpoint::Endpoint::Tcp(peer_addr);
397        let mut base =
398            crate::base::SocketBase::with_endpoint(stream, SocketType::Pair, endpoint, options);
399        base.curve_cipher = handshake_result.curve_cipher;
400        Ok(Self {
401            base,
402            frames: SmallVec::new(),
403        })
404    }
405
406    /// Create a new PAIR socket from a TCP stream with TCP_NODELAY enabled.
407    pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
408        Self::from_tcp_with_options(stream, SocketOptions::default()).await
409    }
410
411    /// Create a new PAIR socket from a TCP stream with TCP_NODELAY and custom options.
412    pub async fn from_tcp_with_options(
413        stream: TcpStream,
414        options: SocketOptions,
415    ) -> io::Result<Self> {
416        // Configure TCP optimizations including keepalive
417        crate::utils::configure_tcp_stream(&stream, &options, "PAIR")?;
418        Self::with_options(stream, options).await
419    }
420
421    /// Check if the socket is currently connected.
422    #[inline]
423    pub fn is_connected(&self) -> bool {
424        self.base.is_connected()
425    }
426
427    /// Try to reconnect to the stored endpoint.
428    pub async fn try_reconnect(&mut self) -> io::Result<()> {
429        self.base.try_reconnect(SocketType::Pair).await
430    }
431
432    /// Receive a message with automatic reconnection on EOF or network error.
433    ///
434    /// If the socket was created with `connect()` and stores an endpoint, this
435    /// method loops: on EOF or broken-pipe it clears the stream and calls
436    /// `try_reconnect()` (which applies exponential backoff), then retries `recv()`.
437    ///
438    /// Respects `max_reconnect_attempts`  -  returns `NotConnected` when exhausted.
439    pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<Bytes>>> {
440        let max = self.base.options.max_reconnect_attempts;
441        let mut attempts = 0u32;
442
443        loop {
444            if self.base.stream.is_none() {
445                if let Some(limit) = max
446                    && attempts >= limit
447                {
448                    return Err(io::Error::new(
449                        io::ErrorKind::NotConnected,
450                        format!("Max {} reconnection attempts exceeded", limit),
451                    ));
452                }
453                attempts += 1;
454                trace!(
455                    "[PAIR] Stream disconnected, reconnecting (attempt {})",
456                    attempts
457                );
458                self.try_reconnect().await?;
459            }
460
461            match self.recv().await {
462                Ok(Some(msg)) => return Ok(Some(msg)),
463                // EOF: read_raw() already set stream = None
464                Ok(None) => {
465                    debug!("[PAIR] EOF on recv, will reconnect");
466                }
467                Err(e) => {
468                    if self.base.stream.is_none()
469                        || matches!(
470                            e.kind(),
471                            io::ErrorKind::ConnectionReset
472                                | io::ErrorKind::ConnectionAborted
473                                | io::ErrorKind::BrokenPipe
474                                | io::ErrorKind::UnexpectedEof
475                        )
476                    {
477                        debug!("[PAIR] Connection error on recv ({}), will reconnect", e);
478                        self.base.stream = None;
479                    } else {
480                        return Err(e);
481                    }
482                }
483            }
484        }
485    }
486
487    /// Send a message with automatic reconnection on network error.
488    ///
489    /// On BrokenPipe / ConnectionReset, `write_from_buf()` already sets
490    /// `stream = None`, so the next loop iteration reconnects automatically.
491    ///
492    /// Respects `max_reconnect_attempts`  -  returns `NotConnected` when exhausted.
493    pub async fn send_with_reconnect(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
494        let max = self.base.options.max_reconnect_attempts;
495        let mut attempts = 0u32;
496
497        loop {
498            if self.base.stream.is_none() {
499                if let Some(limit) = max
500                    && attempts >= limit
501                {
502                    return Err(io::Error::new(
503                        io::ErrorKind::NotConnected,
504                        format!("Max {} reconnection attempts exceeded", limit),
505                    ));
506                }
507                attempts += 1;
508                trace!(
509                    "[PAIR] Stream disconnected, reconnecting (attempt {})",
510                    attempts
511                );
512                self.try_reconnect().await?;
513            }
514
515            match self.send(msg.clone()).await {
516                Ok(()) => return Ok(()),
517                Err(_) if self.base.stream.is_none() => {
518                    // write_from_buf set stream = None → network error, retry
519                    debug!("[PAIR] Send failed (stream lost), will reconnect");
520                }
521                Err(e) => return Err(e),
522            }
523        }
524    }
525}
526
527// Specialized implementation for Inproc streams
528impl PairSocket<InprocStream> {
529    /// Bind to an inproc endpoint.
530    ///
531    /// Creates a new inproc endpoint that other sockets can connect to.
532    /// Inproc endpoints must be bound before they can be connected to.
533    ///
534    /// # Arguments
535    ///
536    /// * `endpoint` - Inproc URI (e.g., "inproc://my-endpoint")
537    ///
538    /// # Example
539    ///
540    /// ```no_run
541    /// use monocoque_zmtp::pair::PairSocket;
542    ///
543    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
544    /// let socket = PairSocket::bind_inproc("inproc://my-pair")?;
545    /// # Ok(())
546    /// # }
547    /// ```
548    pub fn bind_inproc(endpoint: &str) -> io::Result<Self> {
549        Self::bind_inproc_with_options(endpoint, SocketOptions::default())
550    }
551
552    /// Bind to an inproc endpoint with custom configuration and options.
553    pub fn bind_inproc_with_options(endpoint: &str, options: SocketOptions) -> io::Result<Self> {
554        debug!("[PAIR] Binding to inproc endpoint: {}", endpoint);
555
556        // Bind to inproc endpoint
557        let (tx, rx) = monocoque_core::inproc::bind_inproc(endpoint)?;
558        let stream = InprocStream::new(tx, rx);
559
560        // Parse endpoint for storage
561        let parsed_endpoint = Endpoint::parse(endpoint)
562            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
563
564        debug!("[PAIR] Bound to {}", endpoint);
565
566        // For inproc, no handshake needed (same process)
567        Ok(Self {
568            base: SocketBase::with_endpoint(stream, SocketType::Pair, parsed_endpoint, options),
569            frames: SmallVec::new(),
570        })
571    }
572
573    /// Connect to an inproc endpoint.
574    ///
575    /// Connects to a previously bound inproc endpoint.
576    ///
577    /// # Arguments
578    ///
579    /// * `endpoint` - Inproc URI (e.g., "inproc://my-endpoint")
580    ///
581    /// # Example
582    ///
583    /// ```no_run
584    /// use monocoque_zmtp::pair::PairSocket;
585    ///
586    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
587    /// let socket = PairSocket::connect_inproc("inproc://my-pair")?;
588    /// # Ok(())
589    /// # }
590    /// ```
591    pub fn connect_inproc(endpoint: &str) -> io::Result<Self> {
592        Self::connect_inproc_with_options(endpoint, SocketOptions::default())
593    }
594
595    /// Connect to an inproc endpoint with custom configuration and options.
596    pub fn connect_inproc_with_options(endpoint: &str, options: SocketOptions) -> io::Result<Self> {
597        debug!("[PAIR] Connecting to inproc endpoint: {}", endpoint);
598
599        // connect_inproc_bidi returns (to_server_tx, from_server_rx) so we can
600        // both send to the server and receive replies from it. The server must
601        // have been bound with bind_inproc_bidi.
602        let (tx, rx) = monocoque_core::inproc::connect_inproc_bidi(endpoint)?;
603        let stream = InprocStream::new(tx, rx);
604
605        // Parse endpoint for storage
606        let parsed_endpoint = Endpoint::parse(endpoint)
607            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
608
609        debug!("[PAIR] Connected to {}", endpoint);
610
611        // For inproc, no handshake needed (same process)
612        Ok(Self {
613            base: SocketBase::with_endpoint(stream, SocketType::Pair, parsed_endpoint, options),
614            frames: SmallVec::new(),
615        })
616    }
617}
618
619crate::impl_socket_trait!(PairSocket<S>, SocketType::Pair);