use std::fmt::{Debug, Display}; use std::marker::Unpin; use futures::io::{AsyncReadExt, AsyncWriteExt, BufReader}; use futures::{AsyncRead, AsyncWrite}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::service::ServiceId; /// The size of a usize, in bytes. const USIZE_SIZE: usize = std::mem::size_of::(); /// Unique "address" for a server/client connection. /// /// In the case of this local socket implementation, this is a socket address (path on the /// filesystem). Conceptually, this somewhat similar to an IP address + port. #[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)] pub struct ConnectionAddress(pub(super) String); impl ConnectionAddress { /// Returns a `ConnectionAddress` containing a path for a socket address. pub(super) fn new() -> Self { Self(format!("/tmp/warp-ipc-{}.sock", rand::random::())) } } impl Display for ConnectionAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl From for ConnectionAddress { fn from(value: String) -> Self { ConnectionAddress(value) } } /// A unique ID for each request message. /// /// The corresponding response for the request should contain the same ID. pub(super) type RequestId = Uuid; /// Trait for arbitrary messages that may be sent across the 'wire' (the socket). pub trait Message: 'static + Send + Sync + Debug + Clone + DeserializeOwned + Serialize {} impl Message for T where T: 'static + Send + Sync + Debug + Clone + DeserializeOwned + Serialize {} /// Request message sent by clients and received by servers. #[derive(Serialize, Deserialize, Debug, Clone)] pub(super) struct Request { /// A unique ID for the request. pub(super) id: RequestId, /// The ID of the service to which this request belongs. pub(super) service_id: ServiceId, /// The actual request payload. pub(super) bytes: Vec, } impl Request { /// Constructs a `Request`, generating a unique request ID in the process. pub(super) fn new(service_id: ServiceId, bytes: Vec) -> Self { Self { id: Uuid::new_v4(), service_id, bytes, } } pub(super) fn id(&self) -> &RequestId { &self.id } } /// Response message sent by servers and received by clients. #[derive(Serialize, Deserialize, Debug, Clone)] pub(super) enum Response { /// For responses produced "successfully". "Successful" only pertains to the frameworks ability /// to successfully execute the `Service` handler and produce a response. `Service`s may /// internally implement their own error types/response schemas. Success { /// The ID of the request for which this is a response. request_id: RequestId, /// The ID of the service to which this response belongs. service_id: ServiceId, /// The actual response payload. bytes: Vec, }, /// For responses that failed due to a framework-level issue. For example, the client attempted /// to call a service that wasn't registered in the server. Failure { /// The ID of the request for which this is a response. request_id: RequestId, error_message: String, }, } impl Response { /// Constructs a "success" response for the request with the given `request_id`. pub(super) fn success(request_id: RequestId, service_id: ServiceId, bytes: Vec) -> Self { Self::Success { request_id, service_id, bytes, } } /// Constructs a "failure" response for the request with the given `request_id`. pub(super) fn failure(request_id: RequestId, error_message: String) -> Self { Self::Failure { request_id, error_message, } } } #[derive(thiserror::Error, Debug)] pub enum ProtocolError { /// An error occurred when serializing the request or response. #[error(transparent)] Serialization(#[from] bincode::Error), /// The connection was dropped. #[error(transparent)] Disconnected(#[from] std::io::Error), #[error("Unknown error occurred: {0}")] Other(String), } /// Writes the given message to the given `writer`. pub(super) async fn send_message(writer: &mut W, message: M) -> Result<(), ProtocolError> where M: Message, W: AsyncWrite + Unpin, { let serialized_msg = bincode::serialize(&message)?; // Create a buffer to hold the data to be written. let mut buf = Vec::with_capacity(serialized_msg.len() + USIZE_SIZE); // First, add a message "header" - a usize representing the length of the // serialized payload, in bytes. buf.extend_from_slice(&serialized_msg.len().to_be_bytes()); // Next, add the serialized payload itself. buf.extend(serialized_msg); // Finally, write the buffer to the underlying transport. Ok(writer.write_all(&buf[..]).await?) } /// Reads the next message from the given `reader`. pub(super) async fn receive_message(reader: &mut BufReader) -> Result where M: Message, R: AsyncRead + Unpin, { // Start by allocating a buffer that is only large enough to receive the // message header, to ensure we don't accidentally receive multiple messages // in a single read. let mut header_buf = [0; USIZE_SIZE]; // Read the message "header" from the socket. reader.read_exact(&mut header_buf[..]).await?; // Parse the message header - we convert the bytes back into a usize, which // tells us the size of the serialized message, in bytes. We add the size // of a usize to get the total number of bytes we expect to read off the // wire. let payload_len = usize::from_be_bytes(header_buf); // Grow the initial buffer to a sufficient size and read the rest of the // message from the socket. let mut payload_buf = vec![0; payload_len]; reader.read_exact(&mut payload_buf).await?; // Deserialize the message. let message: M = bincode::deserialize(&payload_buf[..])?; Ok(message) }