use bytes::Bytes;
use std::io;
use tracing::debug;
use crate::dealer::DealerSocket;
use crate::pair::PairSocket;
use crate::publisher::PubSocket;
use crate::pull::PullSocket;
use crate::push::PushSocket;
use crate::rep::RepSocket;
use crate::req::ReqSocket;
use crate::router::RouterSocket;
use crate::subscriber::SubSocket;
use crate::xpub::XPubSocket;
use crate::xsub::XSubSocket;
fn is_transient_send_error(err: &io::Error) -> bool {
matches!(
err.kind(),
io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted | io::ErrorKind::TimedOut
)
}
#[async_trait::async_trait(?Send)]
pub trait ProxySocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>>;
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()>;
fn socket_desc(&self) -> &'static str;
}
pub async fn proxy<F, B, C>(
frontend: &mut F,
backend: &mut B,
mut capture: Option<&mut C>,
) -> io::Result<()>
where
F: ProxySocket,
B: ProxySocket,
C: ProxySocket,
{
use futures::{FutureExt, select};
debug!(
"Starting proxy: {} ←→ {}",
frontend.socket_desc(),
backend.socket_desc()
);
loop {
select! {
msg_result = frontend.recv_multipart().fuse() => {
if let Some(msg) = msg_result? {
debug!("Proxy: {} → {}: {} frames",
frontend.socket_desc(),
backend.socket_desc(),
msg.len());
if let Some(ref mut cap) = capture
&& let Err(e) = cap.send_multipart(msg.clone()).await
{
debug!("Capture socket send failed: {}", e);
}
if let Err(e) = backend.send_multipart(msg).await {
if is_transient_send_error(&e) {
debug!("Proxy: transient send to {}, dropping frame: {}",
backend.socket_desc(), e);
} else {
return Err(e);
}
}
}
}
msg_result = backend.recv_multipart().fuse() => {
if let Some(msg) = msg_result? {
debug!("Proxy: {} → {}: {} frames",
backend.socket_desc(),
frontend.socket_desc(),
msg.len());
if let Some(ref mut cap) = capture
&& let Err(e) = cap.send_multipart(msg.clone()).await
{
debug!("Capture socket send failed: {}", e);
}
if let Err(e) = frontend.send_multipart(msg).await {
if is_transient_send_error(&e) {
debug!("Proxy: transient send to {}, dropping frame: {}",
frontend.socket_desc(), e);
} else {
return Err(e);
}
}
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyCommand {
Pause,
Resume,
Terminate,
Statistics,
}
impl ProxyCommand {
pub const fn from_bytes(data: &[u8]) -> Option<Self> {
match data {
b"PAUSE" => Some(Self::Pause),
b"RESUME" => Some(Self::Resume),
b"TERMINATE" => Some(Self::Terminate),
b"STATISTICS" => Some(Self::Statistics),
_ => None,
}
}
pub const fn as_bytes(&self) -> &'static [u8] {
match self {
Self::Pause => b"PAUSE",
Self::Resume => b"RESUME",
Self::Terminate => b"TERMINATE",
Self::Statistics => b"STATISTICS",
}
}
}
pub async fn proxy_steerable<F, B, C, Ctrl>(
frontend: &mut F,
backend: &mut B,
mut capture: Option<&mut C>,
control: &mut Ctrl,
) -> io::Result<()>
where
F: ProxySocket,
B: ProxySocket,
C: ProxySocket,
Ctrl: ProxySocket,
{
use futures::{FutureExt, select};
debug!(
"Starting steerable proxy: {} ←→ {} (control enabled)",
frontend.socket_desc(),
backend.socket_desc()
);
let mut paused = false;
let mut message_count = 0u64;
loop {
select! {
cmd_result = control.recv_multipart().fuse() => {
if let Some(cmd_msg) = cmd_result?
&& let Some(cmd_frame) = cmd_msg.first()
&& let Some(cmd) = ProxyCommand::from_bytes(cmd_frame)
{
debug!("Proxy control command: {:?}", cmd);
match cmd {
ProxyCommand::Pause => {
debug!("Proxy PAUSED");
paused = true;
}
ProxyCommand::Resume => {
debug!("Proxy RESUMED");
paused = false;
}
ProxyCommand::Terminate => {
debug!("Proxy TERMINATING (forwarded {} messages)", message_count);
return Ok(());
}
ProxyCommand::Statistics => {
debug!("Proxy statistics: {} messages forwarded", message_count);
let stats = format!("messages_forwarded={}", message_count);
let _ = control.send_multipart(vec![bytes::Bytes::from(stats)]).await;
}
}
}
}
msg_result = frontend.recv_multipart().fuse() => {
if let Some(msg) = msg_result? {
if paused {
debug!("Proxy: dropped message (paused)");
} else {
debug!("Proxy: {} → {}: {} frames",
frontend.socket_desc(),
backend.socket_desc(),
msg.len());
if let Some(ref mut cap) = capture
&& let Err(e) = cap.send_multipart(msg.clone()).await
{
debug!("Capture socket send failed: {}", e);
}
match backend.send_multipart(msg).await {
Ok(()) => message_count += 1,
Err(e) if is_transient_send_error(&e) => {
debug!("Proxy: transient send to {}, dropping frame: {}",
backend.socket_desc(), e);
}
Err(e) => return Err(e),
}
}
}
}
msg_result = backend.recv_multipart().fuse() => {
if let Some(msg) = msg_result? {
if paused {
debug!("Proxy: dropped message (paused)");
} else {
debug!("Proxy: {} → {}: {} frames",
backend.socket_desc(),
frontend.socket_desc(),
msg.len());
if let Some(ref mut cap) = capture
&& let Err(e) = cap.send_multipart(msg.clone()).await
{
debug!("Capture socket send failed: {}", e);
}
match frontend.send_multipart(msg).await {
Ok(()) => message_count += 1,
Err(e) if is_transient_send_error(&e) => {
debug!("Proxy: transient send to {}, dropping frame: {}",
frontend.socket_desc(), e);
}
Err(e) => return Err(e),
}
}
}
}
}
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for XSubSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
if msg.is_empty() {
return Ok(());
}
let cmd_frame = &msg[0];
if cmd_frame.is_empty() {
return Ok(());
}
let cmd_byte = cmd_frame[0];
let topic: Bytes = if msg.len() >= 2 {
msg[1].clone()
} else if cmd_frame.len() > 1 {
cmd_frame.slice(1..)
} else {
Bytes::new()
};
let event = if cmd_byte == 0x01 {
monocoque_core::subscription::SubscriptionEvent::Subscribe(topic)
} else if cmd_byte == 0x00 {
monocoque_core::subscription::SubscriptionEvent::Unsubscribe(topic)
} else {
return Ok(());
};
self.send_subscription_event(event).await
}
fn socket_desc(&self) -> &'static str {
"XSUB"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for XPubSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
if let Some(event) = self.recv_subscription().await? {
let msg = match event {
monocoque_core::subscription::SubscriptionEvent::Subscribe(topic) => {
vec![Bytes::from(&b"\x01"[..]), topic]
}
monocoque_core::subscription::SubscriptionEvent::Unsubscribe(topic) => {
vec![Bytes::from(&b"\x00"[..]), topic]
}
};
Ok(Some(msg))
} else {
Ok(None)
}
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"XPUB"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for DealerSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"DEALER"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for RouterSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"ROUTER"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for PullSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, _msg: Vec<Bytes>) -> io::Result<()> {
Ok(())
}
fn socket_desc(&self) -> &'static str {
"PULL"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for PushSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
Ok(None)
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"PUSH"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for ReqSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"REQ"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for RepSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"REP"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for PairSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"PAIR"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for PubSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
Ok(None)
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send(msg).await
}
fn socket_desc(&self) -> &'static str {
"PUB"
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for SubSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
self.recv().await
}
async fn send_multipart(&mut self, _msg: Vec<Bytes>) -> io::Result<()> {
Ok(())
}
fn socket_desc(&self) -> &'static str {
"SUB"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transient_send_errors_are_classified() {
for kind in [
io::ErrorKind::WouldBlock,
io::ErrorKind::Interrupted,
io::ErrorKind::TimedOut,
] {
assert!(is_transient_send_error(&io::Error::new(kind, "x")));
}
for kind in [
io::ErrorKind::BrokenPipe,
io::ErrorKind::ConnectionReset,
io::ErrorKind::NotConnected,
] {
assert!(!is_transient_send_error(&io::Error::new(kind, "x")));
}
}
struct MockSocket {
name: &'static str,
recv_queue: Vec<Vec<Bytes>>,
send_queue: Vec<Vec<Bytes>>,
}
impl MockSocket {
fn new(name: &'static str) -> Self {
Self {
name,
recv_queue: Vec::new(),
send_queue: Vec::new(),
}
}
fn enqueue(&mut self, msg: Vec<Bytes>) {
self.recv_queue.push(msg);
}
}
#[async_trait::async_trait(?Send)]
impl ProxySocket for MockSocket {
async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
Ok(self.recv_queue.pop())
}
async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
self.send_queue.push(msg);
Ok(())
}
fn socket_desc(&self) -> &'static str {
self.name
}
}
#[test]
fn test_mock_socket() {
let mut sock = MockSocket::new("test");
sock.enqueue(vec![Bytes::from("hello")]);
assert_eq!(sock.recv_queue.len(), 1);
}
}