rpc.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use async_trait::async_trait;
  19. use log::error;
  20. use smol::lock::{Mutex, MutexGuard};
  21. use std::{
  22. collections::{HashMap, HashSet},
  23. path::PathBuf,
  24. sync::Arc,
  25. };
  26. use tinyjson::JsonValue;
  27. use darkfi::{
  28. dht::DhtNode,
  29. geode::hash_to_string,
  30. net::P2pPtr,
  31. rpc::{
  32. jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult, JsonSubscriber},
  33. p2p_method::HandlerP2p,
  34. server::RequestHandler,
  35. },
  36. system::StoppableTaskPtr,
  37. util::path::expand_path,
  38. Result,
  39. };
  40. use crate::{util::FileSelection, Fud};
  41. pub struct JsonRpcInterface {
  42. fud: Arc<Fud>,
  43. rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
  44. dnet_sub: JsonSubscriber,
  45. event_sub: JsonSubscriber,
  46. }
  47. #[async_trait]
  48. impl RequestHandler<()> for JsonRpcInterface {
  49. async fn handle_request(&self, req: JsonRequest) -> JsonResult {
  50. return match req.method.as_str() {
  51. "ping" => self.pong(req.id, req.params).await,
  52. "put" => self.put(req.id, req.params).await,
  53. "get" => self.get(req.id, req.params).await,
  54. "subscribe" => self.subscribe(req.id, req.params).await,
  55. "remove" => self.remove(req.id, req.params).await,
  56. "list_resources" => self.list_resources(req.id, req.params).await,
  57. "list_buckets" => self.list_buckets(req.id, req.params).await,
  58. "list_seeders" => self.list_seeders(req.id, req.params).await,
  59. "verify" => self.verify(req.id, req.params).await,
  60. "dnet.switch" => self.dnet_switch(req.id, req.params).await,
  61. "dnet.subscribe_events" => self.dnet_subscribe_events(req.id, req.params).await,
  62. "p2p.get_info" => self.p2p_get_info(req.id, req.params).await,
  63. _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
  64. }
  65. }
  66. async fn connections_mut(&self) -> MutexGuard<'_, HashSet<StoppableTaskPtr>> {
  67. self.rpc_connections.lock().await
  68. }
  69. }
  70. impl HandlerP2p for JsonRpcInterface {
  71. fn p2p(&self) -> P2pPtr {
  72. self.fud.p2p.clone()
  73. }
  74. }
  75. /// Fud RPC methods
  76. impl JsonRpcInterface {
  77. pub fn new(fud: Arc<Fud>, dnet_sub: JsonSubscriber, event_sub: JsonSubscriber) -> Self {
  78. Self { fud, rpc_connections: Mutex::new(HashSet::new()), dnet_sub, event_sub }
  79. }
  80. // RPCAPI:
  81. // Put a file onto the network. Takes a local filesystem path as a parameter.
  82. // Returns the file hash that serves as a pointer to the uploaded file.
  83. //
  84. // --> {"jsonrpc": "2.0", "method": "put", "params": ["/foo.txt"], "id": 42}
  85. // <-- {"jsonrpc": "2.0", "result: "df4...3db7", "id": 42}
  86. async fn put(&self, id: u16, params: JsonValue) -> JsonResult {
  87. let params = params.get::<Vec<JsonValue>>().unwrap();
  88. if params.len() != 1 || !params[0].is_string() {
  89. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  90. }
  91. let path = params[0].get::<String>().unwrap();
  92. let path = match expand_path(path.as_str()) {
  93. Ok(v) => v,
  94. Err(_) => return JsonError::new(ErrorCode::InvalidParams, None, id).into(),
  95. };
  96. // A valid path was passed. Let's see if we can read it, and if so,
  97. // add it to Geode.
  98. let res = self.fud.put(&path).await;
  99. if let Err(e) = res {
  100. return JsonError::new(ErrorCode::InternalError, Some(format!("{e}")), id).into()
  101. }
  102. JsonResponse::new(JsonValue::String(hash_to_string(&res.unwrap())), id).into()
  103. }
  104. // RPCAPI:
  105. // Fetch a resource from the network. Takes a hash, path (absolute or relative), and an
  106. // optional list of file paths (only used for directories) as parameters.
  107. // Returns the path where the resource will be located once downloaded.
  108. //
  109. // --> {"jsonrpc": "2.0", "method": "get", "params": ["1211...abfd", "~/myfile.jpg", null], "id": 42}
  110. // <-- {"jsonrpc": "2.0", "result": "/home/user/myfile.jpg", "id": 42}
  111. async fn get(&self, id: u16, params: JsonValue) -> JsonResult {
  112. let params = params.get::<Vec<JsonValue>>().unwrap();
  113. if params.len() != 3 || !params[0].is_string() || !params[1].is_string() {
  114. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  115. }
  116. let mut hash_buf = vec![];
  117. match bs58::decode(params[0].get::<String>().unwrap().as_str()).onto(&mut hash_buf) {
  118. Ok(_) => {}
  119. Err(_) => return JsonError::new(ErrorCode::InvalidParams, None, id).into(),
  120. }
  121. if hash_buf.len() != 32 {
  122. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  123. }
  124. let mut hash_buf_arr = [0u8; 32];
  125. hash_buf_arr.copy_from_slice(&hash_buf);
  126. let hash = blake3::Hash::from_bytes(hash_buf_arr);
  127. let hash_str = hash_to_string(&hash);
  128. let path = match params[1].get::<String>() {
  129. Some(path) => match path.is_empty() {
  130. true => match self.fud.hash_to_path(&hash).ok().flatten() {
  131. Some(path) => path,
  132. None => self.fud.downloads_path.join(&hash_str),
  133. },
  134. false => match PathBuf::from(path).is_absolute() {
  135. true => PathBuf::from(path),
  136. false => self.fud.downloads_path.join(path),
  137. },
  138. },
  139. None => self.fud.downloads_path.join(&hash_str),
  140. };
  141. let files: FileSelection = match &params[2] {
  142. JsonValue::Array(files) => files
  143. .iter()
  144. .filter_map(|v| {
  145. if let JsonValue::String(file) = v {
  146. Some(PathBuf::from(file.clone()))
  147. } else {
  148. None
  149. }
  150. })
  151. .collect(),
  152. JsonValue::Null => FileSelection::All,
  153. _ => return JsonError::new(ErrorCode::InvalidParams, None, id).into(),
  154. };
  155. // Start downloading the resource
  156. if let Err(e) = self.fud.get(&hash, &path, files).await {
  157. return JsonError::new(ErrorCode::InternalError, Some(e.to_string()), id).into()
  158. }
  159. JsonResponse::new(JsonValue::String(path.to_string_lossy().to_string()), id).into()
  160. }
  161. // RPCAPI:
  162. // Subscribe to download events.
  163. //
  164. // --> {"jsonrpc": "2.0", "method": "get", "params": [], "id": 42}
  165. // <-- {"jsonrpc": "2.0", "result": `event`, "id": 42}
  166. async fn subscribe(&self, _id: u16, _params: JsonValue) -> JsonResult {
  167. self.event_sub.clone().into()
  168. }
  169. // RPCAPI:
  170. // Activate or deactivate dnet in the P2P stack.
  171. // By sending `true`, dnet will be activated, and by sending `false` dnet
  172. // will be deactivated. Returns `true` on success.
  173. //
  174. // --> {"jsonrpc": "2.0", "method": "dnet_switch", "params": [true], "id": 42}
  175. // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
  176. async fn dnet_switch(&self, id: u16, params: JsonValue) -> JsonResult {
  177. let params = params.get::<Vec<JsonValue>>().unwrap();
  178. if params.len() != 1 || !params[0].is_bool() {
  179. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  180. }
  181. let switch = params[0].get::<bool>().unwrap();
  182. if *switch {
  183. self.fud.p2p.dnet_enable();
  184. } else {
  185. self.fud.p2p.dnet_disable();
  186. }
  187. JsonResponse::new(JsonValue::Boolean(true), id).into()
  188. }
  189. // RPCAPI:
  190. // Initializes a subscription to p2p dnet events.
  191. // Once a subscription is established, `fud` will send JSON-RPC notifications of
  192. // new network events to the subscriber.
  193. //
  194. // --> {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [], "id": 1}
  195. // <-- {"jsonrpc": "2.0", "method": "dnet.subscribe_events", "params": [`event`]}
  196. pub async fn dnet_subscribe_events(&self, id: u16, params: JsonValue) -> JsonResult {
  197. let params = params.get::<Vec<JsonValue>>().unwrap();
  198. if !params.is_empty() {
  199. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  200. }
  201. self.dnet_sub.clone().into()
  202. }
  203. // RPCAPI:
  204. // Returns resources.
  205. //
  206. // --> {"jsonrpc": "2.0", "method": "list_buckets", "params": [], "id": 1}
  207. // <-- {"jsonrpc": "2.0", "result": [[["abcdef", ["tcp://127.0.0.1:13337"]]]], "id": 1}
  208. pub async fn list_resources(&self, id: u16, params: JsonValue) -> JsonResult {
  209. let params = params.get::<Vec<JsonValue>>().unwrap();
  210. if !params.is_empty() {
  211. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  212. }
  213. let resources_read = self.fud.resources.read().await;
  214. let mut resources: Vec<JsonValue> = vec![];
  215. for (_, resource) in resources_read.iter() {
  216. resources.push(resource.clone().into());
  217. }
  218. JsonResponse::new(JsonValue::Array(resources), id).into()
  219. }
  220. // RPCAPI:
  221. // Returns the current buckets.
  222. //
  223. // --> {"jsonrpc": "2.0", "method": "list_buckets", "params": [], "id": 1}
  224. // <-- {"jsonrpc": "2.0", "result": [[["abcdef", ["tcp://127.0.0.1:13337"]]]], "id": 1}
  225. pub async fn list_buckets(&self, id: u16, params: JsonValue) -> JsonResult {
  226. let params = params.get::<Vec<JsonValue>>().unwrap();
  227. if !params.is_empty() {
  228. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  229. }
  230. let mut buckets = vec![];
  231. for bucket in self.fud.dht.buckets.read().await.iter() {
  232. let mut nodes = vec![];
  233. for node in bucket.nodes.clone() {
  234. let mut addresses = vec![];
  235. for addr in &node.addresses {
  236. addresses.push(JsonValue::String(addr.to_string()));
  237. }
  238. nodes.push(JsonValue::Array(vec![
  239. JsonValue::String(hash_to_string(&node.id())),
  240. JsonValue::Array(addresses),
  241. ]));
  242. }
  243. buckets.push(JsonValue::Array(nodes));
  244. }
  245. JsonResponse::new(JsonValue::Array(buckets), id).into()
  246. }
  247. // RPCAPI:
  248. // Returns the content of the seeders router.
  249. //
  250. // --> {"jsonrpc": "2.0", "method": "list_seeders", "params": [], "id": 1}
  251. // <-- {"jsonrpc": "2.0", "result": {"seeders": {"abcdef": ["ghijkl"]}}, "id": 1}
  252. pub async fn list_seeders(&self, id: u16, params: JsonValue) -> JsonResult {
  253. let params = params.get::<Vec<JsonValue>>().unwrap();
  254. if !params.is_empty() {
  255. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  256. }
  257. let mut seeders_router: HashMap<String, JsonValue> = HashMap::new();
  258. for (hash, items) in self.fud.seeders_router.read().await.iter() {
  259. let mut node_ids = vec![];
  260. for item in items {
  261. node_ids.push(JsonValue::String(hash_to_string(&item.node.id())));
  262. }
  263. seeders_router.insert(hash_to_string(hash), JsonValue::Array(node_ids));
  264. }
  265. let mut res: HashMap<String, JsonValue> = HashMap::new();
  266. res.insert("seeders".to_string(), JsonValue::Object(seeders_router));
  267. JsonResponse::new(JsonValue::Object(res), id).into()
  268. }
  269. // RPCAPI:
  270. // Removes a resource.
  271. //
  272. // --> {"jsonrpc": "2.0", "method": "remove", "params": ["1211...abfd"], "id": 1}
  273. // <-- {"jsonrpc": "2.0", "result": [], "id": 1}
  274. pub async fn remove(&self, id: u16, params: JsonValue) -> JsonResult {
  275. let params = params.get::<Vec<JsonValue>>().unwrap();
  276. if params.len() != 1 || !params[0].is_string() {
  277. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  278. }
  279. let mut hash_buf = [0u8; 32];
  280. match bs58::decode(params[0].get::<String>().unwrap().as_str()).onto(&mut hash_buf) {
  281. Ok(_) => {}
  282. Err(_) => return JsonError::new(ErrorCode::InvalidParams, None, id).into(),
  283. }
  284. self.fud.remove(&blake3::Hash::from_bytes(hash_buf)).await;
  285. JsonResponse::new(JsonValue::Array(vec![]), id).into()
  286. }
  287. // RPCAPI:
  288. // Verifies local files. Takes a list of file hashes as parameters.
  289. // An empty list means all known files.
  290. // Returns the path where the file will be located once downloaded.
  291. //
  292. // --> {"jsonrpc": "2.0", "method": "verify", "params": ["1211...abfd"], "id": 42}
  293. // <-- {"jsonrpc": "2.0", "result": [], "id": 1}
  294. async fn verify(&self, id: u16, params: JsonValue) -> JsonResult {
  295. let params = params.get::<Vec<JsonValue>>().unwrap();
  296. if !params.iter().all(|param| param.is_string()) {
  297. return JsonError::new(ErrorCode::InvalidParams, None, id).into()
  298. }
  299. let hashes = if params.is_empty() {
  300. None
  301. } else {
  302. let hashes_str: Vec<String> =
  303. params.iter().map(|param| param.get::<String>().unwrap().clone()).collect();
  304. let hashes: Result<Vec<blake3::Hash>> = hashes_str
  305. .into_iter()
  306. .map(|hash_str| {
  307. let mut buf = [0u8; 32];
  308. bs58::decode(hash_str).onto(&mut buf)?;
  309. Ok(blake3::Hash::from_bytes(buf))
  310. })
  311. .collect();
  312. if hashes.is_err() {
  313. return JsonError::new(ErrorCode::InvalidParams, None, id).into();
  314. }
  315. Some(hashes.unwrap())
  316. };
  317. if let Err(e) = self.fud.verify_resources(hashes).await {
  318. error!(target: "fud::verify()", "Could not verify resources: {e}");
  319. return JsonError::new(ErrorCode::InternalError, None, id).into();
  320. }
  321. JsonResponse::new(JsonValue::Array(vec![]), id).into()
  322. }
  323. }