use crate::Finalization::{self, NotRoot, Root};
use crate::{
ChunkGroupState, Hash, ParentNode, GROUP_SIZE, HASH_SIZE, HEADER_SIZE, MAX_DEPTH, PARENT_SIZE,
};
use arrayref::array_mut_ref;
use arrayvec::ArrayVec;
use std::cmp;
use std::fmt;
use std::io;
use std::io::prelude::*;
use std::io::SeekFrom;
pub fn encode(input: impl AsRef<[u8]>) -> (Vec<u8>, Hash) {
let bytes = input.as_ref();
let mut vec = Vec::with_capacity(encoded_size(bytes.len() as u64) as usize);
let mut encoder = Encoder::new(io::Cursor::new(&mut vec));
encoder.write_all(bytes).unwrap();
let hash = encoder.finalize().unwrap();
(vec, hash)
}
pub fn outboard(input: impl AsRef<[u8]>) -> (Vec<u8>, Hash) {
let bytes = input.as_ref();
let mut vec = Vec::with_capacity(outboard_size(bytes.len() as u64) as usize);
let mut encoder = Encoder::new_outboard(io::Cursor::new(&mut vec));
encoder.write_all(bytes).unwrap();
let hash = encoder.finalize().unwrap();
(vec, hash)
}
pub fn encoded_size(content_len: u64) -> u128 {
content_len as u128 + outboard_size(content_len)
}
pub fn outboard_size(content_len: u64) -> u128 {
outboard_subtree_size(content_len) + HEADER_SIZE as u128
}
pub(crate) fn encoded_subtree_size(content_len: u64) -> u128 {
content_len as u128 + outboard_subtree_size(content_len)
}
pub(crate) fn outboard_subtree_size(content_len: u64) -> u128 {
let num_parents = count_chunks(content_len) - 1;
num_parents as u128 * PARENT_SIZE as u128
}
pub(crate) fn count_chunks(content_len: u64) -> u64 {
let full_chunks: u64 = content_len / GROUP_SIZE as u64;
let has_partial_chunk: bool = (content_len % GROUP_SIZE as u64) != 0;
cmp::max(1, full_chunks + has_partial_chunk as u64)
}
pub(crate) fn chunk_size(chunk_index: u64, content_len: u64) -> usize {
let chunk_start = chunk_index * GROUP_SIZE as u64;
cmp::min(GROUP_SIZE, (content_len - chunk_start) as usize)
}
fn post_order_parent_nodes_nonfinal(chunk_index: u64) -> u8 {
(!chunk_index).trailing_zeros() as u8
}
fn post_order_parent_nodes_final(chunk_index: u64) -> u8 {
chunk_index.count_ones() as u8
}
pub(crate) fn pre_order_parent_nodes(chunk_index: u64, content_len: u64) -> u8 {
fn bit_length(x: u64) -> u32 {
64 - x.leading_zeros()
}
let total_chunks = count_chunks(content_len);
debug_assert!(chunk_index < total_chunks);
let total_chunks_after_this = total_chunks - chunk_index;
let bit_length_rule = bit_length(total_chunks_after_this - 1);
let trailing_zeros_rule = chunk_index.trailing_zeros();
cmp::min(bit_length_rule, trailing_zeros_rule) as u8
}
#[derive(Clone)]
struct FlipperState {
parents: ArrayVec<crate::ParentNode, MAX_DEPTH>,
content_len: u64,
last_chunk_moved: u64,
parents_needed: u8,
parents_available: u8,
}
impl FlipperState {
pub fn new(content_len: u64) -> Self {
let total_chunks = count_chunks(content_len);
Self {
parents: ArrayVec::new(),
content_len,
last_chunk_moved: count_chunks(content_len), parents_needed: post_order_parent_nodes_final(total_chunks - 1),
parents_available: 0,
}
}
pub fn next(&self) -> FlipperNext {
if self.parents_available > 0 {
FlipperNext::TakeParent
} else if self.parents_needed > 0 {
FlipperNext::FeedParent
} else if self.last_chunk_moved > 0 {
FlipperNext::Chunk(chunk_size(self.last_chunk_moved - 1, self.content_len))
} else {
FlipperNext::Done
}
}
pub fn chunk_moved(&mut self) {
debug_assert!(self.last_chunk_moved > 0);
debug_assert_eq!(self.parents_available, 0);
debug_assert_eq!(self.parents_needed, 0);
self.last_chunk_moved -= 1;
self.parents_available = pre_order_parent_nodes(self.last_chunk_moved, self.content_len);
if self.last_chunk_moved > 0 {
self.parents_needed = post_order_parent_nodes_nonfinal(self.last_chunk_moved - 1);
}
}
pub fn feed_parent(&mut self, parent: crate::ParentNode) {
debug_assert!(self.last_chunk_moved > 0);
debug_assert_eq!(self.parents_available, 0);
debug_assert!(self.parents_needed > 0);
self.parents_needed -= 1;
self.parents.push(parent);
}
pub fn take_parent(&mut self) -> crate::ParentNode {
debug_assert!(self.parents_available > 0);
self.parents_available -= 1;
self.parents.pop().expect("took too many parents")
}
}
impl fmt::Debug for FlipperState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "FlipperState {{ parents: {}, content_len: {}, last_chunk_moved: {}, parents_needed: {}, parents_available: {} }}",
self.parents.len(), self.content_len, self.last_chunk_moved, self.parents_needed, self.parents_available)
}
}
#[derive(Clone, Copy, Debug)]
enum FlipperNext {
FeedParent,
TakeParent,
Chunk(usize),
Done,
}
pub(crate) enum StateFinish {
Parent(ParentNode),
Root(Hash),
}
#[derive(Clone)]
pub(crate) struct State {
subtrees: ArrayVec<Hash, MAX_DEPTH>,
total_len: u64,
}
impl State {
pub fn new() -> Self {
Self {
subtrees: ArrayVec::new(),
total_len: 0,
}
}
pub fn count(&self) -> u64 {
self.total_len
}
fn merge_inner(&mut self, finalization: Finalization) -> ParentNode {
let right_child = self.subtrees.pop().unwrap();
let left_child = self.subtrees.pop().unwrap();
let parent_cv = blake3::guts::parent_cv(&left_child, &right_child, finalization.is_root());
self.subtrees.push(parent_cv);
let mut parent_node = [0; PARENT_SIZE];
parent_node[..HASH_SIZE].copy_from_slice(left_child.as_bytes());
parent_node[HASH_SIZE..].copy_from_slice(right_child.as_bytes());
parent_node
}
fn needs_merge(&self) -> bool {
let chunks = self.total_len / GROUP_SIZE as u64;
self.subtrees.len() > chunks.count_ones() as usize
}
pub fn push_subtree(&mut self, hash: &Hash, len: usize) {
debug_assert!(!self.needs_merge());
self.subtrees.push(*hash);
self.total_len = self
.total_len
.checked_add(len as u64)
.expect("addition overflowed");
}
pub fn merge_parent(&mut self) -> Option<ParentNode> {
if !self.needs_merge() {
return None;
}
Some(self.merge_inner(NotRoot))
}
pub fn merge_finalize(&mut self) -> StateFinish {
#[allow(clippy::comparison_chain)]
if self.subtrees.len() > 2 {
StateFinish::Parent(self.merge_inner(NotRoot))
} else if self.subtrees.len() == 2 {
StateFinish::Parent(self.merge_inner(Root))
} else {
StateFinish::Root(self.subtrees[0])
}
}
}
impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "State {{ ... }}")
}
}
#[derive(Clone, Debug)]
pub struct Encoder<T: Read + Write + Seek> {
inner: T,
chunk_state: ChunkGroupState,
tree_state: State,
outboard: bool,
finalized: bool,
}
impl<T: Read + Write + Seek> Encoder<T> {
pub fn new(inner: T) -> Self {
Self {
inner,
chunk_state: ChunkGroupState::new(0),
tree_state: State::new(),
outboard: false,
finalized: false,
}
}
pub fn new_outboard(inner: T) -> Self {
let mut encoder = Self::new(inner);
encoder.outboard = true;
encoder
}
pub fn finalize(&mut self) -> io::Result<Hash> {
assert!(!self.finalized, "already finalized");
self.finalized = true;
let total_len = self
.tree_state
.count()
.checked_add(self.chunk_state.len() as u64)
.expect("addition overflowed");
debug_assert!(self.chunk_state.len() > 0 || self.tree_state.count() == 0);
let last_chunk_is_root = self.tree_state.count() == 0;
let last_chunk_hash = self.chunk_state.finalize(last_chunk_is_root);
self.tree_state
.push_subtree(&last_chunk_hash, self.chunk_state.len());
let root_hash;
loop {
match self.tree_state.merge_finalize() {
StateFinish::Parent(parent) => self.inner.write_all(&parent)?,
StateFinish::Root(root) => {
root_hash = root;
break;
}
}
}
self.inner.write_all(&crate::encode_len(total_len))?;
self.flip_post_order_stream()?;
Ok(root_hash)
}
pub fn into_inner(self) -> T {
self.inner
}
fn flip_post_order_stream(&mut self) -> io::Result<()> {
let mut write_cursor = self.inner.seek(SeekFrom::End(0))?;
let mut read_cursor = write_cursor - HEADER_SIZE as u64;
let mut header = [0; HEADER_SIZE];
self.inner.seek(SeekFrom::Start(read_cursor))?;
self.inner.read_exact(&mut header)?;
let content_len = crate::decode_len(&header);
let mut flipper = FlipperState::new(content_len);
loop {
match flipper.next() {
FlipperNext::FeedParent => {
let mut parent = [0; PARENT_SIZE];
self.inner
.seek(SeekFrom::Start(read_cursor - PARENT_SIZE as u64))?;
self.inner.read_exact(&mut parent)?;
read_cursor -= PARENT_SIZE as u64;
flipper.feed_parent(parent);
}
FlipperNext::TakeParent => {
let parent = flipper.take_parent();
self.inner
.seek(SeekFrom::Start(write_cursor - PARENT_SIZE as u64))?;
self.inner.write_all(&parent)?;
write_cursor -= PARENT_SIZE as u64;
}
FlipperNext::Chunk(size) => {
if !self.outboard {
let mut chunk = [0; GROUP_SIZE];
self.inner
.seek(SeekFrom::Start(read_cursor - size as u64))?;
self.inner.read_exact(&mut chunk[..size])?;
read_cursor -= size as u64;
self.inner
.seek(SeekFrom::Start(write_cursor - size as u64))?;
self.inner.write_all(&chunk[..size])?;
write_cursor -= size as u64;
}
flipper.chunk_moved();
}
FlipperNext::Done => {
debug_assert_eq!(HEADER_SIZE as u64, write_cursor);
self.inner.rewind()?;
self.inner.write_all(&header)?;
return Ok(());
}
}
}
}
}
impl<T: Read + Write + Seek> Write for Encoder<T> {
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
assert!(!self.finalized, "already finalized");
if input.is_empty() {
return Ok(0);
}
if self.chunk_state.len() == GROUP_SIZE {
let chunk_hash = self.chunk_state.finalize(false);
self.tree_state.push_subtree(&chunk_hash, GROUP_SIZE);
let chunk_counter = self.tree_state.count() / GROUP_SIZE as u64;
self.chunk_state = ChunkGroupState::new(chunk_counter);
while let Some(parent) = self.tree_state.merge_parent() {
self.inner.write_all(&parent)?;
}
}
let want = GROUP_SIZE - self.chunk_state.len();
let take = cmp::min(want, input.len());
if !self.outboard {
self.inner.write_all(&input[..take])?;
}
self.chunk_state.update(&input[..take]);
Ok(take)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Clone, Debug)]
pub(crate) struct ParseState {
content_len: Option<u64>,
content_position: u64, encoding_position: u128,
stack_depth: u8,
upcoming_parents: u8,
final_chunk_validated: bool,
}
impl ParseState {
pub fn new() -> Self {
Self {
content_len: None,
content_position: 0,
encoding_position: 0,
stack_depth: 1,
upcoming_parents: 0, final_chunk_validated: false,
}
}
pub fn content_position(&self) -> u64 {
self.content_position
}
pub fn content_len(&self) -> Option<u64> {
self.content_len
}
fn at_root(&self) -> bool {
self.content_position < GROUP_SIZE as u64 && self.stack_depth == 1
}
fn at_eof(&self) -> bool {
if let Some(content_len) = self.content_len {
if self.content_position >= content_len {
if self.final_chunk_validated {
return true;
}
if content_len > 0 {
debug_assert!(self.content_position < content_len);
}
}
}
false
}
fn next_chunk_start(&self) -> u64 {
debug_assert!(!self.at_eof(), "not valid at EOF");
self.content_position - (self.content_position % GROUP_SIZE as u64)
}
fn next_chunk_index(&self) -> u64 {
debug_assert!(!self.at_eof(), "not valid at EOF");
self.content_position / GROUP_SIZE as u64
}
pub fn finalization(&self) -> Finalization {
if self.at_root() {
Root
} else {
NotRoot
}
}
fn reset_to_root(&mut self) {
let content_len = self.content_len.expect("reset before header");
self.content_position = 0;
self.encoding_position = HEADER_SIZE as u128;
self.stack_depth = 1;
self.upcoming_parents = pre_order_parent_nodes(0, content_len);
}
pub fn read_next(&self) -> NextRead {
let content_len = if let Some(len) = self.content_len {
len
} else {
return NextRead::Header;
};
if self.at_eof() {
NextRead::Done
} else if self.upcoming_parents > 0 {
NextRead::Parent
} else {
NextRead::Chunk {
size: chunk_size(self.next_chunk_index(), content_len),
finalization: self.finalization(),
skip: (self.content_position % GROUP_SIZE as u64) as usize,
index: self.content_position / GROUP_SIZE as u64,
}
}
}
pub fn seek_next(&self, seek_to: u64) -> SeekBookkeeping {
let mut new_state = self.clone();
let next_read = new_state.new_state_seek_next(seek_to);
SeekBookkeeping {
old_state: self.clone(),
new_state,
next_read,
}
}
fn new_state_seek_next(&mut self, mut seek_to: u64) -> NextRead {
let content_len = if let Some(len) = self.content_len {
len
} else {
return NextRead::Header;
};
let mut verifying_final_chunk = false;
if seek_to >= content_len {
if self.final_chunk_validated {
self.content_position = seek_to;
return NextRead::Done;
}
seek_to = content_len.saturating_sub(1);
verifying_final_chunk = true;
}
if self.at_eof() || seek_to < self.next_chunk_start() {
self.reset_to_root();
}
loop {
let distance = seek_to - self.next_chunk_start();
if distance < GROUP_SIZE as u64 {
if verifying_final_chunk {
let size = (content_len - self.next_chunk_start()) as usize;
return NextRead::Chunk {
size,
finalization: self.finalization(),
skip: size, index: self.content_position / GROUP_SIZE as u64,
};
} else {
self.content_position = seek_to;
return NextRead::Done;
}
}
let downshifted_distance = distance
.checked_shr(self.upcoming_parents as u32)
.unwrap_or(0);
if downshifted_distance < GROUP_SIZE as u64 {
debug_assert!(self.upcoming_parents > 0);
return NextRead::Parent;
}
let subtree_size = (GROUP_SIZE as u64) << self.upcoming_parents;
self.content_position = self.next_chunk_start() + subtree_size;
self.encoding_position += encoded_subtree_size(subtree_size);
self.stack_depth -= 1;
self.upcoming_parents = pre_order_parent_nodes(self.next_chunk_index(), content_len);
}
}
pub fn seek_bookkeeping_done(&mut self, bookkeeping: SeekBookkeeping) -> NextRead {
*self = bookkeeping.new_state;
bookkeeping.next_read
}
pub fn len_next(&self) -> LenNext {
if let Some(content_len) = self.content_len {
if self.final_chunk_validated {
LenNext::Len(content_len)
} else {
LenNext::Seek(self.seek_next(content_len))
}
} else {
LenNext::Seek(self.seek_next(0))
}
}
pub fn feed_header(&mut self, header: &[u8; HEADER_SIZE]) {
debug_assert!(self.content_len.is_none(), "second call to feed_header");
let content_len = crate::decode_len(header);
self.content_len = Some(content_len);
self.reset_to_root();
}
pub fn advance_parent(&mut self) {
debug_assert!(
self.upcoming_parents > 0,
"too many calls to advance_parent"
);
self.encoding_position += PARENT_SIZE as u128;
self.stack_depth += 1;
self.upcoming_parents -= 1;
}
pub fn advance_chunk(&mut self) {
debug_assert_eq!(
0, self.upcoming_parents,
"advance_chunk with non-zero upcoming parents"
);
let content_len = self.content_len.expect("advance_chunk before header");
let size = chunk_size(self.next_chunk_index(), content_len);
let skip = self.content_position % GROUP_SIZE as u64;
self.content_position += size as u64 - skip;
self.encoding_position += size as u128;
self.stack_depth -= 1;
if self.content_position >= content_len {
debug_assert_eq!(self.content_position, content_len, "position past EOF");
self.final_chunk_validated = true;
} else {
self.upcoming_parents = pre_order_parent_nodes(self.next_chunk_index(), content_len);
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum NextRead {
Header,
Parent,
Chunk {
size: usize,
finalization: Finalization,
skip: usize,
index: u64,
},
Done,
}
#[derive(Debug)]
pub(crate) struct SeekBookkeeping {
old_state: ParseState,
new_state: ParseState,
next_read: NextRead,
}
impl SeekBookkeeping {
pub fn reset_to_root(&self) -> bool {
self.new_state.at_root() && !self.old_state.at_root()
}
pub fn stack_depth(&self) -> usize {
self.new_state.stack_depth as usize
}
pub fn underlying_seek(&self) -> Option<u128> {
if self.old_state.encoding_position != self.new_state.encoding_position {
Some(self.new_state.encoding_position)
} else {
None
}
}
pub fn underlying_seek_outboard(&self) -> Option<(u64, u64)> {
if self.old_state.encoding_position != self.new_state.encoding_position {
let content = self.new_state.next_chunk_start();
let outboard = (self.new_state.encoding_position - content as u128) as u64;
Some((content, outboard))
} else {
None
}
}
}
#[derive(Debug)]
pub(crate) enum LenNext {
Seek(SeekBookkeeping),
Len(u64),
}
#[derive(Debug)]
pub struct SliceExtractor<T: Read + Seek, O: Read + Seek> {
input: T,
outboard: Option<O>,
slice_start: u64,
slice_len: u64,
slice_bytes_read: u64,
parser: ParseState,
buf: [u8; GROUP_SIZE],
buf_start: usize,
buf_end: usize,
seek_done: bool,
}
impl<T: Read + Seek> SliceExtractor<T, T> {
pub fn new(input: T, slice_start: u64, slice_len: u64) -> Self {
Self::new_inner(input, None, slice_start, slice_len)
}
}
impl<T: Read + Seek, O: Read + Seek> SliceExtractor<T, O> {
pub fn new_outboard(input: T, outboard: O, slice_start: u64, slice_len: u64) -> Self {
Self::new_inner(input, Some(outboard), slice_start, slice_len)
}
pub fn into_inner(self) -> (T, Option<O>) {
(self.input, self.outboard)
}
fn new_inner(input: T, outboard: Option<O>, slice_start: u64, slice_len: u64) -> Self {
Self {
input,
outboard,
slice_start,
slice_len: cmp::max(slice_len, 1),
slice_bytes_read: 0,
parser: ParseState::new(),
buf: [0; GROUP_SIZE],
buf_start: 0,
buf_end: 0,
seek_done: false,
}
}
fn buf_len(&self) -> usize {
self.buf_end - self.buf_start
}
fn read_header(&mut self) -> io::Result<()> {
let header = array_mut_ref!(self.buf, 0, HEADER_SIZE);
if let Some(outboard) = &mut self.outboard {
outboard.read_exact(header)?;
} else {
self.input.read_exact(header)?;
}
self.buf_start = 0;
self.buf_end = HEADER_SIZE;
self.parser.feed_header(header);
Ok(())
}
fn read_parent(&mut self) -> io::Result<()> {
let parent = array_mut_ref!(self.buf, 0, PARENT_SIZE);
if let Some(outboard) = &mut self.outboard {
outboard.read_exact(parent)?;
} else {
self.input.read_exact(parent)?;
}
self.buf_start = 0;
self.buf_end = PARENT_SIZE;
self.parser.advance_parent();
Ok(())
}
fn read_chunk(&mut self, size: usize, skip: usize) -> io::Result<()> {
debug_assert_eq!(0, self.buf_len(), "read_chunk with nonempty buffer");
let chunk = &mut self.buf[..size];
self.input.read_exact(chunk)?;
self.buf_start = 0;
self.buf_end = size;
self.slice_bytes_read += (size - skip) as u64;
self.parser.advance_chunk();
Ok(())
}
fn make_progress_and_buffer_output(&mut self) -> io::Result<()> {
if !self.seek_done {
let bookkeeping = self.parser.seek_next(self.slice_start);
if let Some(outboard) = &mut self.outboard {
if let Some((content_pos, outboard_pos)) = bookkeeping.underlying_seek_outboard() {
self.input.seek(SeekFrom::Start(content_pos))?;
outboard.seek(SeekFrom::Start(outboard_pos))?;
}
} else if let Some(encoding_position) = bookkeeping.underlying_seek() {
self.input
.seek(SeekFrom::Start(cast_offset(encoding_position)?))?;
}
let next_read = self.parser.seek_bookkeeping_done(bookkeeping);
match next_read {
NextRead::Header => return self.read_header(),
NextRead::Parent => return self.read_parent(),
NextRead::Chunk {
size,
finalization: _,
skip,
index: _,
} => return self.read_chunk(size, skip),
NextRead::Done => self.seek_done = true, }
}
if self.slice_bytes_read < self.slice_len {
match self.parser.read_next() {
NextRead::Header => unreachable!(),
NextRead::Parent => return self.read_parent(),
NextRead::Chunk {
size,
finalization: _,
skip,
index: _,
} => return self.read_chunk(size, skip),
NextRead::Done => {} }
}
Ok(())
}
}
impl<T: Read + Seek, O: Read + Seek> Read for SliceExtractor<T, O> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.buf_len() == 0 {
self.make_progress_and_buffer_output()?;
}
let n = cmp::min(buf.len(), self.buf_len());
buf[..n].copy_from_slice(&self.buf[self.buf_start..][..n]);
self.buf_start += n;
Ok(n)
}
}
pub(crate) fn cast_offset(offset: u128) -> io::Result<u64> {
if offset > u64::max_value() as u128 {
Err(io::Error::new(
io::ErrorKind::Other,
"seek offset overflowed u64",
))
} else {
Ok(offset as u64)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{decode::make_test_input, GROUP_LOG};
#[test]
fn test_encode() {
for &case in crate::test::TEST_CASES {
println!("case {case}");
let input = make_test_input(case);
let expected_hash = blake3::hash(&input);
let (encoded, hash) = encode(&input);
assert_eq!(expected_hash, hash);
assert_eq!(encoded.len() as u128, encoded_size(case as u64));
assert_eq!(encoded.len(), encoded.capacity());
assert_eq!(
encoded.len() as u128,
case as u128 + outboard_size(case as u64)
);
}
}
#[test]
fn test_outboard_encode() {
for &case in crate::test::TEST_CASES {
println!("case {case}");
let input = make_test_input(case);
let expected_hash = blake3::hash(&input);
let (outboard, hash) = outboard(&input);
assert_eq!(expected_hash, hash);
assert_eq!(outboard.len() as u128, outboard_size(case as u64));
assert_eq!(outboard.len(), outboard.capacity());
}
}
fn largest_power_of_two_leq(n: u64) -> u64 {
((n / 2) + 1).next_power_of_two()
}
fn make_pre_post_list(total_chunks: u64) -> Vec<(u8, u8)> {
fn recurse(start: u64, size: u64, answers: &mut Vec<(u8, u8)>) {
assert!(size > 0);
if size == 1 {
return;
}
answers[start as usize].0 += 1;
answers[(start + size - 1) as usize].1 += 1;
let split = largest_power_of_two_leq(size - 1);
recurse(start, split, answers);
recurse(start + split, size - split, answers);
}
let mut answers = vec![(0, 0); total_chunks as usize];
recurse(0, total_chunks, &mut answers);
answers
}
#[test]
fn test_make_pre_post_list() {
assert_eq!(make_pre_post_list(1), vec![(0, 0)]);
assert_eq!(make_pre_post_list(2), vec![(1, 0), (0, 1)]);
assert_eq!(make_pre_post_list(3), vec![(2, 0), (0, 1), (0, 1)]);
assert_eq!(make_pre_post_list(4), vec![(2, 0), (0, 1), (1, 0), (0, 2)]);
assert_eq!(
make_pre_post_list(5),
vec![(3, 0), (0, 1), (1, 0), (0, 2), (0, 1)]
);
}
#[test]
fn test_parent_nodes() {
for total_chunks in 1..100 {
let content_len = total_chunks * GROUP_SIZE as u64;
let pre_post_list = make_pre_post_list(total_chunks);
for chunk in 0..total_chunks {
let (expected_pre, expected_post) = pre_post_list[chunk as usize];
let pre = pre_order_parent_nodes(chunk, content_len);
let post = if chunk < total_chunks - 1 {
post_order_parent_nodes_nonfinal(chunk)
} else {
post_order_parent_nodes_final(chunk)
};
assert_eq!(
expected_pre, pre,
"incorrect pre-order parent nodes for chunk {chunk} of total {total_chunks}",
);
assert_eq!(
expected_post, post,
"incorrect post-order parent nodes for chunk {chunk} of total {total_chunks}",
);
}
}
}
fn drive_state(mut input: &[u8]) -> Hash {
let last_chunk_is_root = input.len() <= GROUP_SIZE;
let mut state = State::new();
let mut chunk_index = 0;
while input.len() > GROUP_SIZE {
let hash = ChunkGroupState::new(chunk_index)
.update(&input[..GROUP_SIZE])
.finalize(false);
chunk_index += 1;
state.push_subtree(&hash, GROUP_SIZE);
input = &input[GROUP_SIZE..];
while state.merge_parent().is_some() {}
}
let hash = ChunkGroupState::new(chunk_index)
.update(input)
.finalize(last_chunk_is_root);
state.push_subtree(&hash, input.len());
loop {
match state.merge_finalize() {
StateFinish::Parent(_) => {}
StateFinish::Root(hash) => return hash,
}
}
}
#[test]
fn test_state() {
let buf = vec![0x42; 65537 << GROUP_LOG];
for &case in crate::test::TEST_CASES {
dbg!(case);
let input = &buf[..case];
let expected = blake3::hash(input);
let found = drive_state(input);
assert_eq!(expected, found, "hashes don't match");
}
}
#[test]
#[should_panic]
fn test_finalize_twice_panics() {
let mut encoder = Encoder::new(io::Cursor::new(Vec::<u8>::new()));
let _ = encoder.finalize();
let _ = encoder.finalize();
}
#[test]
#[should_panic]
fn test_write_after_finalize_panics() {
let mut encoder = Encoder::new(io::Cursor::new(Vec::<u8>::new()));
let _ = encoder.finalize();
let _ = encoder.write(&[]);
}
#[test]
fn test_into_inner() {
let v = vec![1u8, 2, 3];
let encoder = Encoder::new(io::Cursor::new(v.clone()));
let extractor =
SliceExtractor::new(io::Cursor::new(encoder.into_inner().into_inner()), 0, 0);
let (r1, r2) = extractor.into_inner();
assert_eq!(r1.into_inner(), v);
assert_eq!(r2, None);
let outboard = SliceExtractor::new_outboard(
io::Cursor::new(v.clone()),
io::Cursor::new(v.clone()),
0,
0,
);
let (r3, r4) = outboard.into_inner();
assert_eq!(r3.into_inner(), v);
assert_eq!(r4.unwrap().into_inner(), v);
}
#[test]
fn test_empty_write_after_one_chunk() {
let input = &[0; GROUP_SIZE];
let mut output = Vec::new();
let mut encoder = Encoder::new(io::Cursor::new(&mut output));
encoder.write_all(input).unwrap();
encoder.write_all(&[]).unwrap();
let hash = encoder.finalize().unwrap();
assert_eq!((output, hash), encode(input));
assert_eq!(hash, blake3::hash(input));
}
}