commonware_runtime/utils/buffer/paged/
read.rs1use super::Checksum;
2use crate::{Blob, Buf, Error, IoBuf};
3use commonware_codec::FixedSize;
4use std::{collections::VecDeque, num::NonZeroU16};
5use tracing::error;
6
7pub(super) struct BufferState {
13 buffer: IoBuf,
15 num_pages: usize,
17 last_page_len: usize,
19}
20
21pub(super) struct PageReader<B: Blob> {
26 blob: B,
28 page_size: usize,
30 logical_page_size: usize,
32 physical_blob_size: u64,
34 logical_blob_size: u64,
36 blob_page: u64,
38 prefetch_count: usize,
40}
41
42impl<B: Blob> PageReader<B> {
43 pub(super) fn new(
52 blob: B,
53 physical_blob_size: u64,
54 logical_blob_size: u64,
55 prefetch_count: usize,
56 logical_page_size: NonZeroU16,
57 ) -> Self {
58 let logical_page_size = logical_page_size.get() as usize;
59 let page_size = logical_page_size + Checksum::SIZE;
60 let physical_pages = physical_blob_size / page_size as u64;
61 let logical_pages = if logical_blob_size == 0 {
62 0
63 } else {
64 ((logical_blob_size - 1) / logical_page_size as u64) + 1
65 };
66 assert_eq!(physical_blob_size % page_size as u64, 0);
67 assert_eq!(physical_pages, logical_pages);
68
69 Self {
70 blob,
71 page_size,
72 logical_page_size,
73 physical_blob_size,
74 logical_blob_size,
75 blob_page: 0,
76 prefetch_count,
77 }
78 }
79
80 pub(super) const fn blob_size(&self) -> u64 {
82 self.logical_blob_size
83 }
84
85 pub(super) const fn page_size(&self) -> usize {
87 self.page_size
88 }
89
90 pub(super) const fn logical_page_size(&self) -> usize {
92 self.logical_page_size
93 }
94
95 pub(super) async fn fill(&mut self) -> Result<Option<(BufferState, usize)>, Error> {
100 let start_offset = match self.blob_page.checked_mul(self.page_size as u64) {
102 Some(o) => o,
103 None => return Err(Error::OffsetOverflow),
104 };
105 if start_offset >= self.physical_blob_size {
106 return Ok(None); }
108
109 let remaining_physical = (self.physical_blob_size - start_offset) as usize;
111 let max_pages = remaining_physical / self.page_size;
112 let pages_to_read = max_pages.min(self.prefetch_count);
113 if pages_to_read == 0 {
114 return Ok(None);
115 }
116 let bytes_to_read = pages_to_read * self.page_size;
117
118 let physical_buf = self
120 .blob
121 .read_at(start_offset, bytes_to_read)
122 .await?
123 .coalesce()
124 .freeze();
125
126 let mut total_logical = 0usize;
128 let mut last_len = 0usize;
129 let is_final_batch = pages_to_read == max_pages;
130 for page_idx in 0..pages_to_read {
131 let page_start = page_idx * self.page_size;
132 let page_slice = &physical_buf.as_ref()[page_start..page_start + self.page_size];
133 let Some(record) = Checksum::validate_page(page_slice) else {
134 error!(page = self.blob_page + page_idx as u64, "CRC mismatch");
135 return Err(Error::InvalidChecksum);
136 };
137 let (len, _) = record.get_crc();
138 let len = len as usize;
139
140 let is_last_page_in_blob = is_final_batch && page_idx + 1 == pages_to_read;
142 if !is_last_page_in_blob && len != self.logical_page_size {
143 error!(
144 page = self.blob_page + page_idx as u64,
145 expected = self.logical_page_size,
146 actual = len,
147 "non-last page has partial length"
148 );
149 return Err(Error::InvalidChecksum);
150 }
151
152 let logical_start = (self.blob_page + page_idx as u64)
153 .checked_mul(self.logical_page_size as u64)
154 .ok_or(Error::OffsetOverflow)?;
155 let logical_remaining = self.logical_blob_size.saturating_sub(logical_start);
156 let logical_remaining_in_page =
157 logical_remaining.min(self.logical_page_size as u64) as usize;
158 let exposed_len = len.min(logical_remaining_in_page);
159
160 total_logical += exposed_len;
161 last_len = exposed_len;
162 }
163 self.blob_page += pages_to_read as u64;
164
165 let state = BufferState {
166 buffer: physical_buf,
167 num_pages: pages_to_read,
168 last_page_len: last_len,
169 };
170
171 Ok(Some((state, total_logical)))
172 }
173}
174
175struct ReplayBuf {
181 page_size: usize,
183 logical_page_size: usize,
185 buffers: VecDeque<BufferState>,
187 current_page: usize,
189 offset_in_page: usize,
191 remaining: usize,
193}
194
195impl ReplayBuf {
196 const fn new(page_size: usize, logical_page_size: usize) -> Self {
198 Self {
199 page_size,
200 logical_page_size,
201 buffers: VecDeque::new(),
202 current_page: 0,
203 offset_in_page: 0,
204 remaining: 0,
205 }
206 }
207
208 fn clear(&mut self) {
210 self.buffers.clear();
211 self.current_page = 0;
212 self.offset_in_page = 0;
213 self.remaining = 0;
214 }
215
216 fn push(&mut self, state: BufferState, logical_bytes: usize) {
218 let skip = if self.buffers.is_empty() {
221 self.offset_in_page
222 } else {
223 0
224 };
225 self.buffers.push_back(state);
226 self.remaining += logical_bytes.saturating_sub(skip);
227 }
228
229 const fn page_len(buf: &BufferState, page_idx: usize, logical_page_size: usize) -> usize {
231 if page_idx + 1 == buf.num_pages {
232 buf.last_page_len
233 } else {
234 logical_page_size
235 }
236 }
237}
238
239impl Buf for ReplayBuf {
240 fn remaining(&self) -> usize {
241 self.remaining
242 }
243
244 fn chunk(&self) -> &[u8] {
245 let Some(buf) = self.buffers.front() else {
246 return &[];
247 };
248 if self.current_page >= buf.num_pages {
249 return &[];
250 }
251 let page_len = Self::page_len(buf, self.current_page, self.logical_page_size);
252 let physical_start = self.current_page * self.page_size + self.offset_in_page;
253 let physical_end = self.current_page * self.page_size + page_len;
254 &buf.buffer.as_ref()[physical_start..physical_end]
255 }
256
257 fn advance(&mut self, mut cnt: usize) {
258 self.remaining = self.remaining.saturating_sub(cnt);
259
260 while cnt > 0 {
261 let Some(buf) = self.buffers.front() else {
262 break;
263 };
264
265 while cnt > 0 && self.current_page < buf.num_pages {
267 let page_len = Self::page_len(buf, self.current_page, self.logical_page_size);
268 let available = page_len - self.offset_in_page;
269 if cnt < available {
270 self.offset_in_page += cnt;
271 return;
272 }
273 cnt -= available;
274 self.current_page += 1;
275 self.offset_in_page = 0;
276 }
277
278 if self.current_page >= buf.num_pages {
280 self.buffers.pop_front();
281 self.current_page = 0;
282 self.offset_in_page = 0;
283 }
284 }
285 }
286}
287
288pub struct Replay<B: Blob> {
293 reader: PageReader<B>,
295 buffer: ReplayBuf,
297 exhausted: bool,
299}
300
301impl<B: Blob> Replay<B> {
302 pub(super) const fn new(reader: PageReader<B>) -> Self {
304 let page_size = reader.page_size();
305 let logical_page_size = reader.logical_page_size();
306 Self {
307 reader,
308 buffer: ReplayBuf::new(page_size, logical_page_size),
309 exhausted: false,
310 }
311 }
312
313 pub const fn blob_size(&self) -> u64 {
315 self.reader.blob_size()
316 }
317
318 pub const fn is_exhausted(&self) -> bool {
323 self.exhausted
324 }
325
326 pub async fn ensure(&mut self, n: usize) -> Result<bool, Error> {
337 while self.buffer.remaining < n && !self.exhausted {
338 match self.reader.fill().await? {
339 Some((state, logical_bytes)) => {
340 self.buffer.push(state, logical_bytes);
341 }
342 None => {
343 self.exhausted = true;
344 }
345 }
346 }
347 Ok(self.buffer.remaining >= n)
348 }
349
350 pub fn seek_to(&mut self, offset: u64) -> Result<(), Error> {
353 if offset > self.reader.blob_size() {
354 return Err(Error::BlobInsufficientLength);
355 }
356
357 self.buffer.clear();
358 self.exhausted = false;
359
360 let page_size = self.reader.logical_page_size as u64;
361 self.reader.blob_page = offset / page_size;
362 self.buffer.current_page = 0;
363 self.buffer.offset_in_page = (offset % page_size) as usize;
364
365 Ok(())
366 }
367}
368
369impl<B: Blob> Buf for Replay<B> {
370 fn remaining(&self) -> usize {
371 self.buffer.remaining()
372 }
373
374 fn chunk(&self) -> &[u8] {
375 self.buffer.chunk()
376 }
377
378 fn advance(&mut self, cnt: usize) {
379 self.buffer.advance(cnt);
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::{super::writer::Writer, *};
386 use crate::{deterministic, Runner as _, Storage as _};
387 use commonware_macros::test_traced;
388 use commonware_utils::{NZUsize, NZU16};
389
390 const PAGE_SIZE: NonZeroU16 = NZU16!(103);
391 const BUFFER_PAGES: usize = 2;
392
393 #[test_traced("DEBUG")]
394 fn test_replay_basic() {
395 let executor = deterministic::Runner::default();
396 executor.start(|context: deterministic::Context| async move {
397 let (blob, blob_size) = context.open("test_partition", b"test_blob").await.unwrap();
398 assert_eq!(blob_size, 0);
399
400 let cache_ref =
401 super::super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_PAGES));
402 let mut append = Writer::new(blob.clone(), blob_size, BUFFER_PAGES * 115, cache_ref)
403 .await
404 .unwrap();
405
406 let data: Vec<u8> = (0u8..=255).cycle().take(300).collect();
408 append.append(&data).await.unwrap();
409 append.sync().await.unwrap();
410
411 let mut replay = append.replay(NZUsize!(BUFFER_PAGES)).await.unwrap();
413
414 replay.ensure(300).await.unwrap();
416
417 assert_eq!(replay.remaining(), 300);
419
420 let mut collected = Vec::new();
422 while replay.remaining() > 0 {
423 let chunk = replay.chunk();
424 collected.extend_from_slice(chunk);
425 let len = chunk.len();
426 replay.advance(len);
427 }
428 assert_eq!(collected, data);
429 });
430 }
431
432 #[test_traced("DEBUG")]
433 fn test_replay_partial_page() {
434 let executor = deterministic::Runner::default();
435 executor.start(|context: deterministic::Context| async move {
436 let (blob, blob_size) = context.open("test_partition", b"test_blob").await.unwrap();
437
438 let cache_ref =
439 super::super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_PAGES));
440 let mut append = Writer::new(blob.clone(), blob_size, BUFFER_PAGES * 115, cache_ref)
441 .await
442 .unwrap();
443
444 let data: Vec<u8> = (1u8..=(PAGE_SIZE.get() + 10) as u8).collect();
446 append.append(&data).await.unwrap();
447 append.sync().await.unwrap();
448
449 let mut replay = append.replay(NZUsize!(BUFFER_PAGES)).await.unwrap();
450
451 replay.ensure(data.len()).await.unwrap();
453
454 assert_eq!(replay.remaining(), data.len());
455 });
456 }
457
458 #[test_traced("DEBUG")]
459 fn test_replay_cross_buffer_boundary() {
460 let executor = deterministic::Runner::default();
463 executor.start(|context: deterministic::Context| async move {
464 let (blob, blob_size) = context.open("test_partition", b"test_blob").await.unwrap();
465 assert_eq!(blob_size, 0);
466
467 let cache_ref =
468 super::super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_PAGES));
469 let mut append = Writer::new(blob.clone(), blob_size, BUFFER_PAGES * 115, cache_ref)
470 .await
471 .unwrap();
472
473 let data: Vec<u8> = (0u8..=255).cycle().take(400).collect();
475 append.append(&data).await.unwrap();
476 append.sync().await.unwrap();
477
478 let mut replay = append.replay(NZUsize!(115)).await.unwrap();
482
483 assert!(replay.ensure(400).await.unwrap());
486 assert_eq!(replay.remaining(), 400);
487
488 let mut collected = Vec::new();
490 let mut chunks_read = 0;
491 while replay.remaining() > 0 {
492 let chunk = replay.chunk();
493 assert!(
494 !chunk.is_empty(),
495 "chunk() returned empty but remaining > 0"
496 );
497 collected.extend_from_slice(chunk);
498 let len = chunk.len();
499 replay.advance(len);
500 chunks_read += 1;
501 }
502
503 assert_eq!(collected, data);
504 assert!(
507 chunks_read >= 4,
508 "Expected at least 4 chunks for 4 pages, got {}",
509 chunks_read
510 );
511 });
512 }
513
514 #[test_traced("DEBUG")]
515 fn test_replay_empty_blob() {
516 let executor = deterministic::Runner::default();
519 executor.start(|context: deterministic::Context| async move {
520 let (blob, blob_size) = context.open("test_partition", b"test_blob").await.unwrap();
521 assert_eq!(blob_size, 0);
522
523 let cache_ref =
524 super::super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_PAGES));
525 let mut append = Writer::new(blob.clone(), blob_size, BUFFER_PAGES * 115, cache_ref)
526 .await
527 .unwrap();
528
529 assert_eq!(append.size(), 0);
531
532 let mut replay = append.replay(NZUsize!(BUFFER_PAGES)).await.unwrap();
534
535 assert_eq!(replay.remaining(), 0);
538
539 assert!(replay.ensure(0).await.unwrap());
541
542 assert!(!replay.ensure(1).await.unwrap());
544
545 assert!(replay.is_exhausted());
547
548 assert!(replay.chunk().is_empty());
550
551 assert_eq!(replay.remaining(), 0);
553 });
554 }
555
556 #[test_traced("DEBUG")]
557 fn test_replay_seek_to() {
558 let executor = deterministic::Runner::default();
559 executor.start(|context: deterministic::Context| async move {
560 let (blob, blob_size) = context.open("test_partition", b"test_blob").await.unwrap();
561
562 let cache_ref =
563 super::super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_PAGES));
564 let mut append = Writer::new(blob.clone(), blob_size, BUFFER_PAGES * 115, cache_ref)
565 .await
566 .unwrap();
567
568 let data: Vec<u8> = (0u8..=255).cycle().take(300).collect();
570 append.append(&data).await.unwrap();
571 append.sync().await.unwrap();
572
573 let mut replay = append.replay(NZUsize!(BUFFER_PAGES)).await.unwrap();
574
575 replay.seek_to(150).unwrap();
577 replay.ensure(50).await.unwrap();
578 assert_eq!(replay.get_u8(), data[150]);
579
580 replay.seek_to(0).unwrap();
582 replay.ensure(1).await.unwrap();
583 assert_eq!(replay.get_u8(), data[0]);
584
585 assert!(replay.seek_to(data.len() as u64 + 1).is_err());
587
588 let seek_offset = 150usize;
590 replay.seek_to(seek_offset as u64).unwrap();
591 let expected_remaining = data.len() - seek_offset;
592 let mut collected = Vec::new();
594 loop {
595 if !replay.ensure(1).await.unwrap() {
597 break; }
599 let chunk = replay.chunk();
600 if chunk.is_empty() {
601 break;
602 }
603 collected.extend_from_slice(chunk);
604 let len = chunk.len();
605 replay.advance(len);
606 }
607 assert_eq!(
608 collected.len(),
609 expected_remaining,
610 "After seeking to {}, should read {} bytes but got {}",
611 seek_offset,
612 expected_remaining,
613 collected.len()
614 );
615 assert_eq!(collected, &data[seek_offset..]);
616 });
617 }
618}