use super::CacheRef;
use crate::{Blob, Error, IoBufMut, IoBufs};
use futures::stream::{FuturesUnordered, StreamExt};
use std::num::NonZeroUsize;
pub struct View<'a, B: Blob> {
pub(super) blob: &'a B,
pub(super) cache_ref: &'a CacheRef,
pub(super) id: u64,
pub(super) size: u64,
pub(super) tail_offset: u64,
pub(super) tail: &'a [u8],
}
impl<B: Blob> Clone for View<'_, B> {
fn clone(&self) -> Self {
*self
}
}
impl<B: Blob> Copy for View<'_, B> {}
impl<B: Blob> View<'_, B> {
fn copy_tail_overlap(&self, buf: &mut [u8], offset: u64) -> usize {
let tail_start = self.tail_offset.max(offset);
let prefix_len = (tail_start - offset) as usize;
let tail_offset = (tail_start - self.tail_offset) as usize;
let tail_len = buf.len() - prefix_len;
let (_, tail_buf) = buf.split_at_mut(prefix_len);
tail_buf.copy_from_slice(&self.tail[tail_offset..tail_offset + tail_len]);
prefix_len
}
pub fn try_read_sync_into(&self, buf: &mut [u8], offset: u64) -> bool {
let Some(end_offset) = offset.checked_add(buf.len() as u64) else {
return false;
};
if end_offset > self.size {
return false;
}
if buf.is_empty() {
return true;
}
if end_offset <= self.tail_offset {
return self.cache_ref.read_cached(self.id, buf, offset) == buf.len();
}
let dst_start = self.copy_tail_overlap(buf, offset);
if dst_start == 0 {
return true;
}
self.cache_ref
.read_cached(self.id, &mut buf[..dst_start], offset)
== dst_start
}
pub async fn read_into(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
let end_offset = offset
.checked_add(buf.len() as u64)
.ok_or(Error::OffsetOverflow)?;
if end_offset > self.size {
return Err(Error::BlobInsufficientLength);
}
let remaining = if end_offset <= self.tail_offset {
buf.len()
} else {
self.copy_tail_overlap(buf, offset)
};
if remaining == 0 {
return Ok(());
}
let cached = self
.cache_ref
.read_cached(self.id, &mut buf[..remaining], offset);
if cached == remaining {
return Ok(());
}
let uncached_offset = offset + cached as u64;
let uncached_len = remaining - cached;
self.cache_ref
.read(
self.blob,
self.id,
&mut buf[cached..cached + uncached_len],
uncached_offset,
)
.await
}
pub async fn read_at(&self, offset: u64, len: usize) -> Result<IoBufs, Error> {
let mut buf = unsafe { self.cache_ref.pool().alloc_len(len) };
self.read_into(buf.as_mut(), offset).await?;
Ok(buf.into())
}
pub async fn read_up_to(
&self,
offset: u64,
len: usize,
bufs: impl Into<IoBufMut> + Send,
) -> Result<(IoBufMut, usize), Error> {
let mut bufs = bufs.into();
if len == 0 {
bufs.truncate(0);
return Ok((bufs, 0));
}
let available = (self.size.saturating_sub(offset) as usize).min(len);
if available == 0 {
return Err(Error::BlobInsufficientLength);
}
unsafe { bufs.set_len(available) };
self.read_into(bufs.as_mut(), offset).await?;
Ok((bufs, available))
}
pub async fn read_many_into(
&self,
buf: &mut [u8],
offsets: &[u64],
item_size: NonZeroUsize,
) -> Result<usize, Error> {
let ranges = || offsets.iter().map(|&o| (o, item_size.get()));
super::validate_read_ranges(buf.len(), ranges(), self.size)?;
if offsets.is_empty() {
return Ok(0);
}
let mut cache_ranges = super::split_read_ranges(buf, ranges(), self.tail_offset, self.tail);
self.cache_ref.read_cached_many(self.id, &mut cache_ranges);
let blob_reads = cache_ranges.len();
if cache_ranges.is_empty() {
return Ok(offsets.len());
}
let mut reads = cache_ranges
.iter_mut()
.map(|(item_buf, offset)| self.cache_ref.read(self.blob, self.id, item_buf, *offset))
.collect::<FuturesUnordered<_>>();
while let Some(result) = reads.next().await {
result?;
}
Ok(offsets.len() - blob_reads)
}
pub fn try_read_many_sync_into(
&self,
buf: &mut [u8],
offsets: &[u64],
item_size: NonZeroUsize,
) -> Vec<usize> {
let ranges = || offsets.iter().map(|&o| (o, item_size.get()));
if super::validate_read_ranges(buf.len(), ranges(), self.size).is_err() {
return (0..offsets.len()).collect();
}
if offsets.is_empty() {
return Vec::new();
}
let mut cache_ranges = super::split_read_ranges(buf, ranges(), self.tail_offset, self.tail);
if cache_ranges.is_empty() {
return Vec::new();
}
self.cache_ref.read_cached_many(self.id, &mut cache_ranges);
map_misses(cache_ranges, |idx| (offsets[idx], item_size.get()))
}
pub fn try_read_ranges_sync_into(&self, buf: &mut [u8], ranges: &[(u64, usize)]) -> Vec<usize> {
if super::validate_read_ranges(buf.len(), ranges.iter().copied(), self.size).is_err() {
return (0..ranges.len()).collect();
}
if ranges.is_empty() {
return Vec::new();
}
let mut cache_ranges =
super::split_read_ranges(buf, ranges.iter().copied(), self.tail_offset, self.tail);
if cache_ranges.is_empty() {
return Vec::new();
}
self.cache_ref.read_cached_many(self.id, &mut cache_ranges);
map_misses(cache_ranges, |idx| ranges[idx])
}
}
fn map_misses(
missed: Vec<(&mut [u8], u64)>,
mut slot: impl FnMut(usize) -> (u64, usize),
) -> Vec<usize> {
let mut misses = Vec::with_capacity(missed.len());
let mut idx = 0;
for (_, offset) in missed {
loop {
let (slot_offset, slot_len) = slot(idx);
if slot_len != 0 && slot_offset == offset {
break;
}
idx += 1;
}
misses.push(idx);
idx += 1;
}
misses
}
#[cfg(test)]
mod tests {
use crate::{buffer::paged::Writer, deterministic, Runner as _, Storage as _};
use commonware_utils::{NZUsize, NZU16};
use std::num::NonZeroU16;
const PAGE_SIZE: NonZeroU16 = NZU16!(103);
const BUFFER_SIZE: usize = PAGE_SIZE.get() as usize * 2;
#[test]
fn test_view_try_read_sync_straddles_cache_and_tail() {
let executor = deterministic::Runner::default();
executor.start(|context: deterministic::Context| async move {
let cache_ref =
super::CacheRef::from_pooler(&context, PAGE_SIZE, NZUsize!(BUFFER_SIZE));
let (blob, blob_size) = context
.open("test_partition", b"view_straddle")
.await
.unwrap();
let mut writer = Writer::new(blob, blob_size, BUFFER_SIZE, cache_ref)
.await
.unwrap();
let page_size = PAGE_SIZE.get() as usize;
writer.append(&vec![0xAA; page_size]).await.unwrap();
writer.append(b"TAIL").await.unwrap();
writer.sync().await.unwrap();
writer.read_at(0, page_size).await.unwrap();
let mut buf = [0u8; 4];
assert!(writer.try_read_sync_into(&mut buf, page_size as u64 - 2));
assert_eq!(&buf, &[0xAA, 0xAA, b'T', b'A']);
});
}
}