style: format core with rustfmt
This commit is contained in:
parent
447be4b38d
commit
8872ec6c98
12 changed files with 186 additions and 81 deletions
|
|
@ -274,7 +274,9 @@ mod tests {
|
||||||
// Both halves must sign: flipping a signature byte breaks it.
|
// Both halves must sign: flipping a signature byte breaks it.
|
||||||
let mut broken = cert;
|
let mut broken = cert;
|
||||||
broken[72] ^= 1;
|
broken[72] ^= 1;
|
||||||
assert!(!RotationCert::from_bytes(&broken).unwrap().verify(&rc.username));
|
assert!(!RotationCert::from_bytes(&broken)
|
||||||
|
.unwrap()
|
||||||
|
.verify(&rc.username));
|
||||||
assert!(RotationCert::from_bytes(&cert[..199]).is_err());
|
assert!(RotationCert::from_bytes(&cert[..199]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,7 @@ impl Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_key(text: &str, key: &str) -> Result<[u8; KEY_LEN], Error> {
|
fn decode_key(text: &str, key: &str) -> Result<[u8; KEY_LEN], Error> {
|
||||||
let identity =
|
let identity = unb32(key).map_err(|e| Error::Other(format!("{text}: undecodable key: {e}")))?;
|
||||||
unb32(key).map_err(|e| Error::Other(format!("{text}: undecodable key: {e}")))?;
|
|
||||||
identity.try_into().map_err(|v: Vec<u8>| {
|
identity.try_into().map_err(|v: Vec<u8>| {
|
||||||
Error::Other(format!(
|
Error::Other(format!(
|
||||||
"{text}: key is {} bytes, expected {KEY_LEN}",
|
"{text}: key is {} bytes, expected {KEY_LEN}",
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,7 @@ impl Session {
|
||||||
/// the hint rides along this way rather than in the stored row.
|
/// the hint rides along this way rather than in the stored row.
|
||||||
#[cfg(feature = "store")]
|
#[cfg(feature = "store")]
|
||||||
fn account_with_dial(store: &Store, dial: Option<&str>) -> Result<Address, Error> {
|
fn account_with_dial(store: &Store, dial: Option<&str>) -> Result<Address, Error> {
|
||||||
let addr = store
|
let addr = store.account()?.ok_or(Error::NotRegistered)?;
|
||||||
.account()?
|
|
||||||
.ok_or(Error::NotRegistered)?;
|
|
||||||
Ok(match dial {
|
Ok(match dial {
|
||||||
Some(dial) => addr.with_dial(dial),
|
Some(dial) => addr.with_dial(dial),
|
||||||
None => addr,
|
None => addr,
|
||||||
|
|
@ -140,7 +138,8 @@ pub fn connect(
|
||||||
{
|
{
|
||||||
let _ = (addr, require_pin);
|
let _ = (addr, require_pin);
|
||||||
Err(Error::Other(
|
Err(Error::Other(
|
||||||
"smol+rns:// addresses need the rns feature; rebuild with --features rns".into(),
|
"smol+rns:// addresses need the rns feature; rebuild with --features rns"
|
||||||
|
.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -437,7 +436,12 @@ pub fn rotate(
|
||||||
/// `connect` + `resolve`, `find_rotation_index`, then the four `set_*`
|
/// `connect` + `resolve`, `find_rotation_index`, then the four `set_*`
|
||||||
/// calls below.
|
/// calls below.
|
||||||
#[cfg(feature = "store")]
|
#[cfg(feature = "store")]
|
||||||
pub fn restore(store: &Store, master: &[u8; KEY_LEN], addr: &Address, timeout: u64) -> Result<u32, Error> {
|
pub fn restore(
|
||||||
|
store: &Store,
|
||||||
|
master: &[u8; KEY_LEN],
|
||||||
|
addr: &Address,
|
||||||
|
timeout: u64,
|
||||||
|
) -> Result<u32, Error> {
|
||||||
let mut session = connect(store, addr, false, timeout)?;
|
let mut session = connect(store, addr, false, timeout)?;
|
||||||
let (identity, _) = resolve(session.transport(), &addr.user)?;
|
let (identity, _) = resolve(session.transport(), &addr.user)?;
|
||||||
session.close();
|
session.close();
|
||||||
|
|
@ -671,7 +675,10 @@ pub fn fetch_with(
|
||||||
acknowledged: !opts.keep,
|
acknowledged: !opts.keep,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
};
|
};
|
||||||
let cancelled = || opts.cancel.is_some_and(|c| c.load(std::sync::atomic::Ordering::Relaxed));
|
let cancelled = || {
|
||||||
|
opts.cancel
|
||||||
|
.is_some_and(|c| c.load(std::sync::atomic::Ordering::Relaxed))
|
||||||
|
};
|
||||||
let mut session = connect(store, &addr, true, opts.timeout)?;
|
let mut session = connect(store, &addr, true, opts.timeout)?;
|
||||||
let transport = session.transport();
|
let transport = session.transport();
|
||||||
authenticate(transport, &addr.user, account, store)?;
|
authenticate(transport, &addr.user, account, store)?;
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,9 @@ pub fn ed25519_to_x25519(identity: &[u8; KEY_LEN]) -> Result<[u8; KEY_LEN], Erro
|
||||||
pub fn agree(secret: &StaticSecret, peer: &[u8; KEY_LEN]) -> Result<[u8; KEY_LEN], Error> {
|
pub fn agree(secret: &StaticSecret, peer: &[u8; KEY_LEN]) -> Result<[u8; KEY_LEN], Error> {
|
||||||
let shared = secret.diffie_hellman(&PublicKey::from(*peer));
|
let shared = secret.diffie_hellman(&PublicKey::from(*peer));
|
||||||
if shared.as_bytes().iter().all(|&b| b == 0) {
|
if shared.as_bytes().iter().all(|&b| b == 0) {
|
||||||
return Err(Error::Other("rejected all-zero key agreement output".into()));
|
return Err(Error::Other(
|
||||||
|
"rejected all-zero key agreement output".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(*shared.as_bytes())
|
Ok(*shared.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +145,12 @@ mod tests {
|
||||||
fn hkdf_matches_rfc5869_case_1() {
|
fn hkdf_matches_rfc5869_case_1() {
|
||||||
let v = crate::vectors::load();
|
let v = crate::vectors::load();
|
||||||
let hk = &v.hkdf_rfc5869_case1;
|
let hk = &v.hkdf_rfc5869_case1;
|
||||||
let okm = hkdf_sha256(&v.hex(&hk.ikm_hex), &v.hex(&hk.salt_hex), &v.hex(&hk.info_hex), 42);
|
let okm = hkdf_sha256(
|
||||||
|
&v.hex(&hk.ikm_hex),
|
||||||
|
&v.hex(&hk.salt_hex),
|
||||||
|
&v.hex(&hk.info_hex),
|
||||||
|
42,
|
||||||
|
);
|
||||||
assert_eq!(data_encoding::HEXLOWER.encode(&okm[..32]), hk.okm_hex_32);
|
assert_eq!(data_encoding::HEXLOWER.encode(&okm[..32]), hk.okm_hex_32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +173,10 @@ mod tests {
|
||||||
let pk0 = *ed25519_dalek::SigningKey::from_bytes(&seed0)
|
let pk0 = *ed25519_dalek::SigningKey::from_bytes(&seed0)
|
||||||
.verifying_key()
|
.verifying_key()
|
||||||
.as_bytes();
|
.as_bytes();
|
||||||
assert_eq!(b32(&ed25519_to_x25519(&pk0).unwrap()), v.ed25519_to_x25519_pk0_b32);
|
assert_eq!(
|
||||||
|
b32(&ed25519_to_x25519(&pk0).unwrap()),
|
||||||
|
v.ed25519_to_x25519_pk0_b32
|
||||||
|
);
|
||||||
// 32 zero bytes do decode to a curve point, but a low-order one;
|
// 32 zero bytes do decode to a curve point, but a low-order one;
|
||||||
// sec 2's rejection happens at agreement time, where the all-zero
|
// sec 2's rejection happens at agreement time, where the all-zero
|
||||||
// output surfaces.
|
// output surfaces.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ pub enum Error {
|
||||||
/// The host has no pinned server key, so the session cannot be
|
/// The host has no pinned server key, so the session cannot be
|
||||||
/// authenticated (sec 4). A GUI routes this to pinning; an auth failure
|
/// authenticated (sec 4). A GUI routes this to pinning; an auth failure
|
||||||
/// is terminal. The port scopes the pin (address::trust_label).
|
/// is terminal. The port scopes the pin (address::trust_label).
|
||||||
NotPinned { host: String, port: u16 },
|
NotPinned {
|
||||||
|
host: String,
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
/// A pinned server presented a different key (sec 4): a hard abort, the
|
/// A pinned server presented a different key (sec 4): a hard abort, the
|
||||||
/// pin is the entire trust model. The port scopes the pin
|
/// pin is the entire trust model. The port scopes the pin
|
||||||
/// (address::trust_label).
|
/// (address::trust_label).
|
||||||
|
|
@ -47,10 +50,15 @@ pub enum Error {
|
||||||
/// The store has no account yet; registering or restoring creates one.
|
/// The store has no account yet; registering or restoring creates one.
|
||||||
NotRegistered,
|
NotRegistered,
|
||||||
/// The rotation index would exceed the chain limit (sec 2).
|
/// The rotation index would exceed the chain limit (sec 2).
|
||||||
ChainLimit { index: u32 },
|
ChainLimit {
|
||||||
|
index: u32,
|
||||||
|
},
|
||||||
/// The store was written by a schema this build cannot read; a host
|
/// The store was written by a schema this build cannot read; a host
|
||||||
/// decides when to migrate, not the library.
|
/// decides when to migrate, not the library.
|
||||||
SchemaVersion { found: i32, expected: i32 },
|
SchemaVersion {
|
||||||
|
found: i32,
|
||||||
|
expected: i32,
|
||||||
|
},
|
||||||
/// The server could not be reached at all.
|
/// The server could not be reached at all.
|
||||||
Unreachable {
|
Unreachable {
|
||||||
host: String,
|
host: String,
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,14 @@ pub fn export(store: &Store, master: &[u8; KEY_LEN]) -> Result<String, Error> {
|
||||||
until,
|
until,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
contacts.insert(address, ContactExport { key: b32(&key), verified, history });
|
contacts.insert(
|
||||||
|
address,
|
||||||
|
ContactExport {
|
||||||
|
key: b32(&key),
|
||||||
|
verified,
|
||||||
|
history,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each folder listing carries its own tier, so rows round-trip into the
|
// Each folder listing carries its own tier, so rows round-trip into the
|
||||||
|
|
@ -180,7 +187,12 @@ pub fn export(store: &Store, master: &[u8; KEY_LEN]) -> Result<String, Error> {
|
||||||
.mail("inbox")?
|
.mail("inbox")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|row| inbox_row(row, TIER_MAIN))
|
.map(|row| inbox_row(row, TIER_MAIN))
|
||||||
.chain(store.mail("requests")?.into_iter().map(|row| inbox_row(row, TIER_REQUESTS)))
|
.chain(
|
||||||
|
store
|
||||||
|
.mail("requests")?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| inbox_row(row, TIER_REQUESTS)),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let sent = store
|
let sent = store
|
||||||
.mail("sent")?
|
.mail("sent")?
|
||||||
|
|
@ -227,8 +239,8 @@ fn inbox_row(row: crate::store::Stored, tier: u8) -> InboxRow {
|
||||||
/// fetch does not re-store it; pins and contacts merge without clobbering;
|
/// fetch does not re-store it; pins and contacts merge without clobbering;
|
||||||
/// one malformed record is skipped and counted, never fatal.
|
/// one malformed record is skipped and counted, never fatal.
|
||||||
pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<ImportSummary, Error> {
|
pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<ImportSummary, Error> {
|
||||||
let container: Container = serde_json::from_str(text)
|
let container: Container =
|
||||||
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
serde_json::from_str(text).map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||||
if container.gsmol_export != 2 {
|
if container.gsmol_export != 2 {
|
||||||
return Err(Error::Other("not a gsmol export file".into()));
|
return Err(Error::Other("not a gsmol export file".into()));
|
||||||
}
|
}
|
||||||
|
|
@ -238,9 +250,10 @@ pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<Impor
|
||||||
let sealed = data_encoding::BASE64
|
let sealed = data_encoding::BASE64
|
||||||
.decode(container.ciphertext.as_bytes())
|
.decode(container.ciphertext.as_bytes())
|
||||||
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||||
let plaintext = open(&export_key(master), &nonce, &sealed)
|
let plaintext = open(&export_key(master), &nonce, &sealed).ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
Error::Other(
|
||||||
Error::Other("couldn't decrypt — exported by a different identity, or the file is corrupted".into())
|
"couldn't decrypt — exported by a different identity, or the file is corrupted".into(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
let payload: Bundle = serde_json::from_slice(&plaintext)
|
let payload: Bundle = serde_json::from_slice(&plaintext)
|
||||||
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||||
|
|
@ -273,8 +286,7 @@ pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<Impor
|
||||||
}
|
}
|
||||||
|
|
||||||
for (address, contact) in &payload.contacts {
|
for (address, contact) in &payload.contacts {
|
||||||
let key: Option<[u8; KEY_LEN]> =
|
let key: Option<[u8; KEY_LEN]> = unb32(&contact.key).ok().and_then(|k| k.try_into().ok());
|
||||||
unb32(&contact.key).ok().and_then(|k| k.try_into().ok());
|
|
||||||
match key {
|
match key {
|
||||||
None => summary.malformed += 1,
|
None => summary.malformed += 1,
|
||||||
Some(key) => match store.contact(address)? {
|
Some(key) => match store.contact(address)? {
|
||||||
|
|
@ -347,7 +359,9 @@ mod tests {
|
||||||
fn seeded_store() -> (Store, [u8; KEY_LEN]) {
|
fn seeded_store() -> (Store, [u8; KEY_LEN]) {
|
||||||
let store = Store::open_in_memory().unwrap();
|
let store = Store::open_in_memory().unwrap();
|
||||||
let master = [7u8; KEY_LEN];
|
let master = [7u8; KEY_LEN];
|
||||||
store.pin_server("example.org", crate::address::DEFAULT_PORT, &[1u8; KEY_LEN]).unwrap();
|
store
|
||||||
|
.pin_server("example.org", crate::address::DEFAULT_PORT, &[1u8; KEY_LEN])
|
||||||
|
.unwrap();
|
||||||
store
|
store
|
||||||
.save_contact("alice@example.org", &[2u8; KEY_LEN], true)
|
.save_contact("alice@example.org", &[2u8; KEY_LEN], true)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -360,7 +374,9 @@ mod tests {
|
||||||
store
|
store
|
||||||
.store_inbox(&[4u8; ID_LEN], b"request", 43, true, false)
|
.store_inbox(&[4u8; ID_LEN], b"request", 43, true, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
store.store_sent(&[5u8; ID_LEN], "bob@example.org", b"sent", 44).unwrap();
|
store
|
||||||
|
.store_sent(&[5u8; ID_LEN], "bob@example.org", b"sent", 44)
|
||||||
|
.unwrap();
|
||||||
(store, master)
|
(store, master)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,11 +392,19 @@ mod tests {
|
||||||
assert_eq!(summary.contacts_added, 1);
|
assert_eq!(summary.contacts_added, 1);
|
||||||
assert_eq!(summary.malformed, 0);
|
assert_eq!(summary.malformed, 0);
|
||||||
|
|
||||||
assert_eq!(fresh.server_pin("example.org", crate::address::DEFAULT_PORT).unwrap(), Some([1u8; KEY_LEN]));
|
assert_eq!(
|
||||||
|
fresh
|
||||||
|
.server_pin("example.org", crate::address::DEFAULT_PORT)
|
||||||
|
.unwrap(),
|
||||||
|
Some([1u8; KEY_LEN])
|
||||||
|
);
|
||||||
let (key, verified) = fresh.contact("alice@example.org").unwrap().unwrap();
|
let (key, verified) = fresh.contact("alice@example.org").unwrap().unwrap();
|
||||||
assert_eq!(key, [2u8; KEY_LEN]);
|
assert_eq!(key, [2u8; KEY_LEN]);
|
||||||
assert!(verified);
|
assert!(verified);
|
||||||
assert_eq!(fresh.history("alice@example.org").unwrap(), [([9u8; KEY_LEN], 500)]);
|
assert_eq!(
|
||||||
|
fresh.history("alice@example.org").unwrap(),
|
||||||
|
[([9u8; KEY_LEN], 500)]
|
||||||
|
);
|
||||||
assert_eq!(fresh.mail("inbox").unwrap().len(), 1);
|
assert_eq!(fresh.mail("inbox").unwrap().len(), 1);
|
||||||
assert_eq!(fresh.mail("requests").unwrap().len(), 1);
|
assert_eq!(fresh.mail("requests").unwrap().len(), 1);
|
||||||
assert_eq!(fresh.mail("inbox").unwrap()[0].kept, true);
|
assert_eq!(fresh.mail("inbox").unwrap()[0].kept, true);
|
||||||
|
|
@ -405,13 +429,18 @@ mod tests {
|
||||||
let file = export(&store, &master).unwrap();
|
let file = export(&store, &master).unwrap();
|
||||||
|
|
||||||
let mine = Store::open_in_memory().unwrap();
|
let mine = Store::open_in_memory().unwrap();
|
||||||
mine.pin_server("example.org", crate::address::DEFAULT_PORT, &[6u8; KEY_LEN]).unwrap();
|
mine.pin_server("example.org", crate::address::DEFAULT_PORT, &[6u8; KEY_LEN])
|
||||||
|
.unwrap();
|
||||||
mine.save_contact("alice@example.org", &[7u8; KEY_LEN], false)
|
mine.save_contact("alice@example.org", &[7u8; KEY_LEN], false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let summary = import(&mine, &master, &file).unwrap();
|
let summary = import(&mine, &master, &file).unwrap();
|
||||||
assert_eq!(summary.pins_conflicted, 1);
|
assert_eq!(summary.pins_conflicted, 1);
|
||||||
assert_eq!(summary.contacts_conflicted, 1);
|
assert_eq!(summary.contacts_conflicted, 1);
|
||||||
assert_eq!(mine.server_pin("example.org", crate::address::DEFAULT_PORT).unwrap(), Some([6u8; KEY_LEN]));
|
assert_eq!(
|
||||||
|
mine.server_pin("example.org", crate::address::DEFAULT_PORT)
|
||||||
|
.unwrap(),
|
||||||
|
Some([6u8; KEY_LEN])
|
||||||
|
);
|
||||||
let (key, _) = mine.contact("alice@example.org").unwrap().unwrap();
|
let (key, _) = mine.contact("alice@example.org").unwrap().unwrap();
|
||||||
assert_eq!(key, [7u8; KEY_LEN]);
|
assert_eq!(key, [7u8; KEY_LEN]);
|
||||||
}
|
}
|
||||||
|
|
@ -423,12 +452,18 @@ mod tests {
|
||||||
// Corrupt one inbox row's id inside the payload by re-sealing a
|
// Corrupt one inbox row's id inside the payload by re-sealing a
|
||||||
// hand-edited payload with the same key.
|
// hand-edited payload with the same key.
|
||||||
let container: Container = serde_json::from_str(&file).unwrap();
|
let container: Container = serde_json::from_str(&file).unwrap();
|
||||||
let nonce = data_encoding::BASE64.decode(container.nonce.as_bytes()).unwrap();
|
let nonce = data_encoding::BASE64
|
||||||
let sealed = data_encoding::BASE64.decode(container.ciphertext.as_bytes()).unwrap();
|
.decode(container.nonce.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
let sealed = data_encoding::BASE64
|
||||||
|
.decode(container.ciphertext.as_bytes())
|
||||||
|
.unwrap();
|
||||||
let plaintext = open(&export_key(&master), &nonce, &sealed).unwrap();
|
let plaintext = open(&export_key(&master), &nonce, &sealed).unwrap();
|
||||||
let mut payload: Bundle = serde_json::from_slice(&plaintext).unwrap();
|
let mut payload: Bundle = serde_json::from_slice(&plaintext).unwrap();
|
||||||
payload.inbox[0].id = "zz".to_string();
|
payload.inbox[0].id = "zz".to_string();
|
||||||
payload.servers.insert("bad".to_string(), "not-base32!".to_string());
|
payload
|
||||||
|
.servers
|
||||||
|
.insert("bad".to_string(), "not-base32!".to_string());
|
||||||
let (nonce, sealed) = seal(&export_key(&master), &serde_json::to_vec(&payload).unwrap());
|
let (nonce, sealed) = seal(&export_key(&master), &serde_json::to_vec(&payload).unwrap());
|
||||||
let file = serde_json::to_string(&Container {
|
let file = serde_json::to_string(&Container {
|
||||||
gsmol_export: 2,
|
gsmol_export: 2,
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,3 @@ pub mod tcp;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod vectors;
|
mod vectors;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,9 @@ pub fn unseal(identities: &[Identity], envelope: &[u8], now: i64) -> Result<Open
|
||||||
let when = i64::from_be_bytes(plaintext[33..41].try_into().unwrap());
|
let when = i64::from_be_bytes(plaintext[33..41].try_into().unwrap());
|
||||||
let body_len = u32::from_be_bytes(plaintext[41..45].try_into().unwrap()) as usize;
|
let body_len = u32::from_be_bytes(plaintext[41..45].try_into().unwrap()) as usize;
|
||||||
if PAYLOAD_HEADER + body_len + SIG_LEN > plaintext.len() {
|
if PAYLOAD_HEADER + body_len + SIG_LEN > plaintext.len() {
|
||||||
return Err(Error::Other("payload body length exceeds the payload".into()));
|
return Err(Error::Other(
|
||||||
|
"payload body length exceeds the payload".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let body = &plaintext[PAYLOAD_HEADER..PAYLOAD_HEADER + body_len];
|
let body = &plaintext[PAYLOAD_HEADER..PAYLOAD_HEADER + body_len];
|
||||||
let signature = &plaintext[PAYLOAD_HEADER + body_len..PAYLOAD_HEADER + body_len + SIG_LEN];
|
let signature = &plaintext[PAYLOAD_HEADER + body_len..PAYLOAD_HEADER + body_len + SIG_LEN];
|
||||||
|
|
@ -276,7 +278,6 @@ mod tests {
|
||||||
core::array::from_fn(|i| i as u8)
|
core::array::from_fn(|i| i as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn identity(n: u32) -> Identity {
|
fn identity(n: u32) -> Identity {
|
||||||
Identity::from_seed(identity_seed(&master(), n))
|
Identity::from_seed(identity_seed(&master(), n))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,7 @@ pub fn connect(destination: &[u8; 16], timeout: Duration) -> Result<[u8; 16], Er
|
||||||
-2 => Err(Error::Other(format!(
|
-2 => Err(Error::Other(format!(
|
||||||
"path known but identity not yet learned for {hex}; try again shortly"
|
"path known but identity not yet learned for {hex}; try again shortly"
|
||||||
))),
|
))),
|
||||||
-3 => Err(Error::Other(format!(
|
-3 => Err(Error::Other(format!("could not establish a link to {hex}"))),
|
||||||
"could not establish a link to {hex}"
|
|
||||||
))),
|
|
||||||
_ => Err(Error::Other(format!(
|
_ => Err(Error::Other(format!(
|
||||||
"RNS connect to {hex} failed (code {rc})"
|
"RNS connect to {hex} failed (code {rc})"
|
||||||
))),
|
))),
|
||||||
|
|
@ -133,10 +131,13 @@ pub fn request(request: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
-1 => Err(Error::Other("no RNS link is open".into())),
|
-1 => Err(Error::Other("no RNS link is open".into())),
|
||||||
-2 => Err(Error::Other("failed to send the request over the link".into())),
|
-2 => Err(Error::Other(
|
||||||
|
"failed to send the request over the link".into(),
|
||||||
|
)),
|
||||||
-3 | -4 => Err(Error::Other(
|
-3 | -4 => Err(Error::Other(
|
||||||
"request failed: no response (closed link, rejected transfer, or timeout \
|
"request failed: no response (closed link, rejected transfer, or timeout \
|
||||||
-- not a status code, upstream spec 13.5)".into(),
|
-- not a status code, upstream spec 13.5)"
|
||||||
|
.into(),
|
||||||
)),
|
)),
|
||||||
-5 => Err(Error::Other("malformed response from server".into())),
|
-5 => Err(Error::Other("malformed response from server".into())),
|
||||||
-6 => Err(Error::Other(
|
-6 => Err(Error::Other(
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,7 @@ impl Store {
|
||||||
expected: SCHEMA_VERSION,
|
expected: SCHEMA_VERSION,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(Store {
|
Ok(Store { db: Mutex::new(db) })
|
||||||
db: Mutex::new(db),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One lock acquisition; the guard's lifetime is the operation's.
|
/// One lock acquisition; the guard's lifetime is the operation's.
|
||||||
|
|
@ -315,12 +313,7 @@ impl Store {
|
||||||
/// Pins a server key, scoped by port (address::trust_label): the default
|
/// Pins a server key, scoped by port (address::trust_label): the default
|
||||||
/// port keeps the bare-host row existing stores already hold, and any
|
/// port keeps the bare-host row existing stores already hold, and any
|
||||||
/// other port earns its own row.
|
/// other port earns its own row.
|
||||||
pub fn pin_server(
|
pub fn pin_server(&self, host: &str, port: u16, key: &[u8; KEY_LEN]) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
host: &str,
|
|
||||||
port: u16,
|
|
||||||
key: &[u8; KEY_LEN],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.db()
|
self.db()
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO servers (host, static, pinned_at) VALUES (?1, ?2, ?3) \
|
"INSERT INTO servers (host, static, pinned_at) VALUES (?1, ?2, ?3) \
|
||||||
|
|
@ -597,10 +590,7 @@ impl Store {
|
||||||
_ => "inbox",
|
_ => "inbox",
|
||||||
};
|
};
|
||||||
Ok(self
|
Ok(self
|
||||||
.one::<i64>(
|
.one::<i64>(&format!("SELECT 1 FROM {table} WHERE id = ?1"), &[id])?
|
||||||
&format!("SELECT 1 FROM {table} WHERE id = ?1"),
|
|
||||||
&[id],
|
|
||||||
)?
|
|
||||||
.is_some())
|
.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -616,8 +606,10 @@ impl Store {
|
||||||
};
|
};
|
||||||
self.db()
|
self.db()
|
||||||
.execute(&format!("DELETE FROM {table} WHERE id = ?1"), params![id])?;
|
.execute(&format!("DELETE FROM {table} WHERE id = ?1"), params![id])?;
|
||||||
self.db()
|
self.db().execute(
|
||||||
.execute("INSERT OR IGNORE INTO seen (id, at) VALUES (?1, ?2)", params![id, now()])?;
|
"INSERT OR IGNORE INTO seen (id, at) VALUES (?1, ?2)",
|
||||||
|
params![id, now()],
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -792,9 +784,13 @@ mod tests {
|
||||||
fn inbox_and_sent_store_sealed_and_deduplicate() {
|
fn inbox_and_sent_store_sealed_and_deduplicate() {
|
||||||
let store = temp_store("mail");
|
let store = temp_store("mail");
|
||||||
let id = [3u8; 32];
|
let id = [3u8; 32];
|
||||||
store.store_inbox(&id, b"envelope", 100, false, false).unwrap();
|
store
|
||||||
|
.store_inbox(&id, b"envelope", 100, false, false)
|
||||||
|
.unwrap();
|
||||||
// A replayed id is not re-stored (sec 10).
|
// A replayed id is not re-stored (sec 10).
|
||||||
store.store_inbox(&id, b"envelope2", 100, true, false).unwrap();
|
store
|
||||||
|
.store_inbox(&id, b"envelope2", 100, true, false)
|
||||||
|
.unwrap();
|
||||||
assert!(store.seen(&id).unwrap());
|
assert!(store.seen(&id).unwrap());
|
||||||
let inbox = store.mail("inbox").unwrap();
|
let inbox = store.mail("inbox").unwrap();
|
||||||
assert_eq!(inbox.len(), 1);
|
assert_eq!(inbox.len(), 1);
|
||||||
|
|
@ -812,11 +808,24 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn pins_and_contacts_round_trip() {
|
fn pins_and_contacts_round_trip() {
|
||||||
let store = temp_store("pins");
|
let store = temp_store("pins");
|
||||||
assert!(store.server_pin("example.org", DEFAULT_PORT).unwrap().is_none());
|
assert!(store
|
||||||
store.pin_server("example.org", DEFAULT_PORT, &[5u8; 32]).unwrap();
|
.server_pin("example.org", DEFAULT_PORT)
|
||||||
assert_eq!(store.server_pin("example.org", DEFAULT_PORT).unwrap(), Some([5u8; 32]));
|
.unwrap()
|
||||||
store.pin_server("example.org", DEFAULT_PORT, &[6u8; 32]).unwrap();
|
.is_none());
|
||||||
assert_eq!(store.server_pin("example.org", DEFAULT_PORT).unwrap(), Some([6u8; 32]));
|
store
|
||||||
|
.pin_server("example.org", DEFAULT_PORT, &[5u8; 32])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
store.server_pin("example.org", DEFAULT_PORT).unwrap(),
|
||||||
|
Some([5u8; 32])
|
||||||
|
);
|
||||||
|
store
|
||||||
|
.pin_server("example.org", DEFAULT_PORT, &[6u8; 32])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
store.server_pin("example.org", DEFAULT_PORT).unwrap(),
|
||||||
|
Some([6u8; 32])
|
||||||
|
);
|
||||||
|
|
||||||
let key = Account::new(master(), 1).unwrap().me().pk();
|
let key = Account::new(master(), 1).unwrap().me().pk();
|
||||||
assert!(store.contact("bob@example.org").unwrap().is_none());
|
assert!(store.contact("bob@example.org").unwrap().is_none());
|
||||||
|
|
@ -848,10 +857,13 @@ mod tests {
|
||||||
let path = scratch_path("schema");
|
let path = scratch_path("schema");
|
||||||
drop(Store::open(&path).expect("open store"));
|
drop(Store::open(&path).expect("open store"));
|
||||||
let db = Connection::open(&path).unwrap();
|
let db = Connection::open(&path).unwrap();
|
||||||
let version: i32 = db.query_row("PRAGMA user_version", [], |row| row.get(0)).unwrap();
|
let version: i32 = db
|
||||||
|
.query_row("PRAGMA user_version", [], |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(version, SCHEMA_VERSION);
|
assert_eq!(version, SCHEMA_VERSION);
|
||||||
// A store from the future is refused, not misread.
|
// A store from the future is refused, not misread.
|
||||||
db.pragma_update(None, "user_version", SCHEMA_VERSION + 1).unwrap();
|
db.pragma_update(None, "user_version", SCHEMA_VERSION + 1)
|
||||||
|
.unwrap();
|
||||||
drop(db);
|
drop(db);
|
||||||
match Store::open(&path) {
|
match Store::open(&path) {
|
||||||
Err(Error::SchemaVersion { found, expected }) => {
|
Err(Error::SchemaVersion { found, expected }) => {
|
||||||
|
|
@ -903,7 +915,9 @@ mod tests {
|
||||||
|
|
||||||
let store = Store::open(&path).expect("v1 store migrates");
|
let store = Store::open(&path).expect("v1 store migrates");
|
||||||
let db = Connection::open(&path).unwrap();
|
let db = Connection::open(&path).unwrap();
|
||||||
let version: i32 = db.query_row("PRAGMA user_version", [], |row| row.get(0)).unwrap();
|
let version: i32 = db
|
||||||
|
.query_row("PRAGMA user_version", [], |row| row.get(0))
|
||||||
|
.unwrap();
|
||||||
drop(db);
|
drop(db);
|
||||||
assert_eq!(version, SCHEMA_VERSION);
|
assert_eq!(version, SCHEMA_VERSION);
|
||||||
// Existing rows survive with the column's default.
|
// Existing rows survive with the column's default.
|
||||||
|
|
@ -911,8 +925,13 @@ mod tests {
|
||||||
assert_eq!(mail.len(), 1);
|
assert_eq!(mail.len(), 1);
|
||||||
assert!(!mail[0].kept);
|
assert!(!mail[0].kept);
|
||||||
// The history table exists and round-trips.
|
// The history table exists and round-trips.
|
||||||
store.save_history("alice@example.org", &[9u8; 32], 42).unwrap();
|
store
|
||||||
assert_eq!(store.history("alice@example.org").unwrap(), [([9u8; 32], 42)]);
|
.save_history("alice@example.org", &[9u8; 32], 42)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
store.history("alice@example.org").unwrap(),
|
||||||
|
[([9u8; 32], 42)]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -964,18 +983,24 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn unpin_returns_to_trust_on_first_use() {
|
fn unpin_returns_to_trust_on_first_use() {
|
||||||
let store = temp_store("unpin");
|
let store = temp_store("unpin");
|
||||||
store.pin_server("example.org", DEFAULT_PORT, &[1u8; KEY_LEN]).unwrap();
|
store
|
||||||
|
.pin_server("example.org", DEFAULT_PORT, &[1u8; KEY_LEN])
|
||||||
|
.unwrap();
|
||||||
store.unpin_server("example.org", DEFAULT_PORT).unwrap();
|
store.unpin_server("example.org", DEFAULT_PORT).unwrap();
|
||||||
assert_eq!(store.server_pin("example.org", DEFAULT_PORT).unwrap(), None);
|
assert_eq!(store.server_pin("example.org", DEFAULT_PORT).unwrap(), None);
|
||||||
// Unpinning what was never pinned is not an error.
|
// Unpinning what was never pinned is not an error.
|
||||||
store.unpin_server("never.example.org", DEFAULT_PORT).unwrap();
|
store
|
||||||
|
.unpin_server("never.example.org", DEFAULT_PORT)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kept_survives_the_folder_listing() {
|
fn kept_survives_the_folder_listing() {
|
||||||
let store = temp_store("kept");
|
let store = temp_store("kept");
|
||||||
store.store_inbox(&[1u8; 32], b"a", 1, false, true).unwrap();
|
store.store_inbox(&[1u8; 32], b"a", 1, false, true).unwrap();
|
||||||
store.store_inbox(&[2u8; 32], b"b", 2, false, false).unwrap();
|
store
|
||||||
|
.store_inbox(&[2u8; 32], b"b", 2, false, false)
|
||||||
|
.unwrap();
|
||||||
let all = store.mail("all").unwrap();
|
let all = store.mail("all").unwrap();
|
||||||
assert!(all[0].kept && !all[1].kept);
|
assert!(all[0].kept && !all[1].kept);
|
||||||
}
|
}
|
||||||
|
|
@ -998,7 +1023,9 @@ mod tests {
|
||||||
fn local_delete_marks_seen_so_a_refetch_does_not_restore() {
|
fn local_delete_marks_seen_so_a_refetch_does_not_restore() {
|
||||||
let store = temp_store("delete-local");
|
let store = temp_store("delete-local");
|
||||||
let id = [3u8; 32];
|
let id = [3u8; 32];
|
||||||
store.store_inbox(&id, b"envelope", 100, false, true).unwrap();
|
store
|
||||||
|
.store_inbox(&id, b"envelope", 100, false, true)
|
||||||
|
.unwrap();
|
||||||
store.delete_local("inbox", &id).unwrap();
|
store.delete_local("inbox", &id).unwrap();
|
||||||
assert!(store.mail("all").unwrap().is_empty());
|
assert!(store.mail("all").unwrap().is_empty());
|
||||||
// Sec 10: the id is seen, so the fetch pipeline will not re-store
|
// Sec 10: the id is seen, so the fetch pipeline will not re-store
|
||||||
|
|
@ -1007,7 +1034,9 @@ mod tests {
|
||||||
assert!(store.seen(&id).unwrap());
|
assert!(store.seen(&id).unwrap());
|
||||||
// Sent copies delete locally too, with the same protection.
|
// Sent copies delete locally too, with the same protection.
|
||||||
let sent_id = [4u8; 32];
|
let sent_id = [4u8; 32];
|
||||||
store.store_sent(&sent_id, "bob@example.org", b"sent", 50).unwrap();
|
store
|
||||||
|
.store_sent(&sent_id, "bob@example.org", b"sent", 50)
|
||||||
|
.unwrap();
|
||||||
store.delete_local("sent", &sent_id).unwrap();
|
store.delete_local("sent", &sent_id).unwrap();
|
||||||
assert!(store.mail("sent").unwrap().is_empty());
|
assert!(store.mail("sent").unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
@ -1021,7 +1050,9 @@ mod tests {
|
||||||
store.account().unwrap().unwrap().short(),
|
store.account().unwrap().unwrap().short(),
|
||||||
"alice@example.org"
|
"alice@example.org"
|
||||||
);
|
);
|
||||||
store.store_inbox(&[1u8; 32], b"envelope", 5, false, false).unwrap();
|
store
|
||||||
|
.store_inbox(&[1u8; 32], b"envelope", 5, false, false)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(store.mail("inbox").unwrap().len(), 1);
|
assert_eq!(store.mail("inbox").unwrap().len(), 1);
|
||||||
// The same handle through the path spelling.
|
// The same handle through the path spelling.
|
||||||
let path = std::path::Path::new(":memory:");
|
let path = std::path::Path::new(":memory:");
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,15 @@ fn handshake(
|
||||||
host: &str,
|
host: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
) -> Result<(TcpStream, TransportState, [u8; KEY_LEN], TransportBindValues), Error> {
|
) -> Result<
|
||||||
|
(
|
||||||
|
TcpStream,
|
||||||
|
TransportState,
|
||||||
|
[u8; KEY_LEN],
|
||||||
|
TransportBindValues,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
let mut stream = connect_timeout(host, port, timeout).map_err(|source| Error::Unreachable {
|
let mut stream = connect_timeout(host, port, timeout).map_err(|source| Error::Unreachable {
|
||||||
host: host.to_string(),
|
host: host.to_string(),
|
||||||
port,
|
port,
|
||||||
|
|
@ -219,7 +227,9 @@ impl Transport for TcpTransport {
|
||||||
fn request(&mut self, op: u8, body: &[u8]) -> Result<Response, Error> {
|
fn request(&mut self, op: u8, body: &[u8]) -> Result<Response, Error> {
|
||||||
let length = 1 + body.len();
|
let length = 1 + body.len();
|
||||||
if length > MAX_FRAME {
|
if length > MAX_FRAME {
|
||||||
return Err(Error::Other("request exceeds the maximum frame size".into()));
|
return Err(Error::Other(
|
||||||
|
"request exceeds the maximum frame size".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let mut frame = Vec::with_capacity(4 + length);
|
let mut frame = Vec::with_capacity(4 + length);
|
||||||
frame.extend_from_slice(&(length as u32).to_be_bytes());
|
frame.extend_from_slice(&(length as u32).to_be_bytes());
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,8 @@ impl Vectors {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hex(&self, hex: &str) -> Vec<u8> {
|
pub fn hex(&self, hex: &str) -> Vec<u8> {
|
||||||
data_encoding::HEXLOWER.decode(hex.as_bytes()).expect("hex decodes")
|
data_encoding::HEXLOWER
|
||||||
|
.decode(hex.as_bytes())
|
||||||
|
.expect("hex decodes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue