#![cfg(not(target_arch = "wasm32"))]
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, OnceLock,
};
use std::time::{SystemTime, UNIX_EPOCH};
use std::collections::BTreeMap;
use offline_protocol_core::{Message, MessagePriority, UserId};
use offline_protocol_leaf::{
store::{
KEY_TYPE_GROUP_EPOCH, KEY_TYPE_GROUP_STATE, KEY_TYPE_IDENTITY, KEY_TYPE_KEY_PACKAGE,
KEY_TYPE_PEER,
},
Handled, LeafDevice, LeafError, LeafEvent, LeafStore, MemoryStore, StoreError,
};
use offline_protocol_mls::{storage::InMemoryStorage, MlsManager, MlsStorage};
use offline_protocol_sealed::{
control_signing_payload_v2, derive_address, prefixes, EncryptedMessage, GroupId,
KeyPackagePayload, WelcomeMessage, ACK_FOR_KEY, CTRL_FRESHNESS_FUTURE_MS,
LEAF_CTRL_FRESHNESS_PAST_MS, MLS_ENVELOPE_COMPACT_V1,
};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
const APP_ID: &str = "com.example.lock";
fn now() -> u64 {
static NOW: OnceLock<u64> = OnceLock::new();
*NOW.get_or_init(|| {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("the clock is past 1970")
.as_secs()
})
}
struct Phone {
manager: MlsManager,
address: String,
}
fn new_phone() -> Phone {
let storage: Arc<dyn MlsStorage> = Arc::new(InMemoryStorage::new());
let bootstrap = MlsManager::new("bootstrap", Arc::clone(&storage)).expect("bootstrap manager");
let public = bootstrap
.get_identity_public_key()
.expect("identity public key");
let address = derive_address(&public).expect("derive").to_string();
drop(bootstrap);
let manager = MlsManager::new(address.clone(), storage).expect("addressed manager");
Phone { manager, address }
}
fn device(store: Arc<dyn LeafStore>) -> LeafDevice {
LeafDevice::open(store, APP_ID).expect("device opens")
}
fn phone_control_frame(phone: &Phone, to: &str, content: String) -> Message {
phone_control_frame_at(phone, to, content, now())
}
fn phone_control_frame_at(phone: &Phone, to: &str, content: String, at_unix_secs: u64) -> Message {
let mut message = Message::new(
UserId::new(&phone.address).expect("phone address is a user id"),
UserId::new(to).expect("device address is a user id"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
content,
);
message.priority = MessagePriority::High;
message.timestamp = offline_protocol_core::Timestamp::from_millis((at_unix_secs as i64) * 1000);
sign_as(phone, &mut message);
message
}
fn sign_as(phone: &Phone, message: &mut Message) {
let payload = control_signing_payload_v2(message).expect("canonical payload");
let signature = phone.manager.sign_data(&payload).expect("phone signs");
let public = phone
.manager
.get_identity_public_key()
.expect("phone public key");
message.metadata.insert(
offline_protocol_sealed::CTRL_SIG_META_KEY.to_string(),
BASE64.encode(&signature),
);
message.metadata.insert(
offline_protocol_sealed::CTRL_PK_META_KEY.to_string(),
BASE64.encode(&public),
);
}
fn import_device_key_package(phone: &Phone, frame: &Message) {
let body = frame
.content
.strip_prefix(prefixes::KEY_PACKAGE)
.expect("frame carries a key package");
let payload: KeyPackagePayload = serde_json::from_str(body).expect("key package body parses");
phone
.manager
.import_key_package(&payload.user_id, &payload.key_package_data)
.expect("the phone accepts the device's key package");
}
fn raw_key_package(frame: &Message) -> Vec<u8> {
let body = frame
.content
.strip_prefix(prefixes::KEY_PACKAGE)
.expect("frame carries a key package");
let payload: KeyPackagePayload = serde_json::from_str(body).expect("key package body parses");
payload.key_package_data
}
fn pair(phone: &Phone, device: &mut LeafDevice) -> String {
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&phone.address, now())
.expect("device mints a key package");
import_device_key_package(phone, &advertisement);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone creates the session");
let welcome_frame = phone_control_frame(
phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome serializes")
),
);
let handled = device.handle(&welcome_frame, now()).expect("device joins");
assert!(
handled.events.contains(&LeafEvent::SessionEstablished {
peer: phone.address.clone(),
}),
"joining a welcome did not establish a session: {:?}",
handled.events
);
let said = spoken(&handled);
assert_eq!(said.len(), 1, "expected one confirmation frame");
let confirm = envelope_of(said[0]);
let opened = phone
.manager
.decrypt_from_user(&confirm, &device_address)
.expect("phone opens the confirmation");
assert_eq!(
opened.as_deref(),
Some(prefixes::SESSION_CONFIRM_ENCRYPTED.as_bytes()),
"the confirmation did not carry the encrypted-confirm marker"
);
device_address
}
fn spoken(handled: &Handled) -> Vec<&Message> {
handled
.outbound
.iter()
.filter(|frame| !frame.metadata.contains_key(ACK_FOR_KEY))
.collect()
}
fn acknowledgements(handled: &Handled) -> Vec<&Message> {
handled
.outbound
.iter()
.filter(|frame| frame.metadata.contains_key(ACK_FOR_KEY))
.collect()
}
fn envelope_of(message: &Message) -> EncryptedMessage {
let body = message
.content
.strip_prefix(prefixes::ENCRYPTED)
.expect("frame carries an envelope");
if body.starts_with('{') {
serde_json::from_str(body).expect("json envelope parses")
} else {
let bytes = BASE64.decode(body).expect("envelope is base64");
EncryptedMessage::from_bytes(&bytes).expect("compact envelope parses")
}
}
fn phone_sealed_frame(phone: &Phone, to: &str, envelope: &EncryptedMessage) -> Message {
let body = BASE64.encode(envelope.to_bytes());
Message::new(
UserId::new(&phone.address).expect("phone address"),
UserId::new(to).expect("device address"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
format!("{}{}", prefixes::ENCRYPTED, body),
)
}
#[test]
fn a_phone_and_a_device_pair_and_talk_both_ways() {
let phone = new_phone();
let store = Arc::new(MemoryStore::new());
let mut device = device(store);
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
let answer = device
.seal(&phone.address, "unlocked", now())
.expect("device seals");
let opened = phone
.manager
.decrypt_from_user(&envelope_of(&answer), &device_address)
.expect("phone opens the answer");
assert_eq!(opened.as_deref(), Some(&b"unlocked"[..]));
}
#[test]
fn a_driven_rekey_reaches_the_device_as_a_session_reset() {
let phone = new_phone();
let store = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
assert!(
device.has_session(&phone.address).expect("session check"),
"pairing left no session"
);
phone
.manager
.delete_session(&device_address)
.expect("phone discards its own session");
let phone_package = phone
.manager
.take_push_key_package(&device_address)
.expect("phone mints a package");
let mut payload: KeyPackagePayload = serde_json::from_str(&format!(
r#"{{"user_id":{},"key_package_data":[]}}"#,
serde_json::to_string(&phone.address).expect("address")
))
.expect("skeleton payload");
payload.key_package_data = phone_package.bundle.key_package_data.clone();
payload.session_reset = true;
payload.env_versions = vec![MLS_ENVELOPE_COMPACT_V1];
let reset_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload serializes")
),
);
let handled = device.handle(&reset_frame, now()).expect("device resets");
assert!(
handled.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}),
"a reset flag did not reset the session: {:?}",
handled.events
);
assert!(
!device.has_session(&phone.address).expect("session check"),
"the device kept a session the phone had already discarded"
);
let said = spoken(&handled);
assert_eq!(said.len(), 1, "a reset did not produce a fresh key package");
let fresh = said[0];
assert!(fresh.content.starts_with(prefixes::KEY_PACKAGE));
import_device_key_package(&phone, fresh);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone re-establishes");
let welcome_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome serializes")
),
);
let rejoined = device
.handle(&welcome_frame, now())
.expect("device rejoins");
assert!(rejoined.events.contains(&LeafEvent::SessionEstablished {
peer: phone.address.clone(),
}));
}
#[test]
fn an_unsigned_control_frame_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let mut frame = phone_control_frame(
&phone,
&device.address().to_string(),
format!("{}{{}}", prefixes::KEY_PACKAGE),
);
frame.metadata.clear();
let err = device
.handle(&frame, now())
.expect_err("unsigned was accepted");
assert!(
matches!(err, LeafError::ControlFrameRefused(_)),
"unsigned control frame produced {err:?}"
);
}
#[test]
fn half_a_signature_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let mut frame = phone_control_frame(
&phone,
&device.address().to_string(),
format!("{}{{}}", prefixes::KEY_PACKAGE),
);
frame
.metadata
.remove(offline_protocol_sealed::CTRL_SIG_META_KEY);
let err = device
.handle(&frame, now())
.expect_err("a key without a signature was accepted");
assert!(
matches!(err, LeafError::ControlFrameRefused(_)),
"half a signature produced {err:?}"
);
}
#[test]
fn a_signing_key_that_does_not_derive_to_the_sender_is_refused() {
let phone = new_phone();
let impostor = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let mut frame = phone_control_frame(
&phone,
&device.address().to_string(),
format!("{}{{}}", prefixes::KEY_PACKAGE),
);
sign_as(&impostor, &mut frame);
let err = device
.handle(&frame, now())
.expect_err("an impostor's key was accepted");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"impersonation produced {err:?}"
);
}
#[test]
fn a_sender_that_is_not_an_address_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let mut frame = phone_control_frame(
&phone,
&device.address().to_string(),
format!("{}{{}}", prefixes::KEY_PACKAGE),
);
frame.sender = UserId::new("alice").expect("nickname is a valid user id");
sign_as(&phone, &mut frame);
let err = device
.handle(&frame, now())
.expect_err("a nickname sender was accepted");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a non-address sender produced {err:?}"
);
}
#[test]
fn a_key_package_that_claims_another_owner_is_refused() {
let phone = new_phone();
let other = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let package = phone
.manager
.take_push_key_package(&device.address().to_string())
.expect("package");
let payload = KeyPackagePayload {
user_id: other.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: false,
wire_versions: vec![],
env_versions: vec![],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let frame = phone_control_frame(
&phone,
&device.address().to_string(),
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
);
let err = device
.handle(&frame, now())
.expect_err("a borrowed-name package was accepted");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a mismatched package owner produced {err:?}"
);
}
#[test]
fn a_welcome_for_another_pairs_group_is_refused() {
let phone = new_phone();
let stranger = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&stranger.address, now())
.expect("device advertises");
import_device_key_package(&stranger, &advertisement);
let welcome = stranger
.manager
.create_session(&device_address)
.expect("stranger creates a session");
let frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome")
),
);
let err = device
.handle(&frame, now())
.expect_err("a relayed welcome was accepted");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a foreign welcome produced {err:?}"
);
}
#[test]
fn state_survives_a_power_cycle() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let device_address = {
let mut device = device(Arc::clone(&store));
pair(&phone, &mut device)
};
let mut revived = LeafDevice::resume(Arc::clone(&store), APP_ID).expect("device resumes");
assert_eq!(
revived.address().to_string(),
device_address,
"the device came back as a different device"
);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"still there?")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = revived.handle(&frame, now()).expect("revived device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "still there?".to_string(),
}],
"a session did not survive the power cycle"
);
}
#[test]
fn an_accepted_frame_is_answered_with_a_delivery_acknowledgement() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
assert!(
frame.requires_ack,
"the frame under test did not ask for an answer, so it proves nothing"
);
let handled = device
.handle(&frame, now())
.expect("device opens the frame");
let answers = acknowledgements(&handled);
assert_eq!(answers.len(), 1, "an accepted frame was not answered");
let ack = answers[0];
assert_eq!(
ack.metadata.get(ACK_FOR_KEY),
Some(&frame.id.to_string()),
"the answer did not name the frame it answers"
);
assert_eq!(
ack.sender.as_str(),
device_address,
"an answer from anyone but the recipient is discarded by the peer"
);
assert_eq!(
ack.recipient.as_str(),
phone.address,
"the answer went somewhere other than the sender"
);
assert!(ack.content.is_empty(), "an answer carries no content");
assert!(
!ack.requires_ack,
"an answer that asks to be answered does not terminate"
);
assert!(
!ack.metadata
.contains_key(offline_protocol_sealed::CTRL_SIG_META_KEY),
"an answer is not a control frame and must not be signed as one"
);
}
#[test]
fn a_frame_from_a_peer_this_device_does_not_know_is_not_answered() {
let phone = new_phone();
let stranger = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let mut frame = phone_control_frame(
&stranger,
&device_address,
String::from("nothing this device parses"),
);
sign_as(&stranger, &mut frame);
assert!(frame.requires_ack, "the frame did not ask for an answer");
let handled = device.handle(&frame, now()).expect("a stranger is ignored");
assert!(
handled.outbound.is_empty(),
"a device answered a peer it has never paired with: {:?}",
handled.outbound
);
}
#[test]
fn a_frame_that_names_a_known_peer_but_proves_nothing_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let forged = Message::new(
UserId::new(&phone.address).expect("phone address is a user id"),
UserId::new(&device_address).expect("device address is a user id"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
String::from("nothing this device parses"),
);
assert!(
forged.requires_ack,
"the frame under test did not ask for an answer, so it proves nothing"
);
let handled = device
.handle(&forged, now())
.expect("an unsigned frame is ignored");
assert!(
handled.outbound.is_empty(),
"a device answered a frame that proved nothing about its sender: {:?}",
handled.outbound
);
}
#[test]
fn frames_that_prove_nothing_cannot_evict_what_a_device_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let genuine = phone_sealed_frame(&phone, &device_address, &sealed);
device
.handle(&genuine, now())
.expect("first delivery opens");
for n in 0..4 {
let forged = Message::new(
UserId::new(&phone.address).expect("phone address is a user id"),
UserId::new(&device_address).expect("device address is a user id"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
format!("junk {n}"),
);
let handled = device.handle(&forged, now()).expect("junk is ignored");
assert!(
handled.outbound.is_empty(),
"a device answered junk: {:?}",
handled.outbound
);
}
let repeat = device
.handle(&genuine, now())
.expect("a retransmission is answered");
assert_eq!(
acknowledgements(&repeat).len(),
1,
"junk pushed a real answer out of the memory, restoring the retry ladder"
);
}
#[test]
fn an_unsolicited_probe_acknowledgement_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let forged = Message::new(
UserId::new(&phone.address).expect("phone address is a user id"),
UserId::new(&device_address).expect("device address is a user id"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
String::from(prefixes::SESSION_CONFIRM_ACK),
);
let handled = device
.handle(&forged, now())
.expect("an unsolicited ack is ignored");
assert!(
handled.outbound.is_empty(),
"a device answered an unsolicited probe acknowledgement: {:?}",
handled.outbound
);
}
#[test]
fn a_frame_that_is_refused_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let mut frame = phone_control_frame(
&phone,
&device_address,
format!("{}not a key package", prefixes::KEY_PACKAGE),
);
sign_as(&phone, &mut frame);
frame.content.push_str(" tampered");
let err = device
.handle(&frame, now())
.expect_err("a tampered frame was accepted");
assert!(
matches!(
err,
LeafError::ControlFrameRefused(_) | LeafError::MalformedFrame(_)
),
"the frame was refused for an unexpected reason: {err:?}"
);
}
#[test]
fn what_a_device_answered_survives_a_power_cycle() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
device.handle(&frame, now()).expect("first delivery opens");
let mut rebooted = LeafDevice::resume(Arc::clone(&store), APP_ID).expect("device resumes");
let handled = rebooted
.handle(&frame, now())
.expect("a replay after a reboot is answered");
assert_eq!(
acknowledgements(&handled).len(),
1,
"a device that rebooted met a retransmission with silence"
);
}
#[test]
fn a_frame_that_does_not_ask_for_an_answer_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let mut frame = phone_sealed_frame(&phone, &device_address, &sealed);
frame.requires_ack = false;
let handled = device
.handle(&frame, now())
.expect("device opens the frame");
assert!(
acknowledgements(&handled).is_empty(),
"a device answered a frame that asked for no answer"
);
}
#[test]
fn a_replayed_sealed_frame_is_answered_again_and_never_opened_twice() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let first = device.handle(&frame, now()).expect("first delivery opens");
assert!(
first.events.contains(&LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: String::from("unlock"),
}),
"the first delivery did not reach firmware: {:?}",
first.events
);
assert_eq!(
acknowledgements(&first).len(),
1,
"an accepted frame was not answered"
);
let second = device.handle(&frame, now()).expect("a replay is answered");
assert_eq!(
acknowledgements(&second).len(),
1,
"a retransmission went unanswered, which is what makes the peer keep asking"
);
assert_eq!(
acknowledgements(&second)[0].metadata.get(ACK_FOR_KEY),
Some(&frame.id.to_string()),
"the repeated answer named a different frame"
);
assert!(
spoken(&second).is_empty(),
"a replay produced a frame beyond the answer: {:?}",
spoken(&second)
);
assert!(
!second
.events
.iter()
.any(|event| matches!(event, LeafEvent::MessageReceived { .. })),
"a replayed frame was delivered to firmware a second time: {:?}",
second.events
);
}
#[test]
fn a_replay_the_device_no_longer_remembers_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let capture = phone_sealed_frame(&phone, &device_address, &sealed);
device
.handle(&capture, now())
.expect("first delivery opens");
for n in 0..4 {
let sealed = phone
.manager
.encrypt_for_user(&device_address, format!("later {n}").as_bytes())
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
device.handle(&frame, now()).expect("later delivery opens");
}
let err = device
.handle(&capture, now())
.expect_err("a forgotten replay was answered anyway");
assert!(
matches!(err, LeafError::Mls(_)),
"a forgotten replay produced {err:?}"
);
}
#[test]
fn the_device_answers_a_probe_with_an_ack() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
pair(&phone, &mut device);
let probe = phone_control_frame(
&phone,
&device.address().to_string(),
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
let handled = device.handle(&probe, now()).expect("device answers");
let said = spoken(&handled);
assert_eq!(said.len(), 1);
assert_eq!(
said[0].content,
prefixes::SESSION_CONFIRM_ACK,
"a probe was not answered with an acknowledgement"
);
assert!(
said[0]
.metadata
.contains_key(offline_protocol_sealed::CTRL_SIG_META_KEY),
"the acknowledgement went out unsigned"
);
}
#[test]
fn a_probe_without_a_session_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let probe = phone_control_frame(
&phone,
&device.address().to_string(),
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
let handled = device.handle(&probe, now()).expect("probe is handled");
assert!(
handled.outbound.is_empty(),
"a device with no session confirmed one anyway"
);
assert!(
matches!(handled.events.as_slice(), [LeafEvent::Ignored { .. }]),
"a session-less probe produced {:?}",
handled.events
);
}
#[test]
fn an_unsolicited_acknowledgement_does_not_establish_a_session() {
let stranger = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let frame = phone_control_frame(
&stranger,
&device.address().to_string(),
prefixes::SESSION_CONFIRM_ACK.to_string(),
);
let handled = device
.handle(&frame, now())
.expect("the acknowledgement is handled");
assert!(
!handled
.events
.iter()
.any(|event| matches!(event, LeafEvent::SessionEstablished { .. })),
"an unsolicited acknowledgement established a session: {:?}",
handled.events
);
assert!(
handled.outbound.is_empty(),
"an unsolicited acknowledgement produced a frame"
);
assert!(
!device
.has_session(&stranger.address)
.expect("session check"),
"an unsolicited acknowledgement left a session on flash"
);
let err = device
.seal(&stranger.address, "unlock", now())
.expect_err("sealed to a peer that only sent an acknowledgement");
assert!(matches!(err, LeafError::NoSession(_)), "produced {err:?}");
}
#[test]
fn a_peer_that_already_has_a_key_package_is_not_sent_another() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let _ = device
.key_package_frame(&phone.address, now())
.expect("device advertises");
let package = phone
.manager
.take_push_key_package(&device_address)
.expect("package");
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: false,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
);
let handled = device
.handle(&frame, now())
.expect("device records the peer");
assert!(
spoken(&handled).is_empty(),
"the device answered a key package with another one, which loops"
);
assert!(handled.events.contains(&LeafEvent::PeerAdvertised {
peer: phone.address.clone(),
}));
}
#[test]
fn the_envelope_encoding_follows_what_the_peer_advertised() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
assert!(device
.peer_env_versions(&phone.address)
.expect("record")
.is_empty());
let floor = device
.seal(&phone.address, "floor", now())
.expect("device seals");
let body = floor
.content
.strip_prefix(prefixes::ENCRYPTED)
.expect("envelope");
assert!(
body.starts_with('{'),
"a peer that advertised nothing was sent a compact envelope"
);
let opened = phone
.manager
.decrypt_from_user(&envelope_of(&floor), &device_address)
.expect("phone opens the floor envelope");
assert_eq!(opened.as_deref(), Some(&b"floor"[..]));
}
#[derive(Default)]
struct FailingStore {
inner: MemoryStore,
failing: AtomicBool,
writes_after_arming: Mutex<usize>,
}
impl FailingStore {
fn arm(&self) {
self.failing.store(true, Ordering::SeqCst);
}
}
impl LeafStore for FailingStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
if self.failing.load(Ordering::SeqCst) {
if let Ok(mut count) = self.writes_after_arming.lock() {
*count += 1;
}
return Err(StoreError::Store("flash is on fire".to_string()));
}
self.inner.store(key_type, key_id, data)
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.load(key_type, key_id)
}
fn delete(&self, key_type: &str, key_id: &str) -> Result<(), StoreError> {
self.inner.delete(key_type, key_id)
}
}
#[test]
fn a_failing_store_produces_no_frame() {
let phone = new_phone();
let store = Arc::new(FailingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
pair(&phone, &mut device);
store.arm();
let err = device
.seal(&phone.address, "unlock", now())
.expect_err("a frame was produced despite the store failing");
assert!(
matches!(err, LeafError::Storage(_)),
"a failing store produced {err:?}"
);
assert!(
*store.writes_after_arming.lock().expect("count") > 0,
"no write was even attempted, so this proves nothing about ordering"
);
}
#[derive(Default)]
struct PeerWriteFailingStore {
inner: MemoryStore,
failing: AtomicBool,
}
impl PeerWriteFailingStore {
fn arm(&self) {
self.failing.store(true, Ordering::SeqCst);
}
}
impl LeafStore for PeerWriteFailingStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
if key_type == KEY_TYPE_PEER && self.failing.load(Ordering::SeqCst) {
return Err(StoreError::Store("flash is on fire".to_string()));
}
self.inner.store(key_type, key_id, data)
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.load(key_type, key_id)
}
fn delete(&self, key_type: &str, key_id: &str) -> Result<(), StoreError> {
self.inner.delete(key_type, key_id)
}
}
#[test]
fn a_store_that_fails_while_recording_the_answer_still_reports_the_message() {
let phone = new_phone();
let store = Arc::new(PeerWriteFailingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
store.arm();
let handled = device
.handle(&frame, now())
.expect("a frame this device opened was reported as a failure");
assert!(
handled.events.contains(&LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: String::from("unlock"),
}),
"the command was discarded because its receipt would not store: {:?}",
handled.events
);
assert!(
acknowledgements(&handled).is_empty(),
"an answer went out with nothing on flash behind it"
);
assert!(
handled
.events
.iter()
.any(|event| matches!(event, LeafEvent::Ignored { reason } if reason.contains("could not be stored"))),
"a store failure was swallowed: {:?}",
handled.events
);
}
#[test]
fn a_device_refuses_to_replace_its_own_identity() {
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let first = LeafDevice::provision(Arc::clone(&store), APP_ID).expect("first provisioning");
let err = LeafDevice::provision(Arc::clone(&store), APP_ID)
.expect_err("a second provisioning replaced the identity");
assert!(matches!(err, LeafError::AlreadyProvisioned));
let reopened = LeafDevice::open(store, APP_ID).expect("open resumes");
assert_eq!(reopened.address().to_string(), first.address().to_string());
}
#[test]
fn a_device_address_derives_from_its_own_key() {
let mut device = device(Arc::new(MemoryStore::new()));
let probe_target = device.address().to_string();
let phone = new_phone();
pair(&phone, &mut device);
let mut frame = phone_control_frame(
&phone,
&probe_target,
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
sign_as(&phone, &mut frame);
let handled = device.handle(&frame, now()).expect("probe answered");
let ack = spoken(&handled)[0];
let key = BASE64
.decode(
ack.metadata
.get(offline_protocol_sealed::CTRL_PK_META_KEY)
.expect("ack carries a key"),
)
.expect("key is base64");
assert_eq!(
derive_address(&key).expect("derive").to_string(),
device.address().to_string(),
"the device signed with a key that does not derive to its own address"
);
}
#[derive(Debug, Default)]
struct CountingStore {
entries: Mutex<BTreeMap<(String, String), Vec<u8>>>,
}
impl CountingStore {
fn keys_of(&self, key_type: &str) -> Vec<String> {
self.entries
.lock()
.expect("lock")
.keys()
.filter(|(held, _)| held == key_type)
.map(|(_, id)| id.clone())
.collect()
}
fn epoch_records(&self) -> Vec<String> {
self.keys_of(KEY_TYPE_GROUP_EPOCH)
.into_iter()
.filter(|id| !id.ends_with(":max"))
.collect()
}
}
impl LeafStore for CountingStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
self.entries
.lock()
.expect("lock")
.insert((key_type.to_string(), key_id.to_string()), data.to_vec());
Ok(())
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
Ok(self
.entries
.lock()
.expect("lock")
.get(&(key_type.to_string(), key_id.to_string()))
.cloned())
}
fn delete(&self, key_type: &str, key_id: &str) -> Result<(), StoreError> {
self.entries
.lock()
.expect("lock")
.remove(&(key_type.to_string(), key_id.to_string()));
Ok(())
}
}
fn commit_to(phone: &Phone, device: &mut LeafDevice, device_address: &str) {
let group_id = GroupId::for_session(&phone.address, device_address).expect("pair group id");
let commit = phone.manager.update_keys(&group_id).expect("phone commits");
let frame = phone_sealed_frame(phone, device_address, &commit);
let handled = device
.handle(&frame, now())
.expect("device applies the commit");
assert!(
handled
.events
.iter()
.any(|event| matches!(event, LeafEvent::CommitApplied { .. })),
"a commit did not apply: {:?}",
handled.events
);
}
#[test]
fn a_commit_trims_the_prior_epochs_it_leaves_behind() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
for _ in 0..12 {
commit_to(&phone, &mut device, &device_address);
}
let records = store.epoch_records();
assert!(
records.len() <= 3,
"twelve commits left {} prior-epoch records: {records:?}",
records.len()
);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"still talking")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "still talking".to_string(),
}]
);
}
#[test]
fn unpairing_erases_the_prior_epoch_records_too() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
commit_to(&phone, &mut device, &device_address);
commit_to(&phone, &mut device, &device_address);
assert!(
!store.epoch_records().is_empty(),
"the setup wrote no epoch records, so this test would pass vacuously"
);
device.unpair(&phone.address).expect("device unpairs");
assert!(
store.epoch_records().is_empty(),
"unpairing left epoch records behind: {:?}",
store.epoch_records()
);
assert!(
store.keys_of(KEY_TYPE_PEER).is_empty(),
"unpairing left the peer record behind"
);
assert!(
!device.has_session(&phone.address).expect("session check"),
"unpairing left a session"
);
}
#[test]
fn a_corrupt_group_state_is_not_reported_as_a_missing_session() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
pair(&phone, &mut device);
let key = store
.keys_of(KEY_TYPE_GROUP_STATE)
.into_iter()
.next()
.expect("pairing wrote group state");
store
.store(KEY_TYPE_GROUP_STATE, &key, b"not a group")
.expect("store");
let err = device
.seal(&phone.address, "unlock", now())
.expect_err("a corrupt group state still produced a frame");
assert!(
matches!(err, LeafError::Storage(_)),
"a corrupt group state produced {err:?}"
);
let stranger = new_phone();
let err = device
.seal(&stranger.address, "unlock", now())
.expect_err("sealed to a peer with no session");
assert!(
matches!(err, LeafError::NoSession(_)),
"an absent session produced {err:?}"
);
}
#[test]
fn unpairing_sweeps_epochs_a_corrupt_marker_cannot_bound() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
commit_to(&phone, &mut device, &device_address);
commit_to(&phone, &mut device, &device_address);
assert!(
!store.epoch_records().is_empty(),
"the setup wrote no epoch records, so this test would pass vacuously"
);
let marker = store
.keys_of(KEY_TYPE_GROUP_EPOCH)
.into_iter()
.find(|id| id.ends_with(":max"))
.expect("a marker was written");
store
.store(KEY_TYPE_GROUP_EPOCH, &marker, b"not eight bytes")
.expect("store");
device.unpair(&phone.address).expect("device unpairs");
assert!(
store.epoch_records().is_empty(),
"a corrupt marker left epoch records behind: {:?}",
store.epoch_records()
);
}
struct CutStore {
inner: MemoryStore,
refuse: Mutex<Option<String>>,
}
impl CutStore {
fn cutting(key_id: &str) -> Self {
Self {
inner: MemoryStore::new(),
refuse: Mutex::new(Some(key_id.to_string())),
}
}
fn restore_power(&self) {
*self.refuse.lock().expect("lock") = None;
}
}
impl LeafStore for CutStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
if self.refuse.lock().expect("lock").as_deref() == Some(key_id) {
return Err(StoreError::Store("power cut".to_string()));
}
self.inner.store(key_type, key_id, data)
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.load(key_type, key_id)
}
fn delete(&self, key_type: &str, key_id: &str) -> Result<(), StoreError> {
self.inner.delete(key_type, key_id)
}
}
#[test]
fn a_provisioning_cut_between_the_two_identity_writes_is_recoverable() {
let store = Arc::new(CutStore::cutting("signature_secret"));
let err = LeafDevice::provision(Arc::clone(&store) as Arc<dyn LeafStore>, APP_ID)
.expect_err("the cut write reported success");
assert!(matches!(err, LeafError::Storage(_)), "cut produced {err:?}");
assert!(
store
.load(KEY_TYPE_IDENTITY, "signature_public")
.expect("load")
.is_some(),
"nothing was written at all, so this proves nothing about ordering"
);
store.restore_power();
let device = LeafDevice::open(Arc::clone(&store) as Arc<dyn LeafStore>, APP_ID)
.expect("a device that was cut mid-provisioning could not be opened");
let resumed = LeafDevice::resume(store as Arc<dyn LeafStore>, APP_ID).expect("device resumes");
assert_eq!(resumed.address().to_string(), device.address().to_string());
}
#[test]
fn a_replayed_session_reset_does_not_tear_down_the_new_session() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
phone
.manager
.delete_session(&device_address)
.expect("phone discards its own session");
let package = phone
.manager
.take_push_key_package(&device_address)
.expect("phone mints a package");
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: true,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let reset_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
);
let handled = device.handle(&reset_frame, now()).expect("device resets");
assert!(handled.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}));
import_device_key_package(&phone, spoken(&handled)[0]);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone re-establishes");
let welcome_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome")
),
);
device
.handle(&welcome_frame, now())
.expect("device rejoins");
assert!(device.has_session(&phone.address).expect("session check"));
let replayed = device
.handle(&reset_frame, now())
.expect("the replay is handled");
assert!(
!replayed.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}),
"a replayed reset tore down the session again: {:?}",
replayed.events
);
assert!(
device.has_session(&phone.address).expect("session check"),
"a replayed reset discarded a session the peer still holds"
);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
}
#[test]
fn a_flood_of_strangers_cannot_displace_an_established_peer() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
let package = phone
.manager
.take_push_key_package(&device_address)
.expect("package");
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: false,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
);
device
.handle(&frame, now())
.expect("device records the phone");
for index in 0..24 {
let stranger = new_phone();
let payload = KeyPackagePayload {
user_id: stranger.address.clone(),
key_package_data: vec![index],
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: false,
wire_versions: vec![],
env_versions: vec![],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let frame = phone_control_frame(
&stranger,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
);
let _ = device.handle(&frame, now());
}
assert!(
store.keys_of(KEY_TYPE_PEER).len() <= 16,
"a flood grew the peer table to {}",
store.keys_of(KEY_TYPE_PEER).len()
);
assert!(
device.has_session(&phone.address).expect("session check"),
"a flood of strangers evicted the established peer"
);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
}
#[test]
fn a_welcome_whose_body_lies_about_its_group_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&phone.address, now())
.expect("device advertises");
let decoy = phone
.manager
.create_group("decoy")
.expect("phone creates a group that is not this pair's");
let (foreign, _commit) = phone
.manager
.add_group_member(
&decoy.group_id,
&device_address,
&raw_key_package(&advertisement),
)
.expect("phone adds the device to the decoy group");
let forged = WelcomeMessage {
group_id: GroupId::for_session(&phone.address, &device_address).expect("pair group id"),
welcome_data: foreign.welcome_data.clone(),
inviter_id: phone.address.clone(),
group_name: None,
timestamp_ms: 0,
};
let frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&forged).expect("welcome")
),
);
let err = device
.handle(&frame, now())
.expect_err("a welcome whose body lied was accepted");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a mismatched welcome produced {err:?}"
);
assert!(
!device.has_session(&phone.address).expect("session check"),
"the refused welcome still left a session on flash"
);
}
struct TornStore {
inner: MemoryStore,
refuse_type: Mutex<Option<String>>,
refusals: Mutex<usize>,
keys: Mutex<Vec<(String, String)>>,
}
impl TornStore {
fn new() -> Self {
Self {
inner: MemoryStore::new(),
refuse_type: Mutex::new(None),
refusals: Mutex::new(0),
keys: Mutex::new(Vec::new()),
}
}
fn epoch_records(&self) -> Vec<String> {
self.keys
.lock()
.expect("lock")
.iter()
.filter(|(held, id)| held == KEY_TYPE_GROUP_EPOCH && !id.ends_with(":max"))
.map(|(_, id)| id.clone())
.collect()
}
fn cut(&self, key_type: &str) {
*self.refuse_type.lock().expect("lock") = Some(key_type.to_string());
}
fn restore_power(&self) {
*self.refuse_type.lock().expect("lock") = None;
}
fn refusals(&self) -> usize {
*self.refusals.lock().expect("lock")
}
}
impl LeafStore for TornStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
if self.refuse_type.lock().expect("lock").as_deref() == Some(key_type) {
*self.refusals.lock().expect("lock") += 1;
return Err(StoreError::Store("power cut".to_string()));
}
self.inner.store(key_type, key_id, data)?;
let mut keys = self.keys.lock().expect("lock");
if !keys.iter().any(|(t, i)| t == key_type && i == key_id) {
keys.push((key_type.to_string(), key_id.to_string()));
}
Ok(())
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.load(key_type, key_id)
}
fn delete(&self, key_type: &str, key_id: &str) -> Result<(), StoreError> {
self.inner.delete(key_type, key_id)?;
self.keys
.lock()
.expect("lock")
.retain(|(t, i)| !(t == key_type && i == key_id));
Ok(())
}
}
#[test]
fn a_cut_between_the_epoch_records_and_the_state_does_not_wedge_the_session() {
let phone = new_phone();
let store = Arc::new(TornStore::new());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
commit_to(&phone, &mut device, &device_address);
store.cut(KEY_TYPE_GROUP_STATE);
let group_id = GroupId::for_session(&phone.address, &device_address).expect("pair group id");
let commit = phone.manager.update_keys(&group_id).expect("phone commits");
let frame = phone_sealed_frame(&phone, &device_address, &commit);
let err = device
.handle(&frame, now())
.expect_err("a state write that failed still reported success");
assert!(
matches!(err, LeafError::Storage(_)),
"a cut state write produced {err:?}"
);
assert!(
store.refusals() > 0,
"no state write was attempted, so this proves nothing about the window"
);
store.restore_power();
let retried = device
.handle(&frame, now())
.expect("the retried commit was refused, so the session is wedged");
assert!(
retried
.events
.iter()
.any(|event| matches!(event, LeafEvent::CommitApplied { .. })),
"the retried commit did not apply: {:?}",
retried.events
);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let app_frame = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&app_frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
let answer = device
.seal(&phone.address, "unlocked", now())
.expect("device seals");
let opened = phone
.manager
.decrypt_from_user(&envelope_of(&answer), &device_address)
.expect("phone opens the answer");
assert_eq!(opened.as_deref(), Some(&b"unlocked"[..]));
}
#[test]
fn a_peer_that_paired_through_a_welcome_is_in_the_audit_list() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
pair(&phone, &mut device);
assert!(
device.has_session(&phone.address).expect("session check"),
"the pairing left no session, so this test would pass vacuously"
);
let peers = device.peers().expect("peers");
assert!(
peers.contains(&phone.address),
"a peer this device holds a session with is missing from the audit list: {peers:?}"
);
device.unpair(&phone.address).expect("device unpairs");
assert!(
!device.has_session(&phone.address).expect("session check"),
"unpairing a peer from the audit list left its session"
);
assert!(
device.peers().expect("peers").is_empty(),
"unpairing left the peer in the audit list"
);
}
#[test]
fn a_probe_against_a_group_state_that_will_not_load_is_not_answered() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
pair(&phone, &mut device);
let key = store
.keys_of(KEY_TYPE_GROUP_STATE)
.into_iter()
.next()
.expect("pairing wrote group state");
store
.store(KEY_TYPE_GROUP_STATE, &key, b"not a group")
.expect("store");
let probe = phone_control_frame(
&phone,
&device.address().to_string(),
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
let err = device
.handle(&probe, now())
.expect_err("a device with an unloadable session confirmed one anyway");
assert!(
matches!(err, LeafError::Storage(_)),
"an unloadable state produced {err:?}"
);
}
#[test]
fn a_resumed_device_refuses_a_public_key_its_secret_did_not_make() {
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let original = LeafDevice::provision(Arc::clone(&store), APP_ID).expect("provisioning");
let address = original.address().to_string();
let own = store
.load(KEY_TYPE_IDENTITY, "signature_public")
.expect("load")
.expect("provisioning wrote a public key");
let other_store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
LeafDevice::provision(Arc::clone(&other_store), APP_ID).expect("second device");
let foreign = other_store
.load(KEY_TYPE_IDENTITY, "signature_public")
.expect("load")
.expect("the second device wrote a public key");
assert_ne!(
foreign, own,
"the two devices minted the same key, so this test would prove nothing"
);
store
.store(KEY_TYPE_IDENTITY, "signature_public", &foreign)
.expect("store");
let err = LeafDevice::resume(Arc::clone(&store), APP_ID)
.expect_err("a device resumed on a key its secret does not derive to");
assert!(
matches!(err, LeafError::Storage(_)),
"a mismatched identity pair produced {err:?}"
);
store
.store(KEY_TYPE_IDENTITY, "signature_public", &own)
.expect("store");
let resumed = LeafDevice::resume(store, APP_ID).expect("the intact pair still resumes");
assert_eq!(resumed.address().to_string(), address);
}
#[test]
fn unpairing_sweeps_epochs_when_the_state_entry_cannot_be_read() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
commit_to(&phone, &mut device, &device_address);
commit_to(&phone, &mut device, &device_address);
assert!(
!store.epoch_records().is_empty(),
"the setup wrote no epoch records, so this test would pass vacuously"
);
let key = store
.keys_of(KEY_TYPE_GROUP_STATE)
.into_iter()
.next()
.expect("pairing wrote group state");
store
.store(KEY_TYPE_GROUP_STATE, &key, b"nope")
.expect("store");
device.unpair(&phone.address).expect("device unpairs");
assert!(
store.epoch_records().is_empty(),
"an unreadable state entry left epoch records behind: {:?}",
store.epoch_records()
);
}
fn advertisement_body(user_id: &str) -> String {
let payload = KeyPackagePayload {
user_id: user_id.to_string(),
key_package_data: vec![7],
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: false,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
)
}
#[test]
fn a_frame_addressed_to_another_node_is_ignored() {
let phone = new_phone();
let bystander = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = device.address().to_string();
let body = advertisement_body(&phone.address);
let overheard = phone_control_frame(&phone, &bystander.address, body.clone());
let handled = device
.handle(&overheard, now())
.expect("overhearing a neighbour is not a failure");
assert!(
handled.outbound.is_empty(),
"the device answered a frame addressed to somebody else: {:?}",
handled.outbound.len()
);
assert!(
matches!(handled.events.as_slice(), [LeafEvent::Ignored { .. }]),
"an overheard frame produced {:?}",
handled.events
);
assert!(
store.keys_of(KEY_TYPE_PEER).is_empty(),
"an overheard frame wrote a peer record: {:?}",
store.keys_of(KEY_TYPE_PEER)
);
assert!(
store.keys_of(KEY_TYPE_KEY_PACKAGE).is_empty(),
"an overheard frame minted a key package: {:?}",
store.keys_of(KEY_TYPE_KEY_PACKAGE)
);
assert!(
device.peers().expect("peers").is_empty(),
"an overheard frame put a peer in the audit list"
);
let addressed = phone_control_frame(&phone, &device_address, body);
let handled = device
.handle(&addressed, now())
.expect("device handles a frame addressed to it");
assert_eq!(
spoken(&handled).len(),
1,
"a frame addressed to this device was not answered: {:?}",
handled.events
);
assert!(
device.peers().expect("peers").contains(&phone.address),
"a frame addressed to this device did not record its sender"
);
}
#[test]
fn a_sealed_frame_addressed_elsewhere_is_not_opened() {
let phone = new_phone();
let bystander = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let elsewhere = phone_sealed_frame(&phone, &bystander.address, &sealed);
let handled = device
.handle(&elsewhere, now())
.expect("a frame addressed elsewhere is not a failure");
assert!(
matches!(handled.events.as_slice(), [LeafEvent::Ignored { .. }]),
"a sealed frame addressed elsewhere produced {:?}",
handled.events
);
let addressed = phone_sealed_frame(&phone, &device_address, &sealed);
let handled = device.handle(&addressed, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
}
#[test]
fn traffic_between_two_neighbours_is_ignored_rather_than_reported_as_a_failure() {
let phone = new_phone();
let bystander = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
pair(&phone, &mut device);
let stranger = new_phone();
let envelope = EncryptedMessage {
group_id: GroupId::for_session(&stranger.address, &bystander.address)
.expect("their pair's group id"),
message_type: offline_protocol_sealed::MlsMessageType::Application,
epoch: 1,
ciphertext: vec![9, 9, 9],
sender_id: stranger.address.clone(),
timestamp_ms: 0,
};
let overheard = phone_sealed_frame(&stranger, &bystander.address, &envelope);
let handled = device
.handle(&overheard, now())
.expect("two neighbours talking is not this device's failure");
assert!(
matches!(handled.events.as_slice(), [LeafEvent::Ignored { .. }]),
"overheard neighbour traffic produced {:?}",
handled.events
);
let sealed = phone
.manager
.encrypt_for_user(&device.address().to_string(), b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device.address().to_string(), &sealed);
let handled = device.handle(&frame, now()).expect("device opens");
assert_eq!(
handled.events,
vec![LeafEvent::MessageReceived {
peer: phone.address.clone(),
text: "unlock".to_string(),
}]
);
}
#[derive(Default)]
struct UndeletableStore {
inner: MemoryStore,
keys: Mutex<Vec<(String, String)>>,
}
impl UndeletableStore {
fn keys_of(&self, key_type: &str) -> Vec<String> {
self.keys
.lock()
.expect("lock")
.iter()
.filter(|(held, _)| held == key_type)
.map(|(_, id)| id.clone())
.collect()
}
}
impl LeafStore for UndeletableStore {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> Result<(), StoreError> {
self.inner.store(key_type, key_id, data)?;
let mut keys = self.keys.lock().expect("lock");
if !keys.iter().any(|(t, i)| t == key_type && i == key_id) {
keys.push((key_type.to_string(), key_id.to_string()));
}
Ok(())
}
fn load(&self, key_type: &str, key_id: &str) -> Result<Option<Vec<u8>>, StoreError> {
self.inner.load(key_type, key_id)
}
fn delete(&self, _key_type: &str, _key_id: &str) -> Result<(), StoreError> {
Err(StoreError::Delete("the sector will not erase".to_string()))
}
}
#[test]
fn a_failed_eviction_leaves_no_key_package_the_index_has_forgotten() {
const KEY_PACKAGE_INDEX: &str = "__index__";
const MAX_UNSPENT: usize = 4;
let store = Arc::new(UndeletableStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let peers: Vec<String> = (0..MAX_UNSPENT).map(|_| new_phone().address).collect();
for peer in &peers {
device
.key_package_frame(peer, now())
.expect("device mints a key package");
}
let held: Vec<String> = store
.keys_of(KEY_TYPE_KEY_PACKAGE)
.into_iter()
.filter(|id| id != KEY_PACKAGE_INDEX)
.collect();
assert_eq!(
held.len(),
MAX_UNSPENT,
"the window did not fill, so the eviction below would never run: {held:?}"
);
let err = device
.key_package_frame(&new_phone().address, now())
.expect_err("a package was minted although its eviction could not be erased");
let raw = store
.load(KEY_TYPE_KEY_PACKAGE, KEY_PACKAGE_INDEX)
.expect("load")
.expect("minting wrote an index");
let index: Vec<String> = serde_json::from_slice(&raw).expect("the index parses");
let orphaned: Vec<String> = store
.keys_of(KEY_TYPE_KEY_PACKAGE)
.into_iter()
.filter(|id| id != KEY_PACKAGE_INDEX)
.filter(|id| !index.contains(id))
.collect();
assert!(
orphaned.is_empty(),
"a failed eviction left key package private material no index names: \
{orphaned:?} (error was {err:?})"
);
}
#[test]
fn an_overheard_key_package_cannot_be_spent_by_the_node_that_overheard_it() {
let phone = new_phone();
let eavesdropper = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&phone.address, now())
.expect("device advertises to the phone");
import_device_key_package(&eavesdropper, &advertisement);
let hijack = eavesdropper
.manager
.create_session(&device_address)
.expect("the eavesdropper builds a group on the copied package");
let frame = phone_control_frame(
&eavesdropper,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&hijack).expect("welcome")
),
);
let err = device
.handle(&frame, now())
.expect_err("an overheard key package was spent by whoever copied it");
assert!(
matches!(err, LeafError::UnsolicitedWelcome(_)),
"spending an overheard key package produced {err:?}"
);
assert!(
!device
.has_session(&eavesdropper.address)
.expect("session check"),
"the refused welcome still left a session on flash"
);
assert!(
!device
.peers()
.expect("peer list")
.contains(&eavesdropper.address),
"a refused welcome still spent a slot in the peer table"
);
import_device_key_package(&phone, &advertisement);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone creates the session on the package it was given");
let welcome_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome serializes")
),
);
let handled = device
.handle(&welcome_frame, now())
.expect("the intended peer can still join");
assert!(
handled.events.contains(&LeafEvent::SessionEstablished {
peer: phone.address.clone(),
}),
"the overheard attempt burned the package it was refused for: {:?}",
handled.events
);
}
#[test]
fn a_welcome_cannot_spend_a_key_package_minted_for_a_different_peer() {
let phone = new_phone();
let neighbour = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let for_phone = device
.key_package_frame(&phone.address, now())
.expect("device advertises to the phone");
device
.key_package_frame(&neighbour.address, now())
.expect("device advertises to the neighbour");
import_device_key_package(&neighbour, &for_phone);
let hijack = neighbour
.manager
.create_session(&device_address)
.expect("the neighbour builds a group on the phone's package");
let frame = phone_control_frame(
&neighbour,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&hijack).expect("welcome")
),
);
let err = device
.handle(&frame, now())
.expect_err("a peer spent a package minted for somebody else");
assert!(
matches!(err, LeafError::UnsolicitedWelcome(_)),
"spending another peer's key package produced {err:?}"
);
assert!(
!device
.has_session(&neighbour.address)
.expect("session check"),
"the refused welcome still left a session on flash"
);
import_device_key_package(&phone, &for_phone);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone creates the session");
let welcome_frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome serializes")
),
);
let handled = device
.handle(&welcome_frame, now())
.expect("the intended peer can still join");
assert!(
handled.events.contains(&LeafEvent::SessionEstablished {
peer: phone.address.clone(),
}),
"the neighbour's attempt burned the phone's package: {:?}",
handled.events
);
}
#[test]
fn a_commit_that_adds_a_third_member_is_refused() {
let phone = new_phone();
let outsider = new_phone();
let store = Arc::new(MemoryStore::new());
let mut device = device(store);
let device_address = pair(&phone, &mut device);
let group_id = GroupId::for_session(&phone.address, &device_address).expect("pair group id");
let bundle = outsider
.manager
.get_or_create_key_package()
.expect("the outsider has a key package");
let (_welcome, commit) = phone
.manager
.add_group_member(&group_id, &outsider.address, &bundle.key_package_data)
.expect("the peer adds a third member to the pair's own group");
let frame = phone_sealed_frame(&phone, &device_address, &commit);
let err = device
.handle(&frame, now())
.expect_err("the device followed its peer into a room it never chose");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a commit that grew the pair produced {err:?}"
);
}
fn widen_the_pair(phone: &Phone, device: &mut LeafDevice, device_address: &str) {
let outsider = new_phone();
let group_id = GroupId::for_session(&phone.address, device_address).expect("pair group id");
let bundle = outsider
.manager
.get_or_create_key_package()
.expect("the outsider has a key package");
let (_welcome, commit) = phone
.manager
.add_group_member(&group_id, &outsider.address, &bundle.key_package_data)
.expect("the peer adds a third member to the pair's own group");
let frame = phone_sealed_frame(phone, device_address, &commit);
let err = device
.handle(&frame, now())
.expect_err("the device followed its peer into a room it never chose");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a commit that grew the pair produced {err:?}"
);
}
#[test]
fn a_group_that_stopped_being_a_pair_is_not_sealed_into() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = pair(&phone, &mut device);
device
.seal(&phone.address, "while it was a pair", now())
.expect("a pair seals");
widen_the_pair(&phone, &mut device, &device_address);
let err = device
.seal(&phone.address, "after", now())
.expect_err("the device sealed into a group holding a third member");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"sealing into a widened group produced {err:?}"
);
drop(device);
let mut revived = LeafDevice::resume(Arc::clone(&store), APP_ID).expect("device resumes");
let err = revived
.seal(&phone.address, "after a reboot", now())
.expect_err("a rebooted device sealed into a group holding a third member");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"sealing into a widened group after a reboot produced {err:?}"
);
}
#[test]
fn a_message_into_a_group_that_stopped_being_a_pair_is_not_opened() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
widen_the_pair(&phone, &mut device, &device_address);
let sealed = phone
.manager
.encrypt_for_user(&device_address, b"unlock")
.expect("phone seals");
let frame = phone_sealed_frame(&phone, &device_address, &sealed);
let err = device
.handle(&frame, now())
.expect_err("the device opened a message from a group holding a third member");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"opening a message from a widened group produced {err:?}"
);
}
#[test]
fn a_probe_against_a_group_that_stopped_being_a_pair_is_not_answered() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
widen_the_pair(&phone, &mut device, &device_address);
let probe = phone_control_frame(
&phone,
&device_address,
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
let err = device
.handle(&probe, now())
.expect_err("the device promised a session it will not seal into");
assert!(
matches!(err, LeafError::IdentityBinding(_)),
"a probe against a widened group produced {err:?}"
);
}
#[test]
fn unpairing_sweeps_an_epoch_record_a_torn_write_left_above_every_anchor() {
let phone = new_phone();
let store = Arc::new(TornStore::new());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
let device_address = pair(&phone, &mut device);
commit_to(&phone, &mut device, &device_address);
commit_to(&phone, &mut device, &device_address);
store.cut(KEY_TYPE_GROUP_STATE);
let group_id = GroupId::for_session(&phone.address, &device_address).expect("pair group id");
let commit = phone.manager.update_keys(&group_id).expect("phone commits");
let frame = phone_sealed_frame(&phone, &device_address, &commit);
let err = device
.handle(&frame, now())
.expect_err("the state write was cut and the frame was handled anyway");
assert!(
matches!(err, LeafError::Storage(_)),
"a cut state write produced {err:?}"
);
store.restore_power();
let before = store.epoch_records();
assert!(
!before.is_empty(),
"nothing was left on flash to sweep, so this test proves nothing"
);
device.unpair(&phone.address).expect("the owner unpairs");
let after = store.epoch_records();
assert!(
after.is_empty(),
"an epoch record survived the erasure its owner asked for: {after:?} \
(before unpairing: {before:?})"
);
}
#[test]
fn unpairing_erases_the_key_package_minted_for_the_peer() {
const KEY_PACKAGE_INDEX: &str = "__index__";
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
device
.key_package_frame(&phone.address, now())
.expect("device mints a key package");
let held: Vec<String> = store
.keys_of(KEY_TYPE_KEY_PACKAGE)
.into_iter()
.filter(|id| id != KEY_PACKAGE_INDEX)
.collect();
assert_eq!(
held.len(),
1,
"no package was minted, so this test proves nothing: {held:?}"
);
device.unpair(&phone.address).expect("the owner unpairs");
let after: Vec<String> = store
.keys_of(KEY_TYPE_KEY_PACKAGE)
.into_iter()
.filter(|id| id != KEY_PACKAGE_INDEX)
.collect();
assert!(
after.is_empty(),
"an init key survived the peer it was minted for: {after:?}"
);
}
#[test]
fn unpairing_survives_a_peer_record_that_will_not_decode() {
let phone = new_phone();
let store = Arc::new(CountingStore::default());
let mut device = device(Arc::clone(&store) as Arc<dyn LeafStore>);
device
.key_package_frame(&phone.address, now())
.expect("device mints a key package");
store
.store(KEY_TYPE_PEER, &phone.address, b"not json at all")
.expect("the store accepts the corrupt record");
device
.unpair(&phone.address)
.expect("a corrupt record left the owner unable to forget a peer");
assert!(
!store.keys_of(KEY_TYPE_PEER).contains(&phone.address),
"the peer record survived unpairing"
);
assert!(
!device.peers().expect("peer list").contains(&phone.address),
"the peer survived unpairing in the index"
);
}
#[test]
fn a_welcome_that_spends_a_package_the_device_no_longer_holds_names_the_real_failure() {
const MAX_UNSPENT: usize = 4;
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&phone.address, now())
.expect("device advertises to the phone");
import_device_key_package(&phone, &advertisement);
for peer in (0..MAX_UNSPENT).map(|_| new_phone().address) {
device
.key_package_frame(&peer, now())
.expect("device mints for another peer");
}
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone creates the session on the package it was given");
let frame = phone_control_frame(
&phone,
&device_address,
format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome serializes")
),
);
let err = device
.handle(&frame, now())
.expect_err("the package this welcome spends is no longer held");
assert!(
matches!(err, LeafError::StaleKeyPackage(_)),
"a welcome spending an evicted package produced {err:?}"
);
}
#[test]
fn a_frame_older_than_the_window_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let stale_at = now() - (LEAF_CTRL_FRESHNESS_PAST_MS / 1000) - 60;
let frame = phone_control_frame_at(
&phone,
&device_address,
format!("{}{{}}", prefixes::SESSION_CONFIRM_PROBE),
stale_at,
);
let refused = device.handle(&frame, now());
assert!(
matches!(refused, Err(LeafError::StaleControlFrame(_))),
"a frame older than the window must be refused as stale, got {refused:?}"
);
let fresh = phone_control_frame_at(
&phone,
&device_address,
format!("{}{{}}", prefixes::SESSION_CONFIRM_PROBE),
now(),
);
assert!(device.handle(&fresh, now()).is_ok());
}
#[test]
fn a_frame_from_the_future_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let ahead = now() + (CTRL_FRESHNESS_FUTURE_MS / 1000) + 60;
let frame = phone_control_frame_at(
&phone,
&device_address,
format!("{}{{}}", prefixes::SESSION_CONFIRM_PROBE),
ahead,
);
assert!(matches!(
device.handle(&frame, now()),
Err(LeafError::StaleControlFrame(_))
));
let slight = phone_control_frame_at(
&phone,
&device_address,
format!("{}{{}}", prefixes::SESSION_CONFIRM_PROBE),
now() + 30,
);
assert!(device.handle(&slight, now()).is_ok());
}
#[test]
fn a_frame_under_the_older_payload_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let mut frame = Message::new(
UserId::new(&phone.address).expect("phone address"),
UserId::new(&device_address).expect("device address"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
format!("{}{{}}", prefixes::SESSION_CONFIRM_PROBE),
);
frame.timestamp = offline_protocol_core::Timestamp::from_millis((now() as i64) * 1000);
let payload = offline_protocol_sealed::control_signing_payload(&frame).expect("v1 payload");
let signature = phone.manager.sign_data(&payload).expect("phone signs");
let public = phone
.manager
.get_identity_public_key()
.expect("phone public key");
frame.metadata.insert(
offline_protocol_sealed::CTRL_SIG_META_KEY.to_string(),
BASE64.encode(&signature),
);
frame.metadata.insert(
offline_protocol_sealed::CTRL_PK_META_KEY.to_string(),
BASE64.encode(&public),
);
let refused = device.handle(&frame, now());
assert!(
matches!(refused, Err(LeafError::ControlFrameRefused(_))),
"a frame under the older payload must not verify here, got {refused:?}"
);
}
#[test]
fn a_reset_older_than_the_last_one_acted_on_is_refused() {
let phone = new_phone();
let mut device = device(Arc::new(MemoryStore::new()));
let device_address = pair(&phone, &mut device);
let reset_frame_at = |at: u64| {
let package = phone
.manager
.take_push_key_package(&device_address)
.expect("phone mints a package");
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: true,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
phone_control_frame_at(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
at,
)
};
let older = reset_frame_at(now() - 3600);
let current = reset_frame_at(now());
let acted = device.handle(¤t, now()).expect("device resets");
assert!(acted.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}));
let replayed = device
.handle(&older, now())
.expect("the older one is handled");
assert!(
!replayed.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}),
"a reset predating the last one acted on must not be spendable: {:?}",
replayed.events
);
}
#[test]
fn a_spent_reset_stays_spent_across_a_power_cycle() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(store.clone());
let device_address = pair(&phone, &mut device);
let package = phone
.manager
.take_push_key_package(&device_address)
.expect("phone mints a package");
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: package.bundle.key_package_data,
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: true,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let reset_frame = phone_control_frame_at(
&phone,
&device_address,
format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
),
now(),
);
let acted = device.handle(&reset_frame, now()).expect("device resets");
assert!(acted.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}));
drop(device);
let mut rebooted = crate::device(store);
let replayed = rebooted
.handle(&reset_frame, now())
.expect("the replay is handled");
assert!(
!replayed.events.contains(&LeafEvent::SessionReset {
peer: phone.address.clone(),
}),
"a reboot must not make a spent reset spendable again: {:?}",
replayed.events
);
}
fn sign_as_undated(phone: &Phone, message: &mut Message) {
let payload =
offline_protocol_sealed::control_signing_payload(message).expect("canonical payload");
let signature = phone.manager.sign_data(&payload).expect("phone signs");
let public = phone
.manager
.get_identity_public_key()
.expect("phone public key");
message.metadata.insert(
offline_protocol_sealed::CTRL_SIG_META_KEY.to_string(),
BASE64.encode(&signature),
);
message.metadata.insert(
offline_protocol_sealed::CTRL_PK_META_KEY.to_string(),
BASE64.encode(&public),
);
}
fn phone_undated_frame(phone: &Phone, to: &str, content: String) -> Message {
let mut message = Message::new(
UserId::new(&phone.address).expect("phone address is a user id"),
UserId::new(to).expect("device address is a user id"),
offline_protocol_core::AppId::new(APP_ID).expect("app id"),
content,
);
message.priority = MessagePriority::High;
message.timestamp = offline_protocol_core::Timestamp::from_millis((now() as i64) * 1000);
sign_as_undated(phone, &mut message);
message
}
#[test]
fn a_key_package_that_states_no_time_is_admitted_because_first_contact_has_none() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = device.address().to_string();
let frame = phone_undated_frame(&phone, &device_address, advertisement_body(&phone.address));
let handled = device
.handle(&frame, now())
.expect("a device that refuses this can never be paired with by a phone");
assert!(
handled.events.iter().any(
|event| matches!(event, LeafEvent::PeerAdvertised { peer } if *peer == phone.address)
),
"the device did not record the phone that just introduced itself: {:?}",
handled.events
);
assert_eq!(
spoken(&handled).len(),
1,
"the device must answer first contact with a key package of its own"
);
assert!(
spoken(&handled)[0]
.content
.starts_with(prefixes::KEY_PACKAGE),
"the answer to a key package is a key package"
);
}
#[test]
fn a_session_reset_that_states_no_time_is_refused_its_teardown() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = pair(&phone, &mut device);
assert!(
device.has_session(&phone.address).expect("session"),
"the pair has to be up for a teardown to be worth refusing"
);
let payload = KeyPackagePayload {
user_id: phone.address.clone(),
key_package_data: vec![7],
remaining_lifetime_ms: 0,
timestamp_ms: 0,
session_reset: true,
wire_versions: vec![],
env_versions: vec![MLS_ENVELOPE_COMPACT_V1],
rich_versions: vec![],
data_versions: vec![],
ctrl_versions: vec![offline_protocol_sealed::CTRL_SIGN_V2],
svc_versions: Vec::new(),
ack_versions: Vec::new(),
nostr_pubkey: None,
};
let content = format!(
"{}{}",
prefixes::KEY_PACKAGE,
serde_json::to_string(&payload).expect("payload")
);
let frame = phone_undated_frame(&phone, &device_address, content);
let handled = device.handle(&frame, now()).expect("the frame is admitted");
assert!(
!handled
.events
.iter()
.any(|event| matches!(event, LeafEvent::SessionReset { .. })),
"a reset on a frame whose age cannot be judged tore the session down: {:?}",
handled.events
);
assert!(
device.has_session(&phone.address).expect("session"),
"the session is gone, so the teardown happened after all"
);
assert!(
handled
.events
.iter()
.any(|event| matches!(event, LeafEvent::PeerAdvertised { .. })),
"the frame's capability half must still be acted on: {:?}",
handled.events
);
}
#[test]
fn a_welcome_that_states_no_time_is_refused() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = device.address().to_string();
let advertisement = device
.key_package_frame(&phone.address, now())
.expect("device mints");
import_device_key_package(&phone, &advertisement);
let welcome = phone
.manager
.create_session(&device_address)
.expect("phone creates the session");
let content = format!(
"{}{}",
prefixes::WELCOME,
serde_json::to_string(&welcome).expect("welcome body")
);
let frame = phone_undated_frame(&phone, &device_address, content);
let refusal = device.handle(&frame, now());
assert!(
matches!(refusal, Err(LeafError::ControlFrameRefused(_))),
"a welcome that states no time was admitted: {refusal:?}"
);
assert!(
!device.has_session(&phone.address).expect("session"),
"the device joined from a welcome it should have refused"
);
}
#[test]
fn a_probe_that_states_no_time_is_refused() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = pair(&phone, &mut device);
let frame = phone_undated_frame(
&phone,
&device_address,
prefixes::SESSION_CONFIRM_PROBE.to_string(),
);
let refusal = device.handle(&frame, now());
assert!(
matches!(refusal, Err(LeafError::ControlFrameRefused(_))),
"a probe that states no time was admitted: {refusal:?}"
);
}
#[test]
fn a_stale_key_package_is_still_refused_rather_than_retried_undated() {
let phone = new_phone();
let store: Arc<dyn LeafStore> = Arc::new(MemoryStore::new());
let mut device = device(Arc::clone(&store));
let device_address = device.address().to_string();
let stale_at = now() - (LEAF_CTRL_FRESHNESS_PAST_MS / 1000) - 60;
let frame = phone_control_frame_at(
&phone,
&device_address,
advertisement_body(&phone.address),
stale_at,
);
let refusal = device.handle(&frame, now());
assert!(
matches!(refusal, Err(LeafError::StaleControlFrame(_))),
"a stale frame under the freshness-bound payload must stay refused for \
its age, not fall through to the payload that states none: {refusal:?}"
);
}