use crate::base::SocketBase;
use crate::{handshake::perform_handshake_with_options, session::SocketType};
use bytes::Bytes;
use compio_io::{AsyncRead, AsyncWrite};
use monocoque_core::endpoint::Endpoint;
use monocoque_core::options::SocketOptions;
use monocoque_core::rt::TcpStream;
use smallvec::SmallVec;
use std::io;
use tracing::{debug, trace};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReqState {
Idle,
AwaitingReply,
}
pub struct ReqSocket<S = TcpStream>
where
S: AsyncRead + AsyncWrite + Unpin,
{
base: SocketBase<S>,
frames: SmallVec<[Bytes; 4]>,
state: ReqState,
request_id: u32,
expected_request_id: Option<u32>,
}
impl<S> ReqSocket<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
pub async fn new(stream: S) -> io::Result<Self> {
Self::with_options(stream, SocketOptions::default()).await
}
pub async fn with_options(mut stream: S, options: SocketOptions) -> io::Result<Self> {
debug!("[REQ] Creating new direct REQ socket");
debug!("[REQ] Performing ZMTP handshake...");
let handshake_result = perform_handshake_with_options(
&mut stream,
SocketType::Req,
options.routing_id.as_deref(),
Some(options.handshake_timeout),
&options,
)
.await
.map_err(|e| io::Error::other(format!("Handshake failed: {}", e)))?;
debug!(
peer_identity = ?handshake_result.peer_identity,
peer_socket_type = ?handshake_result.peer_socket_type,
"[REQ] Handshake complete"
);
debug!("[REQ] Socket initialized");
let mut base = SocketBase::new(stream, SocketType::Req, options);
base.curve_cipher = handshake_result.curve_cipher;
Ok(Self {
base,
frames: SmallVec::new(),
state: ReqState::Idle,
request_id: 0,
expected_request_id: None,
})
}
pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
if !self.base.options.req_relaxed && self.state != ReqState::Idle {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Cannot send while awaiting reply - must call recv() first (use req_relaxed mode to allow multiple outstanding requests)",
));
}
trace!("[REQ] Sending {} frames", msg.len());
let frames_to_send = if self.base.options.req_correlate {
self.request_id = self.request_id.wrapping_add(1);
self.expected_request_id = Some(self.request_id);
trace!(
"[REQ] Correlation enabled, prepending request ID: {}",
self.request_id
);
let mut correlated_msg = Vec::with_capacity(msg.len() + 1);
correlated_msg.push(Bytes::copy_from_slice(&self.request_id.to_be_bytes()));
correlated_msg.extend(msg);
correlated_msg
} else {
msg
};
self.base.encode_message_to_write_buf(&frames_to_send)?;
self.base.write_from_buf().await?;
self.state = ReqState::AwaitingReply;
trace!("[REQ] Message sent successfully");
Ok(())
}
pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
if self.state != ReqState::AwaitingReply {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Cannot recv while in Idle state - must call send() first",
));
}
trace!("[REQ] Waiting for reply");
loop {
loop {
match self.base.process_frame()? {
crate::base::FrameResult::NeedMore => break,
crate::base::FrameResult::CommandHandled => {
if !self.base.send_buffer.is_empty() {
self.base.flush_send_buffer().await?;
}
}
crate::base::FrameResult::Data(more, payload) => {
self.frames.push(payload);
if !more {
let msg: Vec<Bytes> = self.frames.drain(..).collect();
trace!("[REQ] Received {} frames", msg.len());
let validated_msg = if self.base.options.req_correlate {
if msg.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Correlation enabled but received empty message",
));
}
let id_frame = &msg[0];
if id_frame.len() != 4 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Correlation frame has invalid length: {} (expected 4)",
id_frame.len()
),
));
}
let received_id = u32::from_be_bytes([
id_frame[0],
id_frame[1],
id_frame[2],
id_frame[3],
]);
trace!("[REQ] Received correlation ID: {}", received_id);
if let Some(expected) = self.expected_request_id {
if received_id != expected {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Request ID mismatch: expected {}, got {}",
expected, received_id
),
));
}
trace!("[REQ] Correlation ID validated successfully");
}
let mut msg = msg;
msg.remove(0);
msg
} else {
msg
};
self.state = ReqState::Idle;
self.expected_request_id = None;
return Ok(Some(validated_msg));
}
}
}
}
let n = self.base.read_raw().await?;
if n == 0 {
trace!("[REQ] Connection closed");
self.state = ReqState::Idle;
return Ok(None);
}
if self.base.check_heartbeat()? {
self.base.flush_send_buffer().await?;
}
}
}
pub const fn state(&self) -> ReqState {
self.state
}
pub const fn stream_ref(&self) -> Option<&S> {
self.base.stream.as_ref()
}
pub fn stream_mut(&mut self) -> Option<&mut S> {
self.base.stream.as_mut()
}
pub async fn close(self) -> io::Result<()> {
trace!("[REQ] Closing socket");
Ok(())
}
#[inline]
pub const fn options(&self) -> &SocketOptions {
&self.base.options
}
#[inline]
pub fn options_mut(&mut self) -> &mut SocketOptions {
&mut self.base.options
}
#[inline]
pub fn set_options(&mut self, options: SocketOptions) {
self.base.set_options(options);
}
#[inline]
pub const fn socket_type(&self) -> SocketType {
SocketType::Req
}
#[inline]
pub fn last_endpoint(&self) -> Option<&Endpoint> {
self.base.last_endpoint()
}
#[inline]
pub fn has_more(&self) -> bool {
self.base.has_more()
}
#[inline]
pub fn events(&self) -> u32 {
self.base.events()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_req_state_transitions() {
assert_eq!(ReqState::Idle, ReqState::Idle);
assert_eq!(ReqState::AwaitingReply, ReqState::AwaitingReply);
assert_ne!(ReqState::Idle, ReqState::AwaitingReply);
}
#[test]
fn test_compio_stream_creation() {
}
}
impl ReqSocket<TcpStream> {
pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
Self::from_tcp_with_options(stream, SocketOptions::default()).await
}
pub async fn from_tcp_with_options(
stream: TcpStream,
options: SocketOptions,
) -> io::Result<Self> {
crate::utils::configure_tcp_stream(&stream, &options, "REQ")?;
Self::with_options(stream, options).await
}
pub async fn connect(addr: impl monocoque_core::rt::ToSocketAddrs) -> io::Result<Self> {
Self::connect_with_options(addr, SocketOptions::default()).await
}
pub async fn connect_with_options(
addr: impl monocoque_core::rt::ToSocketAddrs,
options: SocketOptions,
) -> io::Result<Self> {
let stream = TcpStream::connect(addr).await?;
let peer_addr = stream.peer_addr()?;
crate::utils::configure_tcp_stream(&stream, &options, "REQ")?;
let mut stream = stream;
let handshake_result = perform_handshake_with_options(
&mut stream,
SocketType::Req,
options.routing_id.as_deref(),
Some(options.handshake_timeout),
&options,
)
.await
.map_err(|e| io::Error::other(format!("Handshake failed: {}", e)))?;
debug!(
peer_identity = ?handshake_result.peer_identity,
peer_socket_type = ?handshake_result.peer_socket_type,
"[REQ] Connected to {} (endpoint stored for reconnection)",
peer_addr
);
let endpoint = monocoque_core::endpoint::Endpoint::Tcp(peer_addr);
let mut base =
crate::base::SocketBase::with_endpoint(stream, SocketType::Req, endpoint, options);
base.curve_cipher = handshake_result.curve_cipher;
Ok(Self {
base,
frames: SmallVec::new(),
state: ReqState::Idle,
request_id: 0,
expected_request_id: None,
})
}
#[inline]
pub fn is_connected(&self) -> bool {
self.base.is_connected()
}
pub async fn try_reconnect(&mut self) -> io::Result<()> {
self.base.try_reconnect(SocketType::Req).await
}
pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<Bytes>>> {
let max = self.base.options.max_reconnect_attempts;
let mut attempts = 0u32;
loop {
if self.base.stream.is_none() {
if let Some(limit) = max
&& attempts >= limit
{
return Err(io::Error::new(
io::ErrorKind::NotConnected,
format!("Max {} reconnection attempts exceeded", limit),
));
}
attempts += 1;
trace!(
"[REQ] Stream disconnected, reconnecting (attempt {})",
attempts
);
self.state = ReqState::Idle;
self.expected_request_id = None;
self.try_reconnect().await?;
}
match self.recv().await {
Ok(Some(msg)) => return Ok(Some(msg)),
Ok(None) => {
debug!("[REQ] EOF on recv, will reconnect");
self.state = ReqState::Idle;
self.expected_request_id = None;
}
Err(e) => {
if self.base.stream.is_none()
|| matches!(
e.kind(),
io::ErrorKind::ConnectionReset
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::BrokenPipe
| io::ErrorKind::UnexpectedEof
)
{
debug!("[REQ] Connection error on recv ({}), will reconnect", e);
self.base.stream = None;
self.state = ReqState::Idle;
self.expected_request_id = None;
} else {
return Err(e);
}
}
}
}
}
pub async fn send_with_reconnect(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
let max = self.base.options.max_reconnect_attempts;
let mut attempts = 0u32;
loop {
if self.base.stream.is_none() {
if let Some(limit) = max
&& attempts >= limit
{
return Err(io::Error::new(
io::ErrorKind::NotConnected,
format!("Max {} reconnection attempts exceeded", limit),
));
}
attempts += 1;
trace!(
"[REQ] Stream disconnected, reconnecting (attempt {})",
attempts
);
self.state = ReqState::Idle;
self.expected_request_id = None;
self.try_reconnect().await?;
}
match self.send(msg.clone()).await {
Ok(()) => return Ok(()),
Err(_) if self.base.stream.is_none() => {
debug!("[REQ] Send failed (stream lost), will reconnect");
self.state = ReqState::Idle;
self.expected_request_id = None;
}
Err(e) => return Err(e),
}
}
}
}
crate::impl_socket_trait!(ReqSocket<S>, SocketType::Req);