onionwire/tests/rotate_hs.rs

123 lines
4.2 KiB
Rust
Raw Permalink Normal View History

//! M4 live: F4 rotate onion locator; identity stays; online peer updates.
use std::time::{Duration, Instant};
use onionwire::Store;
use onionwire::node::Node;
const AFTER: &[u8] = b"after rotate";
#[tokio::test(flavor = "multi_thread")]
#[ignore = "needs live Tor network"]
async fn rotate_changes_onion_not_identity_and_notifies_online_peer() {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()),
)
.with_test_writer()
.try_init();
let root = tempfile::tempdir().expect("tempdir");
let alice_home = root.path().join("alice");
let bob_home = root.path().join("bob");
let carol_home = root.path().join("carol");
eprintln!("starting alice + bob…");
unsafe {
std::env::set_var("ONIONWIRE_STORE_PASSPHRASE", "onionwire-test");
}
let (alice, bob) = tokio::join!(
Node::start(alice_home.clone()),
Node::start(bob_home.clone())
);
let alice = alice.expect("alice");
let bob = bob.expect("bob");
let a_qr = alice.qr_payload().expect("alice qr");
let b_qr = bob.qr_payload().expect("bob qr");
alice.add_friend_from_qr(&b_qr).expect("alice adds bob");
bob.add_friend_from_qr(&a_qr).expect("bob adds alice");
let carol = Store::open_at_with_passphrase(&carol_home, "onionwire-test").expect("carol store");
let a_pk = alice.identity_pk();
carol
.upsert_friend(&a_pk, &alice.onion(), Some("alice"))
.expect("carol knows alice");
carol
.set_friend_prekey(&a_pk, &[0u8; 32])
.expect("carol prekey placeholder");
let fp_before = alice.identity_pk();
let onion_before = alice.onion();
eprintln!("alice onion before={onion_before}");
let outcome = alice.rotate().await.expect("rotate");
let onion_after = alice.onion();
eprintln!(
"alice onion after={onion_after} notified={}/{}",
outcome.notified, outcome.friends
);
assert_eq!(alice.identity_pk(), fp_before, "identity key never changes");
assert_ne!(onion_after, onion_before, "onion locator must change");
assert_eq!(outcome.friends, 1, "only bob is a friend");
assert_eq!(outcome.notified, 1, "online bob got loc");
let deadline = Instant::now() + Duration::from_secs(30);
let bob_row = loop {
let row = bob.friend(&fp_before).expect("bob row");
if row.onion == onion_after {
break row;
}
if Instant::now() >= deadline {
panic!(
"bob locator not updated; have {} want {onion_after}",
row.onion
);
}
tokio::time::sleep(Duration::from_millis(200)).await;
};
assert_eq!(bob_row.pubkey, fp_before);
assert_eq!(bob_row.fingerprint, to_hex(&fp_before));
bob.send(&fp_before, AFTER)
.await
.expect("bob dials new locator");
let deadline = Instant::now() + Duration::from_secs(30);
loop {
let msgs = alice.list_messages(&bob.identity_pk()).expect("alice msgs");
if msgs.iter().any(|m| m.dir == "in" && m.plaintext == AFTER) {
break;
}
if Instant::now() >= deadline {
panic!("alice missing post-rotate message; got {msgs:?}");
}
tokio::time::sleep(Duration::from_millis(200)).await;
}
assert!(
alice.connect_onion(&onion_before).await.is_err(),
"old HS must be hard-cut"
);
let c_row = carol.get_friend(&a_pk).unwrap().unwrap();
assert_eq!(c_row.onion, onion_before, "offline carol stays stale");
assert_eq!(c_row.petname.as_deref(), Some("alice"));
let new_qr = alice.qr_payload().expect("new qr");
let p = onionwire::qr::decode(&new_qr).expect("decode");
carol
.upsert_friend(&p.pubkey, &p.onion, None)
.expect("rescan");
assert_eq!(carol.friend_count().unwrap(), 1);
let c2 = carol.get_friend(&a_pk).unwrap().unwrap();
assert_eq!(c2.onion, onion_after);
assert_eq!(c2.petname.as_deref(), Some("alice"));
assert_eq!(c2.fingerprint, c_row.fingerprint);
}
fn to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}