blob: da2c30308184772baa72e201fad1488dab81a1c4 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
error::Error,
extent::Extent,
format::ExtentClusterHeader,
options::ExtractorOptions,
properties::{DataKind, ExtentKind},
utils::{RangeOps, ReadAndSeek},
},
std::io::{Read, Write},
};
/// Returns `true` if the extent's data needs to be dumped.
fn should_dump_data(extent: &Extent, dump_pii: bool) -> bool {
let properties = extent.properties();
// For ExtentKind::Data, we do not need to dump data when DataKind is
// either Skipped or it is Zeroes.
// We dump Pii only when we forced to dump Pii or it was `Modified` by the
// storage software to obfuscate the Pii data.
match properties.extent_kind {
ExtentKind::Data => match properties.data_kind {
DataKind::Modified => true,
DataKind::Unmodified => true,
_ => false,
},
ExtentKind::Pii => match properties.data_kind {
DataKind::Modified => true,
DataKind::Unmodified => dump_pii,
_ => false,
},
_ => false,
}
}
/// This is in-memory representation of a collection of Extents.
///
/// We hold extents in memory, (coalescing, splitting, merging if needed), till
/// write() is issued.
/// The cluster of extents lives in image file in contiguous location.
#[derive(Debug)]
pub struct ExtentCluster {
pub(crate) extent_tree: std::collections::BTreeMap<u64, Extent>,
options: ExtractorOptions,
}
impl ExtentCluster {
pub fn new(options: &ExtractorOptions) -> Self {
Self { extent_tree: Default::default(), options: options.clone() }
}
// insert_extent's logic is as follows.
// * create a list of all affected extents by this insertion.
// * for each extent in affected extents, split/merge with current extents.
// * replace affected and current extent with the new split/merged extents list.
fn insert_extent(&mut self, current_extent: Extent) -> Result<(), Error> {
let mut affected_extents = vec![];
// Get all extents that may be affected by this extent insertion.
for ext in self.extent_tree.iter_mut() {
if !(current_extent.overlaps(&ext.1) || current_extent.is_adjacent(&ext.1)) {
continue;
}
affected_extents.push(ext.1.clone());
}
// Remove all the affected extents.
for ext in &affected_extents {
self.extent_tree.remove(&ext.start());
}
// Perform split/merge of current extent with one affected extent at a time.
// Note:
// 1. This is performed on extents in the ascending order of extent.start.
// 2. The existing extents are assumed to be non-overlapping with all other
// existing extents.
// 3. Order of insertion changes in intermediate state of the cluster.
let mut new_extents = vec![];
let mut remaining = Some(current_extent.clone());
for (i, ext) in affected_extents.iter().enumerate() {
assert!(current_extent.overlaps(&ext) || current_extent.is_adjacent(&ext));
// If remaining extent is completely consumed then iterate over the rest of affected
// extents and just add them.
// Ex. say we have already inserted extents [2, 5), [5, 8), and [8, 12) all having
// different priorities. Now the current extent is [5, 8) with lower
// priority than all the the extents then we may end up consuming current
// entirely before we parse all of affected_extents.
match remaining {
None => new_extents.push(ext.clone()),
Some(remaining_extent) => {
remaining = remaining_extent.split_or_merge(&ext, &mut new_extents);
assert!(remaining.is_some() || (i >= affected_extents.len() - 2));
}
}
}
// Insert all the split/merged extent into the extent tree.
for ext in new_extents {
self.extent_tree.insert(ext.start(), ext.clone());
}
// It may happen that the current extent maybe unaffected or partially affected.
// If so insert it into the tree.
match remaining {
Some(e) => {
self.extent_tree.insert(e.start(), e);
}
_ => {}
}
Ok(())
}
/// Adds an extent to extent cluster.
pub fn add_extent(&mut self, extent: &Extent) -> Result<(), Error> {
if !extent.storage_range().is_valid() {
return Err(Error::InvalidRange);
}
self.insert_extent(extent.clone())
}
/// Returns number of extent in extent cluster.
pub fn extent_count(&self) -> u64 {
self.extent_tree.len() as u64
}
/// Returns number of data bytes that will be written
/// in this cluster.
fn data_size(&self) -> u64 {
let mut size: u64 = 0;
for (_, extent) in &self.extent_tree {
if !should_dump_data(&extent, self.options.force_dump_pii) {
continue;
}
size = size + extent.storage_range().length();
}
size
}
/// Writes ExtentCluster metadata to out_stream. crc32 to is crc of metadata and any
/// padding required.
fn write_metadata(&self, out_stream: &mut dyn Write) -> Result<u64, Error> {
let mut size =
ExtentClusterHeader::serialize_to(self.extent_count() as u64, 0, out_stream)?;
for (_, extent) in &self.extent_tree {
size = size + extent.write(out_stream)?;
}
let zero_fill_len = (((size + self.options.alignment - 1) / self.options.alignment)
* self.options.alignment)
- size;
let zeroes: Vec<u8> = vec![0; zero_fill_len as usize];
out_stream.write_all(&zeroes).map_err(move |_| Error::WriteFailed)?;
Ok(size + zero_fill_len)
}
/// Write all the data in extents to the out_stream.
///
/// The extent's data is read from the in_stream.
fn write_data(
&self,
in_stream: &mut dyn ReadAndSeek,
out_stream: &mut dyn Write,
) -> Result<u64, Error> {
let mut size = 0;
for (_, extent) in &self.extent_tree {
// No need to dump the data. Only ExtentInfo will be written to the image file.
if !should_dump_data(&extent, self.options.force_dump_pii) {
continue;
}
let bytes_to_read = extent.storage_range().length();
let mut read_buffer: Vec<u8> = vec![0; bytes_to_read as usize];
let offset = extent.storage_range().start;
// Seek to the storage location.
in_stream.seek(std::io::SeekFrom::Start(offset)).map_err(move |_| Error::SeekFailed)?;
let mut r = in_stream.take(bytes_to_read);
// Read from the storage.
r.read(&mut read_buffer[..bytes_to_read as usize]).map_err(move |_| {
eprintln!(
"Failed to read {} bytes from {} offset for {:?}",
bytes_to_read, offset, extent
);
Error::ReadFailed
})?;
// Write to the image file
out_stream.write_all(&read_buffer).map_err(move |_| Error::WriteFailed)?;
size = size + bytes_to_read;
}
Ok(size)
}
/// Iterate over the extent_tree and check that they are in ascending order
/// of start offset and they do not overlap.
fn check_extent_tree(&self) {
let mut prev_or: Option<Extent> = None;
for (_, extent) in &self.extent_tree {
if prev_or.is_some() {
let prev = prev_or.unwrap();
assert!(extent.start() >= prev.end());
}
assert!(extent.storage_range().is_valid());
prev_or = Some(extent.clone());
}
}
/// Writes extent cluster to the image file.
///
/// # Arguments
///
/// `out_stream` : Points to the image file stream.
/// `in_stream` : Stream from where extent data will be read from.
/// `current_offset`: Starting position of the cluster within the image file.
/// `last_cluster` : True if this is the last cluster within the extracted image file.
/// Multi-cluster image files are not yet supported.
///
/// This includes computing checksum of the cluster header and all the extent infos,
/// then writing cluster header, extent info and extent data to the image file.
pub fn write(
&mut self,
mut in_stream: &mut dyn ReadAndSeek,
_current_offset: u64,
last_cluster: bool,
mut out_stream: &mut dyn Write,
) -> Result<u64, Error> {
if !last_cluster {
todo!("Support for more than one cluster is not implemented.")
}
self.check_extent_tree();
// Update all the extent location w.r.t. starting of the extent cluster.
let _data_size = self.data_size();
let mut size = self.write_metadata(out_stream)?;
size = size + self.write_data(&mut in_stream, &mut out_stream)?;
Ok(size)
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::{
extent_cluster::ExtentCluster,
properties::{DataKind, ExtentKind, ExtentProperties},
},
std::io::Cursor,
std::ops::Range,
};
static INSERTED_ADDRESS_START: u64 = 20;
static INSERTED_ADDRESS_END: u64 = 30;
static LOW_PRIORITY_PROPERTIES: ExtentProperties =
ExtentProperties { extent_kind: ExtentKind::Data, data_kind: DataKind::Zeroes };
static HIGH_PRIORITY_PROPERTIES: ExtentProperties =
ExtentProperties { extent_kind: ExtentKind::Pii, data_kind: DataKind::Zeroes };
static INSERTED_PROPERTIES: ExtentProperties =
ExtentProperties { extent_kind: ExtentKind::Data, data_kind: DataKind::Unmodified };
static OVERLAPPING_RIGHT_ADDRESS: Range<u64> =
INSERTED_ADDRESS_END - 5..INSERTED_ADDRESS_END + 6;
fn inserted_range() -> Range<u64> {
INSERTED_ADDRESS_START..INSERTED_ADDRESS_END
}
fn inserted_extent() -> Extent {
Extent::new(
inserted_range(),
INSERTED_PROPERTIES,
// data: Some(vec![1; inserted_range().length() as usize]),
None,
)
.unwrap()
}
// Non-overlapping extent to the right of inserted_extent().
fn right_extent() -> Extent {
Extent::new(
INSERTED_ADDRESS_END + 5..INSERTED_ADDRESS_END + 15,
INSERTED_PROPERTIES,
// data: Some(vec![1; inserted_range().length() as usize]),
None,
)
.unwrap()
}
fn setup_extent_cluster() -> (ExtentCluster, Vec<Extent>) {
let mut cluster = ExtentCluster::new(&Default::default());
match cluster.add_extent(&inserted_extent()) {
Err(why) => println!("why: {:?}", why),
Ok(_) => {}
}
return (cluster, vec![inserted_extent().clone()]);
}
fn verify(file: &str, line: u32, cluster: &ExtentCluster, extents: &Vec<Extent>) {
if cluster.extent_tree.len() != extents.len() {
println!("{}:{} Expected: {:?}\nFound: {:?}", file, line, extents, cluster.extent_tree);
}
assert_eq!(cluster.extent_tree.len(), extents.len());
for (_, ext) in cluster.extent_tree.iter() {
let mut found = false;
for inserted in extents.iter() {
if inserted == ext {
found = true;
break;
}
}
if !found {
println!("Could not find {:?}", *ext);
}
assert!(found);
}
}
#[test]
fn test_setup() {
let (cluster, expected_extents) = setup_extent_cluster();
assert!(INSERTED_PROPERTIES > LOW_PRIORITY_PROPERTIES);
assert!(INSERTED_PROPERTIES < HIGH_PRIORITY_PROPERTIES);
assert!(LOW_PRIORITY_PROPERTIES < HIGH_PRIORITY_PROPERTIES);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_non_overlapping() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let e = Extent::new(40..50, INSERTED_PROPERTIES, None).unwrap();
cluster.add_extent(&e).unwrap();
expected_extents.push(e);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_is_adjacent_not_mergable() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let e = Extent::new(
INSERTED_ADDRESS_END..INSERTED_ADDRESS_END + 5,
LOW_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&e).unwrap();
expected_extents.push(e);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_is_adjacent() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let e =
Extent::new(INSERTED_ADDRESS_END..INSERTED_ADDRESS_END + 5, INSERTED_PROPERTIES, None)
.unwrap();
cluster.add_extent(&e).unwrap();
expected_extents.clear();
expected_extents.push(
Extent::new(
INSERTED_ADDRESS_START..INSERTED_ADDRESS_END + 5,
INSERTED_PROPERTIES,
None,
)
.unwrap(),
);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_adjacent_in_the_middle() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
verify(file!(), line!(), &cluster, &expected_extents);
let e =
Extent::new(INSERTED_ADDRESS_END..INSERTED_ADDRESS_END + 5, INSERTED_PROPERTIES, None)
.unwrap();
cluster.add_extent(&e).unwrap();
expected_extents.clear();
expected_extents.push(
Extent::new(
INSERTED_ADDRESS_START..INSERTED_ADDRESS_END + 15,
INSERTED_PROPERTIES,
None,
)
.unwrap(),
);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_adjacent_in_the_middle_not_mergable() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
verify(file!(), line!(), &cluster, &expected_extents);
let m = Extent::new(
INSERTED_ADDRESS_END..INSERTED_ADDRESS_END + 5,
HIGH_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&m).unwrap();
expected_extents.push(m);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_adjacent_on_both_sides_overlapping_in_middle() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
// Adjacent to left
let mut m = Extent::new(
INSERTED_ADDRESS_START - 10..INSERTED_ADDRESS_START,
LOW_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&m).unwrap();
expected_extents.push(m.clone());
m.set_start(INSERTED_ADDRESS_END);
m.set_end(INSERTED_ADDRESS_END + 10);
cluster.add_extent(&m).unwrap();
expected_extents.push(m.clone());
verify(file!(), line!(), &cluster, &expected_extents);
let m = Extent::new(
INSERTED_ADDRESS_START..INSERTED_ADDRESS_END,
LOW_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&m).unwrap();
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster
.add_extent(
&Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), INSERTED_PROPERTIES, None).unwrap(),
)
.unwrap();
expected_extents[0].set_end(OVERLAPPING_RIGHT_ADDRESS.end);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_low_priority() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let mut e =
Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), LOW_PRIORITY_PROPERTIES, None).unwrap();
cluster.add_extent(&e).unwrap();
e.set_start(INSERTED_ADDRESS_END);
expected_extents.push(e);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_high_priority() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster
.add_extent(
&Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), HIGH_PRIORITY_PROPERTIES, None)
.unwrap(),
)
.unwrap();
expected_extents[0].set_end(OVERLAPPING_RIGHT_ADDRESS.start);
expected_extents.push(
Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), HIGH_PRIORITY_PROPERTIES, None).unwrap(),
);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_in_middlex() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
verify(file!(), line!(), &cluster, &expected_extents);
let middle_extent =
Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), INSERTED_PROPERTIES, None).unwrap();
cluster.add_extent(&middle_extent).unwrap();
expected_extents.clear();
expected_extents.push(
Extent::new(INSERTED_ADDRESS_START..right_extent().end(), INSERTED_PROPERTIES, None)
.unwrap(),
);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_in_middle_low_priority() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
verify(file!(), line!(), &cluster, &expected_extents);
cluster
.add_extent(
&Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), LOW_PRIORITY_PROPERTIES, None)
.unwrap(),
)
.unwrap();
expected_extents.push(
Extent::new(
INSERTED_ADDRESS_END..INSERTED_ADDRESS_END + 5,
LOW_PRIORITY_PROPERTIES,
None,
)
.unwrap(),
);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_in_middle_high_priority() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
verify(file!(), line!(), &cluster, &expected_extents);
let middle_extent =
Extent::new(OVERLAPPING_RIGHT_ADDRESS.clone(), HIGH_PRIORITY_PROPERTIES, None).unwrap();
cluster.add_extent(&middle_extent).unwrap();
expected_extents[0].set_end(OVERLAPPING_RIGHT_ADDRESS.start);
expected_extents[1].set_start(OVERLAPPING_RIGHT_ADDRESS.end);
expected_extents.push(middle_extent);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_multiple() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
let extreme_right_extent = Extent::new(
right_extent().end() + 10..right_extent().end() + 20,
INSERTED_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&extreme_right_extent).unwrap();
expected_extents.push(extreme_right_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
let overlapping_extent = Extent::new(
INSERTED_ADDRESS_START - 5..extreme_right_extent.end() + 10,
INSERTED_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&overlapping_extent).unwrap();
expected_extents.clear();
expected_extents.push(overlapping_extent);
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_overlapping_multiple_not_mergeable() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
cluster.add_extent(&right_extent()).unwrap();
expected_extents.push(right_extent().clone());
let extreme_right_extent = Extent::new(
right_extent().end() + 10..right_extent().end() + 20,
INSERTED_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&extreme_right_extent).unwrap();
expected_extents.push(extreme_right_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
let overlapping_extent = Extent::new(
INSERTED_ADDRESS_START - 5..extreme_right_extent.end() + 10,
LOW_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&overlapping_extent).unwrap();
// The overlapping_extent gets divided into multiple extents.
let mut split_extent = overlapping_extent.clone();
split_extent.set_end(inserted_extent().start());
expected_extents.push(split_extent.clone());
split_extent.set_start(inserted_extent().end());
split_extent.set_end(right_extent().start());
expected_extents.push(split_extent.clone());
split_extent.set_start(right_extent().end());
split_extent.set_end(extreme_right_extent.start());
expected_extents.push(split_extent.clone());
split_extent.set_start(extreme_right_extent.end());
split_extent.set_end(overlapping_extent.end());
expected_extents.push(split_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_splits_existing_extent() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let small_extent = Extent::new(
inserted_extent().start() + 3..inserted_extent().end() - 3,
HIGH_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&small_extent).unwrap();
expected_extents.push(expected_extents[0].clone());
expected_extents[0].set_end(small_extent.start());
expected_extents[1].set_start(small_extent.end());
expected_extents.push(small_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_splits_existing_extent_at_start() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let small_extent = Extent::new(
inserted_extent().start()..inserted_extent().end() - 3,
HIGH_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&small_extent).unwrap();
expected_extents[0].set_start(small_extent.end());
expected_extents.push(small_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
}
#[test]
fn test_add_splits_existing_extent_at_end() {
let (mut cluster, mut expected_extents) = setup_extent_cluster();
let small_extent = Extent::new(
inserted_extent().start() + 3..inserted_extent().end(),
HIGH_PRIORITY_PROPERTIES,
None,
)
.unwrap();
cluster.add_extent(&small_extent).unwrap();
expected_extents[0].set_end(small_extent.start());
expected_extents.push(small_extent.clone());
verify(file!(), line!(), &cluster, &expected_extents);
}
fn dumpable_data_properties() -> ExtentProperties {
ExtentProperties { extent_kind: ExtentKind::Data, data_kind: DataKind::Modified }
}
fn skippable_data_properties() -> ExtentProperties {
ExtentProperties { extent_kind: ExtentKind::Unmmapped, data_kind: DataKind::Skipped }
}
fn pii_data_properties() -> ExtentProperties {
ExtentProperties { extent_kind: ExtentKind::Pii, data_kind: DataKind::Unmodified }
}
fn setup_cluster_write_test(
dump_pii: bool,
) -> (ExtentCluster, ExtractorOptions, Vec<u8>, Cursor<Vec<u8>>) {
let mut options: ExtractorOptions = Default::default();
options.force_dump_pii = dump_pii;
let cluster = ExtentCluster::new(&options);
let out_buffer: Vec<u8> = vec![];
let in_buffer = Cursor::new(vec![0; 2 * 1024 * 1024]);
(cluster, options, out_buffer, in_buffer)
}
#[test]
fn test_extent_cluster_write() {
let (mut cluster, options, mut out_buffer, mut in_buffer) = setup_cluster_write_test(false);
let size = cluster.write(&mut in_buffer, 0, true, &mut out_buffer).unwrap();
assert!(size > 0);
assert_eq!(size % options.alignment, 0);
assert_eq!(out_buffer.len() as u64, size);
}
#[test]
fn test_extent_cluster_write_no_data() {
let (mut cluster, options, mut out_buffer, mut in_buffer) = setup_cluster_write_test(false);
let size = cluster.write(&mut in_buffer, 0, true, &mut out_buffer).unwrap();
assert!(size > 0);
assert_eq!(size % options.alignment, 0);
assert_eq!(out_buffer.len() as u64, size);
let properties = skippable_data_properties();
let mut extent = Extent::new(0..1, properties, None).unwrap();
// Write as many extents as it takes to fill one alignment unit.
let extent_count = options.alignment / extent.serialized_size() as u64;
for i in 0..extent_count {
let start = i * 2 * options.alignment;
extent.set_start(start);
extent.set_end(start + options.alignment);
cluster.add_extent(&extent).unwrap();
}
let mut new_buffer: Vec<u8> = vec![];
let new_size = cluster.write(&mut in_buffer, 0, true, &mut new_buffer).unwrap();
assert!(new_size > size);
assert_eq!(new_size % options.alignment, 0);
assert_eq!(new_buffer.len() as u64, new_size);
}
#[test]
fn test_extent_cluster_write_with_data() {
let (mut cluster, options, mut out_buffer, mut in_buffer) = setup_cluster_write_test(false);
let no_data_extent =
Extent::new(8192..(2 * 8192), skippable_data_properties(), None).unwrap();
let data_extent1 =
Extent::new((2 * 8192)..(3 * 8192), dumpable_data_properties(), None).unwrap();
let data_extent2 =
Extent::new((5 * 8192)..(6 * 8192), dumpable_data_properties(), None).unwrap();
let pii_extent = Extent::new(8 * 8192..(9 * 8192), pii_data_properties(), None).unwrap();
cluster.add_extent(&no_data_extent).unwrap();
cluster.add_extent(&data_extent1).unwrap();
cluster.add_extent(&data_extent2).unwrap();
cluster.add_extent(&pii_extent).unwrap();
let new_size = cluster.write(&mut in_buffer, 0, true, &mut out_buffer).unwrap();
assert_eq!(new_size % options.alignment, 0);
assert_eq!(out_buffer.len() as u64, new_size);
// We end up writing three aligned segments.
// - one cluster header and extents
// - two data blocks
assert_eq!(new_size, options.alignment * 3)
}
#[test]
fn test_extent_cluster_write_with_pii() {
let (mut cluster, options, mut out_buffer, mut in_buffer) = setup_cluster_write_test(true);
// Add skippable data followed by pii at same range.
let no_data_extent =
Extent::new(8192..(15 * 8192), skippable_data_properties(), None).unwrap();
let pii_extent = Extent::new(8192..(5 * 8192), pii_data_properties(), None).unwrap();
cluster.add_extent(&no_data_extent).unwrap();
cluster.add_extent(&pii_extent).unwrap();
let new_size = cluster.write(&mut in_buffer, 0, true, &mut out_buffer).unwrap();
assert_eq!(new_size % options.alignment, 0);
assert_eq!(out_buffer.len() as u64, new_size);
// We end up writing three aligned segments.
// - one cluster header and extents
// - four for pii blocks
assert_eq!(new_size, options.alignment * 5)
}
fn verify_should_dump_data(ekind: ExtentKind, dkind: DataKind, dump_pii: bool, dump: bool) {
let properties = ExtentProperties { extent_kind: ekind, data_kind: dkind };
let extent = Extent::new(4..10, properties, None).unwrap();
assert_eq!(
should_dump_data(&extent, dump_pii),
dump,
"{:?} {} {}",
properties,
dump_pii,
dump
);
}
#[test]
fn test_should_dump_data() {
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Skipped, true, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Zeroes, true, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Unmodified, true, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Modified, true, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Skipped, false, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Zeroes, false, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Unmodified, false, false);
verify_should_dump_data(ExtentKind::Unmmapped, DataKind::Modified, false, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Skipped, true, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Zeroes, true, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Unmodified, true, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Modified, true, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Skipped, false, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Zeroes, false, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Unmodified, false, false);
verify_should_dump_data(ExtentKind::Unused, DataKind::Modified, false, false);
verify_should_dump_data(ExtentKind::Data, DataKind::Skipped, true, false);
verify_should_dump_data(ExtentKind::Data, DataKind::Zeroes, true, false);
verify_should_dump_data(ExtentKind::Data, DataKind::Unmodified, true, true);
verify_should_dump_data(ExtentKind::Data, DataKind::Modified, true, true);
verify_should_dump_data(ExtentKind::Data, DataKind::Skipped, false, false);
verify_should_dump_data(ExtentKind::Data, DataKind::Zeroes, false, false);
verify_should_dump_data(ExtentKind::Data, DataKind::Unmodified, false, true);
verify_should_dump_data(ExtentKind::Data, DataKind::Modified, false, true);
verify_should_dump_data(ExtentKind::Pii, DataKind::Skipped, true, false);
verify_should_dump_data(ExtentKind::Pii, DataKind::Zeroes, true, false);
verify_should_dump_data(ExtentKind::Pii, DataKind::Unmodified, true, true);
verify_should_dump_data(ExtentKind::Pii, DataKind::Modified, true, true);
verify_should_dump_data(ExtentKind::Pii, DataKind::Skipped, false, false);
verify_should_dump_data(ExtentKind::Pii, DataKind::Zeroes, false, false);
verify_should_dump_data(ExtentKind::Pii, DataKind::Unmodified, false, false);
verify_should_dump_data(ExtentKind::Pii, DataKind::Modified, false, true);
}
}