|
@@ -11,6 +11,7 @@ mod serde_tests;
|
|
|
#[macro_use]
|
|
|
extern crate static_assertions;
|
|
|
|
|
|
+use brotli::{CompressorWriter, Decompressor};
|
|
|
use serde_block_tree::{self, read_from, write_to};
|
|
|
use harness::Message;
|
|
|
mod crypto;
|
|
@@ -29,18 +30,48 @@ use serde::{Serialize, Deserialize};
|
|
|
use serde_big_array::BigArray;
|
|
|
use log::{info, error};
|
|
|
|
|
|
+#[derive(Debug)]
|
|
|
enum Error {
|
|
|
Io(std::io::Error),
|
|
|
Serde(serde_block_tree::Error),
|
|
|
Crypto(crypto::Error),
|
|
|
+ IncorrectSize { expected: usize, actual: usize },
|
|
|
+ Custom(Box<dyn std::fmt::Debug + Send + Sync>),
|
|
|
}
|
|
|
|
|
|
+impl Error {
|
|
|
+ fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Error {
|
|
|
+ Error::Custom(Box::new(err))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Display for Error {
|
|
|
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
+ match self {
|
|
|
+ Error::Io(err) => err.fmt(f),
|
|
|
+ Error::Serde(err) => err.fmt(f),
|
|
|
+ Error::Crypto(err) => err.fmt(f),
|
|
|
+ Error::IncorrectSize { expected, actual }
|
|
|
+ => write!(f, "incorrect size {}, expected {}", actual, expected),
|
|
|
+ Error::Custom(err) => err.fmt(f),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::error::Error for Error {}
|
|
|
+
|
|
|
impl From<std::io::Error> for Error {
|
|
|
fn from(err: std::io::Error) -> Self {
|
|
|
Error::Io(err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+impl From<Error> for std::io::Error {
|
|
|
+ fn from(err: Error) -> Self {
|
|
|
+ io::Error::new(io::ErrorKind::Other, err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl From<serde_block_tree::Error> for Error {
|
|
|
fn from(err: serde_block_tree::Error) -> Self {
|
|
|
Error::Serde(err)
|
|
@@ -53,6 +84,12 @@ impl From<crypto::Error> for Error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+impl From<std::num::TryFromIntError> for Error {
|
|
|
+ fn from(err: std::num::TryFromIntError) -> Self {
|
|
|
+ Error::custom(err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
/// A Block tagged with its version number. When a block of a previous version is received over
|
|
@@ -69,6 +106,16 @@ const SECTOR_SZ_DEFAULT: usize = 4096;
|
|
|
trait Sectored {
|
|
|
// Returns the size of the sector for this stream.
|
|
|
fn sector_sz(&self) -> usize;
|
|
|
+
|
|
|
+ fn assert_sector_sz(&self, actual: usize) -> Result<()> {
|
|
|
+ let expected = self.sector_sz();
|
|
|
+ if expected == actual {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ Err(Error::IncorrectSize { expected, actual })
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
@@ -86,12 +133,14 @@ struct NewBlock<T> {
|
|
|
}
|
|
|
|
|
|
impl<T> NewBlock<T> {
|
|
|
- fn compose<U, V: Compose<T, U>>(self, new_body: V) -> NewBlock<U> {
|
|
|
- NewBlock {
|
|
|
+ fn compose<E: Into<Error>, U: Decompose<T>, V: Compose<T, U, Error = E>>(
|
|
|
+ self, new_body: V
|
|
|
+ ) -> Result<NewBlock<U>> {
|
|
|
+ Ok(NewBlock {
|
|
|
header: self.header,
|
|
|
sig: self.sig,
|
|
|
- body: new_body.compose(self.body),
|
|
|
- }
|
|
|
+ body: new_body.compose(self.body).map_err(|err| err.into())?,
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -131,8 +180,13 @@ impl<T: Seek> Seek for NewBlock<T> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-trait Compose<T, U> {
|
|
|
- fn compose(self, inner: T) -> U;
|
|
|
+trait Decompose<T> {
|
|
|
+ fn into_inner(self) -> T;
|
|
|
+}
|
|
|
+
|
|
|
+trait Compose<T, U: Decompose<T>> {
|
|
|
+ type Error;
|
|
|
+ fn compose(self, inner: T) -> std::result::Result<U, Self::Error>;
|
|
|
}
|
|
|
|
|
|
struct FileBody {
|
|
@@ -179,6 +233,279 @@ impl Seek for FileBody {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Extensions to the `Read` trait.
|
|
|
+trait ReadExt: Read {
|
|
|
+ /// Reads repeatedly until one of the following occur:
|
|
|
+ /// 1. The given buffer is full.
|
|
|
+ /// 2. A call to `read` returns 0.
|
|
|
+ /// 3. A call to `read` returns an error.
|
|
|
+ fn fill_buf(&mut self, mut dest: &mut [u8]) -> io::Result<()> {
|
|
|
+ while !dest.is_empty() {
|
|
|
+ let byte_ct = self.read(dest)?;
|
|
|
+ if 0 == byte_ct {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ dest = &mut dest[byte_ct..];
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Read> ReadExt for T {}
|
|
|
+
|
|
|
+impl<T: Write> Decompose<T> for CompressorWriter<T> {
|
|
|
+ fn into_inner(self) -> T {
|
|
|
+ self.into_inner()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Read> Decompose<T> for Decompressor<T> {
|
|
|
+ fn into_inner(self) -> T {
|
|
|
+ self.into_inner()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone)]
|
|
|
+struct BrotliParams {
|
|
|
+ buf_sz: usize,
|
|
|
+ quality: u32,
|
|
|
+ window_sz: u32,
|
|
|
+}
|
|
|
+
|
|
|
+impl BrotliParams {
|
|
|
+ fn new(buf_sz: usize, quality: u32, window_sz: u32) -> BrotliParams {
|
|
|
+ BrotliParams { buf_sz, quality, window_sz }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Write> Compose<T, CompressorWriter<T>> for BrotliParams {
|
|
|
+ type Error = Error;
|
|
|
+ fn compose(self, inner: T) -> Result<CompressorWriter<T>> {
|
|
|
+ Ok(CompressorWriter::new(inner, self.buf_sz, self.quality, self.window_sz))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Read> Compose<T, Decompressor<T>> for BrotliParams {
|
|
|
+ type Error = Error;
|
|
|
+ fn compose(self, inner: T) -> Result<Decompressor<T>> {
|
|
|
+ Ok(Decompressor::new(inner, self.buf_sz))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+struct BufSectored<T> {
|
|
|
+ inner: T,
|
|
|
+ buf: Vec<u8>,
|
|
|
+ dirty: bool,
|
|
|
+ len: usize,
|
|
|
+ pos: usize,
|
|
|
+}
|
|
|
+
|
|
|
+impl<T> BufSectored<T> {
|
|
|
+ fn buf_pos(&self) -> usize {
|
|
|
+ self.pos % self.sector_sz()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn curr_sector_start(&self) -> usize {
|
|
|
+ if self.pos == 0 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ let sect_sz = self.sector_sz();
|
|
|
+ let index = (self.pos - 1) / sect_sz;
|
|
|
+ sect_sz * index
|
|
|
+ }
|
|
|
+
|
|
|
+ fn buf_end(&self) -> usize {
|
|
|
+ let sect_start = self.curr_sector_start();
|
|
|
+ let limit = self.len.min(sect_start + self.sector_sz());
|
|
|
+ limit - sect_start
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl BufSectored<()> {
|
|
|
+ fn new() -> BufSectored<()> {
|
|
|
+ BufSectored {
|
|
|
+ inner: (),
|
|
|
+ buf: Vec::new(),
|
|
|
+ dirty: false,
|
|
|
+ len: 0,
|
|
|
+ pos: 0,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Read> BufSectored<T> {
|
|
|
+ fn fill_internal_buf(&mut self) -> io::Result<()> {
|
|
|
+ self.inner.fill_buf(&mut self.buf)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T> Decompose<T> for BufSectored<T> {
|
|
|
+ fn into_inner(self) -> T {
|
|
|
+ self.inner
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Sectored + Read + Seek> Compose<T, BufSectored<T>> for BufSectored<()> {
|
|
|
+ type Error = Error;
|
|
|
+ fn compose(self, inner: T) -> Result<BufSectored<T>> {
|
|
|
+ let sect_sz = inner.sector_sz();
|
|
|
+ let mut sectored = BufSectored {
|
|
|
+ inner,
|
|
|
+ buf: self.buf,
|
|
|
+ dirty: false,
|
|
|
+ len: 0,
|
|
|
+ pos: 0,
|
|
|
+ };
|
|
|
+ sectored.inner.seek(SeekFrom::Start(0))?;
|
|
|
+ sectored.buf.resize(sect_sz, 0);
|
|
|
+ if sectored.fill_internal_buf().is_ok() {
|
|
|
+ let mut slice = sectored.buf.as_slice();
|
|
|
+ if let Ok(len) = read_from::<u64, _>(&mut slice) {
|
|
|
+ sectored.len = len.try_into()?;
|
|
|
+ sectored.pos = std::mem::size_of::<u64>();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Ok(sectored)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T> Sectored for BufSectored<T> {
|
|
|
+ fn sector_sz(&self) -> usize {
|
|
|
+ self.buf.len()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Seek + Read + Write> Write for BufSectored<T> {
|
|
|
+ fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
|
|
|
+ let src_len_start = src.len();
|
|
|
+ let mut dest = {
|
|
|
+ let buf_pos = self.buf_pos();
|
|
|
+ &mut self.buf[buf_pos..]
|
|
|
+ };
|
|
|
+ while !src.is_empty() {
|
|
|
+ if dest.is_empty() {
|
|
|
+ self.flush()?;
|
|
|
+ dest = {
|
|
|
+ let buf_pos = self.buf_pos();
|
|
|
+ &mut self.buf[buf_pos..]
|
|
|
+ };
|
|
|
+ }
|
|
|
+ let sz = src.len().min(dest.len());
|
|
|
+ (&mut dest[..sz]).copy_from_slice(&src[..sz]);
|
|
|
+ dest = &mut dest[sz..];
|
|
|
+ src = &src[sz..];
|
|
|
+ self.dirty = sz > 0;
|
|
|
+ self.pos += sz;
|
|
|
+ }
|
|
|
+ Ok(src_len_start)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn flush(&mut self) -> io::Result<()> {
|
|
|
+ fn err_conv<T, E: std::error::Error + Send + Sync + 'static>(
|
|
|
+ result: std::result::Result<T, E>
|
|
|
+ ) -> std::result::Result<T, io::Error> {
|
|
|
+ result.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
|
|
+ }
|
|
|
+
|
|
|
+ if !self.dirty {
|
|
|
+ return Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ // Write out the contents of the buffer.
|
|
|
+ let inner_pos = self.inner.stream_position()?;
|
|
|
+ let inner_pos_usize: usize = err_conv(inner_pos.try_into())?;
|
|
|
+ if self.pos <= inner_pos_usize {
|
|
|
+ // The contents of the buffer were previously read from inner, so we write the updated
|
|
|
+ // contents to the same offset.
|
|
|
+ let sect_start: u64 = err_conv(self.curr_sector_start().try_into())?;
|
|
|
+ self.inner.seek(SeekFrom::Start(sect_start))?;
|
|
|
+ }
|
|
|
+ self.inner.write_all(&self.buf)?;
|
|
|
+
|
|
|
+ // Update the stored length.
|
|
|
+ self.len = self.len.max(self.pos);
|
|
|
+ self.inner.seek(SeekFrom::Start(0))?;
|
|
|
+ self.fill_internal_buf()?;
|
|
|
+ {
|
|
|
+ let len: u64 = err_conv(self.len.try_into())?;
|
|
|
+ let mut slice = self.buf.as_mut_slice();
|
|
|
+ err_conv(write_to(&len, &mut slice))?;
|
|
|
+ }
|
|
|
+ self.inner.seek(SeekFrom::Start(0))?;
|
|
|
+ self.inner.write_all(&self.buf)?;
|
|
|
+
|
|
|
+ // Seek back to the previous position.
|
|
|
+ self.inner.seek(SeekFrom::Start(inner_pos))?;
|
|
|
+ self.fill_internal_buf()?;
|
|
|
+ self.dirty = false;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Read> Read for BufSectored<T> {
|
|
|
+ fn read(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
|
|
|
+ if self.pos == self.len {
|
|
|
+ return Ok(0)
|
|
|
+ }
|
|
|
+
|
|
|
+ let dest_len_start = dest.len();
|
|
|
+ let mut src = {
|
|
|
+ let start = self.buf_pos();
|
|
|
+ let end = self.buf_end();
|
|
|
+ &self.buf[start..end]
|
|
|
+ };
|
|
|
+ while !dest.is_empty() {
|
|
|
+ if src.is_empty() {
|
|
|
+ self.fill_internal_buf()?;
|
|
|
+ src = &self.buf[..];
|
|
|
+ }
|
|
|
+ let sz = src.len().min(dest.len());
|
|
|
+ (&mut dest[..sz]).copy_from_slice(&src[..sz]);
|
|
|
+ dest = &mut dest[sz..];
|
|
|
+ src = &src[sz..];
|
|
|
+ self.pos += sz;
|
|
|
+ }
|
|
|
+ Ok(dest_len_start)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Seek + Read + Write> Seek for BufSectored<T> {
|
|
|
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
|
|
+ let stream_pos = self.inner.stream_position()?;
|
|
|
+ let rel_start = match pos {
|
|
|
+ SeekFrom::Start(rel_start) => rel_start,
|
|
|
+ SeekFrom::Current(rel_curr) => {
|
|
|
+ if rel_curr > 0 {
|
|
|
+ stream_pos + rel_curr as u64
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ stream_pos - rel_curr as u64
|
|
|
+ }
|
|
|
+ },
|
|
|
+ SeekFrom::End(_) => return Err(io::Error::new(
|
|
|
+ io::ErrorKind::Unsupported,
|
|
|
+ "seeking relative to the end of the stream is not supported"
|
|
|
+ ))
|
|
|
+ };
|
|
|
+ let sect_sz = self.sector_sz();
|
|
|
+ let sect_index = stream_pos as usize / sect_sz;
|
|
|
+ let sect_index_new = rel_start as usize / sect_sz;
|
|
|
+ let stream_pos = if sect_index != sect_index_new {
|
|
|
+ self.flush()?;
|
|
|
+ let stream_pos = self.inner.seek(SeekFrom::Start((sect_index_new * sect_sz) as u64))?;
|
|
|
+ self.fill_internal_buf()?;
|
|
|
+ stream_pos
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ stream_pos
|
|
|
+ };
|
|
|
+ self.pos = stream_pos as usize;
|
|
|
+ Ok(rel_start)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// A container which binds together ciphertext along with the metadata needed to identify,
|
|
|
/// verify and decrypt it.
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
|
@@ -527,6 +854,8 @@ fn main() {
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
+ use std::{io::Cursor};
|
|
|
+
|
|
|
use super::*;
|
|
|
use test_helpers::*;
|
|
|
|
|
@@ -631,4 +960,122 @@ mod tests {
|
|
|
second.owner = Principal(Hash::Sha2_256(PRINCIPAL2));
|
|
|
assert!(!first.contains(&second));
|
|
|
}
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn brotli_compress_decompress() {
|
|
|
+ const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
|
|
|
+ const SECT_CT: usize = 16;
|
|
|
+ let params = BrotliParams::new(SECT_SZ, 8, 20);
|
|
|
+ let mut memory = Cursor::new([0u8; SECT_SZ * SECT_CT]);
|
|
|
+ {
|
|
|
+ let write: CompressorWriter<_> = params.clone().compose(&mut memory)
|
|
|
+ .expect("compose for write failed");
|
|
|
+ write_fill(write, SECT_SZ, SECT_CT);
|
|
|
+ }
|
|
|
+ memory.seek(SeekFrom::Start(0)).expect("seek failed");
|
|
|
+ {
|
|
|
+ let read: Decompressor<_> = params.compose(&mut memory)
|
|
|
+ .expect("compose for read failed");
|
|
|
+ read_check(read, SECT_SZ, SECT_CT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn make_buf_sectored(
|
|
|
+ sect_sz: usize, sect_ct: usize
|
|
|
+ ) -> BufSectored<SectoredCursor<Vec<u8>>> {
|
|
|
+ BufSectored::new()
|
|
|
+ .compose(SectoredCursor::new(vec![0u8; sect_sz * sect_ct], sect_sz))
|
|
|
+ .expect("compose for sectored buffer failed")
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn buf_sectored_write_to_secret_stream() {
|
|
|
+ const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
|
|
|
+ const SECT_CT: usize = 16;
|
|
|
+ let mut sectored = make_buf_sectored(SECT_SZ, SECT_CT);
|
|
|
+ let sect_sz = sectored.sector_sz();
|
|
|
+ assert_eq!(0, sect_sz % 16);
|
|
|
+ let chunk_sz = sect_sz / 16;
|
|
|
+ let chunk_ct = SECT_CT * 16;
|
|
|
+ write_fill(&mut sectored, chunk_sz, chunk_ct);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn buf_sectored_write_read_sequential() {
|
|
|
+ const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
|
|
|
+ const SECT_CT: usize = 16;
|
|
|
+ let mut sectored = make_buf_sectored(SECT_SZ, SECT_CT);
|
|
|
+ let sect_sz = sectored.sector_sz();
|
|
|
+ assert_eq!(0, sect_sz % 16);
|
|
|
+ let chunk_sz = sect_sz / 16;
|
|
|
+ let chunk_ct = SECT_CT * 16 - 1;
|
|
|
+ write_fill(&mut sectored, chunk_sz, chunk_ct);
|
|
|
+ sectored.seek(SeekFrom::Start(0)).expect("seek failed");
|
|
|
+ let len: u64 = read_from(&mut sectored).expect("read_from failed");
|
|
|
+ assert_eq!(sectored.len, len as usize);
|
|
|
+ read_check(&mut sectored, chunk_sz, chunk_ct);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn buf_sectored_len_preserved() {
|
|
|
+ const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
|
|
|
+ const SECT_CT: usize = 16;
|
|
|
+ let mut sectored = make_buf_sectored(SECT_SZ, SECT_CT);
|
|
|
+ let expected = vec![42u8; 12];
|
|
|
+ // We need to ensure that writing expected will not fill up the buffer in sectored.
|
|
|
+ assert!(expected.len() < sectored.sector_sz() - std::mem::size_of::<usize>());
|
|
|
+
|
|
|
+ sectored.write_all(&expected).expect("write failed");
|
|
|
+ sectored.flush().expect("flush failed");
|
|
|
+ let inner = sectored.into_inner();
|
|
|
+ let mut sectored = BufSectored::new()
|
|
|
+ .compose(inner)
|
|
|
+ .expect("failed to compose sectored buffer");
|
|
|
+ let mut actual = vec![0u8; expected.len()];
|
|
|
+ sectored.fill_buf(actual.as_mut_slice()).expect("failed to fill actual");
|
|
|
+
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn buf_sectored_write_read_random() {
|
|
|
+ const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
|
|
|
+ const SECT_CT: usize = 16;
|
|
|
+ const CAP: usize = SECT_SZ * SECT_CT - std::mem::size_of::<usize>();
|
|
|
+ let mut rando = Randomizer::new([3u8; Randomizer::HASH.len()]);
|
|
|
+ let source = {
|
|
|
+ let mut expected = Vec::with_capacity(CAP);
|
|
|
+ expected.extend((&mut rando).take(expected.capacity()).map(|e| (e % 256) as u8));
|
|
|
+ expected
|
|
|
+ };
|
|
|
+ let rando2 = Randomizer::new([5u8; Randomizer::HASH.len()]);
|
|
|
+ let indices: Vec<(usize, usize)> = rando
|
|
|
+ .zip(rando2)
|
|
|
+ .take(2)
|
|
|
+ .map(|(mut first, mut second)| {
|
|
|
+ first %= source.len();
|
|
|
+ second &= source.len();
|
|
|
+ let low = first.min(second);
|
|
|
+ let high = first.max(second);
|
|
|
+ (low, high)
|
|
|
+ })
|
|
|
+ .collect();
|
|
|
+
|
|
|
+ let mut sectored = make_buf_sectored(SECT_SZ, SECT_CT);
|
|
|
+ sectored.write_all(&[0u8; CAP]).expect("failed to fill sectored");
|
|
|
+ sectored.flush().expect("flush failed");
|
|
|
+ for (low, high) in indices.iter() {
|
|
|
+ sectored.seek(SeekFrom::Start(*low as u64)).expect("seek failed");
|
|
|
+ sectored.write_all(&source[*low..*high]).expect("write failed");
|
|
|
+ sectored.flush().expect("flush failed");
|
|
|
+ }
|
|
|
+ let mut buf = vec![0u8; CAP];
|
|
|
+ for (_k, (low, high)) in indices.iter().enumerate() {
|
|
|
+ sectored.seek(SeekFrom::Start(*low as u64)).expect("seek failed");
|
|
|
+ let actual = &mut buf[*low..*high];
|
|
|
+ sectored.fill_buf(actual).expect("read failed");
|
|
|
+ let expected = &source[*low..*high];
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|