/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{collections::HashSet, io::ErrorKind, sync::Arc}; use async_trait::async_trait; use log::{debug, error, info}; use smol::{ io::{BufReader, ReadHalf, WriteHalf}, lock::{Mutex, MutexGuard}, }; use tinyjson::JsonValue; use url::Url; use super::{ common::{read_from_stream, write_to_stream, INIT_BUF_SIZE}, jsonrpc::*, }; use crate::{ net::transport::{Listener, PtListener, PtStream}, system::{StoppableTask, StoppableTaskPtr}, Error, Result, }; /// Asynchronous trait implementing a handler for incoming JSON-RPC requests. #[async_trait] pub trait RequestHandler: Sync + Send { async fn handle_request(&self, req: JsonRequest) -> JsonResult; async fn pong(&self, id: u16, _params: JsonValue) -> JsonResult { JsonResponse::new(JsonValue::String("pong".to_string()), id).into() } async fn connections_mut(&self) -> MutexGuard<'_, HashSet>; async fn connections(&self) -> Vec { self.connections_mut().await.iter().cloned().collect() } async fn mark_connection(&self, task: StoppableTaskPtr) { self.connections_mut().await.insert(task); } async fn unmark_connection(&self, task: StoppableTaskPtr) { self.connections_mut().await.remove(&task); } async fn active_connections(&self) -> usize { self.connections_mut().await.len() } async fn stop_connections(&self) { info!(target: "rpc::server", "[RPC] Server stopped, closing connections"); for (i, task) in self.connections().await.iter().enumerate() { debug!(target: "rpc::server", "Stopping connection #{}", i); task.stop().await; } } } /// Accept function that should run inside a loop for accepting incoming /// JSON-RPC requests and passing them to the [`RequestHandler`]. #[allow(clippy::type_complexity)] pub async fn accept( reader: Arc>>>>, writer: Arc>>>, addr: Url, rh: Arc, conn_limit: Option, ex: Arc>, ) -> Result<()> { // If there's a connection limit set, we will refuse connections // after this point. if let Some(conn_limit) = conn_limit { if rh.clone().active_connections().await >= conn_limit { debug!( target: "rpc::server::accept()", "Connection limit reached, refusing new conn" ); return Err(Error::RpcConnectionsExhausted) } } // We'll hold our background tasks here let tasks = Arc::new(Mutex::new(HashSet::new())); loop { let mut buf = Vec::with_capacity(INIT_BUF_SIZE); let mut reader_lock = reader.lock().await; let _ = read_from_stream(&mut reader_lock, &mut buf).await?; drop(reader_lock); let line = match String::from_utf8(buf) { Ok(v) => v, Err(e) => { error!( target: "rpc::server::accept()", "[RPC SERVER] Failed parsing string from read buffer: {}", e, ); return Err(e.into()) } }; // Parse the line as JSON let val: JsonValue = match line.trim().parse() { Ok(v) => v, Err(e) => { error!( target: "rpc::server::accept()", "[RPC SERVER] Failed parsing JSON string: {}", e, ); return Err(e.into()) } }; // Cast to JsonRequest let req = match JsonRequest::try_from(&val) { Ok(v) => v, Err(e) => { error!( target: "rpc::server::accept()", "[RPC SERVER] Failed casting JSON to a JsonRequest: {}", e, ); return Err(e.into()) } }; debug!(target: "rpc::server", "{} --> {}", addr, val.stringify()?); let rep = rh.handle_request(req).await; match rep { JsonResult::Subscriber(subscriber) => { let task = StoppableTask::new(); // Clone what needs to go in the background let task_ = task.clone(); let addr_ = addr.clone(); let tasks_ = tasks.clone(); let writer_ = writer.clone(); // Detach the subscriber so we can multiplex further requests task.clone().start( async move { // Subscribe to the inner method subscriber let subscription = subscriber.publisher.subscribe().await; loop { // Listen for notifications let notification = subscription.receive().await; // Push notification debug!(target: "rpc::server", "{} <-- {}", addr_, notification.stringify().unwrap()); let notification = JsonResult::Notification(notification); let mut writer_lock = writer_.lock().await; if let Err(e) = write_to_stream(&mut writer_lock, ¬ification).await { subscription.unsubscribe().await; return Err(e.into()) } drop(writer_lock); } }, move |_| async move { debug!( target: "rpc::server", "Removing background task {} from map", task_.task_id, ); tasks_.lock().await.remove(&task_); }, Error::DetachedTaskStopped, ex.clone(), ); debug!(target: "rpc::server", "Adding background task {} to map", task.task_id); tasks.lock().await.insert(task.clone()); } JsonResult::SubscriberWithReply(subscriber, reply) => { // Write the response debug!(target: "rpc::server", "{} <-- {}", addr, reply.stringify()?); let mut writer_lock = writer.lock().await; write_to_stream(&mut writer_lock, &reply.into()).await?; drop(writer_lock); let task = StoppableTask::new(); // Clone what needs to go in the background let task_ = task.clone(); let addr_ = addr.clone(); let tasks_ = tasks.clone(); let writer_ = writer.clone(); // Detach the subscriber so we can multiplex further requests task.clone().start( async move { // Start the subscriber loop let subscription = subscriber.publisher.subscribe().await; loop { // Listen for notifications let notification = subscription.receive().await; // Push notification debug!(target: "rpc::server", "{} <-- {}", addr_, notification.stringify().unwrap()); let notification = JsonResult::Notification(notification); let mut writer_lock = writer_.lock().await; if let Err(e) = write_to_stream(&mut writer_lock, ¬ification).await { subscription.unsubscribe().await; drop(writer_lock); return Err(e.into()) } drop(writer_lock); } }, move |_| async move { debug!( target: "rpc::server", "Removing background task {} from map", task_.task_id, ); tasks_.lock().await.remove(&task_); }, Error::DetachedTaskStopped, ex.clone(), ); debug!(target: "rpc::server", "Adding background task {} to map", task.task_id); tasks.lock().await.insert(task.clone()); } JsonResult::Request(_) | JsonResult::Notification(_) => { unreachable!("Should never happen") } JsonResult::Response(ref v) => { debug!(target: "rpc::server", "{} <-- {}", addr, v.stringify()?); let mut writer_lock = writer.lock().await; write_to_stream(&mut writer_lock, &rep).await?; drop(writer_lock); } JsonResult::Error(ref v) => { debug!(target: "rpc::server", "{} <-- {}", addr, v.stringify()?); let mut writer_lock = writer.lock().await; write_to_stream(&mut writer_lock, &rep).await?; drop(writer_lock); } } } } /// Wrapper function around [`accept()`] to take the incoming connection and /// pass it forward. async fn run_accept_loop( listener: Box, rh: Arc, conn_limit: Option, ex: Arc>, ) -> Result<()> { loop { match listener.next().await { Ok((stream, url)) => { let rh_ = rh.clone(); info!(target: "rpc::server", "[RPC] Server accepted conn from {}", url); let (reader, writer) = smol::io::split(stream); let reader = Arc::new(Mutex::new(BufReader::new(reader))); let writer = Arc::new(Mutex::new(writer)); let task = StoppableTask::new(); let task_ = task.clone(); let ex_ = ex.clone(); task.clone().start( accept(reader, writer, url.clone(), rh.clone(), conn_limit, ex_), |_| async move { info!(target: "rpc::server", "[RPC] Closed conn from {}", url); rh_.clone().unmark_connection(task_.clone()).await; }, Error::ChannelStopped, ex.clone(), ); rh.clone().mark_connection(task.clone()).await; } // As per accept(2) recommendation: Err(e) if e.raw_os_error().is_some() => match e.raw_os_error().unwrap() { libc::EAGAIN | libc::ECONNABORTED | libc::EPROTO | libc::EINTR => continue, _ => { error!( target: "rpc::server::run_accept_loop()", "[RPC] Server failed listening: {}", e, ); error!( target: "rpc::server::run_accept_loop()", "[RPC] Closing accept loop" ); return Err(e.into()) } }, // In case a TLS handshake fails, we'll get this: Err(e) if e.kind() == ErrorKind::UnexpectedEof => continue, // Errors we didn't handle above: Err(e) => { error!( target: "rpc::server::run_accept_loop()", "[RPC] Unhandled listener.next() error: {}", e, ); error!( target: "rpc::server::run_accept_loop()", "[RPC] Closing acceptloop" ); return Err(e.into()) } } } } /// Start a JSON-RPC server bound to the given accept URL and use the /// given [`RequestHandler`] to handle incoming requests. pub async fn listen_and_serve( accept_url: Url, rh: Arc, conn_limit: Option, ex: Arc>, ) -> Result<()> { let listener = Listener::new(accept_url).await?.listen().await?; run_accept_loop(listener, rh, conn_limit, ex.clone()).await } #[cfg(test)] mod tests { use super::*; use crate::{rpc::client::RpcClient, system::msleep}; use smol::{net::TcpListener, Executor}; struct RpcServer { rpc_connections: Mutex>, } #[async_trait] impl RequestHandler for RpcServer { async fn handle_request(&self, req: JsonRequest) -> JsonResult { match req.method.as_str() { "ping" => return self.pong(req.id, req.params).await, _ => panic!(), } } async fn connections_mut(&self) -> MutexGuard<'_, HashSet> { self.rpc_connections.lock().await } } #[test] fn conn_manager() -> Result<()> { let executor = Arc::new(Executor::new()); // This simulates a server and a client. Through the function, there // are some calls to sleep(), which are used for the tests, because // otherwise they execute too fast. In practice, The RPC server is // a long-running task so when polled, it should handle things in a // correct manner. smol::block_on(executor.run(async { // Find an available port let listener = TcpListener::bind("127.0.0.1:0").await?; let sockaddr = listener.local_addr()?; let endpoint = Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?; drop(listener); let rpc_server = Arc::new(RpcServer { rpc_connections: Mutex::new(HashSet::new()) }); let rpc_server_ = rpc_server.clone(); let server_task = StoppableTask::new(); server_task.clone().start( listen_and_serve(endpoint.clone(), rpc_server.clone(), None, executor.clone()), |res| async move { match res { Ok(()) | Err(Error::RpcServerStopped) => { rpc_server_.stop_connections().await } Err(e) => panic!("{}", e), } }, Error::RpcServerStopped, executor.clone(), ); // Let the server spawn msleep(500).await; // Connect a client let rpc_client0 = RpcClient::new(endpoint.clone(), executor.clone()).await?; msleep(500).await; assert!(rpc_server.active_connections().await == 1); // Connect another client let rpc_client1 = RpcClient::new(endpoint.clone(), executor.clone()).await?; msleep(500).await; assert!(rpc_server.active_connections().await == 2); // And another one let _rpc_client2 = RpcClient::new(endpoint.clone(), executor.clone()).await?; msleep(500).await; assert!(rpc_server.active_connections().await == 3); // Close the first client rpc_client0.stop().await; msleep(500).await; assert!(rpc_server.active_connections().await == 2); // Close the second client rpc_client1.stop().await; msleep(500).await; assert!(rpc_server.active_connections().await == 1); // The Listener should be stopped when we stop the server task. server_task.stop().await; assert!(RpcClient::new(endpoint, executor.clone()).await.is_err()); // After the server is stopped, the connections tasks should also be stopped assert!(rpc_server.active_connections().await == 0); Ok(()) })) } }