Include fuzz testing setup (#274)
This commit is contained in:
14
tests/h2-fuzz/Cargo.toml
Normal file
14
tests/h2-fuzz/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "h2-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
h2 = { path = "../.." }
|
||||
|
||||
env_logger = { version = "0.5.3", default-features = false }
|
||||
futures = "0.1.21"
|
||||
honggfuzz = "0.5"
|
||||
http = "0.1.3"
|
||||
tokio-io = "0.1.4"
|
||||
0
tests/h2-fuzz/README.md
Normal file
0
tests/h2-fuzz/README.md
Normal file
159
tests/h2-fuzz/src/main.rs
Normal file
159
tests/h2-fuzz/src/main.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate tokio_io;
|
||||
#[macro_use]
|
||||
extern crate honggfuzz;
|
||||
extern crate env_logger;
|
||||
extern crate h2;
|
||||
extern crate http;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::{executor, future, task};
|
||||
use http::{Method, Request};
|
||||
use std::cell::Cell;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::Arc;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
struct MockIo<'a> {
|
||||
input: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> MockIo<'a> {
|
||||
fn next_byte(&mut self) -> Option<u8> {
|
||||
if let Some(&c) = self.input.first() {
|
||||
self.input = &self.input[1..];
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
(self.next_byte().unwrap_or(0) as u32) << 8 | self.next_byte().unwrap_or(0) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Read for MockIo<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||
let mut len = self.next_u32() as usize;
|
||||
if self.input.is_empty() {
|
||||
Ok(0)
|
||||
} else if len == 0 {
|
||||
task::current().notify();
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
} else {
|
||||
if len > self.input.len() {
|
||||
len = self.input.len();
|
||||
}
|
||||
|
||||
if len > buf.len() {
|
||||
len = buf.len();
|
||||
}
|
||||
buf[0..len].copy_from_slice(&self.input[0..len]);
|
||||
self.input = &self.input[len..];
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsyncRead for MockIo<'a> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, _buf: &mut [u8]) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for MockIo<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||
let len = std::cmp::min(self.next_u32() as usize, buf.len());
|
||||
if len == 0 {
|
||||
if self.input.is_empty() {
|
||||
Err(io::ErrorKind::BrokenPipe.into())
|
||||
} else {
|
||||
task::current().notify();
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
} else {
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsyncWrite for MockIo<'a> {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
struct MockNotify {
|
||||
notified: Cell<bool>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for MockNotify {}
|
||||
|
||||
impl executor::Notify for MockNotify {
|
||||
fn notify(&self, _id: usize) {
|
||||
self.notified.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl MockNotify {
|
||||
fn take_notify(&self) -> bool {
|
||||
self.notified.replace(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn run(script: &[u8]) -> Result<(), h2::Error> {
|
||||
let notify = Arc::new(MockNotify {
|
||||
notified: Cell::new(false),
|
||||
});
|
||||
let notify_handle: executor::NotifyHandle = notify.clone().into();
|
||||
let io = MockIo { input: script };
|
||||
let (mut h2, mut connection) = h2::client::handshake(io).wait()?;
|
||||
let mut in_progress = None;
|
||||
let future = future::poll_fn(|| {
|
||||
if let Async::Ready(()) = connection.poll()? {
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
if in_progress.is_none() {
|
||||
try_ready!(h2.poll_ready());
|
||||
let request = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri("https://example.com/")
|
||||
.body(())
|
||||
.unwrap();
|
||||
let (resp, mut send) = h2.send_request(request, false)?;
|
||||
send.send_data(vec![0u8; 32769].into(), true).unwrap();
|
||||
drop(send);
|
||||
in_progress = Some(resp);
|
||||
}
|
||||
match in_progress.as_mut().unwrap().poll() {
|
||||
r @ Ok(Async::Ready(_)) | r @ Err(_) => {
|
||||
eprintln!("{:?}", r);
|
||||
in_progress = None;
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
}
|
||||
Ok::<_, h2::Error>(Async::NotReady)
|
||||
});
|
||||
let mut spawn = executor::spawn(future);
|
||||
loop {
|
||||
if let Async::Ready(()) = spawn.poll_future_notify(¬ify_handle, 0)? {
|
||||
return Ok(());
|
||||
}
|
||||
assert!(notify.take_notify());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
loop {
|
||||
fuzz!(|data: &[u8]| {
|
||||
eprintln!("{:?}", run(data));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,19 @@ crate because they transitively depend on the `unstable` feature flag via
|
||||
`h2-support`. Due to a cargo limitation, if these tests existed as part of the
|
||||
`h2` crate, it would require that `h2-support` be published to crates.io and
|
||||
force the `unstable` feature flag to always be on.
|
||||
|
||||
## Setup
|
||||
|
||||
Install honggfuzz for cargo:
|
||||
|
||||
```rust
|
||||
cargo install honggfuzz
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
From within this directory, run the following command:
|
||||
|
||||
```
|
||||
HFUZZ_RUN_ARGS="-t 1" cargo hfuzz run h2-fuzz
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user