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 crate::access_info::{EncryptedFolderAccessKey, UserAccessInfo, UserAccessMode};
10use crate::account::Account;
11use crate::clock::get_time;
12use crate::crypto::AESKey;
13use crate::file_like::FileLike;
14use crate::secret_filename::SecretFileName;
15use crate::signed_file::SignedFile;
16use crate::{pubkey, symkey, SharedResult};
17
18pub type DocumentHmac = [u8; 32];
19
20#[derive(Serialize, Deserialize, Clone, Debug)]
21pub struct FileMetadata {
22 pub id: Uuid,
23 pub file_type: FileType,
24 pub parent: Uuid,
25 pub name: SecretFileName,
26 pub owner: Owner,
27 pub is_deleted: bool,
28 pub document_hmac: Option<DocumentHmac>,
29 pub user_access_keys: Vec<UserAccessInfo>,
30 pub folder_access_key: EncryptedFolderAccessKey,
31}
32
33impl FileMetadata {
34 pub fn create_root(account: &Account) -> SharedResult<Self> {
35 let id = Uuid::new_v4();
36 let key = symkey::generate_key();
37 let pub_key = account.public_key();
38
39 Ok(FileMetadata {
40 id,
41 file_type: FileType::Folder,
42 parent: id,
43 name: SecretFileName::from_str(&account.username, &key, &key)?,
44 owner: Owner(pub_key),
45 is_deleted: false,
46 document_hmac: None,
47 user_access_keys: vec![UserAccessInfo::encrypt(
48 account,
49 &pub_key,
50 &pub_key,
51 &key,
52 UserAccessMode::Write,
53 )?],
54 folder_access_key: symkey::encrypt(&key, &key)?,
55 })
56 }
57
58 pub fn create(
59 id: Uuid, key: AESKey, owner: &PublicKey, parent: Uuid, parent_key: &AESKey, name: &str,
60 file_type: FileType,
61 ) -> SharedResult<Self> {
62 Ok(FileMetadata {
63 id,
64 file_type,
65 parent,
66 name: SecretFileName::from_str(name, &key, parent_key)?,
67 owner: Owner(*owner),
68 is_deleted: false,
69 document_hmac: None,
70 user_access_keys: Default::default(),
71 folder_access_key: symkey::encrypt(parent_key, &key)?,
72 })
73 }
74
75 pub fn sign(self, account: &Account) -> SharedResult<SignedFile> {
76 pubkey::sign(&account.private_key, self, get_time)
77 }
78}
79
80impl PartialEq for FileMetadata {
82 fn eq(&self, other: &Self) -> bool {
83 self.id == other.id
84 && self.file_type == other.file_type
85 && self.parent == other.parent
86 && self.name == other.name
87 && self.owner == other.owner
88 && self.is_deleted == other.is_deleted
89 && self.document_hmac == other.document_hmac
90 && self.user_access_keys == other.user_access_keys
91 }
92}
93
94impl fmt::Display for FileMetadata {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96 write!(f, "{}", self.display())
97 }
98}
99
100#[derive(Serialize, Deserialize, Eq, Clone, Copy)]
101pub struct Owner(pub PublicKey);
102
103impl Hash for Owner {
104 fn hash<H: Hasher>(&self, state: &mut H) {
105 self.0.serialize().hash(state)
106 }
107}
108
109impl PartialEq for Owner {
110 fn eq(&self, other: &Self) -> bool {
111 self.0.serialize() == other.0.serialize()
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 let new = new.clone();
226 Self { old, new }
227 }
228
229 pub fn edit(old: &F, new: &F) -> Self {
230 let old = Some(old.clone());
231 let new = new.clone();
232 Self { old, new }
233 }
234}