Some checks failed
ci / test (push) Has been cancelled
F2 shows the invite string with fp/onion hints instead of a terminal QR. Keep encode/decode and F3 paste; drop the qrcode crate.
119 lines
3.7 KiB
Rust
119 lines
3.7 KiB
Rust
//! M2 QR: roundtrip, garbage → Err, same-k rescan does not duplicate.
|
|
|
|
use std::sync::{Mutex, MutexGuard};
|
|
|
|
use onionwire::qr;
|
|
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: dir,
|
|
_guard: guard,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for TempHome {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
std::env::remove_var("ONIONWIRE_HOME");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn v3onion(tag: &str) -> String {
|
|
format!("{tag}.onion")
|
|
}
|
|
|
|
#[test]
|
|
fn encode_decode_roundtrip() {
|
|
let _home = TempHome::new();
|
|
let store = Store::open().expect("open");
|
|
let me = store.self_identity().expect("self");
|
|
let spk = [7u8; 32];
|
|
let onion = v3onion("alice-locator");
|
|
let raw = qr::encode(&me.identity_sk, &onion, &spk).expect("encode");
|
|
assert!(raw.starts_with("onionwire:v1:k="), "payload prefix: {raw}");
|
|
assert!(raw.contains(":o="));
|
|
assert!(raw.contains(":spk="));
|
|
assert!(raw.contains(":sig="));
|
|
|
|
let p = qr::decode(&raw).expect("decode");
|
|
assert_eq!(p.pubkey, me.identity_pk);
|
|
assert_eq!(p.onion, onion);
|
|
assert_eq!(p.signed_prekey, spk);
|
|
}
|
|
|
|
#[test]
|
|
fn garbage_is_err() {
|
|
assert!(qr::decode("not a qr").is_err());
|
|
assert!(qr::decode("").is_err());
|
|
assert!(qr::decode("onionwire:v1:k=aa").is_err());
|
|
assert!(qr::decode("🔥").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn missing_sig_is_err() {
|
|
let _home = TempHome::new();
|
|
let store = Store::open().expect("open");
|
|
let me = store.self_identity().expect("self");
|
|
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).expect("encode");
|
|
let stripped = raw.rsplit_once(":sig=").expect("sig field").0;
|
|
assert!(qr::decode(stripped).is_err());
|
|
assert!(qr::decode(&format!("{stripped}:sig=")).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn bad_sig_is_err() {
|
|
let _home = TempHome::new();
|
|
let store = Store::open().expect("open");
|
|
let me = store.self_identity().expect("self");
|
|
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).expect("encode");
|
|
let (head, sig) = raw.rsplit_once(":sig=").expect("sig field");
|
|
let mut chars: Vec<char> = sig.chars().collect();
|
|
let last = chars.last_mut().expect("sig chars");
|
|
*last = if *last == '0' { '1' } else { '0' };
|
|
let flipped: String = chars.into_iter().collect();
|
|
assert!(qr::decode(&format!("{head}:sig={flipped}")).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn same_k_rescan_updates_onion_not_duplicate() {
|
|
let _home = TempHome::new();
|
|
let store = Store::open().expect("open");
|
|
let me = store.self_identity().expect("self");
|
|
let spk = [9u8; 32];
|
|
let p1 = qr::decode(&qr::encode(&me.identity_sk, &v3onion("old"), &spk).unwrap()).unwrap();
|
|
store
|
|
.upsert_friend(&p1.pubkey, &p1.onion, Some("alice"))
|
|
.unwrap();
|
|
let p2 = qr::decode(&qr::encode(&me.identity_sk, &v3onion("new"), &spk).unwrap()).unwrap();
|
|
store.upsert_friend(&p2.pubkey, &p2.onion, None).unwrap();
|
|
assert_eq!(store.friend_count().unwrap(), 1);
|
|
let f = store.get_friend(&p2.pubkey).unwrap().unwrap();
|
|
assert_eq!(f.onion, v3onion("new"));
|
|
assert_eq!(f.petname.as_deref(), Some("alice"));
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_k_decode_does_not_insert() {
|
|
let _home = TempHome::new();
|
|
let store = Store::open().expect("open");
|
|
let me = store.self_identity().expect("self");
|
|
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).unwrap();
|
|
let _p = qr::decode(&raw).unwrap();
|
|
assert_eq!(store.friend_count().unwrap(), 0);
|
|
}
|