db.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{path::PathBuf, sync::Arc};
  19. use rand::{rngs::OsRng, Rng};
  20. use smol::lock::Mutex as AsyncMutex;
  21. use turso::{Connection, Value};
  22. use crate::{
  23. app::schema::menu::{channel::Channel, contact::Contact},
  24. error::{Error, Result},
  25. };
  26. pub type AppDbPtr = Arc<AppDb>;
  27. const APP_VERSION_KEY: &str = "app_version";
  28. /// Single turso SQL database owning all app persistent state: channels,
  29. /// contacts, darkirc identity, settings, and app flags. The kvdb-overlay
  30. /// database remains exclusively for the event graph and chat history trees.
  31. pub struct AppDb {
  32. conn: AsyncMutex<Connection>,
  33. }
  34. impl AppDb {
  35. pub async fn new(path: &str) -> Result<AppDbPtr> {
  36. let db = turso::Builder::new_local(path).build().await.map_err(Error::from)?;
  37. let conn = db.connect().map_err(Error::from)?;
  38. let self_ = Arc::new(Self { conn: AsyncMutex::new(conn) });
  39. self_.init_schema().await?;
  40. Ok(self_)
  41. }
  42. async fn init_schema(&self) -> Result<()> {
  43. let conn = self.conn.lock().await;
  44. conn.execute_batch(include_str!("../app.sql")).await?;
  45. // First run: generate the DM identity secret right away. On
  46. // subsequent runs the row exists and the insert is ignored.
  47. let secret: [u8; 32] = OsRng.gen();
  48. conn.execute(
  49. "INSERT OR IGNORE INTO profiles (id, nick, dm_secret) VALUES (1, 'anon', ?1)",
  50. vec![Value::Blob(secret.to_vec())],
  51. )
  52. .await?;
  53. Ok(())
  54. }
  55. pub async fn channels(&self) -> Result<Vec<Channel>> {
  56. let conn = self.conn.lock().await;
  57. let mut stmt = conn.prepare("SELECT name, secret FROM channels ORDER BY name").await?;
  58. let mut rows = stmt.query(()).await?;
  59. let mut out = vec![];
  60. while let Some(row) = rows.next().await? {
  61. let name = row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string();
  62. let secret = match row.get_value(1)? {
  63. Value::Blob(b) if b.len() == 32 => Some(b.as_slice().try_into().unwrap()),
  64. _ => None,
  65. };
  66. out.push(Channel { name, secret });
  67. }
  68. Ok(out)
  69. }
  70. pub async fn channel_get(&self, name: &str) -> Result<Option<Channel>> {
  71. let conn = self.conn.lock().await;
  72. let mut stmt = conn.prepare("SELECT name, secret FROM channels WHERE name = ?1").await?;
  73. let mut rows = stmt.query(vec![Value::Text(name.to_string())]).await?;
  74. let Some(row) = rows.next().await? else { return Ok(None) };
  75. let name = row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string();
  76. let secret = match row.get_value(1)? {
  77. Value::Blob(b) if b.len() == 32 => Some(b.as_slice().try_into().unwrap()),
  78. _ => None,
  79. };
  80. Ok(Some(Channel { name, secret }))
  81. }
  82. pub async fn channel_insert(&self, channel: &Channel) -> Result<()> {
  83. let conn = self.conn.lock().await;
  84. let secret = channel.secret.map(|s| Value::Blob(s.to_vec())).unwrap_or(Value::Null);
  85. conn.execute(
  86. "INSERT OR REPLACE INTO channels (name, secret) VALUES (?1, ?2)",
  87. vec![Value::Text(channel.name.clone()), secret],
  88. )
  89. .await?;
  90. Ok(())
  91. }
  92. pub async fn contacts(&self) -> Result<Vec<Contact>> {
  93. let conn = self.conn.lock().await;
  94. let mut stmt = conn.prepare("SELECT name, public FROM contacts ORDER BY name").await?;
  95. let mut rows = stmt.query(()).await?;
  96. let mut out = vec![];
  97. while let Some(row) = rows.next().await? {
  98. let name = row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string();
  99. let public =
  100. row.get_value(1)?.as_blob().ok_or(Error::TursoErr)?.as_slice().try_into().unwrap();
  101. out.push(Contact { name, public });
  102. }
  103. Ok(out)
  104. }
  105. pub async fn contact_get(&self, name: &str) -> Result<Option<Contact>> {
  106. let conn = self.conn.lock().await;
  107. let mut stmt = conn.prepare("SELECT name, public FROM contacts WHERE name = ?1").await?;
  108. let mut rows = stmt.query(vec![Value::Text(name.to_string())]).await?;
  109. let Some(row) = rows.next().await? else { return Ok(None) };
  110. let name = row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string();
  111. let public =
  112. row.get_value(1)?.as_blob().ok_or(Error::TursoErr)?.as_slice().try_into().unwrap();
  113. Ok(Some(Contact { name, public }))
  114. }
  115. pub async fn contact_insert(&self, contact: &Contact) -> Result<()> {
  116. let conn = self.conn.lock().await;
  117. conn.execute(
  118. "INSERT OR REPLACE INTO contacts (name, public) VALUES (?1, ?2)",
  119. vec![Value::Text(contact.name.clone()), Value::Blob(contact.public.to_vec())],
  120. )
  121. .await?;
  122. Ok(())
  123. }
  124. pub async fn nick_get(&self) -> Result<Option<String>> {
  125. let conn = self.conn.lock().await;
  126. let mut stmt = conn.prepare("SELECT nick FROM profiles WHERE id = 1").await?;
  127. let mut rows = stmt.query(()).await?;
  128. match rows.next().await? {
  129. Some(row) => Ok(Some(row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string())),
  130. None => Ok(None),
  131. }
  132. }
  133. pub async fn nick_set(&self, nick: &str) -> Result<()> {
  134. let conn = self.conn.lock().await;
  135. conn.execute(
  136. "UPDATE profiles SET nick = ?1 WHERE id = 1",
  137. vec![Value::Text(nick.to_string())],
  138. )
  139. .await?;
  140. Ok(())
  141. }
  142. /// The identity row is created with a fresh random secret during
  143. /// schema init, so this only reads.
  144. pub async fn dm_secret(&self) -> Result<[u8; 32]> {
  145. let conn = self.conn.lock().await;
  146. let mut stmt = conn.prepare("SELECT dm_secret FROM profiles WHERE id = 1").await?;
  147. let mut rows = stmt.query(()).await?;
  148. let Some(row) = rows.next().await? else { return Err(Error::TursoErr) };
  149. let Value::Blob(b) = row.get_value(0)? else { return Err(Error::TursoErr) };
  150. if b.len() != 32 {
  151. return Err(Error::TursoErr)
  152. }
  153. Ok(b.as_slice().try_into().unwrap())
  154. }
  155. /// Load all settings rows as (name, idx, value bytes).
  156. pub async fn settings_all(&self) -> Result<Vec<(String, u32, Vec<u8>)>> {
  157. let conn = self.conn.lock().await;
  158. let mut stmt =
  159. conn.prepare("SELECT name, idx, value FROM settings ORDER BY name, idx").await?;
  160. let mut rows = stmt.query(()).await?;
  161. let mut out = vec![];
  162. while let Some(row) = rows.next().await? {
  163. let name = row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string();
  164. let idx = *row.get_value(1)?.as_integer().ok_or(Error::TursoErr)?;
  165. let value = row.get_value(2)?.as_blob().ok_or(Error::TursoErr)?.to_vec();
  166. out.push((name, idx as u32, value));
  167. }
  168. Ok(out)
  169. }
  170. pub async fn setting_get(&self, name: &str, idx: u32) -> Result<Option<Vec<u8>>> {
  171. let conn = self.conn.lock().await;
  172. let mut stmt =
  173. conn.prepare("SELECT value FROM settings WHERE name = ?1 AND idx = ?2").await?;
  174. let mut rows =
  175. stmt.query(vec![Value::Text(name.to_string()), Value::Integer(idx as i64)]).await?;
  176. match rows.next().await? {
  177. Some(row) => Ok(Some(row.get_value(0)?.as_blob().ok_or(Error::TursoErr)?.to_vec())),
  178. None => Ok(None),
  179. }
  180. }
  181. pub async fn setting_put(&self, name: &str, idx: u32, typ: &str, value: &[u8]) -> Result<()> {
  182. let conn = self.conn.lock().await;
  183. conn.execute(
  184. "INSERT OR REPLACE INTO settings (name, idx, type, value) VALUES (?1, ?2, ?3, ?4)",
  185. vec![
  186. Value::Text(name.to_string()),
  187. Value::Integer(idx as i64),
  188. Value::Text(typ.to_string()),
  189. Value::Blob(value.to_vec()),
  190. ],
  191. )
  192. .await?;
  193. Ok(())
  194. }
  195. pub async fn setting_remove_idx(&self, name: &str, idx: u32) -> Result<()> {
  196. let conn = self.conn.lock().await;
  197. conn.execute(
  198. "DELETE FROM settings WHERE name = ?1 AND idx = ?2",
  199. vec![Value::Text(name.to_string()), Value::Integer(idx as i64)],
  200. )
  201. .await?;
  202. Ok(())
  203. }
  204. /// Semver version of the app build that last ran, or `None` on a
  205. /// fresh database.
  206. pub async fn app_version_get(&self) -> Result<Option<String>> {
  207. let conn = self.conn.lock().await;
  208. let mut stmt = conn.prepare("SELECT value FROM flags WHERE name = ?1").await?;
  209. let mut rows = stmt.query(vec![Value::Text(APP_VERSION_KEY.to_string())]).await?;
  210. match rows.next().await? {
  211. Some(row) => Ok(Some(row.get_value(0)?.as_text().ok_or(Error::TursoErr)?.to_string())),
  212. None => Ok(None),
  213. }
  214. }
  215. pub async fn app_version_set(&self, version: &str) -> Result<()> {
  216. let conn = self.conn.lock().await;
  217. conn.execute(
  218. "INSERT OR REPLACE INTO flags (name, value) VALUES (?1, ?2)",
  219. vec![Value::Text(APP_VERSION_KEY.to_string()), Value::Text(version.to_string())],
  220. )
  221. .await?;
  222. Ok(())
  223. }
  224. }
  225. #[cfg(target_os = "android")]
  226. pub fn get_app_db_path() -> PathBuf {
  227. crate::android::get_appdata_path().join("app.db")
  228. }
  229. #[cfg(not(target_os = "android"))]
  230. pub fn get_app_db_path() -> PathBuf {
  231. dirs::data_local_dir().unwrap().join("darkfi/app/app.db")
  232. }
  233. #[cfg(test)]
  234. mod tests {
  235. use super::*;
  236. use crypto_box::SecretKey;
  237. fn temp_db_path(tag: &str) -> String {
  238. let path = std::env::temp_dir()
  239. .join(format!("darkfi-app-db-test-{tag}-{}.db", std::process::id()));
  240. let _ = std::fs::remove_file(&path);
  241. path.to_str().unwrap().to_string()
  242. }
  243. /// Covers the app-storage spec: fresh start creates schema + seeds,
  244. /// channels/contacts/settings/version roundtrip, identity (nick + DM
  245. /// secret) survives a reopen.
  246. #[test]
  247. fn app_db_persistence() {
  248. let path = temp_db_path("persist");
  249. let seed_secret: [u8; 32] = core::array::from_fn(|i| i as u8);
  250. let dm_public = {
  251. let db = smol::block_on(AppDb::new(&path)).unwrap();
  252. assert!(smol::block_on(db.channels()).unwrap().is_empty());
  253. smol::block_on(db.channel_insert(&Channel { name: "dev".into(), secret: None }))
  254. .unwrap();
  255. smol::block_on(db.channel_insert(&Channel {
  256. name: "secret_chan".into(),
  257. secret: Some(seed_secret),
  258. }))
  259. .unwrap();
  260. smol::block_on(
  261. db.contact_insert(&Contact { name: "alice".into(), public: seed_secret }),
  262. )
  263. .unwrap();
  264. smol::block_on(db.nick_set("testnick")).unwrap();
  265. smol::block_on(db.setting_put("net.localnet", 0, "bool", &[1])).unwrap();
  266. smol::block_on(db.setting_put("net.localnet", 2, "bool", &[0])).unwrap();
  267. assert_eq!(smol::block_on(db.app_version_get()).unwrap(), None);
  268. smol::block_on(db.app_version_set(env!("CARGO_PKG_VERSION"))).unwrap();
  269. let secret = smol::block_on(db.dm_secret()).unwrap();
  270. SecretKey::from_bytes(secret).public_key().to_bytes()
  271. };
  272. {
  273. let db = smol::block_on(AppDb::new(&path)).unwrap();
  274. let channels = smol::block_on(db.channels()).unwrap();
  275. assert_eq!(channels.len(), 2);
  276. assert_eq!(channels[0].name, "dev");
  277. assert_eq!(channels[0].secret, None);
  278. assert_eq!(channels[1].name, "secret_chan");
  279. assert_eq!(channels[1].secret, Some(seed_secret));
  280. let chan = smol::block_on(db.channel_get("dev")).unwrap().unwrap();
  281. assert_eq!(chan.name, "dev");
  282. let contacts = smol::block_on(db.contacts()).unwrap();
  283. assert_eq!(contacts.len(), 1);
  284. assert_eq!(contacts[0].name, "alice");
  285. assert_eq!(contacts[0].public, seed_secret);
  286. assert_eq!(smol::block_on(db.nick_get()).unwrap(), Some("testnick".into()));
  287. assert_eq!(smol::block_on(db.setting_get("net.localnet", 0)).unwrap(), Some(vec![1u8]));
  288. assert_eq!(smol::block_on(db.setting_get("net.localnet", 2)).unwrap(), Some(vec![0u8]));
  289. assert_eq!(smol::block_on(db.setting_get("net.localnet", 1)).unwrap(), None);
  290. smol::block_on(db.setting_remove_idx("net.localnet", 2)).unwrap();
  291. assert_eq!(smol::block_on(db.setting_get("net.localnet", 2)).unwrap(), None);
  292. assert_eq!(smol::block_on(db.settings_all()).unwrap().len(), 1);
  293. assert_eq!(
  294. smol::block_on(db.app_version_get()).unwrap(),
  295. Some(env!("CARGO_PKG_VERSION").to_string())
  296. );
  297. // DM identity must be stable across reopen
  298. let secret = smol::block_on(db.dm_secret()).unwrap();
  299. assert_eq!(SecretKey::from_bytes(secret).public_key().to_bytes(), dm_public);
  300. }
  301. let _ = std::fs::remove_file(&path);
  302. }
  303. /// Covers: no-nick start keeps the default row, first-run generates and
  304. /// persists a fresh DM identity (nonzero).
  305. #[test]
  306. fn app_db_fresh_identity() {
  307. let path = temp_db_path("fresh");
  308. let db = smol::block_on(AppDb::new(&path)).unwrap();
  309. assert_eq!(smol::block_on(db.nick_get()).unwrap(), Some("anon".into()));
  310. let secret = smol::block_on(db.dm_secret()).unwrap();
  311. assert!(secret.iter().any(|&x| x != 0));
  312. let again = smol::block_on(db.dm_secret()).unwrap();
  313. assert_eq!(secret, again);
  314. let _ = std::fs::remove_file(&path);
  315. }
  316. }