Skip to main content

coven_sync/
identity.rs

1//! Identity evidence from authenticated sequence slots (§19.3).
2
3use crate::SyncError;
4use coven_crypto::{ObjectDigest, ObjectHasher};
5use coven_merge::Slot;
6use coven_storage::{ObjectPath, Storage, StorageFailure};
7
8/// Authenticated evidence that another installation uses this device id.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
10pub enum DeviceIdEvidence {
11    /// This copy never attempted the bytes signed by its device at this slot.
12    UnattemptedSlot {
13        /// The occupied slot.
14        #[serde(with = "coven_database::slot_json")]
15        slot: Slot,
16    },
17    /// A counted registration replaces this installation.
18    Replaced,
19}
20
21/// Length and SHA-256 of bytes this copy would send.
22pub(crate) fn digest(bytes: &[u8]) -> (u64, ObjectDigest) {
23    let mut hasher = ObjectHasher::new();
24    hasher.update(bytes);
25    (bytes.len() as u64, hasher.finish())
26}
27
28/// Read the complete occupant of an immutable path. A failed read keeps the
29/// attempt pending; occupation alone proves nothing (§19.3).
30pub(crate) async fn stored_digest(
31    storage: &dyn Storage,
32    path: &ObjectPath,
33) -> Result<(u64, ObjectDigest), SyncError> {
34    let mut stream = storage.stream_object(path, None).await?;
35    if &stream.metadata().path != path {
36        return Err(StorageFailure::Protocol
37            .with_source("occupant response names another path")
38            .into());
39    }
40    let mut hasher = ObjectHasher::new();
41    let mut length = 0u64;
42    let mut buffer = vec![0; 64 * 1024];
43    loop {
44        let read = stream.read(&mut buffer).await?;
45        if read == 0 {
46            break;
47        }
48        hasher.update(&buffer[..read]);
49        length += read as u64;
50    }
51    Ok((length, hasher.finish()))
52}
53
54/// A non-slot immutable artifact with other bytes fails its own attempt. Only
55/// authenticated sequence claims prove another installation uses this device id.
56pub(crate) async fn settle_occupied(
57    storage: &dyn Storage,
58    path: &ObjectPath,
59    expected: (u64, ObjectDigest),
60) -> Result<(), SyncError> {
61    if stored_digest(storage, path).await? == expected {
62        return Ok(());
63    }
64    Err(crate::write_object::wrong_identity(path))
65}
66
67#[cfg(test)]
68#[path = "identity_tests.rs"]
69mod tests;