Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add versionize for VecDequeue/HashMap/HashSet. #25

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v0.1.5

- Add versionize proc macro support for HashMap, HashSet and VecDequeue.

# v0.1.4

- Removed Versionize proc macro support for unions. Serializing unions can lead to undefined behaviour especially when no
Expand Down
2 changes: 1 addition & 1 deletion coverage_config_x86_64.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"coverage_score": 93.2, "exclude_path": "", "crate_features": ""}
{"coverage_score": 93.3, "exclude_path": "", "crate_features": ""}
18 changes: 17 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! The Versionize proc macro supports structures and enums.
//! Supported primitives: u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, char, f32, f64,
//! String, Vec<T>, Arrays up to 32 elements, Box<T>, Wrapping<T>, Option<T>, FamStructWrapper<T>,
//! and (T, U).
//! VecDequeue<T>, HashMap<K, V>, HashSet<T> and (T, U).
//!
//! Known issues and limitations:
//! - Union serialization is not supported via the `Versionize` proc macro.
Expand Down Expand Up @@ -54,6 +54,10 @@ pub enum VersionizeError {
StringLength(usize),
/// Vector length exceeded.
VecLength(usize),
/// HashMap length exceeded.
HashMapLength(usize),
/// HashSet length exceeded.
HashSetLength(usize),
}

impl std::fmt::Display for VersionizeError {
Expand All @@ -77,6 +81,18 @@ impl std::fmt::Display for VersionizeError {
bad_len,
primitives::MAX_VEC_SIZE
),
HashMapLength(bad_len) => write!(
f,
"HashMap of length {} exceeded maximum size of {} bytes",
bad_len,
primitives::MAX_HASH_MAP_SIZE
),
HashSetLength(bad_len) => write!(
f,
"HashSet of length {} exceeded maximum size of {} bytes",
bad_len,
primitives::MAX_HASH_SET_SIZE
),
}
}
}
Expand Down
287 changes: 287 additions & 0 deletions src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
//! Serialization support for primitive data types.
#![allow(clippy::float_cmp)]

use std::collections::{HashMap, HashSet, VecDeque};
use std::hash::Hash;

use self::super::{VersionMap, Versionize, VersionizeError, VersionizeResult};
use vmm_sys_util::fam::{FamStruct, FamStructWrapper};

/// Maximum string len in bytes (16KB).
pub const MAX_STRING_LEN: usize = 16384;
/// Maximum vec size in bytes (10MB).
pub const MAX_VEC_SIZE: usize = 10_485_760;
/// Maximum hashmap len in bytes (20MB).
pub const MAX_HASH_MAP_SIZE: usize = 20_971_520;
/// Maximum hashset len in bytes (10MB).
pub const MAX_HASH_SET_SIZE: usize = 10_485_760;

/// Implements the Versionize trait for primitive types that also implement
/// serde's Serialize/Deserialize: use serde_bincode as a backend for
Expand Down Expand Up @@ -333,6 +340,168 @@ where
}
}

impl<T> Versionize for VecDeque<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost like implementation for Vec. Can we do this with a macro to avoid duplicating code ?

