1use std::collections::HashSet;
2
3use crate::LocalLb;
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 LocalLb {
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);
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 info: Option<(DocumentHmac, AESKey, Option<EncryptedDocument>)> = {
72 let tx = self.ro_tx().await;
73 let db = tx.db();
74 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
75
76 let file = tree.find(&id)?;
77 validate::is_document(file)?;
78 let hmac = file.document_hmac().copied();
79
80 if tree.calculate_deleted(&id)? {
81 return Err(LbErrKind::FileNonexistent.into());
82 }
83
84 match hmac {
85 Some(hmac) => {
86 let key = tree.decrypt_key(&id, &self.keychain)?;
87 let local_blob = if self.docs.exists(id, Some(hmac)) {
88 Some(self.docs.get(id, Some(hmac)).await?)
89 } else {
90 None
91 };
92 Some((hmac, key, local_blob))
93 }
94 None => None,
95 }
96 };
97
98 let (hmac, doc) = match info {
101 None => (None, vec![]),
102 Some((hmac, key, local_blob)) => {
103 let encrypted = match local_blob {
104 Some(blob) => blob,
105 None => self.fetch_doc(id, hmac).await?,
107 };
108 let doc = decrypt_decompress_document(&key, &encrypted)?;
109 (Some(hmac), doc)
110 }
111 };
112
113 if user_activity {
114 self.add_doc_event(activity::DocEvent::Read(id, get_time().0))
115 .await?;
116 }
117
118 Ok((hmac, doc))
119 }
120
121 #[instrument(level = "debug", skip(self, content), err(Debug))]
122 pub async fn safe_write(
123 &self, id: Uuid, old_hmac: Option<DocumentHmac>, content: Vec<u8>,
124 ) -> LbResult<DocumentHmac> {
125 let (target_id, key) = {
127 let tx = self.ro_tx().await;
128 let db = tx.db();
129 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
130 let file = tree.find(&id)?;
131 if file.document_hmac() != old_hmac.as_ref() {
132 return Err(LbErrKind::ReReadRequired.into());
133 }
134 let target_id = match file.file_type() {
135 FileType::Document | FileType::Folder => id,
136 FileType::Link { target } => target,
137 };
138 validate::is_document(tree.find(&target_id)?)?;
139 (target_id, tree.decrypt_key(&target_id, &self.keychain)?)
140 };
141
142 let (hmac, encrypted) = compress_encrypt_document(&key, &content)?;
144 let encrypted_size = encrypted.value.len();
145 self.docs
146 .insert_pending(target_id, hmac, &encrypted)
147 .await?;
148
149 {
151 let mut tx = self.begin_tx().await;
152 let db = tx.db();
153 let mut tree = (&db.base_metadata)
154 .to_staged(&mut db.local_metadata)
155 .to_lazy();
156 self.docs.promote_pending(target_id, hmac).await?;
157 if tree.find(&id)?.document_hmac() != old_hmac.as_ref() {
158 return Err(LbErrKind::ReReadRequired.into());
159 }
160 tree.overwrite_document_hmac(
161 &target_id,
162 Some(hmac),
163 Some(encrypted_size),
164 &self.keychain,
165 )?;
166 tx.end();
167 }
168
169 self.events.doc_written(target_id, Actor::User);
173 self.add_doc_event(activity::DocEvent::Write(target_id, get_time().0))
174 .await?;
175
176 Ok(hmac)
177 }
178
179 pub(crate) async fn cleanup(&self) -> LbResult<()> {
180 let tx = self.ro_tx().await;
181 let db = tx.db();
182
183 let tree = db.base_metadata.stage(&db.local_metadata);
184
185 let base_files = tree.base.all_files()?.into_iter();
186 let local_files = tree.staged.all_files()?.into_iter();
187
188 let file_hmacs = base_files
189 .chain(local_files)
190 .filter_map(|f| f.document_hmac().map(|hmac| (*f.id(), *hmac)))
191 .collect::<HashSet<_>>();
192
193 self.docs.retain(file_hmacs).await?;
194
195 drop(tx);
196
197 Ok(())
198 }
199}
200
201fn compress_encrypt_document(
202 key: &AESKey, content: &[u8],
203) -> LbResult<(DocumentHmac, EncryptedDocument)> {
204 let hmac: DocumentHmac = {
205 let mut mac = HmacSha256::new_from_slice(key)
206 .map_err(|err| LbErrKind::Unexpected(format!("hmac creation error: {err:?}")))?;
207 mac.update(content);
208 mac.finalize().into_bytes()
209 }
210 .into();
211 let compressed = compression_service::compress(content)?;
212 let encrypted = symkey::encrypt(key, &compressed)?;
213 Ok((hmac, encrypted))
214}
215
216fn decrypt_decompress_document(
217 key: &AESKey, encrypted: &EncryptedDocument,
218) -> LbResult<DecryptedDocument> {
219 let compressed = symkey::decrypt(key, encrypted)?;
220 let doc = compression_service::decompress(&compressed)?;
221 Ok(doc)
222}