// Copyright 2023 Protocol Labs // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. /// A request-response behaviour using [`cbor4ii::serde`] for serializing and /// deserializing the messages. /// /// # Example /// /// ``` /// # use libp2p_request_response::{cbor, ProtocolSupport, self as request_response}; /// # use libp2p_swarm::{StreamProtocol, SwarmBuilder}; /// #[derive(Debug, serde::Serialize, serde::Deserialize)] /// struct GreetRequest { /// name: String, /// } /// /// #[derive(Debug, serde::Serialize, serde::Deserialize)] /// struct GreetResponse { /// message: String, /// } /// /// let behaviour = cbor::Behaviour::::new( /// [(StreamProtocol::new("/my-cbor-protocol"), ProtocolSupport::Full)], /// request_response::Config::default() /// ); /// ``` pub type Behaviour = crate::Behaviour>; mod codec { use async_trait::async_trait; use cbor4ii::core::error::DecodeError; use futures::prelude::*; use futures::{AsyncRead, AsyncWrite}; use libp2p_swarm::StreamProtocol; use serde::{de::DeserializeOwned, Serialize}; use std::{collections::TryReserveError, convert::Infallible, io, marker::PhantomData}; /// Max request size in bytes const REQUEST_SIZE_MAXIMUM: u64 = 1024 * 1024; /// Max response size in bytes const RESPONSE_SIZE_MAXIMUM: u64 = 10 * 1024 * 1024; pub struct Codec { phantom: PhantomData<(Req, Resp)>, } impl Default for Codec { fn default() -> Self { Codec { phantom: PhantomData, } } } impl Clone for Codec { fn clone(&self) -> Self { Self::default() } } #[async_trait] impl crate::Codec for Codec where Req: Send + Serialize + DeserializeOwned, Resp: Send + Serialize + DeserializeOwned, { type Protocol = StreamProtocol; type Request = Req; type Response = Resp; async fn read_request(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result where T: AsyncRead + Unpin + Send, { let mut vec = Vec::new(); io.take(REQUEST_SIZE_MAXIMUM).read_to_end(&mut vec).await?; cbor4ii::serde::from_slice(vec.as_slice()).map_err(decode_into_io_error) } async fn read_response(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result where T: AsyncRead + Unpin + Send, { let mut vec = Vec::new(); io.take(RESPONSE_SIZE_MAXIMUM).read_to_end(&mut vec).await?; cbor4ii::serde::from_slice(vec.as_slice()).map_err(decode_into_io_error) } async fn write_request( &mut self, _: &Self::Protocol, io: &mut T, req: Self::Request, ) -> io::Result<()> where T: AsyncWrite + Unpin + Send, { let data: Vec = cbor4ii::serde::to_vec(Vec::new(), &req).map_err(encode_into_io_error)?; io.write_all(data.as_ref()).await?; Ok(()) } async fn write_response( &mut self, _: &Self::Protocol, io: &mut T, resp: Self::Response, ) -> io::Result<()> where T: AsyncWrite + Unpin + Send, { let data: Vec = cbor4ii::serde::to_vec(Vec::new(), &resp).map_err(encode_into_io_error)?; io.write_all(data.as_ref()).await?; Ok(()) } } fn decode_into_io_error(err: cbor4ii::serde::DecodeError) -> io::Error { match err { cbor4ii::serde::DecodeError::Core(DecodeError::Read(e)) => { io::Error::new(io::ErrorKind::Other, e) } cbor4ii::serde::DecodeError::Core(e @ DecodeError::Unsupported { .. }) => { io::Error::new(io::ErrorKind::Unsupported, e) } cbor4ii::serde::DecodeError::Core(e @ DecodeError::Eof { .. }) => { io::Error::new(io::ErrorKind::UnexpectedEof, e) } cbor4ii::serde::DecodeError::Core(e) => io::Error::new(io::ErrorKind::InvalidData, e), cbor4ii::serde::DecodeError::Custom(e) => { io::Error::new(io::ErrorKind::Other, e.to_string()) } } } fn encode_into_io_error(err: cbor4ii::serde::EncodeError) -> io::Error { io::Error::new(io::ErrorKind::Other, err) } } #[cfg(test)] mod tests { use crate::cbor::codec::Codec; use crate::Codec as _; use futures::AsyncWriteExt; use futures_ringbuf::Endpoint; use libp2p_swarm::StreamProtocol; use serde::{Deserialize, Serialize}; #[async_std::test] async fn test_codec() { let expected_request = TestRequest { payload: "test_payload".to_string(), }; let expected_response = TestResponse { payload: "test_payload".to_string(), }; let protocol = StreamProtocol::new("/test_cbor/1"); let mut codec = Codec::default(); let (mut a, mut b) = Endpoint::pair(124, 124); codec .write_request(&protocol, &mut a, expected_request.clone()) .await .expect("Should write request"); a.close().await.unwrap(); let actual_request = codec .read_request(&protocol, &mut b) .await .expect("Should read request"); b.close().await.unwrap(); assert_eq!(actual_request, expected_request); let (mut a, mut b) = Endpoint::pair(124, 124); codec .write_response(&protocol, &mut a, expected_response.clone()) .await .expect("Should write response"); a.close().await.unwrap(); let actual_response = codec .read_response(&protocol, &mut b) .await .expect("Should read response"); b.close().await.unwrap(); assert_eq!(actual_response, expected_response); } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct TestRequest { payload: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct TestResponse { payload: String, } }