where
T: Versionize,
{
#[inline]
fn serialize<W: std::io::Write>(
&self,
mut writer: &mut W,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<()> {
if self.len() > MAX_VEC_SIZE / std::mem::size_of::<T>() {
return Err(VersionizeError::VecLength(self.len()));
}

// Serialize in the same fashion as bincode:
// Write len.
bincode::serialize_into(&mut writer, &self.len())
.map_err(|ref err| VersionizeError::Serialize(format!("{:?}", err)))?;
// Walk the vec and write each element.
for element in self {
element.serialize(writer, version_map, app_version)?;
}
Ok(())
}

#[inline]
fn deserialize<R: std::io::Read>(
mut reader: &mut R,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<Self> {
let mut v = VecDeque::new();
let len: usize = bincode::deserialize_from(&mut reader)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;

if len > MAX_VEC_SIZE / std::mem::size_of::<T>() {
return Err(VersionizeError::VecLength(len));
}

for _ in 0..len {
let element: T = T::deserialize(reader, version_map, app_version)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;
v.push_back(element);
}
Ok(v)
}

// Not used yet.
fn version() -> u16 {
1
}
}

impl<K, V> Versionize for HashMap<K, V>
where
K: Versionize + Eq + Hash + Clone,
V: Versionize + Clone,
{
#[inline]
fn serialize<W: std::io::Write>(
&self,
mut writer: &mut W,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<()> {
if self.len() > MAX_HASH_MAP_SIZE / (std::mem::size_of::<K>() + std::mem::size_of::<V>()) {
return Err(VersionizeError::HashMapLength(self.len()));
}

// Write len
bincode::serialize_into(&mut writer, &self.len())
.map_err(|ref err| VersionizeError::Serialize(format!("{:?}", err)))?;
// Walk the hash map and write each element.
for (k, v) in self.iter() {
(k.clone(), v.clone()).serialize(writer, version_map, app_version)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid the temporary clones here ?

}
Ok(())
}

#[inline]
fn deserialize<R: std::io::Read>(
mut reader: &mut R,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<Self> {
let mut map = HashMap::new();
let len: usize = bincode::deserialize_from(&mut reader)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;

if len > MAX_HASH_MAP_SIZE / (std::mem::size_of::<K>() + std::mem::size_of::<V>()) {
return Err(VersionizeError::HashMapLength(len));
}

for _ in 0..len {
let element: (K, V) = <(K, V)>::deserialize(reader, version_map, app_version)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;
map.insert(element.0, element.1);
}
Ok(map)
}

// Not used yet.
fn version() -> u16 {
1
}
}

impl<T> Versionize for HashSet<T>
where
T: Versionize + Hash + Eq,
{
#[inline]
fn serialize<W: std::io::Write>(
&self,
mut writer: &mut W,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<()> {
if self.len() > MAX_HASH_SET_SIZE / std::mem::size_of::<T>() {
return Err(VersionizeError::HashSetLength(self.len()));
}

// Serialize in the same fashion as bincode:
// Write len.
bincode::serialize_into(&mut writer, &self.len())
.map_err(|ref err| VersionizeError::Serialize(format!("{:?}", err)))?;
// Walk the vec and write each element.
for element in self {
element.serialize(writer, version_map, app_version)?;
}
Ok(())
}

#[inline]
fn deserialize<R: std::io::Read>(
mut reader: &mut R,
version_map: &VersionMap,
app_version: u16,
) -> VersionizeResult<Self> {
let mut set = HashSet::new();
let len: usize = bincode::deserialize_from(&mut reader)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;

if len > MAX_HASH_SET_SIZE / std::mem::size_of::<T>() {
return Err(VersionizeError::HashSetLength(len));
}

for _ in 0..len {
let element: T = T::deserialize(reader, version_map, app_version)
.map_err(|ref err| VersionizeError::Deserialize(format!("{:?}", err)))?;
set.insert(element);
}
Ok(set)
}

// Not used yet.
fn version() -> u16 {
1
}
}

// Implement versioning for FAM structures by using the FamStructWrapper interface.
impl<T: Default + FamStruct + Versionize> Versionize for FamStructWrapper<T>
where
Expand Down Expand Up @@ -697,6 +866,68 @@ mod tests {
assert_eq!(store, restore);
}

#[test]
fn test_ser_de_vec_deque() {
let vm = VersionMap::new();
let mut snapshot_mem = vec![0u8; 64];

let mut store = VecDeque::new();
store.push_back("test 1".to_owned());
store.push_back("test 2".to_owned());
store.push_back("test 3".to_owned());

store
.serialize(&mut snapshot_mem.as_mut_slice(), &vm, 1)
.unwrap();
let restore =
<Vec<String> as Versionize>::deserialize(&mut snapshot_mem.as_slice(), &vm, 1).unwrap();

assert_eq!(store, restore);
}

#[test]
fn test_ser_de_hash_map() {
let vm = VersionMap::new();
let mut snapshot_mem = vec![0u8; 128];

let mut store = HashMap::new();
store.insert(1, "test 1".to_owned());
store.insert(2, "test 2".to_owned());
store.insert(3, "test 3".to_owned());

store
.serialize(&mut snapshot_mem.as_mut_slice(), &vm, 1)
.unwrap();
let restore = <HashMap<usize, String> as Versionize>::deserialize(
&mut snapshot_mem.as_slice(),
&vm,
1,
)
.unwrap();

assert_eq!(store, restore);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add negative tests.

}

#[test]
fn test_ser_de_hash_set() {
let vm = VersionMap::new();
let mut snapshot_mem = vec![0u8; 64];

let mut store = HashSet::new();
store.insert("test 1".to_owned());
store.insert("test 2".to_owned());
store.insert("test 3".to_owned());

store
.serialize(&mut snapshot_mem.as_mut_slice(), &vm, 1)
.unwrap();
let restore =
<HashSet<String> as Versionize>::deserialize(&mut snapshot_mem.as_slice(), &vm, 1)
.unwrap();

assert_eq!(store, restore);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

}

#[test]
fn test_ser_de_option() {
let vm = VersionMap::new();
Expand Down Expand Up @@ -790,6 +1021,62 @@ mod tests {
);
}

#[test]
fn test_vec_deque_limit() {
// We need extra 8 bytes for vector len.
let mut snapshot_mem = vec![0u8; MAX_VEC_SIZE + 8];
let err = VecDeque::from(vec![123u8; MAX_VEC_SIZE + 1])
.serialize(&mut snapshot_mem.as_mut_slice(), &VersionMap::new(), 1)
.unwrap_err();
assert_eq!(err, VersionizeError::VecLength(MAX_VEC_SIZE + 1));
assert_eq!(
format!("{}", err),
"Vec of length 10485761 exceeded maximum size of 10485760 bytes"
);
}

#[test]
fn test_hash_map_limit() {
// We need extra 8 bytes for vector len.
let mut snapshot_mem = vec![0u8; MAX_HASH_MAP_SIZE / 16 + 1];
let mut err = HashMap::with_capacity(MAX_HASH_MAP_SIZE / 16 + 1);
for i in 0..(MAX_HASH_MAP_SIZE / 16 + 1) {
err.insert(i, i);
}
let err = err
.serialize(&mut snapshot_mem.as_mut_slice(), &VersionMap::new(), 1)
.unwrap_err();
assert_eq!(
err,
VersionizeError::HashMapLength(MAX_HASH_MAP_SIZE / 16 + 1)
);
assert_eq!(
format!("{}", err),
"HashMap of length 1310721 exceeded maximum size of 20971520 bytes"
);
}

#[test]
fn test_hash_set_limit() {
// We need extra 8 bytes for vector len.
let mut snapshot_mem = vec![0u8; MAX_HASH_SET_SIZE / 8 + 1];
let mut err = HashSet::with_capacity(MAX_HASH_SET_SIZE / 8 + 1);
for i in 0..(MAX_HASH_SET_SIZE / 8 + 1) {
err.insert(i);
}
let err = err
.serialize(&mut snapshot_mem.as_mut_slice(), &VersionMap::new(), 1)
.unwrap_err();
assert_eq!(
err,
VersionizeError::HashSetLength(MAX_HASH_SET_SIZE / 8 + 1)
);
assert_eq!(
format!("{}", err),
"HashSet of length 1310721 exceeded maximum size of 10485760 bytes"
);
}

#[test]
fn test_string_limit() {
// We need extra 8 bytes for string len.
Expand Down