1use std::io;
2
3use serde::{Deserialize, Serialize};
4use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
5use uuid::Uuid;
6
7use crate::model::account::Account;
8use crate::model::api::{
9 AccountFilter, AccountIdentifier, AdminSetUserTierInfo, ServerIndex, StripeAccountTier,
10};
11use crate::model::file::ShareMode;
12use crate::model::file_metadata::{DocumentHmac, FileType};
13use crate::model::path_ops::Filter;
14use crate::service::activity::RankingWeights;
15use crate::service::events::Event;
16
17#[derive(Debug, Serialize, Deserialize)]
18pub enum Frame {
19 Request { seq: u64, body: Request },
20 Response { seq: u64, output: Vec<u8> },
21 Event { stream_seq: u64, body: Event },
22 EventEnd { stream_seq: u64 },
23}
24
25impl Frame {
26 pub async fn read<R: AsyncRead + Unpin>(r: &mut R) -> io::Result<Self> {
27 let mut len_buf = [0u8; 4];
28 r.read_exact(&mut len_buf).await?;
29 let len = u32::from_le_bytes(len_buf) as usize;
30 let mut buf = vec![0u8; len];
31 r.read_exact(&mut buf).await?;
32 bincode::deserialize(&buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
33 }
34
35 pub async fn write<W: AsyncWrite + Unpin>(&self, w: &mut W) -> io::Result<()> {
36 let bytes =
37 bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
38 let len: u32 = bytes.len().try_into().map_err(|_| {
39 io::Error::new(
40 io::ErrorKind::InvalidData,
41 format!("frame {} bytes does not fit in a u32 length prefix", bytes.len()),
42 )
43 })?;
44 w.write_all(&len.to_le_bytes()).await?;
45 w.write_all(&bytes).await?;
46 w.flush().await
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub enum Request {
52 CreateAccount {
53 username: String,
54 api_url: String,
55 welcome_doc: bool,
56 },
57 ImportAccount {
58 key: String,
59 api_url: Option<String>,
60 },
61 ImportAccountPrivateKeyV1 {
62 account: Account,
63 },
64 ImportAccountPhrase {
65 phrase: Vec<String>,
66 api_url: String,
67 },
68 DeleteAccount,
69 GetAccount,
70
71 SuggestedDocs {
72 settings: RankingWeights,
73 },
74 ClearSuggested,
75 ClearSuggestedId {
76 id: Uuid,
77 },
78 AppForegrounded,
79
80 DisappearAccount {
81 username: String,
82 },
83 DisappearFile {
84 id: Uuid,
85 },
86 ListUsers {
87 filter: Option<AccountFilter>,
88 },
89 GetAccountInfo {
90 identifier: AccountIdentifier,
91 },
92 AdminValidateAccount {
93 username: String,
94 },
95 AdminValidateServer,
96 AdminFileInfo {
97 id: Uuid,
98 },
99 RebuildIndex {
100 index: ServerIndex,
101 },
102 SetUserTier {
103 username: String,
104 info: AdminSetUserTierInfo,
105 },
106
107 UpgradeAccountStripe {
108 account_tier: StripeAccountTier,
109 },
110 UpgradeAccountGooglePlay {
111 purchase_token: String,
112 account_id: String,
113 },
114 UpgradeAccountAppStore {
115 original_transaction_id: String,
116 app_account_token: String,
117 },
118 CancelSubscription,
119 GetSubscriptionInfo,
120
121 #[cfg(not(target_family = "wasm"))]
122 RecentPanic,
123 #[cfg(not(target_family = "wasm"))]
124 WritePanicToFile {
125 error_header: String,
126 bt: String,
127 },
128 #[cfg(not(target_family = "wasm"))]
129 DebugInfo {
130 os_info: String,
131 check_docs: bool,
132 },
133
134 ReadDocument {
135 id: Uuid,
136 user_activity: bool,
137 },
138 WriteDocument {
139 id: Uuid,
140 content: Vec<u8>,
141 },
142 ReadDocumentWithHmac {
143 id: Uuid,
144 user_activity: bool,
145 },
146 SafeWrite {
147 id: Uuid,
148 old_hmac: Option<DocumentHmac>,
149 content: Vec<u8>,
150 origin: Option<Uuid>,
151 },
152
153 CreateFile {
154 name: String,
155 parent: Uuid,
156 file_type: FileType,
157 },
158 RenameFile {
159 id: Uuid,
160 new_name: String,
161 },
162 MoveFile {
163 id: Uuid,
164 new_parent: Uuid,
165 },
166 Delete {
167 id: Uuid,
168 },
169 Root,
170 ListMetadatas,
171 GetChildren {
172 id: Uuid,
173 },
174 GetAndGetChildrenRecursively {
175 id: Uuid,
176 },
177 GetFileById {
178 id: Uuid,
179 },
180 GetFileLinkUrl {
181 id: Uuid,
182 },
183 LocalChanges,
184
185 TestRepoIntegrity {
186 check_docs: bool,
187 },
188
189 CreateLinkAtPath {
190 path: String,
191 target_id: Uuid,
192 },
193 CreateAtPath {
194 path: String,
195 },
196 GetByPath {
197 path: String,
198 },
199 GetPathById {
200 id: Uuid,
201 },
202 ListPaths {
203 filter: Option<Filter>,
204 },
205 ListPathsWithIds {
206 filter: Option<Filter>,
207 },
208
209 ShareFile {
210 id: Uuid,
211 username: String,
212 mode: ShareMode,
213 },
214 GetPendingShares,
215 GetPendingShareFiles,
216 KnownUsernames,
217 RejectShare {
218 id: Uuid,
219 },
220
221 PinFile {
222 id: Uuid,
223 },
224 UnpinFile {
225 id: Uuid,
226 },
227 ListPinned,
228
229 GetUsage,
230
231 Sync,
232 Status,
233 GetLastSynced,
234 GetLastSyncedHuman,
235 Subscribe,
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use tokio::io::duplex;
242
243 #[tokio::test]
244 async fn frame_round_trip() {
245 let (mut a, mut b) = duplex(64 * 1024);
246 let frame = Frame::Request { seq: 7, body: Request::Sync };
247 frame.write(&mut a).await.unwrap();
248 let got = Frame::read(&mut b).await.unwrap();
249 assert!(matches!(got, Frame::Request { seq: 7, body: Request::Sync }));
250 }
251}