1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
//-
// Copyright (c) 2020, Jason Lingle
//
// This file is part of Crymap.
//
// Crymap is free software: you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// Crymap is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// Crymap. If not, see <http://www.gnu.org/licenses/>.
//! Support for working with a single mailbox.
//!
//! A mailbox is a collection of messages and their related metadata events. It
//! is functionally independent from any associated child mailboxes.
//!
//! The contents of a mailbox directory are (where `UV` is the UID validity in
//! lowercase hex):
//!
//! - `%`. Symlink to `%UV`.
//!
//! - `%UV/u*`. Directories containing messages in the _hierarchical
//! identifier_ scheme described below.
//!
//! - `%UV/c*`. Directories containing state transactions in the _hierarchical
//! identifier_ scheme described below.
//!
//! - `%UV/rollup/*`. Change rollup files.
//!
//! - `%UV/mailbox.toml`. Immutable metadata about this mailbox. Managed by
//! `MailboxPath`.
//!
//! - `%UV/recent`. Maintains a token for the `\Recent` flag. See
//! `recency_token`.
//!
//! - `%subscribe`. Marker file; if present, the mailbox is subscribed.
//! Managed by `MailboxPath`.
//!
//! Subscriptions are managed (in `mailbox_path`) by a "shadow" hierarchy,
//! where each shadow mailbox directory contains:
//!
//! - `%subscribe`. Marker file; if present, the mailbox is subscribed.
//!
//! - Directories containing child mailboxes, each of which is in a
//! subdirectory corresponding to its name. Managed by `MailboxPath`.
//!
//! The distinction between `%` and `%UV` is to prevent confusion if a mailbox
//! is deleted and recreated while open. Mailboxes are opened through their
//! `%UV` path, so any change in UID validity permanently invalidates them. The
//! symlink is used to be able to access metadata statelessly.
//!
//! There are some wonky special cases to support IMAP's wonky data model.
//!
//! If the `%UV` directory is missing, this is a `\Noselect` mailbox. Mailboxes
//! are normally created as dual-use, but IMAP requires that a `DELETE`
//! operation on a dual-use mailbox with child mailboxes must transmogrify it
//! into a folder-like mailbox.
//!
//! In general:
//!
//! - A mailbox exists (i.e., is visible to IMAP) if its directory exists.
//!
//! - A mailbox is selectable if the `%` subdirectory exists. It is assumed
//! that the contents of that subdirectory will not be partially
//! instantiated.
//!
//! - A mailbox is subscribed if it has a `subscribe` file in its shadow
//! directory.
//!
//! Subscriptions are in a "shadow" hierarchy since RFC 3501 requires them to
//! be effectively disconnected from real mailboxes.
//!
//! ## Hierarchical Identifier scheme
//!
//! Messages and state transactions are stored in a scheme that assigns one
//! path to each 32-bit identifier, with the property that identifiers are
//! assigned in strictly ascending order, and that each identifier is written
//! at most once before being permanently expunged.
//!
//! The nominal path for an identifier is derived as follows:
//!
//! - A path element starting with the identifier type (`c` or `u`) and the
//! number of directory levels beneath it.
//!
//! - Zero or more directory levels which are two lowercase hexadecimal digits,
//! representing consecutive bytes from the identifier (MSB-first), starting
//! from the first non-zero byte (inclusive) and ending on the LSB
//! (exclusive).
//!
//! - A path element which is the two lowercase hexadecimal digits of the LSB
//! of the identifier followed by the extension for its type.
//!
//! Examples (for messages):
//! - `1` → `u0/01.eml`
//! - `255` → `u0/ff.eml`
//! - `12345` → `u1/30/39.eml`
//! - `123456` → `u2/01/e2/40.eml`
//! - `16777216` → `u3/01/00/00/00.eml`
//! - `4294967295` → `u3/ff/ff/ff/ff.eml`
//!
//! This scheme is designed to avoid creating excessive directory levels for
//! small mailboxes while keeping each "section" of the tree small enough to
//! iterate efficiently and allowing some garbage collection.
//!
//! The directory-like elements in the path (other than the top one) are each a
//! symlink to a directory of the same name, but suffixed with `.d`.
//!
//! The bottom level in the tree additionally has a symlink named `alloc`.
//! Initially, it points to `.`. When the directory becomes fully allocated or
//! some time thereafter, the symlink is updated to point to `alloc`, i.e., to
//! be self-referential. All ID allocations go through this extra symlink. This
//! allows gravestones to be removed while still allowing the whole directory
//! to be unambiguously considered fully allocated.
//!
//! When an item is expunged, it is replaced with a symlink to itself. A
//! "garbage collection" process can identify directories containing only such
//! gravestones and replace the directory link with a similar broken symlink,
//! allowing the total file count to be kept low.
//!
//! The gravestone scheme enables a number of consistent, atomic operations:
//!
//! - Reading: Open succeeds iff the item exists; fails with `ELOOP` or
//! `ENOENT` if the item was expunged.
//!
//! - Creating: `link()` succeeds iff the item is unallocated; fails with
//! `EEXISTS` or `ELOOP` if it was already allocated.
//!
//! - Expunging: Using `rename()` to replace an item with a looped symlink
//! either succeeds atomically or fails with `ELOOP` if the item was already
//! expunged and a garbage-collection operation cleaned the containing
//! directory up.
//!
//! - Monitoring: Watch the directory that will contain the next item (creating
//! if needed). The next mutation event must involve that item or something
//! after it.
//!
//! Each of these schemes also has an associated `X-guess` file, which contains
//! a LE u32 that indicates the best guess for the most recently allocated
//! item. It is updated non-atomically every time an item is created.
//!
//! Multiple items can be inserted into a hierarchy by the following process.
//!
//! First, all items are inserted into an isolated hierarchy starting at index
//! 16777216 (0x1_00_00_00). The final item count is rounded up to the next
//! power (not multiple) of 256 to get the allocation unit size. Expunged items
//! are added to the target hierarchy until the next item's id would be a
//! multiple of the allocation unit size. Finally, the directory in the
//! isolated hierarchy that corresponds to the level equal to the allocation
//! size is moved in its entirety into the target hierarchy.
//!
//! Batch inserts are only done for messages, since change transactions
//! naturally store multiple changes in one file already.
//!
//! This approach would allow atomic inserts of up to 16777216 items, but we
//! arteficially limit it to 65536 to reduce the complexity of the code and
//! testing and due to the extremely high fragmentation that batches over 65536
//! items would produce.
//!
//! We also only make one attempt at a bulk insert; if we lose a race to
//! another process, we immediately abandon the attempt so that concurrent bulk
//! inserts do not rapidly exhaust the id space.
//!
//! ## Metadata rollup and garbage collection
//!
//! Whenever a read-write mailbox ingests a state transaction whose CID is
//! evenly divisible by 256, it dumps its state into a rollup file whose name
//! is the `Modseq` in base-10. When new mailbox instances are loaded, they
//! read in the file with the greatest `Modseq`.
//!
//! When a read-write mailbox initialises, any rollups which are older than
//! 24hr become candidates for deletion. If there any, the process scans the
//! change directories for transactions that took place before the `Modseq` of
//! the latest deletion candidate and expunges them all. A garbage collection
//! is performed on the hierarchical identifier scheme. Finally, the obsolete
//! rollups are deleted.
//!
//! The 24 hour grace period is to ensure that backup processes are essentially
//! guaranteed to either see the separate transactions or the rollup that
//! contains them (i.e., to prevent a case where the backup sees no rollups,
//! but then Crymap finishes a rollup and deletes the transactions, then the
//! backup looks at the transactions directory and finds nothing there either).
//!
//! ## Delivery of new messages
//!
//! When a message is to be delivered, it is first fully buffered into a
//! temporary file.
//!
//! We then need to find the UID to assign it. The directory structure used for
//! messages has a simple total order, so we could simply walk down the "right"
//! of the tree to the third level and see if it has any space. However,
//! listing each directory level involves 256 I/O operations. Instead, we use
//! exponential probing starting from either the last known UID plus one in
//! `seqnum` or 1 followed by binary search to find the first unused UID.
//!
//! Create any directories needed for the new UID, and try to rename the
//! temporary file into place. If that fails due to a conflict, increment the
//! UID and try again.
//!
//! ## Message format
//!
//! Each message consists of a u32 LE `size_xor` immediately followed by a data
//! stream. The data stream starts with a `u16` indicating the size of the
//! metadata, then a `MessageMetadata` element in CBOR, and then the raw
//! message text.
//!
//! The two size fields (`size_xor` and the one in the metadata) together
//! encode the size of the message before compression without revealing this in
//! the cleartext and without requiring buffering. `size_xor` is initially
//! written to 0 and a random value is chosen for `size`. Once the message is
//! fully written, the actual length is XORed with `size` and the result is
//! written over `size_xor`.
//!
//! ## Change transaction format and rollup format
//!
//! Change transactions and rollups are stored as unframed `data_stream`s. The
//! cleartext content is CBOR of either `StateTransaction` or `MailboxState`.
//!
//! ## About the layout of this module
//!
//! This module is collectively a single abstraction, i.e., it should be
//! thought of as one large rust file. It is simply split apart because it's
//! unwieldy otherwise.
// Basic struct definitions
// Internal support --- R/W of messages and state transactions
// Also includes low-level APPEND-like operation
// IMAP commands
// Methods are not 1:1 in cases where the IMAP model does not naturally fit the
// architecture. E.g., SELECT, EXAMINE, and STATUS are all the same operation
// and do not include QRESYNC support, which is a separate operation. It is up
// to the IMAP protocol layer to decompose/recompose/reformat these
// discrepancies.
// EXPUNGE, UID EXPUNGE
// FETCH, UID FETCH
// STORE, UID STORE
// IDLE, notifications
// NOOP, CHECK, during IDLE, after commands
// SEARCH, UID SEARCH
// SELECT, EXAMINE, STATUS, also garbage collection
pub use ;
pub use ;
pub use ;
pub use BufferedMessage;