1use super::manager::{Config as ManagerConfig, Manager, WriteFactory};
30use crate::{Context, journal::Error};
31use commonware_codec::{Codec, CodecShared, FixedSize};
32use commonware_cryptography::{Crc32, crc32};
33#[cfg(any(test, feature = "test-utils"))]
34use commonware_runtime::{Blob as _, ReadOptions, Storage, WriteOptions};
35use commonware_runtime::{BufMut, Error as RError, Handle};
36use std::{io::Cursor, num::NonZeroUsize};
37use zstd::{bulk::compress, decode_all};
38
39pub(crate) const CHECKSUM_SIZE: usize = crc32::Digest::SIZE;
41
42#[derive(Clone)]
44pub struct Config<C> {
45 pub partition: String,
47
48 pub compression: Option<u8>,
50
51 pub codec_config: C,
53
54 pub write_buffer: NonZeroUsize,
56}
57
58struct Inner<E: Context, V: Codec> {
60 manager: Manager<E, WriteFactory>,
61
62 compression: Option<u8>,
64
65 codec_config: V::Cfg,
67}
68
69impl<E: Context, V: CodecShared> Inner<E, V> {
70 async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
72 let manager_cfg = ManagerConfig {
73 partition: cfg.partition,
74 factory: WriteFactory {
75 capacity: cfg.write_buffer,
76 pool: context.storage_buffer_pool().clone(),
77 },
78 };
79 let manager = Manager::init(context, manager_cfg).await?;
80
81 Ok(Self {
82 manager,
83 compression: cfg.compression,
84 codec_config: cfg.codec_config,
85 })
86 }
87
88 async fn append(&mut self, section: u64, value: &V) -> Result<(u64, u32), Error> {
90 let buf = if let Some(level) = self.compression {
92 let encoded = value.encode();
94 let mut compressed =
95 compress(&encoded, level as i32).map_err(|_| Error::CompressionFailed)?;
96 let checksum = Crc32::checksum(&compressed);
97 compressed.put_u32(checksum);
98 compressed
99 } else {
100 let entry_size = value.encode_size() + CHECKSUM_SIZE;
102 let mut buf = Vec::with_capacity(entry_size);
103 value.write(&mut buf);
104 let checksum = Crc32::checksum(&buf);
105 buf.put_u32(checksum);
106 buf
107 };
108
109 let entry_size = u32::try_from(buf.len()).map_err(|_| Error::ValueTooLarge)?;
111 let writer = self.manager.get_or_create(section).await?;
112 let offset = writer.size();
113 writer.write_at(offset, buf).await.map_err(Error::Runtime)?;
114
115 Ok((offset, entry_size))
116 }
117
118 async fn get(&self, section: u64, offset: u64, size: u32) -> Result<V, Error> {
120 let writer = self
121 .manager
122 .get(section)?
123 .ok_or(Error::SectionOutOfRange(section))?;
124
125 let buf = writer.read_at(offset, size as usize).await?.coalesce();
127
128 if buf.len() < CHECKSUM_SIZE {
130 return Err(Error::Runtime(RError::BlobInsufficientLength));
131 }
132
133 let data_len = buf.len() - CHECKSUM_SIZE;
134 let compressed_data = &buf.as_ref()[..data_len];
135 let stored_checksum = u32::from_be_bytes(
136 buf.as_ref()[data_len..]
137 .try_into()
138 .expect("checksum is 4 bytes"),
139 );
140
141 let checksum = Crc32::checksum(compressed_data);
143 if checksum != stored_checksum {
144 return Err(Error::ChecksumMismatch(stored_checksum, checksum));
145 }
146
147 let value = if self.compression.is_some() {
149 let decompressed =
150 decode_all(Cursor::new(compressed_data)).map_err(|_| Error::DecompressionFailed)?;
151 V::decode_cfg(decompressed.as_ref(), &self.codec_config).map_err(Error::Codec)?
152 } else {
153 V::decode_cfg(compressed_data, &self.codec_config).map_err(Error::Codec)?
154 };
155
156 Ok(value)
157 }
158
159 async fn verify(&self, section: u64, offset: u64, size: u32) -> Result<bool, Error> {
161 if (size as usize) < CHECKSUM_SIZE {
163 return Ok(false);
164 }
165 let Some(writer) = self.manager.get(section)? else {
166 return Ok(false);
167 };
168
169 let buf = match writer.read_at(offset, size as usize).await {
170 Ok(buf) => buf.coalesce(),
171 Err(RError::BlobInsufficientLength | RError::OffsetOverflow) => return Ok(false),
172 Err(err) => return Err(Error::Runtime(err)),
173 };
174 let data_len = buf.len() - CHECKSUM_SIZE;
175 let stored_checksum = u32::from_be_bytes(
176 buf.as_ref()[data_len..]
177 .try_into()
178 .expect("checksum is 4 bytes"),
179 );
180 Ok(Crc32::checksum(&buf.as_ref()[..data_len]) == stored_checksum)
181 }
182
183 #[cfg(test)]
185 async fn inject(&mut self, section: u64, offset: u64, buf: Vec<u8>) -> Result<(), Error> {
186 let writer = self.manager.get_or_create(section).await?;
187 writer.write_at(offset, buf).await.map_err(Error::Runtime)
188 }
189
190 async fn sync(&mut self, sections: impl crate::Sections) -> Result<(), Error> {
192 self.manager.sync(sections).await
193 }
194
195 async fn start_sync(&mut self, sections: impl crate::Sections) -> Result<Handle<()>, Error> {
197 self.manager.start_sync(sections).await
198 }
199
200 async fn sync_all(&mut self) -> Result<(), Error> {
202 self.manager.sync_all().await
203 }
204
205 fn size(&self, section: u64) -> Result<u64, Error> {
207 self.manager.size(section)
208 }
209
210 async fn rewind(&mut self, section: u64, size: u64) -> Result<(), Error> {
212 self.manager.rewind(section, size).await
213 }
214
215 async fn rewind_section(&mut self, section: u64, size: u64) -> Result<(), Error> {
217 self.manager.rewind_section(section, size).await
218 }
219
220 async fn prune(&mut self, min: u64) -> Result<bool, Error> {
222 self.manager.prune(min).await
223 }
224
225 const fn pruned(&self, section: u64) -> bool {
227 self.manager.pruned(section)
228 }
229
230 fn oldest_section(&self) -> Option<u64> {
232 self.manager.oldest_section()
233 }
234
235 fn newest_section(&self) -> Option<u64> {
237 self.manager.newest_section()
238 }
239
240 fn sections(&self) -> impl Iterator<Item = u64> + '_ {
242 self.manager.sections()
243 }
244
245 async fn remove_section(&mut self, section: u64) -> Result<bool, Error> {
247 self.manager.remove_section(section).await
248 }
249
250 async fn destroy(self) -> Result<(), Error> {
252 self.manager.destroy().await
253 }
254}
255
256pub struct Glob<E: Context, V: Codec>(Box<Inner<E, V>>);
267
268impl<E: Context, V: CodecShared> std::fmt::Debug for Glob<E, V> {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.debug_struct("Glob")
271 .field("oldest_section", &self.oldest_section())
272 .field("newest_section", &self.newest_section())
273 .finish_non_exhaustive()
274 }
275}
276
277impl<E: Context, V: CodecShared> Glob<E, V> {
278 pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
280 Ok(Self(Box::new(Inner::init(context, cfg).await?)))
281 }
282
283 pub async fn append(mut self, section: u64, value: &V) -> Result<(Self, u64, u32), Error> {
289 let (offset, size) = self.0.append(section, value).await?;
290 Ok((self, offset, size))
291 }
292
293 pub async fn get(&self, section: u64, offset: u64, size: u32) -> Result<V, Error> {
298 self.0.get(section, offset, size).await
299 }
300
301 pub(super) async fn verify(&self, section: u64, offset: u64, size: u32) -> Result<bool, Error> {
307 self.0.verify(section, offset, size).await
308 }
309
310 #[cfg(test)]
312 pub(super) async fn inject(
313 &mut self,
314 section: u64,
315 offset: u64,
316 buf: Vec<u8>,
317 ) -> Result<(), Error> {
318 self.0.inject(section, offset, buf).await
319 }
320
321 pub async fn sync(mut self, sections: impl crate::Sections) -> Result<Self, Error> {
323 self.0.sync(sections).await?;
324 Ok(self)
325 }
326
327 pub async fn start_sync(
332 mut self,
333 sections: impl crate::Sections,
334 ) -> Result<(Self, Handle<()>), Error> {
335 let handle = self.0.start_sync(sections).await?;
336 Ok((self, handle))
337 }
338
339 pub async fn sync_all(mut self) -> Result<Self, Error> {
341 self.0.sync_all().await?;
342 Ok(self)
343 }
344
345 pub fn size(&self, section: u64) -> Result<u64, Error> {
347 self.0.size(section)
348 }
349
350 pub async fn rewind(mut self, section: u64, size: u64) -> Result<Self, Error> {
354 self.0.rewind(section, size).await?;
355 Ok(self)
356 }
357
358 pub async fn rewind_section(mut self, section: u64, size: u64) -> Result<Self, Error> {
362 self.0.rewind_section(section, size).await?;
363 Ok(self)
364 }
365
366 pub async fn prune(mut self, min: u64) -> Result<(Self, bool), Error> {
368 let pruned = self.0.prune(min).await?;
369 Ok((self, pruned))
370 }
371
372 pub fn pruned(&self, section: u64) -> bool {
377 self.0.pruned(section)
378 }
379
380 pub fn oldest_section(&self) -> Option<u64> {
382 self.0.oldest_section()
383 }
384
385 pub fn newest_section(&self) -> Option<u64> {
387 self.0.newest_section()
388 }
389
390 pub fn sections(&self) -> impl Iterator<Item = u64> + '_ {
392 self.0.sections()
393 }
394
395 pub async fn remove_section(mut self, section: u64) -> Result<(Self, bool), Error> {
397 let removed = self.0.remove_section(section).await?;
398 Ok((self, removed))
399 }
400
401 pub async fn destroy(self) -> Result<(), Error> {
403 self.0.destroy().await
404 }
405}
406
407#[cfg(any(test, feature = "test-utils"))]
412pub async fn corrupt_frame(
413 storage: &impl Storage,
414 partition: &str,
415 name: &[u8],
416 frame: u64,
417 frame_size: u64,
418) {
419 let offset = frame * frame_size;
420 let (blob, size) = storage.open(partition, name).await.unwrap();
421 assert!(offset < size, "corruption target must be inside the blob");
422 let byte = blob
423 .read_at(offset, 1, ReadOptions::default())
424 .await
425 .unwrap()
426 .coalesce();
427 blob.write_at(offset, vec![byte.as_ref()[0] ^ 0xFF], WriteOptions::SYNC)
428 .await
429 .unwrap();
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use commonware_macros::test_traced;
436 use commonware_runtime::{Runner, Supervisor as _, deterministic};
437 use commonware_utils::NZUsize;
438
439 fn test_cfg() -> Config<()> {
440 Config {
441 partition: "test-partition".into(),
442 compression: None,
443 codec_config: (),
444 write_buffer: NZUsize!(1024),
445 }
446 }
447
448 #[test_traced]
449 fn test_glob_append_and_get() {
450 let executor = deterministic::Runner::default();
451 executor.start(|context| async move {
452 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
453 .await
454 .expect("Failed to init glob");
455
456 let value: i32 = 42;
458 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
459 assert_eq!(offset, 0);
460
461 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
463 assert_eq!(retrieved, value);
464
465 let glob = glob.sync(1).await.expect("Failed to sync");
467 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
468 assert_eq!(retrieved, value);
469
470 glob.destroy().await.expect("Failed to destroy");
471 });
472 }
473
474 #[test_traced]
475 fn test_glob_multiple_values() {
476 let executor = deterministic::Runner::default();
477 executor.start(|context| async move {
478 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
479 .await
480 .expect("Failed to init glob");
481
482 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
484 let mut locations = Vec::new();
485
486 for value in &values {
487 let offset;
488 let size;
489 (glob, offset, size) = glob.append(1, value).await.expect("Failed to append");
490 locations.push((offset, size));
491 }
492
493 for (i, (offset, size)) in locations.iter().enumerate() {
495 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
496 assert_eq!(retrieved, values[i]);
497 }
498
499 glob.destroy().await.expect("Failed to destroy");
500 });
501 }
502
503 #[test_traced]
504 fn test_glob_with_compression() {
505 let executor = deterministic::Runner::default();
506 executor.start(|context| async move {
507 let cfg = Config {
508 partition: "test-partition".into(),
509 compression: Some(3), codec_config: (),
511 write_buffer: NZUsize!(1024),
512 };
513 let glob: Glob<_, [u8; 100]> = Glob::init(context.child("storage"), cfg)
514 .await
515 .expect("Failed to init glob");
516
517 let value: [u8; 100] = [0u8; 100]; let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
520
521 assert!(size < 100 + 4);
523
524 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
526 assert_eq!(retrieved, value);
527
528 glob.destroy().await.expect("Failed to destroy");
529 });
530 }
531
532 #[test_traced]
533 fn test_glob_prune() {
534 let executor = deterministic::Runner::default();
535 executor.start(|context| async move {
536 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
537 .await
538 .expect("Failed to init glob");
539
540 for section in 1..=5 {
542 (glob, _, _) = glob
543 .append(section, &(section as i32))
544 .await
545 .expect("Failed to append");
546 glob = glob.sync(section).await.expect("Failed to sync");
547 }
548
549 let (glob, _) = glob.prune(3).await.expect("Failed to prune");
551
552 assert!(glob.pruned(1));
554 assert!(glob.pruned(2));
555 assert!(!glob.pruned(3));
556
557 assert!(glob.get(1, 0, 8).await.is_err());
559 assert!(glob.get(2, 0, 8).await.is_err());
560
561 assert!(glob.0.manager.blobs.contains_key(&3));
563 assert!(glob.0.manager.blobs.contains_key(&4));
564 assert!(glob.0.manager.blobs.contains_key(&5));
565
566 glob.destroy().await.expect("Failed to destroy");
567 });
568 }
569
570 #[test_traced]
571 fn test_glob_checksum_mismatch() {
572 let executor = deterministic::Runner::default();
573 executor.start(|context| async move {
574 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
575 .await
576 .expect("Failed to init glob");
577
578 let value: i32 = 42;
580 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
581 let mut glob = glob.sync(1).await.expect("Failed to sync");
582
583 let writer = glob.0.manager.blobs.get_mut(&1).unwrap();
585 writer
586 .write_at(offset, vec![0xFF, 0xFF, 0xFF, 0xFF])
587 .await
588 .expect("Failed to corrupt");
589 writer.sync().await.expect("Failed to sync");
590
591 let result = glob.get(1, offset, size).await;
593 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
594
595 glob.destroy().await.expect("Failed to destroy");
596 });
597 }
598
599 #[test_traced]
600 fn test_glob_rewind() {
601 let executor = deterministic::Runner::default();
602 executor.start(|context| async move {
603 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
604 .await
605 .expect("Failed to init glob");
606
607 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
609 let mut locations = Vec::new();
610
611 for value in &values {
612 let offset;
613 let size;
614 (glob, offset, size) = glob.append(1, value).await.expect("Failed to append");
615 locations.push((offset, size));
616 }
617 glob = glob.sync(1).await.expect("Failed to sync");
618
619 let (third_offset, third_size) = locations[2];
621 let rewind_size = third_offset + u64::from(third_size);
622 let glob = glob
623 .rewind_section(1, rewind_size)
624 .await
625 .expect("Failed to rewind");
626
627 for (i, (offset, size)) in locations.iter().take(3).enumerate() {
629 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
630 assert_eq!(retrieved, values[i]);
631 }
632
633 let (fourth_offset, fourth_size) = locations[3];
635 let result = glob.get(1, fourth_offset, fourth_size).await;
636 assert!(result.is_err());
637
638 glob.destroy().await.expect("Failed to destroy");
639 });
640 }
641
642 #[test_traced]
643 fn test_glob_persistence() {
644 let executor = deterministic::Runner::default();
645 executor.start(|context| async move {
646 let cfg = test_cfg();
647
648 let glob: Glob<_, i32> = Glob::init(context.child("first"), cfg.clone())
650 .await
651 .expect("Failed to init glob");
652
653 let value: i32 = 42;
654 let (glob, offset, size) = glob.append(1, &value).await.expect("Failed to append");
655 let glob = glob.sync(1).await.expect("Failed to sync");
656 drop(glob);
657
658 let glob: Glob<_, i32> = Glob::init(context.child("second"), cfg)
660 .await
661 .expect("Failed to reinit glob");
662
663 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
664 assert_eq!(retrieved, value);
665
666 glob.destroy().await.expect("Failed to destroy");
667 });
668 }
669
670 #[test_traced]
671 fn test_glob_get_invalid_size() {
672 let executor = deterministic::Runner::default();
673 executor.start(|context| async move {
674 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
675 .await
676 .expect("Failed to init glob");
677
678 let (glob, offset, _size) = glob.append(1, &42).await.expect("Failed to append");
679 let glob = glob.sync(1).await.expect("Failed to sync");
680
681 assert!(glob.get(1, offset, 0).await.is_err());
683
684 for size in 1..4u32 {
686 let result = glob.get(1, offset, size).await;
687 assert!(matches!(
688 result,
689 Err(Error::Runtime(RError::BlobInsufficientLength))
690 ));
691 }
692
693 glob.destroy().await.expect("Failed to destroy");
694 });
695 }
696
697 #[test_traced]
698 fn test_glob_get_wrong_size() {
699 let executor = deterministic::Runner::default();
700 executor.start(|context| async move {
701 let glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
702 .await
703 .expect("Failed to init glob");
704
705 let (glob, offset, correct_size) = glob.append(1, &42).await.expect("Failed to append");
706 let glob = glob.sync(1).await.expect("Failed to sync");
707
708 let result = glob.get(1, offset, correct_size - 1).await;
710 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
711
712 glob.destroy().await.expect("Failed to destroy");
713 });
714 }
715}