1use super::manager::{Config as ManagerConfig, Manager, WriteFactory};
30use crate::journal::Error;
31use commonware_codec::{Codec, CodecShared, FixedSize};
32use commonware_cryptography::{crc32, Crc32};
33use commonware_runtime::{BufMut, BufferPooler, Error as RError, Handle, Metrics, Storage};
34use std::{io::Cursor, num::NonZeroUsize};
35use zstd::{bulk::compress, decode_all};
36
37#[derive(Clone)]
39pub struct Config<C> {
40 pub partition: String,
42
43 pub compression: Option<u8>,
45
46 pub codec_config: C,
48
49 pub write_buffer: NonZeroUsize,
51}
52
53pub struct Glob<E: BufferPooler + Storage + Metrics, V: Codec> {
59 manager: Manager<E, WriteFactory>,
60
61 compression: Option<u8>,
63
64 codec_config: V::Cfg,
66}
67
68impl<E: BufferPooler + Storage + Metrics, V: CodecShared> Glob<E, V> {
69 pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
71 let manager_cfg = ManagerConfig {
72 partition: cfg.partition,
73 factory: WriteFactory {
74 capacity: cfg.write_buffer,
75 pool: context.storage_buffer_pool().clone(),
76 },
77 };
78 let manager = Manager::init(context, manager_cfg).await?;
79
80 Ok(Self {
81 manager,
82 compression: cfg.compression,
83 codec_config: cfg.codec_config,
84 })
85 }
86
87 pub async fn append(&mut self, section: u64, value: &V) -> Result<(u64, u32), Error> {
93 let buf = if let Some(level) = self.compression {
95 let encoded = value.encode();
97 let mut compressed =
98 compress(&encoded, level as i32).map_err(|_| Error::CompressionFailed)?;
99 let checksum = Crc32::checksum(&compressed);
100 compressed.put_u32(checksum);
101 compressed
102 } else {
103 let entry_size = value.encode_size() + crc32::Digest::SIZE;
105 let mut buf = Vec::with_capacity(entry_size);
106 value.write(&mut buf);
107 let checksum = Crc32::checksum(&buf);
108 buf.put_u32(checksum);
109 buf
110 };
111
112 let entry_size = u32::try_from(buf.len()).map_err(|_| Error::ValueTooLarge)?;
114 let writer = self.manager.get_or_create(section).await?;
115 let offset = writer.size();
116 writer.write_at(offset, buf).await.map_err(Error::Runtime)?;
117
118 Ok((offset, entry_size))
119 }
120
121 pub async fn get(&self, section: u64, offset: u64, size: u32) -> Result<V, Error> {
126 let writer = self
127 .manager
128 .get(section)?
129 .ok_or(Error::SectionOutOfRange(section))?;
130
131 let buf = writer.read_at(offset, size as usize).await?.coalesce();
133
134 if buf.len() < crc32::Digest::SIZE {
136 return Err(Error::Runtime(RError::BlobInsufficientLength));
137 }
138
139 let data_len = buf.len() - crc32::Digest::SIZE;
140 let compressed_data = &buf.as_ref()[..data_len];
141 let stored_checksum = u32::from_be_bytes(
142 buf.as_ref()[data_len..]
143 .try_into()
144 .expect("checksum is 4 bytes"),
145 );
146
147 let checksum = Crc32::checksum(compressed_data);
149 if checksum != stored_checksum {
150 return Err(Error::ChecksumMismatch(stored_checksum, checksum));
151 }
152
153 let value = if self.compression.is_some() {
155 let decompressed =
156 decode_all(Cursor::new(compressed_data)).map_err(|_| Error::DecompressionFailed)?;
157 V::decode_cfg(decompressed.as_ref(), &self.codec_config).map_err(Error::Codec)?
158 } else {
159 V::decode_cfg(compressed_data, &self.codec_config).map_err(Error::Codec)?
160 };
161
162 Ok(value)
163 }
164
165 pub async fn sync(&mut self, sections: impl crate::Sections) -> Result<(), Error> {
167 self.manager.sync(sections).await
168 }
169
170 pub async fn start_sync(
172 &mut self,
173 sections: impl crate::Sections,
174 ) -> Result<Handle<()>, Error> {
175 self.manager.start_sync(sections).await
176 }
177
178 pub async fn sync_all(&mut self) -> Result<(), Error> {
180 self.manager.sync_all().await
181 }
182
183 pub fn size(&self, section: u64) -> Result<u64, Error> {
185 self.manager.size(section)
186 }
187
188 pub async fn rewind(&mut self, section: u64, size: u64) -> Result<(), Error> {
192 self.manager.rewind(section, size).await
193 }
194
195 pub async fn rewind_section(&mut self, section: u64, size: u64) -> Result<(), Error> {
199 self.manager.rewind_section(section, size).await
200 }
201
202 pub async fn prune(&mut self, min: u64) -> Result<bool, Error> {
204 self.manager.prune(min).await
205 }
206
207 pub fn oldest_section(&self) -> Option<u64> {
209 self.manager.oldest_section()
210 }
211
212 pub fn newest_section(&self) -> Option<u64> {
214 self.manager.newest_section()
215 }
216
217 pub fn sections(&self) -> impl Iterator<Item = u64> + '_ {
219 self.manager.sections()
220 }
221
222 pub async fn remove_section(&mut self, section: u64) -> Result<bool, Error> {
224 self.manager.remove_section(section).await
225 }
226
227 pub async fn destroy(self) -> Result<(), Error> {
229 self.manager.destroy().await
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use commonware_macros::test_traced;
237 use commonware_runtime::{deterministic, Runner, Supervisor as _};
238 use commonware_utils::NZUsize;
239
240 fn test_cfg() -> Config<()> {
241 Config {
242 partition: "test-partition".into(),
243 compression: None,
244 codec_config: (),
245 write_buffer: NZUsize!(1024),
246 }
247 }
248
249 #[test_traced]
250 fn test_glob_append_and_get() {
251 let executor = deterministic::Runner::default();
252 executor.start(|context| async move {
253 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
254 .await
255 .expect("Failed to init glob");
256
257 let value: i32 = 42;
259 let (offset, size) = glob.append(1, &value).await.expect("Failed to append");
260 assert_eq!(offset, 0);
261
262 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
264 assert_eq!(retrieved, value);
265
266 glob.sync(1).await.expect("Failed to sync");
268 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
269 assert_eq!(retrieved, value);
270
271 glob.destroy().await.expect("Failed to destroy");
272 });
273 }
274
275 #[test_traced]
276 fn test_glob_multiple_values() {
277 let executor = deterministic::Runner::default();
278 executor.start(|context| async move {
279 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
280 .await
281 .expect("Failed to init glob");
282
283 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
285 let mut locations = Vec::new();
286
287 for value in &values {
288 let (offset, size) = glob.append(1, value).await.expect("Failed to append");
289 locations.push((offset, size));
290 }
291
292 for (i, (offset, size)) in locations.iter().enumerate() {
294 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
295 assert_eq!(retrieved, values[i]);
296 }
297
298 glob.destroy().await.expect("Failed to destroy");
299 });
300 }
301
302 #[test_traced]
303 fn test_glob_with_compression() {
304 let executor = deterministic::Runner::default();
305 executor.start(|context| async move {
306 let cfg = Config {
307 partition: "test-partition".into(),
308 compression: Some(3), codec_config: (),
310 write_buffer: NZUsize!(1024),
311 };
312 let mut glob: Glob<_, [u8; 100]> = Glob::init(context.child("storage"), cfg)
313 .await
314 .expect("Failed to init glob");
315
316 let value: [u8; 100] = [0u8; 100]; let (offset, size) = glob.append(1, &value).await.expect("Failed to append");
319
320 assert!(size < 100 + 4);
322
323 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
325 assert_eq!(retrieved, value);
326
327 glob.destroy().await.expect("Failed to destroy");
328 });
329 }
330
331 #[test_traced]
332 fn test_glob_prune() {
333 let executor = deterministic::Runner::default();
334 executor.start(|context| async move {
335 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
336 .await
337 .expect("Failed to init glob");
338
339 for section in 1..=5 {
341 glob.append(section, &(section as i32))
342 .await
343 .expect("Failed to append");
344 glob.sync(section).await.expect("Failed to sync");
345 }
346
347 glob.prune(3).await.expect("Failed to prune");
349
350 assert!(glob.get(1, 0, 8).await.is_err());
352 assert!(glob.get(2, 0, 8).await.is_err());
353
354 assert!(glob.manager.blobs.contains_key(&3));
356 assert!(glob.manager.blobs.contains_key(&4));
357 assert!(glob.manager.blobs.contains_key(&5));
358
359 glob.destroy().await.expect("Failed to destroy");
360 });
361 }
362
363 #[test_traced]
364 fn test_glob_checksum_mismatch() {
365 let executor = deterministic::Runner::default();
366 executor.start(|context| async move {
367 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
368 .await
369 .expect("Failed to init glob");
370
371 let value: i32 = 42;
373 let (offset, size) = glob.append(1, &value).await.expect("Failed to append");
374 glob.sync(1).await.expect("Failed to sync");
375
376 let writer = glob.manager.blobs.get_mut(&1).unwrap();
378 writer
379 .write_at(offset, vec![0xFF, 0xFF, 0xFF, 0xFF])
380 .await
381 .expect("Failed to corrupt");
382 writer.sync().await.expect("Failed to sync");
383
384 let result = glob.get(1, offset, size).await;
386 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
387
388 glob.destroy().await.expect("Failed to destroy");
389 });
390 }
391
392 #[test_traced]
393 fn test_glob_rewind() {
394 let executor = deterministic::Runner::default();
395 executor.start(|context| async move {
396 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
397 .await
398 .expect("Failed to init glob");
399
400 let values: Vec<i32> = vec![1, 2, 3, 4, 5];
402 let mut locations = Vec::new();
403
404 for value in &values {
405 let (offset, size) = glob.append(1, value).await.expect("Failed to append");
406 locations.push((offset, size));
407 }
408 glob.sync(1).await.expect("Failed to sync");
409
410 let (third_offset, third_size) = locations[2];
412 let rewind_size = third_offset + u64::from(third_size);
413 glob.rewind_section(1, rewind_size)
414 .await
415 .expect("Failed to rewind");
416
417 for (i, (offset, size)) in locations.iter().take(3).enumerate() {
419 let retrieved = glob.get(1, *offset, *size).await.expect("Failed to get");
420 assert_eq!(retrieved, values[i]);
421 }
422
423 let (fourth_offset, fourth_size) = locations[3];
425 let result = glob.get(1, fourth_offset, fourth_size).await;
426 assert!(result.is_err());
427
428 glob.destroy().await.expect("Failed to destroy");
429 });
430 }
431
432 #[test_traced]
433 fn test_glob_persistence() {
434 let executor = deterministic::Runner::default();
435 executor.start(|context| async move {
436 let cfg = test_cfg();
437
438 let mut glob: Glob<_, i32> = Glob::init(context.child("first"), cfg.clone())
440 .await
441 .expect("Failed to init glob");
442
443 let value: i32 = 42;
444 let (offset, size) = glob.append(1, &value).await.expect("Failed to append");
445 glob.sync(1).await.expect("Failed to sync");
446 drop(glob);
447
448 let glob: Glob<_, i32> = Glob::init(context.child("second"), cfg)
450 .await
451 .expect("Failed to reinit glob");
452
453 let retrieved = glob.get(1, offset, size).await.expect("Failed to get");
454 assert_eq!(retrieved, value);
455
456 glob.destroy().await.expect("Failed to destroy");
457 });
458 }
459
460 #[test_traced]
461 fn test_glob_get_invalid_size() {
462 let executor = deterministic::Runner::default();
463 executor.start(|context| async move {
464 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
465 .await
466 .expect("Failed to init glob");
467
468 let (offset, _size) = glob.append(1, &42).await.expect("Failed to append");
469 glob.sync(1).await.expect("Failed to sync");
470
471 assert!(glob.get(1, offset, 0).await.is_err());
473
474 for size in 1..4u32 {
476 let result = glob.get(1, offset, size).await;
477 assert!(matches!(
478 result,
479 Err(Error::Runtime(RError::BlobInsufficientLength))
480 ));
481 }
482
483 glob.destroy().await.expect("Failed to destroy");
484 });
485 }
486
487 #[test_traced]
488 fn test_glob_get_wrong_size() {
489 let executor = deterministic::Runner::default();
490 executor.start(|context| async move {
491 let mut glob: Glob<_, i32> = Glob::init(context.child("storage"), test_cfg())
492 .await
493 .expect("Failed to init glob");
494
495 let (offset, correct_size) = glob.append(1, &42).await.expect("Failed to append");
496 glob.sync(1).await.expect("Failed to sync");
497
498 let result = glob.get(1, offset, correct_size - 1).await;
500 assert!(matches!(result, Err(Error::ChecksumMismatch(_, _))));
501
502 glob.destroy().await.expect("Failed to destroy");
503 });
504 }
505}