sync15/engine/
sync_engine.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use super::CollectionRequest;
6use crate::bso::{IncomingBso, OutgoingBso};
7use crate::client_types::ClientData;
8use crate::{CollectionName, Guid, ServerTimestamp, telemetry};
9use anyhow::Result;
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct CollSyncIds {
14    pub global: Guid,
15    pub coll: Guid,
16}
17
18/// Defines how an engine is associated with a particular set of records
19/// on a sync storage server. It's either disconnected, or believes it is
20/// connected with a specific set of GUIDs. If the server and the engine don't
21/// agree on the exact GUIDs, the engine will assume something radical happened
22/// so it can't believe anything it thinks it knows about the state of the
23/// server (ie, it will "reset" then do a full reconcile)
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum EngineSyncAssociation {
26    /// This store is disconnected (although it may be connected in the future).
27    Disconnected,
28    /// Sync is connected, and has the following sync IDs.
29    Connected(CollSyncIds),
30}
31
32/// The concrete `SyncEngine` implementations
33#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
34pub enum SyncEngineId {
35    // Note that we've derived PartialOrd etc, which uses lexicographic ordering
36    // of the variants. We leverage that such that the higher priority engines
37    // are listed first.
38    // This order matches desktop.
39    Passwords,
40    Tabs,
41    Bookmarks,
42    Addresses,
43    CreditCards,
44    History,
45}
46
47impl SyncEngineId {
48    // Iterate over all possible engines. Note that we've made a policy decision
49    // that this should enumerate in "order" as defined by PartialCmp, and tests
50    // enforce this.
51    pub fn iter() -> impl Iterator<Item = SyncEngineId> {
52        [
53            Self::Passwords,
54            Self::Tabs,
55            Self::Bookmarks,
56            Self::Addresses,
57            Self::CreditCards,
58            Self::History,
59        ]
60        .into_iter()
61    }
62
63    // Get the string identifier for this engine.  This must match the strings in SyncEngineSelection.
64    pub fn name(&self) -> &'static str {
65        match self {
66            Self::Passwords => "passwords",
67            Self::History => "history",
68            Self::Bookmarks => "bookmarks",
69            Self::Tabs => "tabs",
70            Self::Addresses => "addresses",
71            Self::CreditCards => "creditcards",
72        }
73    }
74}
75
76impl fmt::Display for SyncEngineId {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}", self.name())
79    }
80}
81
82impl TryFrom<&str> for SyncEngineId {
83    type Error = String;
84
85    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
86        match value {
87            "passwords" => Ok(Self::Passwords),
88            "history" => Ok(Self::History),
89            "bookmarks" => Ok(Self::Bookmarks),
90            "tabs" => Ok(Self::Tabs),
91            "addresses" => Ok(Self::Addresses),
92            "creditcards" => Ok(Self::CreditCards),
93            _ => Err(value.into()),
94        }
95    }
96}
97
98/// A "sync engine" is a thing that knows how to sync. It's often implemented
99/// by a "store" (which is the generic term responsible for all storage
100/// associated with a component, including storage required for sync.)
101///
102/// The model described by this trait is that engines first "stage" sets of incoming records,
103/// then apply them returning outgoing records, then handle the success (or otherwise) of each
104/// batch as it's uploaded.
105///
106/// Staging incoming records is (or should be ;) done in batches - eg, 1000 record chunks.
107/// Some engines will "stage" these into a database temp table, while ones expecting less records
108/// might just store them in memory.
109///
110/// For outgoing records, a single vec is supplied by the engine. The sync client will use the
111/// batch facilities of the server to make multiple POST requests and commit them.
112/// Sadly it's not truly atomic (there's a batch size limit) - so the model reflects that in that
113/// the engine gets told each time a batch is committed, which might happen more than once for the
114/// supplied vec. We should upgrade this model so the engine can avoid reading every outgoing
115/// record into memory at once (ie, we should try and better reflect the upload batch model at
116/// this level)
117///
118/// Sync Engines should not assume they live for exactly one sync, so `sync_started()` should
119/// clean up any state, including staged records, from previous syncs.
120///
121/// Different engines will produce errors of different types.  To accommodate
122/// this, we force them all to return anyhow::Error.
123pub trait SyncEngine {
124    fn collection_name(&self) -> CollectionName;
125
126    /// Indicates that a sync is starting.
127    fn sync_started(&self) -> Result<()> {
128        Ok(())
129    }
130
131    /// Supplies the engine with the current set of Sync clients (ie, other
132    /// devices connected to the account). Might be called at any time.
133    fn set_clients(&self, _get_client_data: &dyn Fn() -> ClientData) -> Result<()> {
134        Ok(())
135    }
136
137    /// Tells the engine what the local encryption key is for the data managed
138    /// by the engine. This is only used by collections that store data
139    /// encrypted locally and is unrelated to the encryption used by Sync.
140    /// The intent is that for such collections, this key can be used to
141    /// decrypt local data before it is re-encrypted by Sync and sent to the
142    /// storage servers, and similarly, data from the storage servers will be
143    /// decrypted by Sync, then encrypted by the local encryption key before
144    /// being added to the local database.
145    ///
146    /// The expectation is that the key value is being maintained by the
147    /// embedding application in some secure way suitable for the environment
148    /// in which the app is running - eg, the OS "keychain". The value of the
149    /// key is implementation dependent - it is expected that the engine and
150    /// embedding application already have some external agreement about how
151    /// to generate keys and in what form they are exchanged. Finally, there's
152    /// an assumption that sync engines are short-lived and only live for a
153    /// single sync - this means that sync doesn't hold on to the key for an
154    /// extended period. In practice, all sync engines which aren't a "bridged
155    /// engine" are short lived - we might need to rethink this later if we need
156    /// engines with local encryption keys to be used on desktop.
157    ///
158    /// This will panic if called by an engine that doesn't have explicit
159    /// support for local encryption keys as that implies a degree of confusion
160    /// which shouldn't be possible to ignore.
161    fn set_local_encryption_key(&mut self, _key: &str) -> Result<()> {
162        unimplemented!("This engine does not support local encryption");
163    }
164
165    /// Stage some incoming records. This might be called multiple times in the same sync
166    /// if we fetch the incoming records in batches.
167    ///
168    /// Note there is no timestamp provided here, because the procedure for fetching in batches
169    /// means that the timestamp advancing during a batch means we must abort and start again.
170    /// The final collection timestamp after staging all records is supplied to `apply()`
171    fn stage_incoming(
172        &self,
173        inbound: Vec<IncomingBso>,
174        telem: &mut telemetry::Engine,
175    ) -> Result<()>;
176
177    /// Apply the staged records, returning outgoing records.
178    /// Ideally we would adjust this model to better support batching of outgoing records
179    /// without needing to keep them all in memory (ie, an iterator or similar?)
180    fn apply(
181        &self,
182        timestamp: ServerTimestamp,
183        telem: &mut telemetry::Engine,
184    ) -> Result<Vec<OutgoingBso>>;
185
186    /// Indicates that the given record IDs were uploaded successfully to the server.
187    /// This may be called multiple times per sync, once for each batch. Batching is determined
188    /// dynamically based on payload sizes and counts via the server's advertised limits.
189    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> Result<()>;
190
191    /// Called once the sync is finished. Not currently called if uploads fail (which
192    /// seems sad, but the other batching confusion there needs sorting out first).
193    /// Many engines will have nothing to do here, as most "post upload" work should be
194    /// done in `set_uploaded()`
195    fn sync_finished(&self) -> Result<()> {
196        Ok(())
197    }
198
199    /// The engine is responsible for building a single collection request. Engines
200    /// typically will store a lastModified timestamp and use that to build a
201    /// request saying "give me full records since that date" - however, other
202    /// engines might do something fancier. It can return None if the server timestamp
203    /// has not advanced since the last sync.
204    /// This could even later be extended to handle "backfills", and we might end up
205    /// wanting one engine to use multiple collections (eg, as a "foreign key" via guid), etc.
206    fn get_collection_request(
207        &self,
208        server_timestamp: ServerTimestamp,
209    ) -> Result<Option<CollectionRequest>>;
210
211    /// Get persisted sync IDs. If they don't match the global state we'll be
212    /// `reset()` with the new IDs.
213    fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>;
214
215    /// Reset the engine (and associated store) without wiping local data,
216    /// ready for a "first sync".
217    /// `assoc` defines how this store is to be associated with sync.
218    fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>;
219
220    /// Wipes the engine's local data.
221    /// Triggered by a client command (only bookmarks at time of writing),
222    /// or to wipe local data when disconnecting (currently only on desktop
223    /// via a bridged-engine).
224    fn wipe(&self) -> Result<()>;
225
226    // A couple of desktop specific "bridged engine" helpers, where the
227    // last-modified timestamps for collections are handled differently;
228    // who does the `get_collection_request()` etc impacts the owner of the
229    // timestamp.
230    // Engines should do both or neither, longer term it should be absorbed.
231    /// Return the last sync timestamp, only called for bridged engines.
232    fn last_sync(&self) -> Result<Option<ServerTimestamp>> {
233        unimplemented!("This engine is not used as a bridged engine");
234    }
235
236    /// Reset the last sync timestampf or the engine, only called for bridged engines.
237    fn reset_last_sync(&self) -> Result<()> {
238        unimplemented!("This engine is not used as a bridged engine");
239    }
240}
241
242#[cfg(test)]
243mod test {
244    use super::*;
245    use std::iter::zip;
246
247    #[test]
248    fn test_engine_priority() {
249        fn sorted(mut engines: Vec<SyncEngineId>) -> Vec<SyncEngineId> {
250            engines.sort();
251            engines
252        }
253        assert_eq!(
254            vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
255            sorted(vec![SyncEngineId::Passwords, SyncEngineId::Tabs])
256        );
257        assert_eq!(
258            vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
259            sorted(vec![SyncEngineId::Tabs, SyncEngineId::Passwords])
260        );
261    }
262
263    #[test]
264    fn test_engine_enum_order() {
265        let unsorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
266        let mut sorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
267        sorted.sort();
268
269        // iterating should supply identical elements in each.
270        assert!(zip(unsorted, sorted).fold(true, |acc, (a, b)| acc && (a == b)))
271    }
272}