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}