From 7626f8eb34cbf104a2a4b2d1f2e362db21e8de1f Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Sat, 21 Dec 2024 02:52:45 -0500 Subject: [PATCH 1/3] sqlite: overhaul, fix caps vuln --- Cargo.lock | 84 +++++++---------- kinode/src/sqlite.rs | 216 +++++++++++++++++++++---------------------- lib/src/sqlite.rs | 91 +++++++++--------- 3 files changed, 177 insertions(+), 214 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3034948c..3694f86b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ name = "alias" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -1275,7 +1275,7 @@ dependencies = [ "alloy-sol-types 0.8.15", "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "rand 0.8.5", "serde", @@ -1858,7 +1858,7 @@ name = "cat" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -1922,7 +1922,7 @@ dependencies = [ "alloy-sol-types 0.8.15", "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "rand 0.8.5", "serde", @@ -1941,7 +1941,7 @@ version = "0.2.1" dependencies = [ "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "pleco", "serde", "serde_json", @@ -2139,7 +2139,7 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" name = "contacts" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -2735,7 +2735,7 @@ name = "download" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -2747,7 +2747,7 @@ name = "downloads" version = "0.5.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "rand 0.8.5", "serde", @@ -2784,7 +2784,7 @@ dependencies = [ name = "echo" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "wit-bindgen 0.36.0", ] @@ -3052,7 +3052,7 @@ version = "0.2.0" dependencies = [ "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "rand 0.8.5", "serde", @@ -3206,7 +3206,7 @@ dependencies = [ name = "get_block" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -3402,7 +3402,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" name = "help" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "wit-bindgen 0.36.0", ] @@ -3431,7 +3431,7 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" name = "hi" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -3464,7 +3464,7 @@ version = "0.1.2" dependencies = [ "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -3907,7 +3907,7 @@ name = "install" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -4085,7 +4085,7 @@ name = "kfetch" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rmp-serde", "serde", "serde_json", @@ -4097,7 +4097,7 @@ name = "kill" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -4191,29 +4191,7 @@ dependencies = [ [[package]] name = "kinode_process_lib" version = "0.10.0" -source = "git+https://github.com/kinode-dao/process_lib?rev=d97e012#d97e012842dd4cc0e036d5de5048064e770302ab" -dependencies = [ - "alloy 0.8.1", - "alloy-primitives 0.8.15", - "alloy-sol-macro 0.8.15", - "alloy-sol-types 0.8.15", - "anyhow", - "bincode", - "http 1.2.0", - "mime_guess", - "rand 0.8.5", - "rmp-serde", - "serde", - "serde_json", - "thiserror 1.0.69", - "url", - "wit-bindgen 0.36.0", -] - -[[package]] -name = "kinode_process_lib" -version = "0.10.0" -source = "git+https://github.com/kinode-dao/process_lib?rev=ea8490a#ea8490aba4837d04243d84b4f5b76fbefb498007" +source = "git+https://github.com/kinode-dao/process_lib?rev=ef78f0e#ef78f0eb18d00826874258131a1bf471e55796f0" dependencies = [ "alloy 0.8.1", "alloy-primitives 0.8.15", @@ -4278,7 +4256,7 @@ dependencies = [ "alloy-sol-types 0.8.15", "anyhow", "hex", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "rmp-serde", "serde", @@ -4515,7 +4493,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "regex", "serde", "serde_json", @@ -4684,7 +4662,7 @@ dependencies = [ name = "net-diagnostics" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rmp-serde", "serde", "wit-bindgen 0.36.0", @@ -4727,7 +4705,7 @@ dependencies = [ name = "node_info" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -5054,7 +5032,7 @@ dependencies = [ name = "peer" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rmp-serde", "serde", "wit-bindgen 0.36.0", @@ -5064,7 +5042,7 @@ dependencies = [ name = "peers" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rmp-serde", "serde", "wit-bindgen 0.36.0", @@ -5721,7 +5699,7 @@ dependencies = [ name = "reset" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -6191,7 +6169,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rmp-serde", "serde", "serde_json", @@ -6412,7 +6390,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "state" version = "0.1.0" dependencies = [ - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=d97e012)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -6625,7 +6603,7 @@ version = "0.1.1" dependencies = [ "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "rand 0.8.5", "regex", "serde", @@ -6639,7 +6617,7 @@ version = "0.1.1" dependencies = [ "anyhow", "bincode", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", @@ -6930,7 +6908,7 @@ version = "0.2.0" dependencies = [ "anyhow", "clap", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "serde", "serde_json", "wit-bindgen 0.36.0", @@ -7297,7 +7275,7 @@ name = "uninstall" version = "0.1.0" dependencies = [ "anyhow", - "kinode_process_lib 0.10.0 (git+https://github.com/kinode-dao/process_lib?rev=ea8490a)", + "kinode_process_lib 0.10.0", "process_macros", "serde", "serde_json", diff --git a/kinode/src/sqlite.rs b/kinode/src/sqlite.rs index 50dfaf85..7a744336 100644 --- a/kinode/src/sqlite.rs +++ b/kinode/src/sqlite.rs @@ -4,8 +4,9 @@ use dashmap::DashMap; use lib::types::core::{ Address, CapMessage, CapMessageSender, Capability, FdManagerRequest, KernelMessage, LazyLoadBlob, Message, MessageReceiver, MessageSender, PackageId, PrintSender, Printout, - ProcessId, Request, Response, SqlValue, SqliteAction, SqliteError, SqliteRequest, - SqliteResponse, FD_MANAGER_PROCESS_ID, SQLITE_PROCESS_ID, + ProcessId, Request, Response, SqlValue, SqliteAction, SqliteCapabilityKind, + SqliteCapabilityParams, SqliteError, SqliteRequest, SqliteResponse, FD_MANAGER_PROCESS_ID, + SQLITE_PROCESS_ID, }; use rusqlite::Connection; use std::{ @@ -54,51 +55,46 @@ impl SqliteState { } } - pub async fn open_db(&mut self, package_id: PackageId, db: String) -> Result<(), SqliteError> { - let key = (package_id.clone(), db.clone()); - if self.open_dbs.contains_key(&key) { + pub async fn open_db(&mut self, key: &(PackageId, String)) -> Result<(), SqliteError> { + if self.open_dbs.contains_key(key) { let mut access_order = self.access_order.lock().await; - access_order.remove(&key); - access_order.push_back(key); + access_order.remove(key); + access_order.push_back(key.clone()); return Ok(()); } if self.open_dbs.len() as u64 >= self.fds_limit { // close least recently used db - let key = self.access_order.lock().await.pop_front().unwrap(); - self.remove_db(key.0, key.1).await; + let to_close = self.access_order.lock().await.pop_front().unwrap(); + self.remove_db(&to_close).await; } #[cfg(unix)] - let db_path = self.sqlite_path.join(format!("{package_id}")).join(&db); + let db_path = self.sqlite_path.join(format!("{}", key.0)).join(&key.1); #[cfg(target_os = "windows")] let db_path = self .sqlite_path - .join(format!( - "{}_{}", - package_id._package(), - package_id._publisher() - )) - .join(&db); + .join(format!("{}_{}", key.0._package(), key.0._publisher())) + .join(&key.1); fs::create_dir_all(&db_path).await?; - let db_file_path = db_path.join(format!("{}.db", db)); + let db_file_path = db_path.join(format!("{}.db", key.1)); let db_conn = Connection::open(db_file_path)?; let _: String = db_conn.query_row("PRAGMA journal_mode=WAL", [], |row| row.get(0))?; - self.open_dbs.insert(key, Mutex::new(db_conn)); + self.open_dbs.insert(key.clone(), Mutex::new(db_conn)); let mut access_order = self.access_order.lock().await; - access_order.push_back((package_id, db)); + access_order.push_back(key.clone()); Ok(()) } - pub async fn remove_db(&mut self, package_id: PackageId, db: String) { - self.open_dbs.remove(&(package_id.clone(), db.to_string())); + pub async fn remove_db(&mut self, key: &(PackageId, String)) { + self.open_dbs.remove(key); let mut access_order = self.access_order.lock().await; - access_order.remove(&(package_id, db)); + access_order.remove(key); } pub async fn remove_least_recently_used_dbs(&mut self, n: u64) { @@ -106,7 +102,7 @@ impl SqliteState { let mut lock = self.access_order.lock().await; let key = lock.pop_front().unwrap(); drop(lock); - self.remove_db(key.0, key.1).await; + self.remove_db(&key).await; } } } @@ -176,8 +172,7 @@ pub async fn sqlite( tokio::spawn(async move { let mut queue_lock = queue.lock().await; if let Some(km) = queue_lock.pop_front() { - let (km_id, km_rsvp) = - (km.id.clone(), km.rsvp.clone().unwrap_or(km.source.clone())); + let (km_id, km_rsvp) = (km.id, km.rsvp.clone().unwrap_or(km.source.clone())); if let Err(e) = handle_request(km, &mut state, &send_to_caps_oracle).await { Printout::new(1, SQLITE_PROCESS_ID.clone(), format!("sqlite: {e}")) @@ -226,27 +221,31 @@ async fn handle_request( .. }) = message else { - return Err(SqliteError::InputError { - error: "not a request".into(), - }); + // we got a response -- safe to ignore + return Ok(()); }; let request: SqliteRequest = match serde_json::from_slice(&body) { Ok(r) => r, Err(e) => { - println!("sqlite: got invalid Request: {}", e); - return Err(SqliteError::InputError { - error: "didn't serialize to SqliteRequest.".into(), - }); + println!("sqlite: got invalid request: {e}"); + return Err(SqliteError::MalformedRequest); } }; - check_caps(&source, state, send_to_caps_oracle, &request).await?; + let db_key = (request.package_id, request.db); + + check_caps( + &source, + state, + send_to_caps_oracle, + &request.action, + &db_key, + ) + .await?; // always open to ensure db exists - state - .open_db(request.package_id.clone(), request.db.clone()) - .await?; + state.open_db(&db_key).await?; let (body, bytes) = match request.action { SqliteAction::Open => { @@ -257,11 +256,11 @@ async fn handle_request( // handled in check_caps (serde_json::to_vec(&SqliteResponse::Ok).unwrap(), None) } - SqliteAction::Read { query } => { - let db = match state.open_dbs.get(&(request.package_id, request.db)) { + SqliteAction::Query(query) => { + let db = match state.open_dbs.get(&db_key) { Some(db) => db, None => { - return Err(SqliteError::NoDb); + return Err(SqliteError::NoDb(db_key.0, db_key.1)); } }; let db = db.lock().await; @@ -314,10 +313,10 @@ async fn handle_request( ) } SqliteAction::Write { statement, tx_id } => { - let db = match state.open_dbs.get(&(request.package_id, request.db)) { + let db = match state.open_dbs.get(&db_key) { Some(db) => db, None => { - return Err(SqliteError::NoDb); + return Err(SqliteError::NoDb(db_key.0, db_key.1)); } }; let db = db.lock().await; @@ -359,17 +358,17 @@ async fn handle_request( ) } SqliteAction::Commit { tx_id } => { - let db = match state.open_dbs.get(&(request.package_id, request.db)) { + let db = match state.open_dbs.get(&db_key) { Some(db) => db, None => { - return Err(SqliteError::NoDb); + return Err(SqliteError::NoDb(db_key.0, db_key.1)); } }; let mut db = db.lock().await; let txs = match state.txs.remove(&tx_id).map(|(_, tx)| tx) { None => { - return Err(SqliteError::NoTx); + return Err(SqliteError::NoTx(tx_id)); } Some(tx) => tx, }; @@ -389,9 +388,7 @@ async fn handle_request( .query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |_| Ok(())) .map(|_| ()); if let Err(e) = result { - return Err(SqliteError::RusqliteError { - error: e.to_string(), - }); + return Err(SqliteError::RusqliteError(e.to_string())); } } (serde_json::to_vec(&SqliteResponse::Ok).unwrap(), None) @@ -429,119 +426,105 @@ async fn check_caps( source: &Address, state: &mut SqliteState, send_to_caps_oracle: &CapMessageSender, - request: &SqliteRequest, + action: &SqliteAction, + db_key: &(PackageId, String), ) -> Result<(), SqliteError> { let (send_cap_bool, recv_cap_bool) = tokio::sync::oneshot::channel(); let src_package_id = PackageId::new(source.process.package(), source.process.publisher()); - match &request.action { + match action { SqliteAction::Write { .. } | SqliteAction::BeginTx | SqliteAction::Commit { .. } => { - send_to_caps_oracle + let Ok(()) = send_to_caps_oracle .send(CapMessage::Has { on: source.process.clone(), cap: Capability::new( state.our.as_ref().clone(), - serde_json::json!({ - "kind": "write", - "db": request.db.to_string(), + serde_json::to_string(&SqliteCapabilityParams { + kind: SqliteCapabilityKind::Write, + db_key: db_key.clone(), }) - .to_string(), + .unwrap(), ), responder: send_cap_bool, }) - .await?; - let has_cap = recv_cap_bool.await?; - if !has_cap { - return Err(SqliteError::NoCap { - error: request.action.to_string(), - }); - } + .await + else { + return Err(SqliteError::AddCapFailed); + }; + let Ok(_) = recv_cap_bool.await else { + return Err(SqliteError::AddCapFailed); + }; Ok(()) } - SqliteAction::Read { .. } => { - send_to_caps_oracle + SqliteAction::Query { .. } => { + let Ok(()) = send_to_caps_oracle .send(CapMessage::Has { on: source.process.clone(), cap: Capability::new( state.our.as_ref().clone(), - serde_json::json!({ - "kind": "read", - "db": request.db.to_string(), + serde_json::to_string(&SqliteCapabilityParams { + kind: SqliteCapabilityKind::Read, + db_key: db_key.clone(), }) - .to_string(), + .unwrap(), ), responder: send_cap_bool, }) - .await?; - let has_cap = recv_cap_bool.await?; - if !has_cap { - return Err(SqliteError::NoCap { - error: request.action.to_string(), - }); - } + .await + else { + return Err(SqliteError::AddCapFailed); + }; + let Ok(_) = recv_cap_bool.await else { + return Err(SqliteError::AddCapFailed); + }; Ok(()) } SqliteAction::Open => { - if src_package_id != request.package_id { - return Err(SqliteError::NoCap { - error: request.action.to_string(), - }); + if src_package_id != db_key.0 { + return Err(SqliteError::MismatchingPackageId); } add_capability( - "read", - &request.db.to_string(), + SqliteCapabilityKind::Read, + db_key, &state.our, &source, send_to_caps_oracle, ) .await?; add_capability( - "write", - &request.db.to_string(), + SqliteCapabilityKind::Write, + db_key, &state.our, &source, send_to_caps_oracle, ) .await?; - if state - .open_dbs - .contains_key(&(request.package_id.clone(), request.db.clone())) - { + if state.open_dbs.contains_key(db_key) { return Ok(()); } - state - .open_db(request.package_id.clone(), request.db.clone()) - .await?; + state.open_db(db_key).await?; Ok(()) } SqliteAction::RemoveDb => { - if src_package_id != request.package_id { - return Err(SqliteError::NoCap { - error: request.action.to_string(), - }); + if src_package_id != db_key.0 { + return Err(SqliteError::MismatchingPackageId); } - state - .remove_db(request.package_id.clone(), request.db.clone()) - .await; + state.remove_db(db_key).await; #[cfg(unix)] let db_path = state .sqlite_path - .join(format!("{}", request.package_id)) - .join(&request.db); + .join(format!("{}", db_key.0)) + .join(&db_key.1); #[cfg(target_os = "windows")] let db_path = state .sqlite_path - .join(format!( - "{}_{}", - request.package_id._package(), - request.package_id._publisher() - )) - .join(&request.db); + .join(format!("{}_{}", db_key.0._package(), db_key.0._publisher())) + .join(&db_key.1); fs::remove_dir_all(&db_path).await?; @@ -559,9 +542,7 @@ async fn handle_fd_request(km: KernelMessage, state: &mut SqliteState) -> anyhow return Err(anyhow::anyhow!("not a request")); }; - let request: FdManagerRequest = serde_json::from_slice(&body)?; - - match request { + match serde_json::from_slice(&body)? { FdManagerRequest::FdsLimit(new_fds_limit) => { state.fds_limit = new_fds_limit; if state.open_dbs.len() as u64 >= state.fds_limit { @@ -581,25 +562,34 @@ async fn handle_fd_request(km: KernelMessage, state: &mut SqliteState) -> anyhow } async fn add_capability( - kind: &str, - db: &str, + kind: SqliteCapabilityKind, + db_key: &(PackageId, String), our: &Address, source: &Address, send_to_caps_oracle: &CapMessageSender, ) -> Result<(), SqliteError> { let cap = Capability { issuer: our.clone(), - params: serde_json::json!({ "kind": kind, "db": db }).to_string(), + params: serde_json::to_string(&SqliteCapabilityParams { + kind, + db_key: db_key.clone(), + }) + .unwrap(), }; let (send_cap_bool, recv_cap_bool) = tokio::sync::oneshot::channel(); - send_to_caps_oracle + let Ok(()) = send_to_caps_oracle .send(CapMessage::Add { on: source.process.clone(), caps: vec![cap], responder: Some(send_cap_bool), }) - .await?; - let _ = recv_cap_bool.await?; + .await + else { + return Err(SqliteError::AddCapFailed); + }; + let Ok(_) = recv_cap_bool.await else { + return Err(SqliteError::AddCapFailed); + }; Ok(()) } diff --git a/lib/src/sqlite.rs b/lib/src/sqlite.rs index 1e53ec5c..f0b997e3 100644 --- a/lib/src/sqlite.rs +++ b/lib/src/sqlite.rs @@ -1,17 +1,17 @@ -use crate::types::core::{CapMessage, PackageId}; +use crate::types::core::PackageId; use rusqlite::types::{FromSql, FromSqlError, ToSql, ValueRef}; use serde::{Deserialize, Serialize}; use thiserror::Error; /// IPC Request format for the sqlite:distro:sys runtime module. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SqliteRequest { pub package_id: PackageId, pub db: String, pub action: SqliteAction, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteAction { Open, RemoveDb, @@ -19,9 +19,7 @@ pub enum SqliteAction { statement: String, tx_id: Option, }, - Read { - query: String, - }, + Query(String), BeginTx, Commit { tx_id: u64, @@ -29,7 +27,7 @@ pub enum SqliteAction { Backup, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteResponse { Ok, Read, @@ -37,7 +35,7 @@ pub enum SqliteResponse { Err(SqliteError), } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum SqlValue { Integer(i64), Real(f64), @@ -47,28 +45,45 @@ pub enum SqlValue { Null, } -#[derive(Debug, Serialize, Deserialize, Error)] +#[derive(Clone, Debug, Serialize, Deserialize, Error)] pub enum SqliteError { - #[error("sqlite: DbDoesNotExist")] - NoDb, - #[error("sqlite: NoTx")] - NoTx, - #[error("sqlite: No capability: {error}")] - NoCap { error: String }, - #[error("sqlite: UnexpectedResponse")] - UnexpectedResponse, - #[error("sqlite: NotAWriteKeyword")] + #[error("db [{0}, {1}] does not exist")] + NoDb(PackageId, String), + #[error("no transaction {0} found")] + NoTx(u64), + #[error("no write capability for requested DB")] + NoWriteCap, + #[error("no read capability for requested DB")] + NoReadCap, + #[error("request to open or remove DB with mismatching package ID")] + MismatchingPackageId, + #[error("failed to generate capability for new DB")] + AddCapFailed, + #[error("write statement started with non-existent write keyword")] NotAWriteKeyword, - #[error("sqlite: NotAReadKeyword")] + #[error("read query started with non-existent read keyword")] NotAReadKeyword, - #[error("sqlite: Invalid Parameters")] + #[error("parameters blob in read/write was misshapen or contained invalid JSON objects")] InvalidParameters, - #[error("sqlite: IO error: {error}")] - IOError { error: String }, - #[error("sqlite: rusqlite error: {error}")] - RusqliteError { error: String }, - #[error("sqlite: input bytes/json/key error: {error}")] - InputError { error: String }, + #[error("sqlite got a malformed request that failed to deserialize")] + MalformedRequest, + #[error("rusqlite error: {0}")] + RusqliteError(String), + #[error("IO error: {0}")] + IOError(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SqliteCapabilityParams { + pub kind: SqliteCapabilityKind, + pub db_key: (PackageId, String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SqliteCapabilityKind { + Read, + Write, } impl ToSql for SqlValue { @@ -109,32 +124,12 @@ impl std::fmt::Display for SqliteAction { impl From for SqliteError { fn from(err: std::io::Error) -> Self { - SqliteError::IOError { - error: err.to_string(), - } + SqliteError::IOError(err.to_string()) } } impl From for SqliteError { fn from(err: rusqlite::Error) -> Self { - SqliteError::RusqliteError { - error: err.to_string(), - } - } -} - -impl From for SqliteError { - fn from(err: tokio::sync::oneshot::error::RecvError) -> Self { - SqliteError::NoCap { - error: err.to_string(), - } - } -} - -impl From> for SqliteError { - fn from(err: tokio::sync::mpsc::error::SendError) -> Self { - SqliteError::NoCap { - error: err.to_string(), - } + SqliteError::RusqliteError(err.to_string()) } } From e4d9bfef28a751f01e5c2b0c3ddd2e064e6bf8a3 Mon Sep 17 00:00:00 2001 From: bitful-pannul Date: Sun, 22 Dec 2024 02:14:32 +0200 Subject: [PATCH 2/3] sqlite: docstrings for blobs, remove backup --- kinode/src/sqlite.rs | 16 ---------------- lib/src/sqlite.rs | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/kinode/src/sqlite.rs b/kinode/src/sqlite.rs index 7a744336..5cda7a0d 100644 --- a/kinode/src/sqlite.rs +++ b/kinode/src/sqlite.rs @@ -381,18 +381,6 @@ async fn handle_request( tx.commit()?; (serde_json::to_vec(&SqliteResponse::Ok).unwrap(), None) } - SqliteAction::Backup => { - for db_ref in state.open_dbs.iter() { - let db = db_ref.value().lock().await; - let result: rusqlite::Result<()> = db - .query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |_| Ok(())) - .map(|_| ()); - if let Err(e) = result { - return Err(SqliteError::RusqliteError(e.to_string())); - } - } - (serde_json::to_vec(&SqliteResponse::Ok).unwrap(), None) - } }; if let Some(target) = km.rsvp.or_else(|| expects_response.map(|_| source)) { @@ -530,10 +518,6 @@ async fn check_caps( Ok(()) } - SqliteAction::Backup => { - // flushing WALs for backup - Ok(()) - } } } diff --git a/lib/src/sqlite.rs b/lib/src/sqlite.rs index f0b997e3..1861b99d 100644 --- a/lib/src/sqlite.rs +++ b/lib/src/sqlite.rs @@ -13,25 +13,59 @@ pub struct SqliteRequest { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteAction { + /// Opens an existing sqlite database or creates a new one if it doesn't exist. Open, + /// Permanently deletes the entire sqlite database. RemoveDb, + /// Executes a write statement (INSERT/UPDATE/DELETE) + /// + /// * `statement` - SQL statement to execute + /// * `tx_id` - Optional transaction ID + /// * blob: Vec - Parameters for the SQL statement, where SqlValue can be: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) Write { statement: String, tx_id: Option, }, + /// Executes a read query (SELECT) + /// + /// * blob: Vec - Parameters for the SQL query, where SqlValue can be: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) Query(String), + /// Starts a new transaction BeginTx, - Commit { - tx_id: u64, - }, - Backup, + /// Commits transaction with given ID + Commit { tx_id: u64 }, } +/// Responses from SQLite operations #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteResponse { + /// Operation succeeded Ok, + /// Query returned results + /// + /// * blob: Vec> - Array of rows, where each row contains SqlValue types: + /// - null + /// - boolean + /// - i64 + /// - f64 + /// - String + /// - Vec (binary data) Read, + /// Transaction started with ID BeginTx { tx_id: u64 }, + /// Operation failed Err(SqliteError), } From 5fd9661cddc921c6736c56d8261b1a4f2735dc4f Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Sun, 22 Dec 2024 16:44:58 -0500 Subject: [PATCH 3/3] comments --- lib/src/sqlite.rs | 73 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/lib/src/sqlite.rs b/lib/src/sqlite.rs index 1861b99d..90fc009a 100644 --- a/lib/src/sqlite.rs +++ b/lib/src/sqlite.rs @@ -3,7 +3,10 @@ use rusqlite::types::{FromSql, FromSqlError, ToSql, ValueRef}; use serde::{Deserialize, Serialize}; use thiserror::Error; -/// IPC Request format for the sqlite:distro:sys runtime module. +/// Actions are sent to a specific SQLite database. `db` is the name, +/// `package_id` is the [`PackageId`] that created the database. Capabilities +/// are checked: you can access another process's database if it has given +/// you the read and/or write capability to do so. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SqliteRequest { pub package_id: PackageId, @@ -11,11 +14,24 @@ pub struct SqliteRequest { pub action: SqliteAction, } +/// IPC Action format representing operations that can be performed on the +/// SQLite runtime module. These actions are included in a [`SqliteRequest`] +/// sent to the `sqlite:distro:sys` runtime module. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteAction { - /// Opens an existing sqlite database or creates a new one if it doesn't exist. + /// Opens an existing key-value database or creates a new one if it doesn't exist. + /// Requires `package_id` in [`SqliteRequest`] to match the package ID of the sender. + /// The sender will own the database and can remove it with [`SqliteAction::RemoveDb`]. + /// + /// A successful open will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. Open, - /// Permanently deletes the entire sqlite database. + /// Permanently deletes the entire key-value database. + /// Requires `package_id` in [`SqliteRequest`] to match the package ID of the sender. + /// Only the owner can remove the database. + /// + /// A successful remove will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. RemoveDb, /// Executes a write statement (INSERT/UPDATE/DELETE) /// @@ -28,6 +44,12 @@ pub enum SqliteAction { /// - f64 /// - String /// - Vec (binary data) + /// + /// Using this action requires the sender to have the write capability + /// for the database. + /// + /// A successful write will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. Write { statement: String, tx_id: Option, @@ -41,19 +63,35 @@ pub enum SqliteAction { /// - f64 /// - String /// - Vec (binary data) + /// + /// Using this action requires the sender to have the read capability + /// for the database. + /// + /// A successful query will respond with [`SqliteResponse::Query`], where the + /// response blob contains the results of the query. Any error will be contained + /// in the [`SqliteResponse::Err`] variant. Query(String), - /// Starts a new transaction + /// Begins a new transaction for atomic operations. + /// + /// Sending this will prompt a [`SqliteResponse::BeginTx`] response with the + /// transaction ID. Any error will be contained in the [`SqliteResponse::Err`] variant. BeginTx, - /// Commits transaction with given ID + /// Commits all operations in the specified transaction. + /// + /// # Parameters + /// * `tx_id` - The ID of the transaction to commit + /// + /// A successful commit will respond with [`SqliteResponse::Ok`]. Any error will be + /// contained in the [`SqliteResponse::Err`] variant. Commit { tx_id: u64 }, } -/// Responses from SQLite operations #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SqliteResponse { - /// Operation succeeded + /// Indicates successful completion of an operation. + /// Sent in response to actions Open, RemoveDb, Write, Query, BeginTx, and Commit. Ok, - /// Query returned results + /// Returns the results of a query. /// /// * blob: Vec> - Array of rows, where each row contains SqlValue types: /// - null @@ -63,12 +101,16 @@ pub enum SqliteResponse { /// - String /// - Vec (binary data) Read, - /// Transaction started with ID + /// Returns the transaction ID for a newly created transaction. + /// + /// # Fields + /// * `tx_id` - The ID of the newly created transaction BeginTx { tx_id: u64 }, - /// Operation failed + /// Indicates an error occurred during the operation. Err(SqliteError), } +/// Used in blobs to represent array row values in SQLite. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum SqlValue { Integer(i64), @@ -107,6 +149,11 @@ pub enum SqliteError { IOError(String), } +/// The JSON parameters contained in all capabilities issued by `sqlite:distro:sys`. +/// +/// # Fields +/// * `kind` - The kind of capability, either [`SqliteCapabilityKind::Read`] or [`SqliteCapabilityKind::Write`] +/// * `db_key` - The database key, a tuple of the [`PackageId`] that created the database and the database name #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SqliteCapabilityParams { pub kind: SqliteCapabilityKind, @@ -150,12 +197,6 @@ impl FromSql for SqlValue { } } -impl std::fmt::Display for SqliteAction { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - impl From for SqliteError { fn from(err: std::io::Error) -> Self { SqliteError::IOError(err.to_string())