1use std::fmt::{self, Debug, Formatter};
2use std::hash::{Hash, Hasher};
3use std::str::FromStr;
4
5use libsecp256k1::PublicKey;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use super::access_info::{EncryptedFolderAccessKey, UserAccessInfo, UserAccessMode};
10use super::account::Account;
11use super::clock::get_time;
12use super::errors::LbResult;
13use crate::model::crypto::AESKey;
14use crate::model::file_like::FileLike;
15use crate::model::secret_filename::SecretFileName;
16use crate::model::signed_file::SignedFile;
17use crate::model::{pubkey, symkey};
18use crate::service::keychain::Keychain;
19
20pub type DocumentHmac = [u8; 32];
21
22#[derive(Serialize, Deserialize, Clone, Debug)]
23pub struct FileMetadata {
24 pub id: Uuid,
25 pub file_type: FileType,
26 pub parent: Uuid,
27 pub name: SecretFileName,
28 pub owner: Owner,
29 pub is_deleted: bool,
30 pub document_hmac: Option<DocumentHmac>,
31 pub user_access_keys: Vec<UserAccessInfo>,
32 pub folder_access_key: EncryptedFolderAccessKey,
33}
34
35impl FileMetadata {
36 pub fn create_root(account: &Account) -> LbResult<Self> {
37 let id = Uuid::new_v4();
38 let key = symkey::generate_key();
39 let pub_key = account.public_key();
40
41 Ok(FileMetadata {
42 id,
43 file_type: FileType::Folder,
44 parent: id,
45 name: SecretFileName::from_str(&account.username, &key, &key)?,
46 owner: Owner(pub_key),
47 is_deleted: false,
48 document_hmac: None,
49 user_access_keys: vec![UserAccessInfo::encrypt(
50 account,
51 &pub_key,
52 &pub_key,
53 &key,
54 UserAccessMode::Write,
55 )?],
56 folder_access_key: symkey::encrypt(&key, &key)?,
57 })
58 }
59
60 pub fn create(
61 id: Uuid, key: AESKey, owner: &PublicKey, parent: Uuid, parent_key: &AESKey, name: &str,
62 file_type: FileType,
63 ) -> LbResult<Self> {
64 Ok(FileMetadata {
65 id,
66 file_type,
67 parent,
68 name: SecretFileName::from_str(name, &key, parent_key)?,
69 owner: Owner(*owner),
70 is_deleted: false,
71 document_hmac: None,
72 user_access_keys: Default::default(),
73 folder_access_key: symkey::encrypt(parent_key, &key)?,
74 })
75 }
76
77 pub fn sign(self, keychain: &Keychain) -> LbResult<SignedFile> {
78 pubkey::sign(&keychain.get_account()?.private_key, &keychain.get_pk()?, self, get_time)
79 }
80
81 pub fn sign_with(self, account: &Account) -> LbResult<SignedFile> {
82 pubkey::sign(&account.private_key, &account.public_key(), self, get_time)
83 }
84}
85
86impl PartialEq for FileMetadata {
88 fn eq(&self, other: &Self) -> bool {
89 self.id == other.id
90 && self.file_type == other.file_type
91 && self.parent == other.parent
92 && self.name == other.name
93 && self.owner == other.owner
94 && self.is_deleted == other.is_deleted
95 && self.document_hmac == other.document_hmac
96 && self.user_access_keys == other.user_access_keys
97 }
98}
99
100impl fmt::Display for FileMetadata {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 write!(f, "{}", self.display())
103 }
104}
105
106#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Copy)]
107pub struct Owner(pub PublicKey);
108
109impl Hash for Owner {
110 fn hash<H: Hasher>(&self, state: &mut H) {
111 self.0.serialize().hash(state)
112 }
113}
114
115impl Debug for Owner {
116 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117 write!(f, "pub_key:{}", base64::encode(self.0.serialize_compressed()))
118 }
119}
120
121#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize, Copy)]
122pub enum FileType {
123 Document,
124 Folder,
125 Link { target: Uuid },
126}
127
128impl FromStr for FileType {
129 type Err = ();
130 fn from_str(input: &str) -> Result<FileType, Self::Err> {
131 match input {
132 "Document" => Ok(FileType::Document),
133 "Folder" => Ok(FileType::Folder),
134 _ => Err(()),
135 }
136 }
137}
138
139impl fmt::Display for FileType {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 write!(f, "{self:?}")
142 }
143}
144
145#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
146pub struct FileDiff<F: FileLike> {
147 pub old: Option<F>,
148 pub new: F,
149}
150
151impl<F: FileLike> Debug for FileDiff<F> {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 let mut result = &mut f.debug_struct("FileDiff");
154 result = result.field("id", self.id());
155 for diff in self.diff() {
156 result = match diff {
157 Diff::New => result.field("is_new", &true),
158 Diff::Parent => result.field("new_parent", &self.new.parent()),
159 Diff::Name => result.field("new_name", &self.new.secret_name()),
160 Diff::Owner => result.field("new_owner", &self.new.owner()),
161 Diff::Deleted => result.field("new_deleted", &self.new.explicitly_deleted()),
162 Diff::Hmac => result.field("new_hmac", &self.new.document_hmac()),
163 Diff::UserKeys => result.field("new_user_keys", &true),
164 };
165 }
166 result.finish()
167 }
168}
169
170#[derive(PartialEq, Eq, Debug)]
171pub enum Diff {
172 New,
173 Parent,
174 Name,
175 Owner,
176 Deleted,
177 Hmac,
178 UserKeys,
179}
180
181impl<F: FileLike> FileDiff<F> {
182 pub fn id(&self) -> &Uuid {
183 self.new.id()
184 }
185
186 pub fn diff(&self) -> Vec<Diff> {
187 let new = &self.new;
188 use Diff::*;
189 match &self.old {
190 None => vec![New],
191 Some(old) => {
192 let mut changes = vec![];
193
194 if old.parent() != new.parent() {
195 changes.push(Parent)
196 }
197
198 if old.secret_name() != new.secret_name() {
199 changes.push(Name)
200 }
201
202 if old.owner() != new.owner() {
203 changes.push(Owner)
204 }
205
206 if old.explicitly_deleted() != new.explicitly_deleted() {
207 changes.push(Deleted)
208 }
209
210 if old.document_hmac() != new.document_hmac() {
211 changes.push(Hmac);
212 }
213
214 if old.user_access_keys() != new.user_access_keys() {
215 changes.push(UserKeys);
216 }
217
218 changes
219 }
220 }
221 }
222
223 pub fn new(new: F) -> Self {
224 let old = None;
225 Self { old, new }
226 }
227
228 pub fn edit(old: F, new: F) -> Self {
229 let old = Some(old);
230 Self { old, new }
231 }
232}