use std::io;
use std::time::Duration;
use crate::tracing::tx_log_from_str;
use jsonrpsee_types::error::{
reject_too_big_batch_response, ErrorCode, ErrorObject, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG,
};
use jsonrpsee_types::{Id, InvalidRequest, Response, ResponsePayload};
use serde::Serialize;
use serde_json::value::to_raw_value;
use tokio::sync::mpsc;
use super::{DisconnectError, SendTimeoutError, SubscriptionMessage, TrySendError};
#[derive(Debug, Clone)]
pub struct BoundedWriter {
max_len: usize,
buf: Vec<u8>,
}
impl BoundedWriter {
pub fn new(max_len: usize) -> Self {
Self { max_len, buf: Vec::with_capacity(128) }
}
pub fn into_bytes(self) -> Vec<u8> {
self.buf
}
}
impl<'a> io::Write for &'a mut BoundedWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let len = self.buf.len() + buf.len();
if self.max_len >= len {
self.buf.extend_from_slice(buf);
Ok(buf.len())
} else {
Err(io::Error::new(io::ErrorKind::OutOfMemory, "Memory capacity exceeded"))
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct MethodSink {
tx: mpsc::Sender<String>,
max_response_size: u32,
max_log_length: u32,
}
impl MethodSink {
pub fn new(tx: mpsc::Sender<String>) -> Self {
MethodSink { tx, max_response_size: u32::MAX, max_log_length: u32::MAX }
}
pub fn new_with_limit(tx: mpsc::Sender<String>, max_response_size: u32, max_log_length: u32) -> Self {
MethodSink { tx, max_response_size, max_log_length }
}
pub fn is_closed(&self) -> bool {
self.tx.is_closed()
}
pub async fn closed(&self) {
self.tx.closed().await
}
pub const fn max_response_size(&self) -> u32 {
self.max_response_size
}
pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> {
tx_log_from_str(&msg, self.max_log_length);
self.tx.try_send(msg).map_err(Into::into)
}
pub async fn send(&self, msg: String) -> Result<(), DisconnectError> {
tx_log_from_str(&msg, self.max_log_length);
self.tx.send(msg).await.map_err(Into::into)
}
pub async fn send_error<'a>(&self, id: Id<'a>, err: ErrorObject<'a>) -> Result<(), DisconnectError> {
let json =
serde_json::to_string(&Response::new(ResponsePayload::<()>::Error(err), id)).expect("valid JSON; qed");
self.send(json).await
}
pub async fn send_timeout(&self, msg: String, timeout: Duration) -> Result<(), SendTimeoutError> {
tx_log_from_str(&msg, self.max_log_length);
self.tx.send_timeout(msg, timeout).await.map_err(Into::into)
}
pub async fn has_capacity(&self) -> Result<(), DisconnectError> {
match self.tx.reserve().await {
Ok(_) => Ok(()),
Err(_) => Err(DisconnectError(SubscriptionMessage::empty())),
}
}
}
pub fn prepare_error(data: &[u8]) -> (Id<'_>, ErrorCode) {
match serde_json::from_slice::<InvalidRequest>(data) {
Ok(InvalidRequest { id }) => (id, ErrorCode::InvalidRequest),
Err(_) => (Id::Null, ErrorCode::ParseError),
}
}
#[derive(Debug, Clone)]
pub struct MethodResponse {
pub result: String,
pub success_or_error: MethodResponseResult,
}
impl MethodResponse {
pub fn is_success(&self) -> bool {
self.success_or_error.is_success()
}
pub fn is_error(&self) -> bool {
self.success_or_error.is_success()
}
}
#[derive(Debug, Copy, Clone)]
pub enum MethodResponseResult {
Success,
Failed(i32),
}
impl MethodResponseResult {
pub fn is_success(&self) -> bool {
matches!(self, MethodResponseResult::Success)
}
pub fn is_error(&self) -> bool {
matches!(self, MethodResponseResult::Failed(_))
}
pub fn as_error_code(&self) -> Option<i32> {
match self {
Self::Failed(e) => Some(*e),
_ => None,
}
}
}
impl MethodResponse {
pub fn response<T>(id: Id, result: ResponsePayload<T>, max_response_size: usize) -> Self
where
T: Serialize + Clone,
{
let mut writer = BoundedWriter::new(max_response_size);
let success_or_error = if let ResponsePayload::Error(ref e) = result {
MethodResponseResult::Failed(e.code())
} else {
MethodResponseResult::Success
};
match serde_json::to_writer(&mut writer, &Response::new(result, id.clone())) {
Ok(_) => {
let result = unsafe { String::from_utf8_unchecked(writer.into_bytes()) };
Self { result, success_or_error }
}
Err(err) => {
tracing::error!("Error serializing response: {:?}", err);
if err.is_io() {
let data = to_raw_value(&format!("Exceeded max limit of {max_response_size}")).ok();
let err_code = OVERSIZED_RESPONSE_CODE;
let err = ResponsePayload::error_borrowed(ErrorObject::borrowed(
err_code,
OVERSIZED_RESPONSE_MSG,
data.as_deref(),
));
let result =
serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed");
Self { result, success_or_error: MethodResponseResult::Failed(err_code) }
} else {
let err_code = ErrorCode::InternalError;
let result = serde_json::to_string(&Response::new(err_code.into(), id))
.expect("JSON serialization infallible; qed");
Self { result, success_or_error: MethodResponseResult::Failed(err_code.code()) }
}
}
}
}
pub fn error<'a>(id: Id, err: impl Into<ErrorObject<'a>>) -> Self {
let err: ErrorObject = err.into();
let err_code = err.code();
let err = ResponsePayload::error_borrowed(err);
let result = serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed");
Self { result, success_or_error: MethodResponseResult::Failed(err_code) }
}
}
#[derive(Debug, Clone, Default)]
pub struct BatchResponseBuilder {
result: String,
max_response_size: usize,
}
impl BatchResponseBuilder {
pub fn new_with_limit(limit: usize) -> Self {
let mut initial = String::with_capacity(2048);
initial.push('[');
Self { result: initial, max_response_size: limit }
}
pub fn append(&mut self, response: &MethodResponse) -> Result<(), String> {
let len = response.result.len() + self.result.len() + 1;
if len > self.max_response_size {
Err(batch_response_error(Id::Null, reject_too_big_batch_response(self.max_response_size)))
} else {
self.result.push_str(&response.result);
self.result.push(',');
Ok(())
}
}
pub fn is_empty(&self) -> bool {
self.result.len() <= 1
}
pub fn finish(mut self) -> String {
if self.result.len() == 1 {
batch_response_error(Id::Null, ErrorObject::from(ErrorCode::InvalidRequest))
} else {
self.result.pop();
self.result.push(']');
self.result
}
}
}
pub fn batch_response_error(id: Id, err: impl Into<ErrorObject<'static>>) -> String {
let err = ResponsePayload::error_borrowed(err);
serde_json::to_string(&Response::new(err, id)).expect("JSON serialization infallible; qed")
}
#[cfg(test)]
mod tests {
use super::{BatchResponseBuilder, BoundedWriter, Id, MethodResponse, Response};
use jsonrpsee_types::ResponsePayload;
#[test]
fn bounded_serializer_work() {
let mut writer = BoundedWriter::new(100);
let result = ResponsePayload::result(&"success");
let rp = &Response::new(result, Id::Number(1));
assert!(serde_json::to_writer(&mut writer, rp).is_ok());
assert_eq!(String::from_utf8(writer.into_bytes()).unwrap(), r#"{"jsonrpc":"2.0","result":"success","id":1}"#);
}
#[test]
fn bounded_serializer_cap_works() {
let mut writer = BoundedWriter::new(100);
assert!(serde_json::to_writer(&mut writer, &"x".repeat(99)).is_err());
}
#[test]
fn batch_with_single_works() {
let method = MethodResponse::response(Id::Number(1), ResponsePayload::result_borrowed(&"a"), usize::MAX);
assert_eq!(method.result.len(), 37);
let mut builder = BatchResponseBuilder::new_with_limit(39);
builder.append(&method).unwrap();
let batch = builder.finish();
assert_eq!(batch, r#"[{"jsonrpc":"2.0","result":"a","id":1}]"#)
}
#[test]
fn batch_with_multiple_works() {
let m1 = MethodResponse::response(Id::Number(1), ResponsePayload::result_borrowed(&"a"), usize::MAX);
assert_eq!(m1.result.len(), 37);
let limit = 2 + (37 * 2) + 1;
let mut builder = BatchResponseBuilder::new_with_limit(limit);
builder.append(&m1).unwrap();
builder.append(&m1).unwrap();
let batch = builder.finish();
assert_eq!(batch, r#"[{"jsonrpc":"2.0","result":"a","id":1},{"jsonrpc":"2.0","result":"a","id":1}]"#)
}
#[test]
fn batch_empty_err() {
let batch = BatchResponseBuilder::new_with_limit(1024).finish();
let exp_err = r#"{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":null}"#;
assert_eq!(batch, exp_err);
}
#[test]
fn batch_too_big() {
let method = MethodResponse::response(Id::Number(1), ResponsePayload::result_borrowed(&"a".repeat(28)), 128);
assert_eq!(method.result.len(), 64);
let batch = BatchResponseBuilder::new_with_limit(63).append(&method).unwrap_err();
let exp_err = r#"{"jsonrpc":"2.0","error":{"code":-32011,"message":"The batch response was too large","data":"Exceeded max limit of 63"},"id":null}"#;
assert_eq!(batch, exp_err);
}
}