1use crate::*;
2use coven_foundation::id_source::{DeviceId, ObjectId, StoreId};
3use std::num::NonZeroU64;
4use std::sync::Arc;
5
6pub struct Conformance {
9 storage: Arc<dyn Storage>,
10 store: StoreId,
11}
12fn occupied<T>(result: Result<T, StorageError>) -> bool {
14 result.is_err_and(|error| error.own_failure() == Some(StorageFailure::AlreadyExists))
15}
16
17impl Conformance {
18 pub fn new(storage: Arc<dyn Storage>, store: StoreId) -> Self {
20 Self { storage, store }
21 }
22 pub async fn shared_location(&self, other: StoreId) {
24 assert_ne!(self.store, other);
25 let first = ObjectPath::slot(self.store, NonZeroU64::MIN);
26 let second = ObjectPath::slot(other, NonZeroU64::MIN);
27 let (left, right) = tokio::join!(
28 self.storage.create(&first, b"first"),
29 self.storage.create(&second, b"second")
30 );
31 left.unwrap();
32 right.unwrap();
33 for (store, path, bytes) in
34 [(self.store, &first, b"first".as_slice()), (other, &second, b"second".as_slice())]
35 {
36 assert_eq!(self.storage.read(path).await.unwrap(), bytes);
37 let listed = self.storage.list(&ObjectPrefix::all(store)).await.unwrap();
38 assert_eq!(listed.len(), 1);
39 assert_eq!(&listed[0].path, path);
40 }
41 self.storage.delete(&first).await.unwrap();
42 assert_eq!(self.storage.read(&second).await.unwrap(), b"second");
43 self.storage.delete(&second).await.unwrap();
44 }
45 pub async fn run(&self) -> Result<(), StorageError> {
47 use coven_foundation::id_source::DeviceId;
48 let path = ObjectPath::file(self.store, DeviceId(31), ObjectId(uuid::Uuid::from_u128(1)));
49 let other = ObjectPath::file(self.store, DeviceId(32), ObjectId(uuid::Uuid::from_u128(1)));
50 let data = b"encrypted bytes";
51 if self.storage.status(&path).await?.is_some() {
52 return Err(StorageFailure::Protocol.with_source("absent exact status"));
53 }
54 let created = self.storage.create(&path, data).await?;
55 self.storage.create(&other, b"other").await?;
56 let first = self.storage.status(&path).await?.ok_or(StorageFailure::NotFound)?;
57 if created.is_some_and(|created| created != first) {
58 return Err(
59 StorageFailure::Protocol.with_source("create reply differs from exact status")
60 );
61 }
62 let read = self.storage.read_object(&path, None).await?;
63 if read.metadata != first {
64 return Err(
65 StorageFailure::Protocol.with_source("read metadata differs from exact status")
66 );
67 }
68 if read.bytes != data {
69 return Err(StorageFailure::Protocol.with_source("whole read"));
70 }
71 for (start, end) in [(0, 3), (4, 8), (12, 15)] {
72 let range = ByteRange::new(start, end)?;
73 let read = self.storage.read_object(&path, Some(range)).await?;
74 if read.metadata.size != first.size || read.metadata.revision != first.revision {
75 return Err(
76 StorageFailure::Protocol.with_source("range omitted complete-object metadata")
77 );
78 }
79 if read.bytes != range.select(data)? {
80 return Err(StorageFailure::Protocol.with_source("range read"));
81 }
82 }
83 if !matches!(
84 self.storage.read_range(&path, ByteRange::new(15, 16)?).await,
85 Err(error) if error.failure() == StorageFailure::InvalidRange
86 ) {
87 return Err(StorageFailure::Protocol.with_source("past-end range misclassified"));
88 }
89 if !occupied(self.storage.create(&path, b"replacement").await) {
90 return Err(StorageFailure::Protocol.with_source("create-once refusal"));
91 }
92 if !occupied(self.storage.create(&path, data).await) {
93 return Err(StorageFailure::Protocol.with_source("create-once retry refusal"));
94 }
95 if self.storage.status(&path).await?.as_ref() != Some(&first) {
96 return Err(StorageFailure::Protocol
97 .with_source("immutable retry changed publication metadata"));
98 }
99 let listed = self
100 .storage
101 .list(&ObjectPrefix::device_files(self.store, DeviceId(31)))
102 .await?
103 .into_iter()
104 .map(|object| {
105 (object.path, object.size, object.stored_at, object.object_id, object.revision)
106 })
107 .collect::<Vec<_>>();
108 if listed
109 != [(
110 path.clone(),
111 first.size,
112 first.stored_at,
113 first.object_id.clone(),
114 first.revision.clone(),
115 )]
116 {
117 return Err(StorageFailure::Protocol.with_source(format!(
118 "prefix listing metadata: listed={listed:?}, status={first:?}"
119 )));
120 }
121 self.storage.delete(&path).await?;
122 self.storage.delete(&path).await?;
123 self.storage.delete(&other).await?;
124 if self.storage.status(&path).await?.is_some() {
125 return Err(StorageFailure::Protocol.with_source("deleted exact status"));
126 }
127 if self.storage.read(&path).await.err().map(|e| e.failure())
128 != Some(StorageFailure::NotFound)
129 {
130 return Err(StorageFailure::Protocol.with_source("deleted object read"));
131 }
132 if !matches!(
133 self.storage.replace(&first, data).await,
134 Err(error) if error.failure() == StorageFailure::InvalidPath
135 ) {
136 return Err(
137 StorageFailure::Protocol.with_source("immutable object accepted replacement")
138 );
139 }
140 for path in [ObjectPath::floor(self.store)] {
141 self.storage.create(&path, b"last record").await?;
142 let mut previous = self.storage.status(&path).await?.ok_or(StorageFailure::NotFound)?;
143 for bytes in [b"next record", b"last record"] {
144 self.storage.replace(&previous, bytes).await?;
145 let next = self.storage.status(&path).await?.ok_or(StorageFailure::NotFound)?;
146 if previous.revision == next.revision {
147 return Err(
148 StorageFailure::Protocol.with_source("replacement did not change revision")
149 );
150 }
151 if !matches!(self.storage.replace(&previous, b"stale floor").await, Err(error) if error.failure() == StorageFailure::Conflict)
152 {
153 return Err(StorageFailure::Protocol
154 .with_source("stale floor replacement was accepted"));
155 }
156 previous = next;
157 }
158 if self.storage.read(&path).await? != b"last record" {
159 return Err(StorageFailure::Protocol.with_source("object replacement"));
160 }
161 if !self.storage.list(&ObjectPrefix::all(self.store)).await?.iter().any(|object| {
162 object.path == path
163 && object.size == previous.size
164 && object.revision == previous.revision
165 }) {
166 return Err(StorageFailure::Protocol
167 .with_source("replaced object metadata differs from listing"));
168 }
169 self.storage.delete(&path).await?;
170 }
171 self.storage.create(&path, &[]).await?;
172 if !occupied(self.storage.create(&path, &[]).await) {
173 return Err(StorageFailure::Protocol.with_source("empty create-once refusal"));
174 }
175 if !self.storage.read(&path).await?.is_empty() {
176 return Err(StorageFailure::Protocol.with_source("empty object changed"));
177 }
178 if !occupied(self.storage.create(&path, b"different").await) {
179 return Err(StorageFailure::Protocol.with_source("empty create-once refusal"));
180 }
181 if !self.storage.read(&path).await?.is_empty() {
182 return Err(StorageFailure::Protocol.with_source("empty immutable object replaced"));
183 }
184 self.storage.delete(&path).await?;
185 let bytes: Vec<_> = (0..42).map(|index| (index % 251) as u8).collect();
186 self.storage.create(&path, &bytes).await?;
187 if self.storage.read_range(&path, ByteRange::new(5, 37)?).await? != bytes[5..37] {
188 return Err(StorageFailure::Protocol.with_source("range across upload parts changed"));
189 }
190 if self
191 .storage
192 .read_range(&path, ByteRange::new(bytes.len() as u64 - 1, bytes.len() as u64 + 1)?)
193 .await
194 .err()
195 .map(|error| error.failure())
196 != Some(StorageFailure::InvalidRange)
197 {
198 return Err(
199 StorageFailure::Protocol.with_source("clipped past-end range misclassified")
200 );
201 }
202 self.storage.delete(&path).await?;
203 Ok(())
204 }
205
206 pub async fn creation_race(
211 &self,
212 claimants: [Arc<dyn Storage>; 3],
213 ) -> Result<(), StorageError> {
214 let marker = ObjectPath::slot(self.store, NonZeroU64::MIN);
215 let claims: [&[u8]; 3] = [b"claim 1", b"claim 2", b"claim 3"];
216 let (first, second, third) = tokio::join!(
217 claimants[0].create(&marker, claims[0]),
218 claimants[1].create(&marker, claims[1]),
219 claimants[2].create(&marker, claims[2]),
220 );
221 let mut winners = Vec::new();
222 for (index, result) in [first, second, third].into_iter().enumerate() {
223 match result {
224 Ok(_) => winners.push(index),
225 Err(error) if error.own_failure() == Some(StorageFailure::AlreadyExists) => {}
226 Err(error) => return Err(error),
227 }
228 }
229 let [winner] = winners.as_slice() else {
230 return Err(StorageFailure::Protocol
231 .with_source(format!("{} concurrent creates won one path", winners.len())));
232 };
233 if self.storage.read(&marker).await? != claims[*winner] {
234 return Err(StorageFailure::Protocol.with_source("creation race kept another claim"));
235 }
236 claimants[*winner].delete(&marker).await
237 }
238}
239
240impl Conformance {
241 pub async fn listing_times(
244 &self,
245 stored_at: coven_format::value::StorageTime,
246 advance: impl std::future::Future<Output = ()>,
247 ) {
248 let storage = &self.storage;
249 let first = ObjectPath::file(self.store, DeviceId(31), ObjectId(uuid::Uuid::from_u128(1)));
250 let second = ObjectPath::file(self.store, DeviceId(32), ObjectId(uuid::Uuid::from_u128(1)));
251 storage.create(&first, b"first").await.unwrap();
252 storage.create(&second, b"second").await.unwrap();
253 let expected = storage.list(&ObjectPrefix::files(self.store)).await.unwrap();
254 assert_eq!(
255 expected
256 .iter()
257 .map(|object| (&object.path, object.size, object.stored_at))
258 .collect::<Vec<_>>(),
259 [(&first, 5, stored_at), (&second, 6, stored_at)]
260 );
261 advance.await;
262 assert!(occupied(storage.create(&first, b"first").await));
263 assert_eq!(storage.list(&ObjectPrefix::all(self.store)).await.unwrap(), expected);
264 storage.delete(&first).await.unwrap();
265 storage.delete(&second).await.unwrap();
266 }
267
268 pub async fn expired_session(
271 &self,
272 expire: impl std::future::Future<Output = ()>,
273 ) -> StorageError {
274 let storage = &self.storage;
275 let path = ObjectPath::file(self.store, DeviceId(31), ObjectId(uuid::Uuid::from_u128(1)));
276 let mut upload = storage.begin_upload(&path, 4).await.unwrap();
277 storage.resume_upload(&mut upload).await.unwrap();
278 expire.await;
279 let error = storage.resume_upload(&mut upload).await.unwrap_err();
280 assert_eq!(error.failure(), StorageFailure::SessionExpired);
281 let replacement = storage.restart_upload(&upload).await.unwrap();
282 assert_eq!(replacement.path(), &path);
283 assert_eq!(replacement.total_bytes(), 4);
284 assert_eq!(replacement.confirmed_bytes(), 0);
285 assert_ne!(replacement.encode().unwrap().as_bytes(), upload.encode().unwrap().as_bytes());
286 let mut replacement =
287 UploadSession::decode(replacement.encode().unwrap().as_bytes()).unwrap();
288 storage.resume_upload(&mut replacement).await.unwrap();
289 storage.upload_part(&mut replacement, b"data").await.unwrap();
290 storage.finish_upload(&mut replacement).await.unwrap();
291 assert_eq!(storage.read(&path).await.unwrap(), b"data");
292 assert!(matches!(storage.restart_upload(&replacement).await,
293 Err(error) if error.failure() == StorageFailure::InvalidPart));
294 storage.delete(&path).await.unwrap();
295 error
296 }
297
298 pub async fn owner_only_sharing(&self) {
300 for error in [
301 self.storage.grant_access("[email protected]").await.err().unwrap(),
302 self.storage.revoke_access("[email protected]").await.err().unwrap(),
303 ] {
304 assert_eq!(error.failure(), StorageFailure::NotStoreOwner);
305 }
306 }
307
308 pub async fn lost_part_reply(
310 &self,
311 part_size: usize,
312 lose: impl std::future::Future<Output = ()>,
313 ) {
314 let storage = &self.storage;
315 let path = ObjectPath::file(self.store, DeviceId(31), ObjectId(uuid::Uuid::from_u128(1)));
316 let mut upload = storage.begin_upload(&path, 2 * part_size as u64 + 1).await.unwrap();
317 assert_eq!(upload.part_size(), part_size);
318 storage.resume_upload(&mut upload).await.unwrap();
319 let first = vec![41; part_size];
322 storage.upload_part(&mut upload, &first).await.unwrap();
323 let recorded = upload.encode().unwrap();
324 let bytes = vec![42; part_size];
325 lose.await;
326 assert_eq!(
327 storage.upload_part(&mut upload, &bytes).await.unwrap_err().failure(),
328 StorageFailure::Network
329 );
330 assert_eq!(upload.confirmed_bytes(), part_size as u64);
331 let mut upload = UploadSession::decode(recorded.as_bytes()).unwrap();
332 storage.resume_upload(&mut upload).await.unwrap();
333 assert_eq!(upload.confirmed_bytes(), 2 * part_size as u64);
334 storage.upload_part(&mut upload, b"z").await.unwrap();
335 storage.finish_upload(&mut upload).await.unwrap();
336 storage.finish_upload(&mut upload).await.unwrap();
337 let mut expected = first;
338 expected.extend(bytes);
339 expected.push(b'z');
340 assert_eq!(storage.read(&path).await.unwrap(), expected);
341 storage.delete(&path).await.unwrap();
342 }
343
344 pub async fn permission_failures(
346 &self,
347 deny: impl std::future::Future<Output = ()>,
348 ) -> Vec<StorageError> {
349 let storage = &self.storage;
350 let path = ObjectPath::slot(self.store, NonZeroU64::MIN);
351 let floor = ObjectPath::floor(self.store);
352 storage.create(&floor, b"previous floor").await.unwrap();
353 let previous = storage.status(&floor).await.unwrap().unwrap();
354 let mut ready = storage.begin_upload(&path, 4).await.unwrap();
355 let part = vec![1; ready.part_size()];
356 let mut pending = storage.begin_upload(&path, part.len() as u64 + 1).await.unwrap();
357 storage.upload_part(&mut pending, &part).await.unwrap();
358 storage.upload_part(&mut ready, b"data").await.unwrap();
359 deny.await;
360 let mut errors = vec![
361 storage.create(&path, b"data").await.err().unwrap(),
362 storage.replace(&previous, b"data").await.err().unwrap(),
363 storage.read(&path).await.err().unwrap(),
364 storage.status(&path).await.err().unwrap(),
365 storage.read_range(&path, ByteRange::new(0, 1).unwrap()).await.err().unwrap(),
366 storage.list(&ObjectPrefix::all(self.store)).await.err().unwrap(),
367 storage.delete(&path).await.err().unwrap(),
368 storage.resume_upload(&mut pending).await.err().unwrap(),
369 storage.upload_part(&mut pending, b"z").await.err().unwrap(),
370 storage.finish_upload(&mut ready).await.err().unwrap(),
371 storage.abort_upload(&pending).await.err().unwrap(),
372 ];
373 if !matches!(storage.config().provider(), CloudProvider::S3 | CloudProvider::OneDrive) {
374 errors.push(storage.begin_upload(&path, 4).await.err().unwrap());
375 }
376 if storage.config().provider() != CloudProvider::S3 {
377 errors.push(storage.grant_access("member").await.err().unwrap());
378 errors.push(storage.revoke_access("member").await.err().unwrap());
379 }
380 for error in &errors {
381 assert_eq!(error.failure(), StorageFailure::PermissionDenied, "{error:?}");
382 }
383 errors
384 }
385}
386
387impl Conformance {
388 pub async fn conditional_floor_upload(&self) {
391 let storage = &self.storage;
392 let path = ObjectPath::floor(self.store);
393 for seeded in [false, true] {
394 if seeded {
395 storage.create(&path, b"previous").await.unwrap();
396 }
397 let previous = storage.status(&path).await.unwrap();
398 let probe = match &previous {
401 Some(previous) => storage.begin_replace(previous, 1).await.unwrap(),
402 None => storage.begin_upload(&path, 1).await.unwrap(),
403 };
404 let part_size = probe.part_size();
405 storage.abort_upload(&probe).await.unwrap();
406 let total = part_size as u64 + 1;
407 let mut uploads = Vec::new();
408 for _ in 0..2 {
409 uploads.push(match &previous {
410 Some(previous) => storage.begin_replace(previous, total).await.unwrap(),
411 None => storage.begin_upload(&path, total).await.unwrap(),
412 });
413 }
414 for (index, upload) in uploads.iter_mut().enumerate() {
415 storage.upload_part(upload, &vec![index as u8; part_size]).await.unwrap();
416 assert_eq!(storage.status(&path).await.unwrap(), previous);
417 let target = upload.target().clone();
418 *upload = UploadSession::decode(upload.encode().unwrap().as_bytes()).unwrap();
419 storage.resume_upload(upload).await.unwrap();
420 assert_eq!(upload.confirmed_bytes(), part_size as u64);
421 assert_eq!(upload.target(), &target);
422 }
423 storage.upload_part(&mut uploads[0], b"a").await.unwrap();
424 storage.finish_upload(&mut uploads[0]).await.unwrap();
425 let result = match storage.upload_part(&mut uploads[1], b"b").await {
426 Ok(()) => storage.finish_upload(&mut uploads[1]).await,
427 Err(error) => Err(error),
428 };
429 let error = result.unwrap_err();
430 assert_eq!(
431 error.own_failure(),
432 Some(if seeded { StorageFailure::Conflict } else { StorageFailure::AlreadyExists })
433 );
434 for upload in &uploads {
435 storage.abort_upload(upload).await.unwrap();
436 }
437 let mut expected = vec![0; part_size];
438 expected.push(b'a');
439 assert_eq!(storage.read(&path).await.unwrap(), expected);
440 let current = storage.status(&path).await.unwrap().unwrap();
441 if let Some(previous) = previous {
442 assert_ne!(current.revision, previous.revision);
443 }
444 storage.replace(¤t, b"last").await.unwrap();
446 assert_eq!(storage.read(&path).await.unwrap(), b"last");
447 storage.delete(&path).await.unwrap();
448 }
449 }
450}