diff --git a/.vscode/launch.json b/.vscode/launch.json index c3af759b..b0f99ce6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,13 @@ } ], "configurations": [ + { + "name": "Python: Attach using Process Id", + "type": "python", + "request": "attach", + "processId": "${command:pickProcess}", + "justMyCode": true + }, { "type": "lldb", "request": "attach", diff --git a/Cargo.lock b/Cargo.lock index 047c015a..bf108ff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -845,27 +845,6 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e2d432d1601d61d1e11140d04e9d239b5cf7316fa1106523c3d86eea19c29d" -[[package]] -name = "capnp-futures" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d520e0af228b92de357f230f4987ee4f9786f2b8aa24b9cfe53f5b11c17198" -dependencies = [ - "capnp", - "futures", -] - -[[package]] -name = "capnp-rpc" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab8e869783e491cbcc350427a5e775aa4d8a1deaa5198d74332957cfa430779" -dependencies = [ - "capnp", - "capnp-futures", - "futures", -] - [[package]] name = "capnpc" version = "0.17.1" @@ -1678,6 +1657,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "ed25519" version = "1.5.3" @@ -4689,6 +4674,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4854,6 +4863,17 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_json" version = "1.0.96" @@ -5886,6 +5906,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "triomphe" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "trust-dns-proto" version = "0.22.0" @@ -6120,9 +6150,6 @@ dependencies = [ "async-std", "async-tungstenite 0.8.0", "bugsalot", - "capnp", - "capnp-rpc", - "capnpc", "cfg-if 1.0.0", "clap 3.2.25", "config", @@ -6131,8 +6158,10 @@ dependencies = [ "cursive-flexi-logger-view", "cursive_buffered_backend", "cursive_table_view", + "data-encoding", "directories", "flexi_logger", + "flume", "futures", "hex", "json", @@ -6141,10 +6170,11 @@ dependencies = [ "serde", "serde_derive", "serial_test", + "stop-token", "thiserror", "tokio 1.28.2", "tokio-util", - "veilid-core", + "veilid-tools", ] [[package]] @@ -6206,12 +6236,14 @@ dependencies = [ "owo-colors", "paranoid-android", "parking_lot 0.12.1", + "paste", "range-set-blaze", "rkyv", "rtnetlink", "rusqlite", "rustls 0.19.1", "rustls-pemfile 0.2.1", + "schemars", "secrecy", "send_wrapper 0.6.0", "serde", @@ -6287,9 +6319,6 @@ dependencies = [ "async-tungstenite 0.22.2", "backtrace", "bugsalot", - "capnp", - "capnp-rpc", - "capnpc", "cfg-if 1.0.0", "clap 3.2.25", "color-eyre", @@ -6327,6 +6356,7 @@ dependencies = [ "tracing-subscriber", "url", "veilid-core", + "wg", "windows-service", ] @@ -6637,6 +6667,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "wg" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f390449c16e0679435fc97a6b49d24e67f09dd05fea1de54db1b60902896d273" +dependencies = [ + "atomic-waker", + "parking_lot 0.12.1", + "triomphe", +] + [[package]] name = "which" version = "4.4.0" diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index f98dd173..1701aa1e 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -3,7 +3,6 @@ name = "veilid-cli" version = "0.1.0" authors = ["John Smith "] edition = "2021" -build = "build.rs" license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)" [[bin]] @@ -13,8 +12,8 @@ path = "src/main.rs" [features] default = [ "rt-tokio" ] macos = [ "cursive/ncurses-backend" ] -rt-async-std = [ "async-std", "veilid-core/rt-async-std", "cursive/rt-async-std" ] -rt-tokio = [ "tokio", "tokio-util", "veilid-core/rt-tokio", "cursive/rt-tokio" ] +rt-async-std = [ "async-std", "veilid-tools/rt-async-std", "cursive/rt-async-std" ] +rt-tokio = [ "tokio", "tokio-util", "veilid-tools/rt-tokio", "cursive/rt-tokio" ] [dependencies] async-std = { version = "^1.9", features = ["unstable", "attributes"], optional = true } @@ -36,19 +35,17 @@ serde = "^1" serde_derive = "^1" parking_lot = "^0" cfg-if = "^1" -capnp = "^0" -capnp-rpc = "^0" config = { version = "^0", features = ["yaml"] } bugsalot = { git = "https://github.com/crioux/bugsalot.git" } flexi_logger = { version = "^0", features = ["use_chrono_for_offset"] } thiserror = "^1" crossbeam-channel = "^0" hex = "^0" -veilid-core = { path = "../veilid-core" } +veilid-tools = { path = "../veilid-tools" } json = "^0" +stop-token = { version = "^0", default-features = false } +flume = { version = "^0", features = ["async"] } +data-encoding = { version = "^2" } [dev-dependencies] serial_test = "^0" - -[build-dependencies] -capnpc = "^0" diff --git a/veilid-cli/build.rs b/veilid-cli/build.rs deleted file mode 100644 index fa57d860..00000000 --- a/veilid-cli/build.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - ::capnpc::CompilerCommand::new() - .file("../veilid-server/proto/veilid-client.capnp") - .src_prefix("../veilid-server/") - .run() - .expect("compiling schema"); -} diff --git a/veilid-cli/src/client_api_connection.rs b/veilid-cli/src/client_api_connection.rs index b2681257..e790dcd9 100644 --- a/veilid-cli/src/client_api_connection.rs +++ b/veilid-cli/src/client_api_connection.rs @@ -1,511 +1,392 @@ use crate::command_processor::*; use crate::tools::*; -use crate::veilid_client_capnp::*; -use capnp::capability::Promise; -use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, Disconnector, RpcSystem}; -use futures::future::FutureExt; -use serde::de::DeserializeOwned; -use std::cell::RefCell; +use futures::stream::FuturesUnordered; +use futures::StreamExt; use std::net::SocketAddr; -use std::rc::Rc; -use veilid_core::tools::*; -use veilid_core::*; +use std::time::SystemTime; +use stop_token::{future::FutureExt as _, StopSource}; -macro_rules! capnp_failed { - ($ex:expr) => {{ - let msg = format!("Capnp Error: {}", $ex); - error!("{}", msg); - Promise::err(capnp::Error::failed(msg)) - }}; -} - -macro_rules! pry_result { - ($ex:expr) => { - match $ex { - Ok(v) => v, - Err(e) => { - return capnp_failed!(e); - } - } - }; -} - -fn map_to_internal_error(e: T) -> VeilidAPIError { - VeilidAPIError::Internal { - message: e.to_string(), - } -} - -fn decode_api_result( - reader: &api_result::Reader, -) -> VeilidAPIResult { - match reader.which().map_err(map_to_internal_error)? { - api_result::Which::Ok(v) => { - let ok_val = v.map_err(map_to_internal_error)?; - let res: T = veilid_core::deserialize_json(ok_val).map_err(map_to_internal_error)?; - Ok(res) - } - api_result::Which::Err(e) => { - let err_val = e.map_err(map_to_internal_error)?; - let res: VeilidAPIError = - veilid_core::deserialize_json(err_val).map_err(map_to_internal_error)?; - Err(res) - } - } -} - -struct VeilidClientImpl { - comproc: CommandProcessor, -} - -impl VeilidClientImpl { - pub fn new(comproc: CommandProcessor) -> Self { - Self { comproc } - } -} - -impl veilid_client::Server for VeilidClientImpl { - fn update( - &mut self, - params: veilid_client::UpdateParams, - _results: veilid_client::UpdateResults, - ) -> Promise<(), ::capnp::Error> { - let veilid_update = pry!(pry!(params.get()).get_veilid_update()); - let veilid_update: VeilidUpdate = pry_result!(deserialize_json(veilid_update)); - - match veilid_update { - VeilidUpdate::Log(log) => { - self.comproc.update_log(log); - } - VeilidUpdate::AppMessage(msg) => { - self.comproc.update_app_message(msg); - } - VeilidUpdate::AppCall(call) => { - self.comproc.update_app_call(call); - } - VeilidUpdate::Attachment(attachment) => { - self.comproc.update_attachment(attachment); - } - VeilidUpdate::Network(network) => { - self.comproc.update_network_status(network); - } - VeilidUpdate::Config(config) => { - self.comproc.update_config(config); - } - VeilidUpdate::RouteChange(route) => { - self.comproc.update_route(route); - } - VeilidUpdate::Shutdown => self.comproc.update_shutdown(), - VeilidUpdate::ValueChange(value_change) => { - self.comproc.update_value_change(value_change); - } - } - - Promise::ok(()) +cfg_if! { + if #[cfg(feature="rt-async-std")] { + use async_std::io::prelude::BufReadExt; + use async_std::io::WriteExt; + use async_std::io::BufReader; + } else if #[cfg(feature="rt-tokio")] { + use tokio::io::AsyncBufReadExt; + use tokio::io::AsyncWriteExt; + use tokio::io::BufReader; } } struct ClientApiConnectionInner { comproc: CommandProcessor, connect_addr: Option, - disconnector: Option>, - server: Option>>, - server_settings: Option, + request_sender: Option>, + disconnector: Option, disconnect_requested: bool, - cancel_eventual: Eventual, + reply_channels: HashMap>, + next_req_id: u32, } -type Handle = Rc>; - #[derive(Clone)] pub struct ClientApiConnection { - inner: Handle, + inner: Arc>, } impl ClientApiConnection { pub fn new(comproc: CommandProcessor) -> Self { Self { - inner: Rc::new(RefCell::new(ClientApiConnectionInner { + inner: Arc::new(Mutex::new(ClientApiConnectionInner { comproc, connect_addr: None, + request_sender: None, disconnector: None, - server: None, - server_settings: None, disconnect_requested: false, - cancel_eventual: Eventual::new(), + reply_channels: HashMap::new(), + next_req_id: 0, })), } } - pub fn cancel(&self) { - let eventual = { - let inner = self.inner.borrow(); - inner.cancel_eventual.clone() + pub fn cancel_all(&self) { + let mut inner = self.inner.lock(); + inner.reply_channels.clear(); + } + + async fn process_veilid_state<'a>(&self, state: &json::JsonValue) { + let comproc = self.inner.lock().comproc.clone(); + comproc.update_attachment(&state["attachment"]); + comproc.update_network_status(&state["network"]); + comproc.update_config(&state["config"]); + } + + async fn process_response(&self, response: json::JsonValue) { + // find the operation id and send the response to the channel for it + let Some(id) = response["id"].as_u32() else { + error!("invalid id: {}", response); + return; }; - eventual.resolve(); // don't need to await this - } - - async fn process_veilid_state<'a>( - &'a mut self, - veilid_state: VeilidState, - ) -> Result<(), String> { - let mut inner = self.inner.borrow_mut(); - inner.comproc.update_attachment(veilid_state.attachment); - inner.comproc.update_network_status(veilid_state.network); - inner.comproc.update_config(veilid_state.config); - Ok(()) - } - - async fn spawn_rpc_system( - &mut self, - connect_addr: SocketAddr, - mut rpc_system: RpcSystem, - ) -> Result<(), String> { - let mut request; - { - let mut inner = self.inner.borrow_mut(); - - // Get the bootstrap server connection object - inner.server = Some(Rc::new(RefCell::new( - rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server), - ))); - - // Store our disconnector future for later (must happen after bootstrap, contrary to documentation) - inner.disconnector = Some(rpc_system.get_disconnector()); - - // Get a client object to pass to the server for status update callbacks - let client = capnp_rpc::new_client(VeilidClientImpl::new(inner.comproc.clone())); - - // Register our client and get a registration object back - request = inner - .server - .as_ref() - .unwrap() - .borrow_mut() - .register_request(); - request.get().set_veilid_client(client); - - inner - .comproc - .set_connection_state(ConnectionState::Connected( - connect_addr, - std::time::SystemTime::now(), - )); + let reply_channel = { + let mut inner = self.inner.lock(); + inner.reply_channels.remove(&id) + }; + let Some(reply_channel) = reply_channel else { + warn!("received cancelled reply: {}", response); + return; + }; + if let Err(e) = reply_channel.send_async(response).await { + error!("failed to process reply: {}", e); + return; } - - let rpc_jh = spawn_local(rpc_system); - - let reg_res: Result = (async { - // Send the request and get the state object and the registration object - let response = request - .send() - .promise - .await - .map_err(|e| format!("failed to send register request: {}", e))?; - let response = response - .get() - .map_err(|e| format!("failed to get register response: {}", e))?; - - // Get the registration object, which drops our connection when it is dropped - let registration = response - .get_registration() - .map_err(|e| format!("failed to get registration object: {}", e))?; - - // Get the initial veilid state - let veilid_state = response - .get_state() - .map_err(|e| format!("failed to get initial veilid state: {}", e))?; - - // Set up our state for the first time - let veilid_state: VeilidState = deserialize_json(veilid_state) - .map_err(|e| format!("failed to get deserialize veilid state: {}", e))?; - self.process_veilid_state(veilid_state).await?; - - // Save server settings - let server_settings = response - .get_settings() - .map_err(|e| format!("failed to get initial veilid server settings: {}", e))? - .to_owned(); - self.inner.borrow_mut().server_settings = Some(server_settings.clone()); - - // Don't drop the registration, doing so will remove the client - // object mapping from the server which we need for the update backchannel - Ok(registration) - }) - .await; - - let _registration = match reg_res { - Ok(v) => v, - Err(e) => { - rpc_jh.abort().await; - return Err(e); - } - }; - - // Wait until rpc system completion or disconnect was requested - let res = rpc_jh.await; - res.map_err(|e| format!("client RPC system error: {}", e)) } - async fn handle_connection(&mut self, connect_addr: SocketAddr) -> Result<(), String> { + async fn process_veilid_update(&self, update: json::JsonValue) { + let comproc = self.inner.lock().comproc.clone(); + let Some(kind) = update["kind"].as_str() else { + comproc.log_message(format!("missing update kind: {}", update)); + return; + }; + match kind { + "Log" => { + comproc.update_log(&update); + } + "AppMessage" => { + comproc.update_app_message(&update); + } + "AppCall" => { + comproc.update_app_call(&update); + } + "Attachment" => { + comproc.update_attachment(&update); + } + "Network" => { + comproc.update_network_status(&update); + } + "Config" => { + comproc.update_config(&update); + } + "RouteChange" => { + comproc.update_route(&update); + } + "Shutdown" => comproc.update_shutdown(), + "ValueChange" => { + comproc.update_value_change(&update); + } + _ => { + comproc.log_message(format!("unknown update kind: {}", update)); + } + } + } + + async fn handle_connection(&self, connect_addr: SocketAddr) -> Result<(), String> { trace!("ClientApiConnection::handle_connection"); - self.inner.borrow_mut().connect_addr = Some(connect_addr); // Connect the TCP socket let stream = TcpStream::connect(connect_addr) .await .map_err(map_to_string)?; + // If it succeed, disable nagle algorithm stream.set_nodelay(true).map_err(map_to_string)?; - // Create the VAT network + // State we connected + let comproc = self.inner.lock().comproc.clone(); + comproc.set_connection_state(ConnectionState::Connected(connect_addr, SystemTime::now())); + + // Split the stream cfg_if! { if #[cfg(feature="rt-async-std")] { use futures::AsyncReadExt; - let (reader, writer) = stream.split(); + let (reader, mut writer) = stream.split(); + let mut reader = BufReader::new(reader); } else if #[cfg(feature="rt-tokio")] { - pub use tokio_util::compat::*; - let (reader, writer) = stream.into_split(); - let reader = reader.compat(); - let writer = writer.compat_write(); + let (reader, mut writer) = stream.into_split(); + let mut reader = BufReader::new(reader); } } - let rpc_network = Box::new(twoparty::VatNetwork::new( - reader, - writer, - rpc_twoparty_capnp::Side::Client, - Default::default(), - )); + // Requests to send + let (requests_tx, requests_rx) = flume::unbounded(); - // Create the rpc system - let rpc_system = RpcSystem::new(rpc_network, None); + // Create disconnection mechanism + let stop_token = { + let stop_source = StopSource::new(); + let token = stop_source.token(); + let mut inner = self.inner.lock(); + inner.connect_addr = Some(connect_addr); + inner.disconnector = Some(stop_source); + inner.request_sender = Some(requests_tx); + token + }; - // Process the rpc system until we decide we're done - match self.spawn_rpc_system(connect_addr, rpc_system).await { - Ok(()) => {} - Err(e) => { - error!("Failed to spawn client RPC system: {}", e); + // Futures to process unordered + let mut unord = FuturesUnordered::new(); + + // Process lines + let this = self.clone(); + let recv_messages_future = async move { + let mut linebuf = String::new(); + while let Ok(size) = reader.read_line(&mut linebuf).await { + // Exit on EOF + if size == 0 { + // Disconnected + break; + } + + let line = linebuf.trim().to_owned(); + linebuf.clear(); + + // Unmarshal json + let j = match json::parse(&line) { + Ok(v) => v, + Err(e) => { + error!("failed to parse server response: {}", e); + continue; + } + }; + + if j["type"] == "Update" { + this.process_veilid_update(j).await; + } else if j["type"] == "Response" { + this.process_response(j).await; + } } - } + // + let mut inner = this.inner.lock(); + inner.request_sender = None; + }; + unord.push(system_boxed(recv_messages_future)); - // Drop the server and disconnector too (if we still have it) - let mut inner = self.inner.borrow_mut(); + // Requests send processor + let send_requests_future = async move { + while let Ok(req) = requests_rx.recv_async().await { + if let Err(e) = writer.write_all(req.as_bytes()).await { + error!("failed to write request: {}", e) + } + } + }; + unord.push(system_boxed(send_requests_future)); + + // Request initial server state + let capi = self.clone(); + spawn_detached_local(async move { + let mut req = json::JsonValue::new_object(); + req["op"] = "GetState".into(); + let Some(resp) = capi.perform_request(req).await else { + error!("failed to get state"); + return; + }; + if resp.has_key("error") { + error!("failed to get state: {}", resp["error"]); + return; + } + capi.process_veilid_state(&resp["value"]).await; + }); + + // Send and receive until we're done or a stop is requested + while let Ok(Some(())) = unord.next().timeout_at(stop_token.clone()).await {} + + // // Drop the server and disconnector too (if we still have it) + let mut inner = self.inner.lock(); let disconnect_requested = inner.disconnect_requested; - inner.server_settings = None; - inner.server = None; + inner.request_sender = None; inner.disconnector = None; inner.disconnect_requested = false; inner.connect_addr = None; - if !disconnect_requested { - // Connection lost - Err("Connection lost".to_owned()) - } else { - // Connection finished + // Connection finished + if disconnect_requested { Ok(()) + } else { + Err("Connection lost".to_owned()) } } - pub fn cancellable(&mut self, p: Promise) -> Promise - where - T: 'static, - { - let (mut cancel_instance, cancel_eventual) = { - let inner = self.inner.borrow(); - ( - inner.cancel_eventual.instance_empty().fuse(), - inner.cancel_eventual.clone(), - ) - }; - let mut p = p.fuse(); + async fn perform_request(&self, mut req: json::JsonValue) -> Option { + let (sender, reply_rx) = { + let mut inner = self.inner.lock(); - Promise::from_future(async move { - let out = select! { - a = p => { - a - }, - _ = cancel_instance => { - Err(capnp::Error::failed("cancelled".into())) - } + // Get the request sender + let Some(sender) = inner.request_sender.clone() else { + error!("dropping request, not connected"); + return None; }; - drop(cancel_instance); - cancel_eventual.reset(); - out - }) + + // Get next id + let id = inner.next_req_id; + inner.next_req_id += 1; + + // Add the id + req["id"] = id.into(); + + // Make a reply receiver + let (reply_tx, reply_rx) = flume::bounded(1); + inner.reply_channels.insert(id, reply_tx); + (sender, reply_rx) + }; + + // Send the request + let req_ndjson = req.dump() + "\n"; + if let Err(e) = sender.send_async(req_ndjson).await { + error!("failed to send request: {}", e); + return None; + } + + // Wait for the reply + let Ok(r) = reply_rx.recv_async().await else { + // Cancelled + return None; + }; + + Some(r) } - pub async fn server_attach(&mut self) -> Result<(), String> { + pub async fn server_attach(&self) -> Result<(), String> { trace!("ClientApiConnection::server_attach"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring attach request".to_owned())? - .clone() + + let mut req = json::JsonValue::new_object(); + req["op"] = "Attach".into(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let request = server.borrow().attach_request(); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - let reader = response - .get() - .map_err(map_to_string)? - .get_result() - .map_err(map_to_string)?; - let res: VeilidAPIResult<()> = decode_api_result(&reader); - res.map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(()) } - pub async fn server_detach(&mut self) -> Result<(), String> { + pub async fn server_detach(&self) -> Result<(), String> { trace!("ClientApiConnection::server_detach"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring detach request".to_owned())? - .clone() + let mut req = json::JsonValue::new_object(); + req["op"] = "Detach".into(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let request = server.borrow().detach_request(); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - let reader = response - .get() - .map_err(map_to_string)? - .get_result() - .map_err(map_to_string)?; - let res: VeilidAPIResult<()> = decode_api_result(&reader); - res.map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(()) } - pub async fn server_shutdown(&mut self) -> Result<(), String> { + pub async fn server_shutdown(&self) -> Result<(), String> { trace!("ClientApiConnection::server_shutdown"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring attach request".to_owned())? - .clone() + let mut req = json::JsonValue::new_object(); + req["op"] = "Control".into(); + req["args"] = json::JsonValue::new_array(); + req["args"].push("Shutdown").unwrap(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let request = server.borrow().shutdown_request(); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - response.get().map(drop).map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(()) } - pub async fn server_debug(&mut self, what: String) -> Result { + pub async fn server_debug(&self, what: String) -> Result { trace!("ClientApiConnection::server_debug"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring debug request".to_owned())? - .clone() + let mut req = json::JsonValue::new_object(); + req["op"] = "Debug".into(); + req["command"] = what.into(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let mut request = server.borrow().debug_request(); - request.get().set_command(&what); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - let reader = response - .get() - .map_err(map_to_string)? - .get_result() - .map_err(map_to_string)?; - let res: VeilidAPIResult = decode_api_result(&reader); - res.map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(resp["value"].to_string()) } pub async fn server_change_log_level( - &mut self, + &self, layer: String, - log_level: VeilidConfigLogLevel, + log_level: String, ) -> Result<(), String> { trace!("ClientApiConnection::change_log_level"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring change_log_level request".to_owned())? - .clone() + let mut req = json::JsonValue::new_object(); + req["op"] = "Control".into(); + req["args"] = json::JsonValue::new_array(); + req["args"].push("ChangeLogLevel").unwrap(); + req["args"].push(layer).unwrap(); + req["args"].push(log_level).unwrap(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let mut request = server.borrow().change_log_level_request(); - request.get().set_layer(&layer); - let log_level_json = veilid_core::serialize_json(&log_level); - request.get().set_log_level(&log_level_json); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - let reader = response - .get() - .map_err(map_to_string)? - .get_result() - .map_err(map_to_string)?; - let res: VeilidAPIResult<()> = decode_api_result(&reader); - res.map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(()) } - pub async fn server_appcall_reply( - &mut self, - id: OperationId, - msg: Vec, - ) -> Result<(), String> { + pub async fn server_appcall_reply(&self, id: u64, msg: Vec) -> Result<(), String> { trace!("ClientApiConnection::appcall_reply"); - let server = { - let inner = self.inner.borrow(); - inner - .server - .as_ref() - .ok_or_else(|| "Not connected, ignoring change_log_level request".to_owned())? - .clone() + let mut req = json::JsonValue::new_object(); + req["op"] = "AppCallReply".into(); + req["call_id"] = id.to_string().into(); + req["message"] = data_encoding::BASE64URL_NOPAD.encode(&msg).into(); + let Some(resp) = self.perform_request(req).await else { + return Err("Cancelled".to_owned()); }; - let mut request = server.borrow().app_call_reply_request(); - request.get().set_id(id.as_u64()); - request.get().set_message(&msg); - let response = self - .cancellable(request.send().promise) - .await - .map_err(map_to_string)?; - let reader = response - .get() - .map_err(map_to_string)? - .get_result() - .map_err(map_to_string)?; - let res: VeilidAPIResult<()> = decode_api_result(&reader); - res.map_err(map_to_string) + if resp.has_key("error") { + return Err(resp["error"].to_string()); + } + Ok(()) } // Start Client API connection - pub async fn connect(&mut self, connect_addr: SocketAddr) -> Result<(), String> { + pub async fn connect(&self, connect_addr: SocketAddr) -> Result<(), String> { trace!("ClientApiConnection::connect"); // Save the address to connect to self.handle_connection(connect_addr).await } // End Client API connection - pub async fn disconnect(&mut self) { + pub async fn disconnect(&self) { trace!("ClientApiConnection::disconnect"); - let disconnector = self.inner.borrow_mut().disconnector.take(); - match disconnector { - Some(d) => { - self.inner.borrow_mut().disconnect_requested = true; - d.await.unwrap(); - } - None => { - debug!("disconnector doesn't exist"); - } + let mut inner = self.inner.lock(); + if inner.disconnector.is_some() { + inner.disconnector = None; + inner.disconnect_requested = true; } } } diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs index 60fe90d7..dcf0a8cb 100644 --- a/veilid-cli/src/command_processor.rs +++ b/veilid-cli/src/command_processor.rs @@ -1,21 +1,19 @@ use crate::client_api_connection::*; use crate::settings::Settings; +use crate::tools::*; use crate::ui::*; -use std::cell::*; use std::net::SocketAddr; -use std::rc::Rc; use std::time::SystemTime; -use veilid_core::tools::*; -use veilid_core::*; +use veilid_tools::*; -pub fn convert_loglevel(s: &str) -> Result { +pub fn convert_loglevel(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { - "off" => Ok(VeilidConfigLogLevel::Off), - "error" => Ok(VeilidConfigLogLevel::Error), - "warn" => Ok(VeilidConfigLogLevel::Warn), - "info" => Ok(VeilidConfigLogLevel::Info), - "debug" => Ok(VeilidConfigLogLevel::Debug), - "trace" => Ok(VeilidConfigLogLevel::Trace), + "off" => Ok("Off".to_owned()), + "error" => Ok("Error".to_owned()), + "warn" => Ok("Warn".to_owned()), + "info" => Ok("Info".to_owned()), + "debug" => Ok("Debug".to_owned()), + "trace" => Ok("Trace".to_owned()), _ => Err(format!("Invalid log level: {}", s)), } } @@ -39,7 +37,7 @@ impl ConnectionState { } struct CommandProcessorInner { - ui: UI, + ui_sender: UISender, capi: Option, reconnect: bool, finished: bool, @@ -47,21 +45,19 @@ struct CommandProcessorInner { autoreconnect: bool, server_addr: Option, connection_waker: Eventual, - last_call_id: Option, + last_call_id: Option, } -type Handle = Rc>; - #[derive(Clone)] pub struct CommandProcessor { - inner: Handle, + inner: Arc>, } impl CommandProcessor { - pub fn new(ui: UI, settings: &Settings) -> Self { + pub fn new(ui_sender: UISender, settings: &Settings) -> Self { Self { - inner: Rc::new(RefCell::new(CommandProcessorInner { - ui, + inner: Arc::new(Mutex::new(CommandProcessorInner { + ui_sender, capi: None, reconnect: settings.autoreconnect, finished: false, @@ -73,20 +69,20 @@ impl CommandProcessor { })), } } - pub fn set_client_api_connection(&mut self, capi: ClientApiConnection) { - self.inner.borrow_mut().capi = Some(capi); + pub fn set_client_api_connection(&self, capi: ClientApiConnection) { + self.inner.lock().capi = Some(capi); } - fn inner(&self) -> Ref { - self.inner.borrow() + fn inner(&self) -> MutexGuard { + self.inner.lock() } - fn inner_mut(&self) -> RefMut { - self.inner.borrow_mut() + fn inner_mut(&self) -> MutexGuard { + self.inner.lock() } - fn ui(&self) -> UI { - self.inner.borrow().ui.clone() + fn ui_sender(&self) -> UISender { + self.inner.lock().ui_sender.clone() } fn capi(&self) -> ClientApiConnection { - self.inner.borrow().capi.as_ref().unwrap().clone() + self.inner.lock().capi.as_ref().unwrap().clone() } fn word_split(line: &str) -> (String, Option) { @@ -103,12 +99,12 @@ impl CommandProcessor { pub fn cancel_command(&self) { trace!("CommandProcessor::cancel_command"); let capi = self.capi(); - capi.cancel(); + capi.cancel_all(); } pub fn cmd_help(&self, _rest: Option, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_help"); - self.ui().add_node_event( + self.ui_sender().add_node_event( r#"Commands: exit/quit - exit the client disconnect - disconnect the client from the Veilid node @@ -121,14 +117,14 @@ reply - reply to an AppCall not handled directly by the server "# .to_owned(), ); - let ui = self.ui(); + let ui = self.ui_sender(); ui.send_callback(callback); Ok(()) } pub fn cmd_exit(&self, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_exit"); - let ui = self.ui(); + let ui = self.ui_sender(); ui.send_callback(callback); ui.quit(); Ok(()) @@ -136,8 +132,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_shutdown(&self, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_shutdown"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { if let Err(e) = capi.server_shutdown().await { error!("Server command 'shutdown' failed to execute: {}", e); @@ -149,8 +145,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_attach(&self, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_attach"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { if let Err(e) = capi.server_attach().await { error!("Server command 'attach' failed: {}", e); @@ -162,8 +158,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_detach(&self, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_detach"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { if let Err(e) = capi.server_detach().await { error!("Server command 'detach' failed: {}", e); @@ -175,8 +171,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_disconnect(&self, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_disconnect"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { capi.disconnect().await; ui.send_callback(callback); @@ -186,8 +182,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_debug(&self, rest: Option, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_debug"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { match capi.server_debug(rest.unwrap_or_default()).await { Ok(output) => ui.display_string_dialog("Debug Output", output, callback), @@ -203,8 +199,8 @@ reply - reply to an AppCall not handled directly by the server callback: UICallback, ) -> Result<(), String> { trace!("CommandProcessor::cmd_change_log_level"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); spawn_detached_local(async move { let (layer, rest) = Self::word_split(&rest.unwrap_or_default()); let log_level = match convert_loglevel(&rest.unwrap_or_default()) { @@ -235,8 +231,8 @@ reply - reply to an AppCall not handled directly by the server pub fn cmd_reply(&self, rest: Option, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_reply"); - let mut capi = self.capi(); - let ui = self.ui(); + let capi = self.capi(); + let ui = self.ui_sender(); let some_last_id = self.inner_mut().last_call_id.take(); spawn_detached_local(async move { let (first, second) = Self::word_split(&rest.clone().unwrap_or_default()); @@ -249,7 +245,7 @@ reply - reply to an AppCall not handled directly by the server } Ok(v) => v, }; - (OperationId::new(id), second) + (id, second) } else { let id = match some_last_id { None => { @@ -307,14 +303,14 @@ reply - reply to an AppCall not handled directly by the server "change_log_level" => self.cmd_change_log_level(rest, callback), "reply" => self.cmd_reply(rest, callback), _ => { - let ui = self.ui(); + let ui = self.ui_sender(); ui.send_callback(callback); Err(format!("Invalid command: {}", cmd)) } } } - pub async fn connection_manager(&mut self) { + pub async fn connection_manager(&self) { // Connect until we're done while !self.inner_mut().finished { // Wait for connection request @@ -342,7 +338,7 @@ reply - reply to an AppCall not handled directly by the server } else { debug!("Retrying connection to {}", server_addr); } - let mut capi = self.capi(); + let capi = self.capi(); let res = capi.connect(server_addr).await; if res.is_ok() { info!( @@ -377,7 +373,7 @@ reply - reply to an AppCall not handled directly by the server // called by ui //////////////////////////////////////////// - pub fn set_server_address(&mut self, server_addr: Option) { + pub fn set_server_address(&self, server_addr: Option) { self.inner_mut().server_addr = server_addr; } pub fn get_server_address(&self) -> Option { @@ -387,54 +383,65 @@ reply - reply to an AppCall not handled directly by the server // calls into ui //////////////////////////////////////////// - pub fn update_attachment(&mut self, attachment: veilid_core::VeilidStateAttachment) { - self.inner_mut().ui.set_attachment_state( - attachment.state, - attachment.public_internet_ready, - attachment.local_network_ready, + pub fn log_message(&self, message: String) { + self.inner().ui_sender.add_node_event(message); + } + + pub fn update_attachment(&self, attachment: &json::JsonValue) { + self.inner_mut().ui_sender.set_attachment_state( + attachment["state"].as_str().unwrap_or_default().to_owned(), + attachment["public_internet_ready"] + .as_bool() + .unwrap_or_default(), + attachment["local_network_ready"] + .as_bool() + .unwrap_or_default(), ); } - pub fn update_network_status(&mut self, network: veilid_core::VeilidStateNetwork) { - self.inner_mut().ui.set_network_status( - network.started, - network.bps_down.as_u64(), - network.bps_up.as_u64(), - network.peers, + pub fn update_network_status(&self, network: &json::JsonValue) { + self.inner_mut().ui_sender.set_network_status( + network["started"].as_bool().unwrap_or_default(), + json_str_u64(&network["bps_down"]), + json_str_u64(&network["bps_up"]), + network["peers"] + .members() + .cloned() + .collect::>(), ); } - pub fn update_config(&mut self, config: veilid_core::VeilidStateConfig) { - self.inner_mut().ui.set_config(config.config) + pub fn update_config(&self, config: &json::JsonValue) { + self.inner_mut().ui_sender.set_config(&config["config"]) } - pub fn update_route(&mut self, route: veilid_core::VeilidRouteChange) { + pub fn update_route(&self, route: &json::JsonValue) { let mut out = String::new(); - if !route.dead_routes.is_empty() { - out.push_str(&format!("Dead routes: {:?}", route.dead_routes)); + if route["dead_routes"].len() != 0 { + out.push_str(&format!("Dead routes: {:?}", route["dead_routes"])); } - if !route.dead_remote_routes.is_empty() { + if route["dead_routes"].len() != 0 { if !out.is_empty() { out.push_str("\n"); } out.push_str(&format!( "Dead remote routes: {:?}", - route.dead_remote_routes + route["dead_remote_routes"] )); } if !out.is_empty() { - self.inner().ui.add_node_event(out); + self.inner().ui_sender.add_node_event(out); } } - pub fn update_value_change(&mut self, value_change: veilid_core::VeilidValueChange) { - let out = format!("Value change: {:?}", value_change); - self.inner().ui.add_node_event(out); + pub fn update_value_change(&self, value_change: &json::JsonValue) { + let out = format!("Value change: {:?}", value_change.as_str().unwrap_or("???")); + self.inner().ui_sender.add_node_event(out); } - pub fn update_log(&mut self, log: veilid_core::VeilidLog) { - self.inner().ui.add_node_event(format!( + pub fn update_log(&self, log: &json::JsonValue) { + self.inner().ui_sender.add_node_event(format!( "{}: {}{}", - log.log_level, - log.message, - if let Some(bt) = log.backtrace { + log["log_level"].as_str().unwrap_or("???"), + log["message"].as_str().unwrap_or("???"), + if let Some(bt) = log["backtrace"].as_str() { format!("\nBacktrace:\n{}", bt) } else { "".to_owned() @@ -442,79 +449,83 @@ reply - reply to an AppCall not handled directly by the server )); } - pub fn update_app_message(&mut self, msg: veilid_core::VeilidAppMessage) { + pub fn update_app_message(&self, msg: &json::JsonValue) { + let message = json_str_vec_u8(&msg["message"]); + // check is message body is ascii printable let mut printable = true; - for c in msg.message() { + for c in &message { if *c < 32 || *c > 126 { printable = false; } } let strmsg = if printable { - String::from_utf8_lossy(msg.message()).to_string() + String::from_utf8_lossy(&message).to_string() } else { - hex::encode(msg.message()) + hex::encode(message) }; self.inner() - .ui - .add_node_event(format!("AppMessage ({:?}): {}", msg.sender(), strmsg)); + .ui_sender + .add_node_event(format!("AppMessage ({:?}): {}", msg["sender"], strmsg)); } - pub fn update_app_call(&mut self, call: veilid_core::VeilidAppCall) { + pub fn update_app_call(&self, call: &json::JsonValue) { + let message = json_str_vec_u8(&call["message"]); + // check is message body is ascii printable let mut printable = true; - for c in call.message() { + for c in &message { if *c < 32 || *c > 126 { printable = false; } } let strmsg = if printable { - String::from_utf8_lossy(call.message()).to_string() + String::from_utf8_lossy(&message).to_string() } else { - format!("#{}", hex::encode(call.message())) + format!("#{}", hex::encode(&message)) }; - self.inner().ui.add_node_event(format!( + let id = json_str_u64(&call["call_id"]); + + self.inner().ui_sender.add_node_event(format!( "AppCall ({:?}) id = {:016x} : {}", - call.sender(), - call.id().as_u64(), - strmsg + call["sender"], id, strmsg )); - self.inner_mut().last_call_id = Some(call.id()); + self.inner_mut().last_call_id = Some(id); } - pub fn update_shutdown(&mut self) { + pub fn update_shutdown(&self) { // Do nothing with this, we'll process shutdown when rpc connection closes } // called by client_api_connection // calls into ui //////////////////////////////////////////// - pub fn set_connection_state(&mut self, state: ConnectionState) { - self.inner_mut().ui.set_connection_state(state); + pub fn set_connection_state(&self, state: ConnectionState) { + self.inner_mut().ui_sender.set_connection_state(state); } // called by ui //////////////////////////////////////////// - pub fn start_connection(&mut self) { + pub fn start_connection(&self) { self.inner_mut().reconnect = true; self.inner_mut().connection_waker.resolve(); } - // pub fn stop_connection(&mut self) { + // pub fn stop_connection(&self) { // self.inner_mut().reconnect = false; // let mut capi = self.capi().clone(); // spawn_detached(async move { // capi.disconnect().await; // }); // } - pub fn cancel_reconnect(&mut self) { + pub fn cancel_reconnect(&self) { self.inner_mut().reconnect = false; self.inner_mut().connection_waker.resolve(); } - pub fn quit(&mut self) { + pub fn quit(&self) { self.inner_mut().finished = true; self.inner_mut().reconnect = false; self.inner_mut().connection_waker.resolve(); @@ -523,8 +534,8 @@ reply - reply to an AppCall not handled directly by the server // called by ui // calls into client_api_connection //////////////////////////////////////////// - pub fn attach(&mut self) { - let mut capi = self.capi(); + pub fn attach(&self) { + let capi = self.capi(); spawn_detached_local(async move { if let Err(e) = capi.server_attach().await { @@ -533,8 +544,8 @@ reply - reply to an AppCall not handled directly by the server }); } - pub fn detach(&mut self) { - let mut capi = self.capi(); + pub fn detach(&self) { + let capi = self.capi(); spawn_detached_local(async move { if let Err(e) = capi.server_detach().await { diff --git a/veilid-cli/src/main.rs b/veilid-cli/src/main.rs index 9239478f..f51c58e3 100644 --- a/veilid-cli/src/main.rs +++ b/veilid-cli/src/main.rs @@ -3,7 +3,6 @@ #![recursion_limit = "256"] use crate::tools::*; -use veilid_core::tools::*; use clap::{Arg, ColorChoice, Command}; use flexi_logger::*; @@ -18,11 +17,6 @@ mod settings; mod tools; mod ui; -#[allow(clippy::all)] -pub mod veilid_client_capnp { - include!(concat!(env!("OUT_DIR"), "/proto/veilid_client_capnp.rs")); -} - fn parse_command_line(default_config_path: &OsStr) -> Result { let matches = Command::new("veilid-cli") .version("0.1") @@ -97,7 +91,7 @@ fn main() -> Result<(), String> { } // Create UI object - let mut sivui = ui::UI::new(settings.interface.node_log.scrollback, &settings); + let (mut sivui, uisender) = ui::UI::new(settings.interface.node_log.scrollback, &settings); // Set up loggers { @@ -160,19 +154,19 @@ fn main() -> Result<(), String> { // Create command processor debug!("Creating Command Processor "); - let mut comproc = command_processor::CommandProcessor::new(sivui.clone(), &settings); + let comproc = command_processor::CommandProcessor::new(uisender, &settings); sivui.set_command_processor(comproc.clone()); // Create client api client side info!("Starting API connection"); - let mut capi = client_api_connection::ClientApiConnection::new(comproc.clone()); + let capi = client_api_connection::ClientApiConnection::new(comproc.clone()); // Save client api in command processor comproc.set_client_api_connection(capi.clone()); // Keep a connection to the server comproc.set_server_address(server_addr); - let mut comproc2 = comproc.clone(); + let comproc2 = comproc.clone(); let connection_future = comproc.connection_manager(); // Start async diff --git a/veilid-cli/src/peers_table_view.rs b/veilid-cli/src/peers_table_view.rs index ff328476..2d158de6 100644 --- a/veilid-cli/src/peers_table_view.rs +++ b/veilid-cli/src/peers_table_view.rs @@ -1,7 +1,6 @@ use super::*; use cursive_table_view::*; use std::cmp::Ordering; -use veilid_core::*; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum PeerTableColumn { @@ -24,8 +23,11 @@ pub enum PeerTableColumn { // } // } -fn format_ts(ts: Timestamp) -> String { - let ts = ts.as_u64(); +fn format_ts(ts: &json::JsonValue) -> String { + if ts.is_null() { + return "---".to_owned(); + } + let ts = json_str_u64(ts); let secs = timestamp_to_secs(ts); if secs >= 1.0 { format!("{:.2}s", timestamp_to_secs(ts)) @@ -34,8 +36,11 @@ fn format_ts(ts: Timestamp) -> String { } } -fn format_bps(bps: ByteCount) -> String { - let bps = bps.as_u64(); +fn format_bps(bps: &json::JsonValue) -> String { + if bps.is_null() { + return "---".to_owned(); + } + let bps = json_str_u64(bps); if bps >= 1024u64 * 1024u64 * 1024u64 { format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0) } else if bps >= 1024u64 * 1024u64 { @@ -47,25 +52,20 @@ fn format_bps(bps: ByteCount) -> String { } } -impl TableViewItem for PeerTableData { +impl TableViewItem for json::JsonValue { fn to_column(&self, column: PeerTableColumn) -> String { match column { - PeerTableColumn::NodeId => self - .node_ids - .first() - .map(|n| n.to_string()) - .unwrap_or_else(|| "???".to_owned()), - PeerTableColumn::Address => self.peer_address.clone(), - PeerTableColumn::LatencyAvg => format!( - "{}", - self.peer_stats - .latency - .as_ref() - .map(|l| format_ts(l.average)) - .unwrap_or("---".to_owned()) - ), - PeerTableColumn::TransferDownAvg => format_bps(self.peer_stats.transfer.down.average), - PeerTableColumn::TransferUpAvg => format_bps(self.peer_stats.transfer.up.average), + PeerTableColumn::NodeId => self["node_ids"][0].to_string(), + PeerTableColumn::Address => self["peer_address"].to_string(), + PeerTableColumn::LatencyAvg => { + format!("{}", format_ts(&self["peer_stats"]["latency"]["average"])) + } + PeerTableColumn::TransferDownAvg => { + format_bps(&self["peer_stats"]["transfer"]["down"]["average"]) + } + PeerTableColumn::TransferUpAvg => { + format_bps(&self["peer_stats"]["transfer"]["up"]["average"]) + } } } @@ -76,26 +76,20 @@ impl TableViewItem for PeerTableData { match column { PeerTableColumn::NodeId => self.to_column(column).cmp(&other.to_column(column)), PeerTableColumn::Address => self.to_column(column).cmp(&other.to_column(column)), - PeerTableColumn::LatencyAvg => self - .peer_stats - .latency - .as_ref() - .map(|l| l.average) - .cmp(&other.peer_stats.latency.as_ref().map(|l| l.average)), - PeerTableColumn::TransferDownAvg => self - .peer_stats - .transfer - .down - .average - .cmp(&other.peer_stats.transfer.down.average), - PeerTableColumn::TransferUpAvg => self - .peer_stats - .transfer - .up - .average - .cmp(&other.peer_stats.transfer.up.average), + PeerTableColumn::LatencyAvg => json_str_u64(&self["peer_stats"]["latency"]["average"]) + .cmp(&json_str_u64(&other["peer_stats"]["latency"]["average"])), + PeerTableColumn::TransferDownAvg => { + json_str_u64(&self["peer_stats"]["transfer"]["down"]["average"]).cmp(&json_str_u64( + &other["peer_stats"]["transfer"]["down"]["average"], + )) + } + PeerTableColumn::TransferUpAvg => { + json_str_u64(&self["peer_stats"]["transfer"]["up"]["average"]).cmp(&json_str_u64( + &other["peer_stats"]["transfer"]["up"]["average"], + )) + } } } } -pub type PeersTableView = TableView; +pub type PeersTableView = TableView; diff --git a/veilid-cli/src/tools.rs b/veilid-cli/src/tools.rs index bf58d24c..7e6fe04c 100644 --- a/veilid-cli/src/tools.rs +++ b/veilid-cli/src/tools.rs @@ -1,5 +1,10 @@ -use cfg_if::*; +pub use cfg_if::*; +pub use log::*; +pub use parking_lot::*; +pub use veilid_tools::*; + use core::future::Future; +use core::str::FromStr; cfg_if! { if #[cfg(feature="rt-async-std")] { @@ -17,3 +22,13 @@ cfg_if! { } } + +pub fn json_str_u64(value: &json::JsonValue) -> u64 { + u64::from_str(value.as_str().unwrap_or_default()).unwrap_or_default() +} + +pub fn json_str_vec_u8(value: &json::JsonValue) -> Vec { + data_encoding::BASE64URL_NOPAD + .decode(value.as_str().unwrap_or_default().as_bytes()) + .unwrap_or_default() +} diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs index 48b38db9..a38ff6dc 100644 --- a/veilid-cli/src/ui.rs +++ b/veilid-cli/src/ui.rs @@ -1,6 +1,7 @@ use crate::command_processor::*; use crate::peers_table_view::*; use crate::settings::Settings; +use crate::tools::*; use crossbeam_channel::Sender; use cursive::align::*; use cursive::event::*; @@ -12,12 +13,8 @@ use cursive::Cursive; use cursive::CursiveRunnable; use cursive_flexi_logger_view::{CursiveLogWriter, FlexiLoggerView}; //use cursive_multiplex::*; -use log::*; -use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; -use std::rc::Rc; use thiserror::Error; -use veilid_core::*; ////////////////////////////////////////////////////////////// /// @@ -50,20 +47,20 @@ impl Dirty { pub type UICallback = Box; struct UIState { - attachment_state: Dirty, + attachment_state: Dirty, public_internet_ready: Dirty, local_network_ready: Dirty, network_started: Dirty, network_down_up: Dirty<(f32, f32)>, connection_state: Dirty, - peers_state: Dirty>, + peers_state: Dirty>, node_id: Dirty, } impl UIState { pub fn new() -> Self { Self { - attachment_state: Dirty::new(AttachmentState::Detached), + attachment_state: Dirty::new("Detached".to_owned()), public_internet_ready: Dirty::new(false), local_network_ready: Dirty::new(false), network_started: Dirty::new(false), @@ -83,19 +80,15 @@ pub struct UIInner { ui_state: UIState, log_colors: HashMap, cmdproc: Option, - cb_sink: Sender>, cmd_history: VecDeque, cmd_history_position: usize, cmd_history_max_size: usize, connection_dialog_state: Option, } -type Handle = Rc>; - -#[derive(Clone)] pub struct UI { - siv: Handle, - inner: Handle, + siv: CursiveRunnable, + inner: Arc>, } #[derive(Error, Debug)] @@ -113,11 +106,11 @@ impl UI { inner.cmdproc.as_ref().unwrap().clone() } - fn inner(s: &mut Cursive) -> std::cell::Ref<'_, UIInner> { - s.user_data::>().unwrap().borrow() + fn inner(s: &mut Cursive) -> MutexGuard<'_, UIInner> { + s.user_data::>>().unwrap().lock() } - fn inner_mut(s: &mut Cursive) -> std::cell::RefMut<'_, UIInner> { - s.user_data::>().unwrap().borrow_mut() + fn inner_mut(s: &mut Cursive) -> MutexGuard<'_, UIInner> { + s.user_data::>>().unwrap().lock() } fn setup_colors(siv: &mut CursiveRunnable, inner: &mut UIInner, settings: &Settings) { @@ -239,15 +232,16 @@ impl UI { s.find_name("peers").unwrap() } fn render_attachment_state(inner: &mut UIInner) -> String { - let att = match inner.ui_state.attachment_state.get() { - AttachmentState::Detached => "[----]", - AttachmentState::Attaching => "[/ ]", - AttachmentState::AttachedWeak => "[| ]", - AttachmentState::AttachedGood => "[|| ]", - AttachmentState::AttachedStrong => "[||| ]", - AttachmentState::FullyAttached => "[||||]", - AttachmentState::OverAttached => "[++++]", - AttachmentState::Detaching => "[////]", + let att = match inner.ui_state.attachment_state.get().as_str() { + "Detached" => "[----]", + "Attaching" => "[/ ]", + "AttachedWeak" => "[| ]", + "AttachedGood" => "[|| ]", + "AttachedStrong" => "[||| ]", + "FullyAttached" => "[||||]", + "OverAttached" => "[++++]", + "Detaching" => "[////]", + _ => "[????]", }; let pi = if *inner.ui_state.public_internet_ready.get() { "+P" @@ -272,15 +266,16 @@ impl UI { } fn render_button_attach<'a>(inner: &mut UIInner) -> (&'a str, bool) { if let ConnectionState::Connected(_, _) = inner.ui_state.connection_state.get() { - match inner.ui_state.attachment_state.get() { - AttachmentState::Detached => ("Attach", true), - AttachmentState::Attaching => ("Detach", true), - AttachmentState::AttachedWeak => ("Detach", true), - AttachmentState::AttachedGood => ("Detach", true), - AttachmentState::AttachedStrong => ("Detach", true), - AttachmentState::FullyAttached => ("Detach", true), - AttachmentState::OverAttached => ("Detach", true), - AttachmentState::Detaching => ("Detach", false), + match inner.ui_state.attachment_state.get().as_str() { + "Detached" => ("Attach", true), + "Attaching" => ("Detach", true), + "AttachedWeak" => ("Detach", true), + "AttachedGood" => ("Detach", true), + "AttachedStrong" => ("Detach", true), + "FullyAttached" => ("Detach", true), + "OverAttached" => ("Detach", true), + "Detaching" => ("Detach", false), + _ => ("???", false), } } else { (" ---- ", false) @@ -412,17 +407,19 @@ impl UI { } fn on_button_attach_pressed(s: &mut Cursive) { - let action: Option = match Self::inner_mut(s).ui_state.attachment_state.get() { - AttachmentState::Detached => Some(true), - AttachmentState::Attaching => Some(false), - AttachmentState::AttachedWeak => Some(false), - AttachmentState::AttachedGood => Some(false), - AttachmentState::AttachedStrong => Some(false), - AttachmentState::FullyAttached => Some(false), - AttachmentState::OverAttached => Some(false), - AttachmentState::Detaching => None, + let action: Option = match Self::inner_mut(s).ui_state.attachment_state.get().as_str() + { + "Detached" => Some(true), + "Attaching" => Some(false), + "AttachedWeak" => Some(false), + "AttachedGood" => Some(false), + "AttachedStrong" => Some(false), + "FullyAttached" => Some(false), + "OverAttached" => Some(false), + "Detaching" => None, + _ => None, }; - let mut cmdproc = Self::command_processor(s); + let cmdproc = Self::command_processor(s); if let Some(a) = action { if a { cmdproc.attach(); @@ -704,7 +701,7 @@ impl UI { //////////////////////////////////////////////////////////////////////////// // Public functions - pub fn new(node_log_scrollback: usize, settings: &Settings) -> Self { + pub fn new(node_log_scrollback: usize, settings: &Settings) -> (Self, UISender) { cursive_flexi_logger_view::resize(node_log_scrollback); // Instantiate the cursive runnable @@ -723,9 +720,9 @@ impl UI { let cb_sink = runnable.cb_sink().clone(); // Create the UI object - let this = Self { - siv: Rc::new(RefCell::new(runnable)), - inner: Rc::new(RefCell::new(UIInner { + let mut this = Self { + siv: runnable, + inner: Arc::new(Mutex::new(UIInner { ui_state: UIState::new(), log_colors: Default::default(), cmdproc: None, @@ -737,15 +734,13 @@ impl UI { cmd_history_position: 0, cmd_history_max_size: settings.interface.command_line.history_size, connection_dialog_state: None, - cb_sink, })), }; - let mut siv = this.siv.borrow_mut(); - let mut inner = this.inner.borrow_mut(); + let mut inner = this.inner.lock(); // Make the inner object accessible in callbacks easily - siv.set_user_data(this.inner.clone()); + this.siv.set_user_data(this.inner.clone()); // Create layouts @@ -828,87 +823,44 @@ impl UI { .child(TextView::new(version)), ); - siv.add_fullscreen_layer(mainlayout); + this.siv.add_fullscreen_layer(mainlayout); - UI::setup_colors(&mut siv, &mut inner, settings); - UI::setup_quit_handler(&mut siv); - siv.set_global_callback(cursive::event::Event::CtrlChar('k'), UI::clear_handler); + UI::setup_colors(&mut this.siv, &mut inner, settings); + UI::setup_quit_handler(&mut this.siv); + this.siv + .set_global_callback(cursive::event::Event::CtrlChar('k'), UI::clear_handler); drop(inner); - drop(siv); - this + let inner = this.inner.clone(); + (this, UISender { inner, cb_sink }) } pub fn cursive_flexi_logger(&self) -> Box { - let mut flv = - cursive_flexi_logger_view::cursive_flexi_logger(self.siv.borrow().cb_sink().clone()); - flv.set_colors(self.inner.borrow().log_colors.clone()); + let mut flv = cursive_flexi_logger_view::cursive_flexi_logger(self.siv.cb_sink().clone()); + flv.set_colors(self.inner.lock().log_colors.clone()); flv } pub fn set_command_processor(&mut self, cmdproc: CommandProcessor) { - let mut inner = self.inner.borrow_mut(); + let mut inner = self.inner.lock(); inner.cmdproc = Some(cmdproc); - let _ = inner.cb_sink.send(Box::new(UI::update_cb)); - } - pub fn set_attachment_state( - &mut self, - state: AttachmentState, - public_internet_ready: bool, - local_network_ready: bool, - ) { - let mut inner = self.inner.borrow_mut(); - inner.ui_state.attachment_state.set(state); - inner - .ui_state - .public_internet_ready - .set(public_internet_ready); - inner.ui_state.local_network_ready.set(local_network_ready); - - let _ = inner.cb_sink.send(Box::new(UI::update_cb)); - } - pub fn set_network_status( - &mut self, - started: bool, - bps_down: u64, - bps_up: u64, - peers: Vec, - ) { - let mut inner = self.inner.borrow_mut(); - inner.ui_state.network_started.set(started); - inner.ui_state.network_down_up.set(( - ((bps_down as f64) / 1000.0f64) as f32, - ((bps_up as f64) / 1000.0f64) as f32, - )); - inner.ui_state.peers_state.set(peers); - let _ = inner.cb_sink.send(Box::new(UI::update_cb)); - } - pub fn set_config(&mut self, config: VeilidConfigInner) { - let mut inner = self.inner.borrow_mut(); - - inner - .ui_state - .node_id - .set(config.network.routing_table.node_id.to_string()); - } - pub fn set_connection_state(&mut self, state: ConnectionState) { - let mut inner = self.inner.borrow_mut(); - inner.ui_state.connection_state.set(state); - let _ = inner.cb_sink.send(Box::new(UI::update_cb)); } - pub fn add_node_event(&self, event: String) { - let inner = self.inner.borrow(); - let color = *inner.log_colors.get(&Level::Info).unwrap(); - let mut starting_style: Style = color.into(); - for line in event.lines() { - let (spanned_string, end_style) = - cursive::utils::markup::ansi::parse_with_starting_style(starting_style, line); - cursive_flexi_logger_view::push_to_log(spanned_string); - starting_style = end_style; - } - let _ = inner.cb_sink.send(Box::new(UI::update_cb)); + // Note: Cursive is not re-entrant, can't borrow_mut self.siv again after this + pub async fn run_async(&mut self) { + self.siv.run_async().await; } + // pub fn run(&mut self) { + // self.siv.run(); + // } +} +#[derive(Clone)] +pub struct UISender { + inner: Arc>, + cb_sink: Sender>, +} + +impl UISender { pub fn display_string_dialog( &self, title: T, @@ -917,31 +869,84 @@ impl UI { ) { let title = title.to_string(); let text = text.to_string(); - let inner = self.inner.borrow(); - let _ = inner.cb_sink.send(Box::new(move |s| { + let _ = self.cb_sink.send(Box::new(move |s| { UI::display_string_dialog_cb(s, title, text, close_cb) })); } pub fn quit(&self) { - let inner = self.inner.borrow(); - let _ = inner.cb_sink.send(Box::new(|s| { + let _ = self.cb_sink.send(Box::new(|s| { s.quit(); })); } pub fn send_callback(&self, callback: UICallback) { - let inner = self.inner.borrow(); - let _ = inner.cb_sink.send(Box::new(move |s| callback(s))); + let _ = self.cb_sink.send(Box::new(move |s| callback(s))); + } + pub fn set_attachment_state( + &mut self, + state: String, + public_internet_ready: bool, + local_network_ready: bool, + ) { + { + let mut inner = self.inner.lock(); + inner.ui_state.attachment_state.set(state); + inner + .ui_state + .public_internet_ready + .set(public_internet_ready); + inner.ui_state.local_network_ready.set(local_network_ready); + } + + let _ = self.cb_sink.send(Box::new(UI::update_cb)); + } + pub fn set_network_status( + &mut self, + started: bool, + bps_down: u64, + bps_up: u64, + peers: Vec, + ) { + { + let mut inner = self.inner.lock(); + inner.ui_state.network_started.set(started); + inner.ui_state.network_down_up.set(( + ((bps_down as f64) / 1000.0f64) as f32, + ((bps_up as f64) / 1000.0f64) as f32, + )); + inner.ui_state.peers_state.set(peers); + } + let _ = self.cb_sink.send(Box::new(UI::update_cb)); + } + pub fn set_config(&mut self, config: &json::JsonValue) { + let mut inner = self.inner.lock(); + + inner + .ui_state + .node_id + .set(config["network"]["routing_table"]["node_id"].to_string()); + } + pub fn set_connection_state(&mut self, state: ConnectionState) { + { + let mut inner = self.inner.lock(); + inner.ui_state.connection_state.set(state); + } + let _ = self.cb_sink.send(Box::new(UI::update_cb)); } - // Note: Cursive is not re-entrant, can't borrow_mut self.siv again after this - pub async fn run_async(&mut self) { - let mut siv = self.siv.borrow_mut(); - siv.run_async().await; + pub fn add_node_event(&self, event: String) { + { + let inner = self.inner.lock(); + let color = *inner.log_colors.get(&Level::Info).unwrap(); + let mut starting_style: Style = color.into(); + for line in event.lines() { + let (spanned_string, end_style) = + cursive::utils::markup::ansi::parse_with_starting_style(starting_style, line); + cursive_flexi_logger_view::push_to_log(spanned_string); + starting_style = end_style; + } + } + let _ = self.cb_sink.send(Box::new(UI::update_cb)); } - // pub fn run(&mut self) { - // let mut siv = self.siv.borrow_mut(); - // siv.run(); - // } } diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index d646c587..8919c73a 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -73,6 +73,7 @@ weak-table = "0.3.2" range-set-blaze = "0.1.5" argon2 = "0.5.0" paste = "1.0.12" +schemars = "0.8.12" # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 2fa813b6..3d813450 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -496,13 +496,15 @@ struct Question @0xd8510bc33492ef70 { getValueQ @5 :OperationGetValueQ; setValueQ @6 :OperationSetValueQ; watchValueQ @7 :OperationWatchValueQ; - supplyBlockQ @8 :OperationSupplyBlockQ; - findBlockQ @9 :OperationFindBlockQ; + # #[cfg(feature="unstable-blockstore")] + # supplyBlockQ @8 :OperationSupplyBlockQ; + # findBlockQ @9 :OperationFindBlockQ; # Tunnel operations - startTunnelQ @10 :OperationStartTunnelQ; - completeTunnelQ @11 :OperationCompleteTunnelQ; - cancelTunnelQ @12 :OperationCancelTunnelQ; + # #[cfg(feature="unstable-tunnels")] + # startTunnelQ @10 :OperationStartTunnelQ; + # completeTunnelQ @11 :OperationCompleteTunnelQ; + # cancelTunnelQ @12 :OperationCancelTunnelQ; } } @@ -532,14 +534,17 @@ struct Answer @0xacacb8b6988c1058 { appCallA @2 :OperationAppCallA; getValueA @3 :OperationGetValueA; setValueA @4 :OperationSetValueA; - watchValueA @5 :OperationWatchValueA; - supplyBlockA @6 :OperationSupplyBlockA; - findBlockA @7 :OperationFindBlockA; + watchValueA @5 :OperationWatchValueA; + + # #[cfg(feature="unstable-blockstore")] + #supplyBlockA @6 :OperationSupplyBlockA; + #findBlockA @7 :OperationFindBlockA; # Tunnel operations - startTunnelA @8 :OperationStartTunnelA; - completeTunnelA @9 :OperationCompleteTunnelA; - cancelTunnelA @10 :OperationCancelTunnelA; + # #[cfg(feature="unstable-tunnels")] + # startTunnelA @8 :OperationStartTunnelA; + # completeTunnelA @9 :OperationCompleteTunnelA; + # cancelTunnelA @10 :OperationCancelTunnelA; } } diff --git a/veilid-core/src/attachment_manager.rs b/veilid-core/src/attachment_manager.rs index fca5650d..a373bea2 100644 --- a/veilid-core/src/attachment_manager.rs +++ b/veilid-core/src/attachment_manager.rs @@ -30,7 +30,7 @@ impl AttachmentManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, ) -> AttachmentManagerUnlockedInner { AttachmentManagerUnlockedInner { @@ -40,6 +40,7 @@ impl AttachmentManager { storage_manager, protected_store, table_store, + #[cfg(feature = "unstable-blockstore")] block_store, crypto, ), @@ -60,7 +61,7 @@ impl AttachmentManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, ) -> Self { Self { @@ -70,6 +71,7 @@ impl AttachmentManager { storage_manager, protected_store, table_store, + #[cfg(feature = "unstable-blockstore")] block_store, crypto, )), diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index 555bdcf2..ba128687 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -17,6 +17,7 @@ struct ServicesContext { pub protected_store: Option, pub table_store: Option, + #[cfg(feature = "unstable-blockstore")] pub block_store: Option, pub crypto: Option, pub attachment_manager: Option, @@ -30,6 +31,7 @@ impl ServicesContext { update_callback, protected_store: None, table_store: None, + #[cfg(feature = "unstable-blockstore")] block_store: None, crypto: None, attachment_manager: None, @@ -42,7 +44,7 @@ impl ServicesContext { update_callback: UpdateCallback, protected_store: ProtectedStore, table_store: TableStore, - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, attachment_manager: AttachmentManager, storage_manager: StorageManager, @@ -52,6 +54,7 @@ impl ServicesContext { update_callback, protected_store: Some(protected_store), table_store: Some(table_store), + #[cfg(feature = "unstable-blockstore")] block_store: Some(block_store), crypto: Some(crypto), attachment_manager: Some(attachment_manager), @@ -103,22 +106,25 @@ impl ServicesContext { self.crypto = Some(crypto.clone()); // Set up block store - trace!("init block store"); - let block_store = BlockStore::new(self.config.clone()); - if let Err(e) = block_store.init().await { - error!("failed to init block store: {}", e); - self.shutdown().await; - return Err(e); + #[cfg(feature = "unstable-blockstore")] + { + trace!("init block store"); + let block_store = BlockStore::new(self.config.clone()); + if let Err(e) = block_store.init().await { + error!("failed to init block store: {}", e); + self.shutdown().await; + return Err(e); + } + self.block_store = Some(block_store.clone()); } - self.block_store = Some(block_store.clone()); // Set up storage manager trace!("init storage manager"); let storage_manager = StorageManager::new( self.config.clone(), self.crypto.clone().unwrap(), - self.protected_store.clone().unwrap(), self.table_store.clone().unwrap(), + #[cfg(feature = "unstable-blockstore")] self.block_store.clone().unwrap(), ); if let Err(e) = storage_manager.init().await { @@ -136,6 +142,7 @@ impl ServicesContext { storage_manager, protected_store, table_store, + #[cfg(feature = "unstable-blockstore")] block_store, crypto, ); @@ -162,6 +169,7 @@ impl ServicesContext { trace!("terminate storage manager"); storage_manager.terminate().await; } + #[cfg(feature = "unstable-blockstore")] if let Some(block_store) = &mut self.block_store { trace!("terminate block store"); block_store.terminate().await; @@ -198,6 +206,7 @@ pub struct VeilidCoreContext { pub storage_manager: StorageManager, pub protected_store: ProtectedStore, pub table_store: TableStore, + #[cfg(feature = "unstable-blockstore")] pub block_store: BlockStore, pub crypto: Crypto, pub attachment_manager: AttachmentManager, @@ -251,6 +260,7 @@ impl VeilidCoreContext { storage_manager: sc.storage_manager.unwrap(), protected_store: sc.protected_store.unwrap(), table_store: sc.table_store.unwrap(), + #[cfg(feature = "unstable-blockstore")] block_store: sc.block_store.unwrap(), crypto: sc.crypto.unwrap(), attachment_manager: sc.attachment_manager.unwrap(), @@ -264,6 +274,7 @@ impl VeilidCoreContext { self.update_callback.clone(), self.protected_store, self.table_store, + #[cfg(feature = "unstable-blockstore")] self.block_store, self.crypto, self.attachment_manager, diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index 822687eb..966633dd 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -25,11 +25,10 @@ pub use none::*; #[cfg(feature = "enable-crypto-vld0")] pub use vld0::*; -use crate::*; +use super::*; use core::convert::TryInto; use hashlink::linked_hash_map::Entry; use hashlink::LruCache; -use serde::{Deserialize, Serialize}; /// Handle to a particular cryptosystem pub type CryptoSystemVersion = Arc; diff --git a/veilid-core/src/intf/native/mod.rs b/veilid-core/src/intf/native/mod.rs index 018cba41..d8cc6811 100644 --- a/veilid-core/src/intf/native/mod.rs +++ b/veilid-core/src/intf/native/mod.rs @@ -1,8 +1,12 @@ +#[cfg(feature = "unstable-blockstore")] mod block_store; + mod protected_store; mod system; +#[cfg(feature = "unstable-blockstore")] pub use block_store::*; + pub use protected_store::*; pub use system::*; diff --git a/veilid-core/src/intf/native/protected_store.rs b/veilid-core/src/intf/native/protected_store.rs index d8e46918..380a0c34 100644 --- a/veilid-core/src/intf/native/protected_store.rs +++ b/veilid-core/src/intf/native/protected_store.rs @@ -69,7 +69,7 @@ impl ProtectedStore { )); // Ensure permissions are correct - ensure_file_private_owner(&insecure_keyring_file)?; + ensure_file_private_owner(&insecure_keyring_file).map_err(|e| eyre!("{}", e))?; // Open the insecure keyring inner.keyring_manager = Some( diff --git a/veilid-core/src/intf/wasm/mod.rs b/veilid-core/src/intf/wasm/mod.rs index b69ada7b..113d35db 100644 --- a/veilid-core/src/intf/wasm/mod.rs +++ b/veilid-core/src/intf/wasm/mod.rs @@ -1,8 +1,12 @@ +#[cfg(feature = "unstable-blockstore")] mod block_store; + mod protected_store; mod system; +#[cfg(feature = "unstable-blockstore")] pub use block_store::*; + pub use protected_store::*; pub use system::*; diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index bca8a6fe..f49417d9 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -41,14 +41,6 @@ pub use self::veilid_config::*; pub use self::veilid_layer_filter::*; pub use veilid_tools as tools; -use enumset::*; -use rkyv::{ - bytecheck, bytecheck::CheckBytes, de::deserializers::SharedDeserializeMap, with::Skip, - Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize, -}; -type RkyvDefaultValidator<'t> = rkyv::validation::validators::DefaultValidator<'t>; -use serde::*; - pub mod veilid_capnp { include!(concat!(env!("OUT_DIR"), "/proto/veilid_capnp.rs")); } @@ -93,4 +85,20 @@ pub static DEFAULT_LOG_IGNORE_LIST: [&str; 21] = [ "attohttpc", ]; +use cfg_if::*; +use enumset::*; +use eyre::{bail, eyre, Report as EyreReport, Result as EyreResult, WrapErr}; +use parking_lot::*; +use rkyv::{ + bytecheck, bytecheck::CheckBytes, de::deserializers::SharedDeserializeMap, with::Skip, + Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize, +}; +use tracing::*; use veilid_tools::*; +type RkyvDefaultValidator<'t> = rkyv::validation::validators::DefaultValidator<'t>; +use futures_util::stream::FuturesUnordered; +use owo_colors::OwoColorize; +use schemars::{schema_for, JsonSchema}; +use serde::*; +use stop_token::*; +use thiserror::Error as ThisError; diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 3a57e134..e7abd622 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -150,6 +150,7 @@ struct NetworkManagerUnlockedInner { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, + #[cfg(feature="unstable-blockstore")] block_store: BlockStore, crypto: Crypto, // Accessors @@ -181,6 +182,7 @@ impl NetworkManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, + #[cfg(feature="unstable-blockstore")] block_store: BlockStore, crypto: Crypto, ) -> NetworkManagerUnlockedInner { @@ -189,6 +191,7 @@ impl NetworkManager { storage_manager, protected_store, table_store, + #[cfg(feature="unstable-blockstore")] block_store, crypto, routing_table: RwLock::new(None), @@ -204,6 +207,7 @@ impl NetworkManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, + #[cfg(feature="unstable-blockstore")] block_store: BlockStore, crypto: Crypto, ) -> Self { @@ -214,6 +218,7 @@ impl NetworkManager { storage_manager, protected_store, table_store, + #[cfg(feature="unstable-blockstore")] block_store, crypto, )), @@ -241,6 +246,7 @@ impl NetworkManager { pub fn table_store(&self) -> TableStore { self.unlocked_inner.table_store.clone() } + #[cfg(feature="unstable-blockstore")] pub fn block_store(&self) -> BlockStore { self.unlocked_inner.block_store.clone() } @@ -679,12 +685,12 @@ impl NetworkManager { peer_info, false, ) { - None => { + Ok(nr) => nr, + Err(e) => { return Ok(NetworkResult::invalid_message( - "unable to register reverse connect peerinfo", - )) + format!("unable to register reverse connect peerinfo: {}", e) + )); } - Some(nr) => nr, }; // Make a reverse connection to the peer and send the receipt to it @@ -702,13 +708,12 @@ impl NetworkManager { peer_info, false, ) { - None => { + Ok(nr) => nr, + Err(e) => { return Ok(NetworkResult::invalid_message( - //sender_id, - "unable to register hole punch connect peerinfo", + format!("unable to register hole punch connect peerinfo: {}", e) )); } - Some(nr) => nr, }; // Get the udp direct dialinfo for the hole punch @@ -1071,8 +1076,17 @@ impl NetworkManager { // Dial info filter comes from the target node ref let dial_info_filter = target_node_ref.dial_info_filter(); - let sequencing = target_node_ref.sequencing(); + let mut sequencing = target_node_ref.sequencing(); + + // If the node has had lost questions or failures to send, prefer sequencing + // to improve reliability. The node may be experiencing UDP fragmentation drops + // or other firewalling issues and may perform better with TCP. + let unreliable = target_node_ref.peer_stats().rpc_stats.failed_to_send > 0 || target_node_ref.peer_stats().rpc_stats.recent_lost_answers > 0; + if unreliable && sequencing < Sequencing::PreferOrdered { + sequencing = Sequencing::PreferOrdered; + } + // Get the best contact method with these parameters from the routing domain let cm = routing_table.get_contact_method( routing_domain, &peer_a, @@ -1088,7 +1102,7 @@ impl NetworkManager { ContactMethod::Direct(di) => NodeContactMethod::Direct(di), ContactMethod::SignalReverse(relay_key, target_key) => { let relay_nr = routing_table - .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter) + .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)? .ok_or_else(|| eyre!("couldn't look up relay"))?; if !target_node_ref.node_ids().contains(&target_key) { bail!("target noderef didn't match target key"); @@ -1097,7 +1111,7 @@ impl NetworkManager { } ContactMethod::SignalHolePunch(relay_key, target_key) => { let relay_nr = routing_table - .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter) + .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)? .ok_or_else(|| eyre!("couldn't look up relay"))?; if target_node_ref.node_ids().contains(&target_key) { bail!("target noderef didn't match target key"); @@ -1106,13 +1120,13 @@ impl NetworkManager { } ContactMethod::InboundRelay(relay_key) => { let relay_nr = routing_table - .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter) + .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)? .ok_or_else(|| eyre!("couldn't look up relay"))?; NodeContactMethod::InboundRelay(relay_nr) } ContactMethod::OutboundRelay(relay_key) => { let relay_nr = routing_table - .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter) + .lookup_and_filter_noderef(relay_key, routing_domain.into(), dial_info_filter)? .ok_or_else(|| eyre!("couldn't look up relay"))?; NodeContactMethod::OutboundRelay(relay_nr) } @@ -1415,7 +1429,13 @@ impl NetworkManager { // We should, because relays are chosen by nodes that have established connectivity and // should be mutually in each others routing tables. The node needing the relay will be // pinging this node regularly to keep itself in the routing table - routing_table.lookup_node_ref(recipient_id) + match routing_table.lookup_node_ref(recipient_id) { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed to look up recipient node for relay, dropping outbound relayed packet: {}" ,e); + return Ok(false); + } + } }; if let Some(relay_nr) = some_relay_nr { @@ -1457,12 +1477,12 @@ impl NetworkManager { connection_descriptor, ts, ) { - None => { + Ok(v) => v, + Err(e) => { // If the node couldn't be registered just skip this envelope, - // the error will have already been logged + log_net!(debug "failed to register node with existing connection: {}", e); return Ok(false); } - Some(v) => v, }; source_noderef.add_envelope_version(envelope.get_version()); @@ -1559,7 +1579,7 @@ impl NetworkManager { peers: { let mut out = Vec::new(); for (k, v) in routing_table.get_recent_peers() { - if let Some(nr) = routing_table.lookup_node_ref(k) { + if let Ok(Some(nr)) = routing_table.lookup_node_ref(k) { let peer_stats = nr.peer_stats(); let peer = PeerTableData { node_ids: nr.node_ids().iter().copied().collect(), diff --git a/veilid-core/src/network_manager/native/start_protocols.rs b/veilid-core/src/network_manager/native/start_protocols.rs index 36f285e4..76cdaf68 100644 --- a/veilid-core/src/network_manager/native/start_protocols.rs +++ b/veilid-core/src/network_manager/native/start_protocols.rs @@ -212,7 +212,8 @@ impl Network { } else { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise - let sockaddrs = listen_address_to_socket_addrs(&listen_address)?; + let sockaddrs = + listen_address_to_socket_addrs(&listen_address).map_err(|e| eyre!("{}", e))?; if sockaddrs.is_empty() { bail!("No valid listen address: {}", listen_address); } @@ -236,7 +237,8 @@ impl Network { } else { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise - let sockaddrs = listen_address_to_socket_addrs(&listen_address)?; + let sockaddrs = + listen_address_to_socket_addrs(&listen_address).map_err(|e| eyre!("{}", e))?; if sockaddrs.is_empty() { bail!("No valid listen address: {}", listen_address); } diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index 46206d0c..f133f4e5 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -603,7 +603,7 @@ impl BucketEntryInner { } } pub(super) fn check_dead(&self, cur_ts: Timestamp) -> bool { - // If we have failured to send NEVER_REACHED_PING_COUNT times in a row, the node is dead + // If we have failed to send NEVER_REACHED_PING_COUNT times in a row, the node is dead if self.peer_stats.rpc_stats.failed_to_send >= NEVER_REACHED_PING_COUNT { return true; } diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 1d63fcb5..2223d189 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -628,14 +628,14 @@ impl RoutingTable { } /// Resolve an existing routing table entry using any crypto kind and return a reference to it - pub fn lookup_any_node_ref(&self, node_id_key: PublicKey) -> Option { + pub fn lookup_any_node_ref(&self, node_id_key: PublicKey) -> EyreResult> { self.inner .read() .lookup_any_node_ref(self.clone(), node_id_key) } /// Resolve an existing routing table entry and return a reference to it - pub fn lookup_node_ref(&self, node_id: TypedKey) -> Option { + pub fn lookup_node_ref(&self, node_id: TypedKey) -> EyreResult> { self.inner.read().lookup_node_ref(self.clone(), node_id) } @@ -645,7 +645,7 @@ impl RoutingTable { node_id: TypedKey, routing_domain_set: RoutingDomainSet, dial_info_filter: DialInfoFilter, - ) -> Option { + ) -> EyreResult> { self.inner.read().lookup_and_filter_noderef( self.clone(), node_id, @@ -662,7 +662,7 @@ impl RoutingTable { routing_domain: RoutingDomain, peer_info: PeerInfo, allow_invalid: bool, - ) -> Option { + ) -> EyreResult { self.inner.write().register_node_with_peer_info( self.clone(), routing_domain, @@ -678,7 +678,7 @@ impl RoutingTable { node_id: TypedKey, descriptor: ConnectionDescriptor, timestamp: Timestamp, - ) -> Option { + ) -> EyreResult { self.inner.write().register_node_with_existing_connection( self.clone(), node_id, @@ -711,7 +711,7 @@ impl RoutingTable { // (uses same logic as send_data, ensuring last_connection works for UDP) for e in &recent_peers { let mut dead = true; - if let Some(nr) = self.lookup_node_ref(*e) { + if let Ok(Some(nr)) = self.lookup_node_ref(*e) { if let Some(last_connection) = nr.last_connection() { out.push((*e, RecentPeersEntry { last_connection })); dead = false; @@ -1017,10 +1017,11 @@ impl RoutingTable { } // Register the node if it's new - if let Some(nr) = - self.register_node_with_peer_info(RoutingDomain::PublicInternet, p, false) - { - out.push(nr); + match self.register_node_with_peer_info(RoutingDomain::PublicInternet, p, false) { + Ok(nr) => out.push(nr), + Err(e) => { + log_rtab!(debug "failed to register node with peer info from find node answer: {}", e); + } } } out diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index 1f637801..5cb53c64 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -192,25 +192,24 @@ pub trait NodeRefBase: Sized { } dif } - fn relay(&self, routing_domain: RoutingDomain) -> Option { + fn relay(&self, routing_domain: RoutingDomain) -> EyreResult> { self.operate_mut(|rti, e| { - e.signed_node_info(routing_domain) - .and_then(|n| n.relay_peer_info()) - .and_then(|rpi| { - // If relay is ourselves, then return None, because we can't relay through ourselves - // and to contact this node we should have had an existing inbound connection - if rti.unlocked_inner.matches_own_node_id(rpi.node_ids()) { - return None; - } + let Some(sni) = e.signed_node_info(routing_domain) else { + return Ok(None); + }; + let Some(rpi) = sni.relay_peer_info() else { + return Ok(None); + }; + // If relay is ourselves, then return None, because we can't relay through ourselves + // and to contact this node we should have had an existing inbound connection + if rti.unlocked_inner.matches_own_node_id(rpi.node_ids()) { + bail!("Can't relay though ourselves"); + } - // Register relay node and return noderef - rti.register_node_with_peer_info( - self.routing_table(), - routing_domain, - rpi, - false, - ) - }) + // Register relay node and return noderef + let nr = + rti.register_node_with_peer_info(self.routing_table(), routing_domain, rpi, false)?; + Ok(Some(nr)) }) } diff --git a/veilid-core/src/routing_table/privacy.rs b/veilid-core/src/routing_table/privacy.rs index fc670375..66f08c57 100644 --- a/veilid-core/src/routing_table/privacy.rs +++ b/veilid-core/src/routing_table/privacy.rs @@ -37,15 +37,27 @@ impl RouteNode { match self { RouteNode::NodeId(id) => { // - routing_table.lookup_node_ref(TypedKey::new(crypto_kind, *id)) + match routing_table.lookup_node_ref(TypedKey::new(crypto_kind, *id)) { + Ok(nr) => nr, + Err(e) => { + log_rtab!(debug "failed to look up route node: {}", e); + return None; + } + } } RouteNode::PeerInfo(pi) => { // - routing_table.register_node_with_peer_info( + match routing_table.register_node_with_peer_info( RoutingDomain::PublicInternet, pi.clone(), false, - ) + ) { + Ok(nr) => Some(nr), + Err(e) => { + log_rtab!(debug "failed to register route node: {}", e); + return None; + } + } } } } diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs index 3f29ba0d..db490b08 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs @@ -378,7 +378,14 @@ impl RouteSpecStore { // Already seen this node, should not be in the route twice return None; } - if let Some(relay) = node.locked_mut(rti).relay(RoutingDomain::PublicInternet) { + let opt_relay = match node.locked_mut(rti).relay(RoutingDomain::PublicInternet) { + Ok(r) => r, + Err(e) => { + log_rtab!(error "failed to get relay for route node: {}", e); + return None; + } + }; + if let Some(relay) = opt_relay { let relay_id = relay.locked(rti).best_node_id(); if !seen_nodes.insert(relay_id) { // Already seen this node, should not be in the route twice @@ -869,13 +876,15 @@ impl RouteSpecStore { }; let opt_first_hop = match pr_first_hop_node { - RouteNode::NodeId(id) => rti.lookup_node_ref(routing_table.clone(), TypedKey::new(crypto_kind, id)), - RouteNode::PeerInfo(pi) => rti.register_node_with_peer_info( - routing_table.clone(), - RoutingDomain::PublicInternet, - pi, - false, - ), + RouteNode::NodeId(id) => rti.lookup_node_ref(routing_table.clone(), TypedKey::new(crypto_kind, id))?, + RouteNode::PeerInfo(pi) => { + Some(rti.register_node_with_peer_info( + routing_table.clone(), + RoutingDomain::PublicInternet, + pi, + false, + )?) + } }; if opt_first_hop.is_none() { // Can't reach this private route any more @@ -1322,9 +1331,9 @@ impl RouteSpecStore { } // ensure this isn't also an allocated route - if inner.content.get_id_by_key(&private_route.public_key.value).is_some() { - bail!("should not import allocated route"); - } + // if inner.content.get_id_by_key(&private_route.public_key.value).is_some() { + // bail!("should not import allocated route"); + // } } inner.cache.cache_remote_private_route(cur_ts, id, private_routes); diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs index b193c398..9c5fbafe 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store_content.rs @@ -40,7 +40,7 @@ impl RouteSpecStoreContent { // Go through best route and resolve noderefs let mut hop_node_refs = Vec::with_capacity(rsd.hops.len()); for h in &rsd.hops { - let Some(nr) = routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) else { + let Ok(Some(nr)) = routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) else { dead_ids.push(rsid.clone()); break; }; diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index eea23a83..e7b8cd05 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -651,14 +651,13 @@ impl RoutingTableInner { outer_self: RoutingTable, node_ids: &TypedKeySet, update_func: F, - ) -> Option + ) -> EyreResult where F: FnOnce(&mut RoutingTableInner, &mut BucketEntryInner), { // Ensure someone isn't trying register this node itself if self.unlocked_inner.matches_own_node_id(node_ids) { - log_rtab!(debug "can't register own node"); - return None; + bail!("can't register own node"); } // Look up all bucket entries and make sure we only have zero or one @@ -688,8 +687,7 @@ impl RoutingTableInner { if let Some(best_entry) = best_entry { // Update the entry with all of the node ids if let Err(e) = self.update_bucket_entries(best_entry.clone(), node_ids) { - log_rtab!(debug "Not registering new ids for existing node: {}", e); - return None; + bail!("Not registering new ids for existing node: {}", e); } // Make a noderef to return @@ -699,7 +697,7 @@ impl RoutingTableInner { best_entry.with_mut_inner(|e| update_func(self, e)); // Return the noderef - return Some(nr); + return Ok(nr); } // If no entry exists yet, add the first entry to a bucket, possibly evicting a bucket member @@ -712,8 +710,7 @@ impl RoutingTableInner { // Update the other bucket entries with the remaining node ids if let Err(e) = self.update_bucket_entries(new_entry.clone(), node_ids) { - log_rtab!(debug "Not registering new node: {}", e); - return None; + bail!("Not registering new node: {}", e); } // Make node ref to return @@ -725,7 +722,7 @@ impl RoutingTableInner { // Kick the bucket log_rtab!(debug "Routing table now has {} nodes, {} live", self.bucket_entry_count(), self.get_entry_count(RoutingDomainSet::all(), BucketEntryState::Unreliable, &VALID_CRYPTO_KINDS)); - Some(nr) + Ok(nr) } /// Resolve an existing routing table entry using any crypto kind and return a reference to it @@ -733,28 +730,35 @@ impl RoutingTableInner { &self, outer_self: RoutingTable, node_id_key: PublicKey, - ) -> Option { - VALID_CRYPTO_KINDS.iter().find_map(|ck| { - self.lookup_node_ref(outer_self.clone(), TypedKey::new(*ck, node_id_key)) - }) + ) -> EyreResult> { + for ck in VALID_CRYPTO_KINDS { + if let Some(nr) = + self.lookup_node_ref(outer_self.clone(), TypedKey::new(ck, node_id_key))? + { + return Ok(Some(nr)); + } + } + Ok(None) } /// Resolve an existing routing table entry and return a reference to it - pub fn lookup_node_ref(&self, outer_self: RoutingTable, node_id: TypedKey) -> Option { + pub fn lookup_node_ref( + &self, + outer_self: RoutingTable, + node_id: TypedKey, + ) -> EyreResult> { if self.unlocked_inner.matches_own_node_id(&[node_id]) { - log_rtab!(error "can't look up own node id in routing table"); - return None; + bail!("can't look up own node id in routing table"); } if !VALID_CRYPTO_KINDS.contains(&node_id.kind) { - log_rtab!(error "can't look up node id with invalid crypto kind"); - return None; + bail!("can't look up node id with invalid crypto kind"); } let bucket_index = self.unlocked_inner.calculate_bucket_index(&node_id); let bucket = self.get_bucket(bucket_index); - bucket + Ok(bucket .entry(&node_id.value) - .map(|e| NodeRef::new(outer_self, e, None)) + .map(|e| NodeRef::new(outer_self, e, None))) } /// Resolve an existing routing table entry and return a filtered reference to it @@ -764,15 +768,15 @@ impl RoutingTableInner { node_id: TypedKey, routing_domain_set: RoutingDomainSet, dial_info_filter: DialInfoFilter, - ) -> Option { + ) -> EyreResult> { let nr = self.lookup_node_ref(outer_self, node_id)?; - Some( + Ok(nr.map(|nr| { nr.filtered_clone( NodeRefFilter::new() .with_dial_info_filter(dial_info_filter) .with_routing_domain_set(routing_domain_set), - ), - ) + ) + })) } /// Resolve an existing routing table entry and call a function on its entry without using a noderef @@ -802,50 +806,53 @@ impl RoutingTableInner { routing_domain: RoutingDomain, peer_info: PeerInfo, allow_invalid: bool, - ) -> Option { + ) -> EyreResult { // if our own node is in the list, then ignore it as we don't add ourselves to our own routing table if self .unlocked_inner .matches_own_node_id(peer_info.node_ids()) { - log_rtab!(debug "can't register own node id in routing table"); - return None; + bail!("can't register own node id in routing table"); } // node can not be its own relay let rids = peer_info.signed_node_info().relay_ids(); let nids = peer_info.node_ids(); if nids.contains_any(&rids) { - log_rtab!(debug "node can not be its own relay"); - return None; + bail!("node can not be its own relay"); } if !allow_invalid { // verify signature if !peer_info.signed_node_info().has_any_signature() { - log_rtab!(debug "signed node info for {:?} has no valid signature", peer_info.node_ids()); - return None; + bail!( + "signed node info for {:?} has no valid signature", + peer_info.node_ids() + ); } // verify signed node info is valid in this routing domain if !self.signed_node_info_is_valid_in_routing_domain( routing_domain, peer_info.signed_node_info(), ) { - log_rtab!(debug "signed node info for {:?} not valid in the {:?} routing domain", peer_info.node_ids(), routing_domain); - return None; + bail!( + "signed node info for {:?} not valid in the {:?} routing domain", + peer_info.node_ids(), + routing_domain + ); } } let (node_ids, signed_node_info) = peer_info.destructure(); - self.create_node_ref(outer_self, &node_ids, |_rti, e| { + let mut nr = self.create_node_ref(outer_self, &node_ids, |_rti, e| { e.update_signed_node_info(routing_domain, signed_node_info); - }) - .map(|mut nr| { - nr.set_filter(Some( - NodeRefFilter::new().with_routing_domain(routing_domain), - )); - nr - }) + })?; + + nr.set_filter(Some( + NodeRefFilter::new().with_routing_domain(routing_domain), + )); + + Ok(nr) } /// Shortcut function to add a node to our routing table if it doesn't exist @@ -856,17 +863,15 @@ impl RoutingTableInner { node_id: TypedKey, descriptor: ConnectionDescriptor, timestamp: Timestamp, - ) -> Option { - let out = self.create_node_ref(outer_self, &TypedKeySet::from(node_id), |_rti, e| { + ) -> EyreResult { + let nr = self.create_node_ref(outer_self, &TypedKeySet::from(node_id), |_rti, e| { // this node is live because it literally just connected to us e.touch_last_seen(timestamp); - }); - if let Some(nr) = &out { - // set the most recent node address for connection finding and udp replies - nr.locked_mut(self) - .set_last_connection(descriptor, timestamp); - } - out + })?; + // set the most recent node address for connection finding and udp replies + nr.locked_mut(self) + .set_last_connection(descriptor, timestamp); + Ok(nr) } ////////////////////////////////////////////////////////////////////// diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs index 25a51416..f811e4aa 100644 --- a/veilid-core/src/routing_table/tasks/bootstrap.rs +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -259,19 +259,27 @@ impl RoutingTable { // Got peer info, let's add it to the routing table for pi in peer_info { // Register the node - if let Some(nr) = - self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, false) - { - // Add this our futures to process in parallel - for crypto_kind in VALID_CRYPTO_KINDS { - let routing_table = self.clone(); - let nr = nr.clone(); - unord.push( - // lets ask bootstrap to find ourselves now - async move { routing_table.reverse_find_node(crypto_kind, nr, true).await } - .instrument(Span::current()), - ); + let nr = match self.register_node_with_peer_info( + RoutingDomain::PublicInternet, + pi, + false, + ) { + Ok(nr) => nr, + Err(e) => { + log_rtab!(error "failed to register direct bootstrap peer info: {}", e); + continue; } + }; + + // Add this our futures to process in parallel + for crypto_kind in VALID_CRYPTO_KINDS { + let routing_table = self.clone(); + let nr = nr.clone(); + unord.push( + // lets ask bootstrap to find ourselves now + async move { routing_table.reverse_find_node(crypto_kind, nr, true).await } + .instrument(Span::current()), + ); } } } @@ -341,44 +349,46 @@ impl RoutingTable { let pi = PeerInfo::new(bsrec.node_ids, sni); - if let Some(nr) = - self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true) - { - // Add this our futures to process in parallel - for crypto_kind in VALID_CRYPTO_KINDS { - // Do we need to bootstrap this crypto kind? - let eckey = (RoutingDomain::PublicInternet, crypto_kind); - let cnt = entry_count.get(&eckey).copied().unwrap_or_default(); - if cnt != 0 { + let nr = + match self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true) { + Ok(nr) => nr, + Err(e) => { + log_rtab!(error "failed to register bootstrap peer info: {}", e); continue; } - - // Bootstrap this crypto kind - let nr = nr.clone(); - let routing_table = self.clone(); - unord.push( - async move { - // Need VALID signed peer info, so ask bootstrap to find_node of itself - // which will ensure it has the bootstrap's signed peer info as part of the response - let _ = routing_table.find_target(crypto_kind, nr.clone()).await; - - // Ensure we got the signed peer info - if !nr - .signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) - { - log_rtab!(warn - "bootstrap at {:?} did not return valid signed node info", - nr - ); - // If this node info is invalid, it will time out after being unpingable - } else { - // otherwise this bootstrap is valid, lets ask it to find ourselves now - routing_table.reverse_find_node(crypto_kind, nr, true).await - } - } - .instrument(Span::current()), - ); + }; + // Add this our futures to process in parallel + for crypto_kind in VALID_CRYPTO_KINDS { + // Do we need to bootstrap this crypto kind? + let eckey = (RoutingDomain::PublicInternet, crypto_kind); + let cnt = entry_count.get(&eckey).copied().unwrap_or_default(); + if cnt != 0 { + continue; } + + // Bootstrap this crypto kind + let nr = nr.clone(); + let routing_table = self.clone(); + unord.push( + async move { + // Need VALID signed peer info, so ask bootstrap to find_node of itself + // which will ensure it has the bootstrap's signed peer info as part of the response + let _ = routing_table.find_target(crypto_kind, nr.clone()).await; + + // Ensure we got the signed peer info + if !nr.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) { + log_rtab!(warn + "bootstrap at {:?} did not return valid signed node info", + nr + ); + // If this node info is invalid, it will time out after being unpingable + } else { + // otherwise this bootstrap is valid, lets ask it to find ourselves now + routing_table.reverse_find_node(crypto_kind, nr, true).await + } + } + .instrument(Span::current()), + ); } } diff --git a/veilid-core/src/routing_table/tasks/relay_management.rs b/veilid-core/src/routing_table/tasks/relay_management.rs index 3bc93145..8d980c04 100644 --- a/veilid-core/src/routing_table/tasks/relay_management.rs +++ b/veilid-core/src/routing_table/tasks/relay_management.rs @@ -51,14 +51,19 @@ impl RoutingTable { // The outbound relay is the host of the PWA if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await { // Register new outbound relay - if let Some(nr) = self.register_node_with_peer_info( + match self.register_node_with_peer_info( RoutingDomain::PublicInternet, outbound_relay_peerinfo, false, ) { - info!("Outbound relay node selected: {}", nr); - editor.set_relay_node(nr); - got_outbound_relay = true; + Ok(nr) => { + log_rtab!("Outbound relay node selected: {}", nr); + editor.set_relay_node(nr); + got_outbound_relay = true; + } + Err(e) => { + log_rtab!(error "failed to register node with peer info: {}", e); + } } } } diff --git a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs index c8202de2..5f06866e 100644 --- a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs +++ b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs @@ -1,9 +1,9 @@ use crate::*; - -const SERIALIZED_PEERINFO: &str = r###"{"node_ids":["FAKE:eFOfgm_FNZBsTRi7KAESNwYFAUGgX2uDrTRWAL8ucjM"],"signed_node_info":{"Direct":{"node_info":{"network_class":"InboundCapable","outbound_protocols":1,"address_types":3,"envelope_support":[0],"crypto_support":[[86,76,68,48]],"dial_info_detail_list":[{"class":"Direct","dial_info":{"kind":"UDP","socket_address":{"address":{"IPV4":"1.2.3.4"},"port":5150}}},{"class":"Direct","dial_info":{"kind":"UDP","socket_address":{"address":{"IPV6":"bad:cafe::1"},"port":5150}}},{"class":"Direct","dial_info":{"kind":"TCP","socket_address":{"address":{"IPV4":"5.6.7.8"},"port":5150}}},{"class":"Direct","dial_info":{"kind":"TCP","socket_address":{"address":{"IPV6":"bad:cafe::1"},"port":5150}}},{"class":"Direct","dial_info":{"kind":"WS","socket_address":{"address":{"IPV4":"9.10.11.12"},"port":5150},"request":"bootstrap-1.dev.veilid.net:5150/ws"}},{"class":"Direct","dial_info":{"kind":"WS","socket_address":{"address":{"IPV6":"bad:cafe::1"},"port":5150},"request":"bootstrap-1.dev.veilid.net:5150/ws"}}]},"timestamp":1685058646770389,"signatures":[]}}}"###; +use routing_table::*; fn fake_routing_table() -> routing_table::RoutingTable { let veilid_config = VeilidConfig::new(); + #[cfg(feature = "unstable-blockstore")] let block_store = BlockStore::new(veilid_config.clone()); let protected_store = ProtectedStore::new(veilid_config.clone()); let table_store = TableStore::new(veilid_config.clone(), protected_store.clone()); @@ -11,8 +11,8 @@ fn fake_routing_table() -> routing_table::RoutingTable { let storage_manager = storage_manager::StorageManager::new( veilid_config.clone(), crypto.clone(), - protected_store.clone(), table_store.clone(), + #[cfg(feature = "unstable-blockstore")] block_store.clone(), ); let network_manager = network_manager::NetworkManager::new( @@ -20,10 +20,11 @@ fn fake_routing_table() -> routing_table::RoutingTable { storage_manager, protected_store.clone(), table_store.clone(), + #[cfg(feature = "unstable-blockstore")] block_store.clone(), crypto.clone(), ); - routing_table::RoutingTable::new(network_manager) + RoutingTable::new(network_manager) } pub async fn test_routingtable_buckets_round_trip() { @@ -82,11 +83,35 @@ pub async fn test_routingtable_buckets_round_trip() { } pub async fn test_round_trip_peerinfo() { - let pi: routing_table::PeerInfo = deserialize_json(SERIALIZED_PEERINFO).unwrap(); + let mut tks = TypedKeySet::new(); + tks.add(TypedKey::new( + CRYPTO_KIND_VLD0, + CryptoKey::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]), + )); + let pi: PeerInfo = PeerInfo::new( + tks, + SignedNodeInfo::Direct(SignedDirectNodeInfo::new( + NodeInfo::new( + NetworkClass::OutboundOnly, + ProtocolTypeSet::new(), + AddressTypeSet::new(), + vec![0], + vec![CRYPTO_KIND_VLD0], + vec![], + ), + Timestamp::new(0), + Vec::new(), + )), + ); + let s = serialize_json(&pi); + let pi2 = deserialize_json(&s).expect("Should deserialize"); + let s2 = serialize_json(&pi2); - let back = serialize_json(pi); - - assert_eq!(SERIALIZED_PEERINFO, back); + assert_eq!(pi, pi2); + assert_eq!(s, s2); } pub async fn test_all() { diff --git a/veilid-core/src/routing_table/types/node_info.rs b/veilid-core/src/routing_table/types/node_info.rs index ca5cbde8..e67f4711 100644 --- a/veilid-core/src/routing_table/types/node_info.rs +++ b/veilid-core/src/routing_table/types/node_info.rs @@ -1,7 +1,16 @@ use super::*; #[derive( - Clone, Default, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Clone, + Default, + PartialEq, + Eq, + Debug, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct NodeInfo { diff --git a/veilid-core/src/routing_table/types/peer_info.rs b/veilid-core/src/routing_table/types/peer_info.rs index b7037646..e3037459 100644 --- a/veilid-core/src/routing_table/types/peer_info.rs +++ b/veilid-core/src/routing_table/types/peer_info.rs @@ -1,6 +1,8 @@ use super::*; -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[derive( + Clone, Debug, Serialize, Deserialize, PartialEq, Eq, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] #[archive_attr(repr(C), derive(CheckBytes))] pub struct PeerInfo { node_ids: TypedKeySet, diff --git a/veilid-core/src/routing_table/types/signed_direct_node_info.rs b/veilid-core/src/routing_table/types/signed_direct_node_info.rs index e6fee40d..32efb8de 100644 --- a/veilid-core/src/routing_table/types/signed_direct_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_direct_node_info.rs @@ -1,7 +1,9 @@ use super::*; /// Signed NodeInfo that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] #[archive_attr(repr(C), derive(CheckBytes))] pub struct SignedDirectNodeInfo { node_info: NodeInfo, diff --git a/veilid-core/src/routing_table/types/signed_node_info.rs b/veilid-core/src/routing_table/types/signed_node_info.rs index 5557a76a..60e99c89 100644 --- a/veilid-core/src/routing_table/types/signed_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_node_info.rs @@ -1,6 +1,8 @@ use super::*; -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum SignedNodeInfo { Direct(SignedDirectNodeInfo), diff --git a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs index 8a429e4c..cdfda702 100644 --- a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs @@ -1,7 +1,9 @@ use super::*; /// Signed NodeInfo with a relay that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] #[archive_attr(repr(C), derive(CheckBytes))] pub struct SignedRelayedNodeInfo { node_info: NodeInfo, diff --git a/veilid-core/src/rpc_processor/coders/mod.rs b/veilid-core/src/rpc_processor/coders/mod.rs index 34bc7913..4f76486a 100644 --- a/veilid-core/src/rpc_processor/coders/mod.rs +++ b/veilid-core/src/rpc_processor/coders/mod.rs @@ -22,6 +22,7 @@ mod signed_relayed_node_info; mod signed_value_data; mod signed_value_descriptor; mod socket_address; +#[cfg(feature = "unstable-tunnels")] mod tunnel; mod typed_key; mod typed_signature; @@ -50,6 +51,7 @@ pub use signed_relayed_node_info::*; pub use signed_value_data::*; pub use signed_value_descriptor::*; pub use socket_address::*; +#[cfg(feature = "unstable-tunnels")] pub use tunnel::*; pub use typed_key::*; pub use typed_signature::*; diff --git a/veilid-core/src/rpc_processor/coders/operations/answer.rs b/veilid-core/src/rpc_processor/coders/operations/answer.rs index 9f01da68..356d1ba5 100644 --- a/veilid-core/src/rpc_processor/coders/operations/answer.rs +++ b/veilid-core/src/rpc_processor/coders/operations/answer.rs @@ -37,10 +37,15 @@ pub enum RPCAnswerDetail { GetValueA(RPCOperationGetValueA), SetValueA(RPCOperationSetValueA), WatchValueA(RPCOperationWatchValueA), + #[cfg(feature = "unstable-blockstore")] SupplyBlockA(RPCOperationSupplyBlockA), + #[cfg(feature = "unstable-blockstore")] FindBlockA(RPCOperationFindBlockA), + #[cfg(feature = "unstable-tunnels")] StartTunnelA(RPCOperationStartTunnelA), + #[cfg(feature = "unstable-tunnels")] CompleteTunnelA(RPCOperationCompleteTunnelA), + #[cfg(feature = "unstable-tunnels")] CancelTunnelA(RPCOperationCancelTunnelA), } @@ -53,10 +58,15 @@ impl RPCAnswerDetail { RPCAnswerDetail::GetValueA(_) => "GetValueA", RPCAnswerDetail::SetValueA(_) => "SetValueA", RPCAnswerDetail::WatchValueA(_) => "WatchValueA", + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::SupplyBlockA(_) => "SupplyBlockA", + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::FindBlockA(_) => "FindBlockA", + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::StartTunnelA(_) => "StartTunnelA", + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CompleteTunnelA(_) => "CompleteTunnelA", + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CancelTunnelA(_) => "CancelTunnelA", } } @@ -68,10 +78,15 @@ impl RPCAnswerDetail { RPCAnswerDetail::GetValueA(r) => r.validate(validate_context), RPCAnswerDetail::SetValueA(r) => r.validate(validate_context), RPCAnswerDetail::WatchValueA(r) => r.validate(validate_context), + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::SupplyBlockA(r) => r.validate(validate_context), + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::FindBlockA(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::StartTunnelA(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CompleteTunnelA(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CancelTunnelA(r) => r.validate(validate_context), } } @@ -110,26 +125,31 @@ impl RPCAnswerDetail { let out = RPCOperationWatchValueA::decode(&op_reader)?; RPCAnswerDetail::WatchValueA(out) } + #[cfg(feature = "unstable-blockstore")] veilid_capnp::answer::detail::SupplyBlockA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSupplyBlockA::decode(&op_reader)?; RPCAnswerDetail::SupplyBlockA(out) } + #[cfg(feature = "unstable-blockstore")] veilid_capnp::answer::detail::FindBlockA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindBlockA::decode(&op_reader)?; RPCAnswerDetail::FindBlockA(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::StartTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStartTunnelA::decode(&op_reader)?; RPCAnswerDetail::StartTunnelA(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::CompleteTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCompleteTunnelA::decode(&op_reader)?; RPCAnswerDetail::CompleteTunnelA(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::answer::detail::CancelTunnelA(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCancelTunnelA::decode(&op_reader)?; @@ -151,16 +171,21 @@ impl RPCAnswerDetail { RPCAnswerDetail::WatchValueA(d) => { d.encode(&mut builder.reborrow().init_watch_value_a()) } + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::SupplyBlockA(d) => { d.encode(&mut builder.reborrow().init_supply_block_a()) } + #[cfg(feature = "unstable-blockstore")] RPCAnswerDetail::FindBlockA(d) => d.encode(&mut builder.reborrow().init_find_block_a()), + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::StartTunnelA(d) => { d.encode(&mut builder.reborrow().init_start_tunnel_a()) } + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CompleteTunnelA(d) => { d.encode(&mut builder.reborrow().init_complete_tunnel_a()) } + #[cfg(feature = "unstable-tunnels")] RPCAnswerDetail::CancelTunnelA(d) => { d.encode(&mut builder.reborrow().init_cancel_tunnel_a()) } diff --git a/veilid-core/src/rpc_processor/coders/operations/mod.rs b/veilid-core/src/rpc_processor/coders/operations/mod.rs index 7caf0fb2..72f4c520 100644 --- a/veilid-core/src/rpc_processor/coders/operations/mod.rs +++ b/veilid-core/src/rpc_processor/coders/operations/mod.rs @@ -2,18 +2,14 @@ mod answer; mod operation; mod operation_app_call; mod operation_app_message; -mod operation_cancel_tunnel; -mod operation_complete_tunnel; -mod operation_find_block; mod operation_find_node; mod operation_get_value; mod operation_return_receipt; mod operation_route; mod operation_set_value; mod operation_signal; -mod operation_start_tunnel; mod operation_status; -mod operation_supply_block; + mod operation_validate_dial_info; mod operation_value_changed; mod operation_watch_value; @@ -21,22 +17,29 @@ mod question; mod respond_to; mod statement; +#[cfg(feature = "unstable-blockstore")] +mod operation_find_block; +#[cfg(feature = "unstable-blockstore")] +mod operation_supply_block; + +#[cfg(feature = "unstable-tunnels")] +mod operation_cancel_tunnel; +#[cfg(feature = "unstable-tunnels")] +mod operation_complete_tunnel; +#[cfg(feature = "unstable-tunnels")] +mod operation_start_tunnel; + pub use answer::*; pub use operation::*; pub use operation_app_call::*; pub use operation_app_message::*; -pub use operation_cancel_tunnel::*; -pub use operation_complete_tunnel::*; -pub use operation_find_block::*; pub use operation_find_node::*; pub use operation_get_value::*; pub use operation_return_receipt::*; pub use operation_route::*; pub use operation_set_value::*; pub use operation_signal::*; -pub use operation_start_tunnel::*; pub use operation_status::*; -pub use operation_supply_block::*; pub use operation_validate_dial_info::*; pub use operation_value_changed::*; pub use operation_watch_value::*; @@ -44,4 +47,16 @@ pub use question::*; pub use respond_to::*; pub use statement::*; +#[cfg(feature = "unstable-blockstore")] +pub use operation_find_block::*; +#[cfg(feature = "unstable-blockstore")] +pub use operation_supply_block::*; + +#[cfg(feature = "unstable-tunnels")] +pub use operation_cancel_tunnel::*; +#[cfg(feature = "unstable-tunnels")] +pub use operation_complete_tunnel::*; +#[cfg(feature = "unstable-tunnels")] +pub use operation_start_tunnel::*; + use super::*; diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs index 4bb6ac06..c7254421 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs @@ -1,5 +1,6 @@ use super::*; +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub struct RPCOperationCancelTunnelQ { id: TunnelId, @@ -37,6 +38,7 @@ impl RPCOperationCancelTunnelQ { } } +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub enum RPCOperationCancelTunnelA { Tunnel(TunnelId), diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs index 46b0258a..f699d613 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs @@ -1,5 +1,6 @@ use super::*; +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub struct RPCOperationCompleteTunnelQ { id: TunnelId, @@ -74,6 +75,7 @@ impl RPCOperationCompleteTunnelQ { } } +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub enum RPCOperationCompleteTunnelA { Tunnel(FullTunnel), diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs index b3741462..5df5d78a 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs @@ -1,5 +1,6 @@ use super::*; +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub struct RPCOperationStartTunnelQ { id: TunnelId, @@ -64,6 +65,7 @@ impl RPCOperationStartTunnelQ { } } +#[cfg(feature = "unstable-tunnels")] #[derive(Debug, Clone)] pub enum RPCOperationStartTunnelA { Partial(PartialTunnel), diff --git a/veilid-core/src/rpc_processor/coders/operations/question.rs b/veilid-core/src/rpc_processor/coders/operations/question.rs index f0f2c056..12591220 100644 --- a/veilid-core/src/rpc_processor/coders/operations/question.rs +++ b/veilid-core/src/rpc_processor/coders/operations/question.rs @@ -49,10 +49,15 @@ pub enum RPCQuestionDetail { GetValueQ(RPCOperationGetValueQ), SetValueQ(RPCOperationSetValueQ), WatchValueQ(RPCOperationWatchValueQ), + #[cfg(feature = "unstable-blockstore")] SupplyBlockQ(RPCOperationSupplyBlockQ), + #[cfg(feature = "unstable-blockstore")] FindBlockQ(RPCOperationFindBlockQ), + #[cfg(feature = "unstable-tunnels")] StartTunnelQ(RPCOperationStartTunnelQ), + #[cfg(feature = "unstable-tunnels")] CompleteTunnelQ(RPCOperationCompleteTunnelQ), + #[cfg(feature = "unstable-tunnels")] CancelTunnelQ(RPCOperationCancelTunnelQ), } @@ -65,10 +70,15 @@ impl RPCQuestionDetail { RPCQuestionDetail::GetValueQ(_) => "GetValueQ", RPCQuestionDetail::SetValueQ(_) => "SetValueQ", RPCQuestionDetail::WatchValueQ(_) => "WatchValueQ", + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::SupplyBlockQ(_) => "SupplyBlockQ", + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::FindBlockQ(_) => "FindBlockQ", + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::StartTunnelQ(_) => "StartTunnelQ", + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CompleteTunnelQ(_) => "CompleteTunnelQ", + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CancelTunnelQ(_) => "CancelTunnelQ", } } @@ -80,10 +90,15 @@ impl RPCQuestionDetail { RPCQuestionDetail::GetValueQ(r) => r.validate(validate_context), RPCQuestionDetail::SetValueQ(r) => r.validate(validate_context), RPCQuestionDetail::WatchValueQ(r) => r.validate(validate_context), + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::SupplyBlockQ(r) => r.validate(validate_context), + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::FindBlockQ(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::StartTunnelQ(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CompleteTunnelQ(r) => r.validate(validate_context), + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CancelTunnelQ(r) => r.validate(validate_context), } } @@ -123,26 +138,31 @@ impl RPCQuestionDetail { let out = RPCOperationWatchValueQ::decode(&op_reader)?; RPCQuestionDetail::WatchValueQ(out) } + #[cfg(feature = "unstable-blockstore")] veilid_capnp::question::detail::SupplyBlockQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationSupplyBlockQ::decode(&op_reader)?; RPCQuestionDetail::SupplyBlockQ(out) } + #[cfg(feature = "unstable-blockstore")] veilid_capnp::question::detail::FindBlockQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationFindBlockQ::decode(&op_reader)?; RPCQuestionDetail::FindBlockQ(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::StartTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationStartTunnelQ::decode(&op_reader)?; RPCQuestionDetail::StartTunnelQ(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::CompleteTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCompleteTunnelQ::decode(&op_reader)?; RPCQuestionDetail::CompleteTunnelQ(out) } + #[cfg(feature = "unstable-tunnels")] veilid_capnp::question::detail::CancelTunnelQ(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationCancelTunnelQ::decode(&op_reader)?; @@ -164,18 +184,23 @@ impl RPCQuestionDetail { RPCQuestionDetail::WatchValueQ(d) => { d.encode(&mut builder.reborrow().init_watch_value_q()) } + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::SupplyBlockQ(d) => { d.encode(&mut builder.reborrow().init_supply_block_q()) } + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::FindBlockQ(d) => { d.encode(&mut builder.reborrow().init_find_block_q()) } + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::StartTunnelQ(d) => { d.encode(&mut builder.reborrow().init_start_tunnel_q()) } + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CompleteTunnelQ(d) => { d.encode(&mut builder.reborrow().init_complete_tunnel_q()) } + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CancelTunnelQ(d) => { d.encode(&mut builder.reborrow().init_cancel_tunnel_q()) } diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index ae19a4b0..e30052c8 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -312,7 +312,12 @@ impl RPCProcessor { NetworkResult::value(Destination::direct(peer_noderef)) } else { // Look up the sender node, we should have added it via senderNodeInfo before getting here. - if let Some(sender_noderef) = self.routing_table.lookup_node_ref(sender_node_id) { + let res = match self.routing_table.lookup_node_ref(sender_node_id) { + Ok(v) => v, + Err(e) => return NetworkResult::invalid_message( + format!("failed to look up node info for respond to: {}", e) + )}; + if let Some(sender_noderef) = res { NetworkResult::value(Destination::relay(peer_noderef, sender_noderef)) } else { return NetworkResult::invalid_message( diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index c526c77f..805b62e4 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -4,23 +4,30 @@ mod fanout_call; mod operation_waiter; mod rpc_app_call; mod rpc_app_message; -mod rpc_cancel_tunnel; -mod rpc_complete_tunnel; mod rpc_error; -mod rpc_find_block; mod rpc_find_node; mod rpc_get_value; mod rpc_return_receipt; mod rpc_route; mod rpc_set_value; mod rpc_signal; -mod rpc_start_tunnel; mod rpc_status; -mod rpc_supply_block; mod rpc_validate_dial_info; mod rpc_value_changed; mod rpc_watch_value; +#[cfg(feature = "unstable-blockstore")] +mod rpc_find_block; +#[cfg(feature = "unstable-blockstore")] +mod rpc_supply_block; + +#[cfg(feature = "unstable-tunnels")] +mod rpc_cancel_tunnel; +#[cfg(feature = "unstable-tunnels")] +mod rpc_complete_tunnel; +#[cfg(feature = "unstable-tunnels")] +mod rpc_start_tunnel; + pub use coders::*; pub use destination::*; pub use fanout_call::*; @@ -473,7 +480,10 @@ impl RPCProcessor { let routing_table = this.routing_table(); // First see if we have the node in our routing table already - if let Some(nr) = routing_table.lookup_node_ref(node_id) { + if let Some(nr) = routing_table + .lookup_node_ref(node_id) + .map_err(RPCError::internal)? + { // ensure we have some dial info for the entry already, // if not, we should do the find_node anyway if nr.has_any_dial_info() { @@ -837,6 +847,10 @@ impl RPCProcessor { // Record for node if this was not sent via a route if safety_route.is_none() && remote_private_route.is_none() { node_ref.stats_failed_to_send(send_ts, wants_answer); + + // Also clear the last_connections for the entry so we make a new connection next time + node_ref.clear_last_connections(); + return; } @@ -865,6 +879,10 @@ impl RPCProcessor { // Record for node if this was not sent via a route if safety_route.is_none() && remote_private_route.is_none() { node_ref.stats_question_lost(); + + // Also clear the last_connections for the entry so we make a new connection next time + node_ref.clear_last_connections(); + return; } // Get route spec store @@ -1331,20 +1349,26 @@ impl RPCProcessor { // Sender PeerInfo was specified, update our routing table with it if !self.filter_node_info(routing_domain, sender_peer_info.signed_node_info()) { - return Err(RPCError::invalid_format( + return Ok(NetworkResult::invalid_message( "sender peerinfo has invalid peer scope", )); } - opt_sender_nr = self.routing_table().register_node_with_peer_info( + opt_sender_nr = match self.routing_table().register_node_with_peer_info( routing_domain, sender_peer_info.clone(), false, - ); + ) { + Ok(v) => Some(v), + Err(e) => return Ok(NetworkResult::invalid_message(e)), + } } // look up sender node, in case it's different than our peer due to relaying if opt_sender_nr.is_none() { - opt_sender_nr = self.routing_table().lookup_node_ref(sender_node_id) + opt_sender_nr = match self.routing_table().lookup_node_ref(sender_node_id) { + Ok(v) => v, + Err(e) => return Ok(NetworkResult::invalid_message(e)), + } } // Update the 'seen our node info' timestamp to determine if this node needs a @@ -1408,10 +1432,15 @@ impl RPCProcessor { RPCQuestionDetail::GetValueQ(_) => self.process_get_value_q(msg).await, RPCQuestionDetail::SetValueQ(_) => self.process_set_value_q(msg).await, RPCQuestionDetail::WatchValueQ(_) => self.process_watch_value_q(msg).await, + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::SupplyBlockQ(_) => self.process_supply_block_q(msg).await, + #[cfg(feature = "unstable-blockstore")] RPCQuestionDetail::FindBlockQ(_) => self.process_find_block_q(msg).await, + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::StartTunnelQ(_) => self.process_start_tunnel_q(msg).await, + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CompleteTunnelQ(_) => self.process_complete_tunnel_q(msg).await, + #[cfg(feature = "unstable-tunnels")] RPCQuestionDetail::CancelTunnelQ(_) => self.process_cancel_tunnel_q(msg).await, }, RPCOperationKind::Statement(s) => match s.detail() { diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index 80919d4c..55399a0a 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -99,10 +99,14 @@ impl RPCProcessor { } /// Exposed to API for apps to return app call answers - pub async fn app_call_reply(&self, id: OperationId, message: Vec) -> Result<(), RPCError> { + pub async fn app_call_reply( + &self, + call_id: OperationId, + message: Vec, + ) -> Result<(), RPCError> { self.unlocked_inner .waiting_app_call_table - .complete_op_waiter(id, message) + .complete_op_waiter(call_id, message) .await } } diff --git a/veilid-core/src/rpc_processor/rpc_status.rs b/veilid-core/src/rpc_processor/rpc_status.rs index f45ea125..179508ce 100644 --- a/veilid-core/src/rpc_processor/rpc_status.rs +++ b/veilid-core/src/rpc_processor/rpc_status.rs @@ -30,9 +30,19 @@ impl RPCProcessor { let routing_domain = match target.best_routing_domain() { Some(rd) => rd, None => { + // Because this exits before calling 'question()', + // a failure to find a routing domain constitutes a send failure + let send_ts = get_aligned_timestamp(); + self.record_send_failure( + RPCKind::Question, + send_ts, + target.clone(), + None, + None, + ); return Ok(NetworkResult::no_connection_other( "no routing domain for target", - )) + )); } }; (Some(target.clone()), routing_domain) @@ -45,9 +55,26 @@ impl RPCProcessor { let routing_domain = match relay.best_routing_domain() { Some(rd) => rd, None => { + // Because this exits before calling 'question()', + // a failure to find a routing domain constitutes a send failure for both the target and its relay + let send_ts = get_aligned_timestamp(); + self.record_send_failure( + RPCKind::Question, + send_ts, + relay.clone(), + None, + None, + ); + self.record_send_failure( + RPCKind::Question, + send_ts, + target.clone(), + None, + None, + ); return Ok(NetworkResult::no_connection_other( "no routing domain for peer", - )) + )); } }; (Some(target.clone()), routing_domain) diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index 96f772d2..64a42530 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -28,8 +28,8 @@ const FLUSH_RECORD_STORES_INTERVAL_SECS: u32 = 1; struct StorageManagerUnlockedInner { config: VeilidConfig, crypto: Crypto, - protected_store: ProtectedStore, table_store: TableStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, // Background processes @@ -46,15 +46,14 @@ impl StorageManager { fn new_unlocked_inner( config: VeilidConfig, crypto: Crypto, - protected_store: ProtectedStore, table_store: TableStore, - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, ) -> StorageManagerUnlockedInner { StorageManagerUnlockedInner { config, crypto, - protected_store, table_store, + #[cfg(feature = "unstable-blockstore")] block_store, flush_record_stores_task: TickTask::new(FLUSH_RECORD_STORES_INTERVAL_SECS), } @@ -66,15 +65,14 @@ impl StorageManager { pub fn new( config: VeilidConfig, crypto: Crypto, - protected_store: ProtectedStore, table_store: TableStore, - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, ) -> StorageManager { let unlocked_inner = Arc::new(Self::new_unlocked_inner( config, crypto, - protected_store, table_store, + #[cfg(feature = "unstable-blockstore")] block_store, )); let this = StorageManager { diff --git a/veilid-core/src/veilid_api/api.rs b/veilid-core/src/veilid_api/api.rs index ab40f11f..e694f111 100644 --- a/veilid-core/src/veilid_api/api.rs +++ b/veilid-core/src/veilid_api/api.rs @@ -70,6 +70,7 @@ impl VeilidAPI { } Err(VeilidAPIError::not_initialized()) } + #[cfg(feature = "unstable-blockstore")] pub fn block_store(&self) -> VeilidAPIResult { let inner = self.inner.lock(); if let Some(context) = &inner.context { @@ -257,10 +258,14 @@ impl VeilidAPI { // App Calls #[instrument(level = "debug", skip(self))] - pub async fn app_call_reply(&self, id: OperationId, message: Vec) -> VeilidAPIResult<()> { + pub async fn app_call_reply( + &self, + call_id: OperationId, + message: Vec, + ) -> VeilidAPIResult<()> { let rpc_processor = self.rpc_processor()?; rpc_processor - .app_call_reply(id, message) + .app_call_reply(call_id, message) .await .map_err(|e| e.into()) } @@ -268,6 +273,7 @@ impl VeilidAPI { //////////////////////////////////////////////////////////////// // Tunnel Building + #[cfg(feature = "unstable-tunnels")] #[instrument(level = "debug", err, skip(self))] pub async fn start_tunnel( &self, @@ -277,6 +283,7 @@ impl VeilidAPI { panic!("unimplemented"); } + #[cfg(feature = "unstable-tunnels")] #[instrument(level = "debug", err, skip(self))] pub async fn complete_tunnel( &self, @@ -287,6 +294,7 @@ impl VeilidAPI { panic!("unimplemented"); } + #[cfg(feature = "unstable-tunnels")] #[instrument(level = "debug", err, skip(self))] pub async fn cancel_tunnel(&self, _tunnel_id: TunnelId) -> VeilidAPIResult { panic!("unimplemented"); diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 7151b680..436eec3b 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -31,16 +31,22 @@ fn get_string(text: &str) -> Option { Some(text.to_owned()) } -fn get_route_id(rss: RouteSpecStore, allow_remote: bool) -> impl Fn(&str) -> Option { +fn get_route_id( + rss: RouteSpecStore, + allow_allocated: bool, + allow_remote: bool, +) -> impl Fn(&str) -> Option { return move |text: &str| { if text.is_empty() { return None; } match RouteId::from_str(text).ok() { Some(key) => { - let routes = rss.list_allocated_routes(|k, _| Some(*k)); - if routes.contains(&key) { - return Some(key); + if allow_allocated { + let routes = rss.list_allocated_routes(|k, _| Some(*k)); + if routes.contains(&key) { + return Some(key); + } } if allow_remote { let rroutes = rss.list_remote_routes(|k, _| Some(*k)); @@ -50,11 +56,13 @@ fn get_route_id(rss: RouteSpecStore, allow_remote: bool) -> impl Fn(&str) -> Opt } } None => { - let routes = rss.list_allocated_routes(|k, _| Some(*k)); - for r in routes { - let rkey = r.encode(); - if rkey.starts_with(text) { - return Some(r); + if allow_allocated { + let routes = rss.list_allocated_routes(|k, _| Some(*k)); + for r in routes { + let rkey = r.encode(); + if rkey.starts_with(text) { + return Some(r); + } } } if allow_remote { @@ -90,7 +98,7 @@ fn get_safety_selection(text: &str, routing_table: RoutingTable) -> Option impl FnOnce(&str) -> Option impl FnOnce(&str) -> Option 2 { @@ -747,7 +765,7 @@ impl VeilidAPI { 1, "debug_route", "route_id", - get_route_id(rss.clone(), false), + get_route_id(rss.clone(), true, false), )?; // Unpublish route @@ -769,7 +787,7 @@ impl VeilidAPI { 1, "debug_route", "route_id", - get_route_id(rss.clone(), true), + get_route_id(rss.clone(), true, true), )?; match rss.debug_route(&route_id) { @@ -831,7 +849,7 @@ impl VeilidAPI { 1, "debug_route", "route_id", - get_route_id(rss.clone(), true), + get_route_id(rss.clone(), true, true), )?; let success = rss @@ -916,7 +934,7 @@ impl VeilidAPI { entry nodeinfo config [key [new value]] - purge + purge attach detach restart network diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs index 778b4830..0eb7b791 100644 --- a/veilid-core/src/veilid_api/error.rs +++ b/veilid-core/src/veilid_api/error.rs @@ -117,6 +117,7 @@ macro_rules! apibail_already_initialized { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] #[serde(tag = "kind")] @@ -136,7 +137,10 @@ pub enum VeilidAPIError { #[error("No connection: {message}")] NoConnection { message: String }, #[error("Key not found: {key}")] - KeyNotFound { key: TypedKey }, + KeyNotFound { + #[schemars(with="String")] + key: TypedKey + }, #[error("Internal: {message}")] Internal { message: String }, #[error("Unimplemented: {message}")] diff --git a/veilid-core/src/veilid_api/json_api/crypto_system.rs b/veilid-core/src/veilid_api/json_api/crypto_system.rs new file mode 100644 index 00000000..5fa499c8 --- /dev/null +++ b/veilid-core/src/veilid_api/json_api/crypto_system.rs @@ -0,0 +1,228 @@ +use super::*; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct CryptoSystemRequest { + pub cs_id: u32, + #[serde(flatten)] + pub cs_op: CryptoSystemRequestOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct CryptoSystemResponse { + pub cs_id: u32, + #[serde(flatten)] + pub cs_op: CryptoSystemResponseOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "cs_op")] +pub enum CryptoSystemRequestOp { + Release, + CachedDh { + #[schemars(with = "String")] + key: PublicKey, + #[schemars(with = "String")] + secret: SecretKey, + }, + ComputeDh { + #[schemars(with = "String")] + key: PublicKey, + #[schemars(with = "String")] + secret: SecretKey, + }, + RandomBytes { + len: u32, + }, + DefaultSaltLength, + HashPassword { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + password: Vec, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + salt: Vec, + }, + VerifyPassword { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + password: Vec, + password_hash: String, + }, + DeriveSharedSecret { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + password: Vec, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + salt: Vec, + }, + RandomNonce, + RandomSharedSecret, + GenerateKeyPair, + GenerateHash { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + }, + ValidateKeyPair { + #[schemars(with = "String")] + key: PublicKey, + #[schemars(with = "String")] + secret: SecretKey, + }, + ValidateHash { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + #[schemars(with = "String")] + hash_digest: HashDigest, + }, + Distance { + #[schemars(with = "String")] + key1: CryptoKey, + #[schemars(with = "String")] + key2: CryptoKey, + }, + Sign { + #[schemars(with = "String")] + key: PublicKey, + #[schemars(with = "String")] + secret: SecretKey, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + }, + Verify { + #[schemars(with = "String")] + key: PublicKey, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + #[schemars(with = "String")] + secret: Signature, + }, + AeadOverhead, + DecryptAead { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + body: Vec, + #[schemars(with = "String")] + nonce: Nonce, + #[schemars(with = "String")] + shared_secret: SharedSecret, + #[serde(with = "opt_json_as_base64")] + #[schemars(with = "Option")] + associated_data: Option>, + }, + EncryptAead { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + body: Vec, + #[schemars(with = "String")] + nonce: Nonce, + #[schemars(with = "String")] + shared_secret: SharedSecret, + #[serde(with = "opt_json_as_base64")] + #[schemars(with = "Option")] + associated_data: Option>, + }, + CryptNoAuth { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + body: Vec, + #[schemars(with = "String")] + nonce: Nonce, + #[schemars(with = "String")] + shared_secret: SharedSecret, + }, +} +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "cs_op")] +pub enum CryptoSystemResponseOp { + InvalidId, + Release, + CachedDh { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + ComputeDh { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + RandomBytes { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, + }, + DefaultSaltLength { + value: u32, + }, + HashPassword { + #[serde(flatten)] + result: ApiResult, + }, + VerifyPassword { + #[serde(flatten)] + result: ApiResult, + }, + DeriveSharedSecret { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + RandomNonce { + #[schemars(with = "String")] + value: Nonce, + }, + RandomSharedSecret { + #[schemars(with = "String")] + value: SharedSecret, + }, + GenerateKeyPair { + #[schemars(with = "String")] + value: KeyPair, + }, + GenerateHash { + #[schemars(with = "String")] + value: HashDigest, + }, + ValidateKeyPair { + value: bool, + }, + ValidateHash { + value: bool, + }, + Distance { + #[schemars(with = "String")] + value: CryptoKeyDistance, + }, + Sign { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + Verify { + #[serde(flatten)] + result: ApiResult<()>, + }, + AeadOverhead { + value: u32, + }, + DecryptAead { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithVecU8, + }, + EncryptAead { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithVecU8, + }, + CryptNoAuth { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, + }, +} diff --git a/veilid-core/src/veilid_api/json_api/mod.rs b/veilid-core/src/veilid_api/json_api/mod.rs new file mode 100644 index 00000000..4e0c4bde --- /dev/null +++ b/veilid-core/src/veilid_api/json_api/mod.rs @@ -0,0 +1,319 @@ +use super::*; + +mod routing_context; +pub use routing_context::*; + +mod table_db; +pub use table_db::*; + +mod crypto_system; +pub use crypto_system::*; + +mod process; +pub use process::*; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Request { + /// Operation Id (pairs with Response, or empty if unidirectional) + #[serde(default)] + pub id: u32, + /// The request operation variant + #[serde(flatten)] + pub op: RequestOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type")] +pub enum RecvMessage { + Response(Response), + Update(VeilidUpdate), +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Response { + /// Operation Id (pairs with Request, or empty if unidirectional) + #[serde(default)] + pub id: u32, + /// The response operation variant + #[serde(flatten)] + pub op: ResponseOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "op")] +pub enum RequestOp { + Control { + args: Vec, + }, + GetState, + Attach, + Detach, + NewPrivateRoute, + NewCustomPrivateRoute { + #[schemars(with = "Vec")] + kinds: Vec, + #[serde(default)] + stability: Stability, + #[serde(default)] + sequencing: Sequencing, + }, + ImportRemotePrivateRoute { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + blob: Vec, + }, + ReleasePrivateRoute { + #[schemars(with = "String")] + route_id: RouteId, + }, + AppCallReply { + #[schemars(with = "String")] + call_id: OperationId, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + message: Vec, + }, + // Routing Context + NewRoutingContext, + RoutingContext(RoutingContextRequest), + // TableDb + OpenTableDb { + name: String, + column_count: u32, + }, + DeleteTableDb { + name: String, + }, + TableDb(TableDbRequest), + TableDbTransaction(TableDbTransactionRequest), + // Crypto + GetCryptoSystem { + #[schemars(with = "String")] + kind: CryptoKind, + }, + BestCryptoSystem, + CryptoSystem(CryptoSystemRequest), + VerifySignatures { + #[schemars(with = "Vec")] + node_ids: Vec, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + #[schemars(with = "Vec")] + signatures: Vec, + }, + GenerateSignatures { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + #[schemars(with = "Vec")] + key_pairs: Vec, + }, + GenerateKeyPair { + #[schemars(with = "String")] + kind: CryptoKind, + }, + // Misc + Now, + Debug { + command: String, + }, + VeilidVersionString, + VeilidVersion, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct NewPrivateRouteResult { + #[schemars(with = "String")] + route_id: RouteId, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + blob: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "op")] +pub enum ResponseOp { + Control { + #[serde(flatten)] + result: ApiResult, + }, + GetState { + #[serde(flatten)] + result: ApiResult, + }, + Attach { + #[serde(flatten)] + result: ApiResult<()>, + }, + Detach { + #[serde(flatten)] + result: ApiResult<()>, + }, + NewPrivateRoute { + #[serde(flatten)] + result: ApiResult, + }, + NewCustomPrivateRoute { + #[serde(flatten)] + result: ApiResult, + }, + ImportRemotePrivateRoute { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + ReleasePrivateRoute { + #[serde(flatten)] + result: ApiResult<()>, + }, + AppCallReply { + #[serde(flatten)] + result: ApiResult<()>, + }, + // Routing Context + NewRoutingContext { + value: u32, + }, + RoutingContext(RoutingContextResponse), + // TableDb + OpenTableDb { + #[serde(flatten)] + result: ApiResult, + }, + DeleteTableDb { + #[serde(flatten)] + result: ApiResult, + }, + TableDb(TableDbResponse), + TableDbTransaction(TableDbTransactionResponse), + // Crypto + GetCryptoSystem { + #[serde(flatten)] + result: ApiResult, + }, + BestCryptoSystem { + #[serde(flatten)] + result: ApiResult, + }, + CryptoSystem(CryptoSystemResponse), + VerifySignatures { + #[serde(flatten)] + #[schemars(with = "ApiResult>")] + result: ApiResultWithVecString, + }, + GenerateSignatures { + #[serde(flatten)] + #[schemars(with = "ApiResult>")] + result: ApiResultWithVecString>, + }, + GenerateKeyPair { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithString, + }, + // Misc + Now { + #[schemars(with = "String")] + value: Timestamp, + }, + Debug { + #[serde(flatten)] + result: ApiResult, + }, + VeilidVersionString { + value: String, + }, + VeilidVersion { + major: u32, + minor: u32, + patch: u32, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResult +where + T: Clone + fmt::Debug + JsonSchema, +{ + Ok { value: T }, + Err { error: VeilidAPIError }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResultWithString +where + T: Clone + fmt::Debug, +{ + Ok { + #[schemars(with = "String")] + value: T, + }, + Err { + error: VeilidAPIError, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResultWithVecU8 { + Ok { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, + }, + Err { + error: VeilidAPIError, + }, +} +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +pub struct VecU8 { + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResultWithVecVecU8 { + Ok { + #[schemars(with = "Vec")] + value: Vec, + }, + Err { + error: VeilidAPIError, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResultWithVecString +where + T: Clone + fmt::Debug, +{ + Ok { + #[schemars(with = "Vec")] + value: T, + }, + Err { + error: VeilidAPIError, + }, +} + +pub fn emit_schemas(out: &mut HashMap) { + let schema_request = schema_for!(Request); + let schema_recv_message = schema_for!(RecvMessage); + + out.insert( + "Request".to_owned(), + serde_json::to_string_pretty(&schema_request).unwrap(), + ); + + out.insert( + "RecvMessage".to_owned(), + serde_json::to_string_pretty(&schema_recv_message).unwrap(), + ); +} diff --git a/veilid-core/src/veilid_api/json_api/process.rs b/veilid-core/src/veilid_api/json_api/process.rs new file mode 100644 index 00000000..84eb97eb --- /dev/null +++ b/veilid-core/src/veilid_api/json_api/process.rs @@ -0,0 +1,774 @@ +use super::*; +use futures_util::FutureExt; + +pub fn to_json_api_result( + r: VeilidAPIResult, +) -> json_api::ApiResult { + match r { + Err(e) => json_api::ApiResult::Err { error: e }, + Ok(v) => json_api::ApiResult::Ok { value: v }, + } +} + +pub fn to_json_api_result_with_string( + r: VeilidAPIResult, +) -> json_api::ApiResultWithString { + match r { + Err(e) => json_api::ApiResultWithString::Err { error: e }, + Ok(v) => json_api::ApiResultWithString::Ok { value: v }, + } +} + +pub fn to_json_api_result_with_vec_string( + r: VeilidAPIResult, +) -> json_api::ApiResultWithVecString { + match r { + Err(e) => json_api::ApiResultWithVecString::Err { error: e }, + Ok(v) => json_api::ApiResultWithVecString::Ok { value: v }, + } +} + +pub fn to_json_api_result_with_vec_u8(r: VeilidAPIResult>) -> json_api::ApiResultWithVecU8 { + match r { + Err(e) => json_api::ApiResultWithVecU8::Err { error: e }, + Ok(v) => json_api::ApiResultWithVecU8::Ok { value: v }, + } +} + +pub fn to_json_api_result_with_vec_vec_u8( + r: VeilidAPIResult>>, +) -> json_api::ApiResultWithVecVecU8 { + match r { + Err(e) => json_api::ApiResultWithVecVecU8::Err { error: e }, + Ok(v) => json_api::ApiResultWithVecVecU8::Ok { + value: v.into_iter().map(|v| VecU8 { value: v }).collect(), + }, + } +} + +struct JsonRequestProcessorInner { + routing_contexts: BTreeMap, + table_dbs: BTreeMap, + table_db_transactions: BTreeMap, + crypto_systems: BTreeMap, +} + +#[derive(Clone)] +pub struct JsonRequestProcessor { + api: VeilidAPI, + inner: Arc>, +} + +impl JsonRequestProcessor { + pub fn new(api: VeilidAPI) -> Self { + Self { + api, + inner: Arc::new(Mutex::new(JsonRequestProcessorInner { + routing_contexts: Default::default(), + table_dbs: Default::default(), + table_db_transactions: Default::default(), + crypto_systems: Default::default(), + })), + } + } + + // Routing Context + fn add_routing_context(&self, routing_context: RoutingContext) -> u32 { + let mut inner = self.inner.lock(); + let mut next_id: u32 = 1; + while inner.routing_contexts.contains_key(&next_id) { + next_id += 1; + } + inner.routing_contexts.insert(next_id, routing_context); + next_id + } + fn lookup_routing_context(&self, id: u32, rc_id: u32) -> Result { + let inner = self.inner.lock(); + let Some(routing_context) = inner.routing_contexts.get(&rc_id).cloned() else { + return Err(Response { + id, + op: ResponseOp::RoutingContext(RoutingContextResponse { + rc_id, + rc_op: RoutingContextResponseOp::InvalidId + }) + }); + }; + Ok(routing_context) + } + fn release_routing_context(&self, id: u32) -> i32 { + let mut inner = self.inner.lock(); + if inner.routing_contexts.remove(&id).is_none() { + return 0; + } + return 1; + } + + // TableDB + fn add_table_db(&self, table_db: TableDB) -> u32 { + let mut inner = self.inner.lock(); + let mut next_id: u32 = 1; + while inner.table_dbs.contains_key(&next_id) { + next_id += 1; + } + inner.table_dbs.insert(next_id, table_db); + next_id + } + fn lookup_table_db(&self, id: u32, db_id: u32) -> Result { + let inner = self.inner.lock(); + let Some(table_db) = inner.table_dbs.get(&db_id).cloned() else { + return Err(Response { + id, + op: ResponseOp::TableDb(TableDbResponse { + db_id, + db_op: TableDbResponseOp::InvalidId + }) + }); + }; + Ok(table_db) + } + fn release_table_db(&self, id: u32) -> i32 { + let mut inner = self.inner.lock(); + if inner.table_dbs.remove(&id).is_none() { + return 0; + } + return 1; + } + + // TableDBTransaction + fn add_table_db_transaction(&self, tdbt: TableDBTransaction) -> u32 { + let mut inner = self.inner.lock(); + let mut next_id: u32 = 1; + while inner.table_db_transactions.contains_key(&next_id) { + next_id += 1; + } + inner.table_db_transactions.insert(next_id, tdbt); + next_id + } + fn lookup_table_db_transaction( + &self, + id: u32, + tx_id: u32, + ) -> Result { + let inner = self.inner.lock(); + let Some(table_db_transaction) = inner.table_db_transactions.get(&tx_id).cloned() else { + return Err(Response { + id, + op: ResponseOp::TableDbTransaction(TableDbTransactionResponse { + tx_id, + tx_op: TableDbTransactionResponseOp::InvalidId + }) + }); + }; + Ok(table_db_transaction) + } + fn release_table_db_transaction(&self, id: u32) -> i32 { + let mut inner = self.inner.lock(); + if inner.table_db_transactions.remove(&id).is_none() { + return 0; + } + return 1; + } + + // CryptoSystem + fn add_crypto_system(&self, csv: CryptoSystemVersion) -> u32 { + let mut inner = self.inner.lock(); + let mut next_id: u32 = 1; + while inner.crypto_systems.contains_key(&next_id) { + next_id += 1; + } + inner.crypto_systems.insert(next_id, csv); + next_id + } + fn lookup_crypto_system(&self, id: u32, cs_id: u32) -> Result { + let inner = self.inner.lock(); + let Some(crypto_system) = inner.crypto_systems.get(&cs_id).cloned() else { + return Err(Response { + id, + op: ResponseOp::CryptoSystem(CryptoSystemResponse { + cs_id, + cs_op: CryptoSystemResponseOp::InvalidId + }) + }); + }; + Ok(crypto_system) + } + fn release_crypto_system(&self, id: u32) -> i32 { + let mut inner = self.inner.lock(); + if inner.crypto_systems.remove(&id).is_none() { + return 0; + } + return 1; + } + + // Target + + // Parse target + async fn parse_target(&self, s: String) -> VeilidAPIResult { + // Is this a route id? + if let Ok(rrid) = RouteId::from_str(&s) { + let routing_table = self.api.routing_table()?; + let rss = routing_table.route_spec_store(); + + // Is this a valid remote route id? (can't target allocated routes) + if rss.is_route_id_remote(&rrid) { + return Ok(Target::PrivateRoute(rrid)); + } + } + + // Is this a node id? + if let Ok(nid) = TypedKey::from_str(&s) { + return Ok(Target::NodeId(nid)); + } + + Err(VeilidAPIError::invalid_target()) + } + + ////////////////////////////////////////////////////////////////////////////////////// + + pub async fn process_routing_context_request( + &self, + routing_context: RoutingContext, + rcr: RoutingContextRequest, + ) -> RoutingContextResponse { + let rc_op = match rcr.rc_op { + RoutingContextRequestOp::Release => { + self.release_routing_context(rcr.rc_id); + RoutingContextResponseOp::Release {} + } + RoutingContextRequestOp::WithPrivacy => RoutingContextResponseOp::WithPrivacy { + result: to_json_api_result( + routing_context + .clone() + .with_privacy() + .map(|new_rc| self.add_routing_context(new_rc)), + ), + }, + RoutingContextRequestOp::WithCustomPrivacy { stability } => { + RoutingContextResponseOp::WithCustomPrivacy { + result: to_json_api_result( + routing_context + .clone() + .with_custom_privacy(stability) + .map(|new_rc| self.add_routing_context(new_rc)), + ), + } + } + RoutingContextRequestOp::WithSequencing { sequencing } => { + RoutingContextResponseOp::WithSequencing { + value: self + .add_routing_context(routing_context.clone().with_sequencing(sequencing)), + } + } + RoutingContextRequestOp::AppCall { target, request } => { + RoutingContextResponseOp::AppCall { + result: to_json_api_result_with_vec_u8( + self.parse_target(target) + .then(|tr| async { routing_context.app_call(tr?, request).await }) + .await, + ), + } + } + RoutingContextRequestOp::AppMessage { target, message } => { + RoutingContextResponseOp::AppMessage { + result: to_json_api_result( + self.parse_target(target) + .then(|tr| async { routing_context.app_message(tr?, message).await }) + .await, + ), + } + } + RoutingContextRequestOp::CreateDhtRecord { kind, schema } => { + RoutingContextResponseOp::CreateDhtRecord { + result: to_json_api_result( + routing_context.create_dht_record(kind, schema).await, + ), + } + } + RoutingContextRequestOp::OpenDhtRecord { key, writer } => { + RoutingContextResponseOp::OpenDhtRecord { + result: to_json_api_result(routing_context.open_dht_record(key, writer).await), + } + } + RoutingContextRequestOp::CloseDhtRecord { key } => { + RoutingContextResponseOp::CloseDhtRecord { + result: to_json_api_result(routing_context.close_dht_record(key).await), + } + } + RoutingContextRequestOp::DeleteDhtRecord { key } => { + RoutingContextResponseOp::DeleteDhtRecord { + result: to_json_api_result(routing_context.delete_dht_record(key).await), + } + } + RoutingContextRequestOp::GetDhtValue { + key, + subkey, + force_refresh, + } => RoutingContextResponseOp::GetDhtValue { + result: to_json_api_result( + routing_context + .get_dht_value(key, subkey, force_refresh) + .await, + ), + }, + RoutingContextRequestOp::SetDhtValue { key, subkey, data } => { + RoutingContextResponseOp::SetDhtValue { + result: to_json_api_result( + routing_context.set_dht_value(key, subkey, data).await, + ), + } + } + RoutingContextRequestOp::WatchDhtValues { + key, + subkeys, + expiration, + count, + } => RoutingContextResponseOp::WatchDhtValues { + result: to_json_api_result( + routing_context + .watch_dht_values(key, subkeys, expiration, count) + .await, + ), + }, + RoutingContextRequestOp::CancelDhtWatch { key, subkeys } => { + RoutingContextResponseOp::CancelDhtWatch { + result: to_json_api_result( + routing_context.cancel_dht_watch(key, subkeys).await, + ), + } + } + }; + RoutingContextResponse { + rc_id: rcr.rc_id, + rc_op, + } + } + + pub async fn process_table_db_request( + &self, + table_db: TableDB, + tdr: TableDbRequest, + ) -> TableDbResponse { + let db_op = match tdr.db_op { + TableDbRequestOp::Release => { + self.release_table_db(tdr.db_id); + TableDbResponseOp::Release {} + } + TableDbRequestOp::GetColumnCount => TableDbResponseOp::GetColumnCount { + result: to_json_api_result(table_db.get_column_count()), + }, + TableDbRequestOp::GetKeys { col } => TableDbResponseOp::GetKeys { + result: to_json_api_result_with_vec_vec_u8(table_db.get_keys(col).await), + }, + TableDbRequestOp::Transact => TableDbResponseOp::Transact { + value: self.add_table_db_transaction(table_db.transact()), + }, + TableDbRequestOp::Store { col, key, value } => TableDbResponseOp::Store { + result: to_json_api_result(table_db.store(col, &key, &value).await), + }, + TableDbRequestOp::Load { col, key } => TableDbResponseOp::Load { + result: to_json_api_result( + table_db + .load(col, &key) + .await + .map(|vopt| vopt.map(|v| VecU8 { value: v })), + ), + }, + TableDbRequestOp::Delete { col, key } => TableDbResponseOp::Delete { + result: to_json_api_result( + table_db + .delete(col, &key) + .await + .map(|vopt| vopt.map(|v| VecU8 { value: v })), + ), + }, + }; + TableDbResponse { + db_id: tdr.db_id, + db_op, + } + } + + pub async fn process_table_db_transaction_request( + &self, + table_db_transaction: TableDBTransaction, + tdtr: TableDbTransactionRequest, + ) -> TableDbTransactionResponse { + let tx_op = match tdtr.tx_op { + TableDbTransactionRequestOp::Commit => TableDbTransactionResponseOp::Commit { + result: to_json_api_result(table_db_transaction.commit().await.map(|_| { + self.release_table_db_transaction(tdtr.tx_id); + })), + }, + TableDbTransactionRequestOp::Rollback => { + table_db_transaction.rollback(); + self.release_table_db_transaction(tdtr.tx_id); + TableDbTransactionResponseOp::Rollback {} + } + TableDbTransactionRequestOp::Store { col, key, value } => { + table_db_transaction.store(col, &key, &value); + TableDbTransactionResponseOp::Store {} + } + TableDbTransactionRequestOp::Delete { col, key } => { + table_db_transaction.delete(col, &key); + TableDbTransactionResponseOp::Delete {} + } + }; + TableDbTransactionResponse { + tx_id: tdtr.tx_id, + tx_op, + } + } + + pub async fn process_crypto_system_request( + &self, + csv: CryptoSystemVersion, + csr: CryptoSystemRequest, + ) -> CryptoSystemResponse { + let cs_op = match csr.cs_op { + CryptoSystemRequestOp::Release => { + self.release_crypto_system(csr.cs_id); + CryptoSystemResponseOp::Release {} + } + CryptoSystemRequestOp::CachedDh { key, secret } => CryptoSystemResponseOp::CachedDh { + result: to_json_api_result_with_string(csv.cached_dh(&key, &secret)), + }, + CryptoSystemRequestOp::ComputeDh { key, secret } => CryptoSystemResponseOp::ComputeDh { + result: to_json_api_result_with_string(csv.compute_dh(&key, &secret)), + }, + CryptoSystemRequestOp::RandomBytes { len } => CryptoSystemResponseOp::RandomBytes { + value: csv.random_bytes(len), + }, + CryptoSystemRequestOp::DefaultSaltLength => CryptoSystemResponseOp::DefaultSaltLength { + value: csv.default_salt_length(), + }, + CryptoSystemRequestOp::HashPassword { password, salt } => { + CryptoSystemResponseOp::HashPassword { + result: to_json_api_result(csv.hash_password(&password, &salt)), + } + } + CryptoSystemRequestOp::VerifyPassword { + password, + password_hash, + } => CryptoSystemResponseOp::VerifyPassword { + result: to_json_api_result(csv.verify_password(&password, &password_hash)), + }, + CryptoSystemRequestOp::DeriveSharedSecret { password, salt } => { + CryptoSystemResponseOp::DeriveSharedSecret { + result: to_json_api_result_with_string( + csv.derive_shared_secret(&password, &salt), + ), + } + } + CryptoSystemRequestOp::RandomNonce => CryptoSystemResponseOp::RandomNonce { + value: csv.random_nonce(), + }, + CryptoSystemRequestOp::RandomSharedSecret => { + CryptoSystemResponseOp::RandomSharedSecret { + value: csv.random_shared_secret(), + } + } + CryptoSystemRequestOp::GenerateKeyPair => CryptoSystemResponseOp::GenerateKeyPair { + value: csv.generate_keypair(), + }, + CryptoSystemRequestOp::GenerateHash { data } => CryptoSystemResponseOp::GenerateHash { + value: csv.generate_hash(&data), + }, + CryptoSystemRequestOp::ValidateKeyPair { key, secret } => { + CryptoSystemResponseOp::ValidateKeyPair { + value: csv.validate_keypair(&key, &secret), + } + } + CryptoSystemRequestOp::ValidateHash { data, hash_digest } => { + CryptoSystemResponseOp::ValidateHash { + value: csv.validate_hash(&data, &hash_digest), + } + } + CryptoSystemRequestOp::Distance { key1, key2 } => CryptoSystemResponseOp::Distance { + value: csv.distance(&key1, &key2), + }, + CryptoSystemRequestOp::Sign { key, secret, data } => CryptoSystemResponseOp::Sign { + result: to_json_api_result_with_string(csv.sign(&key, &secret, &data)), + }, + CryptoSystemRequestOp::Verify { key, data, secret } => CryptoSystemResponseOp::Verify { + result: to_json_api_result(csv.verify(&key, &data, &secret)), + }, + CryptoSystemRequestOp::AeadOverhead => CryptoSystemResponseOp::AeadOverhead { + value: csv.aead_overhead() as u32, + }, + CryptoSystemRequestOp::DecryptAead { + body, + nonce, + shared_secret, + associated_data, + } => CryptoSystemResponseOp::DecryptAead { + result: to_json_api_result_with_vec_u8(csv.decrypt_aead( + &body, + &nonce, + &shared_secret, + associated_data.as_ref().map(|ad| ad.as_slice()), + )), + }, + CryptoSystemRequestOp::EncryptAead { + body, + nonce, + shared_secret, + associated_data, + } => CryptoSystemResponseOp::EncryptAead { + result: to_json_api_result_with_vec_u8(csv.encrypt_aead( + &body, + &nonce, + &shared_secret, + associated_data.as_ref().map(|ad| ad.as_slice()), + )), + }, + CryptoSystemRequestOp::CryptNoAuth { + body, + nonce, + shared_secret, + } => CryptoSystemResponseOp::CryptNoAuth { + value: csv.crypt_no_auth_unaligned(&body, &nonce, &shared_secret), + }, + }; + CryptoSystemResponse { + cs_id: csr.cs_id, + cs_op, + } + } + + pub async fn process_request(self, request: Request) -> Response { + let id = request.id; + + let op = match request.op { + RequestOp::Control { args: _args } => ResponseOp::Control { + result: to_json_api_result(VeilidAPIResult::Err(VeilidAPIError::unimplemented( + "control should be handled by veilid-core host application", + ))), + }, + RequestOp::GetState => ResponseOp::GetState { + result: to_json_api_result(self.api.get_state().await), + }, + RequestOp::Attach => ResponseOp::Attach { + result: to_json_api_result(self.api.attach().await), + }, + RequestOp::Detach => ResponseOp::Detach { + result: to_json_api_result(self.api.detach().await), + }, + RequestOp::NewPrivateRoute => ResponseOp::NewPrivateRoute { + result: to_json_api_result(self.api.new_private_route().await.map(|r| { + NewPrivateRouteResult { + route_id: r.0, + blob: r.1, + } + })), + }, + RequestOp::NewCustomPrivateRoute { + kinds, + stability, + sequencing, + } => ResponseOp::NewCustomPrivateRoute { + result: to_json_api_result( + self.api + .new_custom_private_route(&kinds, stability, sequencing) + .await + .map(|r| NewPrivateRouteResult { + route_id: r.0, + blob: r.1, + }), + ), + }, + RequestOp::ImportRemotePrivateRoute { blob } => ResponseOp::ImportRemotePrivateRoute { + result: to_json_api_result_with_string(self.api.import_remote_private_route(blob)), + }, + RequestOp::ReleasePrivateRoute { route_id } => ResponseOp::ReleasePrivateRoute { + result: to_json_api_result(self.api.release_private_route(route_id)), + }, + RequestOp::AppCallReply { call_id, message } => ResponseOp::AppCallReply { + result: to_json_api_result(self.api.app_call_reply(call_id, message).await), + }, + RequestOp::NewRoutingContext => ResponseOp::NewRoutingContext { + value: self.add_routing_context(self.api.routing_context()), + }, + RequestOp::RoutingContext(rcr) => { + let routing_context = match self.lookup_routing_context(id, rcr.rc_id) { + Ok(v) => v, + Err(e) => return e, + }; + ResponseOp::RoutingContext( + self.process_routing_context_request(routing_context, rcr) + .await, + ) + } + RequestOp::OpenTableDb { name, column_count } => { + let table_store = match self.api.table_store() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::OpenTableDb { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::OpenTableDb { + result: to_json_api_result( + table_store + .open(&name, column_count) + .await + .map(|table_db| self.add_table_db(table_db)), + ), + } + } + RequestOp::DeleteTableDb { name } => { + let table_store = match self.api.table_store() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::OpenTableDb { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::DeleteTableDb { + result: to_json_api_result(table_store.delete(&name).await), + } + } + RequestOp::TableDb(tdr) => { + let table_db = match self.lookup_table_db(id, tdr.db_id) { + Ok(v) => v, + Err(e) => return e, + }; + ResponseOp::TableDb(self.process_table_db_request(table_db, tdr).await) + } + RequestOp::TableDbTransaction(tdtr) => { + let table_db_transaction = match self.lookup_table_db_transaction(id, tdtr.tx_id) { + Ok(v) => v, + Err(e) => return e, + }; + ResponseOp::TableDbTransaction( + self.process_table_db_transaction_request(table_db_transaction, tdtr) + .await, + ) + } + RequestOp::GetCryptoSystem { kind } => { + let crypto = match self.api.crypto() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::GetCryptoSystem { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::GetCryptoSystem { + result: to_json_api_result( + crypto + .get(kind) + .ok_or_else(|| { + VeilidAPIError::invalid_argument( + "unsupported cryptosystem", + "kind", + kind, + ) + }) + .map(|csv| self.add_crypto_system(csv)), + ), + } + } + RequestOp::BestCryptoSystem => { + let crypto = match self.api.crypto() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::GetCryptoSystem { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::BestCryptoSystem { + result: to_json_api_result(Ok(self.add_crypto_system(crypto.best()))), + } + } + RequestOp::CryptoSystem(csr) => { + let csv = match self.lookup_crypto_system(id, csr.cs_id) { + Ok(v) => v, + Err(e) => return e, + }; + ResponseOp::CryptoSystem(self.process_crypto_system_request(csv, csr).await) + } + RequestOp::VerifySignatures { + node_ids, + data, + signatures, + } => { + let crypto = match self.api.crypto() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::GetCryptoSystem { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::VerifySignatures { + result: to_json_api_result_with_vec_string(crypto.verify_signatures( + &node_ids, + &data, + &signatures, + )), + } + } + RequestOp::GenerateSignatures { data, key_pairs } => { + let crypto = match self.api.crypto() { + Ok(v) => v, + Err(e) => { + return Response { + id, + op: ResponseOp::GetCryptoSystem { + result: to_json_api_result(Err(e)), + }, + } + } + }; + ResponseOp::GenerateSignatures { + result: to_json_api_result_with_vec_string(crypto.generate_signatures( + &data, + &key_pairs, + |k, s| TypedSignature::new(k.kind, s), + )), + } + } + RequestOp::GenerateKeyPair { kind } => ResponseOp::GenerateKeyPair { + result: to_json_api_result_with_string(Crypto::generate_keypair(kind)), + }, + RequestOp::Now => ResponseOp::Now { + value: get_aligned_timestamp(), + }, + RequestOp::Debug { command } => ResponseOp::Debug { + result: to_json_api_result(self.api.debug(command).await), + }, + RequestOp::VeilidVersionString => ResponseOp::VeilidVersionString { + value: veilid_version_string(), + }, + RequestOp::VeilidVersion => { + let (major, minor, patch) = veilid_version(); + + ResponseOp::VeilidVersion { + major, + minor, + patch, + } + } + }; + + Response { id, op } + } +} diff --git a/veilid-core/src/veilid_api/json_api/routing_context.rs b/veilid-core/src/veilid_api/json_api/routing_context.rs new file mode 100644 index 00000000..94c55a9e --- /dev/null +++ b/veilid-core/src/veilid_api/json_api/routing_context.rs @@ -0,0 +1,143 @@ +use super::*; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct RoutingContextRequest { + pub rc_id: u32, + #[serde(flatten)] + pub rc_op: RoutingContextRequestOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct RoutingContextResponse { + pub rc_id: u32, + #[serde(flatten)] + pub rc_op: RoutingContextResponseOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "rc_op")] +pub enum RoutingContextRequestOp { + Release, + WithPrivacy, + WithCustomPrivacy { + stability: Stability, + }, + WithSequencing { + sequencing: Sequencing, + }, + AppCall { + target: String, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + request: Vec, + }, + AppMessage { + target: String, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + message: Vec, + }, + CreateDhtRecord { + #[schemars(with = "String")] + kind: CryptoKind, + schema: DHTSchema, + }, + OpenDhtRecord { + #[schemars(with = "String")] + key: TypedKey, + #[schemars(with = "Option")] + writer: Option, + }, + CloseDhtRecord { + #[schemars(with = "String")] + key: TypedKey, + }, + DeleteDhtRecord { + #[schemars(with = "String")] + key: TypedKey, + }, + GetDhtValue { + #[schemars(with = "String")] + key: TypedKey, + subkey: ValueSubkey, + force_refresh: bool, + }, + SetDhtValue { + #[schemars(with = "String")] + key: TypedKey, + subkey: ValueSubkey, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + }, + WatchDhtValues { + #[schemars(with = "String")] + key: TypedKey, + subkeys: ValueSubkeyRangeSet, + expiration: Timestamp, + count: u32, + }, + CancelDhtWatch { + #[schemars(with = "String")] + key: TypedKey, + subkeys: ValueSubkeyRangeSet, + }, +} +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "rc_op")] +pub enum RoutingContextResponseOp { + InvalidId, + Release, + WithPrivacy { + #[serde(flatten)] + result: ApiResult, + }, + WithCustomPrivacy { + #[serde(flatten)] + result: ApiResult, + }, + WithSequencing { + value: u32, + }, + AppCall { + #[serde(flatten)] + #[schemars(with = "ApiResult")] + result: ApiResultWithVecU8, + }, + AppMessage { + #[serde(flatten)] + result: ApiResult<()>, + }, + CreateDhtRecord { + #[serde(flatten)] + result: ApiResult, + }, + OpenDhtRecord { + #[serde(flatten)] + result: ApiResult, + }, + CloseDhtRecord { + #[serde(flatten)] + result: ApiResult<()>, + }, + DeleteDhtRecord { + #[serde(flatten)] + result: ApiResult<()>, + }, + GetDhtValue { + #[serde(flatten)] + result: ApiResult>, + }, + SetDhtValue { + #[serde(flatten)] + result: ApiResult>, + }, + WatchDhtValues { + #[serde(flatten)] + result: ApiResult, + }, + CancelDhtWatch { + #[serde(flatten)] + result: ApiResult, + }, +} diff --git a/veilid-core/src/veilid_api/json_api/table_db.rs b/veilid-core/src/veilid_api/json_api/table_db.rs new file mode 100644 index 00000000..e2741730 --- /dev/null +++ b/veilid-core/src/veilid_api/json_api/table_db.rs @@ -0,0 +1,129 @@ +use super::*; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDbRequest { + pub db_id: u32, + #[serde(flatten)] + pub db_op: TableDbRequestOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDbResponse { + pub db_id: u32, + #[serde(flatten)] + pub db_op: TableDbResponseOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "db_op")] +pub enum TableDbRequestOp { + Release, + GetColumnCount, + GetKeys { + col: u32, + }, + Transact, + Store { + col: u32, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + key: Vec, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, + }, + Load { + col: u32, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + key: Vec, + }, + Delete { + col: u32, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + key: Vec, + }, +} +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "db_op")] +pub enum TableDbResponseOp { + InvalidId, + Release, + GetColumnCount { + #[serde(flatten)] + result: ApiResult, + }, + GetKeys { + #[serde(flatten)] + #[schemars(with = "ApiResult>")] + result: ApiResultWithVecVecU8, + }, + Transact { + value: u32, + }, + Store { + #[serde(flatten)] + result: ApiResult<()>, + }, + Load { + #[serde(flatten)] + #[schemars(with = "ApiResult>")] + result: ApiResult>, + }, + Delete { + #[serde(flatten)] + #[schemars(with = "ApiResult>")] + result: ApiResult>, + }, +} + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDbTransactionRequest { + pub tx_id: u32, + #[serde(flatten)] + pub tx_op: TableDbTransactionRequestOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct TableDbTransactionResponse { + pub tx_id: u32, + #[serde(flatten)] + pub tx_op: TableDbTransactionResponseOp, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "tx_op")] +pub enum TableDbTransactionRequestOp { + Commit, + Rollback, + Store { + col: u32, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + key: Vec, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + value: Vec, + }, + Delete { + col: u32, + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + key: Vec, + }, +} +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "tx_op")] +pub enum TableDbTransactionResponseOp { + InvalidId, + Commit { + #[serde(flatten)] + result: ApiResult<()>, + }, + Rollback {}, + Store {}, + Delete {}, +} diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index d046b71f..9c8e4e2d 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -7,6 +7,7 @@ mod routing_context; mod serialize_helpers; mod types; +pub mod json_api; pub mod tests; pub use api::*; @@ -20,6 +21,7 @@ pub use alloc::string::ToString; pub use attachment_manager::AttachmentManager; pub use core::str::FromStr; pub use crypto::*; +#[cfg(feature = "unstable-blockstore")] pub use intf::BlockStore; pub use intf::ProtectedStore; pub use network_manager::NetworkManager; diff --git a/veilid-core/src/veilid_api/routing_context.rs b/veilid-core/src/veilid_api/routing_context.rs index 952a42b7..e73e54ca 100644 --- a/veilid-core/src/veilid_api/routing_context.rs +++ b/veilid-core/src/veilid_api/routing_context.rs @@ -290,10 +290,12 @@ impl RoutingContext { /////////////////////////////////// /// Block Store + #[cfg(feature = "unstable-blockstore")] pub async fn find_block(&self, _block_id: PublicKey) -> VeilidAPIResult> { panic!("unimplemented"); } + #[cfg(feature = "unstable-blockstore")] pub async fn supply_block(&self, _block_id: PublicKey) -> VeilidAPIResult { panic!("unimplemented"); } diff --git a/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs b/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs index 5e98624c..afdab167 100644 --- a/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs +++ b/veilid-core/src/veilid_api/serialize_helpers/serialize_json.rs @@ -55,6 +55,27 @@ pub mod json_as_base64 { } } +pub mod opt_json_as_base64 { + use data_encoding::BASE64URL_NOPAD; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(v: &Option>, s: S) -> Result { + let base64 = v.as_ref().map(|x| BASE64URL_NOPAD.encode(&x)); + Option::::serialize(&base64, s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result>, D::Error> { + let base64 = Option::::deserialize(d)?; + base64 + .map(|x| { + BASE64URL_NOPAD + .decode(x.as_bytes()) + .map_err(|e| serde::de::Error::custom(e)) + }) + .transpose() + } +} + pub mod json_as_string { use std::fmt::Display; use std::str::FromStr; diff --git a/veilid-core/src/veilid_api/serialize_helpers/serialize_range_set_blaze.rs b/veilid-core/src/veilid_api/serialize_helpers/serialize_range_set_blaze.rs index 5ef5fa46..2b98a3e3 100644 --- a/veilid-core/src/veilid_api/serialize_helpers/serialize_range_set_blaze.rs +++ b/veilid-core/src/veilid_api/serialize_helpers/serialize_range_set_blaze.rs @@ -9,11 +9,10 @@ pub fn serialize( v: &RangeSetBlaze, s: S, ) -> Result { - let cnt = v.ranges_len() * 2; + let cnt = v.ranges_len(); let mut seq = s.serialize_seq(Some(cnt))?; for range in v.ranges() { - seq.serialize_element(range.start())?; - seq.serialize_element(range.end())?; + seq.serialize_element(&(range.start(), range.end()))?; } seq.end() } @@ -41,10 +40,7 @@ pub fn deserialize<'de, T: Integer + Deserialize<'de>, D: Deserializer<'de>>( { let mut values = RangeSetBlaze::::new(); - while let Some(start) = seq.next_element()? { - let Some(end) = seq.next_element()? else { - break; - }; + while let Some((start, end)) = seq.next_element()? { values.ranges_insert(start..=end); } diff --git a/veilid-core/src/veilid_api/tests/fixtures.rs b/veilid-core/src/veilid_api/tests/fixtures.rs index 7f86ac22..bb503260 100644 --- a/veilid-core/src/veilid_api/tests/fixtures.rs +++ b/veilid-core/src/veilid_api/tests/fixtures.rs @@ -212,10 +212,6 @@ pub fn fix_veilidvaluechange() -> VeilidValueChange { key: fix_typedkey(), subkeys: vec![1, 2, 3, 4], count: 5, - value: ValueData { - seq: 23, - data: b"ValueData".to_vec(), - writer: fix_cryptokey(), - }, + value: ValueData::new_with_seq(23, b"ValueData".to_vec(), fix_cryptokey()), } } diff --git a/veilid-core/src/veilid_api/tests/test_serialize_json.rs b/veilid-core/src/veilid_api/tests/test_serialize_json.rs index 78df9607..bf508779 100644 --- a/veilid-core/src/veilid_api/tests/test_serialize_json.rs +++ b/veilid-core/src/veilid_api/tests/test_serialize_json.rs @@ -17,10 +17,15 @@ pub async fn test_all() { test_transferstatsdownup().await; test_rpcstats().await; test_peerstats().await; + #[cfg(feature = "unstable-tunnels")] test_tunnelmode().await; + #[cfg(feature = "unstable-tunnels")] test_tunnelerror().await; + #[cfg(feature = "unstable-tunnels")] test_tunnelendpoint().await; + #[cfg(feature = "unstable-tunnels")] test_fulltunnel().await; + #[cfg(feature = "unstable-tunnels")] test_partialtunnel().await; test_veilidloglevel().await; test_veilidlog().await; diff --git a/veilid-core/src/veilid_api/tests/test_types.rs b/veilid-core/src/veilid_api/tests/test_types.rs index b56cc024..7cbe27bb 100644 --- a/veilid-core/src/veilid_api/tests/test_types.rs +++ b/veilid-core/src/veilid_api/tests/test_types.rs @@ -13,21 +13,18 @@ pub async fn test_alignedu64() { // app_messsage_call pub async fn test_veilidappmessage() { - let orig = VeilidAppMessage { - sender: Some(fix_typedkey()), - message: b"Hi there!".to_vec(), - }; + let orig = VeilidAppMessage::new(Some(fix_typedkey()), b"Hi there!".to_vec()); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); } pub async fn test_veilidappcall() { - let orig = VeilidAppCall { - sender: Some(fix_typedkey()), - message: b"Well, hello!".to_vec(), - id: AlignedU64::from(123), - }; + let orig = VeilidAppCall::new( + Some(fix_typedkey()), + b"Well, hello!".to_vec(), + AlignedU64::from(123), + ); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); @@ -116,12 +113,15 @@ pub async fn test_peerstats() { // tunnel +#[cfg(feature = "unstable-tunnels")] pub async fn test_tunnelmode() { let orig = TunnelMode::Raw; let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); } + +#[cfg(feature = "unstable-tunnels")] pub async fn test_tunnelerror() { let orig = TunnelError::NoCapacity; let copy = deserialize_json(&serialize_json(&orig)).unwrap(); @@ -129,6 +129,7 @@ pub async fn test_tunnelerror() { assert_eq!(orig, copy); } +#[cfg(feature = "unstable-tunnels")] pub async fn test_tunnelendpoint() { let orig = TunnelEndpoint { mode: TunnelMode::Raw, @@ -139,6 +140,7 @@ pub async fn test_tunnelendpoint() { assert_eq!(orig, copy); } +#[cfg(feature = "unstable-tunnels")] pub async fn test_fulltunnel() { let orig = FullTunnel { id: AlignedU64::from(42), @@ -157,6 +159,7 @@ pub async fn test_fulltunnel() { assert_eq!(orig, copy); } +#[cfg(feature = "unstable-tunnels")] pub async fn test_partialtunnel() { let orig = PartialTunnel { id: AlignedU64::from(42), diff --git a/veilid-core/src/veilid_api/tests/test_types_dht.rs b/veilid-core/src/veilid_api/tests/test_types_dht.rs index f098e06b..73f32eda 100644 --- a/veilid-core/src/veilid_api/tests/test_types_dht.rs +++ b/veilid-core/src/veilid_api/tests/test_types_dht.rs @@ -5,12 +5,12 @@ use range_set_blaze::*; // dht_record_descriptors pub async fn test_dhtrecorddescriptor() { - let orig = DHTRecordDescriptor { - key: fix_typedkey(), - owner: fix_cryptokey(), - owner_secret: Some(fix_cryptokey()), - schema: DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 4321 }), - }; + let orig = DHTRecordDescriptor::new( + fix_typedkey(), + fix_cryptokey(), + Some(fix_cryptokey()), + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt: 4321 }), + ); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); @@ -19,11 +19,7 @@ pub async fn test_dhtrecorddescriptor() { // value_data pub async fn test_valuedata() { - let orig = ValueData { - seq: 42, - data: b"Brent Spiner".to_vec(), - writer: fix_cryptokey(), - }; + let orig = ValueData::new_with_seq(42, b"Brent Spiner".to_vec(), fix_cryptokey()); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); @@ -32,9 +28,7 @@ pub async fn test_valuedata() { // value_subkey_range_set pub async fn test_valuesubkeyrangeset() { - let orig = ValueSubkeyRangeSet { - data: RangeSetBlaze::from_iter([20..=30]), - }; + let orig = ValueSubkeyRangeSet::new_with_data(RangeSetBlaze::from_iter([20..=30])); let copy = deserialize_json(&serialize_json(&orig)).unwrap(); assert_eq!(orig, copy); diff --git a/veilid-core/src/veilid_api/tests/test_types_dht_schema.rs b/veilid-core/src/veilid_api/tests/test_types_dht_schema.rs index a7a8379c..582d4040 100644 --- a/veilid-core/src/veilid_api/tests/test_types_dht_schema.rs +++ b/veilid-core/src/veilid_api/tests/test_types_dht_schema.rs @@ -1,6 +1,5 @@ use super::fixtures::*; use crate::*; -use range_set_blaze::*; // dlft diff --git a/veilid-core/src/veilid_api/types/aligned_u64.rs b/veilid-core/src/veilid_api/types/aligned_u64.rs index 6a64b86d..566bac84 100644 --- a/veilid-core/src/veilid_api/types/aligned_u64.rs +++ b/veilid-core/src/veilid_api/types/aligned_u64.rs @@ -18,10 +18,16 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[repr(C, align(8))] #[archive_attr(repr(C, align(8)), derive(CheckBytes))] -pub struct AlignedU64(u64); +#[serde(transparent)] +pub struct AlignedU64( + #[serde(with = "json_as_string")] + #[schemars(with = "String")] + u64, +); impl From for AlignedU64 { fn from(v: u64) -> Self { diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index f2485d46..36fa47e1 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -2,15 +2,27 @@ use super::*; /// Direct statement blob passed to hosting application for processing #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidAppMessage { /// Some(sender) if the message was sent directly, None if received via a private/safety route #[serde(with = "opt_json_as_string")] + #[schemars(with = "Option")] pub sender: Option, + /// The content of the message to deliver to the application #[serde(with = "json_as_base64")] + #[schemars(with = "String")] pub message: Vec, } @@ -29,27 +41,41 @@ impl VeilidAppMessage { /// Direct question blob passed to hosting application for processing to send an eventual AppReply #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidAppCall { /// Some(sender) if the request was sent directly, None if received via a private/safety route #[serde(with = "opt_json_as_string")] - pub sender: Option, + #[schemars(with = "Option")] + sender: Option, + /// The content of the request to deliver to the application #[serde(with = "json_as_base64")] - pub message: Vec, + #[schemars(with = "String")] + message: Vec, + /// The id to reply to #[serde(with = "json_as_string")] - pub id: OperationId, + #[schemars(with = "String")] + call_id: OperationId, } impl VeilidAppCall { - pub fn new(sender: Option, message: Vec, id: OperationId) -> Self { + pub fn new(sender: Option, message: Vec, call_id: OperationId) -> Self { Self { sender, message, - id, + call_id, } } @@ -60,6 +86,6 @@ impl VeilidAppCall { &self.message } pub fn id(&self) -> OperationId { - self.id + self.call_id } } diff --git a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs index 1b04050b..6162fd9f 100644 --- a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs +++ b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs @@ -13,18 +13,22 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct DHTRecordDescriptor { /// DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] - pub key: TypedKey, + #[schemars(with = "String")] + key: TypedKey, /// The public key of the owner - pub owner: PublicKey, + #[schemars(with = "String")] + owner: PublicKey, /// If this key is being created: Some(the secret key of the owner) /// If this key is just being opened: None - pub owner_secret: Option, + #[schemars(with = "Option")] + owner_secret: Option, /// The schema in use associated with the key - pub schema: DHTSchema, + schema: DHTSchema, } impl DHTRecordDescriptor { diff --git a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs index bd9c0858..a742bdec 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs @@ -13,6 +13,7 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct DHTSchemaDFLT { diff --git a/veilid-core/src/veilid_api/types/dht/schema/mod.rs b/veilid-core/src/veilid_api/types/dht/schema/mod.rs index b043afcc..2361e32c 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/mod.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/mod.rs @@ -19,6 +19,7 @@ pub use smpl::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] #[serde(tag = "kind")] diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs index 90b20b86..9b2c7df1 100644 --- a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -13,10 +13,12 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct DHTSchemaSMPLMember { /// Member key + #[schemars(with = "String")] pub m_key: PublicKey, /// Member subkey count pub m_cnt: u16, @@ -35,6 +37,7 @@ pub struct DHTSchemaSMPLMember { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct DHTSchemaSMPL { diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs index 5ac50649..c57fc2e5 100644 --- a/veilid-core/src/veilid_api/types/dht/value_data.rs +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -13,12 +13,21 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct ValueData { - pub seq: ValueSeqNum, - pub data: Vec, - pub writer: PublicKey, + /// An increasing sequence number to time-order the DHT record changes + seq: ValueSeqNum, + + /// The contents of a DHT Record + #[serde(with = "json_as_base64")] + #[schemars(with = "String")] + data: Vec, + + /// The public identity key of the writer of the data + #[schemars(with = "String")] + writer: PublicKey, } impl ValueData { pub const MAX_LEN: usize = 32768; diff --git a/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs b/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs index 376530b5..466346d3 100644 --- a/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs +++ b/veilid-core/src/veilid_api/types/dht/value_subkey_range_set.rs @@ -15,12 +15,15 @@ use range_set_blaze::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] +#[serde(transparent)] pub struct ValueSubkeyRangeSet { #[with(RkyvRangeSetBlaze)] #[serde(with = "serialize_range_set_blaze")] - pub data: RangeSetBlaze, + #[schemars(with = "Vec<(u32,u32)>")] + data: RangeSetBlaze, } impl ValueSubkeyRangeSet { @@ -29,6 +32,9 @@ impl ValueSubkeyRangeSet { data: Default::default(), } } + pub fn new_with_data(data: RangeSetBlaze) -> Self { + Self { data } + } pub fn single(value: ValueSubkey) -> Self { let mut data = RangeSetBlaze::new(); data.insert(value); diff --git a/veilid-core/src/veilid_api/types/fourcc.rs b/veilid-core/src/veilid_api/types/fourcc.rs index edce2b03..1d56da33 100644 --- a/veilid-core/src/veilid_api/types/fourcc.rs +++ b/veilid-core/src/veilid_api/types/fourcc.rs @@ -15,8 +15,11 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes, PartialOrd, Ord, PartialEq, Eq, Hash))] +#[serde(try_from = "String")] +#[serde(into = "String")] pub struct FourCC(pub [u8; 4]); impl From<[u8; 4]> for FourCC { @@ -37,6 +40,12 @@ impl From for u32 { } } +impl From for String { + fn from(u: FourCC) -> Self { + String::from_utf8_lossy(&u.0).to_string() + } +} + impl TryFrom<&[u8]> for FourCC { type Error = VeilidAPIError; fn try_from(b: &[u8]) -> Result { @@ -44,6 +53,13 @@ impl TryFrom<&[u8]> for FourCC { } } +impl TryFrom for FourCC { + type Error = VeilidAPIError; + fn try_from(s: String) -> Result { + Self::from_str(s.as_str()) + } +} + impl fmt::Display for FourCC { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "{}", String::from_utf8_lossy(&self.0)) diff --git a/veilid-core/src/veilid_api/types/mod.rs b/veilid-core/src/veilid_api/types/mod.rs index 6dea3db5..d3c2d9dc 100644 --- a/veilid-core/src/veilid_api/types/mod.rs +++ b/veilid-core/src/veilid_api/types/mod.rs @@ -4,6 +4,7 @@ mod dht; mod fourcc; mod safety; mod stats; +#[cfg(feature = "unstable-tunnels")] mod tunnel; mod veilid_log; mod veilid_state; @@ -16,6 +17,7 @@ pub use dht::*; pub use fourcc::*; pub use safety::*; pub use stats::*; +#[cfg(feature = "unstable-tunnels")] pub use tunnel::*; pub use veilid_log::*; pub use veilid_state::*; diff --git a/veilid-core/src/veilid_api/types/safety.rs b/veilid-core/src/veilid_api/types/safety.rs index 27cb46ba..970619c2 100644 --- a/veilid-core/src/veilid_api/types/safety.rs +++ b/veilid-core/src/veilid_api/types/safety.rs @@ -15,6 +15,7 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum Sequencing { @@ -44,6 +45,7 @@ impl Default for Sequencing { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum Stability { @@ -72,6 +74,7 @@ impl Default for Stability { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum SafetySelection { @@ -111,10 +114,12 @@ impl Default for SafetySelection { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct SafetySpec { /// preferred safety route set id if it still exists + #[schemars(with = "Option")] pub preferred_route: Option, /// must be greater than 0 pub hop_count: usize, diff --git a/veilid-core/src/veilid_api/types/stats.rs b/veilid-core/src/veilid_api/types/stats.rs index 8ea79e11..a7bbce14 100644 --- a/veilid-core/src/veilid_api/types/stats.rs +++ b/veilid-core/src/veilid_api/types/stats.rs @@ -11,14 +11,12 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct LatencyStats { - #[serde(with = "json_as_string")] pub fastest: TimestampDuration, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] pub average: TimestampDuration, // average latency over the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] pub slowest: TimestampDuration, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies } @@ -33,16 +31,13 @@ pub struct LatencyStats { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct TransferStats { - #[serde(with = "json_as_string")] - pub total: ByteCount, // total amount transferred ever - #[serde(with = "json_as_string")] + pub total: ByteCount, // total amount transferred ever pub maximum: ByteCount, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] pub average: ByteCount, // average rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] pub minimum: ByteCount, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts } @@ -57,6 +52,7 @@ pub struct TransferStats { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct TransferStatsDownUp { @@ -75,17 +71,15 @@ pub struct TransferStatsDownUp { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct RPCStats { pub messages_sent: u32, // number of rpcs that have been sent in the total_time range pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range pub questions_in_flight: u32, // number of questions issued that have yet to be answered - #[serde(with = "opt_json_as_string")] pub last_question_ts: Option, // when the peer was last questioned (either successfully or not) and we wanted an answer - #[serde(with = "opt_json_as_string")] pub last_seen_ts: Option, // when the peer was last seen for any reason, including when we first attempted to reach out to it - #[serde(with = "opt_json_as_string")] pub first_consecutive_seen_ts: Option, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one @@ -102,12 +96,12 @@ pub struct RPCStats { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct PeerStats { - #[serde(with = "json_as_string")] pub time_added: Timestamp, // when the peer was added to the routing table - pub rpc_stats: RPCStats, // information about RPCs + pub rpc_stats: RPCStats, // information about RPCs pub latency: Option, // latencies for communications with the peer pub transfer: TransferStatsDownUp, // Stats for communications with the peer } diff --git a/veilid-core/src/veilid_api/types/tunnel.rs b/veilid-core/src/veilid_api/types/tunnel.rs index bdea8a1c..370b45ee 100644 --- a/veilid-core/src/veilid_api/types/tunnel.rs +++ b/veilid-core/src/veilid_api/types/tunnel.rs @@ -1,8 +1,11 @@ +#[cfg(feature = "unstable-tunnels")] use super::*; /// Tunnel identifier +#[cfg(feature = "unstable-tunnels")] pub type TunnelId = AlignedU64; +#[cfg(feature = "unstable-tunnels")] #[derive( Copy, Clone, @@ -16,6 +19,7 @@ pub type TunnelId = AlignedU64; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum TunnelMode { @@ -23,6 +27,7 @@ pub enum TunnelMode { Turn, } +#[cfg(feature = "unstable-tunnels")] #[derive( Copy, Clone, @@ -36,6 +41,7 @@ pub enum TunnelMode { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum TunnelError { @@ -45,8 +51,18 @@ pub enum TunnelError { NoCapacity, // Endpoint is full } +#[cfg(feature = "unstable-tunnels")] #[derive( - Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Clone, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct TunnelEndpoint { @@ -54,6 +70,7 @@ pub struct TunnelEndpoint { pub description: String, // XXX: TODO } +#[cfg(feature = "unstable-tunnels")] impl Default for TunnelEndpoint { fn default() -> Self { Self { @@ -63,6 +80,7 @@ impl Default for TunnelEndpoint { } } +#[cfg(feature = "unstable-tunnels")] #[derive( Clone, Debug, @@ -74,6 +92,7 @@ impl Default for TunnelEndpoint { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct FullTunnel { @@ -83,6 +102,7 @@ pub struct FullTunnel { pub remote: TunnelEndpoint, } +#[cfg(feature = "unstable-tunnels")] #[derive( Clone, Debug, @@ -94,6 +114,7 @@ pub struct FullTunnel { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct PartialTunnel { diff --git a/veilid-core/src/veilid_api/types/veilid_log.rs b/veilid-core/src/veilid_api/types/veilid_log.rs index 5eab945c..f377115c 100644 --- a/veilid-core/src/veilid_api/types/veilid_log.rs +++ b/veilid-core/src/veilid_api/types/veilid_log.rs @@ -14,6 +14,7 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum VeilidLogLevel { @@ -63,22 +64,45 @@ impl VeilidLogLevel { } } +impl FromStr for VeilidLogLevel { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + Ok(match s { + "Error" => Self::Error, + "Warn" => Self::Warn, + "Info" => Self::Info, + "Debug" => Self::Debug, + "Trace" => Self::Trace, + _ => { + apibail_invalid_argument!("Can't convert str", "s", s); + } + }) + } +} impl fmt::Display for VeilidLogLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let text = match self { - Self::Error => "ERROR", - Self::Warn => "WARN", - Self::Info => "INFO", - Self::Debug => "DEBUG", - Self::Trace => "TRACE", + Self::Error => "Error", + Self::Warn => "Warn", + Self::Info => "Info", + Self::Debug => "Debug", + Self::Trace => "Trace", }; write!(f, "{}", text) } } - /// A VeilidCore log message with optional backtrace #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidLog { diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index ca583953..c86dac03 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -12,6 +12,7 @@ use super::*; RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] pub enum AttachmentState { @@ -60,7 +61,16 @@ impl TryFrom for AttachmentState { } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidStateAttachment { @@ -70,39 +80,76 @@ pub struct VeilidStateAttachment { } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct PeerTableData { + #[schemars(with = "Vec")] pub node_ids: Vec, pub peer_address: String, pub peer_stats: PeerStats, } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidStateNetwork { pub started: bool, - #[serde(with = "json_as_string")] pub bps_down: ByteCount, - #[serde(with = "json_as_string")] pub bps_up: ByteCount, pub peers: Vec, } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidRouteChange { + #[schemars(with = "Vec")] pub dead_routes: Vec, + #[schemars(with = "Vec")] pub dead_remote_routes: Vec, } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidStateConfig { @@ -110,10 +157,20 @@ pub struct VeilidStateConfig { } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidValueChange { + #[schemars(with = "String")] pub key: TypedKey, pub subkeys: Vec, pub count: u32, @@ -121,7 +178,16 @@ pub struct VeilidValueChange { } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(u8), derive(CheckBytes))] #[serde(tag = "kind")] @@ -138,7 +204,16 @@ pub enum VeilidUpdate { } #[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + JsonSchema, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct VeilidState { diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 68c06e6f..98badf13 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -25,6 +25,7 @@ pub type ConfigCallback = Arc ConfigCallbackReturn + Send + Sy RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigHTTPS { pub enabled: bool, @@ -54,6 +55,7 @@ pub struct VeilidConfigHTTPS { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigHTTP { pub enabled: bool, @@ -79,6 +81,7 @@ pub struct VeilidConfigHTTP { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigApplication { pub https: VeilidConfigHTTPS, @@ -106,6 +109,7 @@ pub struct VeilidConfigApplication { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigUDP { pub enabled: bool, @@ -135,6 +139,7 @@ pub struct VeilidConfigUDP { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigTCP { pub connect: bool, @@ -166,6 +171,7 @@ pub struct VeilidConfigTCP { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigWS { pub connect: bool, @@ -198,6 +204,7 @@ pub struct VeilidConfigWS { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigWSS { pub connect: bool, @@ -226,6 +233,7 @@ pub struct VeilidConfigWSS { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigProtocol { pub udp: VeilidConfigUDP, @@ -253,6 +261,7 @@ pub struct VeilidConfigProtocol { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigTLS { pub certificate_path: String, @@ -273,6 +282,7 @@ pub struct VeilidConfigTLS { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigDHT { pub max_find_node_count: u32, @@ -309,6 +319,7 @@ pub struct VeilidConfigDHT { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigRPC { pub concurrency: u32, @@ -333,9 +344,12 @@ pub struct VeilidConfigRPC { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigRoutingTable { + #[schemars(with = "Vec")] pub node_id: TypedKeySet, + #[schemars(with = "Vec")] pub node_id_secret: TypedSecretSet, pub bootstrap: Vec, pub limit_over_attached: u32, @@ -358,6 +372,7 @@ pub struct VeilidConfigRoutingTable { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigNetwork { pub connection_initial_timeout_ms: u32, @@ -391,6 +406,7 @@ pub struct VeilidConfigNetwork { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigTableStore { pub directory: String, @@ -408,6 +424,7 @@ pub struct VeilidConfigTableStore { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigBlockStore { pub directory: String, @@ -425,6 +442,7 @@ pub struct VeilidConfigBlockStore { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigProtectedStore { pub allow_insecure_fallback: bool, @@ -446,6 +464,7 @@ pub struct VeilidConfigProtectedStore { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigCapabilities { pub protocol_udp: bool, @@ -468,6 +487,7 @@ pub struct VeilidConfigCapabilities { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub enum VeilidConfigLogLevel { Off, @@ -525,6 +545,35 @@ impl Default for VeilidConfigLogLevel { Self::Off } } +impl FromStr for VeilidConfigLogLevel { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + Ok(match s { + "Off" => Self::Off, + "Error" => Self::Error, + "Warn" => Self::Warn, + "Info" => Self::Info, + "Debug" => Self::Debug, + "Trace" => Self::Trace, + _ => { + apibail_invalid_argument!("Can't convert str", "s", s); + } + }) + } +} +impl fmt::Display for VeilidConfigLogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let text = match self { + Self::Off => "Off", + Self::Error => "Error", + Self::Warn => "Warn", + Self::Info => "Info", + Self::Debug => "Debug", + Self::Trace => "Trace", + }; + write!(f, "{}", text) + } +} #[derive( Default, @@ -537,6 +586,7 @@ impl Default for VeilidConfigLogLevel { RkyvArchive, RkyvSerialize, RkyvDeserialize, + JsonSchema, )] pub struct VeilidConfigInner { pub program_name: String, @@ -729,7 +779,7 @@ impl VeilidConfig { self.inner.read() } - fn safe_config(&self) -> VeilidConfigInner { + pub fn safe_config(&self) -> VeilidConfigInner { let mut safe_cfg = self.inner.read().clone(); // Remove secrets @@ -737,7 +787,6 @@ impl VeilidConfig { safe_cfg.protected_store.device_encryption_key_password = "".to_owned(); safe_cfg.protected_store.new_device_encryption_key_password = None; - safe_cfg } @@ -753,6 +802,11 @@ impl VeilidConfig { let out = f(&mut editedinner)?; // Validate Self::validate(&mut editedinner)?; + // See if things have changed + if *inner == editedinner { + // No changes, return early + return Ok(out); + } // Commit changes *inner = editedinner.clone(); out diff --git a/veilid-flutter/lib/veilid_config.dart b/veilid-flutter/lib/veilid_config.dart index 8633d3e2..86a32406 100644 --- a/veilid-flutter/lib/veilid_config.dart +++ b/veilid-flutter/lib/veilid_config.dart @@ -856,7 +856,8 @@ class VeilidConfigProtectedStore { directory = json['directory'], delete = json['delete'], deviceEncryptionKeyPassword = json['device_encryption_key_password'], - newDeviceEncryptionKeyPassword = json['new_device_encryption_key_password']; + newDeviceEncryptionKeyPassword = + json['new_device_encryption_key_password']; } //////////// diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 5f17e7ec..f39773e8 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -797,7 +797,7 @@ class VeilidTableDBTransactionFFI extends VeilidTableDBTransaction { } @override - Future delete(int col, Uint8List key) { + Future delete(int col, Uint8List key) { final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); final recvPort = ReceivePort("veilid_table_db_transaction_delete"); @@ -888,7 +888,7 @@ class VeilidTableDBFFI extends VeilidTableDB { } @override - Future delete(int col, Uint8List key) { + Future delete(int col, Uint8List key) async { final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); final recvPort = ReceivePort("veilid_table_db_delete"); @@ -899,7 +899,11 @@ class VeilidTableDBFFI extends VeilidTableDB { col, nativeEncodedKey, ); - return processFuturePlain(recvPort.first); + String? out = await processFuturePlain(recvPort.first); + if (out == null) { + return null; + } + return base64UrlNoPadDecode(out); } } @@ -1556,12 +1560,12 @@ class VeilidFFI implements Veilid { } @override - Future appCallReply(String id, Uint8List message) { - final nativeId = id.toNativeUtf8(); + Future appCallReply(String call_id, Uint8List message) { + final nativeCallId = call_id.toNativeUtf8(); final nativeEncodedMessage = base64UrlNoPadEncode(message).toNativeUtf8(); final recvPort = ReceivePort("app_call_reply"); final sendPort = recvPort.sendPort; - _appCallReply(sendPort.nativePort, nativeId, nativeEncodedMessage); + _appCallReply(sendPort.nativePort, nativeCallId, nativeEncodedMessage); return processFutureVoid(recvPort.first); } diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 8a323516..f1c5f84b 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -368,7 +368,7 @@ class VeilidTableDBTransactionJS extends VeilidTableDBTransaction { } @override - Future delete(int col, Uint8List key) { + Future delete(int col, Uint8List key) { final encodedKey = base64UrlNoPadEncode(key); return _wrapApiPromise(js_util.callMethod( @@ -580,10 +580,10 @@ class VeilidJS implements Veilid { } @override - Future appCallReply(String id, Uint8List message) { + Future appCallReply(String callId, Uint8List message) { var encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise( - js_util.callMethod(wasm, "app_call_reply", [id, encodedMessage])); + js_util.callMethod(wasm, "app_call_reply", [callId, encodedMessage])); } @override diff --git a/veilid-flutter/lib/veilid_state.dart b/veilid-flutter/lib/veilid_state.dart index fea12cdf..8d90dbff 100644 --- a/veilid-flutter/lib/veilid_state.dart +++ b/veilid-flutter/lib/veilid_state.dart @@ -262,7 +262,9 @@ abstract class VeilidUpdate { case "AppCall": { return VeilidAppCall( - sender: json["sender"], message: json["message"], id: json["id"]); + sender: json["sender"], + message: json["message"], + callId: json["call_id"]); } case "Attachment": { @@ -348,22 +350,22 @@ class VeilidAppMessage implements VeilidUpdate { class VeilidAppCall implements VeilidUpdate { final String? sender; final Uint8List message; - final String id; + final String callId; // VeilidAppCall({ required this.sender, required this.message, - required this.id, + required this.callId, }); @override Map toJson() { return { - 'kind': "AppMessage", + 'kind': "AppCall", 'sender': sender, 'message': base64UrlNoPadEncode(message), - 'id': id, + 'call_id': callId, }; } } @@ -511,16 +513,17 @@ class VeilidStateNetwork { /// VeilidStateConfig class VeilidStateConfig { - final Map config; + final VeilidConfig config; VeilidStateConfig({ required this.config, }); - VeilidStateConfig.fromJson(dynamic json) : config = json['config']; + VeilidStateConfig.fromJson(dynamic json) + : config = VeilidConfig.fromJson(json['config']); Map toJson() { - return {'config': config}; + return {'config': config.toJson()}; } } diff --git a/veilid-flutter/lib/veilid_table_db.dart b/veilid-flutter/lib/veilid_table_db.dart index 84923fd0..19bee7f2 100644 --- a/veilid-flutter/lib/veilid_table_db.dart +++ b/veilid-flutter/lib/veilid_table_db.dart @@ -8,7 +8,7 @@ abstract class VeilidTableDBTransaction { Future commit(); Future rollback(); Future store(int col, Uint8List key, Uint8List value); - Future delete(int col, Uint8List key); + Future delete(int col, Uint8List key); Future storeJson(int col, Uint8List key, Object? object, {Object? Function(Object? nonEncodable)? toEncodable}) async { diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index d18d0e30..2a9b65a7 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -707,21 +707,21 @@ pub extern "C" fn release_private_route(port: i64, route_id: FfiStr) { } #[no_mangle] -pub extern "C" fn app_call_reply(port: i64, id: FfiStr, message: FfiStr) { - let id = id.into_opt_string().unwrap_or_default(); +pub extern "C" fn app_call_reply(port: i64, call_id: FfiStr, message: FfiStr) { + let call_id = call_id.into_opt_string().unwrap_or_default(); let message = message.into_opt_string().unwrap_or_default(); DartIsolateWrapper::new(port).spawn_result(async move { - let id = match id.parse() { + let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(e, "id", id)) + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(e, "call_id", call_id)) } }; let message = data_encoding::BASE64URL_NOPAD .decode(message.as_bytes()) .map_err(|e| veilid_core::VeilidAPIError::invalid_argument(e, "message", message))?; let veilid_api = get_veilid_api().await?; - veilid_api.app_call_reply(id, message).await?; + veilid_api.app_call_reply(call_id, message).await?; APIRESULT_VOID }); } @@ -908,8 +908,8 @@ pub extern "C" fn table_db_transaction_delete(port: i64, id: u32, col: u32, key: tdbt.clone() }; - let out = tdbt.delete(col, &key); - APIResult::Ok(out) + tdbt.delete(col, &key); + APIRESULT_VOID }); } diff --git a/veilid-python/.gitignore b/veilid-python/.gitignore new file mode 100644 index 00000000..68bc17f9 --- /dev/null +++ b/veilid-python/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/veilid-python/README.md b/veilid-python/README.md new file mode 100644 index 00000000..868cd237 --- /dev/null +++ b/veilid-python/README.md @@ -0,0 +1,25 @@ +# Veilid Bindings for Python + +## Usage + +To use: +``` +poetry add veilid_python +``` +or +``` +pip3 install veilid_python +``` + + +## Development + +To run tests: +``` +poetry run pytest +``` + +To update schema for validation with the latest copy from a running `veilid-server`: +``` +./update_schema.sh +``` diff --git a/veilid-python/poetry.lock b/veilid-python/poetry.lock new file mode 100644 index 00000000..85a37c9b --- /dev/null +++ b/veilid-python/poetry.lock @@ -0,0 +1,165 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jsonschema" +version = "4.17.3" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "pytest" +version = "7.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, + {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.21.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, + {file = "pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "03a349f63b3d28e64191b6dd845333914827806332b52f5c52ccbd2863c93b4b" diff --git a/veilid-python/pyproject.toml b/veilid-python/pyproject.toml new file mode 100644 index 00000000..7b59ddd4 --- /dev/null +++ b/veilid-python/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "veilid-python" +version = "0.1.0" +description = "" +authors = ["Christien Rioux "] +readme = "README.md" +packages = [{include = "veilid"}] + +[tool.poetry.dependencies] +python = "^3.11" +jsonschema = "^4.17.3" + +[tool.poetry.group.dev.dependencies] +pytest = "^7.3.2" +pytest-asyncio = "^0.21.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/veilid-python/tests/__init__.py b/veilid-python/tests/__init__.py new file mode 100644 index 00000000..428f1e4c --- /dev/null +++ b/veilid-python/tests/__init__.py @@ -0,0 +1,34 @@ +from typing import Callable, Awaitable +import os +import pytest +pytest_plugins = ('pytest_asyncio',) + +import veilid + + +################################################################## +VEILID_SERVER = os.getenv("VEILID_SERVER") +if VEILID_SERVER is not None: + vsparts = VEILID_SERVER.split(":") + VEILID_SERVER = vsparts[0] + if len(vsparts) == 2: + VEILID_SERVER_PORT = int(vsparts[1]) + else: + VEILID_SERVER_PORT = 5959 +else: + VEILID_SERVER = "localhost" + VEILID_SERVER_PORT = 5959 + +################################################################## + +async def simple_connect_and_run(func: Callable[[veilid.VeilidAPI], Awaitable]): + api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, simple_update_callback) + async with api: + + # purge routes to ensure we start fresh + await api.debug("purge routes") + + await func(api) + +async def simple_update_callback(update: veilid.VeilidUpdate): + print("VeilidUpdate: {}".format(update)) diff --git a/veilid-python/tests/test_basic.py b/veilid-python/tests/test_basic.py new file mode 100644 index 00000000..b2c44b23 --- /dev/null +++ b/veilid-python/tests/test_basic.py @@ -0,0 +1,39 @@ +# Basic veilid tests + +import veilid +import pytest +from . import * + +################################################################## + +@pytest.mark.asyncio +async def test_connect(): + async def func(api: veilid.VeilidAPI): + pass + await simple_connect_and_run(func) + + +@pytest.mark.asyncio +async def test_get_node_id(): + async def func(api: veilid.VeilidAPI): + # get our own node id + state = await api.get_state() + node_id = state.config.config.network.routing_table.node_id.pop() + await simple_connect_and_run(func) + +@pytest.mark.asyncio +async def test_fail_connect(): + with pytest.raises(Exception): + api = await veilid.json_api_connect("fuahwelifuh32luhwafluehawea", 1, simple_update_callback) + async with api: + pass + +@pytest.mark.asyncio +async def test_version(): + async def func(api: veilid.VeilidAPI): + v = await api.veilid_version() + print("veilid_version: {}".format(v.__dict__)) + vstr = await api.veilid_version_string() + print("veilid_version_string: {}".format(vstr)) + await simple_connect_and_run(func) + diff --git a/veilid-python/tests/test_crypto.py b/veilid-python/tests/test_crypto.py new file mode 100644 index 00000000..489beb20 --- /dev/null +++ b/veilid-python/tests/test_crypto.py @@ -0,0 +1,42 @@ +# Crypto veilid tests + +import veilid +import pytest +from . import * + +################################################################## + +@pytest.mark.asyncio +async def test_best_crypto_system(): + async def func(api: veilid.VeilidAPI): + bcs = await api.best_crypto_system() + await simple_connect_and_run(func) + +@pytest.mark.asyncio +async def test_get_crypto_system(): + async def func(api: veilid.VeilidAPI): + cs = await api.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0) + # clean up handle early + del cs + await simple_connect_and_run(func) + +@pytest.mark.asyncio +async def test_get_crypto_system_invalid(): + async def func(api: veilid.VeilidAPI): + with pytest.raises(veilid.VeilidAPIError): + cs = await api.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_NONE) + await simple_connect_and_run(func) + +@pytest.mark.asyncio +async def test_hash_and_verify_password(): + async def func(api: veilid.VeilidAPI): + bcs = await api.best_crypto_system() + nonce = await bcs.random_nonce() + salt = nonce.to_bytes() + # Password match + phash = await bcs.hash_password(b"abc123", salt) + assert await bcs.verify_password(b"abc123", phash) + # Password mismatch + phash2 = await bcs.hash_password(b"abc1234", salt) + assert not await bcs.verify_password(b"abc12345", phash) + await simple_connect_and_run(func) diff --git a/veilid-python/tests/test_routing_context.py b/veilid-python/tests/test_routing_context.py new file mode 100644 index 00000000..d1842907 --- /dev/null +++ b/veilid-python/tests/test_routing_context.py @@ -0,0 +1,93 @@ +# Routing context veilid tests + +import veilid +import pytest +import asyncio +import json +from . import * + +################################################################## + +@pytest.mark.asyncio +async def test_routing_contexts(): + async def func(api: veilid.VeilidAPI): + rc = await api.new_routing_context() + rcp = await rc.with_privacy() + rcps = await rcp.with_sequencing(veilid.Sequencing.ENSURE_ORDERED) + rcpsr = await rcps.with_custom_privacy(veilid.Stability.RELIABLE) + await simple_connect_and_run(func) + +@pytest.mark.asyncio +async def test_routing_context_app_message_loopback(): + + app_message_queue = asyncio.Queue() + + async def app_message_queue_update_callback(update: veilid.VeilidUpdate): + if update.kind == veilid.VeilidUpdateKind.APP_MESSAGE: + await app_message_queue.put(update) + + api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, app_message_queue_update_callback) + async with api: + + # purge routes to ensure we start fresh + await api.debug("purge routes") + + # make a routing context that uses a safety route + rc = await (await api.new_routing_context()).with_privacy() + + # make a new local private route + prl, blob = await api.new_private_route() + + # import it as a remote route as well so we can send to it + prr = await api.import_remote_private_route(blob) + + # send an app message to our own private route + message = b"abcd1234" + await rc.app_message(prr, message) + + # we should get the same message back + update: veilid.VeilidUpdate = await asyncio.wait_for(app_message_queue.get(), timeout=10) + appmsg: veilid.VeilidAppMessage = update.detail + assert appmsg.message == message + + +@pytest.mark.asyncio +async def test_routing_context_app_call_loopback(): + + app_call_queue = asyncio.Queue() + + async def app_call_queue_update_callback(update: veilid.VeilidUpdate): + if update.kind == veilid.VeilidUpdateKind.APP_CALL: + await app_call_queue.put(update) + + api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, app_call_queue_update_callback) + async with api: + + # purge routes to ensure we start fresh + await api.debug("purge routes") + + # make a routing context that uses a safety route + rc = await (await api.new_routing_context()).with_privacy() + + # make a new local private route + prl, blob = await api.new_private_route() + + # import it as a remote route as well so we can send to it + prr = await api.import_remote_private_route(blob) + + # send an app message to our own private route + request = b"abcd1234" + app_call_task = asyncio.create_task(rc.app_call(prr, request), name = "app call task") + + # we should get the same request back + update: veilid.VeilidUpdate = await asyncio.wait_for(app_call_queue.get(), timeout=10) + appcall: veilid.VeilidAppCall = update.detail + assert appcall.message == request + + # now we reply to the request + reply = b"qwer5678" + await api.app_call_reply(appcall.call_id, reply) + + # now we should get the reply from the call + result = await app_call_task + assert result == reply diff --git a/veilid-python/update_schema.sh b/veilid-python/update_schema.sh new file mode 100755 index 00000000..41f2366c --- /dev/null +++ b/veilid-python/update_schema.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -eo pipefail +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +VEILID_SERVER=$SCRIPTDIR/../target/debug/veilid-server + +# Ensure executable exists +if [ ! -f "$VEILID_SERVER" ]; then + echo "$VEILID_SERVER does not exist. Build with cargo build." + exit 1 +fi + +# Produce schema from veilid-server +$VEILID_SERVER --emit-schema Request > $SCRIPTDIR/veilid/schema/Request.json +$VEILID_SERVER --emit-schema RecvMessage > $SCRIPTDIR/veilid/schema/RecvMessage.json + + diff --git a/veilid-python/veilid/__init__.py b/veilid-python/veilid/__init__.py new file mode 100644 index 00000000..be213c07 --- /dev/null +++ b/veilid-python/veilid/__init__.py @@ -0,0 +1,6 @@ +from .api import * +from .config import * +from .error import * +from .json_api import * +from .error import * +from .types import * diff --git a/veilid-python/veilid/api.py b/veilid-python/veilid/api.py new file mode 100644 index 00000000..f201a324 --- /dev/null +++ b/veilid-python/veilid/api.py @@ -0,0 +1,211 @@ +from abc import ABC, abstractmethod +from typing import Self + +from .state import * +from .config import * +from .error import * +from .types import * + +class RoutingContext(ABC): + @abstractmethod + async def with_privacy(self) -> Self: + pass + @abstractmethod + async def with_custom_privacy(self, stability: Stability) -> Self: + pass + @abstractmethod + async def with_sequencing(self, sequencing: Sequencing) -> Self: + pass + @abstractmethod + async def app_call(self, target: TypedKey | RouteId, request: bytes) -> bytes: + pass + @abstractmethod + async def app_message(self, target: TypedKey | RouteId, message: bytes): + pass + @abstractmethod + async def create_dht_record(self, kind: CryptoKind, schema: DHTSchema) -> DHTRecordDescriptor: + pass + @abstractmethod + async def open_dht_record(self, key: TypedKey, writer: Optional[KeyPair]) -> DHTRecordDescriptor: + pass + @abstractmethod + async def close_dht_record(self, key: TypedKey): + pass + @abstractmethod + async def delete_dht_record(self, key: TypedKey): + pass + @abstractmethod + async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]: + pass + @abstractmethod + async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]: + pass + @abstractmethod + async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp: + pass + @abstractmethod + async def cancel_dht_watch(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool: + pass + + +class TableDbTransaction(ABC): + @abstractmethod + async def commit(self): + pass + @abstractmethod + async def rollback(self): + pass + @abstractmethod + async def store(self, col: int, key: bytes, value: bytes): + pass + @abstractmethod + async def delete(self, col: int, key: bytes): + pass + +class TableDb(ABC): + @abstractmethod + async def get_column_count(self) -> int: + pass + @abstractmethod + async def get_keys(self, col: int) -> list[bytes]: + pass + @abstractmethod + async def transact(self) -> TableDbTransaction: + pass + @abstractmethod + async def store(self, col: int, key: bytes, value: bytes): + pass + @abstractmethod + async def load(self, col: int, key: bytes) -> Optional[bytes]: + pass + @abstractmethod + async def delete(self, col: int, key: bytes) -> Optional[bytes]: + pass + +class CryptoSystem(ABC): + @abstractmethod + async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: + pass + @abstractmethod + async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: + pass + @abstractmethod + async def random_bytes(self, len: int) -> bytes: + pass + @abstractmethod + async def default_salt_length(self) -> int: + pass + @abstractmethod + async def hash_password(self, password: bytes, salt: bytes) -> str: + pass + @abstractmethod + async def verify_password(self, password: bytes, password_hash: str) -> bool: + pass + @abstractmethod + async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret: + pass + @abstractmethod + async def random_nonce(self) -> Nonce: + pass + @abstractmethod + async def random_shared_secret(self) -> SharedSecret: + pass + @abstractmethod + async def generate_key_pair(self) -> KeyPair: + pass + @abstractmethod + async def generate_hash(self, data: bytes) -> HashDigest: + pass + @abstractmethod + async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool: + pass + @abstractmethod + async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool: + pass + @abstractmethod + async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance: + pass + @abstractmethod + async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature: + pass + @abstractmethod + async def verify(self, key: PublicKey, data: bytes, signature: Signature): + pass + @abstractmethod + async def aead_overhead(self) -> int: + pass + @abstractmethod + async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: + pass + @abstractmethod + async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: + pass + @abstractmethod + async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes: + pass + + +class VeilidAPI(ABC): + @abstractmethod + async def control(self, args: list[str]) -> str: + pass + @abstractmethod + async def get_state(self) -> VeilidState: + pass + @abstractmethod + async def attach(self): + pass + @abstractmethod + async def detach(self): + pass + @abstractmethod + async def new_private_route(self) -> Tuple[RouteId, bytes]: + pass + @abstractmethod + async def new_custom_private_route(self, kinds: list[CryptoKind], stability: Stability, sequencing: Sequencing) -> Tuple[RouteId, bytes]: + pass + @abstractmethod + async def import_remote_private_route(self, blob: bytes) -> RouteId: + pass + @abstractmethod + async def release_private_route(self, route_id: RouteId): + pass + @abstractmethod + async def app_call_reply(self, call_id: OperationId, message: bytes): + pass + @abstractmethod + async def new_routing_context(self) -> RoutingContext: + pass + @abstractmethod + async def open_table_db(self, name: str, column_count: int) -> TableDb: + pass + @abstractmethod + async def delete_table_db(self, name: str): + pass + @abstractmethod + async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem: + pass + @abstractmethod + async def best_crypto_system(self) -> CryptoSystem: + pass + @abstractmethod + async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]: + pass + @abstractmethod + async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]: + pass + @abstractmethod + async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]: + pass + @abstractmethod + async def now(self) -> Timestamp: + pass + @abstractmethod + async def debug(self, command: str) -> str: + pass + @abstractmethod + async def veilid_version_string(self) -> str: + pass + @abstractmethod + async def veilid_version(self) -> VeilidVersion: + pass diff --git a/veilid-python/veilid/config.py b/veilid-python/veilid/config.py new file mode 100644 index 00000000..d9394c4e --- /dev/null +++ b/veilid-python/veilid/config.py @@ -0,0 +1,553 @@ +from typing import Self, Optional +from enum import StrEnum +from json import dumps + +from .types import * + +class VeilidConfigLogLevel(StrEnum): + OFF = 'Off' + ERROR = 'Error' + WARN = 'Warn' + INFO = 'Info' + DEBUG = 'Debug' + TRACE = 'Trace' + +class VeilidConfigCapabilities: + protocol_udp: bool + protocol_connect_tcp: bool + protocol_accept_tcp: bool + protocol_connect_ws: bool + protocol_accept_ws: bool + protocol_connect_wss: bool + protocol_accept_wss: bool + + def __init__(self, protocol_udp: bool, protocol_connect_tcp: bool, protocol_accept_tcp: bool, + protocol_connect_ws: bool, protocol_accept_ws: bool, protocol_connect_wss: bool, protocol_accept_wss: bool): + + self.protocol_udp = protocol_udp + self.protocol_connect_tcp = protocol_connect_tcp + self.protocol_accept_tcp = protocol_accept_tcp + self.protocol_connect_ws = protocol_connect_ws + self.protocol_accept_ws = protocol_accept_ws + self.protocol_connect_wss = protocol_connect_wss + self.protocol_accept_wss = protocol_accept_wss + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigCapabilities(j['protocol_udp'], + j['protocol_connect_tcp'], + j['protocol_accept_tcp'], + j['protocol_connect_ws'], + j['protocol_accept_ws'], + j['protocol_connect_wss'], + j['protocol_accept_wss']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigProtectedStore: + allow_insecure_fallback: bool + always_use_insecure_storage: bool + directory: str + delete: bool + device_encryption_key_password: str + new_device_encryption_key_password: Optional[str] + + def __init__(self, allow_insecure_fallback: bool, always_use_insecure_storage: bool, + directory: str, delete: bool, device_encryption_key_password: str, new_device_encryption_key_password: Optional[str]): + + self.allow_insecure_fallback = allow_insecure_fallback + self.always_use_insecure_storage = always_use_insecure_storage + self.directory = directory + self.delete = delete + self.device_encryption_key_password = device_encryption_key_password + self.new_device_encryption_key_password = new_device_encryption_key_password + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigProtectedStore(j['allow_insecure_fallback'], j['always_use_insecure_storage'], + j['directory'], j['delete'], j['device_encryption_key_password'], j['new_device_encryption_key_password']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigTableStore: + directory: str + delete: bool + + def __init__(self, directory: str, delete: bool): + self.directory = directory + self.delete = delete + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigTableStore(j['directory'], j['delete']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigBlockStore: + directory: str + delete: bool + + def __init__(self, directory: str, delete: bool): + self.directory = directory + self.delete = delete + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigBlockStore(j['directory'], j['delete']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigRoutingTable: + node_id: list[TypedKey] + node_id_secret: list[TypedSecret] + bootstrap: list[str] + limit_over_attached: int + limit_fully_attached: int + limit_attached_strong: int + limit_attached_good: int + limit_attached_weak: int + + def __init__(self, node_id: list[TypedKey], node_id_secret: list[TypedSecret], bootstrap: list[str], limit_over_attached: int, + limit_fully_attached: int, limit_attached_strong: int, limit_attached_good: int, limit_attached_weak: int): + + self.node_id = node_id + self.node_id_secret = node_id_secret + self.bootstrap = bootstrap + self.limit_over_attached = limit_over_attached + self.limit_fully_attached = limit_fully_attached + self.limit_attached_strong = limit_attached_strong + self.limit_attached_good = limit_attached_good + self.limit_attached_weak = limit_attached_weak + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigRoutingTable( + list(map(lambda x: TypedKey(x), j['node_id'])), + list(map(lambda x: TypedSecret(x), j['node_id_secret'])), + j['bootstrap'], + j['limit_over_attached'], + j['limit_fully_attached'], + j['limit_attached_strong'], + j['limit_attached_good'], + j['limit_attached_weak']) + def to_json(self) -> dict: + return self.__dict__ + + +class VeilidConfigRPC: + concurrency: int + queue_size: int + max_timestamp_behind_ms: Optional[int] + max_timestamp_ahead_ms: Optional[int] + timeout_ms: int + max_route_hop_count: int + default_route_hop_count: int + + def __init__(self, concurrency: int, queue_size: int, max_timestamp_behind_ms: Optional[int], max_timestamp_ahead_ms: Optional[int], + timeout_ms: int, max_route_hop_count: int, default_route_hop_count: int): + + self.concurrency = concurrency + self.queue_size = queue_size + self.max_timestamp_behind_ms = max_timestamp_behind_ms + self.max_timestamp_ahead_ms = max_timestamp_ahead_ms + self.timeout_ms = timeout_ms + self.max_route_hop_count = max_route_hop_count + self.default_route_hop_count = default_route_hop_count + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigRPC( + j['concurrency'], + j['queue_size'], + j['max_timestamp_behind_ms'], + j['max_timestamp_ahead_ms'], + j['timeout_ms'], + j['max_route_hop_count'], + j['default_route_hop_count']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigDHT: + max_find_node_count: int + resolve_node_timeout_ms: int + resolve_node_count: int + resolve_node_fanout: int + get_value_timeout_ms: int + get_value_count: int + get_value_fanout: int + set_value_timeout_ms: int + set_value_count: int + set_value_fanout: int + min_peer_count: int + min_peer_refresh_time_ms: int + validate_dial_info_receipt_time_ms: int + local_subkey_cache_size: int + local_max_subkey_cache_memory_mb: int + remote_subkey_cache_size: int + remote_max_records: int + remote_max_subkey_cache_memory_mb: int + remote_max_storage_space_mb: int + + def __init__(self, max_find_node_count: int, resolve_node_timeout_ms: int, resolve_node_count: int, + resolve_node_fanout: int, get_value_timeout_ms: int, get_value_count: int, get_value_fanout: int, + set_value_timeout_ms: int, set_value_count: int, set_value_fanout: int, + min_peer_count: int, min_peer_refresh_time_ms: int, validate_dial_info_receipt_time_ms: int, + local_subkey_cache_size: int, local_max_subkey_cache_memory_mb: int, + remote_subkey_cache_size: int, remote_max_records: int, remote_max_subkey_cache_memory_mb: int, remote_max_storage_space_mb: int): + + self.max_find_node_count = max_find_node_count + self.resolve_node_timeout_ms =resolve_node_timeout_ms + self.resolve_node_count = resolve_node_count + self.resolve_node_fanout = resolve_node_fanout + self.get_value_timeout_ms = get_value_timeout_ms + self.get_value_count = get_value_count + self.get_value_fanout = get_value_fanout + self.set_value_timeout_ms = set_value_timeout_ms + self.set_value_count = set_value_count + self.set_value_fanout = set_value_fanout + self.min_peer_count = min_peer_count + self.min_peer_refresh_time_ms = min_peer_refresh_time_ms + self.validate_dial_info_receipt_time_ms = validate_dial_info_receipt_time_ms + self.local_subkey_cache_size = local_subkey_cache_size + self.local_max_subkey_cache_memory_mb = local_max_subkey_cache_memory_mb + self.remote_subkey_cache_size = remote_subkey_cache_size + self.remote_max_records = remote_max_records + self.remote_max_subkey_cache_memory_mb = remote_max_subkey_cache_memory_mb + self.remote_max_storage_space_mb = remote_max_storage_space_mb + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigDHT( + j['max_find_node_count'], + j['resolve_node_timeout_ms'], + j['resolve_node_count'], + j['resolve_node_fanout'], + j['get_value_timeout_ms'], + j['get_value_count'], + j['get_value_fanout'], + j['set_value_timeout_ms'], + j['set_value_count'], + j['set_value_fanout'], + j['min_peer_count'], + j['min_peer_refresh_time_ms'], + j['validate_dial_info_receipt_time_ms'], + j['local_subkey_cache_size'], + j['local_max_subkey_cache_memory_mb'], + j['remote_subkey_cache_size'], + j['remote_max_records'], + j['remote_max_subkey_cache_memory_mb'], + j['remote_max_storage_space_mb']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigTLS: + certificate_path: str + private_key_path: str + connection_initial_timeout_ms: int + + def __init__(self, certificate_path: str, private_key_path: str, connection_initial_timeout_ms: int): + self.certificate_path = certificate_path + self.private_key_path = private_key_path + self.connection_initial_timeout_ms = connection_initial_timeout_ms + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigTLS( + j['certificate_path'], + j['private_key_path'], + j['connection_initial_timeout_ms']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigHTTPS: + enabled: bool + listen_address: str + path: str + url: Optional[str] + + def __init__(self, enabled: bool, listen_address: str, path: str, url: Optional[str]): + self.enabled = enabled + self.listen_address = listen_address + self.path = path + self.url = url + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigHTTPS( + j['enabled'], + j['listen_address'], + j['path'], + j['url']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigHTTP: + enabled: bool + listen_address: str + path: str + url: Optional[str] + + def __init__(self, enabled: bool, listen_address: str, path: str, url: Optional[str]): + self.enabled = enabled + self.listen_address = listen_address + self.path = path + self.url = url + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigHTTP( + j['enabled'], + j['listen_address'], + j['path'], + j['url']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigApplication: + https: VeilidConfigHTTPS + http: VeilidConfigHTTP + + def __init__(self, https: VeilidConfigHTTPS, http: VeilidConfigHTTP): + self.https = https + self.http = http + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigApplication( + VeilidConfigHTTPS.from_json(j['https']), + VeilidConfigHTTP.from_json(j['http'])) + def to_json(self) -> dict: + return self.__dict__ + + +class VeilidConfigUDP: + enabled: bool + socket_pool_size: int + listen_address: str + public_address: Optional[str] + + def __init__(self, enabled: bool, socket_pool_size: int, listen_address: str, public_address: Optional[str]): + self.enabled = enabled + self.socket_pool_size = socket_pool_size + self.listen_address = listen_address + self.public_address = public_address + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigUDP( + j['enabled'], + j['socket_pool_size'], + j['listen_address'], + j['public_address']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigTCP: + connect: bool + listen: bool + max_connections: int + listen_address: str + public_address: Optional[str] + + def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, public_address: Optional[str]): + self.connect = connect + self.listen = listen + self.max_connections = max_connections + self.listen_address = listen_address + self.public_address = public_address + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigTCP( + j['connect'], + j['listen'], + j['max_connections'], + j['listen_address'], + j['public_address']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigWS: + connect: bool + listen: bool + max_connections: int + listen_address: str + path: str + url: Optional[str] + + def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, path: str, url: Optional[str]): + self.connect = connect + self.listen = listen + self.max_connections = max_connections + self.listen_address = listen_address + self.path = path + self.url = url + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigWS( + j['connect'], + j['listen'], + j['max_connections'], + j['listen_address'], + j['path'], + j['url']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigWSS: + connect: bool + listen: bool + max_connections: int + listen_address: str + path: str + url: Optional[str] + + def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, path: str, url: Optional[str]): + self.connect = connect + self.listen = listen + self.max_connections = max_connections + self.listen_address = listen_address + self.path = path + self.url = url + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigWSS( + j['connect'], + j['listen'], + j['max_connections'], + j['listen_address'], + j['path'], + j['url']) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfigProtocol: + udp: VeilidConfigUDP + tcp: VeilidConfigTCP + ws: VeilidConfigWS + wss: VeilidConfigWSS + + def __init__(self, udp: VeilidConfigUDP, tcp: VeilidConfigTCP, ws: VeilidConfigWS, wss: VeilidConfigWSS): + self.udp = udp + self.tcp = tcp + self.ws = ws + self.wss = wss + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigProtocol( + VeilidConfigUDP.from_json(j['udp']), + VeilidConfigTCP.from_json(j['tcp']), + VeilidConfigWS.from_json(j['ws']), + VeilidConfigWSS.from_json(j['wss'])) + def to_json(self) -> dict: + return self.__dict__ + + +class VeilidConfigNetwork: + connection_initial_timeout_ms: int + connection_inactivity_timeout_ms: int + max_connections_per_ip4: int + max_connections_per_ip6_prefix: int + max_connections_per_ip6_prefix_size: int + max_connection_frequency_per_min: int + client_whitelist_timeout_ms: int + reverse_connection_receipt_time_ms: int + hole_punch_receipt_time_ms: int + routing_table: VeilidConfigRoutingTable + rpc: VeilidConfigRPC + dht: VeilidConfigDHT + upnp: bool + detect_address_changes: bool + restricted_nat_retries: int + tls: VeilidConfigTLS + application: VeilidConfigApplication + protocol: VeilidConfigProtocol + + def __init__(self, connection_initial_timeout_ms: int, connection_inactivity_timeout_ms: int, + max_connections_per_ip4: int, max_connections_per_ip6_prefix: int, + max_connections_per_ip6_prefix_size: int, max_connection_frequency_per_min: int, + client_whitelist_timeout_ms: int, reverse_connection_receipt_time_ms: int, + hole_punch_receipt_time_ms: int, routing_table: VeilidConfigRoutingTable, + rpc: VeilidConfigRPC, dht: VeilidConfigDHT, upnp: bool, detect_address_changes: bool, + restricted_nat_retries: int, tls: VeilidConfigTLS, application: VeilidConfigApplication, protocol: VeilidConfigProtocol): + + self.connection_initial_timeout_ms = connection_initial_timeout_ms + self.connection_inactivity_timeout_ms = connection_inactivity_timeout_ms + self.max_connections_per_ip4 = max_connections_per_ip4 + self.max_connections_per_ip6_prefix = max_connections_per_ip6_prefix + self.max_connections_per_ip6_prefix_size = max_connections_per_ip6_prefix_size + self.max_connection_frequency_per_min = max_connection_frequency_per_min + self.client_whitelist_timeout_ms = client_whitelist_timeout_ms + self.reverse_connection_receipt_time_ms = reverse_connection_receipt_time_ms + self.hole_punch_receipt_time_ms = hole_punch_receipt_time_ms + self.routing_table = routing_table + self.rpc = rpc + self.dht = dht + self.upnp = upnp + self.detect_address_changes = detect_address_changes + self.restricted_nat_retries = restricted_nat_retries + self.tls = tls + self.application = application + self.protocol = protocol + + @staticmethod + def from_json(j: dict) -> Self: + return VeilidConfigNetwork( + j['connection_initial_timeout_ms'], + j['connection_inactivity_timeout_ms'], + j['max_connections_per_ip4'], + j['max_connections_per_ip6_prefix'], + j['max_connections_per_ip6_prefix_size'], + j['max_connection_frequency_per_min'], + j['client_whitelist_timeout_ms'], + j['reverse_connection_receipt_time_ms'], + j['hole_punch_receipt_time_ms'], + VeilidConfigRoutingTable.from_json(j['routing_table']), + VeilidConfigRPC.from_json(j['rpc']), + VeilidConfigDHT.from_json(j['dht']), + j['upnp'], + j['detect_address_changes'], + j['restricted_nat_retries'], + VeilidConfigTLS.from_json(j['tls']), + VeilidConfigApplication.from_json(j['application']), + VeilidConfigProtocol.from_json(j['protocol'])) + def to_json(self) -> dict: + return self.__dict__ + +class VeilidConfig: + program_name: str + namespace: str + capabilities: VeilidConfigCapabilities + protected_store: VeilidConfigProtectedStore + table_store: VeilidConfigTableStore + block_store: VeilidConfigBlockStore + network: VeilidConfigNetwork + + def __init__(self, program_name: str, namespace: str, capabilities: VeilidConfigCapabilities, + protected_store: VeilidConfigProtectedStore, table_store: VeilidConfigTableStore, + block_store: VeilidConfigBlockStore, network: VeilidConfigNetwork): + + self.program_name = program_name + self.namespace = namespace + self.capabilities = capabilities + self.protected_store = protected_store + self.table_store = table_store + self.block_store = block_store + self.network = network + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidConfig(j['program_name'], j['namespace'], + VeilidConfigCapabilities.from_json(j['capabilities']), + VeilidConfigProtectedStore.from_json(j['protected_store']), + VeilidConfigTableStore.from_json(j['table_store']), + VeilidConfigBlockStore.from_json(j['block_store']), + VeilidConfigNetwork.from_json(j['network'])) + def to_json(self) -> dict: + return self.__dict__ + + \ No newline at end of file diff --git a/veilid-python/veilid/error.py b/veilid-python/veilid/error.py new file mode 100644 index 00000000..07a242f4 --- /dev/null +++ b/veilid-python/veilid/error.py @@ -0,0 +1,142 @@ +from typing import Self, Any + +class VeilidAPIError(Exception): + """Veilid API error exception base class""" + pass + @staticmethod + def from_json(j: dict) -> Self: + match j['kind']: + case 'NotInitialized': + return VeilidAPIErrorNotInitialized() + case 'AlreadyInitialized': + return VeilidAPIErrorAlreadyInitialized() + case 'Timeout': + return VeilidAPIErrorTimeout() + case 'TryAgain': + return VeilidAPIErrorTryAgain() + case 'Shutdown': + return VeilidAPIErrorShutdown() + case 'InvalidTarget': + return VeilidAPIErrorInvalidTarget() + case 'NoConnection': + return VeilidAPIErrorNoConnection(j['message']) + case 'KeyNotFound': + return VeilidAPIErrorKeyNotFound(j['key']) + case 'Internal': + return VeilidAPIErrorInternal(j['message']) + case 'Unimplemented': + return VeilidAPIErrorUnimplemented(j['message']) + case 'ParseError': + return VeilidAPIErrorParseError(j['message'], j['value']) + case 'InvalidArgument': + return VeilidAPIErrorInvalidArgument(j['context'], j['argument'], j['value']) + case 'MissingArgument': + return VeilidAPIErrorMissingArgument(j['context'], j['argument']) + case 'Generic': + return VeilidAPIErrorGeneric(j['message']) + case _: + return VeilidAPIError("Unknown exception type: {}".format(j['kind'])) + + +class VeilidAPIErrorNotInitialized(VeilidAPIError): + """Veilid was not initialized""" + def __init__(self): + super().__init__("Not initialized") + +class VeilidAPIErrorAlreadyInitialized(VeilidAPIError): + """Veilid was already initialized""" + def __init__(self): + super().__init__("Already initialized") + +class VeilidAPIErrorTimeout(VeilidAPIError): + """Veilid operation timed out""" + def __init__(self): + super().__init__("Timeout") + +class VeilidAPIErrorTryAgain(VeilidAPIError): + """Operation could not be performed at this time, retry again later""" + def __init__(self): + super().__init__("Try again") + +class VeilidAPIErrorShutdown(VeilidAPIError): + """Veilid was already shut down""" + def __init__(self): + super().__init__("Shutdown") + +class VeilidAPIErrorInvalidTarget(VeilidAPIError): + """Target of operation is not valid""" + def __init__(self): + super().__init__("Invalid target") + +class VeilidAPIErrorNoConnection(VeilidAPIError): + """Connection could not be established""" + message: str + def __init__(self, message: str): + super().__init__("No connection") + self.message = message + +class VeilidAPIErrorKeyNotFound(VeilidAPIError): + """Key was not found""" + key: str + def __init__(self, key: str): + super().__init__("Key not found") + self.key = key + +class VeilidAPIErrorInternal(VeilidAPIError): + """Veilid experienced an internal failure""" + message: str + def __init__(self, message: str): + super().__init__("Internal") + self.message = message + +class VeilidAPIErrorUnimplemented(VeilidAPIError): + """Functionality is not yet implemented""" + message: str + def __init__(self, message: str): + super().__init__("Unimplemented") + self.message = message + +class VeilidAPIErrorParseError(VeilidAPIError): + """Value was not in a parseable format""" + message: str + value: str + def __init__(self, message: str, value: str): + super().__init__("Parse error") + self.message = message + self.value = value + +class VeilidAPIErrorInvalidArgument(VeilidAPIError): + """Argument is not valid in this context""" + context: str + argument: str + value: str + def __init__(self, context: str, argument: str, value: str): + super().__init__("Invalid argument") + self.context = context + self.argument = argument + self.value = value + +class VeilidAPIErrorMissingArgument(VeilidAPIError): + """Required argument was missing""" + context: str + argument: str + def __init__(self, context: str, argument: str): + super().__init__("Missing argument") + self.context = context + self.argument = argument + +class VeilidAPIErrorGeneric(VeilidAPIError): + """Generic error message""" + message: str + def __init__(self, message: str): + super().__init__("Generic") + self.message = message + + +def raise_api_result(api_result: dict) -> Any: + if "value" in api_result: + return api_result["value"] + elif "error" in api_result: + raise VeilidAPIError.from_json(api_result["error"]) + else: + raise ValueError("Invalid format for ApiResult") \ No newline at end of file diff --git a/veilid-python/veilid/json_api.py b/veilid-python/veilid/json_api.py new file mode 100644 index 00000000..eb388fc3 --- /dev/null +++ b/veilid-python/veilid/json_api.py @@ -0,0 +1,636 @@ +import json +import asyncio +from jsonschema import validators, exceptions + +from typing import Callable, Awaitable, Mapping + +from .api import * +from .state import * +from .config import * +from .error import * +from .types import * +from .operations import * + +############################################################## + +import importlib.resources as importlib_resources +from . import schema + +def _get_schema_validator(schema): + cls = validators.validator_for(schema) + cls.check_schema(schema) + validator = cls(schema) + return validator + +def _schema_validate(validator, instance): + error = exceptions.best_match(validator.iter_errors(instance)) + if error is not None: + raise error + +_VALIDATOR_REQUEST = _get_schema_validator(json.loads((importlib_resources.files(schema) / 'Request.json').read_text())) +_VALIDATOR_RECV_MESSAGE = _get_schema_validator(json.loads((importlib_resources.files(schema) / 'RecvMessage.json').read_text())) + + +############################################################## + +class _JsonVeilidAPI(VeilidAPI): + reader: asyncio.StreamReader + writer: asyncio.StreamWriter + update_callback: Callable[[VeilidUpdate], Awaitable] + handle_recv_messages_task: Optional[asyncio.Task] + validate_schemas: bool + # Shared Mutable State + lock: asyncio.Lock + next_id: int + in_flight_requests: Mapping[str, asyncio.Future] + + def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, update_callback: Callable[[VeilidUpdate], Awaitable], validate_schema: bool = True): + self.reader = reader + self.writer = writer + self.update_callback = update_callback + self.validate_schema = validate_schema + self.handle_recv_messages_task = None + self.lock = asyncio.Lock() + self.next_id = 1 + self.in_flight_requests = dict() + + async def __aenter__(self) -> Self: + return self + + async def __aexit__(self, *excinfo): + await self.close() + + async def _cleanup_close(self): + await self.lock.acquire() + try: + self.reader = None + self.writer.close() + await self.writer.wait_closed() + self.writer = None + + for (reqid, reqfuture) in self.in_flight_requests.items(): + reqfuture.cancel() + + finally: + self.lock.release() + + async def close(self): + # Take the task + await self.lock.acquire() + try: + if self.handle_recv_messages_task is None: + return + handle_recv_messages_task = self.handle_recv_messages_task + self.handle_recv_messages_task = None + finally: + self.lock.release() + # Cancel it + handle_recv_messages_task.cancel() + try: + await handle_recv_messages_task + except asyncio.CancelledError: + pass + + @staticmethod + async def connect(host: str, port: int, update_callback: Callable[[VeilidUpdate], Awaitable]) -> Self: + reader, writer = await asyncio.open_connection(host, port) + veilid_api = _JsonVeilidAPI(reader, writer, update_callback) + veilid_api.handle_recv_messages_task = asyncio.create_task(veilid_api.handle_recv_messages(), name = "JsonVeilidAPI.handle_recv_messages") + return veilid_api + + async def handle_recv_message_response(self, j: dict): + id = j['id'] + await self.lock.acquire() + try: + # Get and remove the in-flight request + reqfuture = self.in_flight_requests.pop(id, None) + finally: + self.lock.release() + # Resolve the request's future to the response json + if reqfuture is not None: + reqfuture.set_result(j) + + + async def handle_recv_messages(self): + # Read lines until we're done + try: + while True: + linebytes = await self.reader.readline() + if not linebytes.endswith(b'\n'): + break + + # Parse line as ndjson + j = json.loads(linebytes.strip()) + + if self.validate_schema: + _schema_validate(_VALIDATOR_RECV_MESSAGE, j) + + # Process the message + if j['type'] == "Response": + await self.handle_recv_message_response(j) + elif j['type'] == "Update": + await self.update_callback(VeilidUpdate.from_json(j)) + finally: + await self._cleanup_close() + + async def allocate_request_future(self, id: int) -> asyncio.Future: + reqfuture = asyncio.get_running_loop().create_future() + + await self.lock.acquire() + try: + self.in_flight_requests[id] = reqfuture + finally: + self.lock.release() + + return reqfuture + + async def cancel_request_future(self, id: int): + await self.lock.acquire() + try: + reqfuture = self.in_flight_requests.pop(id, None) + reqfuture.cancel() + finally: + self.lock.release() + + def send_one_way_ndjson_request(self, op: Operation, **kwargs): + + if self.writer is None: + return + + # Make NDJSON string for request + # Always use id 0 because no reply will be received for one-way requests + req = { "id": 0, "op": op } + for k, v in kwargs.items(): + req[k] = v + reqstr = VeilidJSONEncoder.dumps(req) + "\n" + reqbytes = reqstr.encode() + + if self.validate_schema: + _schema_validate(_VALIDATOR_REQUEST, json.loads(reqbytes)) + + # Send to socket without waitings + self.writer.write(reqbytes) + + async def send_ndjson_request(self, op: Operation, validate: Optional[Callable[[dict, dict], None]] = None, **kwargs) -> dict: + + # Get next id + await self.lock.acquire() + try: + id = self.next_id + self.next_id += 1 + writer = self.writer + finally: + self.lock.release() + + # Make NDJSON string for request + req = { "id": id, "op": op } + for k, v in kwargs.items(): + req[k] = v + reqstr = VeilidJSONEncoder.dumps(req) + "\n" + reqbytes = reqstr.encode() + + if self.validate_schema: + _schema_validate(_VALIDATOR_REQUEST, json.loads(reqbytes)) + + # Allocate future for request + reqfuture = await self.allocate_request_future(id) + + # Send to socket + try: + writer.write(reqbytes) + await writer.drain() + except: + # Send failed, release future + await self.cancel_request_future(id) + raise + + # Wait for response + response = await reqfuture + + # Validate if we have a validator + if response["op"] != req["op"]: + raise ValueError("Response op does not match request op") + if validate is not None: + validate(req, response) + + return response + + async def control(self, args: list[str]) -> str: + return raise_api_result(await self.send_ndjson_request(Operation.CONTROL, args = args)) + async def get_state(self) -> VeilidState: + return VeilidState.from_json(raise_api_result(await self.send_ndjson_request(Operation.GET_STATE))) + async def attach(self): + raise_api_result(await self.send_ndjson_request(Operation.ATTACH)) + async def detach(self): + raise_api_result(await self.send_ndjson_request(Operation.DETACH)) + async def new_private_route(self) -> Tuple[RouteId, bytes]: + return NewPrivateRouteResult.from_json(raise_api_result(await self.send_ndjson_request(Operation.NEW_PRIVATE_ROUTE))).to_tuple() + async def new_custom_private_route(self, kinds: list[CryptoKind], stability: Stability, sequencing: Sequencing) -> Tuple[RouteId, bytes]: + return NewPrivateRouteResult.from_json(raise_api_result( + await self.send_ndjson_request(Operation.NEW_CUSTOM_PRIVATE_ROUTE, + kinds = kinds, + stability = stability, + sequencing = sequencing) + )).to_tuple() + async def import_remote_private_route(self, blob: bytes) -> RouteId: + return RouteId(raise_api_result( + await self.send_ndjson_request(Operation.IMPORT_REMOTE_PRIVATE_ROUTE, + blob = blob) + )) + async def release_private_route(self, route_id: RouteId): + raise_api_result( + await self.send_ndjson_request(Operation.RELEASE_PRIVATE_ROUTE, + route_id = route_id) + ) + async def app_call_reply(self, call_id: OperationId, message: bytes): + raise_api_result( + await self.send_ndjson_request(Operation.APP_CALL_REPLY, + call_id = call_id, + message = message) + ) + async def new_routing_context(self) -> RoutingContext: + rc_id = raise_api_result(await self.send_ndjson_request(Operation.NEW_ROUTING_CONTEXT)) + return _JsonRoutingContext(self, rc_id) + async def open_table_db(self, name: str, column_count: int) -> TableDb: + db_id = raise_api_result(await self.send_ndjson_request(Operation.OPEN_TABLE_DB, + name = name, + column_count = column_count)) + return _JsonTableDb(self, db_id) + async def delete_table_db(self, name: str): + return raise_api_result(await self.send_ndjson_request(Operation.DELETE_TABLE_DB, + name = name)) + async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem: + cs_id = raise_api_result(await self.send_ndjson_request(Operation.GET_CRYPTO_SYSTEM, + kind = kind)) + return _JsonCryptoSystem(self, cs_id) + async def best_crypto_system(self) -> CryptoSystem: + cs_id = raise_api_result(await self.send_ndjson_request(Operation.BEST_CRYPTO_SYSTEM)) + return _JsonCryptoSystem(self, cs_id) + async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]: + return list(map(lambda x: TypedKey(x), raise_api_result(await self.send_ndjson_request(Operation.VERIFY_SIGNATURES, + node_ids = node_ids, + data = data, + signatures = signatures)))) + async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]: + return list(map(lambda x: TypedSignature(x), raise_api_result(await self.send_ndjson_request(Operation.GENERATE_SIGNATURES, + data = data, + key_pairs = key_pairs)))) + async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]: + return list(map(lambda x: TypedKeyPair(x), raise_api_result(await self.send_ndjson_request(Operation.GENERATE_KEY_PAIR, + kind = kind)))) + async def now(self) -> Timestamp: + return Timestamp(raise_api_result(await self.send_ndjson_request(Operation.NOW))) + async def debug(self, command: str) -> str: + return raise_api_result(await self.send_ndjson_request(Operation.DEBUG, + command = command + )) + async def veilid_version_string(self) -> str: + return raise_api_result(await self.send_ndjson_request(Operation.VEILID_VERSION_STRING)) + async def veilid_version(self) -> VeilidVersion: + v = await self.send_ndjson_request(Operation.VEILID_VERSION) + return VeilidVersion(v['major'], v['minor'], v['patch']) + +###################################################### + +def validate_rc_op(request: dict, response: dict): + if response['rc_op'] != request['rc_op']: + raise ValueError("Response rc_op does not match request rc_op") + +class _JsonRoutingContext(RoutingContext): + api: _JsonVeilidAPI + rc_id: int + + def __init__(self, api: _JsonVeilidAPI, rc_id: int): + self.api = api + self.rc_id = rc_id + + def __del__(self): + self.api.send_one_way_ndjson_request(Operation.ROUTING_CONTEXT, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.RELEASE) + + async def with_privacy(self) -> Self: + new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.WITH_PRIVACY)) + return _JsonRoutingContext(self.api, new_rc_id) + + async def with_custom_privacy(self, stability: Stability) -> Self: + new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.WITH_CUSTOM_PRIVACY, + stability = stability)) + return _JsonRoutingContext(self.api, new_rc_id) + async def with_sequencing(self, sequencing: Sequencing) -> Self: + new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.WITH_SEQUENCING, + sequencing = sequencing)) + return _JsonRoutingContext(self.api, new_rc_id) + async def app_call(self, target: TypedKey | RouteId, request: bytes) -> bytes: + return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.APP_CALL, + target = target, + request = request))) + async def app_message(self, target: TypedKey | RouteId, message: bytes): + raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.APP_MESSAGE, + target = target, + message = message)) + async def create_dht_record(self, kind: CryptoKind, schema: DHTSchema) -> DHTRecordDescriptor: + return DHTRecordDescriptor.from_json(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.CREATE_DHT_RECORD, + kind = kind, + schema = schema))) + async def open_dht_record(self, key: TypedKey, writer: Optional[KeyPair]) -> DHTRecordDescriptor: + return DHTRecordDescriptor.from_json(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.OPEN_DHT_RECORD, + key = key, + writer = writer))) + async def close_dht_record(self, key: TypedKey): + raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.CLOSE_DHT_RECORD, + key = key)) + async def delete_dht_record(self, key: TypedKey): + raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.DELETE_DHT_RECORD, + key = key)) + async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]: + ret = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.GET_DHT_VALUE, + key = key, + subkey = subkey, + force_refresh = force_refresh)) + return None if ret is None else ValueData.from_json(ret) + async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]: + ret = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.SET_DHT_VALUE, + key = key, + subkey = subkey, + data = data)) + return None if ret is None else ValueData.from_json(ret) + async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp: + return Timestamp(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.WATCH_DHT_VALUES, + key = key, + subkeys = subkeys, + expiration = expiration, + count = count))) + async def cancel_dht_watch(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool: + return raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op, + rc_id = self.rc_id, + rc_op = RoutingContextOperation.CANCEL_DHT_WATCH, + key = key, + subkeys = subkeys)) + +###################################################### + +def validate_tx_op(request: dict, response: dict): + if response['tx_op'] != request['tx_op']: + raise ValueError("Response tx_op does not match request tx_op") + +class _JsonTableDbTransaction(TableDbTransaction): + api: _JsonVeilidAPI + tx_id: int + done: bool + + def __init__(self, api: _JsonVeilidAPI, tx_id: int): + self.api = api + self.tx_id = tx_id + self.done = False + + def __del__(self): + if not self.done: + raise AssertionError("Should have committed or rolled back transaction") + + async def commit(self): + raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op, + tx_id = self.tx_id, + tx_op = TableDbTransactionOperation.COMMIT)) + self.done = True + async def rollback(self): + raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op, + tx_id = self.tx_id, + tx_op = TableDbTransactionOperation.ROLLBACK)) + self.done = True + async def store(self, col: int, key: bytes, value: bytes): + await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op, + tx_id = self.tx_id, + tx_op = TableDbTransactionOperation.STORE, + col = col, + key = key, + value = value) + async def delete(self, col: int, key: bytes): + await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op, + tx_id = self.tx_id, + tx_op = TableDbTransactionOperation.DELETE, + col = col, + key = key) + +###################################################### + +def validate_db_op(request: dict, response: dict): + if response['db_op'] != request['db_op']: + raise ValueError("Response db_op does not match request db_op") + +class _JsonTableDb(TableDb): + api: _JsonVeilidAPI + db_id: int + + def __init__(self, api: _JsonVeilidAPI, db_id: int): + self.api = api + self.db_id = db_id + + def __del__(self): + self.api.send_one_way_ndjson_request(Operation.TABLE_DB, + db_id = self.db_id, + rc_op = TableDbOperation.RELEASE) + + async def get_column_count(self) -> int: + return raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.GET_COLUMN_COUNT)) + async def get_keys(self, col: int) -> list[bytes]: + return list(map(lambda x: urlsafe_b64decode_no_pad(x), raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.GET_KEYS, + col = col)))) + async def transact(self) -> TableDbTransaction: + tx_id = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.TRANSACT)) + return _JsonTableDbTransaction(self.api, tx_id) + async def store(self, col: int, key: bytes, value: bytes): + return raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.STORE, + col = col, + key = key, + value = value)) + async def load(self, col: int, key: bytes) -> Optional[bytes]: + res = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.LOAD, + col = col, + key = key)) + return None if res is None else urlsafe_b64decode_no_pad(res) + async def delete(self, col: int, key: bytes) -> Optional[bytes]: + res = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op, + db_id = self.db_id, + db_op = TableDbOperation.DELETE, + col = col, + key = key)) + return None if res is None else urlsafe_b64decode_no_pad(res) + +###################################################### + + + +def validate_cs_op(request: dict, response: dict): + if response['cs_op'] != request['cs_op']: + raise ValueError("Response cs_op does not match request cs_op") + +class _JsonCryptoSystem(CryptoSystem): + api: _JsonVeilidAPI + cs_id: int + + def __init__(self, api: _JsonVeilidAPI, cs_id: int): + self.api = api + self.cs_id = cs_id + + def __del__(self): + self.api.send_one_way_ndjson_request(Operation.CRYPTO_SYSTEM, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.RELEASE) + + async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: + return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.CACHED_DH, + key = key, + secret = secret))) + async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: + return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.COMPUTE_DH, + key = key, + secret = secret))) + async def random_bytes(self, len: int) -> bytes: + return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.RANDOM_BYTES, + len = len))) + async def default_salt_length(self) -> int: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.DEFAULT_SALT_LENGTH)) + async def hash_password(self, password: bytes, salt: bytes) -> str: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.HASH_PASSWORD, + password = password, + salt = salt)) + async def verify_password(self, password: bytes, password_hash: str) -> bool: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.VERIFY_PASSWORD, + password = password, + password_hash = password_hash)) + async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret: + return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.DERIVE_SHARED_SECRET, + password = password, + salt = salt))) + async def random_nonce(self) -> Nonce: + return Nonce(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.RANDOM_NONCE))) + async def random_shared_secret(self) -> SharedSecret: + return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.RANDOM_SHARED_SECRET))) + async def generate_key_pair(self) -> KeyPair: + return KeyPair(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.GENERATE_KEY_PAIR))) + async def generate_hash(self, data: bytes) -> HashDigest: + return HashDigest(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.GENERATE_HASH, + data = data))) + async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.VALIDATE_KEY_PAIR, + key = key, + secret = secret)) + async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.VALIDATE_HASH, + data = data, + hash_digest = hash_digest)) + async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance: + return CryptoKeyDistance(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.DISTANCE, + key1 = key1, + key2 = key2))) + async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature: + return Signature(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.SIGN, + key = key, + secret = secret, + data = data))) + async def verify(self, key: PublicKey, data: bytes, signature: Signature): + raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.VERIFY, + key = key, + data = data, + signature = signature)) + async def aead_overhead(self) -> int: + return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.AEAD_OVERHEAD)) + async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: + return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.DECRYPT_AEAD, + body = body, + nonce = nonce, + shared_secret = shared_secret, + associated_data = associated_data))) + async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: + return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.ENCRYPT_AEAD, + body = body, + nonce = nonce, + shared_secret = shared_secret, + associated_data = associated_data))) + async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes: + return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op, + cs_id = self.cs_id, + cs_op = CryptoSystemOperation.CRYPT_NO_AUTH, + body = body, + nonce = nonce, + shared_secret = shared_secret))) + + +###################################################### + +async def json_api_connect(host:str, port:int, update_callback: Callable[[VeilidUpdate], Awaitable]) -> VeilidAPI: + return await _JsonVeilidAPI.connect(host, port, update_callback) + diff --git a/veilid-python/veilid/operations.py b/veilid-python/veilid/operations.py new file mode 100644 index 00000000..297a0c77 --- /dev/null +++ b/veilid-python/veilid/operations.py @@ -0,0 +1,91 @@ +from enum import StrEnum +from typing import Self + +class Operation(StrEnum): + CONTROL = "Control" + GET_STATE = "GetState" + ATTACH = "Attach" + DETACH = "Detach" + NEW_PRIVATE_ROUTE = "NewPrivateRoute" + NEW_CUSTOM_PRIVATE_ROUTE = "NewCustomPrivateRoute" + IMPORT_REMOTE_PRIVATE_ROUTE = "ImportRemotePrivateRoute" + RELEASE_PRIVATE_ROUTE = "ReleasePrivateRoute" + APP_CALL_REPLY = "AppCallReply" + NEW_ROUTING_CONTEXT = "NewRoutingContext" + ROUTING_CONTEXT = "RoutingContext" + OPEN_TABLE_DB = "OpenTableDb" + DELETE_TABLE_DB = "DeleteTableDb" + TABLE_DB = "TableDb" + TABLE_DB_TRANSACTION = "TableDbTransaction" + GET_CRYPTO_SYSTEM = "GetCryptoSystem" + BEST_CRYPTO_SYSTEM = "BestCryptoSystem" + CRYPTO_SYSTEM = "CryptoSystem" + VERIFY_SIGNATURES = "VerifySignatures" + GENERATE_SIGNATURES = "GenerateSignatures" + GENERATE_KEY_PAIR = "GenerateKeyPair" + NOW = "Now" + DEBUG = "Debug" + VEILID_VERSION_STRING = "VeilidVersionString" + VEILID_VERSION = "VeilidVersion" + +class RoutingContextOperation(StrEnum): + INVALID_ID = "InvalidId" + RELEASE = "Release" + WITH_PRIVACY = "WithPrivacy" + WITH_CUSTOM_PRIVACY = "WithCustomPrivacy" + WITH_SEQUENCING = "WithSequencing" + APP_CALL = "AppCall" + APP_MESSAGE = "AppMessage" + CREATE_DHT_RECORD = "CreateDhtRecord" + OPEN_DHT_RECORD = "OpenDhtRecord" + CLOSE_DHT_RECORD = "CloseDhtRecord" + DELETE_DHT_RECORD = "DeleteDhtRecord" + GET_DHT_VALUE = "GetDhtValue" + SET_DHT_VALUE = "SetDhtValue" + WATCH_DHT_VALUES = "WatchDhtValues" + CANCEL_DHT_WATCH = "CancelDhtWatch" + +class TableDbOperation(StrEnum): + INVALID_ID = "InvalidId" + RELEASE = "Release" + GET_COLUMN_COUNT = "GetColumnCount" + GET_KEYS = "GetKeys" + TRANSACT = "Transact" + STORE = "Store" + LOAD = "Load" + DELETE = "Delete" + +class TableDbTransactionOperation(StrEnum): + INVALID_ID = "InvalidId" + COMMIT = "Commit" + ROLLBACK = "Rollback" + STORE = "Store" + DELETE = "Delete" + +class CryptoSystemOperation(StrEnum): + INVALID_ID = "InvalidId" + RELEASE = "Release" + CACHED_DH = "CachedDh" + COMPUTE_DH = "ComputeDh" + RANDOM_BYTES = "RandomBytes" + DEFAULT_SALT_LENGTH = "DefaultSaltLength" + HASH_PASSWORD = "HashPassword" + VERIFY_PASSWORD = "VerifyPassword" + DERIVE_SHARED_SECRET = "DeriveSharedSecret" + RANDOM_NONCE = "RandomNonce" + RANDOM_SHARED_SECRET = "RandomSharedSecret" + GENERATE_KEY_PAIR = "GenerateKeyPair" + GENERATE_HASH = "GenerateHash" + VALIDATE_KEY_PAIR = "ValidateKeyPair" + VALIDATE_HASH = "ValidateHash" + DISTANCE = "Distance" + SIGN = "Sign" + VERIFY = "Verify" + AEAD_OVERHEAD = "AeadOverhead" + DECRYPT_AEAD = "DecryptAead" + ENCRYPT_AEAD = "EncryptAead" + CRYPT_NO_AUTH = "CryptNoAuth" + +class RecvMessageType(StrEnum): + RESPONSE = "Response" + UPDATE = "Update" diff --git a/veilid-python/veilid/schema/RecvMessage.json b/veilid-python/veilid/schema/RecvMessage.json new file mode 100644 index 00000000..15b37df1 --- /dev/null +++ b/veilid-python/veilid/schema/RecvMessage.json @@ -0,0 +1,3924 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RecvMessage", + "oneOf": [ + { + "type": "object", + "oneOf": [ + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Control" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "$ref": "#/definitions/VeilidState" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "GetState" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Attach" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Detach" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "$ref": "#/definitions/NewPrivateRouteResult" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "NewPrivateRoute" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "$ref": "#/definitions/NewPrivateRouteResult" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "NewCustomPrivateRoute" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "ImportRemotePrivateRoute" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "ReleasePrivateRoute" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "AppCallReply" + ] + } + } + }, + { + "type": "object", + "required": [ + "op", + "value" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "NewRoutingContext" + ] + }, + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "InvalidId" + ] + } + } + }, + { + "type": "object", + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithPrivacy" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithCustomPrivacy" + ] + } + } + }, + { + "type": "object", + "required": [ + "rc_op", + "value" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithSequencing" + ] + }, + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "AppCall" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "AppMessage" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "$ref": "#/definitions/DHTRecordDescriptor" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "CreateDhtRecord" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "$ref": "#/definitions/DHTRecordDescriptor" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "OpenDhtRecord" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "CloseDhtRecord" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "DeleteDhtRecord" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "value": { + "anyOf": [ + { + "$ref": "#/definitions/ValueData" + }, + { + "type": "null" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "GetDhtValue" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "value": { + "anyOf": [ + { + "$ref": "#/definitions/ValueData" + }, + { + "type": "null" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "SetDhtValue" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WatchDhtValues" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "CancelDhtWatch" + ] + } + } + } + ], + "required": [ + "op", + "rc_id" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "RoutingContext" + ] + }, + "rc_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "OpenTableDb" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "DeleteTableDb" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "InvalidId" + ] + } + } + }, + { + "type": "object", + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "GetColumnCount" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "GetKeys" + ] + } + } + }, + { + "type": "object", + "required": [ + "db_op", + "value" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Transact" + ] + }, + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Store" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "value": { + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Load" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "value": { + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Delete" + ] + } + } + } + ], + "required": [ + "db_id", + "op" + ], + "properties": { + "db_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "op": { + "type": "string", + "enum": [ + "TableDb" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "InvalidId" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Commit" + ] + } + } + }, + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Rollback" + ] + } + } + }, + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Store" + ] + } + } + }, + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Delete" + ] + } + } + } + ], + "required": [ + "op", + "tx_id" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "TableDbTransaction" + ] + }, + "tx_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "GetCryptoSystem" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "BestCryptoSystem" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "InvalidId" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "CachedDh" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ComputeDh" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomBytes" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "DefaultSaltLength" + ] + }, + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "HashPassword" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "VerifyPassword" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "DeriveSharedSecret" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomNonce" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomSharedSecret" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "GenerateKeyPair" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "GenerateHash" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ValidateKeyPair" + ] + }, + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ValidateHash" + ] + }, + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Distance" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Sign" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "null" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Verify" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "AeadOverhead" + ] + }, + "value": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "DecryptAead" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "EncryptAead" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "CryptNoAuth" + ] + }, + "value": { + "type": "string" + } + } + } + ], + "required": [ + "cs_id", + "op" + ], + "properties": { + "cs_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "op": { + "type": "string", + "enum": [ + "CryptoSystem" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "VerifySignatures" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "GenerateSignatures" + ] + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "GenerateKeyPair" + ] + } + } + }, + { + "type": "object", + "required": [ + "op", + "value" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Now" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "anyOf": [ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "$ref": "#/definitions/VeilidAPIError" + } + } + } + ], + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Debug" + ] + } + } + }, + { + "type": "object", + "required": [ + "op", + "value" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "VeilidVersionString" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "major", + "minor", + "op", + "patch" + ], + "properties": { + "major": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "minor": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "op": { + "type": "string", + "enum": [ + "VeilidVersion" + ] + }, + "patch": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + ], + "required": [ + "type" + ], + "properties": { + "id": { + "description": "Operation Id (pairs with Request, or empty if unidirectional)", + "default": 0, + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "type": { + "type": "string", + "enum": [ + "Response" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "description": "A VeilidCore log message with optional backtrace", + "type": "object", + "required": [ + "kind", + "log_level", + "message" + ], + "properties": { + "backtrace": { + "type": [ + "string", + "null" + ] + }, + "kind": { + "type": "string", + "enum": [ + "Log" + ] + }, + "log_level": { + "$ref": "#/definitions/VeilidLogLevel" + }, + "message": { + "type": "string" + } + } + }, + { + "description": "Direct statement blob passed to hosting application for processing", + "type": "object", + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "AppMessage" + ] + }, + "message": { + "description": "The content of the message to deliver to the application", + "type": "string" + }, + "sender": { + "description": "Some(sender) if the message was sent directly, None if received via a private/safety route", + "type": [ + "string", + "null" + ] + } + } + }, + { + "description": "Direct question blob passed to hosting application for processing to send an eventual AppReply", + "type": "object", + "required": [ + "call_id", + "kind", + "message" + ], + "properties": { + "call_id": { + "description": "The id to reply to", + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "AppCall" + ] + }, + "message": { + "description": "The content of the request to deliver to the application", + "type": "string" + }, + "sender": { + "description": "Some(sender) if the request was sent directly, None if received via a private/safety route", + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind", + "local_network_ready", + "public_internet_ready", + "state" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Attachment" + ] + }, + "local_network_ready": { + "type": "boolean" + }, + "public_internet_ready": { + "type": "boolean" + }, + "state": { + "$ref": "#/definitions/AttachmentState" + } + } + }, + { + "type": "object", + "required": [ + "bps_down", + "bps_up", + "kind", + "peers", + "started" + ], + "properties": { + "bps_down": { + "type": "string" + }, + "bps_up": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "Network" + ] + }, + "peers": { + "type": "array", + "items": { + "$ref": "#/definitions/PeerTableData" + } + }, + "started": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "config", + "kind" + ], + "properties": { + "config": { + "$ref": "#/definitions/VeilidConfigInner" + }, + "kind": { + "type": "string", + "enum": [ + "Config" + ] + } + } + }, + { + "type": "object", + "required": [ + "dead_remote_routes", + "dead_routes", + "kind" + ], + "properties": { + "dead_remote_routes": { + "type": "array", + "items": { + "type": "string" + } + }, + "dead_routes": { + "type": "array", + "items": { + "type": "string" + } + }, + "kind": { + "type": "string", + "enum": [ + "RouteChange" + ] + } + } + }, + { + "type": "object", + "required": [ + "count", + "key", + "kind", + "subkeys", + "value" + ], + "properties": { + "count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "key": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "ValueChange" + ] + }, + "subkeys": { + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "value": { + "$ref": "#/definitions/ValueData" + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Shutdown" + ] + } + } + } + ], + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Update" + ] + } + } + } + ], + "definitions": { + "AttachmentState": { + "description": "Attachment abstraction for network 'signal strength'", + "type": "string", + "enum": [ + "Detached", + "Attaching", + "AttachedWeak", + "AttachedGood", + "AttachedStrong", + "FullyAttached", + "OverAttached", + "Detaching" + ] + }, + "DHTRecordDescriptor": { + "description": "DHT Record Descriptor", + "type": "object", + "required": [ + "key", + "owner", + "schema" + ], + "properties": { + "key": { + "description": "DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ]", + "type": "string" + }, + "owner": { + "description": "The public key of the owner", + "type": "string" + }, + "owner_secret": { + "description": "If this key is being created: Some(the secret key of the owner) If this key is just being opened: None", + "type": [ + "string", + "null" + ] + }, + "schema": { + "description": "The schema in use associated with the key", + "allOf": [ + { + "$ref": "#/definitions/DHTSchema" + } + ] + } + } + }, + "DHTSchema": { + "description": "Enum over all the supported DHT Schemas", + "oneOf": [ + { + "description": "Default DHT Schema (DFLT)", + "type": "object", + "required": [ + "kind", + "o_cnt" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "DFLT" + ] + }, + "o_cnt": { + "description": "Owner subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + { + "description": "Simple DHT Schema (SMPL)", + "type": "object", + "required": [ + "kind", + "members", + "o_cnt" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SMPL" + ] + }, + "members": { + "description": "Members", + "type": "array", + "items": { + "$ref": "#/definitions/DHTSchemaSMPLMember" + } + }, + "o_cnt": { + "description": "Owner subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + } + ] + }, + "DHTSchemaSMPLMember": { + "description": "Simple DHT Schema (SMPL) Member", + "type": "object", + "required": [ + "m_cnt", + "m_key" + ], + "properties": { + "m_cnt": { + "description": "Member subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "m_key": { + "description": "Member key", + "type": "string" + } + } + }, + "LatencyStats": { + "type": "object", + "required": [ + "average", + "fastest", + "slowest" + ], + "properties": { + "average": { + "type": "string" + }, + "fastest": { + "type": "string" + }, + "slowest": { + "type": "string" + } + } + }, + "NewPrivateRouteResult": { + "type": "object", + "required": [ + "blob", + "route_id" + ], + "properties": { + "blob": { + "type": "string" + }, + "route_id": { + "type": "string" + } + } + }, + "PeerStats": { + "type": "object", + "required": [ + "rpc_stats", + "time_added", + "transfer" + ], + "properties": { + "latency": { + "anyOf": [ + { + "$ref": "#/definitions/LatencyStats" + }, + { + "type": "null" + } + ] + }, + "rpc_stats": { + "$ref": "#/definitions/RPCStats" + }, + "time_added": { + "type": "string" + }, + "transfer": { + "$ref": "#/definitions/TransferStatsDownUp" + } + } + }, + "PeerTableData": { + "type": "object", + "required": [ + "node_ids", + "peer_address", + "peer_stats" + ], + "properties": { + "node_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "peer_address": { + "type": "string" + }, + "peer_stats": { + "$ref": "#/definitions/PeerStats" + } + } + }, + "RPCStats": { + "type": "object", + "required": [ + "failed_to_send", + "messages_rcvd", + "messages_sent", + "questions_in_flight", + "recent_lost_answers" + ], + "properties": { + "failed_to_send": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "first_consecutive_seen_ts": { + "type": [ + "string", + "null" + ] + }, + "last_question_ts": { + "type": [ + "string", + "null" + ] + }, + "last_seen_ts": { + "type": [ + "string", + "null" + ] + }, + "messages_rcvd": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "messages_sent": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "questions_in_flight": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "recent_lost_answers": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "TransferStats": { + "type": "object", + "required": [ + "average", + "maximum", + "minimum", + "total" + ], + "properties": { + "average": { + "type": "string" + }, + "maximum": { + "type": "string" + }, + "minimum": { + "type": "string" + }, + "total": { + "type": "string" + } + } + }, + "TransferStatsDownUp": { + "type": "object", + "required": [ + "down", + "up" + ], + "properties": { + "down": { + "$ref": "#/definitions/TransferStats" + }, + "up": { + "$ref": "#/definitions/TransferStats" + } + } + }, + "ValueData": { + "type": "object", + "required": [ + "data", + "seq", + "writer" + ], + "properties": { + "data": { + "description": "The contents of a DHT Record", + "type": "string" + }, + "seq": { + "description": "An increasing sequence number to time-order the DHT record changes", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "writer": { + "description": "The public identity key of the writer of the data", + "type": "string" + } + } + }, + "VeilidAPIError": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "NotInitialized" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "AlreadyInitialized" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Timeout" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "TryAgain" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Shutdown" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "InvalidTarget" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "NoConnection" + ] + }, + "message": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "key", + "kind" + ], + "properties": { + "key": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "KeyNotFound" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Internal" + ] + }, + "message": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Unimplemented" + ] + }, + "message": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "kind", + "message", + "value" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "ParseError" + ] + }, + "message": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "argument", + "context", + "kind", + "value" + ], + "properties": { + "argument": { + "type": "string" + }, + "context": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "InvalidArgument" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "argument", + "context", + "kind" + ], + "properties": { + "argument": { + "type": "string" + }, + "context": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "MissingArgument" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind", + "message" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "Generic" + ] + }, + "message": { + "type": "string" + } + } + } + ] + }, + "VeilidConfigApplication": { + "description": "Application configuration\n\nConfigure web access to the Prograssive Web App (PWA)\n\nTo be implemented...", + "type": "object", + "required": [ + "http", + "https" + ], + "properties": { + "http": { + "$ref": "#/definitions/VeilidConfigHTTP" + }, + "https": { + "$ref": "#/definitions/VeilidConfigHTTPS" + } + } + }, + "VeilidConfigBlockStore": { + "type": "object", + "required": [ + "delete", + "directory" + ], + "properties": { + "delete": { + "type": "boolean" + }, + "directory": { + "type": "string" + } + } + }, + "VeilidConfigCapabilities": { + "type": "object", + "required": [ + "protocol_accept_tcp", + "protocol_accept_ws", + "protocol_accept_wss", + "protocol_connect_tcp", + "protocol_connect_ws", + "protocol_connect_wss", + "protocol_udp" + ], + "properties": { + "protocol_accept_tcp": { + "type": "boolean" + }, + "protocol_accept_ws": { + "type": "boolean" + }, + "protocol_accept_wss": { + "type": "boolean" + }, + "protocol_connect_tcp": { + "type": "boolean" + }, + "protocol_connect_ws": { + "type": "boolean" + }, + "protocol_connect_wss": { + "type": "boolean" + }, + "protocol_udp": { + "type": "boolean" + } + } + }, + "VeilidConfigDHT": { + "description": "Configure the Distributed Hash Table (DHT)", + "type": "object", + "required": [ + "get_value_count", + "get_value_fanout", + "get_value_timeout_ms", + "local_max_subkey_cache_memory_mb", + "local_subkey_cache_size", + "max_find_node_count", + "min_peer_count", + "min_peer_refresh_time_ms", + "remote_max_records", + "remote_max_storage_space_mb", + "remote_max_subkey_cache_memory_mb", + "remote_subkey_cache_size", + "resolve_node_count", + "resolve_node_fanout", + "resolve_node_timeout_ms", + "set_value_count", + "set_value_fanout", + "set_value_timeout_ms", + "validate_dial_info_receipt_time_ms" + ], + "properties": { + "get_value_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "get_value_fanout": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "get_value_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "local_max_subkey_cache_memory_mb": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "local_subkey_cache_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_find_node_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_peer_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_peer_refresh_time_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "remote_max_records": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "remote_max_storage_space_mb": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "remote_max_subkey_cache_memory_mb": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "remote_subkey_cache_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "resolve_node_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "resolve_node_fanout": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "resolve_node_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "set_value_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "set_value_fanout": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "set_value_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "validate_dial_info_receipt_time_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "VeilidConfigHTTP": { + "description": "Enable and configure HTTP access to the Veilid node\n\n```yaml http: enabled: false listen_address: ':5150' path: 'app\" url: 'https://localhost:5150' ```", + "type": "object", + "required": [ + "enabled", + "listen_address", + "path" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "path": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidConfigHTTPS": { + "description": "Enable and configure HTTPS access to the Veilid node\n\n```yaml https: enabled: false listen_address: ':5150' path: 'app' url: 'https://localhost:5150' ```", + "type": "object", + "required": [ + "enabled", + "listen_address", + "path" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "path": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidConfigInner": { + "type": "object", + "required": [ + "block_store", + "capabilities", + "namespace", + "network", + "program_name", + "protected_store", + "table_store" + ], + "properties": { + "block_store": { + "$ref": "#/definitions/VeilidConfigBlockStore" + }, + "capabilities": { + "$ref": "#/definitions/VeilidConfigCapabilities" + }, + "namespace": { + "type": "string" + }, + "network": { + "$ref": "#/definitions/VeilidConfigNetwork" + }, + "program_name": { + "type": "string" + }, + "protected_store": { + "$ref": "#/definitions/VeilidConfigProtectedStore" + }, + "table_store": { + "$ref": "#/definitions/VeilidConfigTableStore" + } + } + }, + "VeilidConfigNetwork": { + "type": "object", + "required": [ + "application", + "client_whitelist_timeout_ms", + "connection_inactivity_timeout_ms", + "connection_initial_timeout_ms", + "detect_address_changes", + "dht", + "hole_punch_receipt_time_ms", + "max_connection_frequency_per_min", + "max_connections_per_ip4", + "max_connections_per_ip6_prefix", + "max_connections_per_ip6_prefix_size", + "protocol", + "restricted_nat_retries", + "reverse_connection_receipt_time_ms", + "routing_table", + "rpc", + "tls", + "upnp" + ], + "properties": { + "application": { + "$ref": "#/definitions/VeilidConfigApplication" + }, + "client_whitelist_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "connection_inactivity_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "connection_initial_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "detect_address_changes": { + "type": "boolean" + }, + "dht": { + "$ref": "#/definitions/VeilidConfigDHT" + }, + "hole_punch_receipt_time_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_connection_frequency_per_min": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_connections_per_ip4": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_connections_per_ip6_prefix": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_connections_per_ip6_prefix_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "protocol": { + "$ref": "#/definitions/VeilidConfigProtocol" + }, + "restricted_nat_retries": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "reverse_connection_receipt_time_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "routing_table": { + "$ref": "#/definitions/VeilidConfigRoutingTable" + }, + "rpc": { + "$ref": "#/definitions/VeilidConfigRPC" + }, + "tls": { + "$ref": "#/definitions/VeilidConfigTLS" + }, + "upnp": { + "type": "boolean" + } + } + }, + "VeilidConfigProtectedStore": { + "type": "object", + "required": [ + "allow_insecure_fallback", + "always_use_insecure_storage", + "delete", + "device_encryption_key_password", + "directory" + ], + "properties": { + "allow_insecure_fallback": { + "type": "boolean" + }, + "always_use_insecure_storage": { + "type": "boolean" + }, + "delete": { + "type": "boolean" + }, + "device_encryption_key_password": { + "type": "string" + }, + "directory": { + "type": "string" + }, + "new_device_encryption_key_password": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidConfigProtocol": { + "description": "Configure Network Protocols\n\nVeilid can communicate over UDP, TCP, and Web Sockets.\n\nAll protocols are available by default, and the Veilid node will sort out which protocol is used for each peer connection.", + "type": "object", + "required": [ + "tcp", + "udp", + "ws", + "wss" + ], + "properties": { + "tcp": { + "$ref": "#/definitions/VeilidConfigTCP" + }, + "udp": { + "$ref": "#/definitions/VeilidConfigUDP" + }, + "ws": { + "$ref": "#/definitions/VeilidConfigWS" + }, + "wss": { + "$ref": "#/definitions/VeilidConfigWSS" + } + } + }, + "VeilidConfigRPC": { + "description": "Configure RPC", + "type": "object", + "required": [ + "concurrency", + "default_route_hop_count", + "max_route_hop_count", + "queue_size", + "timeout_ms" + ], + "properties": { + "concurrency": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "default_route_hop_count": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "max_route_hop_count": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "max_timestamp_ahead_ms": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "max_timestamp_behind_ms": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "queue_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "VeilidConfigRoutingTable": { + "description": "Configure the network routing table", + "type": "object", + "required": [ + "bootstrap", + "limit_attached_good", + "limit_attached_strong", + "limit_attached_weak", + "limit_fully_attached", + "limit_over_attached", + "node_id", + "node_id_secret" + ], + "properties": { + "bootstrap": { + "type": "array", + "items": { + "type": "string" + } + }, + "limit_attached_good": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "limit_attached_strong": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "limit_attached_weak": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "limit_fully_attached": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "limit_over_attached": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "node_id": { + "type": "array", + "items": { + "type": "string" + } + }, + "node_id_secret": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "VeilidConfigTCP": { + "description": "Enable and configure TCP\n\n```yaml tcp: connect: true listen: true max_connections: 32 listen_address: ':5150' public_address: ''", + "type": "object", + "required": [ + "connect", + "listen", + "listen_address", + "max_connections" + ], + "properties": { + "connect": { + "type": "boolean" + }, + "listen": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "max_connections": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "public_address": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidConfigTLS": { + "description": "Configure TLS\n\n```yaml tls: certificate_path: /path/to/cert private_key_path: /path/to/private/key connection_initial_timeout_ms: 2000", + "type": "object", + "required": [ + "certificate_path", + "connection_initial_timeout_ms", + "private_key_path" + ], + "properties": { + "certificate_path": { + "type": "string" + }, + "connection_initial_timeout_ms": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "private_key_path": { + "type": "string" + } + } + }, + "VeilidConfigTableStore": { + "type": "object", + "required": [ + "delete", + "directory" + ], + "properties": { + "delete": { + "type": "boolean" + }, + "directory": { + "type": "string" + } + } + }, + "VeilidConfigUDP": { + "description": "Enable and configure UDP\n\n```yaml udp: enabled: true socket_pool_size: 0 listen_address: ':5150' public_address: '' ```", + "type": "object", + "required": [ + "enabled", + "listen_address", + "socket_pool_size" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "public_address": { + "type": [ + "string", + "null" + ] + }, + "socket_pool_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "VeilidConfigWS": { + "description": "Enable and configure Web Sockets\n\n```yaml ws: connect: true listen: true max_connections: 16 listen_address: ':5150' path: 'ws' url: 'ws://localhost:5150/ws'", + "type": "object", + "required": [ + "connect", + "listen", + "listen_address", + "max_connections", + "path" + ], + "properties": { + "connect": { + "type": "boolean" + }, + "listen": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "max_connections": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "path": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidConfigWSS": { + "description": "Enable and configure Secure Web Sockets\n\n```yaml wss: connect: true listen: false max_connections: 16 listen_address: ':5150' path: 'ws' url: ''", + "type": "object", + "required": [ + "connect", + "listen", + "listen_address", + "max_connections", + "path" + ], + "properties": { + "connect": { + "type": "boolean" + }, + "listen": { + "type": "boolean" + }, + "listen_address": { + "type": "string" + }, + "max_connections": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "path": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + } + } + }, + "VeilidLogLevel": { + "description": "Log level for VeilidCore", + "type": "string", + "enum": [ + "Error", + "Warn", + "Info", + "Debug", + "Trace" + ] + }, + "VeilidState": { + "type": "object", + "required": [ + "attachment", + "config", + "network" + ], + "properties": { + "attachment": { + "$ref": "#/definitions/VeilidStateAttachment" + }, + "config": { + "$ref": "#/definitions/VeilidStateConfig" + }, + "network": { + "$ref": "#/definitions/VeilidStateNetwork" + } + } + }, + "VeilidStateAttachment": { + "type": "object", + "required": [ + "local_network_ready", + "public_internet_ready", + "state" + ], + "properties": { + "local_network_ready": { + "type": "boolean" + }, + "public_internet_ready": { + "type": "boolean" + }, + "state": { + "$ref": "#/definitions/AttachmentState" + } + } + }, + "VeilidStateConfig": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/VeilidConfigInner" + } + } + }, + "VeilidStateNetwork": { + "type": "object", + "required": [ + "bps_down", + "bps_up", + "peers", + "started" + ], + "properties": { + "bps_down": { + "type": "string" + }, + "bps_up": { + "type": "string" + }, + "peers": { + "type": "array", + "items": { + "$ref": "#/definitions/PeerTableData" + } + }, + "started": { + "type": "boolean" + } + } + } + } +} diff --git a/veilid-python/veilid/schema/Request.json b/veilid-python/veilid/schema/Request.json new file mode 100644 index 00000000..82119da0 --- /dev/null +++ b/veilid-python/veilid/schema/Request.json @@ -0,0 +1,1585 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Request", + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "args", + "op" + ], + "properties": { + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "op": { + "type": "string", + "enum": [ + "Control" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "GetState" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Attach" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Detach" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "NewPrivateRoute" + ] + } + } + }, + { + "type": "object", + "required": [ + "kinds", + "op" + ], + "properties": { + "kinds": { + "type": "array", + "items": { + "type": "string" + } + }, + "op": { + "type": "string", + "enum": [ + "NewCustomPrivateRoute" + ] + }, + "sequencing": { + "default": "NoPreference", + "allOf": [ + { + "$ref": "#/definitions/Sequencing" + } + ] + }, + "stability": { + "default": "LowLatency", + "allOf": [ + { + "$ref": "#/definitions/Stability" + } + ] + } + } + }, + { + "type": "object", + "required": [ + "blob", + "op" + ], + "properties": { + "blob": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "ImportRemotePrivateRoute" + ] + } + } + }, + { + "type": "object", + "required": [ + "op", + "route_id" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "ReleasePrivateRoute" + ] + }, + "route_id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "call_id", + "message", + "op" + ], + "properties": { + "call_id": { + "type": "string" + }, + "message": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "AppCallReply" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "NewRoutingContext" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "required": [ + "rc_op" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithPrivacy" + ] + } + } + }, + { + "type": "object", + "required": [ + "rc_op", + "stability" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithCustomPrivacy" + ] + }, + "stability": { + "$ref": "#/definitions/Stability" + } + } + }, + { + "type": "object", + "required": [ + "rc_op", + "sequencing" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "WithSequencing" + ] + }, + "sequencing": { + "$ref": "#/definitions/Sequencing" + } + } + }, + { + "type": "object", + "required": [ + "rc_op", + "request", + "target" + ], + "properties": { + "rc_op": { + "type": "string", + "enum": [ + "AppCall" + ] + }, + "request": { + "type": "string" + }, + "target": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "message", + "rc_op", + "target" + ], + "properties": { + "message": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "AppMessage" + ] + }, + "target": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "kind", + "rc_op", + "schema" + ], + "properties": { + "kind": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "CreateDhtRecord" + ] + }, + "schema": { + "$ref": "#/definitions/DHTSchema" + } + } + }, + { + "type": "object", + "required": [ + "key", + "rc_op" + ], + "properties": { + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "OpenDhtRecord" + ] + }, + "writer": { + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "key", + "rc_op" + ], + "properties": { + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "CloseDhtRecord" + ] + } + } + }, + { + "type": "object", + "required": [ + "key", + "rc_op" + ], + "properties": { + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "DeleteDhtRecord" + ] + } + } + }, + { + "type": "object", + "required": [ + "force_refresh", + "key", + "rc_op", + "subkey" + ], + "properties": { + "force_refresh": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "GetDhtValue" + ] + }, + "subkey": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "data", + "key", + "rc_op", + "subkey" + ], + "properties": { + "data": { + "type": "string" + }, + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "SetDhtValue" + ] + }, + "subkey": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "count", + "expiration", + "key", + "rc_op", + "subkeys" + ], + "properties": { + "count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "expiration": { + "type": "string" + }, + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "WatchDhtValues" + ] + }, + "subkeys": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, + { + "type": "object", + "required": [ + "key", + "rc_op", + "subkeys" + ], + "properties": { + "key": { + "type": "string" + }, + "rc_op": { + "type": "string", + "enum": [ + "CancelDhtWatch" + ] + }, + "subkeys": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + } + ], + "required": [ + "op", + "rc_id" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "RoutingContext" + ] + }, + "rc_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "column_count", + "name", + "op" + ], + "properties": { + "column_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "name": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "OpenTableDb" + ] + } + } + }, + { + "type": "object", + "required": [ + "name", + "op" + ], + "properties": { + "name": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "DeleteTableDb" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "GetColumnCount" + ] + } + } + }, + { + "type": "object", + "required": [ + "col", + "db_op" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "db_op": { + "type": "string", + "enum": [ + "GetKeys" + ] + } + } + }, + { + "type": "object", + "required": [ + "db_op" + ], + "properties": { + "db_op": { + "type": "string", + "enum": [ + "Transact" + ] + } + } + }, + { + "type": "object", + "required": [ + "col", + "db_op", + "key", + "value" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "db_op": { + "type": "string", + "enum": [ + "Store" + ] + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "col", + "db_op", + "key" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "db_op": { + "type": "string", + "enum": [ + "Load" + ] + }, + "key": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "col", + "db_op", + "key" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "db_op": { + "type": "string", + "enum": [ + "Delete" + ] + }, + "key": { + "type": "string" + } + } + } + ], + "required": [ + "db_id", + "op" + ], + "properties": { + "db_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "op": { + "type": "string", + "enum": [ + "TableDb" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Commit" + ] + } + } + }, + { + "type": "object", + "required": [ + "tx_op" + ], + "properties": { + "tx_op": { + "type": "string", + "enum": [ + "Rollback" + ] + } + } + }, + { + "type": "object", + "required": [ + "col", + "key", + "tx_op", + "value" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "key": { + "type": "string" + }, + "tx_op": { + "type": "string", + "enum": [ + "Store" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "col", + "key", + "tx_op" + ], + "properties": { + "col": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "key": { + "type": "string" + }, + "tx_op": { + "type": "string", + "enum": [ + "Delete" + ] + } + } + } + ], + "required": [ + "op", + "tx_id" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "TableDbTransaction" + ] + }, + "tx_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "kind", + "op" + ], + "properties": { + "kind": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "GetCryptoSystem" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "BestCryptoSystem" + ] + } + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Release" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "key", + "secret" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "CachedDh" + ] + }, + "key": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "key", + "secret" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ComputeDh" + ] + }, + "key": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "len" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomBytes" + ] + }, + "len": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "DefaultSaltLength" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "password", + "salt" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "HashPassword" + ] + }, + "password": { + "type": "string" + }, + "salt": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "password", + "password_hash" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "VerifyPassword" + ] + }, + "password": { + "type": "string" + }, + "password_hash": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "password", + "salt" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "DeriveSharedSecret" + ] + }, + "password": { + "type": "string" + }, + "salt": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomNonce" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "RandomSharedSecret" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "GenerateKeyPair" + ] + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "data" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "GenerateHash" + ] + }, + "data": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "key", + "secret" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ValidateKeyPair" + ] + }, + "key": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "data", + "hash_digest" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "ValidateHash" + ] + }, + "data": { + "type": "string" + }, + "hash_digest": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "key1", + "key2" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Distance" + ] + }, + "key1": { + "type": "string" + }, + "key2": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "data", + "key", + "secret" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Sign" + ] + }, + "data": { + "type": "string" + }, + "key": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op", + "data", + "key", + "secret" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Verify" + ] + }, + "data": { + "type": "string" + }, + "key": { + "type": "string" + }, + "secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "AeadOverhead" + ] + } + } + }, + { + "type": "object", + "required": [ + "body", + "cs_op", + "nonce", + "shared_secret" + ], + "properties": { + "associated_data": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": "string" + }, + "cs_op": { + "type": "string", + "enum": [ + "DecryptAead" + ] + }, + "nonce": { + "type": "string" + }, + "shared_secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "body", + "cs_op", + "nonce", + "shared_secret" + ], + "properties": { + "associated_data": { + "type": [ + "string", + "null" + ] + }, + "body": { + "type": "string" + }, + "cs_op": { + "type": "string", + "enum": [ + "EncryptAead" + ] + }, + "nonce": { + "type": "string" + }, + "shared_secret": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "body", + "cs_op", + "nonce", + "shared_secret" + ], + "properties": { + "body": { + "type": "string" + }, + "cs_op": { + "type": "string", + "enum": [ + "CryptNoAuth" + ] + }, + "nonce": { + "type": "string" + }, + "shared_secret": { + "type": "string" + } + } + } + ], + "required": [ + "cs_id", + "op" + ], + "properties": { + "cs_id": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "op": { + "type": "string", + "enum": [ + "CryptoSystem" + ] + } + } + }, + { + "type": "object", + "required": [ + "data", + "node_ids", + "op", + "signatures" + ], + "properties": { + "data": { + "type": "string" + }, + "node_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "op": { + "type": "string", + "enum": [ + "VerifySignatures" + ] + }, + "signatures": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "data", + "key_pairs", + "op" + ], + "properties": { + "data": { + "type": "string" + }, + "key_pairs": { + "type": "array", + "items": { + "type": "string" + } + }, + "op": { + "type": "string", + "enum": [ + "GenerateSignatures" + ] + } + } + }, + { + "type": "object", + "required": [ + "kind", + "op" + ], + "properties": { + "kind": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "GenerateKeyPair" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "Now" + ] + } + } + }, + { + "type": "object", + "required": [ + "command", + "op" + ], + "properties": { + "command": { + "type": "string" + }, + "op": { + "type": "string", + "enum": [ + "Debug" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "VeilidVersionString" + ] + } + } + }, + { + "type": "object", + "required": [ + "op" + ], + "properties": { + "op": { + "type": "string", + "enum": [ + "VeilidVersion" + ] + } + } + } + ], + "properties": { + "id": { + "description": "Operation Id (pairs with Response, or empty if unidirectional)", + "default": 0, + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "definitions": { + "DHTSchema": { + "description": "Enum over all the supported DHT Schemas", + "oneOf": [ + { + "description": "Default DHT Schema (DFLT)", + "type": "object", + "required": [ + "kind", + "o_cnt" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "DFLT" + ] + }, + "o_cnt": { + "description": "Owner subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + { + "description": "Simple DHT Schema (SMPL)", + "type": "object", + "required": [ + "kind", + "members", + "o_cnt" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SMPL" + ] + }, + "members": { + "description": "Members", + "type": "array", + "items": { + "$ref": "#/definitions/DHTSchemaSMPLMember" + } + }, + "o_cnt": { + "description": "Owner subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + } + ] + }, + "DHTSchemaSMPLMember": { + "description": "Simple DHT Schema (SMPL) Member", + "type": "object", + "required": [ + "m_cnt", + "m_key" + ], + "properties": { + "m_cnt": { + "description": "Member subkey count", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "m_key": { + "description": "Member key", + "type": "string" + } + } + }, + "Sequencing": { + "type": "string", + "enum": [ + "NoPreference", + "PreferOrdered", + "EnsureOrdered" + ] + }, + "Stability": { + "type": "string", + "enum": [ + "LowLatency", + "Reliable" + ] + } + } +} diff --git a/veilid-python/veilid/state.py b/veilid-python/veilid/state.py new file mode 100644 index 00000000..acb5c37c --- /dev/null +++ b/veilid-python/veilid/state.py @@ -0,0 +1,350 @@ +from typing import Self, Optional +from enum import StrEnum + +from .types import * +from .config import * + +class AttachmentState(StrEnum): + DETACHED = 'Detached' + ATTACHING = 'Attaching' + ATTACHED_WEAK = 'AttachedWeak' + ATTACHED_GOOD = 'AttachedGood' + ATTACHED_STRONG = 'AttachedStrong' + FULLY_ATTACHED = 'FullyAttached' + OVER_ATTACHED = 'OverAttached' + DETACHING = 'Detaching' + +class VeilidStateAttachment: + state: AttachmentState + public_internet_ready: bool + local_network_ready: bool + + def __init__(self, state: AttachmentState, public_internet_ready: bool, local_network_ready: bool): + self.state = state + self.public_internet_ready = public_internet_ready + self.local_network_ready = local_network_ready + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidStateAttachment( + AttachmentState(j['state']), + j['public_internet_ready'], + j['local_network_ready']) + +class RPCStats: + messages_sent: int + messages_rcvd: int + questions_in_flight: int + last_question_ts: Optional[Timestamp] + last_seen_ts: Optional[Timestamp] + first_consecutive_seen_ts: Optional[Timestamp] + recent_lost_answers: int + failed_to_send: int + + def __init__(self, messages_sent: int, messages_rcvd: int, questions_in_flight: int, + last_question_ts: Optional[Timestamp], last_seen_ts: Optional[Timestamp], + first_consecutive_seen_ts: Optional[Timestamp], recent_lost_answers: int, failed_to_send: int): + self.messages_sent = messages_sent + self.messages_rcvd = messages_rcvd + self.questions_in_flight = questions_in_flight + self.last_question_ts = last_question_ts + self.last_seen_ts = last_seen_ts + self.first_consecutive_seen_ts = first_consecutive_seen_ts + self.recent_lost_answers = recent_lost_answers + self.failed_to_send = failed_to_send + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return RPCStats( + j['messages_sent'], + j['messages_rcvd'], + j['questions_in_flight'], + None if j['last_question_ts'] is None else Timestamp(j['last_question_ts']), + None if j['last_seen_ts'] is None else Timestamp(j['last_seen_ts']), + None if j['first_consecutive_seen_ts'] is None else Timestamp(j['first_consecutive_seen_ts']), + j['recent_lost_answers'], + j['failed_to_send']) + +class LatencyStats: + fastest: TimestampDuration + average: TimestampDuration + slowest: TimestampDuration + + def __init__(self, fastest: TimestampDuration, average: TimestampDuration, slowest: TimestampDuration): + self.fastest = fastest + self.average = average + self.slowest = slowest + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return LatencyStats( + TimestampDuration(j['fastest']), + TimestampDuration(j['average']), + TimestampDuration(j['slowest'])) + + +class TransferStats: + total: ByteCount + maximum: ByteCount + average: ByteCount + minimum: ByteCount + + def __init__(self, total: ByteCount, maximum: ByteCount, average: ByteCount, minimum: ByteCount): + self.total = total + self.maximum = maximum + self.average = average + self.minimum = minimum + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return TransferStats( + ByteCount(j['total']), + ByteCount(j['maximum']), + ByteCount(j['average']), + ByteCount(j['minimum'])) + + +class TransferStatsDownUp: + down: TransferStats + up: TransferStats + + def __init__(self, down: TransferStats, up: TransferStats): + self.down = down + self.up = up + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return TransferStatsDownUp( + TransferStats.from_json(j['down']), + TransferStats.from_json(j['up'])) + +class PeerStats: + time_added: Timestamp + rpc_stats: RPCStats + latency: Optional[LatencyStats] + transfer: TransferStatsDownUp + + def __init__(self, time_added: Timestamp, rpc_stats: RPCStats, latency: Optional[LatencyStats], transfer: TransferStatsDownUp): + self.time_added = time_added + self.rpc_stats = rpc_stats + self.latency = latency + self.transfer = transfer + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return PeerStats( + j['time_added'], + RPCStats.from_json(j['rpc_stats']), + None if j['latency'] is None else LatencyStats.from_json(j['latency']), + TransferStatsDownUp.from_json(j['transfer'])) + +class PeerTableData: + node_ids: list[str] + peer_address: str + peer_stats: PeerStats + + def __init__(self, node_ids: list[str], peer_address: str, peer_stats: PeerStats): + self.node_ids = node_ids + self.peer_address = peer_address + self.peer_stats = peer_stats + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return PeerTableData( + j['node_ids'], + j['peer_address'], + PeerStats.from_json(j['peer_stats'])) + +class VeilidStateNetwork: + started: bool + bps_down: ByteCount + bps_up: ByteCount + peers: list[PeerTableData] + + def __init__(self, started: bool, bps_down: ByteCount, bps_up: ByteCount, peers: list[PeerTableData]): + self.started = started + self.bps_down = bps_down + self.bps_up = bps_up + self.peers = peers + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidStateNetwork( + j['started'], + ByteCount(j['bps_down']), + ByteCount(j['bps_up']), + list(map(lambda x: PeerTableData.from_json(x), j['peers']))) + +class VeilidStateConfig: + config: VeilidConfig + + def __init__(self, config: VeilidConfig): + self.config = config + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidStateConfig( + VeilidConfig.from_json(j['config']) + ) + +class VeilidState: + attachment: VeilidStateAttachment + network: VeilidStateNetwork + config: VeilidStateConfig + + def __init__(self, attachment: VeilidStateAttachment, network: VeilidStateNetwork, config: VeilidStateConfig): + self.attachment = attachment + self.network = network + self.config = config + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidState( + VeilidStateAttachment.from_json(j['attachment']), + VeilidStateNetwork.from_json(j['network']), + VeilidStateConfig.from_json(j['config'])) + +class VeilidLog: + log_level: VeilidLogLevel + message: str + backtrace: Optional[str] + + def __init__(self, log_level: VeilidLogLevel, message: str, backtrace: Optional[str]): + self.log_level = log_level + self.message = message + self.backtrace = backtrace + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidLog( + VeilidLogLevel(j['log_level']), + j['message'], + j['backtrace']) + +class VeilidAppMessage: + sender: Optional[TypedKey] + message: bytes + + def __init__(self, sender: Optional[TypedKey], message: bytes): + self.sender = sender + self.message = message + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidAppMessage( + None if j['sender'] is None else TypedKey(j['sender']), + urlsafe_b64decode_no_pad(j['message'])) + +class VeilidAppCall: + sender: Optional[TypedKey] + message: bytes + call_id: str + + def __init__(self, sender: Optional[TypedKey], message: bytes, call_id: str): + self.sender = sender + self.message = message + self.call_id = call_id + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidAppCall( + None if j['sender'] is None else TypedKey(j['sender']), + urlsafe_b64decode_no_pad(j['message']), + j['call_id']) + +class VeilidRouteChange: + dead_routes: list[RouteId] + dead_remote_routes: list[RouteId] + + def __init__(self, dead_routes: list[RouteId], dead_remote_routes: list[RouteId]): + self.dead_routes = dead_routes + self.dead_remote_routes = dead_remote_routes + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidRouteChange( + list(map(lambda x: RouteId(x), j['dead_routes'])), + list(map(lambda x: RouteId(x), j['dead_remote_routes']))) + +class VeilidValueChange: + key: TypedKey + subkeys: list[ValueSubkey] + count: int + value: ValueData + + def __init__(self, key: TypedKey, subkeys: list[ValueSubkey], count: int, value: ValueData): + self.key = key + self.subkeys = subkeys + self.count = count + self.value = value + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + return VeilidValueChange( + TypedKey(j['key']), + list(map(lambda x: ValueSubkey(x), j['subkeys'])), + j['count'], + ValueData.from_json(j['value'])) + + +class VeilidUpdateKind(StrEnum): + LOG = "Log" + APP_MESSAGE = "AppMessage" + APP_CALL = "AppCall" + ATTACHMENT = "Attachment" + NETWORK = "Network" + CONFIG = "Config" + ROUTE_CHANGE = "RouteChange" + VALUE_CHANGE = "ValueChange" + SHUTDOWN = "Shutdown" + +class VeilidUpdate: + kind: VeilidUpdateKind + detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange] + + def __init__(self, kind: VeilidUpdateKind, detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange]): + self.kind = kind + self.detail = detail + + @staticmethod + def from_json(j: dict) -> Self: + '''JSON object hook''' + kind = VeilidUpdateKind(j['kind']) + detail = None + match kind: + case VeilidUpdateKind.LOG: + detail = VeilidLog.from_json(j) + case VeilidUpdateKind.APP_MESSAGE: + detail = VeilidAppMessage.from_json(j) + case VeilidUpdateKind.APP_CALL: + detail = VeilidAppCall.from_json(j) + case VeilidUpdateKind.ATTACHMENT: + detail = VeilidStateAttachment.from_json(j) + case VeilidUpdateKind.NETWORK: + detail = VeilidStateNetwork.from_json(j) + case VeilidUpdateKind.CONFIG: + detail = VeilidStateConfig.from_json(j) + case VeilidUpdateKind.ROUTE_CHANGE: + detail = VeilidRouteChange.from_json(j) + case VeilidUpdateKind.VALUE_CHANGE: + detail = VeilidValueChange.from_json(j) + case VeilidUpdateKind.SHUTDOWN: + detail = None + case _: + raise ValueError("Unknown VeilidUpdateKind") + return VeilidUpdate(kind, detail) diff --git a/veilid-python/veilid/types.py b/veilid-python/veilid/types.py new file mode 100644 index 00000000..684f3b17 --- /dev/null +++ b/veilid-python/veilid/types.py @@ -0,0 +1,301 @@ +import time +import json +import base64 + +from enum import StrEnum +from typing import Self, Optional, Any, Tuple + +#################################################################### + +def urlsafe_b64encode_no_pad(b: bytes) -> str: + """ + Removes any `=` used as padding from the encoded string. + """ + return base64.urlsafe_b64encode(b).decode().rstrip("=") + + +def urlsafe_b64decode_no_pad(s: str) -> bytes: + """ + Adds back in the required padding before decoding. + """ + padding = 4 - (len(s) % 4) + s = s + ("=" * padding) + return base64.urlsafe_b64decode(s) + +class VeilidJSONEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, bytes): + return urlsafe_b64encode_no_pad(o) + if hasattr(o, "to_json") and callable(o.to_json): + return o.to_json() + return json.JSONEncoder.default(self, o) + + @staticmethod + def dumps(req: Any, *args, **kwargs) -> str: + return json.dumps(req, cls = VeilidJSONEncoder, *args, **kwargs) + +#################################################################### + +class VeilidLogLevel(StrEnum): + ERROR = 'Error' + WARN = 'Warn' + INFO = 'Info' + DEBUG = 'Debug' + TRACE = 'Trace' + +class CryptoKind(StrEnum): + CRYPTO_KIND_NONE = "NONE" + CRYPTO_KIND_VLD0 = "VLD0" + +class Stability(StrEnum): + LOW_LATENCY = "LowLatency" + RELIABLE = "Reliable" + +class Sequencing(StrEnum): + NO_PREFERENCE = "NoPreference" + PREFER_ORDERED = "PreferOrdered" + ENSURE_ORDERED = "EnsureOrdered" + +class DHTSchemaKind(StrEnum): + DFLT = "DFLT" + SMPL = "SMPL" + +#################################################################### + +class Timestamp(int): + pass + +class TimestampDuration(int): + pass + +class ByteCount(int): + pass + +class OperationId(int): + pass + +class RouteId(str): + pass + +class CryptoKey: + def to_bytes(self) -> bytes: + return urlsafe_b64decode_no_pad(self) + +class CryptoKeyDistance(CryptoKey, str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return CryptoKeyDistance(urlsafe_b64encode_no_pad(b)) + +class PublicKey(CryptoKey, str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return PublicKey(urlsafe_b64encode_no_pad(b)) + +class SecretKey(CryptoKey, str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return SecretKey(urlsafe_b64encode_no_pad(b)) + +class SharedSecret(CryptoKey, str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return SharedSecret(urlsafe_b64encode_no_pad(b)) + +class HashDigest(CryptoKey, str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return HashDigest(urlsafe_b64encode_no_pad(b)) + +class Signature(str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return Signature(urlsafe_b64encode_no_pad(b)) + def to_bytes(self) -> bytes: + return urlsafe_b64decode_no_pad(self) + +class Nonce(str): + @staticmethod + def from_bytes(b: bytes) -> Self: + return Signature(urlsafe_b64encode_no_pad(b)) + def to_bytes(self) -> bytes: + return urlsafe_b64decode_no_pad(self) + +class KeyPair(str): + @staticmethod + def from_parts(key: PublicKey, secret: SecretKey) -> Self: + return KeyPair(key + ":" + secret) + def key(self) -> PublicKey: + return PublicKey(str.split(":", 1)[0]) + def secret(self) -> SecretKey: + return SecretKey(str.split(":", 1)[1]) + def to_parts(self) -> Tuple[PublicKey, SecretKey]: + parts = str.split(":", 1) + return (PublicKey(parts[0]), SecretKey(parts[1])) + +class CryptoTyped: + def kind(self) -> CryptoKind: + if self[4] != ':': + raise ValueError("Not CryptoTyped") + return CryptoKind(self[0:4]) + def _value(self) -> str: + if self[4] != ':': + raise ValueError("Not CryptoTyped") + return self[5:] + +class TypedKey(CryptoTyped, str): + @staticmethod + def from_value(kind: CryptoKind, value: PublicKey) -> Self: + return TypedKey(kind + ":" + value) + def value(self) -> PublicKey: + PublicKey(self._value()) + +class TypedSecret(CryptoTyped, str): + @staticmethod + def from_value(kind: CryptoKind, value: SecretKey) -> Self: + return TypedSecret(kind + ":" + value) + def value(self) -> SecretKey: + SecretKey(self._value()) + +class TypedKeyPair(CryptoTyped, str): + @staticmethod + def from_value(kind: CryptoKind, value: KeyPair) -> Self: + return TypedKeyPair(kind + ":" + value) + def value(self) -> KeyPair: + KeyPair(self._value()) + +class TypedSignature(CryptoTyped, str): + @staticmethod + def from_value(kind: CryptoKind, value: Signature) -> Self: + return TypedSignature(kind + ":" + value) + def value(self) -> Signature: + Signature(self._value()) + +class ValueSubkey(int): + pass + +class ValueSeqNum(int): + pass + +#################################################################### + +class VeilidVersion: + _major: int + _minor: int + _patch: int + def __init__(self, major: int, minor: int, patch: int): + self._major = major + self._minor = minor + self._patch = patch + @property + def major(self): + return self._major + @property + def minor(self): + return self._minor + @property + def patch(self): + return self._patch + +class NewPrivateRouteResult: + route_id: RouteId + blob: bytes + + def __init__(self, route_id: RouteId, blob: bytes): + self.route_id = route_id + self.blob = blob + + def to_tuple(self) -> Tuple[RouteId, bytes]: + return (self.route_id, self.blob) + + @staticmethod + def from_json(j: dict) -> Self: + return NewPrivateRouteResult( + RouteId(j['route_id']), + urlsafe_b64decode_no_pad(j['blob'])) + +class DHTSchemaSMPLMember: + m_key: PublicKey + m_cnt: int + def __init__(self, m_key: PublicKey, m_cnt: int): + self.m_key = m_key + self.m_cnt = m_cnt + @staticmethod + def from_json(j: dict) -> Self: + return DHTSchemaSMPLMember( + PublicKey(j['m_key']), + j['m_cnt']) + def to_json(self) -> dict: + return self.__dict__ + +class DHTSchema: + kind: DHTSchemaKind + + def __init__(self, kind: DHTSchemaKind, **kwargs): + self.kind = kind + for k, v in kwargs.items(): + setattr(self, k, v) + + @staticmethod + def dflt(o_cnt: int) -> Self: + Self(DHTSchemaKind.DFLT, o_cnt = o_cnt) + + @staticmethod + def smpl(o_cnt: int, members: list[DHTSchemaSMPLMember]) -> Self: + Self(DHTSchemaKind.SMPL, o_cnt = o_cnt, members = members) + + @staticmethod + def from_json(j: dict) -> Self: + if DHTSchemaKind(j['kind']) == DHTSchemaKind.DFLT: + return DHTSchema.dflt(j['o_cnt']) + if DHTSchemaKind(j['kind']) == DHTSchemaKind.SMPL: + return DHTSchema.smpl( + j['o_cnt'], + list(map(lambda x: DHTSchemaSMPLMember.from_json(x), j['members']))) + raise Exception("Unknown DHTSchema kind", j['kind']) + + def to_json(self) -> dict: + return self.__dict__ + +class DHTRecordDescriptor: + key: TypedKey + owner: PublicKey + owner_secret: Optional[SecretKey] + schema: DHTSchema + + def __init__(self, key: TypedKey, owner: PublicKey, owner_secret: Optional[SecretKey], schema: DHTSchema): + self.key = key + self.owner = owner + self.owner_secret = owner_secret + self.schema = schema + + @staticmethod + def from_json(j: dict) -> Self: + DHTRecordDescriptor( + TypedKey(j['key']), + PublicKey(j['owner']), + None if j['owner_secret'] is None else SecretKey(j['owner_secret']), + DHTSchema.from_json(j['schema'])) + + def to_json(self) -> dict: + return self.__dict__ + +class ValueData: + seq: ValueSeqNum + data: bytes + writer: PublicKey + + def __init__(self, seq: ValueSeqNum, data: bytes, writer: PublicKey): + self.seq = seq + self.data = data + self.writer = writer + + @staticmethod + def from_json(j: dict) -> Self: + DHTRecordDescriptor( + ValueSeqNum(j['seq']), + urlsafe_b64decode_no_pad(j['data']), + PublicKey(j['writer'])) + + def to_json(self) -> dict: + return self.__dict__ + diff --git a/veilid-server/Cargo.toml b/veilid-server/Cargo.toml index 977e55a1..11317371 100644 --- a/veilid-server/Cargo.toml +++ b/veilid-server/Cargo.toml @@ -3,7 +3,6 @@ name = "veilid-server" version = "0.1.0" authors = ["John Smith "] edition = "2021" -build = "build.rs" license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)" [[bin]] @@ -11,13 +10,13 @@ name = "veilid-server" path = "src/main.rs" [features] -default = [ "rt-tokio", "veilid-core/default" ] -crypto-test = [ "rt-tokio", "veilid-core/crypto-test"] -crypto-test-none = [ "rt-tokio", "veilid-core/crypto-test-none"] +default = ["rt-tokio", "veilid-core/default"] +crypto-test = ["rt-tokio", "veilid-core/crypto-test"] +crypto-test-none = ["rt-tokio", "veilid-core/crypto-test-none"] -rt-async-std = [ "veilid-core/rt-async-std", "async-std", "opentelemetry/rt-async-std", "opentelemetry-otlp/grpc-sys" ] -rt-tokio = [ "veilid-core/rt-tokio", "tokio", "tokio-stream", "tokio-util", "opentelemetry/rt-tokio", "console-subscriber" ] -tracking = [ "veilid-core/tracking" ] +rt-async-std = ["veilid-core/rt-async-std", "async-std", "opentelemetry/rt-async-std", "opentelemetry-otlp/grpc-sys"] +rt-tokio = ["veilid-core/rt-tokio", "tokio", "tokio-stream", "tokio-util", "opentelemetry/rt-tokio", "console-subscriber"] +tracking = ["veilid-core/tracking"] [dependencies] veilid-core = { path = "../veilid-core", default-features = false } @@ -39,9 +38,7 @@ color-eyre = { version = "^0", default-features = false } backtrace = "^0" clap = "^3" directories = "^4" -capnp = "^0" parking_lot = "^0" -capnp-rpc = "^0" config = { version = "^0", features = ["yaml"] } cfg-if = "^1" serde = "^1" @@ -58,6 +55,7 @@ rpassword = "^6" hostname = "^0" stop-token = { version = "^0", default-features = false } sysinfo = { version = "^0.28.4", default-features = false } +wg = "0.3.2" [target.'cfg(windows)'.dependencies] windows-service = "^0" @@ -74,6 +72,3 @@ tracing-journald = "^0" [dev-dependencies] serial_test = "^0" - -[build-dependencies] -capnpc = "^0" diff --git a/veilid-server/build.rs b/veilid-server/build.rs deleted file mode 100644 index a9e4db80..00000000 --- a/veilid-server/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - ::capnpc::CompilerCommand::new() - .file("proto/veilid-client.capnp") - .run() - .expect("compiling schema"); -} diff --git a/veilid-server/proto/veilid-client.capnp b/veilid-server/proto/veilid-client.capnp deleted file mode 100644 index 469a3b09..00000000 --- a/veilid-server/proto/veilid-client.capnp +++ /dev/null @@ -1,25 +0,0 @@ -@0xd29582d26b2fb073; - -struct ApiResult @0x8111724bdb812929 { - union { - ok @0 :Text; - err @1 :Text; - } -} - -interface Registration @0xdd45f30a7c22e391 {} - -interface VeilidServer @0xcb2c699f14537f94 { - register @0 (veilidClient :VeilidClient) -> (registration :Registration, state :Text, settings :Text); - debug @1 (command :Text) -> (result :ApiResult); - attach @2 () -> (result :ApiResult); - detach @3 () -> (result :ApiResult); - shutdown @4 (); - getState @5 () -> (result :ApiResult); - changeLogLevel @6 (layer :Text, logLevel :Text) -> (result :ApiResult); - appCallReply @7 (id :UInt64, message :Data) -> (result :ApiResult); -} - -interface VeilidClient @0xbfcea60fb2ba4736 { - update @0 (veilidUpdate :Text); -} \ No newline at end of file diff --git a/veilid-server/src/client_api.rs b/veilid-server/src/client_api.rs index 269dce3a..d13b2a7d 100644 --- a/veilid-server/src/client_api.rs +++ b/veilid-server/src/client_api.rs @@ -1,294 +1,53 @@ use crate::settings::*; use crate::tools::*; -use crate::veilid_client_capnp::*; use crate::veilid_logs::VeilidLogs; -use capnp::capability::Promise; -use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, RpcSystem}; use cfg_if::*; -use futures_util::{future::try_join_all, FutureExt as FuturesFutureExt, StreamExt}; -use serde::*; -use std::cell::RefCell; +use futures_util::{future::try_join_all, stream::FuturesUnordered, StreamExt}; +use parking_lot::Mutex; use std::collections::HashMap; -use std::fmt; use std::net::SocketAddr; -use std::rc::Rc; -use stop_token::future::FutureExt; +use std::sync::Arc; +use stop_token::future::FutureExt as _; use stop_token::*; use tracing::*; +use veilid_core::json_api::JsonRequestProcessor; +use veilid_core::tools::*; use veilid_core::*; +use wg::AsyncWaitGroup; -// Encoding for ApiResult -fn encode_api_result( - result: &VeilidAPIResult, - builder: &mut api_result::Builder, -) { - match result { - Ok(v) => { - builder.set_ok(&serialize_json(v)); - } - Err(e) => { - builder.set_err(&serialize_json(e)); - } - } -} - -// --- interface Registration --------------------------------- - -struct RegistrationHandle { - client: veilid_client::Client, - requests_in_flight: i32, -} - -struct RegistrationMap { - registrations: HashMap, -} - -impl RegistrationMap { - fn new() -> Self { - Self { - registrations: HashMap::new(), - } - } -} - -struct RegistrationImpl { - id: u64, - registration_map: Rc>, -} - -impl RegistrationImpl { - fn new(id: u64, registrations: Rc>) -> Self { - Self { - id, - registration_map: registrations, - } - } -} - -impl Drop for RegistrationImpl { - fn drop(&mut self) { - debug!("Registration dropped"); - self.registration_map - .borrow_mut() - .registrations - .remove(&self.id); - } -} - -impl registration::Server for RegistrationImpl {} - -// --- interface VeilidServer --------------------------------- - -struct VeilidServerImpl { - veilid_api: veilid_core::VeilidAPI, - veilid_logs: VeilidLogs, - settings: Settings, - next_id: u64, - pub registration_map: Rc>, -} - -impl VeilidServerImpl { - #[instrument(level = "trace", skip_all)] - pub fn new( - veilid_api: veilid_core::VeilidAPI, - veilid_logs: VeilidLogs, - settings: Settings, - ) -> Self { - Self { - next_id: 0, - registration_map: Rc::new(RefCell::new(RegistrationMap::new())), - veilid_api, - veilid_logs, - settings, - } - } -} - -impl veilid_server::Server for VeilidServerImpl { - #[instrument(level = "trace", skip_all)] - fn register( - &mut self, - params: veilid_server::RegisterParams, - mut results: veilid_server::RegisterResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::register"); - - self.registration_map.borrow_mut().registrations.insert( - self.next_id, - RegistrationHandle { - client: pry!(pry!(params.get()).get_veilid_client()), - requests_in_flight: 0, - }, - ); - - let veilid_api = self.veilid_api.clone(); - let settings = self.settings.clone(); - let registration = capnp_rpc::new_client(RegistrationImpl::new( - self.next_id, - self.registration_map.clone(), - )); - self.next_id += 1; - - Promise::from_future(async move { - let state = veilid_api - .get_state() - .await - .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))?; - let state = serialize_json(state); - - let mut res = results.get(); - res.set_registration(registration); - res.set_state(&state); - - let settings = &*settings.read(); - let settings_json_string = serialize_json(settings); - let mut settings_json = json::parse(&settings_json_string) - .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))?; - settings_json["core"]["network"].remove("node_id_secret"); - let safe_settings_json = settings_json.to_string(); - res.set_settings(&safe_settings_json); - - Ok(()) - }) - } - - #[instrument(level = "trace", skip_all)] - fn debug( - &mut self, - params: veilid_server::DebugParams, - mut results: veilid_server::DebugResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::debug"); - let veilid_api = self.veilid_api.clone(); - let command = pry!(pry!(params.get()).get_command()).to_owned(); - - Promise::from_future(async move { - let result = veilid_api.debug(command).await; - encode_api_result(&result, &mut results.get().init_result()); - Ok(()) - }) - } - - #[instrument(level = "trace", skip_all)] - fn attach( - &mut self, - _params: veilid_server::AttachParams, - mut results: veilid_server::AttachResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::attach"); - let veilid_api = self.veilid_api.clone(); - Promise::from_future(async move { - let result = veilid_api.attach().await; - encode_api_result(&result, &mut results.get().init_result()); - Ok(()) - }) - } - - #[instrument(level = "trace", skip_all)] - fn detach( - &mut self, - _params: veilid_server::DetachParams, - mut results: veilid_server::DetachResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::detach"); - let veilid_api = self.veilid_api.clone(); - Promise::from_future(async move { - let result = veilid_api.detach().await; - encode_api_result(&result, &mut results.get().init_result()); - Ok(()) - }) - } - - #[instrument(level = "trace", skip_all)] - fn shutdown( - &mut self, - _params: veilid_server::ShutdownParams, - mut _results: veilid_server::ShutdownResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::shutdown"); - - cfg_if::cfg_if! { - if #[cfg(windows)] { - assert!(false, "write me!"); - } - else { - crate::server::shutdown(); - } - } - - Promise::ok(()) - } - - #[instrument(level = "trace", skip_all)] - fn get_state( - &mut self, - _params: veilid_server::GetStateParams, - mut results: veilid_server::GetStateResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::get_state"); - let veilid_api = self.veilid_api.clone(); - Promise::from_future(async move { - let result = veilid_api.get_state().await; - encode_api_result(&result, &mut results.get().init_result()); - Ok(()) - }) - } - - #[instrument(level = "trace", skip_all)] - fn change_log_level( - &mut self, - params: veilid_server::ChangeLogLevelParams, - mut results: veilid_server::ChangeLogLevelResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::change_log_level"); - - let layer = pry!(pry!(params.get()).get_layer()).to_owned(); - let log_level_json = pry!(pry!(params.get()).get_log_level()).to_owned(); - let log_level: veilid_core::VeilidConfigLogLevel = - pry!(veilid_core::deserialize_json(&log_level_json) - .map_err(|e| ::capnp::Error::failed(format!("{:?}", e)))); - - let result = self.veilid_logs.change_log_level(layer, log_level); - encode_api_result(&result, &mut results.get().init_result()); - Promise::ok(()) - } - - #[instrument(level = "trace", skip_all)] - fn app_call_reply( - &mut self, - params: veilid_server::AppCallReplyParams, - mut results: veilid_server::AppCallReplyResults, - ) -> Promise<(), ::capnp::Error> { - trace!("VeilidServerImpl::app_call_reply"); - - let id = OperationId::new(pry!(params.get()).get_id()); - let message = pry!(pry!(params.get()).get_message()).to_owned(); - - let veilid_api = self.veilid_api.clone(); - Promise::from_future(async move { - let result = veilid_api.app_call_reply(id, message).await; - encode_api_result(&result, &mut results.get().init_result()); - Ok(()) - }) +cfg_if! { + if #[cfg(feature="rt-async-std")] { + use async_std::io::prelude::BufReadExt; + use async_std::io::WriteExt; + } else if #[cfg(feature="rt-tokio")] { + use tokio::io::AsyncBufReadExt; + use tokio::io::AsyncWriteExt; } } // --- Client API Server-Side --------------------------------- -type ClientApiAllFuturesJoinHandle = - JoinHandle, Box<(dyn std::error::Error + 'static)>>>; +type ClientApiAllFuturesJoinHandle = MustJoinHandle>>; + +struct RequestLine { + // Request to process + line: String, + // Where to send the response + responses_tx: flume::Sender, +} struct ClientApiInner { veilid_api: veilid_core::VeilidAPI, veilid_logs: VeilidLogs, settings: Settings, - registration_map: Rc>, stop: Option, join_handle: Option, + update_channels: HashMap<(SocketAddr, SocketAddr), flume::Sender>, } +#[derive(Clone)] pub struct ClientApi { - inner: RefCell, + inner: Arc>, } impl ClientApi { @@ -297,24 +56,43 @@ impl ClientApi { veilid_api: veilid_core::VeilidAPI, veilid_logs: VeilidLogs, settings: Settings, - ) -> Rc { - Rc::new(Self { - inner: RefCell::new(ClientApiInner { + ) -> Self { + Self { + inner: Arc::new(Mutex::new(ClientApiInner { veilid_api, veilid_logs, settings, - registration_map: Rc::new(RefCell::new(RegistrationMap::new())), stop: Some(StopSource::new()), join_handle: None, - }), - }) + update_channels: HashMap::new(), + })), + } + } + + #[instrument(level = "trace", skip_all)] + fn shutdown(&self) { + trace!("ClientApi::shutdown"); + + crate::server::shutdown(); + } + + #[instrument(level = "trace", skip_all)] + fn change_log_level( + &self, + layer: String, + log_level: VeilidConfigLogLevel, + ) -> VeilidAPIResult<()> { + trace!("ClientApi::change_log_level"); + + let veilid_logs = self.inner.lock().veilid_logs.clone(); + veilid_logs.change_log_level(layer, log_level) } #[instrument(level = "trace", skip(self))] - pub async fn stop(self: Rc) { + pub async fn stop(&self) { trace!("ClientApi::stop requested"); let jh = { - let mut inner = self.inner.borrow_mut(); + let mut inner = self.inner.lock(); if inner.join_handle.is_none() { trace!("ClientApi stop ignored"); return; @@ -324,17 +102,13 @@ impl ClientApi { }; trace!("ClientApi::stop: waiting for stop"); if let Err(err) = jh.await { - error!("{}", err); + eprintln!("{}", err); } trace!("ClientApi::stop: stopped"); } - #[instrument(level = "trace", skip(self, client), err)] - async fn handle_incoming( - self: Rc, - bind_addr: SocketAddr, - client: veilid_server::Client, - ) -> Result<(), Box> { + #[instrument(level = "trace", skip(self), err)] + async fn handle_incoming(self, bind_addr: SocketAddr) -> std::io::Result<()> { let listener = TcpListener::bind(bind_addr).await?; debug!("Client API listening on: {:?}", bind_addr); @@ -347,119 +121,307 @@ impl ClientApi { } } - let stop_token = self.inner.borrow().stop.as_ref().unwrap().token(); - let incoming_loop = async move { - while let Ok(Some(stream_result)) = - incoming_stream.next().timeout_at(stop_token.clone()).await - { - let stream = stream_result?; - stream.set_nodelay(true)?; - cfg_if! { - if #[cfg(feature="rt-async-std")] { - use futures_util::AsyncReadExt; - let (reader, writer) = stream.split(); - } else if #[cfg(feature="rt-tokio")] { - use tokio_util::compat::*; - let (reader, writer) = stream.into_split(); - let reader = reader.compat(); - let writer = writer.compat_write(); - } - } - let network = twoparty::VatNetwork::new( - reader, - writer, - rpc_twoparty_capnp::Side::Server, - Default::default(), - ); + // Make wait group for all incoming connections + let awg = AsyncWaitGroup::new(); - let rpc_system = RpcSystem::new(Box::new(network), Some(client.clone().client)); + let stop_token = self.inner.lock().stop.as_ref().unwrap().token(); + while let Ok(Some(stream_result)) = + incoming_stream.next().timeout_at(stop_token.clone()).await + { + // Get the stream to process + let stream = stream_result?; + stream.set_nodelay(true)?; - spawn_local(rpc_system.map(drop)); - } - Ok::<(), Box>(()) - }; + // Increment wait group + awg.add(1); + let t_awg = awg.clone(); - incoming_loop.await + // Process the connection + spawn(self.clone().handle_connection(stream, t_awg)).detach(); + } + + // Wait for all connections to terminate + awg.wait().await; + + Ok(()) } - #[instrument(level = "trace", skip_all)] - fn send_request_to_all_clients(self: Rc, request: F) - where - F: Fn(u64, &mut RegistrationHandle) -> Option<::capnp::capability::RemotePromise>, - T: capnp::traits::Pipelined + capnp::traits::Owned + 'static + Unpin, - { - // Send status update to each registered client - let registration_map = self.inner.borrow().registration_map.clone(); - let registration_map1 = registration_map.clone(); - let regs = &mut registration_map.borrow_mut().registrations; - for (&id, mut registration) in regs.iter_mut() { - if registration.requests_in_flight >= 256 { - println!( - "too many requests in flight: {}", - registration.requests_in_flight - ); + // Process control messages for the server + async fn process_control(self, args: Vec) -> VeilidAPIResult { + if args.len() == 0 { + apibail_generic!("no control request specified"); + } + if args[0] == "Shutdown" { + if args.len() != 1 { + apibail_generic!("wrong number of arguments"); + } + self.shutdown(); + Ok("".to_owned()) + } else if args[0] == "ChangeLogLevel" { + if args.len() != 3 { + apibail_generic!("wrong number of arguments"); + } + let log_level = VeilidConfigLogLevel::from_str(&args[2])?; + self.change_log_level(args[1].clone(), log_level)?; + Ok("".to_owned()) + } else if args[0] == "GetServerSettings" { + if args.len() != 1 { + apibail_generic!("wrong number of arguments"); + } + let settings = self.inner.lock().settings.clone(); + let settings = &*settings.read(); + let settings_json_string = serialize_json(settings); + let mut settings_json = + json::parse(&settings_json_string).map_err(VeilidAPIError::internal)?; + settings_json["core"]["network"].remove("node_id_secret"); + settings_json["core"]["protected_store"].remove("device_encryption_key_password"); + settings_json["core"]["protected_store"].remove("new_device_encryption_key_password"); + let safe_settings_json = settings_json.to_string(); + Ok(safe_settings_json) + } else if args[0] == "EmitSchema" { + if args.len() != 2 { + apibail_generic!("wrong number of arguments"); } - registration.requests_in_flight += 1; - if let Some(request_promise) = request(id, registration) { - let registration_map2 = registration_map1.clone(); - spawn_local(request_promise.promise.map(move |r| match r { - Ok(_) => { - if let Some(ref mut s) = - registration_map2.borrow_mut().registrations.get_mut(&id) - { - s.requests_in_flight -= 1; - } - } - Err(e) => { - println!("Got error: {:?}. Dropping registation.", e); - registration_map2.borrow_mut().registrations.remove(&id); - } - })); + let mut schemas = HashMap::::new(); + veilid_core::json_api::emit_schemas(&mut schemas); + + let Some(schema) = schemas.get(&args[1]) else { + apibail_invalid_argument!("invalid schema", "schema", args[1].clone()); + }; + + Ok(schema.clone()) + } else { + apibail_generic!("unknown control message"); + } + } + + async fn process_request_line( + self, + jrp: JsonRequestProcessor, + request_line: RequestLine, + ) -> VeilidAPIResult> { + let line = request_line.line; + let responses_tx = request_line.responses_tx; + + // Unmarshal NDJSON - newline => json + // (trim all whitespace around input lines just to make things more permissive for API users) + let request: json_api::Request = deserialize_json(&line)?; + + // See if this is a control message or a veilid-core message + let response = if let json_api::RequestOp::Control { args } = request.op { + // Process control messages + json_api::Response { + id: request.id, + op: json_api::ResponseOp::Control { + result: json_api::to_json_api_result(self.process_control(args).await), + }, + } + } else { + // Process with ndjson api + jrp.clone().process_request(request).await + }; + + // Marshal json + newline => NDJSON + let response_string = serialize_json(json_api::RecvMessage::Response(response)) + "\n"; + if let Err(e) = responses_tx.send_async(response_string).await { + eprintln!("response not sent: {}", e) + } + VeilidAPIResult::Ok(None) + } + + async fn next_request_line( + requests_rx: flume::Receiver>, + ) -> VeilidAPIResult> { + Ok(requests_rx.recv_async().await.ok().flatten()) + } + + async fn receive_requests( + self, + mut reader: R, + requests_tx: flume::Sender>, + responses_tx: flume::Sender, + ) -> VeilidAPIResult> { + let mut linebuf = String::new(); + while let Ok(size) = reader.read_line(&mut linebuf).await { + // Eof? + if size == 0 { + break; + } + + // Put the processing in the async queue + let line = linebuf.trim().to_owned(); + linebuf.clear(); + + // Ignore newlines + if line.len() == 0 { + continue; + } + + // Enqueue the line for processing in parallel + let request_line = RequestLine { + line, + responses_tx: responses_tx.clone(), + }; + if let Err(e) = requests_tx.send_async(Some(request_line)).await { + eprintln!("failed to enqueue request: {}", e); + break; + } + } + + VeilidAPIResult::Ok(None) + } + + async fn send_responses( + self, + responses_rx: flume::Receiver, + mut writer: W, + ) -> VeilidAPIResult> { + while let Ok(resp) = responses_rx.recv_async().await { + if let Err(_) = writer.write_all(resp.as_bytes()).await { + break; + } + } + VeilidAPIResult::Ok(None) + } + + pub async fn handle_connection(self, stream: TcpStream, awg: AsyncWaitGroup) { + // Get address of peer + let peer_addr = match stream.peer_addr() { + Ok(v) => v, + Err(e) => { + eprintln!("can't get peer address: {}", e); + return; + } + }; + // Get local address + let local_addr = match stream.local_addr() { + Ok(v) => v, + Err(e) => { + eprintln!("can't get local address: {}", e); + return; + } + }; + // Get connection tuple + let conn_tuple = (local_addr, peer_addr); + + debug!( + "Accepted Client API Connection: {:?} -> {:?}", + peer_addr, local_addr + ); + + // Make stop token to quit when stop() is requested externally + let stop_token = self.inner.lock().stop.as_ref().unwrap().token(); + + // Split into reader and writer halves + // with line buffering on the reader + cfg_if! { + if #[cfg(feature="rt-async-std")] { + use futures_util::AsyncReadExt; + let (reader, mut writer) = stream.split(); + let reader = BufReader::new(reader); + } else if #[cfg(feature="rt-tokio")] { + let (reader, writer) = stream.into_split(); + let reader = BufReader::new(reader); + } + } + + // Make request processor for this connection + let api = self.inner.lock().veilid_api.clone(); + let jrp = json_api::JsonRequestProcessor::new(api); + + // Futures to process unordered + let mut unord = FuturesUnordered::new(); + + // Requests and responses are done serially to the socket + // but the requests are processed in parallel by the FuturesUnordered + let (requests_tx, requests_rx) = flume::unbounded(); + let (responses_tx, responses_rx) = flume::unbounded(); + + // Start sending updates + self.inner + .lock() + .update_channels + .insert(conn_tuple, responses_tx.clone()); + + // Request receive processor future + // Receives from socket and enqueues RequestLines + // Completes when the connection is closed or there is a failure + unord.push(system_boxed(self.clone().receive_requests( + reader, + requests_tx, + responses_tx, + ))); + + // Response send processor + // Sends finished response strings out the socket + // Completes when the responses channel is closed + unord.push(system_boxed( + self.clone().send_responses(responses_rx, writer), + )); + + // Add future to process first request + unord.push(system_boxed(Self::next_request_line(requests_rx.clone()))); + + // Send and receive until we're done or a stop is requested + while let Ok(Some(r)) = unord.next().timeout_at(stop_token.clone()).await { + // See if we got some work to do + let request_line = match r { + Ok(Some(request_line)) => { + // Add future to process next request + unord.push(system_boxed(Self::next_request_line(requests_rx.clone()))); + + // Socket receive future returned something to process + request_line + } + Ok(None) => { + // Non-request future finished + continue; + } + Err(e) => { + // Connection processing failure, abort + eprintln!("Connection processing failure: {}", e); + break; + } + }; + + // Enqueue unordered future to process request line in parallel + unord.push(system_boxed( + self.clone().process_request_line(jrp.clone(), request_line), + )); + } + + // Stop sending updates + self.inner.lock().update_channels.remove(&conn_tuple); + + debug!( + "Closed Client API Connection: {:?} -> {:?}", + peer_addr, local_addr + ); + + awg.done(); + } + + #[instrument(level = "trace", skip(self))] + pub fn handle_update(&self, veilid_update: veilid_core::VeilidUpdate) { + // serialize update to NDJSON + let veilid_update = serialize_json(json_api::RecvMessage::Update(veilid_update)) + "\n"; + + // Pass other updates to clients + let inner = self.inner.lock(); + for ch in inner.update_channels.values() { + if let Err(_) = ch.send(veilid_update.clone()) { + // eprintln!("failed to send update: {}", e); } } } #[instrument(level = "trace", skip(self))] - pub fn handle_update(self: Rc, veilid_update: veilid_core::VeilidUpdate) { - // serialize update - let veilid_update = serialize_json(veilid_update); - - // Pass other updates to clients - self.send_request_to_all_clients(|_id, registration| { - match veilid_update - .len() - .try_into() - .map_err(|e| ::capnp::Error::failed(format!("{:?}", e))) - { - Ok(len) => { - let mut request = registration.client.update_request(); - let mut rpc_veilid_update = request.get().init_veilid_update(len); - rpc_veilid_update.push_str(&veilid_update); - Some(request.send()) - } - Err(_) => None, - } - }); - } - - #[instrument(level = "trace", skip(self))] - pub fn run(self: Rc, bind_addrs: Vec) { - // Create client api VeilidServer - let veilid_server_impl = VeilidServerImpl::new( - self.inner.borrow().veilid_api.clone(), - self.inner.borrow().veilid_logs.clone(), - self.inner.borrow().settings.clone(), - ); - self.inner.borrow_mut().registration_map = veilid_server_impl.registration_map.clone(); - - // Make a client object for the server to send to each rpc client - let client: veilid_server::Client = capnp_rpc::new_client(veilid_server_impl); - + pub fn run(&self, bind_addrs: Vec) { let bind_futures = bind_addrs .iter() - .map(|addr| self.clone().handle_incoming(*addr, client.clone())); + .map(|addr| self.clone().handle_incoming(*addr)); let bind_futures_join = try_join_all(bind_futures); - self.inner.borrow_mut().join_handle = Some(spawn_local(bind_futures_join)); + self.inner.lock().join_handle = Some(spawn(bind_futures_join)); } } diff --git a/veilid-server/src/cmdline.rs b/veilid-server/src/cmdline.rs index 3d4fe163..cdc4158a 100644 --- a/veilid-server/src/cmdline.rs +++ b/veilid-server/src/cmdline.rs @@ -132,6 +132,14 @@ fn do_clap_matches(default_config_path: &OsStr) -> Result EyreResult<()> { #[cfg(windows)] @@ -71,6 +63,24 @@ fn main() -> EyreResult<()> { bail!("missing crypto kind"); } } + // -- Emit JSON-Schema -- + if matches.occurrences_of("emit-schema") != 0 { + if let Some(esstr) = matches.value_of("emit-schema") { + let mut schemas = HashMap::::new(); + veilid_core::json_api::emit_schemas(&mut schemas); + + if let Some(schema) = schemas.get(esstr) { + println!("{}", schema); + } else { + println!("Valid schemas:"); + for s in schemas.keys() { + println!(" {}", s); + } + } + + return Ok(()); + } + } // See if we're just running a quick command let (server_mode, success, failure) = if matches.occurrences_of("set-node-id") != 0 { diff --git a/veilid-server/src/server.rs b/veilid-server/src/server.rs index 1ffa64f1..e918fc2f 100644 --- a/veilid-server/src/server.rs +++ b/veilid-server/src/server.rs @@ -2,7 +2,6 @@ use crate::client_api; use crate::settings::*; use crate::tools::*; use crate::veilid_logs::*; -use crate::*; use flume::{unbounded, Receiver, Sender}; use futures_util::select; use futures_util::FutureExt; @@ -11,7 +10,7 @@ use parking_lot::Mutex; use std::sync::Arc; use std::time::{Duration, Instant}; use tracing::*; -use veilid_core::tools::SingleShotEventual; +use veilid_core::tools::*; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ServerMode { @@ -140,7 +139,7 @@ pub async fn run_veilid_server_internal( break; } } - sleep(Duration::from_millis(100)).await; + sleep(100).await; } match veilid_api.debug("txtrecord".to_string()).await { Ok(v) => { diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index ab7e36ef..d6ee6f3b 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -2,6 +2,7 @@ use directories::*; +use crate::tools::*; use serde_derive::*; use std::ffi::OsStr; use std::net::SocketAddr; diff --git a/veilid-server/src/tools.rs b/veilid-server/src/tools.rs index 0e7b51e3..65f296d5 100644 --- a/veilid-server/src/tools.rs +++ b/veilid-server/src/tools.rs @@ -1,43 +1,48 @@ -use cfg_if::*; -use core::future::Future; +pub use cfg_if::*; +pub use color_eyre::eyre::{bail, ensure, eyre, Result as EyreResult, WrapErr}; +pub use core::future::Future; +pub use parking_lot::*; +pub use tracing::*; cfg_if! { if #[cfg(feature="rt-async-std")] { - pub use async_std::task::JoinHandle; +// pub use async_std::task::JoinHandle; pub use async_std::net::TcpListener; - //pub use async_std::net::TcpStream; + pub use async_std::net::TcpStream; + pub use async_std::io::BufReader; //pub use async_std::future::TimeoutError; - pub fn spawn + Send + 'static, T: Send + 'static>(f: F) -> JoinHandle { - async_std::task::spawn_local(f) - } - pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { - async_std::task::spawn_local(f) - } + //pub fn spawn_detached + Send + 'static, T: Send + 'static>(f: F) -> JoinHandle { + //async_std::task::spawn(f) + //} + // pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { + // async_std::task::spawn_local(f) + // } // pub fn spawn_detached_local + 'static, T: 'static>(f: F) { // let _ = async_std::task::spawn_local(f); // } - pub use async_std::task::sleep; - pub use async_std::future::timeout; + //pub use async_std::task::sleep; + //pub use async_std::future::timeout; pub fn block_on, T>(f: F) -> T { async_std::task::block_on(f) } } else if #[cfg(feature="rt-tokio")] { - pub use tokio::task::JoinHandle; + //pub use tokio::task::JoinHandle; pub use tokio::net::TcpListener; - //pub use tokio::net::TcpStream; + pub use tokio::net::TcpStream; + pub use tokio::io::BufReader; //pub use tokio_util::compat::*; //pub use tokio::time::error::Elapsed as TimeoutError; - pub fn spawn + Send + 'static, T: Send + 'static>(f: F) -> JoinHandle { - tokio::task::spawn(f) - } - pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { - tokio::task::spawn_local(f) - } + //pub fn spawn_detached + Send + 'static, T: Send + 'static>(f: F) -> JoinHandle { + //tokio::task::spawn(f) + //} + // pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { + // tokio::task::spawn_local(f) + // } // pub fn spawn_detached_local + 'static, T: 'static>(f: F) { // let _ = tokio::task::spawn_local(f); // } - pub use tokio::time::sleep; - pub use tokio::time::timeout; + //pub use tokio::time::sleep; + //pub use tokio::time::timeout; pub fn block_on, T>(f: F) -> T { let rt = tokio::runtime::Runtime::new().unwrap(); let local = tokio::task::LocalSet::new(); diff --git a/veilid-server/src/unix.rs b/veilid-server/src/unix.rs index 1d2aa389..68f10760 100644 --- a/veilid-server/src/unix.rs +++ b/veilid-server/src/unix.rs @@ -2,13 +2,11 @@ use crate::server::*; use crate::settings::Settings; use crate::tools::*; use crate::veilid_logs::*; -use crate::*; use clap::ArgMatches; use futures_util::StreamExt; use signal_hook::consts::signal::*; use signal_hook_async_std::Signals; -//use std::io::Read; -use tracing::*; +use veilid_core::tools::*; #[instrument(skip(signals))] async fn handle_signals(mut signals: Signals) { @@ -33,24 +31,7 @@ pub fn run_daemon(settings: Settings, _matches: ArgMatches) -> EyreResult<()> { let mut daemon = daemonize::Daemonize::new(); let s = settings.read(); if let Some(pid_file) = s.daemon.pid_file.clone() { - daemon = daemon.pid_file(pid_file.clone()); //.chown_pid_file(true); - // daemon = daemon.exit_action(move || { - // // wait for pid file to exist before exiting parent - // let pid_path = std::path::Path::new(&pid_file); - // loop { - // if let Ok(mut f) = std::fs::File::open(pid_path) { - // let mut s = String::new(); - // if f.read_to_string(&mut s).is_ok() - // && !s.is_empty() - // && s.parse::().is_ok() - // { - // println!("pidfile found"); - // break; - // } - // } - // std::thread::sleep(std::time::Duration::from_millis(100)); - // } - // }) + daemon = daemon.pid_file(pid_file.clone()); } if let Some(chroot) = &s.daemon.chroot { daemon = daemon.chroot(chroot); diff --git a/veilid-tools/src/lib.rs b/veilid-tools/src/lib.rs index ed8e90c8..253e6c8b 100644 --- a/veilid-tools/src/lib.rs +++ b/veilid-tools/src/lib.rs @@ -28,27 +28,6 @@ mod tools; #[cfg(target_arch = "wasm32")] mod wasm; -pub use cfg_if::*; -#[allow(unused_imports)] -pub use eyre::{bail, ensure, eyre, Report as EyreReport, Result as EyreResult, WrapErr}; -pub use futures_util::future::{select, Either}; -pub use futures_util::select; -pub use futures_util::stream::FuturesUnordered; -pub use futures_util::{AsyncRead, AsyncWrite}; -pub use log_thru::*; -pub use owo_colors::OwoColorize; -pub use parking_lot::*; -pub use split_url::*; -pub use static_assertions::*; -pub use stop_token::*; -pub use thiserror::Error as ThisError; -cfg_if! { - if #[cfg(feature = "tracing")] { - pub use tracing::*; - } else { - pub use log::*; - } -} pub type PinBox = Pin>; pub type PinBoxFuture = PinBox + 'static>; pub type PinBoxFutureLifetime<'a, T> = PinBox + 'a>; @@ -120,6 +99,7 @@ pub use eventual_value_clone::*; pub use interval::*; pub use ip_addr_port::*; pub use ip_extra::*; +pub use log_thru::*; pub use must_join_handle::*; pub use must_join_single_future::*; pub use mutable_future::*; @@ -128,20 +108,32 @@ pub use random::*; pub use single_shot_eventual::*; pub use sleep::*; pub use spawn::*; +pub use split_url::*; pub use tick_task::*; pub use timeout::*; pub use timeout_or::*; pub use timestamp::*; pub use tools::*; + #[cfg(target_arch = "wasm32")] pub use wasm::*; // Tests must be public for wasm-pack tests pub mod tests; -// For iOS tests - -#[no_mangle] -pub extern "C" fn main_rs() { - // start game code here +cfg_if! { + if #[cfg(feature = "tracing")] { + use tracing::*; + } else { + use log::*; + } } +use cfg_if::*; +use futures_util::{AsyncRead, AsyncWrite}; +use parking_lot::*; +use stop_token::*; +use thiserror::Error as ThisError; + +// For iOS tests +#[no_mangle] +pub extern "C" fn main_rs() {} diff --git a/veilid-tools/src/must_join_handle.rs b/veilid-tools/src/must_join_handle.rs index ff7231f1..a4afd1dc 100644 --- a/veilid-tools/src/must_join_handle.rs +++ b/veilid-tools/src/must_join_handle.rs @@ -16,6 +16,22 @@ impl MustJoinHandle { } } + pub fn detach(mut self) { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + self.join_handle = None; + } else if #[cfg(feature="rt-tokio")] { + self.join_handle = None; + } else if #[cfg(target_arch = "wasm32")] { + self.join_handle.take().detach(); + self.completed = true; + } else { + compile_error!("needs executor implementation") + } + } + self.completed = true; + } + #[allow(unused_mut)] pub async fn abort(mut self) { if !self.completed { diff --git a/veilid-tools/src/network_result.rs b/veilid-tools/src/network_result.rs index 0ddb3973..437000bc 100644 --- a/veilid-tools/src/network_result.rs +++ b/veilid-tools/src/network_result.rs @@ -337,19 +337,13 @@ macro_rules! network_result_value_or_log { ($r: expr => $f:tt) => { match $r { NetworkResult::Timeout => { - log_network_result!( - "{} at {}@{}:{}", - "Timeout".cyan(), - file!(), - line!(), - column!() - ); + log_network_result!("{} at {}@{}:{}", "Timeout", file!(), line!(), column!()); $f } NetworkResult::ServiceUnavailable => { log_network_result!( "{} at {}@{}:{}", - "ServiceUnavailable".cyan(), + "ServiceUnavailable", file!(), line!(), column!() @@ -359,7 +353,7 @@ macro_rules! network_result_value_or_log { NetworkResult::NoConnection(e) => { log_network_result!( "{}({}) at {}@{}:{}", - "No connection".cyan(), + "No connection", e.to_string(), file!(), line!(), @@ -370,7 +364,7 @@ macro_rules! network_result_value_or_log { NetworkResult::AlreadyExists(e) => { log_network_result!( "{}({}) at {}@{}:{}", - "Already exists".cyan(), + "Already exists", e.to_string(), file!(), line!(), @@ -381,7 +375,7 @@ macro_rules! network_result_value_or_log { NetworkResult::InvalidMessage(s) => { log_network_result!( "{}({}) at {}@{}:{}", - "Invalid message".cyan(), + "Invalid message", s, file!(), line!(), diff --git a/veilid-tools/src/tools.rs b/veilid-tools/src/tools.rs index 4aba1f00..92ce1444 100644 --- a/veilid-tools/src/tools.rs +++ b/veilid-tools/src/tools.rs @@ -97,11 +97,13 @@ cfg_if! { ////////////////////////////////////////////////////////////////////////////////////////////////////////////// -pub fn split_port(name: &str) -> EyreResult<(String, Option)> { +pub fn split_port(name: &str) -> Result<(String, Option), String> { if let Some(split) = name.rfind(':') { let hoststr = &name[0..split]; let portstr = &name[split + 1..]; - let port: u16 = portstr.parse::().wrap_err("invalid port")?; + let port: u16 = portstr + .parse::() + .map_err(|e| format!("invalid port: {}", e))?; Ok((hoststr.to_string(), Some(port))) } else { @@ -130,8 +132,8 @@ pub fn ms_to_us(ms: u32) -> u64 { (ms as u64) * 1000u64 } -pub fn us_to_ms(us: u64) -> EyreResult { - u32::try_from(us / 1000u64).wrap_err("could not convert microseconds") +pub fn us_to_ms(us: u64) -> Result { + u32::try_from(us / 1000u64).map_err(|e| format!("could not convert microseconds: {}", e)) } // Calculate retry attempt with logarhythmic falloff @@ -224,7 +226,7 @@ pub fn compatible_unspecified_socket_addr(socket_addr: &SocketAddr) -> SocketAdd } } -pub fn listen_address_to_socket_addrs(listen_address: &str) -> EyreResult> { +pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result, String> { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise let ip_addrs = vec![ @@ -235,7 +237,7 @@ pub fn listen_address_to_socket_addrs(listen_address: &str) -> EyreResult() - .wrap_err("Invalid port format in udp listen address")?; + .map_err(|e| format!("Invalid port format in udp listen address: {}", e))?; ip_addrs.iter().map(|a| SocketAddr::new(*a, port)).collect() } else if let Ok(port) = listen_address.parse::() { ip_addrs.iter().map(|a| SocketAddr::new(*a, port)).collect() @@ -243,11 +245,11 @@ pub fn listen_address_to_socket_addrs(listen_address: &str) -> EyreResult>(path: P) -> EyreResult<()> + pub fn ensure_file_private_owner>(path: P) -> Result<(), String> { let path = path.as_ref(); if !path.exists() { @@ -286,13 +288,13 @@ cfg_if::cfg_if! { let uid = Uid::effective(); let gid = Gid::effective(); - let meta = std::fs::metadata(path).wrap_err("unable to get metadata for path")?; + let meta = std::fs::metadata(path).map_err(|e| format!("unable to get metadata for path: {}", e))?; if meta.mode() != 0o600 { - std::fs::set_permissions(path,std::fs::Permissions::from_mode(0o600)).wrap_err("unable to set correct permissions on path")?; + std::fs::set_permissions(path,std::fs::Permissions::from_mode(0o600)).map_err(|e| format!("unable to set correct permissions on path: {}", e))?; } if meta.uid() != uid.as_raw() || meta.gid() != gid.as_raw() { - bail!("path has incorrect owner/group"); + return Err("path has incorrect owner/group".to_owned()); } Ok(()) } @@ -300,7 +302,7 @@ cfg_if::cfg_if! { //use std::os::windows::fs::MetadataExt; //use windows_permissions::*; - pub fn ensure_file_private_owner>(path: P) -> EyreResult<()> + pub fn ensure_file_private_owner>(path: P) -> Result<(), String> { let path = path.as_ref(); if !path.exists() { diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index a43f7ce5..72b2c102 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -641,19 +641,21 @@ pub fn release_private_route(route_id: String) -> Promise { } #[wasm_bindgen()] -pub fn app_call_reply(id: String, message: String) -> Promise { +pub fn app_call_reply(call_id: String, message: String) -> Promise { let message: Vec = data_encoding::BASE64URL_NOPAD .decode(message.as_bytes()) .unwrap(); wrap_api_future_void(async move { - let id = match id.parse() { + let call_id = match call_id.parse() { Ok(v) => v, Err(e) => { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(e, "id", id)) + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument( + e, "call_id", call_id, + )) } }; let veilid_api = get_veilid_api()?; - veilid_api.app_call_reply(id, message).await?; + veilid_api.app_call_reply(call_id, message).await?; APIRESULT_UNDEFINED }) }