Skip to content

Commit 874d72d

Browse files
authored
Improve packable implementations (#7)
* Add fuzzy tests
1 parent ad4499f commit 874d72d

File tree

17 files changed

+1080
-315
lines changed

17 files changed

+1080
-315
lines changed

fuzz/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
target
2+
corpus
3+
artifacts

fuzz/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "msgpacker-fuzz"
3+
version = "0.0.0"
4+
authors = ["Automatically generated"]
5+
publish = false
6+
edition = "2018"
7+
8+
[package.metadata]
9+
cargo-fuzz = true
10+
11+
[dependencies]
12+
arbitrary = { version = "1.1", features = ["derive"] }
13+
libfuzzer-sys = "0.4"
14+
msgpacker = { path = "../msgpacker" }
15+
16+
[workspace]
17+
members = ["."]
18+
19+
[[bin]]
20+
name = "messages"
21+
path = "fuzz_targets/messages.rs"
22+
test = false
23+
doc = false

fuzz/fuzz_targets/messages.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![no_main]
2+
use libfuzzer_sys::fuzz_target;
3+
4+
use arbitrary::Arbitrary;
5+
use msgpacker::prelude::*;
6+
7+
#[derive(Arbitrary, MsgPacker, Debug, Clone, PartialEq, Eq)]
8+
pub struct AllMessages {
9+
pub uint64: Option<u64>,
10+
pub int64: Option<i64>,
11+
pub b: Option<bool>,
12+
pub f_32: Option<f32>,
13+
pub f_64: Option<f64>,
14+
pub string: Option<String>,
15+
pub bin: Option<Vec<u8>>,
16+
}
17+
18+
fuzz_target!(|m: AllMessages| {
19+
let mut m = m;
20+
21+
let buf: Vec<u8> = vec![];
22+
let mut packer = CursorPacker::new(buf);
23+
24+
packer.pack(m.clone()).expect("failed to pack message");
25+
packer.set_position(0);
26+
27+
let mut p: AllMessages = packer.unpack().expect("failed to unpack message");
28+
29+
// NaN equality doesn't hold
30+
if m.f_32.filter(|f| f.is_nan()).is_some() {
31+
m.f_32.take();
32+
}
33+
if m.f_64.filter(|f| f.is_nan()).is_some() {
34+
m.f_64.take();
35+
}
36+
if p.f_32.filter(|f| f.is_nan()).is_some() {
37+
p.f_32.take();
38+
}
39+
if p.f_64.filter(|f| f.is_nan()).is_some() {
40+
p.f_64.take();
41+
}
42+
43+
assert_eq!(m, p);
44+
});

msgpacker-derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "msgpacker-derive"
3-
version = "0.1.0"
3+
version = "0.1.2"
44
authors = ["Victor Lopez <victor@codx.io>"]
55
categories = ["compression", "encoding", "parser-implementations"]
66
edition = "2021"

msgpacker-derive/src/lib.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn msg_packer(input: TokenStream) -> TokenStream {
1616
let data = input.data;
1717

1818
let mut values: Punctuated<FieldValue, Token![,]> = Punctuated::new();
19-
let block: Block = match data {
19+
let (block, block_size): (Block, Block) = match data {
2020
Data::Struct(syn::DataStruct {
2121
struct_token: _,
2222
fields: Fields::Named(f),
@@ -25,30 +25,54 @@ pub fn msg_packer(input: TokenStream) -> TokenStream {
2525
.named
2626
.into_pairs()
2727
.map(|p| p.into_value())
28-
.fold(syn::parse_str("{}").unwrap(), |mut block, field| {
28+
.fold((syn::parse_str("{}").unwrap(), syn::parse_str("{}").unwrap()), |(mut block, mut block_size), field| {
2929
let ident = field.ident.as_ref().cloned().unwrap();
3030
let ty = field.ty;
3131

32+
block_size.stmts.push(parse_quote! {
33+
n += <#ty as msgpacker::prelude::SizeableMessage>::packed_len(&self.#ident);
34+
});
35+
3236
block.stmts.push(parse_quote! {
3337
n += <#ty as msgpacker::prelude::Packable>::pack(&self.#ident, packer.by_ref())?;
3438
});
3539

3640
let fv = FieldValue {
3741
attrs: vec![],
38-
member: Member::Named(ident.clone()),
42+
member: Member::Named(ident),
3943
colon_token: Some(<Token![:]>::default()),
4044
expr: parse_quote! {
4145
<#ty as msgpacker::prelude::Unpackable>::unpack(unpacker.by_ref())?
4246
},
4347
};
4448
values.push(fv);
4549

46-
block
50+
(block, block_size)
4751
}),
4852
_ => todo!(),
4953
};
5054

5155
let expanded = quote! {
56+
impl msgpacker::prelude::SizeableMessage for #name {
57+
fn packed_len(&self) -> usize {
58+
let mut n = 0;
59+
60+
#block_size
61+
62+
n
63+
}
64+
}
65+
66+
impl<'a> msgpacker::prelude::SizeableMessage for &'a #name {
67+
fn packed_len(&self) -> usize {
68+
let mut n = 0;
69+
70+
#block_size
71+
72+
n
73+
}
74+
}
75+
5276
impl msgpacker::prelude::Packable for #name {
5377
fn pack<W>(&self, mut packer: W) -> std::io::Result<usize>
5478
where
@@ -62,6 +86,19 @@ pub fn msg_packer(input: TokenStream) -> TokenStream {
6286
}
6387
}
6488

89+
impl<'a> msgpacker::prelude::Packable for &'a #name {
90+
fn pack<W>(&self, mut packer: W) -> std::io::Result<usize>
91+
where
92+
W: std::io::Write
93+
{
94+
let mut n = 0;
95+
96+
#block
97+
98+
Ok(n)
99+
}
100+
}
101+
65102
impl msgpacker::prelude::Unpackable for #name {
66103
fn unpack<R>(mut unpacker: R) -> std::io::Result<Self>
67104
where

msgpacker/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "msgpacker"
3-
version = "0.2.1"
3+
version = "0.2.2"
44
authors = ["Victor Lopez <victor@codx.io>"]
55
categories = ["compression", "encoding", "parser-implementations"]
66
edition = "2021"

msgpacker/src/buffer.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,18 @@ where
5959
pub const unsafe fn cast_fixed_array<const M: usize, const N: usize>(array: [u8; M]) -> [u8; N] {
6060
*mem::transmute::<&[u8; M], &[u8; N]>(&array)
6161
}
62+
63+
#[test]
64+
fn take_buf_wont_panic_for_small_buf() {
65+
use std::io::Read;
66+
67+
const LEN: usize = 10;
68+
69+
let mut cursor = io::Cursor::new([0u8; LEN]);
70+
71+
let err = unsafe { take_buf(cursor.by_ref(), LEN + 1) }
72+
.err()
73+
.expect("buffer isn't big enough");
74+
75+
assert_eq!(io::ErrorKind::UnexpectedEof, err.kind());
76+
}

msgpacker/src/consts.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Packable representation for variant `None` of [`Option`]
2+
pub const OPTION_NONE: isize = 0x00;
3+
4+
/// Packable representation for variant `Some` of [`Option`]
5+
pub const OPTION_SOME: isize = 0x01;
6+
7+
/// Packable representation for variant `Ok` of [`Result`]
8+
pub const RESULT_OK: isize = 0x00;
9+
10+
/// Packable representation for variant `Err` of [`Result`]
11+
pub const RESULT_ERR: isize = 0x01;

msgpacker/src/float.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::buffer;
22
use crate::format::MessageFormat;
3+
use crate::packer::SizeableMessage;
34

45
use std::io;
56

@@ -57,6 +58,18 @@ impl Float {
5758
}
5859
}
5960

61+
debug_assert_eq!(n, self.packed_len());
62+
6063
Ok(n)
6164
}
6265
}
66+
67+
impl SizeableMessage for Float {
68+
fn packed_len(&self) -> usize {
69+
match self {
70+
Self::F32(_) => 5,
71+
72+
Self::F64(_) => 9,
73+
}
74+
}
75+
}

msgpacker/src/format.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::packer::SizeableMessage;
2+
13
/// [specs](https://github.com/msgpack/msgpack/blob/master/spec.md#formats)
24
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35
pub enum MessageFormat {
@@ -77,6 +79,12 @@ pub enum MessageFormat {
7779
NegativeFixInt(i8),
7880
}
7981

82+
impl SizeableMessage for MessageFormat {
83+
fn packed_len(&self) -> usize {
84+
1
85+
}
86+
}
87+
8088
impl From<u8> for MessageFormat {
8189
fn from(b: u8) -> Self {
8290
use MessageFormat::*;

0 commit comments

Comments
 (0)