1use std::collections::HashSet;
2
3use crate::Lb;
4use crate::model::clock::get_time;
5use crate::model::crypto::{AESKey, DecryptedDocument, EncryptedDocument};
6use crate::model::errors::{LbErrKind, LbResult};
7use crate::model::file_like::FileLike;
8use crate::model::file_metadata::{DocumentHmac, FileType};
9use crate::model::secret_filename::HmacSha256;
10use crate::model::tree_like::TreeLike;
11use crate::model::{compression_service, symkey, validate};
12use hmac::{Mac, NewMac};
13use uuid::Uuid;
14
15use super::activity;
16use super::events::Actor;
17
18impl Lb {
19 #[instrument(level = "debug", skip(self), err(Debug))]
20 pub async fn read_document(
21 &self, id: Uuid, user_activity: bool,
22 ) -> LbResult<DecryptedDocument> {
23 let (_, doc) = self.read_document_with_hmac(id, user_activity).await?;
24 Ok(doc)
25 }
26
27 #[instrument(level = "debug", skip(self, content), err(Debug))]
28 pub async fn write_document(&self, id: Uuid, content: &[u8]) -> LbResult<()> {
29 let (id, key) = {
31 let tx = self.ro_tx().await;
32 let db = tx.db();
33 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
34 let id = match tree.find(&id)?.file_type() {
35 FileType::Document | FileType::Folder => id,
36 FileType::Link { target } => target,
37 };
38 validate::is_document(tree.find(&id)?)?;
39 (id, tree.decrypt_key(&id, &self.keychain)?)
40 };
41
42 let (hmac, encrypted) = compress_encrypt_document(&key, content)?;
44 let encrypted_size = encrypted.value.len();
45 self.docs.insert_pending(id, hmac, &encrypted).await?;
46
47 {
49 let mut tx = self.begin_tx().await;
50 let db = tx.db();
51 let mut tree = (&db.base_metadata)
52 .to_staged(&mut db.local_metadata)
53 .to_lazy();
54 self.docs.promote_pending(id, hmac).await?;
55 tree.overwrite_document_hmac(&id, Some(hmac), Some(encrypted_size), &self.keychain)?;
56 tx.end();
57 }
58
59 self.events.doc_written(id, Actor::User(None));
60 self.add_doc_event(activity::DocEvent::Write(id, get_time().0))
61 .await?;
62
63 Ok(())
64 }
65
66 #[instrument(level = "debug", skip(self), err(Debug))]
67 pub async fn read_document_with_hmac(
68 &self, id: Uuid, user_activity: bool,
69 ) -> LbResult<(Option<DocumentHmac>, DecryptedDocument)> {
70 let mut retried = false;
72 let info: Option<(DocumentHmac, AESKey, Option<EncryptedDocument>)> = loop {
73 let tx = self.ro_tx().await;
74 let db = tx.db();
75 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
76
77 let file = tree.find(&id)?;
78 validate::is_document(file)?;
79 let hmac = file.document_hmac().copied();
80
81 if tree.calculate_deleted(&id)? {
82 return Err(LbErrKind::FileNonexistent.into());
83 }
84
85 match hmac {
86 Some(hmac) => {
87 let key = tree.decrypt_key(&id, &self.keychain)?;
88 let local_blob = self.docs.maybe_get(id, Some(hmac)).await?;
89 if local_blob.is_none() && !retried {
90 drop(tx);
91 self.begin_tx().await.end();
93 retried = true;
94 continue;
95 }
96 break Some((hmac, key, local_blob));
97 }
98 None => break None,
99 }
100 };
101
102 let (hmac, doc) = match info {
105 None => (None, vec![]),
106 Some((hmac, key, local_blob)) => {
107 let encrypted = match local_blob {
108 Some(blob) => blob,
109 None => self.fetch_doc(id, hmac).await?,
111 };
112 let doc = decrypt_decompress_document(&key, &encrypted)?;
113 (Some(hmac), doc)
114 }
115 };
116
117 if user_activity {
118 self.add_doc_event(activity::DocEvent::Read(id, get_time().0))
119 .await?;
120 }
121
122 Ok((hmac, doc))
123 }
124
125 #[instrument(level = "debug", skip(self, content), err(Debug))]
126 pub async fn safe_write(
127 &self, id: Uuid, old_hmac: Option<DocumentHmac>, content: Vec<u8>, origin: Option<Uuid>,
128 ) -> LbResult<DocumentHmac> {
129 let (target_id, key) = {
131 let tx = self.ro_tx().await;
132 let db = tx.db();
133 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
134 let file = tree.find(&id)?;
135 if file.document_hmac() != old_hmac.as_ref() {
136 return Err(LbErrKind::ReReadRequired.into());
137 }
138 let target_id = match file.file_type() {
139 FileType::Document | FileType::Folder => id,
140 FileType::Link { target } => target,
141 };
142 validate::is_document(tree.find(&target_id)?)?;
143 (target_id, tree.decrypt_key(&target_id, &self.keychain)?)
144 };
145
146 let (hmac, encrypted) = compress_encrypt_document(&key, &content)?;
148 let encrypted_size = encrypted.value.len();
149 self.docs
150 .insert_pending(target_id, hmac, &encrypted)
151 .await?;
152
153 {
155 let mut tx = self.begin_tx().await;
156 let db = tx.db();
157 let mut tree = (&db.base_metadata)
158 .to_staged(&mut db.local_metadata)
159 .to_lazy();
160 self.docs.promote_pending(target_id, hmac).await?;
161 if tree.find(&id)?.document_hmac() != old_hmac.as_ref() {
162 return Err(LbErrKind::ReReadRequired.into());
163 }
164 tree.overwrite_document_hmac(
165 &target_id,
166 Some(hmac),
167 Some(encrypted_size),
168 &self.keychain,
169 )?;
170 tx.end();
171 }
172
173 self.events.doc_written(target_id, Actor::User(origin));
174 self.add_doc_event(activity::DocEvent::Write(target_id, get_time().0))
175 .await?;
176
177 Ok(hmac)
178 }
179
180 pub(crate) async fn cleanup(&self) -> LbResult<()> {
181 let tx = self.ro_tx().await;
182 let db = tx.db();
183
184 let tree = db.base_metadata.stage(&db.local_metadata);
185
186 let base_files = tree.base.all_files()?.into_iter();
187 let local_files = tree.staged.all_files()?.into_iter();
188
189 let file_hmacs = base_files
190 .chain(local_files)
191 .filter_map(|f| f.document_hmac().map(|hmac| (*f.id(), *hmac)))
192 .collect::<HashSet<_>>();
193
194 self.docs.retain(file_hmacs).await?;
195
196 drop(tx);
197
198 Ok(())
199 }
200}
201
202fn compress_encrypt_document(
203 key: &AESKey, content: &[u8],
204) -> LbResult<(DocumentHmac, EncryptedDocument)> {
205 let hmac: DocumentHmac = {
206 let mut mac = HmacSha256::new_from_slice(key)
207 .map_err(|err| LbErrKind::Unexpected(format!("hmac creation error: {err:?}")))?;
208 mac.update(content);
209 mac.finalize().into_bytes()
210 }
211 .into();
212 let compressed = compression_service::compress(content)?;
213 let encrypted = symkey::encrypt(key, &compressed)?;
214 Ok((hmac, encrypted))
215}
216
217fn decrypt_decompress_document(
218 key: &AESKey, encrypted: &EncryptedDocument,
219) -> LbResult<DecryptedDocument> {
220 let compressed = symkey::decrypt(key, encrypted)?;
221 let doc = compression_service::decompress(&compressed)?;
222 Ok(doc)
223}