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