1use super::super::{Context, hash, valid};
2use super::{codec::Descriptor, *};
3use crate::sync::seed_claim::membership::Membership;
4use crate::{
5 attachments::lifecycle::LifecyclePolicy,
6 db::{Database, begin_immediate},
7 sync::seed_claim::Secret,
8};
9use anyhow::{Context as _, Result, ensure};
10use sqlx::SqliteConnection;
11
12async fn load(
13 conn: &mut SqliteConnection,
14 object: &[u8; 32],
15 commitment: &[u8; 32],
16) -> Result<(Descriptor, bool)> {
17 let (bytes, complete): (Vec<u8>, bool) =
18 sqlx::query_as("SELECT descriptor,complete FROM server_e2ee_images WHERE object=?")
19 .bind(object.as_slice())
20 .fetch_optional(conn)
21 .await?
22 .context("error encrypted-image-unknown")?;
23 valid(hash(&bytes) == *commitment)?;
24 let d = Descriptor::decode(&bytes)?;
25 valid(d.object == *object)?;
26 Ok((d, complete))
27}
28async fn refresh(conn: &mut SqliteConnection, now: i64) -> Result<()> {
33 sqlx::query("UPDATE server_e2ee_images SET unreferenced_at=CASE WHEN EXISTS(SELECT 1 FROM server_e2ee_image_references r JOIN server_e2ee_image_parents p ON p.workspace=r.workspace AND p.parent=r.parent WHERE r.object=server_e2ee_images.object AND r.deleted=0 AND (p.deleted=0 OR p.protected=1 OR p.version IS NULL)) OR EXISTS(SELECT 1 FROM server_e2ee_image_tickets t WHERE t.object=server_e2ee_images.object AND t.expires_at>?) THEN NULL ELSE COALESCE(unreferenced_at,?) END").bind(now).bind(now).execute(conn).await?;
34 Ok(())
35}
36pub(in crate::sync::encrypted_tail) async fn retain_parent(
39 conn: &mut SqliteConnection,
40 workspace: &str,
41 parent: &str,
42) -> Result<()> {
43 sqlx::query("UPDATE server_e2ee_images SET unreferenced_at=NULL WHERE unreferenced_at IS NOT NULL AND object IN (SELECT r.object FROM server_e2ee_image_references r JOIN server_e2ee_image_parents p ON p.workspace=r.workspace AND p.parent=r.parent WHERE r.workspace=? AND r.parent=? AND r.deleted=0 AND (p.deleted=0 OR p.protected=1 OR p.version IS NULL))").bind(workspace).bind(parent).execute(conn).await?;
44 Ok(())
45}
46async fn protected(
47 conn: &mut SqliteConnection,
48 object: &[u8; 32],
49 workspace: &str,
50) -> Result<bool> {
51 Ok(sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_references r JOIN server_e2ee_image_parents p ON p.workspace=r.workspace AND p.parent=r.parent WHERE r.object=? AND r.workspace=? AND r.deleted=0 AND (p.deleted=0 OR p.protected=1 OR p.version IS NULL))").bind(object.as_slice()).bind(workspace).fetch_one(conn).await?)
52}
53async fn scope(conn: &mut SqliteConnection, object: &[u8; 32], workspace: &str) -> Result<()> {
54 valid(
55 sqlx::query_scalar(
56 "SELECT EXISTS(SELECT 1 FROM server_e2ee_image_scopes WHERE object=? AND workspace=?)",
57 )
58 .bind(object.as_slice())
59 .bind(workspace)
60 .fetch_one(conn)
61 .await?,
62 )
63}
64async fn reserve(
65 conn: &mut SqliteConnection,
66 d: &Descriptor,
67 workspace: &str,
68 device: &[u8; 32],
69 quota: i64,
70 now: i64,
71) -> Result<()> {
72 sqlx::query("DELETE FROM server_e2ee_image_tickets WHERE expires_at<=?")
73 .bind(now)
74 .execute(&mut *conn)
75 .await?;
76 let existing: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_tickets WHERE object=? AND workspace=? AND device=? AND expires_at>?)").bind(d.object.as_slice()).bind(workspace).bind(device.as_slice()).bind(now).fetch_one(&mut *conn).await?;
77 if existing {
78 return Ok(());
79 }
80 let reactivating: bool = sqlx::query_scalar("SELECT origin IS NULL AND NOT EXISTS(SELECT 1 FROM server_e2ee_image_chunks c WHERE c.object=i.object) AND NOT EXISTS(SELECT 1 FROM server_e2ee_image_tickets t WHERE t.object=i.object AND t.expires_at>?) FROM server_e2ee_images i WHERE object=?")
81 .bind(now).bind(d.object.as_slice()).fetch_one(&mut *conn).await?;
82 if reactivating {
83 let active: i64 = sqlx::query_scalar("SELECT count(*) FROM server_e2ee_images i WHERE origin IS NULL AND (EXISTS(SELECT 1 FROM server_e2ee_image_chunks c WHERE c.object=i.object) OR EXISTS(SELECT 1 FROM server_e2ee_image_tickets t WHERE t.object=i.object AND t.expires_at>?))")
84 .bind(now).fetch_one(&mut *conn).await?;
85 valid(active < 128)?;
86 }
87 let counted = protected(conn, &d.object, workspace).await? || sqlx::query_scalar::<_,bool>("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_tickets WHERE object=? AND workspace=? AND expires_at>?)").bind(d.object.as_slice()).bind(workspace).bind(now).fetch_one(&mut *conn).await?;
88 if !counted {
89 let used: i64 = sqlx::query_scalar("SELECT COALESCE(SUM(i.byte_size),0) FROM server_e2ee_images i WHERE EXISTS(SELECT 1 FROM server_e2ee_image_references r JOIN server_e2ee_image_parents p ON p.workspace=r.workspace AND p.parent=r.parent WHERE r.object=i.object AND r.workspace=? AND r.deleted=0 AND (p.deleted=0 OR p.protected=1 OR p.version IS NULL)) OR EXISTS(SELECT 1 FROM server_e2ee_image_tickets t WHERE t.object=i.object AND t.workspace=? AND t.expires_at>?)").bind(workspace).bind(workspace).bind(now).fetch_one(&mut *conn).await?;
90 ensure!(
91 used.checked_add(d.byte_size()).is_some_and(|n| n <= quota),
92 "error attachment-quota-exceeded"
93 );
94 }
95 let count: i64 =
96 sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_tickets WHERE expires_at>?")
97 .bind(now)
98 .fetch_one(&mut *conn)
99 .await?;
100 valid(count < 128)?;
101 let mut id = [0; 32];
102 getrandom::fill(&mut id).map_err(|_| anyhow::anyhow!("error encrypted-image-entropy"))?;
103 let expires = now
104 .checked_add(crate::attachments::lifecycle::LEASE_TTL.as_secs() as i64)
105 .context("error encrypted-image-clock")?;
106 sqlx::query("INSERT INTO server_e2ee_image_tickets(reservation,object,workspace,device,expires_at) VALUES(?,?,?,?,?) ON CONFLICT(object,workspace,device) DO UPDATE SET reservation=excluded.reservation,expires_at=excluded.expires_at")
107 .bind(id.as_slice()).bind(d.object.as_slice()).bind(workspace).bind(device.as_slice()).bind(expires).execute(&mut *conn).await?;
108 sqlx::query("UPDATE server_e2ee_images SET unreferenced_at=NULL WHERE object=?")
109 .bind(d.object.as_slice())
110 .execute(conn)
111 .await?;
112 Ok(())
113}
114async fn ticket(
115 conn: &mut SqliteConnection,
116 object: &[u8; 32],
117 workspace: &str,
118 device: &[u8; 32],
119 ticket: &Ticket,
120 now: i64,
121) -> Result<()> {
122 valid(sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_tickets WHERE object=? AND workspace=? AND device=? AND reservation=? AND expires_at>?)").bind(object.as_slice()).bind(workspace).bind(device.as_slice()).bind(ticket.reservation.as_slice()).bind(now).fetch_one(conn).await?)
123}
124async fn status(
125 conn: &mut SqliteConnection,
126 d: &Descriptor,
127 workspace: &str,
128 device: &[u8; 32],
129 now: i64,
130) -> Result<Reply> {
131 let complete: bool =
132 sqlx::query_scalar("SELECT complete FROM server_e2ee_images WHERE object=?")
133 .bind(d.object.as_slice())
134 .fetch_one(&mut *conn)
135 .await?;
136 let indices: Vec<i64> = sqlx::query_scalar(
137 "SELECT chunk_index FROM server_e2ee_image_chunks WHERE object=? ORDER BY chunk_index",
138 )
139 .bind(d.object.as_slice())
140 .fetch_all(&mut *conn)
141 .await?;
142 let t:Option<(Vec<u8>,i64)>=sqlx::query_as("SELECT reservation,expires_at FROM server_e2ee_image_tickets WHERE object=? AND workspace=? AND device=? AND expires_at>?").bind(d.object.as_slice()).bind(workspace).bind(device.as_slice()).bind(now).fetch_optional(conn).await?;
143 let (reservation, expires_at) = match t {
144 Some((id, e)) => (
145 Some(
146 id.try_into()
147 .map_err(|_| anyhow::anyhow!("error encrypted-image-storage"))?,
148 ),
149 Some(e),
150 ),
151 None => (None, None),
152 };
153 Ok(Reply::Status(Status {
154 complete,
155 missing: (0..d.artifact.chunks.len())
156 .filter(|i| !indices.contains(&(*i as i64)))
157 .collect(),
158 reservation,
159 expires_at,
160 }))
161}
162async fn eligible_object(
163 conn: &mut SqliteConnection,
164 m: &Membership,
165 d: &Descriptor,
166) -> Result<()> {
167 valid(m.generations().iter().any(|g| g.id == d.generation))?;
168 if d.generation != m.current_generation().id {
169 let admitted: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_images WHERE object=? AND origin IS NOT NULL AND descriptor=?)")
170 .bind(d.object.as_slice()).bind(d.encode()?).fetch_one(conn).await?;
171 valid(admitted)?;
172 }
173 Ok(())
174}
175impl Database {
176 pub async fn prune_encrypted_images(
180 &self,
181 grace: std::time::Duration,
182 limit: usize,
183 ) -> Result<usize> {
184 let mut conn = self.acquire_writer().await?;
185 let mut tx = begin_immediate(&mut conn).await?;
186 let now = chrono::Utc::now().timestamp();
187 refresh(&mut tx, now).await?;
188 let cutoff = now
189 .checked_sub(i64::try_from(grace.as_secs())?)
190 .context("error encrypted-image-clock")?;
191 let objects:Vec<Vec<u8>>=sqlx::query_scalar("SELECT object FROM server_e2ee_images WHERE unreferenced_at<=? AND (complete=1 OR EXISTS(SELECT 1 FROM server_e2ee_image_chunks c WHERE c.object=server_e2ee_images.object)) ORDER BY object LIMIT ?").bind(cutoff).bind(i64::try_from(limit)?).fetch_all(&mut *tx).await?;
192 for object in &objects {
195 sqlx::query("UPDATE server_e2ee_images SET complete=0 WHERE object=?")
196 .bind(object)
197 .execute(&mut *tx)
198 .await?;
199 sqlx::query("DELETE FROM server_e2ee_image_chunks WHERE object=?")
200 .bind(object)
201 .execute(&mut *tx)
202 .await?;
203 }
204 tx.commit().await?;
205 Ok(objects.len())
206 }
207 pub async fn encrypted_image_exchange(
208 &self,
209 context: &Context,
210 bearer: &Secret,
211 op: Operation,
212 policy: LifecyclePolicy,
213 ) -> Result<Reply> {
214 let mut conn = self.acquire_writer().await?;
215 let mut tx = begin_immediate(&mut conn).await?;
216 let current =
217 crate::sync::seed_claim::membership::persistence::current(self, &mut tx).await?;
218 current
219 .membership
220 .authenticate(&context.authentication(bearer), false)?;
221 ensure!(
222 !current.membership.rotation_pending()
223 || matches!(&op, Operation::Status { .. } | Operation::Read { .. }),
224 "error membership-rotation-pending"
225 );
226 let binding = current.membership.publication().binding();
227 valid(
228 context.stream == binding.stream_id
229 && context.descriptor == binding.descriptor_commitment,
230 )?;
231 let now = chrono::Utc::now().timestamp();
232 let reply = match op {
233 Operation::Declare {
234 workspace,
235 descriptor,
236 } => {
237 workspace.parse::<crate::ids::WorkspaceId>()?;
238 let d = Descriptor::decode(&descriptor)?;
239 valid(d.vault == context.vault && d.stream == context.stream)?;
240 eligible_object(&mut tx, ¤t.membership, &d).await?;
241 let old: Option<Vec<u8>> =
242 sqlx::query_scalar("SELECT descriptor FROM server_e2ee_images WHERE object=?")
243 .bind(d.object.as_slice())
244 .fetch_optional(&mut *tx)
245 .await?;
246 if let Some(old) = old {
247 valid(old == descriptor)?;
248 } else {
249 let total: i64 = sqlx::query_scalar("SELECT count(*) FROM server_e2ee_images")
250 .fetch_one(&mut *tx)
251 .await?;
252 valid(total < 65536)?;
253 sqlx::query("INSERT INTO server_e2ee_images(object,byte_size,descriptor,complete,unreferenced_at) VALUES(?,?,?,0,?)").bind(d.object.as_slice()).bind(d.byte_size()).bind(&descriptor).bind(now).execute(&mut *tx).await?;
254 }
255 let known:bool=sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_scopes WHERE object=? AND workspace=?)").bind(d.object.as_slice()).bind(&workspace).fetch_one(&mut *tx).await?;
256 if !known {
257 let count: i64 =
258 sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_scopes")
259 .fetch_one(&mut *tx)
260 .await?;
261 valid(count < 262144)?;
262 }
263 sqlx::query(
264 "INSERT OR IGNORE INTO server_e2ee_image_scopes(object,workspace) VALUES(?,?)",
265 )
266 .bind(d.object.as_slice())
267 .bind(&workspace)
268 .execute(&mut *tx)
269 .await?;
270 load(&mut tx, &d.object, &hash(&descriptor)).await?;
271 reserve(
272 &mut tx,
273 &d,
274 &workspace,
275 &context.device,
276 policy.quota_bytes,
277 now,
278 )
279 .await?;
280 status(&mut tx, &d, &workspace, &context.device, now).await?
281 }
282 other => dispatch_existing(&mut tx, context, ¤t.membership, other, now).await?,
283 };
284 tx.commit().await?;
285 Ok(reply)
286 }
287}
288async fn dispatch_existing(
289 conn: &mut SqliteConnection,
290 context: &Context,
291 membership: &Membership,
292 op: Operation,
293 now: i64,
294) -> Result<Reply> {
295 let (workspace, object, commitment) = match &op {
296 Operation::Status {
297 workspace,
298 object,
299 descriptor_commitment,
300 }
301 | Operation::Put {
302 workspace,
303 object,
304 descriptor_commitment,
305 ..
306 }
307 | Operation::Complete {
308 workspace,
309 object,
310 descriptor_commitment,
311 ..
312 }
313 | Operation::Read {
314 workspace,
315 object,
316 descriptor_commitment,
317 ..
318 }
319 | Operation::Release {
320 workspace,
321 object,
322 descriptor_commitment,
323 ..
324 } => (workspace, *object, *descriptor_commitment),
325 _ => unreachable!(),
326 };
327 workspace.parse::<crate::ids::WorkspaceId>()?;
328 scope(conn, &object, workspace).await?;
329 let (d, _) = load(conn, &object, &commitment).await?;
330 valid(d.vault == context.vault && d.stream == context.stream)?;
331 eligible_object(conn, membership, &d).await?;
332 match &op {
333 Operation::Status { .. } => status(conn, &d, workspace, &context.device, now).await,
334 Operation::Read { index, .. } => {
335 valid(*index < d.artifact.chunks.len())?;
336 let admitted: bool = sqlx::query_scalar(
337 "SELECT origin IS NOT NULL FROM server_e2ee_images WHERE object=?",
338 )
339 .bind(object.as_slice())
340 .fetch_one(&mut *conn)
341 .await?;
342 valid(admitted)?;
343 let bytes: Option<Vec<u8>> = sqlx::query_scalar(
344 "SELECT bytes FROM server_e2ee_image_chunks WHERE object=? AND chunk_index=?",
345 )
346 .bind(object.as_slice())
347 .bind(*index as i64)
348 .fetch_optional(conn)
349 .await?;
350 Ok(match bytes {
351 Some(b) => {
352 d.verify_chunk(*index, &b)?;
353 Reply::Chunk(b)
354 }
355 None => Reply::Unavailable,
356 })
357 }
358 Operation::Put { reservation, .. }
359 | Operation::Complete { reservation, .. }
360 | Operation::Release { reservation, .. } => {
361 ticket(
362 conn,
363 &object,
364 workspace,
365 &context.device,
366 &Ticket {
367 reservation: *reservation,
368 },
369 now,
370 )
371 .await?;
372 match &op {
373 Operation::Put { index, record, .. } => {
374 d.verify_chunk(*index, record)?;
375 let old:Option<Vec<u8>>=sqlx::query_scalar("SELECT bytes FROM server_e2ee_image_chunks WHERE object=? AND chunk_index=?").bind(object.as_slice()).bind(*index as i64).fetch_optional(&mut *conn).await?;
376 valid(old.as_ref().is_none_or(|b| b == record))?;
377 sqlx::query("INSERT OR IGNORE INTO server_e2ee_image_chunks(object,chunk_index,bytes) VALUES(?,?,?)").bind(object.as_slice()).bind(*index as i64).bind(record).execute(&mut *conn).await?;
378 }
379 Operation::Complete { .. } => {
380 let records:Vec<Vec<u8>>=sqlx::query_scalar("SELECT bytes FROM server_e2ee_image_chunks WHERE object=? ORDER BY chunk_index").bind(object.as_slice()).fetch_all(&mut *conn).await?;
381 d.verify(&records)?;
382 sqlx::query("UPDATE server_e2ee_images SET complete=1 WHERE object=?")
383 .bind(object.as_slice())
384 .execute(&mut *conn)
385 .await?;
386 }
387 Operation::Release { .. } => {
388 sqlx::query("DELETE FROM server_e2ee_image_tickets WHERE reservation=?")
389 .bind(reservation.as_slice())
390 .execute(&mut *conn)
391 .await?;
392 }
393 _ => unreachable!(),
394 }
395 Ok(Reply::Done)
396 }
397 _ => unreachable!(),
398 }
399}
400
401pub(in crate::sync::encrypted_tail) async fn admit(
402 conn: &mut SqliteConnection,
403 context: &Context,
404 membership: &Membership,
405 id: &str,
406 p: &super::super::domain::Projection,
407 t: Option<&Ticket>,
408) -> Result<()> {
409 use super::super::domain::Projection;
410 let now = chrono::Utc::now().timestamp();
411 match p {
412 Projection::Move {
413 source,
414 target,
415 tasks,
416 } => {
417 valid(t.is_none())?;
418 super::super::moves::admit(conn, source, target, tasks).await?;
419 }
420 Projection::Ref {
421 workspace,
422 task,
423 reference,
424 descriptor,
425 deleted,
426 version,
427 } => {
428 let authored_workspace = workspace;
429 let routed = super::super::moves::server_workspace(conn, task, workspace).await?;
430 let workspace = &routed;
431 let d = Descriptor::decode(descriptor)?;
432 eligible_object(conn, membership, &d).await?;
433 let (_, complete) = load(conn, &d.object, &hash(descriptor)).await?;
434 valid(complete && d.vault == context.vault && d.stream == context.stream)?;
435 let records: Vec<Vec<u8>> = sqlx::query_scalar(
436 "SELECT bytes FROM server_e2ee_image_chunks WHERE object=? ORDER BY chunk_index",
437 )
438 .bind(d.object.as_slice())
439 .fetch_all(&mut *conn)
440 .await?;
441 d.verify(&records)?;
442 let exists:bool=sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM server_e2ee_image_references WHERE workspace=? AND reference=?)").bind(workspace).bind(reference).fetch_one(&mut *conn).await?;
443 valid(!exists)?;
444 if !protected(conn, &d.object, workspace).await? {
445 ticket(
446 conn,
447 &d.object,
448 authored_workspace,
449 &context.device,
450 t.context("error encrypted-image-reservation-required")?,
451 now,
452 )
453 .await?;
454 }
455 let count: i64 =
456 sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_references")
457 .fetch_one(&mut *conn)
458 .await?;
459 valid(count < 262144)?;
460 let state:Option<(Option<String>,bool,bool)>=sqlx::query_as("SELECT version,deleted,protected FROM server_e2ee_image_parents WHERE workspace=? AND parent=?").bind(workspace).bind(task).fetch_optional(&mut *conn).await?;
461 if state.is_none() {
462 let count: i64 =
463 sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_parents")
464 .fetch_one(&mut *conn)
465 .await?;
466 valid(count < 262144)?;
467 }
468 let (known, state_deleted, sticky) = state.unwrap_or((None, false, false));
469 let mut state = crate::sync::persistence::parent_liveness::ParentState {
470 version: known.clone(),
471 deleted: state_deleted,
472 protected: sticky,
473 };
474 state.protect_hint(*deleted, version.as_deref());
475 sqlx::query("INSERT INTO server_e2ee_image_parents(workspace,parent,version,deleted,protected) VALUES(?,?,?,?,?) ON CONFLICT(workspace,parent) DO UPDATE SET protected=excluded.protected").bind(workspace).bind(task).bind(known).bind(state_deleted).bind(state.protected).execute(&mut *conn).await?;
476 sqlx::query("INSERT INTO server_e2ee_image_references(workspace,reference,parent,deleted,object) VALUES(?,?,?,0,?)").bind(workspace).bind(reference).bind(task).bind(d.object.as_slice()).execute(&mut *conn).await?;
477 sqlx::query("UPDATE server_e2ee_images SET origin=COALESCE(origin,?) WHERE object=?")
478 .bind(id)
479 .bind(d.object.as_slice())
480 .execute(&mut *conn)
481 .await?;
482 if let Some(t) = t {
483 sqlx::query("DELETE FROM server_e2ee_image_tickets WHERE reservation=? AND device=? AND workspace=? AND object=?").bind(t.reservation.as_slice()).bind(context.device.as_slice()).bind(authored_workspace).bind(d.object.as_slice()).execute(&mut *conn).await?;
484 }
485 sqlx::query(
486 "INSERT OR IGNORE INTO server_e2ee_image_scopes(object, workspace) VALUES (?, ?)",
487 )
488 .bind(d.object.as_slice())
489 .bind(workspace)
490 .execute(&mut *conn)
491 .await?;
492 let scopes: i64 = sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_scopes")
493 .fetch_one(&mut *conn)
494 .await?;
495 valid(scopes <= 262144)?;
496 retain_parent(conn, workspace, task).await?;
497 }
498 Projection::Unref {
499 workspace,
500 task,
501 reference,
502 } => {
503 let workspace = super::super::moves::server_workspace(conn, task, workspace).await?;
504 let changed=sqlx::query("UPDATE server_e2ee_image_references SET deleted=1 WHERE workspace=? AND reference=? AND parent=?").bind(&workspace).bind(reference).bind(task).execute(&mut *conn).await?.rows_affected();
505 valid(changed == 1)?;
506 }
507 _ => valid(t.is_none())?,
508 }
509 Ok(())
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[tokio::test]
517 async fn retired_declaration_cannot_bypass_active_object_limit() {
518 let root = tempfile::tempdir().unwrap();
519 let db = Database::open(&root.path().join("test.sqlite"))
520 .await
521 .unwrap();
522 let a = crate::sync::encrypted_tail::tests::authority();
523 let mut conn = db.acquire_writer().await.unwrap();
524 let mut tx = begin_immediate(&mut conn).await.unwrap();
525 let (retired, _) = Descriptor::seal(&a, b"x").unwrap();
526 sqlx::query("INSERT INTO server_e2ee_images(object,byte_size,descriptor) VALUES(?,?,?)")
527 .bind(retired.object.as_slice())
528 .bind(retired.byte_size())
529 .bind(retired.encode().unwrap())
530 .execute(&mut *tx)
531 .await
532 .unwrap();
533 for _ in 0..128 {
535 let (d, records) = Descriptor::seal(&a, b"x").unwrap();
536 sqlx::query(
537 "INSERT INTO server_e2ee_images(object,byte_size,descriptor) VALUES(?,?,?)",
538 )
539 .bind(d.object.as_slice())
540 .bind(d.byte_size())
541 .bind(d.encode().unwrap())
542 .execute(&mut *tx)
543 .await
544 .unwrap();
545 sqlx::query(
546 "INSERT INTO server_e2ee_image_chunks(object,chunk_index,bytes) VALUES(?,0,?)",
547 )
548 .bind(d.object.as_slice())
549 .bind(&records[0])
550 .execute(&mut *tx)
551 .await
552 .unwrap();
553 }
554 assert!(
555 reserve(
556 &mut tx,
557 &retired,
558 "0000000000000000",
559 &a.context.device,
560 i64::MAX,
561 123
562 )
563 .await
564 .is_err()
565 );
566 let tickets: i64 = sqlx::query_scalar("SELECT count(*) FROM server_e2ee_image_tickets")
567 .fetch_one(&mut *tx)
568 .await
569 .unwrap();
570 assert_eq!(tickets, 0);
571 }
572}