/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 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 . */ //! JSON-RPC client-side implementation. use std::time::Duration; use async_std::io::timeout; use futures::{select, AsyncReadExt, AsyncWriteExt, FutureExt}; use log::{debug, error}; use serde_json::{json, Value}; use url::Url; use super::jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult}; use crate::{ net::transport::{ TcpTransport, TorTransport, Transport, TransportName, TransportStream, UnixTransport, }, system::SubscriberPtr, Error, Result, }; /// JSON-RPC client implementation using asynchronous channels. pub struct RpcClient { send: smol::channel::Sender<(Value, bool)>, recv: smol::channel::Receiver, stop_signal: smol::channel::Sender<()>, url: Url, } impl RpcClient { /// Instantiate a new JSON-RPC client that will connect to the given URL. pub async fn new(url: Url) -> Result { let (send, recv, stop_signal) = Self::open_channels(&url).await?; Ok(Self { send, recv, stop_signal, url }) } /// Close the channels of an instantiated [`RpcClient`]. pub async fn close(&self) -> Result<()> { self.stop_signal.send(()).await?; Ok(()) } /// Listen instantiated client for notifications. /// NOTE: Subscriber listeners must perform response handling. pub async fn subscribe( &self, req: JsonRequest, subscriber: SubscriberPtr, ) -> Result<()> { // Perform initial request. debug!(target: "rpc::client", "--> {}", serde_json::to_string(&req)?); // If the connection is closed, the sender will get an error for sending to a closed channel. if let Err(e) = self.send.send((json!(req), false)).await { error!(target: "rpc::client", "JSON-RPC client unable to send to {} (channels closed): {}", self.url, e); return Err(Error::NetworkOperationFailed) } loop { // If the connection is closed, the receiver will get an error for waiting on a closed channel. let notification = self.recv.recv().await; if notification.is_err() { error!(target: "rpc::client", "JSON-RPC client unable to recv from {} (channels closed)", self.url); break } // Notify subscribed channels let notification = notification?; debug!(target: "rpc::client", "<-- {}", serde_json::to_string(¬ification)?); subscriber.notify(notification.clone()).await; // Stop listenning on error match notification { JsonResult::Notification(_) => {} _ => break, } // Triggering next consume if let Err(e) = self.send.send((json!(req), false)).await { error!(target: "rpc::client", "JSON-RPC client unable to send to {} (channels closed): {}", self.url, e); break } } subscriber.notify(JsonError::new(ErrorCode::InternalError, None, req.id).into()).await; Err(Error::NetworkOperationFailed) } /// Send a given JSON-RPC request over the instantiated client. pub async fn request(&self, value: JsonRequest) -> Result { let req_id = value.id.clone().as_u64().unwrap(); debug!(target: "rpc::client", "--> {}", serde_json::to_string(&value)?); // If the connection is closed, the sender will get an error for // sending to a closed channel. if let Err(e) = self.send.send((json!(value), true)).await { error!(target: "rpc::client", "JSON-RPC client unable to send to {} (channels closed): {}", self.url, e); return Err(Error::NetworkOperationFailed) } // If the connection is closed, the receiver will get an error for // waiting on a closed channel. let reply = self.recv.recv().await; if reply.is_err() { error!(target: "rpc::client", "JSON-RPC client unable to recv from {} (channels closed)", self.url); return Err(Error::NetworkOperationFailed) } match reply? { JsonResult::Response(r) => { // Check if the IDs match let resp_id = r.id.as_u64(); if resp_id.is_none() { let e = JsonError::new(ErrorCode::InvalidId, None, r.id); return Err(Error::JsonRpcError(e.error.message.to_string())) } if resp_id.unwrap() != req_id { let e = JsonError::new(ErrorCode::InvalidId, None, r.id); return Err(Error::JsonRpcError(e.error.message.to_string())) } debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&r)?); Ok(r.result) } JsonResult::Error(e) => { debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&e)?); Err(Error::JsonRpcError(e.error.message.to_string())) } JsonResult::Notification(n) => { debug!(target: "rpc::client", "<-- {}", serde_json::to_string(&n)?); Err(Error::JsonRpcError("Unexpected reply".to_string())) } JsonResult::Subscriber(_) => Err(Error::JsonRpcError("Unexpected reply".to_string())), } } /// Oneshot send a given JSON-RPC request over the instantiated client /// and close the channels on reply. pub async fn oneshot_request(&self, value: JsonRequest) -> Result { let rep = self.request(value).await?; self.stop_signal.send(()).await?; Ok(rep) } /// Instantiate channels for a new [`RpcClient`]. async fn open_channels( uri: &Url, ) -> Result<( smol::channel::Sender<(Value, bool)>, smol::channel::Receiver, smol::channel::Sender<()>, )> { let (data_send, data_recv) = smol::channel::unbounded(); let (result_send, result_recv) = smol::channel::unbounded(); let (stop_send, stop_recv) = smol::channel::unbounded(); let transport_name = TransportName::try_from(uri.clone())?; macro_rules! reqrep { ($stream:expr, $transport:expr, $upgrade:expr) => {{ if let Err(err) = $stream { error!(target: "rpc::client", "JSON-RPC client setup for {} failed: {}", uri, err); return Err(Error::ConnectFailed) } let stream = $stream?.await; if let Err(err) = stream { error!(target: "rpc::client", "JSON-RPC client connection to {} failed: {}", uri, err); return Err(Error::ConnectFailed) } let stream = stream?; match $upgrade { None => { smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv)) .detach(); } Some(u) if u == "tls" => { let stream = $transport.upgrade_dialer(stream)?.await?; smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv)) .detach(); } Some(u) => return Err(Error::UnsupportedTransportUpgrade(u)), } }}; } match transport_name { TransportName::Tcp(upgrade) => { let transport = TcpTransport::new(None, 1024); let stream = transport.dial(uri.clone(), None); reqrep!(stream, transport, upgrade); } TransportName::Tor(upgrade) => { let socks5_url = TorTransport::get_dialer_env()?; let transport = TorTransport::new(socks5_url, None)?; let stream = transport.clone().dial(uri.clone(), None); reqrep!(stream, transport, upgrade); } TransportName::Unix => { let transport = UnixTransport::new(); let stream = transport.dial(uri.clone(), None); reqrep!(stream, transport, None); } _ => unimplemented!(), } Ok((data_send, result_recv, stop_send)) } /// Internal function that loops on a given stream and multiplexes the data. async fn reqrep_loop( mut stream: T, result_send: smol::channel::Sender, data_recv: smol::channel::Receiver<(Value, bool)>, stop_recv: smol::channel::Receiver<()>, ) -> Result<()> { // If timeout is enabled and we don't get a reply within 30 seconds, we'll fail. let read_timeout = Duration::from_secs(30); loop { // FIXME: Nasty size. 8M let mut buf = vec![0; 1024 * 8192]; select! { tuple = data_recv.recv().fuse() => { let (data, with_timeout) = tuple?; let data_bytes = serde_json::to_vec(&data)?; stream.write_all(&data_bytes).await?; // Since we are using async read and write, // the other side might not have finished writing // to the stream. To mitigate this, we perform a read // and check if data can be converted to a JsonResult. // If data is incomplete, this will fail, therefore, // we re-execute read and write after previous read in the buffer, // and repeat until the data in buffer can be converted. let mut n = 0; loop { n += if with_timeout { timeout(read_timeout, async { stream.read(&mut buf[n..]).await }).await? } else { stream.read(&mut buf[n..]).await? }; match serde_json::from_slice(&buf[0..n]) { Ok(reply) => { result_send.send(reply).await?; break }, Err(e) => debug!(target: "rpc::client", "JSON-RPC client retrying failed convertion with error: {}", e), } } } _ = stop_recv.recv().fuse() => break } } Ok(()) } }