lb_rs/model/
server_ops.rs

1use super::errors::{DiffError, LbErrKind, LbResult};
2use super::meta::Meta;
3use super::server_meta::{IntoServerMeta, ServerMeta};
4use super::signed_meta::SignedMeta;
5use crate::model::clock::get_time;
6use crate::model::file_like::FileLike;
7use crate::model::file_metadata::FileDiff;
8use crate::model::lazy::{LazyStaged1, LazyTree};
9use crate::model::server_tree::ServerTree;
10use crate::model::signed_file::SignedFile;
11use crate::model::tree_like::TreeLike;
12
13type LazyServerStaged1<'a> = LazyStaged1<ServerTree<'a>, Vec<ServerMeta>>;
14
15impl<'a> LazyTree<ServerTree<'a>> {
16    /// Validates a diff prior to staging it. Performs individual validations, then validations that
17    /// require a tree
18    pub fn stage_diff(self, changes: Vec<FileDiff<SignedFile>>) -> LbResult<LazyServerStaged1<'a>> {
19        let mut changes_meta: Vec<FileDiff<SignedMeta>> = vec![];
20        for change in changes {
21            let mut new_meta: SignedMeta = change.new.into();
22            let mut old_meta = change.old.map(SignedMeta::from);
23            if let Some(old) = &mut old_meta {
24                let current_size = *self
25                    .maybe_find(old.id())
26                    .ok_or(LbErrKind::Diff(DiffError::OldFileNotFound))?
27                    .file
28                    .timestamped_value
29                    .value
30                    .doc_size();
31
32                match &mut old.timestamped_value.value {
33                    Meta::V1 { doc_size, .. } => {
34                        *doc_size = current_size;
35                    }
36                };
37
38                match &mut new_meta.timestamped_value.value {
39                    Meta::V1 { doc_size, .. } => {
40                        *doc_size = current_size;
41                    }
42                };
43            }
44
45            changes_meta.push(FileDiff { old: old_meta, new: new_meta });
46        }
47
48        self.stage_diff_v2(changes_meta)
49    }
50
51    pub fn stage_diff_v2(
52        self, mut changes: Vec<FileDiff<SignedMeta>>,
53    ) -> LbResult<LazyServerStaged1<'a>> {
54        // Check new.id == old.id
55        for change in &changes {
56            if let Some(old) = &change.old {
57                if old.id() != change.new.id() {
58                    return Err(LbErrKind::Diff(DiffError::DiffMalformed))?;
59                }
60            }
61        }
62
63        // Check for changes to digest
64        for change in &changes {
65            match &change.old {
66                Some(old) => {
67                    if old.timestamped_value.value.document_hmac()
68                        != change.new.timestamped_value.value.document_hmac()
69                    {
70                        return Err(LbErrKind::Diff(DiffError::HmacModificationInvalid))?;
71                    }
72
73                    if old.timestamped_value.value.doc_size()
74                        != change.new.timestamped_value.value.doc_size()
75                    {
76                        return Err(LbErrKind::Diff(DiffError::SizeModificationInvalid))?;
77                    }
78                }
79                None => {
80                    if change.new.timestamped_value.value.doc_size().is_some() {
81                        return Err(LbErrKind::Diff(DiffError::SizeModificationInvalid))?;
82                    }
83                    if change.new.timestamped_value.value.document_hmac().is_some() {
84                        return Err(LbErrKind::Diff(DiffError::HmacModificationInvalid))?;
85                    }
86                }
87            }
88        }
89
90        // Check for race conditions and populate prior size
91        for change in &mut changes {
92            match &change.old {
93                Some(old) => {
94                    let current = &self
95                        .maybe_find(old.id())
96                        .ok_or(LbErrKind::Diff(DiffError::OldFileNotFound))?
97                        .file;
98
99                    if current != old {
100                        return Err(LbErrKind::Diff(DiffError::OldVersionIncorrect))?;
101                    }
102                }
103                None => {
104                    // if you're claiming this file is new, it must be globally unique
105                    if self.tree.files.maybe_find(change.new.id()).is_some() {
106                        return Err(LbErrKind::Diff(DiffError::OldVersionRequired))?;
107                    }
108                }
109            }
110        }
111
112        let now = get_time().0 as u64;
113        let changes = changes
114            .into_iter()
115            .map(|change| change.new.add_time(now))
116            .collect();
117
118        Ok(self.stage(changes))
119    }
120}