Skip to main content

coven_foundation/
id_source.rs

1//! New ids, including each install's 64-bit device id (§10).
2
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8/// A store's identity, independent of its name and location on disk.
9#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct StoreId(
12    /// The store's UUID.
13    pub Uuid,
14);
15
16impl std::fmt::Display for StoreId {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        self.0.fmt(f)
19    }
20}
21
22/// A circle's identity, independent of its name or current key (§15.5).
23#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
24#[serde(transparent)]
25pub struct CircleId(
26    /// The circle's UUID, supplied by the id source.
27    pub Uuid,
28);
29
30impl std::fmt::Display for CircleId {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        self.0.fmt(f)
33    }
34}
35
36/// A store or circle key's identity, named by the entry introducing it (§12.1).
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
38#[serde(transparent)]
39pub struct KeyId(
40    /// The key's UUID, supplied by the id source.
41    pub Uuid,
42);
43
44impl std::fmt::Display for KeyId {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        self.0.fmt(f)
47    }
48}
49
50/// A member's random stable identity, independent of their keys (§10).
51/// Device ownership, circle membership and slot authors name it; a key
52/// replacement keeps it.
53#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
54#[serde(transparent)]
55pub struct MemberId(
56    /// The member's UUID, supplied by the id source.
57    pub Uuid,
58);
59
60impl std::fmt::Display for MemberId {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        self.0.fmt(f)
63    }
64}
65
66impl std::str::FromStr for MemberId {
67    type Err = InvalidMemberId;
68    /// Only the canonical lowercase hyphenated spelling (D10); a member
69    /// spelling never encodes a public key.
70    fn from_str(value: &str) -> Result<Self, Self::Err> {
71        canonical_uuid(value).map(Self).map_err(|_not_canonical| InvalidMemberId)
72    }
73}
74
75/// Text that is not a member id's canonical lowercase hyphenated UUID (D10).
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct InvalidMemberId;
78
79impl std::fmt::Display for InvalidMemberId {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.write_str("not a canonical member id")
82    }
83}
84
85impl std::error::Error for InvalidMemberId {}
86
87/// The UUID `text` spells in its one canonical form, lowercase and
88/// hyphenated (D10). Any other spelling of the same UUID names nothing.
89pub fn canonical_uuid(text: &str) -> Result<Uuid, NotCanonicalUuid> {
90    match Uuid::try_parse(text) {
91        Ok(id) if id.hyphenated().to_string() == text => Ok(id),
92        _ => Err(NotCanonicalUuid),
93    }
94}
95
96/// Text that is not a UUID's canonical lowercase hyphenated spelling (D10).
97#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
98#[error("not a canonical lowercase hyphenated UUID")]
99pub struct NotCanonicalUuid;
100
101/// An invitation's identity, independent of its one-time secret (§13.2).
102#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
103#[serde(transparent)]
104pub struct InviteId(
105    /// The invite's UUID, supplied by the id source.
106    pub Uuid,
107);
108
109impl std::fmt::Display for InviteId {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        self.0.fmt(f)
112    }
113}
114
115/// A file record's random identity, independent of its content and of where
116/// its bytes are stored (§17.2).
117#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
118#[serde(transparent)]
119pub struct FileId(
120    /// The file's UUID, supplied by the id source.
121    pub Uuid,
122);
123
124impl std::fmt::Display for FileId {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        self.0.fmt(f)
127    }
128}
129
130/// A stored object's random id, the last part of its path
131/// `<store>/files/<uploader>/<object>` (§17.5).
132#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
133#[serde(transparent)]
134pub struct ObjectId(
135    /// The object's UUID, supplied by the id source.
136    pub Uuid,
137);
138
139impl std::fmt::Display for ObjectId {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        self.0.fmt(f)
142    }
143}
144
145/// One install of the app, by its 64-bit device id (§10).
146#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
147#[serde(transparent)]
148pub struct DeviceId(
149    /// The install's 64-bit id.
150    pub u64,
151);
152
153/// The source of new ids, supplied when creating or opening a store.
154pub trait IdSource: Send + Sync {
155    /// A fresh UUID. Distinct calls must yield distinct ids.
156    fn new_id(&self) -> Uuid;
157
158    /// A fresh device id derived from this source, without another randomness
159    /// source. Every install and restored copy receives a new device id (§10.4).
160    ///
161    /// Uses 62 payload bits from the UUID's last eight bytes and two from its
162    /// eighth byte: all 64 are random in UUIDv4 and UUIDv7. The version,
163    /// variant and UUIDv7 timestamp contribute no bits.
164    fn new_device_id(&self) -> DeviceId {
165        let (high, low) = self.new_id().as_u64_pair();
166        DeviceId((low & 0x3fff_ffff_ffff_ffff) | ((high & 3) << 62))
167    }
168}
169
170/// A shared source of new ids.
171pub type IdSourceRef = Arc<dyn IdSource>;
172
173/// Random UUIDv4 ids, independent of the clock (E1).
174pub struct UuidIds;
175
176impl IdSource for UuidIds {
177    fn new_id(&self) -> Uuid {
178        Uuid::new_v4()
179    }
180}
181
182/// A deterministic sequence of UUIDs and device ids, starting at one.
183///
184/// One source shares its sequence across both methods. It panics on exhaustion
185/// instead of wrapping and reusing an id. Separate sources repeat the sequence.
186#[cfg(feature = "test-utils")]
187pub struct SequentialIds(std::sync::atomic::AtomicU64);
188
189#[cfg(feature = "test-utils")]
190impl SequentialIds {
191    /// A sequence whose first UUID's payload and first device id are one.
192    pub fn new() -> Self {
193        Self(std::sync::atomic::AtomicU64::new(1))
194    }
195}
196
197#[cfg(feature = "test-utils")]
198impl IdSource for SequentialIds {
199    fn new_id(&self) -> Uuid {
200        use std::sync::atomic::Ordering;
201        let n = self
202            .0
203            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
204            .expect("sequential id source exhausted");
205        // Preserve all 64 sequence bits in the payload positions used above.
206        Uuid::from_u64_pair(0x7000 | (n >> 62), 0x8000_0000_0000_0000 | (n & 0x3fff_ffff_ffff_ffff))
207    }
208}
209
210#[cfg(test)]
211#[path = "id_source_tests.rs"]
212mod tests;