lockbook_shared/
lazy.rs

1use crate::access_info::UserAccessMode;
2use crate::account::Account;
3use crate::crypto::{AESKey, DecryptedDocument, EncryptedDocument};
4use crate::file_like::FileLike;
5use crate::file_metadata::{FileType, Owner};
6use crate::staged::StagedTree;
7use crate::tree_like::{TreeLike, TreeLikeMut};
8use crate::{compression_service, symkey, SharedErrorKind, SharedResult};
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11use uuid::Uuid;
12
13#[derive(Debug)]
14pub struct LazyTree<T: TreeLike> {
15    pub tree: T,
16    pub name: HashMap<Uuid, String>,
17    pub key: HashMap<Uuid, AESKey>,
18    pub implicit_deleted: HashMap<Uuid, bool>,
19    pub children: HashMap<Uuid, HashSet<Uuid>>,
20}
21
22impl<T: TreeLike> LazyTree<T> {
23    pub fn new(tree: T) -> Self {
24        Self {
25            name: HashMap::new(),
26            key: HashMap::new(),
27            implicit_deleted: HashMap::new(),
28            children: HashMap::new(),
29            tree,
30        }
31    }
32}
33
34impl<T: TreeLike> LazyTree<T> {
35    pub fn access_mode(&self, owner: Owner, id: &Uuid) -> SharedResult<Option<UserAccessMode>> {
36        let mut file = self.find(id)?;
37        let mut max_access_mode = None;
38        let mut visited_ids = vec![];
39        while !visited_ids.contains(file.id()) {
40            visited_ids.push(*file.id());
41            let access_mode = file.access_mode(&owner);
42            if access_mode > max_access_mode {
43                max_access_mode = access_mode;
44            }
45            if file.parent() == file.id() {
46                break; // root
47            } else if let Some(parent) = self.maybe_find(file.parent()) {
48                file = parent
49            } else {
50                break; // share root
51            }
52        }
53        Ok(max_access_mode)
54    }
55
56    pub fn in_pending_share(&mut self, id: &Uuid) -> SharedResult<bool> {
57        let mut id = *id;
58        loop {
59            if self.find(&id)?.parent() == self.find(&id)?.id() {
60                return Ok(false); // root
61            } else if let Some(link) = self.linked_by(&id)? {
62                id = link;
63            } else if self.maybe_find(self.find(&id)?.parent()).is_some() {
64                id = *self.find(&id)?.parent();
65            } else {
66                return Ok(true); // share root
67            }
68        }
69    }
70
71    pub fn all_children(&mut self) -> SharedResult<&HashMap<Uuid, HashSet<Uuid>>> {
72        if self.children.is_empty() {
73            let mut all_children: HashMap<Uuid, HashSet<Uuid>> = HashMap::new();
74            for file in self.all_files()? {
75                if !file.is_root() {
76                    let mut children = all_children.remove(file.parent()).unwrap_or_default();
77                    children.insert(*file.id());
78                    all_children.insert(*file.parent(), children);
79                }
80            }
81            self.children = all_children;
82        }
83
84        Ok(&self.children)
85    }
86
87    pub fn calculate_deleted(&mut self, id: &Uuid) -> SharedResult<bool> {
88        let (visited_ids, deleted) = {
89            let mut file = self.find(id)?;
90            let mut visited_ids = vec![];
91            let mut deleted = false;
92
93            while !file.is_root()
94                && self.maybe_find(file.parent()).is_some()
95                && !visited_ids.contains(file.id())
96            {
97                visited_ids.push(*file.id());
98                if let Some(&implicit) = self.implicit_deleted.get(file.id()) {
99                    deleted = implicit;
100                    break;
101                }
102
103                if file.explicitly_deleted() {
104                    deleted = true;
105                    break;
106                }
107
108                file = match self.maybe_find_parent(file) {
109                    Some(file) => file,
110                    None => {
111                        if !file.user_access_keys().is_empty() {
112                            break;
113                        } else {
114                            return Err(SharedErrorKind::FileParentNonexistent.into());
115                        }
116                    }
117                }
118            }
119
120            (visited_ids, deleted)
121        };
122
123        for id in visited_ids {
124            self.implicit_deleted.insert(id, deleted);
125        }
126
127        Ok(deleted)
128    }
129
130    pub fn decrypt_key(&mut self, id: &Uuid, account: &Account) -> SharedResult<AESKey> {
131        let mut file_id = *self.find(id)?.id();
132        let mut visited_ids = vec![];
133
134        loop {
135            if self.key.get(&file_id).is_some() {
136                break;
137            }
138
139            let my_pk = account.public_key();
140
141            let maybe_file_key = if let Some(user_access) = self
142                .find(&file_id)?
143                .user_access_keys()
144                .iter()
145                .find(|access| access.encrypted_for == my_pk)
146            {
147                Some(user_access.decrypt(account)?)
148            } else {
149                None
150            };
151            if let Some(file_key) = maybe_file_key {
152                self.key.insert(file_id, file_key);
153                break;
154            }
155
156            visited_ids.push(file_id);
157            file_id = *self.find_parent(self.find(&file_id)?)?.id();
158        }
159
160        for id in visited_ids.iter().rev() {
161            let decrypted_key = {
162                let file = self.find(id)?;
163                let parent = self.find_parent(file)?;
164                let parent_key = self
165                    .key
166                    .get(parent.id())
167                    .ok_or(SharedErrorKind::Unexpected(
168                        "parent key should have been populated by prior routine",
169                    ))?;
170                let encrypted_key = file.folder_access_key();
171                symkey::decrypt(parent_key, encrypted_key)?
172            };
173            self.key.insert(*id, decrypted_key);
174        }
175
176        Ok(*self.key.get(id).ok_or(SharedErrorKind::Unexpected(
177            "parent key should have been populated by prior routine (2)",
178        ))?)
179    }
180
181    pub fn name(&mut self, id: &Uuid, account: &Account) -> SharedResult<String> {
182        if let Some(name) = self.name.get(id) {
183            return Ok(name.clone());
184        }
185        let key = self.decrypt_key(id, account)?;
186        let name = self.find(id)?.secret_name().to_string(&key)?;
187        self.name.insert(*id, name.clone());
188        Ok(name)
189    }
190
191    pub fn name_using_links(&mut self, id: &Uuid, account: &Account) -> SharedResult<String> {
192        let id = if let Some(link) = self.linked_by(id)? { link } else { *id };
193        self.name(&id, account)
194    }
195
196    pub fn parent_using_links(&mut self, id: &Uuid) -> SharedResult<Uuid> {
197        let id = if let Some(link) = self.linked_by(id)? { link } else { *id };
198        Ok(*self.find(&id)?.parent())
199    }
200
201    pub fn linked_by(&mut self, id: &Uuid) -> SharedResult<Option<Uuid>> {
202        for link_id in self.owned_ids() {
203            if let FileType::Link { target } = self.find(&link_id)?.file_type() {
204                if id == &target && !self.calculate_deleted(&link_id)? {
205                    return Ok(Some(link_id));
206                }
207            }
208        }
209        Ok(None)
210    }
211
212    /// Returns ids of files whose parent is the argument. Does not include the argument.
213    /// TODO could consider returning a reference to the underlying cached value
214    pub fn children(&mut self, id: &Uuid) -> SharedResult<HashSet<Uuid>> {
215        // Check cache
216        if let Some(children) = self.children.get(id) {
217            return Ok(children.clone());
218        }
219
220        // Confirm file exists
221        let file = self.find(id)?;
222        if !file.is_folder() {
223            return Ok(HashSet::default());
224        }
225
226        // Populate cache
227        self.all_children()?;
228
229        // Return value from cache
230        if let Some(children) = self.children.get(id) {
231            return Ok(children.clone());
232        }
233
234        Ok(HashSet::new())
235    }
236
237    // todo: cache?
238    pub fn children_using_links(&mut self, id: &Uuid) -> SharedResult<HashSet<Uuid>> {
239        let mut children = HashSet::new();
240        for child in self.children(id)? {
241            if let FileType::Link { target } = self.find(&child)?.file_type() {
242                if !self.calculate_deleted(&child)? {
243                    children.insert(target);
244                }
245            } else {
246                children.insert(child);
247            }
248        }
249
250        Ok(children)
251    }
252
253    /// Returns ids of files for which the argument is an ancestor—the files' children, recursively. Does not include the argument.
254    /// This function tolerates cycles.
255    pub fn descendants(&mut self, id: &Uuid) -> SharedResult<HashSet<Uuid>> {
256        // todo: caching?
257        let mut result = HashSet::new();
258        let mut to_process = vec![*id];
259        let mut i = 0;
260        while i < to_process.len() {
261            let new_descendents = self
262                .children(&to_process[i])?
263                .into_iter()
264                .filter(|f| !result.contains(f))
265                .collect::<Vec<Uuid>>();
266            // TODO could consider optimizing by not exploring documents
267            to_process.extend(new_descendents.iter());
268            result.extend(new_descendents.into_iter());
269            i += 1;
270        }
271        Ok(result)
272    }
273
274    pub fn descendants_using_links(&mut self, id: &Uuid) -> SharedResult<HashSet<Uuid>> {
275        // todo: caching?
276        let mut result = HashSet::new();
277        let mut to_process = vec![*id];
278        let mut i = 0;
279        while i < to_process.len() {
280            let new_descendents = self
281                .children_using_links(&to_process[i])?
282                .into_iter()
283                .filter(|f| !result.contains(f))
284                .collect::<Vec<Uuid>>();
285            // TODO could consider optimizing by not exploring documents
286            to_process.extend(new_descendents.iter());
287            result.extend(new_descendents.into_iter());
288            i += 1;
289        }
290        Ok(result)
291    }
292
293    // todo: move to TreeLike
294    /// Returns ids of files for which the argument is a descendent—the files' parent, recursively. Does not include the argument.
295    /// This function tolerates cycles.
296    pub fn ancestors(&self, id: &Uuid) -> SharedResult<HashSet<Uuid>> {
297        let mut result = HashSet::new();
298        let mut current_file = self.find(id)?;
299        while !current_file.is_root()
300            && self.maybe_find(current_file.parent()).is_some()
301            && !result.contains(current_file.parent())
302        {
303            result.insert(*current_file.parent());
304            current_file = self.find_parent(current_file)?;
305        }
306        Ok(result)
307    }
308
309    pub fn decrypt_document(
310        &mut self, id: &Uuid, encrypted: &EncryptedDocument, account: &Account,
311    ) -> SharedResult<DecryptedDocument> {
312        let key = self.decrypt_key(id, account)?;
313        let compressed = symkey::decrypt(&key, encrypted)?;
314        compression_service::decompress(&compressed)
315    }
316
317    pub fn stage<T2: TreeLikeMut<F = T::F>>(self, staged: T2) -> LazyTree<StagedTree<T, T2>> {
318        // todo: optimize by performing minimal updates on self caches
319        LazyTree::<StagedTree<T, T2>> {
320            tree: StagedTree::new(self.tree, staged),
321            name: HashMap::new(),
322            key: self.key,
323            implicit_deleted: HashMap::new(),
324            children: HashMap::new(),
325        }
326    }
327
328    pub fn stage_removals(self, removed: HashSet<Uuid>) -> LazyTree<StagedTree<T, Option<T::F>>> {
329        // todo: optimize by performing minimal updates on self caches
330        LazyTree::<StagedTree<T, Option<T::F>>> {
331            tree: StagedTree::removal(self.tree, removed),
332            name: HashMap::new(),
333            key: self.key,
334            implicit_deleted: HashMap::new(),
335            children: HashMap::new(),
336        }
337    }
338
339    // todo: optimize
340    pub fn assert_names_decryptable(&mut self, account: &Account) -> SharedResult<()> {
341        for id in self.owned_ids() {
342            if self.name(&id, account).is_err() {
343                return Err(SharedErrorKind::ValidationFailure(
344                    ValidationFailure::NonDecryptableFileName(id),
345                )
346                .into());
347            }
348        }
349        Ok(())
350    }
351}
352
353pub type Stage1<Base, Local> = StagedTree<Base, Local>;
354pub type LazyStaged1<Base, Local> = LazyTree<Stage1<Base, Local>>;
355pub type Stage2<Base, Local, Staged> = StagedTree<StagedTree<Base, Local>, Staged>;
356pub type LazyStage2<Base, Local, Staged> = LazyTree<Stage2<Base, Local, Staged>>;
357
358#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq)]
359pub enum ValidationFailure {
360    Orphan(Uuid),
361    Cycle(HashSet<Uuid>),
362    PathConflict(HashSet<Uuid>),
363    FileNameTooLong(Uuid),
364    NonFolderWithChildren(Uuid),
365    FileWithDifferentOwnerParent(Uuid),
366    NonDecryptableFileName(Uuid),
367    SharedLink { link: Uuid, shared_ancestor: Uuid },
368    DuplicateLink { target: Uuid },
369    BrokenLink(Uuid),
370    OwnedLink(Uuid),
371}
372
373impl<T> LazyTree<T>
374where
375    T: TreeLikeMut,
376{
377    pub fn stage_and_promote<S: TreeLikeMut<F = T::F>>(
378        &mut self, mut staged: S,
379    ) -> SharedResult<()> {
380        for id in staged.owned_ids() {
381            if let Some(removed) = staged.remove(id)? {
382                self.tree.insert(removed)?;
383            }
384        }
385        // todo: incremental cache update
386        self.name = HashMap::new();
387        self.implicit_deleted = HashMap::new();
388        self.children = HashMap::new();
389        Ok(())
390    }
391
392    pub fn stage_validate_and_promote<S: TreeLikeMut<F = T::F>>(
393        &mut self, mut staged: S, owner: Owner,
394    ) -> SharedResult<()> {
395        StagedTree::new(&self.tree, &mut staged)
396            .to_lazy()
397            .validate(owner)?;
398        self.stage_and_promote(staged)?;
399        Ok(())
400    }
401
402    pub fn stage_removals_and_promote(&mut self, removed: HashSet<Uuid>) -> SharedResult<()> {
403        for id in removed {
404            self.tree.remove(id)?;
405        }
406        // todo: incremental cache update
407        self.name = HashMap::new();
408        self.implicit_deleted = HashMap::new();
409        self.children = HashMap::new();
410        Ok(())
411    }
412}
413
414impl<Base, Staged> LazyStaged1<Base, Staged>
415where
416    Base: TreeLikeMut,
417    Staged: TreeLikeMut<F = Base::F>,
418{
419    // todo: incrementalism
420    pub fn promote(self) -> SharedResult<LazyTree<Base>> {
421        let mut staged = self.tree.staged;
422        let mut base = self.tree.base;
423        for id in staged.owned_ids() {
424            if let Some(removed) = staged.remove(id)? {
425                base.insert(removed)?;
426            }
427        }
428        for id in self.tree.removed {
429            base.remove(id)?;
430        }
431
432        Ok(LazyTree {
433            tree: base,
434            name: HashMap::new(),
435            key: self.key,
436            implicit_deleted: HashMap::new(),
437            children: HashMap::new(),
438        })
439    }
440}
441
442impl<Base, Staged> LazyStaged1<Base, Staged>
443where
444    Base: TreeLike,
445    Staged: TreeLikeMut<F = Base::F>,
446{
447    // todo: incrementalism
448    pub fn unstage(self) -> (LazyTree<Base>, Staged) {
449        (
450            LazyTree {
451                tree: self.tree.base,
452                name: HashMap::new(),
453                key: self.key,
454                implicit_deleted: HashMap::new(),
455                children: HashMap::new(),
456            },
457            self.tree.staged,
458        )
459    }
460}
461
462impl<T: TreeLike> TreeLike for LazyTree<T> {
463    type F = T::F;
464
465    fn ids(&self) -> HashSet<&Uuid> {
466        self.tree.ids()
467    }
468
469    fn maybe_find(&self, id: &Uuid) -> Option<&Self::F> {
470        self.tree.maybe_find(id)
471    }
472}