Skip to main content

monocoque_zmtp/
proxy.rs

1//! Message proxy (broker) implementation for ZeroMQ patterns.
2//!
3//! A proxy connects frontend and backend sockets, forwarding messages
4//! bidirectionally. This enables common patterns like message brokers,
5//! load balancers, and forwarders without application logic.
6//!
7//! # Supported Patterns
8//!
9//! - **PUB-SUB broker**: XSUB frontend ←→ XPUB backend
10//! - **REQ-REP load balancer**: ROUTER frontend ←→ DEALER backend
11//! - **PUSH-PULL forwarder**: PULL frontend ←→ PUSH backend
12//!
13//! # Message Flow
14//!
15//! ```text
16//! Publishers → XSUB (frontend) → XPUB (backend) → Subscribers
17//! Clients    → ROUTER (frontend) → DEALER (backend) → Workers
18//! ```
19//!
20//! # Example: PUB-SUB Broker
21//!
22//! ```rust,ignore
23//! use monocoque_zmtp::proxy::{proxy, ProxySocket};
24//! use monocoque_zmtp::xsub::XSubSocket;
25//! use monocoque_zmtp::xpub::XPubSocket;
26//!
27//! #[compio::main]
28//! async fn main() -> std::io::Result<()> {
29//!     // Publishers connect to 5555
30//!     let mut frontend = XSubSocket::bind("127.0.0.1:5555").await?;
31//!
32//!     // Subscribers connect to 5556
33//!     let mut backend = XPubSocket::bind("127.0.0.1:5556").await?;
34//!
35//!     // Forward messages and subscriptions bidirectionally
36//!     proxy(&mut frontend, &mut backend, None).await?;
37//!     Ok(())
38//! }
39//! ```
40//!
41//! # Example: REQ-REP Load Balancer
42//!
43//! ```rust,ignore
44//! use monocoque_zmtp::proxy::{proxy, ProxySocket};
45//! use monocoque_zmtp::router::RouterSocket;
46//! use monocoque_zmtp::dealer::DealerSocket;
47//!
48//! #[compio::main]
49//! async fn main() -> std::io::Result<()> {
50//!     // Clients connect to 5555
51//!     let mut frontend = RouterSocket::bind("127.0.0.1:5555").await?;
52//!
53//!     // Workers connect to 5556
54//!     let mut backend = DealerSocket::bind("127.0.0.1:5556").await?;
55//!
56//!     // Load balance requests across workers
57//!     proxy(&mut frontend, &mut backend, None).await?;
58//!     Ok(())
59//! }
60//! ```
61
62use bytes::Bytes;
63use std::io;
64use tracing::debug;
65
66// Import socket types
67use crate::dealer::DealerSocket;
68use crate::pair::PairSocket;
69use crate::publisher::PubSocket;
70use crate::pull::PullSocket;
71use crate::push::PushSocket;
72use crate::rep::RepSocket;
73use crate::req::ReqSocket;
74use crate::router::RouterSocket;
75use crate::subscriber::SubSocket;
76use crate::xpub::XPubSocket;
77use crate::xsub::XSubSocket;
78
79/// Whether a forward-side send error is transient (the frame can be dropped and
80/// the proxy kept running) or fatal (the peer is gone and the loop should stop).
81///
82/// Transient: `WouldBlock` (HWM/EAGAIN), `Interrupted`, `TimedOut`. A single
83/// such hiccup must not tear down the whole proxy. Everything else (broken pipe,
84/// reset, not connected) is treated as fatal and propagates, so a permanently
85/// dead peer cannot spin the loop forwarding-and-dropping forever.
86fn is_transient_send_error(err: &io::Error) -> bool {
87    matches!(
88        err.kind(),
89        io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted | io::ErrorKind::TimedOut
90    )
91}
92
93/// Socket types that can participate in a proxy.
94///
95/// Sockets must implement multipart message send/receive operations
96/// to be used in a proxy pattern.
97///
98/// Note: This trait is designed for single-threaded async runtimes like compio
99/// and does not require `Send`.
100#[async_trait::async_trait(?Send)]
101pub trait ProxySocket {
102    /// Receive a multipart message from the socket.
103    ///
104    /// Returns `None` if no message is available or connection closed.
105    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>>;
106
107    /// Send a multipart message to the socket.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if the send operation fails.
112    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()>;
113
114    /// Get a description of the socket for logging.
115    fn socket_desc(&self) -> &'static str;
116}
117
118/// Run a bidirectional message proxy between frontend and backend sockets.
119///
120/// Messages are forwarded in both directions:
121/// - Frontend → Backend
122/// - Backend → Frontend
123///
124/// An optional capture socket receives copies of all messages for monitoring.
125///
126/// # Parameters
127///
128/// - `frontend`: Socket facing clients/publishers
129/// - `backend`: Socket facing workers/subscribers
130/// - `capture`: Optional socket to receive message copies
131///
132/// # Patterns
133///
134/// - **PUB-SUB**: `XSUB` (frontend) ←→ `XPUB` (backend)
135/// - **REQ-REP**: `ROUTER` (frontend) ←→ `DEALER` (backend)
136/// - **PUSH-PULL**: `PULL` (frontend) ←→ `PUSH` (backend)
137///
138/// # Blocking
139///
140/// This function runs forever, forwarding messages until an error occurs.
141///
142/// # Errors
143///
144/// Returns an error if a socket operation fails.
145///
146/// # Example
147///
148/// ```rust,ignore
149/// use monocoque_zmtp::proxy::{proxy, ProxySocket};
150/// use monocoque_zmtp::xsub::XSubSocket;
151/// use monocoque_zmtp::xpub::XPubSocket;
152///
153/// #[compio::main]
154/// async fn main() -> std::io::Result<()> {
155///     let mut frontend = XSubSocket::bind("127.0.0.1:5555").await?;
156///     let mut backend = XPubSocket::bind("127.0.0.1:5556").await?;
157///
158///     proxy(&mut frontend, &mut backend, None).await
159/// }
160/// ```
161pub async fn proxy<F, B, C>(
162    frontend: &mut F,
163    backend: &mut B,
164    mut capture: Option<&mut C>,
165) -> io::Result<()>
166where
167    F: ProxySocket,
168    B: ProxySocket,
169    C: ProxySocket,
170{
171    use futures::{FutureExt, select};
172
173    debug!(
174        "Starting proxy: {} ←→ {}",
175        frontend.socket_desc(),
176        backend.socket_desc()
177    );
178
179    loop {
180        // Use select! to multiplex between frontend and backend in single-threaded runtime
181        select! {
182            // Forward frontend → backend
183            msg_result = frontend.recv_multipart().fuse() => {
184                if let Some(msg) = msg_result? {
185                    debug!("Proxy: {} → {}: {} frames",
186                           frontend.socket_desc(),
187                           backend.socket_desc(),
188                           msg.len());
189
190                    // Send copy to capture if present
191                    if let Some(ref mut cap) = capture
192                        && let Err(e) = cap.send_multipart(msg.clone()).await
193                    {
194                        debug!("Capture socket send failed: {}", e);
195                    }
196
197                    // Forward to backend. A transient error (HWM/EAGAIN) drops
198                    // this frame but keeps the proxy alive; a fatal error tears
199                    // the loop down.
200                    if let Err(e) = backend.send_multipart(msg).await {
201                        if is_transient_send_error(&e) {
202                            debug!("Proxy: transient send to {}, dropping frame: {}",
203                                   backend.socket_desc(), e);
204                        } else {
205                            return Err(e);
206                        }
207                    }
208                }
209            }
210
211            // Forward backend → frontend
212            msg_result = backend.recv_multipart().fuse() => {
213                if let Some(msg) = msg_result? {
214                    debug!("Proxy: {} → {}: {} frames",
215                           backend.socket_desc(),
216                           frontend.socket_desc(),
217                           msg.len());
218
219                    // Send copy to capture if present
220                    if let Some(ref mut cap) = capture
221                        && let Err(e) = cap.send_multipart(msg.clone()).await
222                    {
223                        debug!("Capture socket send failed: {}", e);
224                    }
225
226                    // Forward to frontend (transient errors keep the proxy up).
227                    if let Err(e) = frontend.send_multipart(msg).await {
228                        if is_transient_send_error(&e) {
229                            debug!("Proxy: transient send to {}, dropping frame: {}",
230                                   frontend.socket_desc(), e);
231                        } else {
232                            return Err(e);
233                        }
234                    }
235                }
236            }
237        }
238    }
239}
240
241/// Control commands for steerable proxy.
242///
243/// Sent as single-frame messages to the control socket.
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub enum ProxyCommand {
246    /// Pause message forwarding (buffering continues)
247    Pause,
248    /// Resume message forwarding
249    Resume,
250    /// Terminate the proxy loop
251    Terminate,
252    /// Report statistics  -  replies with `"messages_forwarded=N"` on the control socket.
253    Statistics,
254}
255
256impl ProxyCommand {
257    /// Parse command from bytes.
258    pub const fn from_bytes(data: &[u8]) -> Option<Self> {
259        match data {
260            b"PAUSE" => Some(Self::Pause),
261            b"RESUME" => Some(Self::Resume),
262            b"TERMINATE" => Some(Self::Terminate),
263            b"STATISTICS" => Some(Self::Statistics),
264            _ => None,
265        }
266    }
267
268    /// Convert command to bytes.
269    pub const fn as_bytes(&self) -> &'static [u8] {
270        match self {
271            Self::Pause => b"PAUSE",
272            Self::Resume => b"RESUME",
273            Self::Terminate => b"TERMINATE",
274            Self::Statistics => b"STATISTICS",
275        }
276    }
277}
278
279/// Run a steerable bidirectional message proxy with control socket.
280///
281/// Like [`proxy()`] but can be controlled via a control socket that receives commands:
282/// - `PAUSE` - Stop forwarding messages (buffering continues)
283/// - `RESUME` - Resume forwarding messages
284/// - `TERMINATE` - Stop the proxy and return
285/// - `STATISTICS` - Future: report proxy statistics
286///
287/// # Parameters
288///
289/// - `frontend`: Socket facing clients/publishers
290/// - `backend`: Socket facing workers/subscribers
291/// - `capture`: Optional socket to receive message copies
292/// - `control`: Socket that receives control commands
293///
294/// # Control Socket Protocol
295///
296/// Send single-frame messages with command text:
297/// ```text
298/// PAUSE       - Pause forwarding
299/// RESUME      - Resume forwarding
300/// TERMINATE   - Stop proxy
301/// STATISTICS  - Get stats (future)
302/// ```
303///
304/// # Example
305///
306/// ```rust,ignore
307/// use monocoque_zmtp::proxy::{proxy_steerable, ProxySocket, ProxyCommand};
308/// use monocoque_zmtp::router::RouterSocket;
309/// use monocoque_zmtp::dealer::DealerSocket;
310/// use monocoque_zmtp::pair::PairSocket;
311///
312/// #[compio::main]
313/// async fn main() -> std::io::Result<()> {
314///     // Broker sockets
315///     let (_, mut frontend) = RouterSocket::bind("127.0.0.1:5555").await?;
316///     let (_, mut backend) = DealerSocket::bind("127.0.0.1:5556").await?;
317///
318///     // Control socket
319///     let (_, mut control) = PairSocket::bind("127.0.0.1:5557").await?;
320///
321///     // Run steerable proxy
322///     proxy_steerable(&mut frontend, &mut backend, None, &mut control).await?;
323///     Ok(())
324/// }
325/// ```
326///
327/// Send control commands from another socket:
328/// ```no_run
329/// use monocoque_zmtp::pair::PairSocket;
330/// use bytes::Bytes;
331///
332/// # async fn send_control() -> std::io::Result<()> {
333/// let mut control_client = PairSocket::connect("127.0.0.1:5557").await?;
334///
335/// // Pause proxy
336/// control_client.send(vec![Bytes::from("PAUSE")]).await?;
337///
338/// // Resume proxy
339/// control_client.send(vec![Bytes::from("RESUME")]).await?;
340///
341/// // Terminate proxy
342/// control_client.send(vec![Bytes::from("TERMINATE")]).await?;
343/// # Ok(())
344/// # }
345/// ```
346pub async fn proxy_steerable<F, B, C, Ctrl>(
347    frontend: &mut F,
348    backend: &mut B,
349    mut capture: Option<&mut C>,
350    control: &mut Ctrl,
351) -> io::Result<()>
352where
353    F: ProxySocket,
354    B: ProxySocket,
355    C: ProxySocket,
356    Ctrl: ProxySocket,
357{
358    use futures::{FutureExt, select};
359
360    debug!(
361        "Starting steerable proxy: {} ←→ {} (control enabled)",
362        frontend.socket_desc(),
363        backend.socket_desc()
364    );
365
366    let mut paused = false;
367    let mut message_count = 0u64;
368
369    loop {
370        select! {
371            // Check for control commands
372            cmd_result = control.recv_multipart().fuse() => {
373                if let Some(cmd_msg) = cmd_result?
374                    && let Some(cmd_frame) = cmd_msg.first()
375                    && let Some(cmd) = ProxyCommand::from_bytes(cmd_frame)
376                {
377                    debug!("Proxy control command: {:?}", cmd);
378
379                    match cmd {
380                        ProxyCommand::Pause => {
381                            debug!("Proxy PAUSED");
382                            paused = true;
383                        }
384                        ProxyCommand::Resume => {
385                            debug!("Proxy RESUMED");
386                            paused = false;
387                        }
388                        ProxyCommand::Terminate => {
389                            debug!("Proxy TERMINATING (forwarded {} messages)", message_count);
390                            return Ok(());
391                        }
392                        ProxyCommand::Statistics => {
393                            debug!("Proxy statistics: {} messages forwarded", message_count);
394                            let stats = format!("messages_forwarded={}", message_count);
395                            let _ = control.send_multipart(vec![bytes::Bytes::from(stats)]).await;
396                        }
397                    }
398                }
399            }
400
401            // Forward frontend → backend (if not paused)
402            msg_result = frontend.recv_multipart().fuse() => {
403                if let Some(msg) = msg_result? {
404                    if paused {
405                        debug!("Proxy: dropped message (paused)");
406                    } else {
407                        debug!("Proxy: {} → {}: {} frames",
408                               frontend.socket_desc(),
409                               backend.socket_desc(),
410                               msg.len());
411
412                        // Send copy to capture if present
413                        if let Some(ref mut cap) = capture
414                            && let Err(e) = cap.send_multipart(msg.clone()).await
415                        {
416                            debug!("Capture socket send failed: {}", e);
417                        }
418
419                        // Forward to backend (transient errors keep the proxy up).
420                        match backend.send_multipart(msg).await {
421                            Ok(()) => message_count += 1,
422                            Err(e) if is_transient_send_error(&e) => {
423                                debug!("Proxy: transient send to {}, dropping frame: {}",
424                                       backend.socket_desc(), e);
425                            }
426                            Err(e) => return Err(e),
427                        }
428                    }
429                }
430            }
431
432            // Forward backend → frontend (if not paused)
433            msg_result = backend.recv_multipart().fuse() => {
434                if let Some(msg) = msg_result? {
435                    if paused {
436                        debug!("Proxy: dropped message (paused)");
437                    } else {
438                        debug!("Proxy: {} → {}: {} frames",
439                               backend.socket_desc(),
440                               frontend.socket_desc(),
441                               msg.len());
442
443                        // Send copy to capture if present
444                        if let Some(ref mut cap) = capture
445                            && let Err(e) = cap.send_multipart(msg.clone()).await
446                        {
447                            debug!("Capture socket send failed: {}", e);
448                        }
449
450                        // Forward to frontend (transient errors keep the proxy up).
451                        match frontend.send_multipart(msg).await {
452                            Ok(()) => message_count += 1,
453                            Err(e) if is_transient_send_error(&e) => {
454                                debug!("Proxy: transient send to {}, dropping frame: {}",
455                                       frontend.socket_desc(), e);
456                            }
457                            Err(e) => return Err(e),
458                        }
459                    }
460                }
461            }
462        }
463    }
464}
465
466// ===== ProxySocket Implementations =====
467
468// XSUB socket (frontend in PUB-SUB broker)
469#[async_trait::async_trait(?Send)]
470impl ProxySocket for XSubSocket {
471    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
472        self.recv().await
473    }
474
475    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
476        // In a PUB-SUB broker the proxy receives subscription events from XPUB
477        // (backend) and must forward them upstream via XSUB so the publisher stops
478        // or starts sending the relevant topics.
479        //
480        // The message format produced by XPubSocket::recv_multipart is:
481        //   [b"\x01", topic]  - subscribe
482        //   [b"\x00", topic]  - unsubscribe
483        //
484        // We reconstruct the raw ZMTP subscription frame and dispatch it.
485        if msg.is_empty() {
486            return Ok(());
487        }
488
489        let cmd_frame = &msg[0];
490        if cmd_frame.is_empty() {
491            return Ok(());
492        }
493
494        let cmd_byte = cmd_frame[0];
495        // Topic is either in a second frame or appended after the command byte
496        // in the same frame, depending on how the message was encoded.
497        let topic: Bytes = if msg.len() >= 2 {
498            msg[1].clone()
499        } else if cmd_frame.len() > 1 {
500            cmd_frame.slice(1..)
501        } else {
502            Bytes::new()
503        };
504
505        let event = if cmd_byte == 0x01 {
506            monocoque_core::subscription::SubscriptionEvent::Subscribe(topic)
507        } else if cmd_byte == 0x00 {
508            monocoque_core::subscription::SubscriptionEvent::Unsubscribe(topic)
509        } else {
510            // Unknown command - ignore
511            return Ok(());
512        };
513
514        self.send_subscription_event(event).await
515    }
516
517    fn socket_desc(&self) -> &'static str {
518        "XSUB"
519    }
520}
521
522// XPUB socket (backend in PUB-SUB broker)
523#[async_trait::async_trait(?Send)]
524impl ProxySocket for XPubSocket {
525    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
526        // XPUB receives subscription events, not data
527        // Map subscription events to message format
528        if let Some(event) = self.recv_subscription().await? {
529            let msg = match event {
530                monocoque_core::subscription::SubscriptionEvent::Subscribe(topic) => {
531                    vec![Bytes::from(&b"\x01"[..]), topic]
532                }
533                monocoque_core::subscription::SubscriptionEvent::Unsubscribe(topic) => {
534                    vec![Bytes::from(&b"\x00"[..]), topic]
535                }
536            };
537            Ok(Some(msg))
538        } else {
539            Ok(None)
540        }
541    }
542
543    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
544        self.send(msg).await
545    }
546
547    fn socket_desc(&self) -> &'static str {
548        "XPUB"
549    }
550}
551
552// DEALER socket (backend in REQ-REP load balancer)
553#[async_trait::async_trait(?Send)]
554impl ProxySocket for DealerSocket {
555    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
556        self.recv().await
557    }
558
559    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
560        self.send(msg).await
561    }
562
563    fn socket_desc(&self) -> &'static str {
564        "DEALER"
565    }
566}
567
568// ROUTER socket (frontend in REQ-REP load balancer)
569#[async_trait::async_trait(?Send)]
570impl ProxySocket for RouterSocket {
571    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
572        self.recv().await
573    }
574
575    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
576        self.send(msg).await
577    }
578
579    fn socket_desc(&self) -> &'static str {
580        "ROUTER"
581    }
582}
583
584// PULL socket (frontend in PUSH-PULL forwarder)
585#[async_trait::async_trait(?Send)]
586impl ProxySocket for PullSocket {
587    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
588        self.recv().await
589    }
590
591    async fn send_multipart(&mut self, _msg: Vec<Bytes>) -> io::Result<()> {
592        // PULL doesn't send
593        Ok(())
594    }
595
596    fn socket_desc(&self) -> &'static str {
597        "PULL"
598    }
599}
600
601// PUSH socket (backend in PUSH-PULL forwarder)
602#[async_trait::async_trait(?Send)]
603impl ProxySocket for PushSocket {
604    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
605        // PUSH doesn't receive
606        Ok(None)
607    }
608
609    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
610        self.send(msg).await
611    }
612
613    fn socket_desc(&self) -> &'static str {
614        "PUSH"
615    }
616}
617
618// REQ socket
619#[async_trait::async_trait(?Send)]
620impl ProxySocket for ReqSocket {
621    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
622        self.recv().await
623    }
624
625    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
626        self.send(msg).await
627    }
628
629    fn socket_desc(&self) -> &'static str {
630        "REQ"
631    }
632}
633
634// REP socket
635#[async_trait::async_trait(?Send)]
636impl ProxySocket for RepSocket {
637    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
638        self.recv().await
639    }
640
641    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
642        self.send(msg).await
643    }
644
645    fn socket_desc(&self) -> &'static str {
646        "REP"
647    }
648}
649
650// PAIR socket
651#[async_trait::async_trait(?Send)]
652impl ProxySocket for PairSocket {
653    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
654        self.recv().await
655    }
656
657    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
658        self.send(msg).await
659    }
660
661    fn socket_desc(&self) -> &'static str {
662        "PAIR"
663    }
664}
665
666// PUB socket (typically not used in proxy, but included for completeness)
667#[async_trait::async_trait(?Send)]
668impl ProxySocket for PubSocket {
669    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
670        // PUB doesn't receive
671        Ok(None)
672    }
673
674    async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
675        self.send(msg).await
676    }
677
678    fn socket_desc(&self) -> &'static str {
679        "PUB"
680    }
681}
682
683// SUB socket (typically not used directly in proxy, XSUB is preferred)
684#[async_trait::async_trait(?Send)]
685impl ProxySocket for SubSocket {
686    async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
687        self.recv().await
688    }
689
690    async fn send_multipart(&mut self, _msg: Vec<Bytes>) -> io::Result<()> {
691        // SUB doesn't send data
692        Ok(())
693    }
694
695    fn socket_desc(&self) -> &'static str {
696        "SUB"
697    }
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703
704    #[test]
705    fn transient_send_errors_are_classified() {
706        // Transient: dropped frame, proxy stays up.
707        for kind in [
708            io::ErrorKind::WouldBlock,
709            io::ErrorKind::Interrupted,
710            io::ErrorKind::TimedOut,
711        ] {
712            assert!(is_transient_send_error(&io::Error::new(kind, "x")));
713        }
714        // Fatal: proxy tears down.
715        for kind in [
716            io::ErrorKind::BrokenPipe,
717            io::ErrorKind::ConnectionReset,
718            io::ErrorKind::NotConnected,
719        ] {
720            assert!(!is_transient_send_error(&io::Error::new(kind, "x")));
721        }
722    }
723
724    /// Mock socket for testing proxy logic
725    struct MockSocket {
726        name: &'static str,
727        recv_queue: Vec<Vec<Bytes>>,
728        send_queue: Vec<Vec<Bytes>>,
729    }
730
731    impl MockSocket {
732        fn new(name: &'static str) -> Self {
733            Self {
734                name,
735                recv_queue: Vec::new(),
736                send_queue: Vec::new(),
737            }
738        }
739
740        fn enqueue(&mut self, msg: Vec<Bytes>) {
741            self.recv_queue.push(msg);
742        }
743    }
744
745    #[async_trait::async_trait(?Send)]
746    impl ProxySocket for MockSocket {
747        async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
748            Ok(self.recv_queue.pop())
749        }
750
751        async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
752            self.send_queue.push(msg);
753            Ok(())
754        }
755
756        fn socket_desc(&self) -> &'static str {
757            self.name
758        }
759    }
760
761    #[test]
762    fn test_mock_socket() {
763        let mut sock = MockSocket::new("test");
764        sock.enqueue(vec![Bytes::from("hello")]);
765        assert_eq!(sock.recv_queue.len(), 1);
766    }
767
768    // TODO: Add integration tests with real sockets
769    // - Test XSUB-XPUB broker pattern
770    // - Test ROUTER-DEALER load balancer
771    // - Test capture socket monitoring
772    // - Test error handling when socket fails
773}