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::{telemetry, CollectionName, Guid, ServerTimestamp};
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 `prepare_for_sync()` 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 /// Prepares the engine for syncing. The tabs engine currently uses this to
127 /// store the current list of clients, which it uses to look up device names
128 /// and types.
129 ///
130 /// Note that this method is only called by `sync_multiple`, and only if a
131 /// command processor is registered. In particular, `prepare_for_sync` will
132 /// not be called if the store is synced using `sync::synchronize` or
133 /// `sync_multiple::sync_multiple`. It _will_ be called if the store is
134 /// synced via the Sync Manager.
135 ///
136 /// TODO(issue #2590): This is pretty cludgey and will be hard to extend for
137 /// any case other than the tabs case. We should find another way to support
138 /// tabs...
139 fn prepare_for_sync(&self, _get_client_data: &dyn Fn() -> ClientData) -> Result<()> {
140 Ok(())
141 }
142
143 /// Tells the engine what the local encryption key is for the data managed
144 /// by the engine. This is only used by collections that store data
145 /// encrypted locally and is unrelated to the encryption used by Sync.
146 /// The intent is that for such collections, this key can be used to
147 /// decrypt local data before it is re-encrypted by Sync and sent to the
148 /// storage servers, and similarly, data from the storage servers will be
149 /// decrypted by Sync, then encrypted by the local encryption key before
150 /// being added to the local database.
151 ///
152 /// The expectation is that the key value is being maintained by the
153 /// embedding application in some secure way suitable for the environment
154 /// in which the app is running - eg, the OS "keychain". The value of the
155 /// key is implementation dependent - it is expected that the engine and
156 /// embedding application already have some external agreement about how
157 /// to generate keys and in what form they are exchanged. Finally, there's
158 /// an assumption that sync engines are short-lived and only live for a
159 /// single sync - this means that sync doesn't hold on to the key for an
160 /// extended period. In practice, all sync engines which aren't a "bridged
161 /// engine" are short lived - we might need to rethink this later if we need
162 /// engines with local encryption keys to be used on desktop.
163 ///
164 /// This will panic if called by an engine that doesn't have explicit
165 /// support for local encryption keys as that implies a degree of confusion
166 /// which shouldn't be possible to ignore.
167 fn set_local_encryption_key(&mut self, _key: &str) -> Result<()> {
168 unimplemented!("This engine does not support local encryption");
169 }
170
171 /// Stage some incoming records. This might be called multiple times in the same sync
172 /// if we fetch the incoming records in batches.
173 ///
174 /// Note there is no timestamp provided here, because the procedure for fetching in batches
175 /// means that the timestamp advancing during a batch means we must abort and start again.
176 /// The final collection timestamp after staging all records is supplied to `apply()`
177 fn stage_incoming(
178 &self,
179 inbound: Vec<IncomingBso>,
180 telem: &mut telemetry::Engine,
181 ) -> Result<()>;
182
183 /// Apply the staged records, returning outgoing records.
184 /// Ideally we would adjust this model to better support batching of outgoing records
185 /// without needing to keep them all in memory (ie, an iterator or similar?)
186 fn apply(
187 &self,
188 timestamp: ServerTimestamp,
189 telem: &mut telemetry::Engine,
190 ) -> Result<Vec<OutgoingBso>>;
191
192 /// Indicates that the given record IDs were uploaded successfully to the server.
193 /// This may be called multiple times per sync, once for each batch. Batching is determined
194 /// dynamically based on payload sizes and counts via the server's advertised limits.
195 fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> Result<()>;
196
197 /// Called once the sync is finished. Not currently called if uploads fail (which
198 /// seems sad, but the other batching confusion there needs sorting out first).
199 /// Many engines will have nothing to do here, as most "post upload" work should be
200 /// done in `set_uploaded()`
201 fn sync_finished(&self) -> Result<()> {
202 Ok(())
203 }
204
205 /// The engine is responsible for building a single collection request. Engines
206 /// typically will store a lastModified timestamp and use that to build a
207 /// request saying "give me full records since that date" - however, other
208 /// engines might do something fancier. It can return None if the server timestamp
209 /// has not advanced since the last sync.
210 /// This could even later be extended to handle "backfills", and we might end up
211 /// wanting one engine to use multiple collections (eg, as a "foreign key" via guid), etc.
212 fn get_collection_request(
213 &self,
214 server_timestamp: ServerTimestamp,
215 ) -> Result<Option<CollectionRequest>>;
216
217 /// Get persisted sync IDs. If they don't match the global state we'll be
218 /// `reset()` with the new IDs.
219 fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>;
220
221 /// Reset the engine (and associated store) without wiping local data,
222 /// ready for a "first sync".
223 /// `assoc` defines how this store is to be associated with sync.
224 fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>;
225
226 /// Wipes the engine's data
227 /// This is typically triggered by a client command, which at the time of writing, only
228 /// supported wiping bookmarks.
229 ///
230 /// This panics if triggered on a sync engine that does not explicitly implement wipe, because
231 /// that implies a confustion that shouldn't occur.
232 fn wipe(&self) -> Result<()> {
233 unimplemented!("The engine does not implement wipe, no wipe should be requested")
234 }
235}
236
237#[cfg(test)]
238mod test {
239 use super::*;
240 use std::iter::zip;
241
242 #[test]
243 fn test_engine_priority() {
244 fn sorted(mut engines: Vec<SyncEngineId>) -> Vec<SyncEngineId> {
245 engines.sort();
246 engines
247 }
248 assert_eq!(
249 vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
250 sorted(vec![SyncEngineId::Passwords, SyncEngineId::Tabs])
251 );
252 assert_eq!(
253 vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
254 sorted(vec![SyncEngineId::Tabs, SyncEngineId::Passwords])
255 );
256 }
257
258 #[test]
259 fn test_engine_enum_order() {
260 let unsorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
261 let mut sorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
262 sorted.sort();
263
264 // iterating should supply identical elements in each.
265 assert!(zip(unsorted, sorted).fold(true, |acc, (a, b)| acc && (a == b)))
266 }
267}