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.
|
||||
let mut broken = cert;
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -172,8 +172,7 @@ impl Address {
|
|||
}
|
||||
|
||||
fn decode_key(text: &str, key: &str) -> Result<[u8; KEY_LEN], Error> {
|
||||
let identity =
|
||||
unb32(key).map_err(|e| Error::Other(format!("{text}: undecodable key: {e}")))?;
|
||||
let identity = unb32(key).map_err(|e| Error::Other(format!("{text}: undecodable key: {e}")))?;
|
||||
identity.try_into().map_err(|v: Vec<u8>| {
|
||||
Error::Other(format!(
|
||||
"{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.
|
||||
#[cfg(feature = "store")]
|
||||
fn account_with_dial(store: &Store, dial: Option<&str>) -> Result<Address, Error> {
|
||||
let addr = store
|
||||
.account()?
|
||||
.ok_or(Error::NotRegistered)?;
|
||||
let addr = store.account()?.ok_or(Error::NotRegistered)?;
|
||||
Ok(match dial {
|
||||
Some(dial) => addr.with_dial(dial),
|
||||
None => addr,
|
||||
|
|
@ -140,8 +138,9 @@ pub fn connect(
|
|||
{
|
||||
let _ = (addr, require_pin);
|
||||
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_*`
|
||||
/// calls below.
|
||||
#[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 (identity, _) = resolve(session.transport(), &addr.user)?;
|
||||
session.close();
|
||||
|
|
@ -671,7 +675,10 @@ pub fn fetch_with(
|
|||
acknowledged: !opts.keep,
|
||||
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 transport = session.transport();
|
||||
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> {
|
||||
let shared = secret.diffie_hellman(&PublicKey::from(*peer));
|
||||
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())
|
||||
}
|
||||
|
|
@ -143,7 +145,12 @@ mod tests {
|
|||
fn hkdf_matches_rfc5869_case_1() {
|
||||
let v = crate::vectors::load();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +173,10 @@ mod tests {
|
|||
let pk0 = *ed25519_dalek::SigningKey::from_bytes(&seed0)
|
||||
.verifying_key()
|
||||
.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;
|
||||
// sec 2's rejection happens at agreement time, where the all-zero
|
||||
// output surfaces.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ pub enum Error {
|
|||
/// The host has no pinned server key, so the session cannot be
|
||||
/// authenticated (sec 4). A GUI routes this to pinning; an auth failure
|
||||
/// 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
|
||||
/// pin is the entire trust model. The port scopes the pin
|
||||
/// (address::trust_label).
|
||||
|
|
@ -47,10 +50,15 @@ pub enum Error {
|
|||
/// The store has no account yet; registering or restoring creates one.
|
||||
NotRegistered,
|
||||
/// 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
|
||||
/// 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.
|
||||
Unreachable {
|
||||
host: String,
|
||||
|
|
|
|||
|
|
@ -171,7 +171,14 @@ pub fn export(store: &Store, master: &[u8; KEY_LEN]) -> Result<String, Error> {
|
|||
until,
|
||||
})
|
||||
.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
|
||||
|
|
@ -180,7 +187,12 @@ pub fn export(store: &Store, master: &[u8; KEY_LEN]) -> Result<String, Error> {
|
|||
.mail("inbox")?
|
||||
.into_iter()
|
||||
.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<_>>();
|
||||
let sent = store
|
||||
.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;
|
||||
/// one malformed record is skipped and counted, never fatal.
|
||||
pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<ImportSummary, Error> {
|
||||
let container: Container = serde_json::from_str(text)
|
||||
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||
let container: Container =
|
||||
serde_json::from_str(text).map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||
if container.gsmol_export != 2 {
|
||||
return Err(Error::Other("not a gsmol export file".into()));
|
||||
}
|
||||
|
|
@ -238,10 +250,11 @@ pub fn import(store: &Store, master: &[u8; KEY_LEN], text: &str) -> Result<Impor
|
|||
let sealed = data_encoding::BASE64
|
||||
.decode(container.ciphertext.as_bytes())
|
||||
.map_err(|_| Error::Other("not a gsmol export file".into()))?;
|
||||
let plaintext = open(&export_key(master), &nonce, &sealed)
|
||||
.ok_or_else(|| {
|
||||
Error::Other("couldn't decrypt — exported by a different identity, or the file is corrupted".into())
|
||||
})?;
|
||||
let plaintext = open(&export_key(master), &nonce, &sealed).ok_or_else(|| {
|
||||
Error::Other(
|
||||
"couldn't decrypt — exported by a different identity, or the file is corrupted".into(),
|
||||
)
|
||||
})?;
|
||||
let payload: Bundle = serde_json::from_slice(&plaintext)
|
||||
.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 {
|
||||
let key: Option<[u8; KEY_LEN]> =
|
||||
unb32(&contact.key).ok().and_then(|k| k.try_into().ok());
|
||||
let key: Option<[u8; KEY_LEN]> = unb32(&contact.key).ok().and_then(|k| k.try_into().ok());
|
||||
match key {
|
||||
None => summary.malformed += 1,
|
||||
Some(key) => match store.contact(address)? {
|
||||
|
|
@ -347,7 +359,9 @@ mod tests {
|
|||
fn seeded_store() -> (Store, [u8; KEY_LEN]) {
|
||||
let store = Store::open_in_memory().unwrap();
|
||||
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
|
||||
.save_contact("alice@example.org", &[2u8; KEY_LEN], true)
|
||||
.unwrap();
|
||||
|
|
@ -360,7 +374,9 @@ mod tests {
|
|||
store
|
||||
.store_inbox(&[4u8; ID_LEN], b"request", 43, true, false)
|
||||
.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)
|
||||
}
|
||||
|
||||
|
|
@ -376,11 +392,19 @@ mod tests {
|
|||
assert_eq!(summary.contacts_added, 1);
|
||||
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();
|
||||
assert_eq!(key, [2u8; KEY_LEN]);
|
||||
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("requests").unwrap().len(), 1);
|
||||
assert_eq!(fresh.mail("inbox").unwrap()[0].kept, true);
|
||||
|
|
@ -405,13 +429,18 @@ mod tests {
|
|||
let file = export(&store, &master).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)
|
||||
.unwrap();
|
||||
let summary = import(&mine, &master, &file).unwrap();
|
||||
assert_eq!(summary.pins_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();
|
||||
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
|
||||
// hand-edited payload with the same key.
|
||||
let container: Container = serde_json::from_str(&file).unwrap();
|
||||
let nonce = data_encoding::BASE64.decode(container.nonce.as_bytes()).unwrap();
|
||||
let sealed = data_encoding::BASE64.decode(container.ciphertext.as_bytes()).unwrap();
|
||||
let nonce = data_encoding::BASE64
|
||||
.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 mut payload: Bundle = serde_json::from_slice(&plaintext).unwrap();
|
||||
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 file = serde_json::to_string(&Container {
|
||||
gsmol_export: 2,
|
||||
|
|
|
|||
|
|
@ -24,4 +24,3 @@ pub mod tcp;
|
|||
pub mod transport;
|
||||
#[cfg(test)]
|
||||
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 body_len = u32::from_be_bytes(plaintext[41..45].try_into().unwrap()) as usize;
|
||||
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 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)
|
||||
}
|
||||
|
||||
|
||||
fn identity(n: u32) -> Identity {
|
||||
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!(
|
||||
"path known but identity not yet learned for {hex}; try again shortly"
|
||||
))),
|
||||
-3 => Err(Error::Other(format!(
|
||||
"could not establish a link to {hex}"
|
||||
))),
|
||||
-3 => Err(Error::Other(format!("could not establish a link to {hex}"))),
|
||||
_ => Err(Error::Other(format!(
|
||||
"RNS connect to {hex} failed (code {rc})"
|
||||
))),
|
||||
|
|
@ -133,15 +131,18 @@ pub fn request(request: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
|
|||
Ok(out)
|
||||
}
|
||||
-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(
|
||||
"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())),
|
||||
-6 => Err(Error::Other(
|
||||
"response larger than one application frame; refusing to truncate it".into(),
|
||||
)),
|
||||
)),
|
||||
_ => Err(Error::Other(format!("RNS request failed (code {rc})"))),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,9 +140,7 @@ impl Store {
|
|||
expected: SCHEMA_VERSION,
|
||||
});
|
||||
}
|
||||
Ok(Store {
|
||||
db: Mutex::new(db),
|
||||
})
|
||||
Ok(Store { db: Mutex::new(db) })
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// port keeps the bare-host row existing stores already hold, and any
|
||||
/// other port earns its own row.
|
||||
pub fn pin_server(
|
||||
&self,
|
||||
host: &str,
|
||||
port: u16,
|
||||
key: &[u8; KEY_LEN],
|
||||
) -> Result<(), Error> {
|
||||
pub fn pin_server(&self, host: &str, port: u16, key: &[u8; KEY_LEN]) -> Result<(), Error> {
|
||||
self.db()
|
||||
.execute(
|
||||
"INSERT INTO servers (host, static, pinned_at) VALUES (?1, ?2, ?3) \
|
||||
|
|
@ -597,10 +590,7 @@ impl Store {
|
|||
_ => "inbox",
|
||||
};
|
||||
Ok(self
|
||||
.one::<i64>(
|
||||
&format!("SELECT 1 FROM {table} WHERE id = ?1"),
|
||||
&[id],
|
||||
)?
|
||||
.one::<i64>(&format!("SELECT 1 FROM {table} WHERE id = ?1"), &[id])?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
|
|
@ -616,8 +606,10 @@ impl Store {
|
|||
};
|
||||
self.db()
|
||||
.execute(&format!("DELETE FROM {table} WHERE id = ?1"), params![id])?;
|
||||
self.db()
|
||||
.execute("INSERT OR IGNORE INTO seen (id, at) VALUES (?1, ?2)", params![id, now()])?;
|
||||
self.db().execute(
|
||||
"INSERT OR IGNORE INTO seen (id, at) VALUES (?1, ?2)",
|
||||
params![id, now()],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -792,9 +784,13 @@ mod tests {
|
|||
fn inbox_and_sent_store_sealed_and_deduplicate() {
|
||||
let store = temp_store("mail");
|
||||
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).
|
||||
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());
|
||||
let inbox = store.mail("inbox").unwrap();
|
||||
assert_eq!(inbox.len(), 1);
|
||||
|
|
@ -812,11 +808,24 @@ mod tests {
|
|||
#[test]
|
||||
fn pins_and_contacts_round_trip() {
|
||||
let store = temp_store("pins");
|
||||
assert!(store.server_pin("example.org", DEFAULT_PORT).unwrap().is_none());
|
||||
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]));
|
||||
assert!(store
|
||||
.server_pin("example.org", DEFAULT_PORT)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
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();
|
||||
assert!(store.contact("bob@example.org").unwrap().is_none());
|
||||
|
|
@ -848,10 +857,13 @@ mod tests {
|
|||
let path = scratch_path("schema");
|
||||
drop(Store::open(&path).expect("open store"));
|
||||
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);
|
||||
// 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);
|
||||
match Store::open(&path) {
|
||||
Err(Error::SchemaVersion { found, expected }) => {
|
||||
|
|
@ -903,7 +915,9 @@ mod tests {
|
|||
|
||||
let store = Store::open(&path).expect("v1 store migrates");
|
||||
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);
|
||||
assert_eq!(version, SCHEMA_VERSION);
|
||||
// Existing rows survive with the column's default.
|
||||
|
|
@ -911,8 +925,13 @@ mod tests {
|
|||
assert_eq!(mail.len(), 1);
|
||||
assert!(!mail[0].kept);
|
||||
// The history table exists and round-trips.
|
||||
store.save_history("alice@example.org", &[9u8; 32], 42).unwrap();
|
||||
assert_eq!(store.history("alice@example.org").unwrap(), [([9u8; 32], 42)]);
|
||||
store
|
||||
.save_history("alice@example.org", &[9u8; 32], 42)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
store.history("alice@example.org").unwrap(),
|
||||
[([9u8; 32], 42)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -964,18 +983,24 @@ mod tests {
|
|||
#[test]
|
||||
fn unpin_returns_to_trust_on_first_use() {
|
||||
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();
|
||||
assert_eq!(store.server_pin("example.org", DEFAULT_PORT).unwrap(), None);
|
||||
// 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]
|
||||
fn kept_survives_the_folder_listing() {
|
||||
let store = temp_store("kept");
|
||||
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();
|
||||
assert!(all[0].kept && !all[1].kept);
|
||||
}
|
||||
|
|
@ -998,7 +1023,9 @@ mod tests {
|
|||
fn local_delete_marks_seen_so_a_refetch_does_not_restore() {
|
||||
let store = temp_store("delete-local");
|
||||
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();
|
||||
assert!(store.mail("all").unwrap().is_empty());
|
||||
// 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());
|
||||
// Sent copies delete locally too, with the same protection.
|
||||
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();
|
||||
assert!(store.mail("sent").unwrap().is_empty());
|
||||
}
|
||||
|
|
@ -1021,7 +1050,9 @@ mod tests {
|
|||
store.account().unwrap().unwrap().short(),
|
||||
"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);
|
||||
// The same handle through the path spelling.
|
||||
let path = std::path::Path::new(":memory:");
|
||||
|
|
|
|||
|
|
@ -177,7 +177,15 @@ fn handshake(
|
|||
host: &str,
|
||||
port: u16,
|
||||
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 {
|
||||
host: host.to_string(),
|
||||
port,
|
||||
|
|
@ -219,7 +227,9 @@ impl Transport for TcpTransport {
|
|||
fn request(&mut self, op: u8, body: &[u8]) -> Result<Response, Error> {
|
||||
let length = 1 + body.len();
|
||||
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);
|
||||
frame.extend_from_slice(&(length as u32).to_be_bytes());
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ impl Vectors {
|
|||
}
|
||||
|
||||
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