style: format core with rustfmt

This commit is contained in:
randogoth 2026-09-30 21:46:41 +03:00
parent 447be4b38d
commit 8872ec6c98
12 changed files with 186 additions and 81 deletions

View file

@ -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());
}

View file

@ -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}",

View file

@ -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)?;

View file

@ -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.

View file

@ -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,

View file

@ -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,

View file

@ -24,4 +24,3 @@ pub mod tcp;
pub mod transport;
#[cfg(test)]
mod vectors;

View file

@ -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))
}

View file

@ -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})"))),
}
}

View file

@ -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:");

View file

@ -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());

View file

@ -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")
}
}