[verified] Add sqlite store and first-run identity.
Friends are UNIQUE(pubkey); onion is a locator. Honor ONIONWIRE_HOME.
This commit is contained in:
parent
84b9c940cc
commit
0a0dd3a8d8
6 changed files with 349 additions and 42 deletions
54
Cargo.lock
generated
54
Cargo.lock
generated
|
|
@ -1269,12 +1269,6 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -1525,7 +1519,7 @@ version = "0.15.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash 0.1.5",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1533,9 +1527,6 @@ name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
|
||||||
"foldhash 0.2.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
|
@ -1545,11 +1536,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.11.1"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
|
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.15.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2055,10 +2046,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.37.0"
|
version = "0.34.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
|
checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
@ -2353,7 +2345,10 @@ name = "onionwire"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arti-client",
|
"arti-client",
|
||||||
|
"ed25519-dalek",
|
||||||
"futures",
|
"futures",
|
||||||
|
"rand 0.8.8",
|
||||||
|
"rusqlite",
|
||||||
"safelog",
|
"safelog",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -2762,6 +2757,7 @@ version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
@ -2958,21 +2954,11 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rsqlite-vfs"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"thiserror 2.0.20",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.39.0"
|
version = "0.36.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
|
checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
|
|
@ -2980,7 +2966,6 @@ dependencies = [
|
||||||
"hashlink",
|
"hashlink",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlite-wasm-rs",
|
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3432,18 +3417,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
|
checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlite-wasm-rs"
|
|
||||||
version = "0.5.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"js-sys",
|
|
||||||
"rsqlite-vfs",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ssh-cipher-fork-arti"
|
name = "ssh-cipher-fork-arti"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -3676,7 +3649,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"js-sys",
|
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ publish = false
|
||||||
arti-client = { version = "0.46", features = ["tokio", "onion-service-client", "onion-service-service"] }
|
arti-client = { version = "0.46", features = ["tokio", "onion-service-client", "onion-service-service"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
safelog = "0.9"
|
safelog = "0.9"
|
||||||
|
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||||
|
rand = "0.8"
|
||||||
|
rusqlite = { version = "0.36", features = ["bundled"] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "time"] }
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "time"] }
|
||||||
tor-cell = "0.46"
|
tor-cell = "0.46"
|
||||||
tor-hsservice = "0.46"
|
tor-hsservice = "0.46"
|
||||||
|
|
|
||||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod store;
|
||||||
|
|
||||||
|
pub use store::{Friend, SelfIdentity, Store};
|
||||||
18
src/main.rs
18
src/main.rs
|
|
@ -1,3 +1,19 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
match onionwire::Store::open() {
|
||||||
|
Ok(store) => match store.self_identity() {
|
||||||
|
Ok(me) => {
|
||||||
|
let fp: String = me.identity_pk.iter().map(|b| format!("{b:02x}")).collect();
|
||||||
|
println!("onionwire ready");
|
||||||
|
println!("fingerprint {fp}");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("onionwire: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("onionwire: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
195
src/store.rs
Normal file
195
src/store.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use ed25519_dalek::SigningKey;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl From<rusqlite::Error> for Error {
|
||||||
|
fn from(e: rusqlite::Error) -> Self {
|
||||||
|
Self(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
Self(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Store {
|
||||||
|
conn: Connection,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelfIdentity {
|
||||||
|
pub identity_sk: Vec<u8>,
|
||||||
|
pub identity_pk: Vec<u8>,
|
||||||
|
pub onion: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Friend {
|
||||||
|
pub pubkey: Vec<u8>,
|
||||||
|
pub fingerprint: String,
|
||||||
|
pub petname: Option<String>,
|
||||||
|
pub onion: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn open() -> Result<Self> {
|
||||||
|
Self::open_at(&home_dir()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_at(home: &Path) -> Result<Self> {
|
||||||
|
mkdir_700(home)?;
|
||||||
|
mkdir_700(&home.join("arti"))?;
|
||||||
|
let db_path = home.join("onionwire.db");
|
||||||
|
let conn = Connection::open(&db_path)?;
|
||||||
|
conn.execute_batch(
|
||||||
|
"
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE IF NOT EXISTS friends (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
pubkey BLOB NOT NULL UNIQUE,
|
||||||
|
fingerprint TEXT NOT NULL,
|
||||||
|
petname TEXT,
|
||||||
|
onion TEXT NOT NULL,
|
||||||
|
onion_updated_at INTEGER NOT NULL,
|
||||||
|
added_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
friend_id INTEGER NOT NULL REFERENCES friends(id),
|
||||||
|
dir TEXT NOT NULL CHECK(dir IN ('in','out')),
|
||||||
|
plaintext BLOB NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS self (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
|
identity_sk BLOB NOT NULL,
|
||||||
|
identity_pk BLOB NOT NULL,
|
||||||
|
onion TEXT NOT NULL,
|
||||||
|
onion_rotated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
let store = Self { conn };
|
||||||
|
store.ensure_self()?;
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_self(&self) -> Result<()> {
|
||||||
|
let exists: i64 =
|
||||||
|
self.conn
|
||||||
|
.query_row("SELECT COUNT(*) FROM self WHERE id = 1", [], |row| {
|
||||||
|
row.get(0)
|
||||||
|
})?;
|
||||||
|
if exists == 0 {
|
||||||
|
let signing = SigningKey::generate(&mut OsRng);
|
||||||
|
let sk = signing.to_bytes().to_vec();
|
||||||
|
let pk = signing.verifying_key().to_bytes().to_vec();
|
||||||
|
self.conn.execute(
|
||||||
|
"INSERT INTO self (id, identity_sk, identity_pk, onion, onion_rotated_at)
|
||||||
|
VALUES (1, ?1, ?2, '', 0)",
|
||||||
|
params![sk, pk],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn self_identity(&self) -> Result<SelfIdentity> {
|
||||||
|
self.conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT identity_sk, identity_pk, onion FROM self WHERE id = 1",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
Ok(SelfIdentity {
|
||||||
|
identity_sk: row.get(0)?,
|
||||||
|
identity_pk: row.get(1)?,
|
||||||
|
onion: row.get(2)?,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn friend_count(&self) -> Result<i64> {
|
||||||
|
self.conn
|
||||||
|
.query_row("SELECT COUNT(*) FROM friends", [], |row| row.get(0))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsert_friend(&self, pubkey: &[u8], onion: &str, petname: Option<&str>) -> Result<()> {
|
||||||
|
let now = unix_now();
|
||||||
|
let fp = fingerprint(pubkey);
|
||||||
|
self.conn.execute(
|
||||||
|
"INSERT INTO friends (pubkey, fingerprint, petname, onion, onion_updated_at, added_at)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?5)
|
||||||
|
ON CONFLICT(pubkey) DO UPDATE SET
|
||||||
|
onion = excluded.onion,
|
||||||
|
onion_updated_at = excluded.onion_updated_at,
|
||||||
|
petname = COALESCE(excluded.petname, friends.petname)",
|
||||||
|
params![pubkey, fp, petname, onion, now],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_friend(&self, pubkey: &[u8]) -> Result<Option<Friend>> {
|
||||||
|
self.conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT pubkey, fingerprint, petname, onion FROM friends WHERE pubkey = ?1",
|
||||||
|
params![pubkey],
|
||||||
|
|row| {
|
||||||
|
Ok(Friend {
|
||||||
|
pubkey: row.get(0)?,
|
||||||
|
fingerprint: row.get(1)?,
|
||||||
|
petname: row.get(2)?,
|
||||||
|
onion: row.get(3)?,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn home_dir() -> Result<PathBuf> {
|
||||||
|
if let Some(p) = std::env::var_os("ONIONWIRE_HOME") {
|
||||||
|
return Ok(PathBuf::from(p));
|
||||||
|
}
|
||||||
|
let home = std::env::var_os("HOME").ok_or_else(|| Error("HOME not set".into()))?;
|
||||||
|
Ok(PathBuf::from(home).join(".local/share/onionwire"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkdir_700(path: &Path) -> Result<()> {
|
||||||
|
fs::create_dir_all(path)?;
|
||||||
|
let mut perms = fs::metadata(path)?.permissions();
|
||||||
|
perms.set_mode(0o700);
|
||||||
|
fs::set_permissions(path, perms)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unix_now() -> i64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs() as i64)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fingerprint(pubkey: &[u8]) -> String {
|
||||||
|
pubkey.iter().map(|b| format!("{b:02x}")).collect()
|
||||||
|
}
|
||||||
118
tests/store.rs
Normal file
118
tests/store.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
//! M1 store: identity on `self`, friends keyed by pubkey, onion is locator.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use onionwire::Store;
|
||||||
|
|
||||||
|
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
|
struct TempHome {
|
||||||
|
dir: tempfile::TempDir,
|
||||||
|
_guard: MutexGuard<'static, ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TempHome {
|
||||||
|
fn new() -> Self {
|
||||||
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
|
let guard = ENV_LOCK.lock().expect("env lock");
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("ONIONWIRE_HOME", dir.path());
|
||||||
|
}
|
||||||
|
Self { dir, _guard: guard }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &Path {
|
||||||
|
self.dir.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TempHome {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("ONIONWIRE_HOME");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode(path: &Path) -> u32 {
|
||||||
|
fs::metadata(path).unwrap().permissions().mode() & 0o777
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pk(tag: u8) -> [u8; 32] {
|
||||||
|
let mut k = [0u8; 32];
|
||||||
|
k[0] = tag;
|
||||||
|
k
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_run_creates_0700_dirs_and_self_row() {
|
||||||
|
let home = TempHome::new();
|
||||||
|
let store = Store::open().expect("open");
|
||||||
|
|
||||||
|
let arti = home.path().join("arti");
|
||||||
|
let db = home.path().join("onionwire.db");
|
||||||
|
assert!(arti.is_dir(), "arti dir");
|
||||||
|
assert!(db.is_file(), "onionwire.db");
|
||||||
|
assert_eq!(mode(home.path()), 0o700);
|
||||||
|
assert_eq!(mode(&arti), 0o700);
|
||||||
|
|
||||||
|
let me = store.self_identity().expect("self");
|
||||||
|
assert_eq!(me.identity_pk.len(), 32);
|
||||||
|
assert_eq!(me.identity_sk.len(), 32);
|
||||||
|
assert_ne!(me.identity_pk, me.identity_sk);
|
||||||
|
assert_eq!(me.onion, "");
|
||||||
|
assert_eq!(store.friend_count().unwrap(), 0);
|
||||||
|
|
||||||
|
drop(store);
|
||||||
|
let store2 = Store::open().expect("reopen");
|
||||||
|
let me2 = store2.self_identity().expect("self again");
|
||||||
|
assert_eq!(me.identity_pk, me2.identity_pk);
|
||||||
|
assert_eq!(me.identity_sk, me2.identity_sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_friend_by_pubkey() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
let store = Store::open().expect("open");
|
||||||
|
store
|
||||||
|
.upsert_friend(&pk(1), "aaa.onion", Some("alice"))
|
||||||
|
.expect("insert");
|
||||||
|
let f = store.get_friend(&pk(1)).expect("row").expect("found");
|
||||||
|
assert_eq!(f.onion, "aaa.onion");
|
||||||
|
assert_eq!(f.petname.as_deref(), Some("alice"));
|
||||||
|
assert_eq!(f.pubkey, pk(1));
|
||||||
|
assert!(!f.fingerprint.is_empty());
|
||||||
|
assert_eq!(store.friend_count().unwrap(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_pubkey_different_onion_updates_locator() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
let store = Store::open().expect("open");
|
||||||
|
store
|
||||||
|
.upsert_friend(&pk(1), "old.onion", Some("alice"))
|
||||||
|
.expect("insert");
|
||||||
|
store
|
||||||
|
.upsert_friend(&pk(1), "new.onion", None)
|
||||||
|
.expect("update");
|
||||||
|
assert_eq!(store.friend_count().unwrap(), 1);
|
||||||
|
let f = store.get_friend(&pk(1)).expect("row").expect("found");
|
||||||
|
assert_eq!(f.onion, "new.onion");
|
||||||
|
assert_eq!(f.petname.as_deref(), Some("alice"), "petname is local");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_pubkey_inserts_new_row() {
|
||||||
|
let _home = TempHome::new();
|
||||||
|
let store = Store::open().expect("open");
|
||||||
|
store.upsert_friend(&pk(1), "a.onion", None).expect("first");
|
||||||
|
store
|
||||||
|
.upsert_friend(&pk(2), "b.onion", None)
|
||||||
|
.expect("second");
|
||||||
|
assert_eq!(store.friend_count().unwrap(), 2);
|
||||||
|
assert_eq!(store.get_friend(&pk(1)).unwrap().unwrap().onion, "a.onion");
|
||||||
|
assert_eq!(store.get_friend(&pk(2)).unwrap().unwrap().onion, "b.onion");
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue