THRIFT-5855: Add rust fuzzers
Add fuzzers for Rust support, to improve the reliability/robustness of the implementation
diff --git a/FUZZING.md b/FUZZING.md
index a81dfe4..456c0b2 100644
--- a/FUZZING.md
+++ b/FUZZING.md
@@ -21,10 +21,10 @@
- Java/JVM (and other JVM languages)
- JavaScript
- Python
+- Rust
We are working on adding fuzzers for the following languages:
-- Rust
- Swift
- netstd
diff --git a/configure.ac b/configure.ac
index 4f55aad..a93f701 100644
--- a/configure.ac
+++ b/configure.ac
@@ -807,6 +807,7 @@
lib/rb/Makefile
lib/rs/Makefile
lib/rs/test/Makefile
+ lib/rs/test/fuzz/Makefile
lib/rs/test_recursive/Makefile
lib/rs/test_recursive/src/Makefile
lib/rs/test_recursive/src/maintenance/Makefile
diff --git a/lib/rs/src/protocol/compact.rs b/lib/rs/src/protocol/compact.rs
index d47df5d..319f28a 100644
--- a/lib/rs/src/protocol/compact.rs
+++ b/lib/rs/src/protocol/compact.rs
@@ -483,7 +483,7 @@
) -> crate::Result<()> {
let elem_identifier = collection_type_to_u8(element_type);
if element_count <= 14 {
- let header = (element_count as u8) << 4 | elem_identifier;
+ let header = ((element_count as u8) << 4) | elem_identifier;
self.write_byte(header)
} else {
let header = 0xF0 | elem_identifier;
diff --git a/lib/rs/test/Makefile.am b/lib/rs/test/Makefile.am
index 73fe156..f3c1c78 100644
--- a/lib/rs/test/Makefile.am
+++ b/lib/rs/test/Makefile.am
@@ -17,6 +17,12 @@
# under the License.
#
+SUBDIRS = .
+
+if WITH_TESTS
+SUBDIRS += fuzz
+endif
+
THRIFT = $(top_builddir)/compiler/cpp/thrift
stubs: thrifts/Base_One.thrift thrifts/Base_Two.thrift thrifts/Midlayer.thrift thrifts/Ultimate.thrift $(top_builddir)/test/Recursive.thrift $(THRIFT)
diff --git a/lib/rs/test/fuzz/.gitignore b/lib/rs/test/fuzz/.gitignore
new file mode 100644
index 0000000..80ed2f8
--- /dev/null
+++ b/lib/rs/test/fuzz/.gitignore
@@ -0,0 +1,6 @@
+target
+corpus
+artifacts
+coverage
+lib/fuzz_test.rs
+Cargo.lock
\ No newline at end of file
diff --git a/lib/rs/test/fuzz/Cargo.toml b/lib/rs/test/fuzz/Cargo.toml
new file mode 100644
index 0000000..38272b4
--- /dev/null
+++ b/lib/rs/test/fuzz/Cargo.toml
@@ -0,0 +1,86 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+# //
+# http://www.apache.org/licenses/LICENSE-2.0
+# //
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[package]
+name = "thrift-fuzz"
+version = "0.0.0"
+publish = false
+edition = "2021"
+
+[package.metadata]
+cargo-fuzz = true
+
+[lib]
+path = "lib/mod.rs"
+
+[dependencies]
+libfuzzer-sys = "0.4"
+uuid = { version = "1", features = ["arbitrary"] }
+arbitrary = { version = "1", features = ["derive"] }
+ordered-float = { version = "4.6.0", features = ["arbitrary"] }
+clap = { version = "4.5", features = ["derive"] }
+rand = "0.9"
+
+[dependencies.thrift]
+path = "../../../../lib/rs"
+
+[[bin]]
+name = "corpus_generator"
+path = "bin/corpus_generator.rs"
+
+[[bin]]
+name = "parse_compact"
+path = "fuzz_targets/parse_compact.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "parse_binary"
+path = "fuzz_targets/parse_binary.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "roundtrip_binary"
+path = "fuzz_targets/roundtrip_binary.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "roundtrip_compact"
+path = "fuzz_targets/roundtrip_compact.rs"
+test = false
+doc = false
+bench = false
+
+# TODO (THRIFT-5891): Enable these once we fix round-trip correctness.
+# [[bin]]
+# name = "structured_roundtrip_compact"
+# path = "fuzz_targets/structured_roundtrip_compact.rs"
+# test = false
+# doc = false
+# bench = false
+
+# [[bin]]
+# name = "structured_roundtrip_binary"
+# path = "fuzz_targets/structured_roundtrip_binary.rs"
+# test = false
+# doc = false
+# bench = false
\ No newline at end of file
diff --git a/lib/rs/test/fuzz/Makefile.am b/lib/rs/test/fuzz/Makefile.am
new file mode 100644
index 0000000..4f49120
--- /dev/null
+++ b/lib/rs/test/fuzz/Makefile.am
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+# These sed commands are needed to work around the fact that the generator doesn't
+# support the arbitrary crate yet.
+stubs: $(top_builddir)/test/FuzzTest.thrift $(THRIFT)
+ $(THRIFT) -out lib/ --gen rs $(top_builddir)/test/FuzzTest.thrift
+ sed -i 's/thrift::OrderedFloat/ordered_float::OrderedFloat/g' lib/fuzz_test.rs
+ sed -i 's/derive(/derive(arbitrary::Arbitrary, /g' lib/fuzz_test.rs
+
+check: stubs
+ $(CARGO) fmt --all -- --check
+ $(CARGO) clippy --all -- -D warnings
+ $(CARGO) build
+
+clean-local:
+ $(CARGO) clean
+ -$(RM) Cargo.lock
+ -$(RM) -r target/
+ -$(RM) -r corpus/
+ -$(RM) -r artifacts/
+ -$(RM) -r coverage/
+ -$(RM) lib/fuzz_test.rs
+
+distdir:
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+EXTRA_DIST = \
+ Cargo.toml \
+ lib/mod.rs \
+ fuzz_targets/parse_compact.rs \
+ fuzz_targets/parse_binary.rs \
+ fuzz_targets/roundtrip_binary.rs \
+ fuzz_targets/roundtrip_compact.rs \
+ fuzz_targets/structured_roundtrip_compact.rs \
+ fuzz_targets/structured_roundtrip_binary.rs \
+ bin/corpus_generator.rs
diff --git a/lib/rs/test/fuzz/README.md b/lib/rs/test/fuzz/README.md
new file mode 100644
index 0000000..3a8cd8f
--- /dev/null
+++ b/lib/rs/test/fuzz/README.md
@@ -0,0 +1,20 @@
+# Rust fuzzing README
+
+To build the fuzz targets, simply run `make check` in this directory
+
+These are standard cargo fuzz targets, so you can use the [standard cargo fuzz commands](https://rust-fuzz.github.io/book/introduction.html) to build and run them. You can also build with cargo fuzz directly after the initial build with `make check`, e.g. run `cargo fuzz run $fuzzer_name`
+
+We currently have six fuzz targets:
+
+* parse_compact -- fuzzes the deserialization of the Compact protocol
+* parse_binary -- fuzzes the deserialization of the Binary protocol
+* roundtrip_compact -- fuzzes the roundtrip of the Compact protocol (i.e. serializes and then deserializes and compares the result to the original)
+* roundtrip_binary -- fuzzes the roundtrip of the Binary protocol
+* structured_roundtrip_compact -- roundtrip, but starts from a valid compact thrift structure
+* structured_roundtrip_binary -- roundtrip, but starts from a valid binary thrift structure
+
+Some of the roundtrip fuzzers are structure aware, i.e. they generate mostly valid thrift structures, so we can also test serialization in addition to deserialization. We do have non structure aware roundtrip fuzzers as well, to match what's present in other languages (and also handle some corner cases).
+
+We also have a corpus generator script that can be used to generate a corpus of fuzz inputs. It can be run with `cargo run --bin corpus_generator -- --output-dir <output_dir> --protocol <binary|compact> --buffer-size <buffer_size> --random-size <random_size>`.
+
+This is useful for generating corpora for the parsing fuzzers, and can be used across all languages (for cases where the other languages don't have good native structure aware fuzzing support).
\ No newline at end of file
diff --git a/lib/rs/test/fuzz/bin/corpus_generator.rs b/lib/rs/test/fuzz/bin/corpus_generator.rs
new file mode 100644
index 0000000..ed104f8
--- /dev/null
+++ b/lib/rs/test/fuzz/bin/corpus_generator.rs
@@ -0,0 +1,167 @@
+use arbitrary::{Arbitrary, Unstructured};
+use clap::Parser;
+use std::fs::{self, File};
+use std::io::Read;
+use std::path::Path;
+use thrift::protocol::{
+ TBinaryOutputProtocol, TCompactOutputProtocol, TOutputProtocol, TSerializable,
+};
+use thrift::transport::TBufferChannel;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+struct Args {
+ /// Input directory containing raw binary files (mutually exclusive with --generate)
+ #[arg(short, long, group = "input")]
+ input_dir: Option<String>,
+
+ /// Number of random files to generate (mutually exclusive with --input-dir)
+ #[arg(short, long, group = "input")]
+ generate: Option<usize>,
+
+ /// Output directory for serialized FuzzTest files
+ #[arg(short, long)]
+ output_dir: String,
+
+ /// Protocol to use for serialization (binary or compact)
+ #[arg(short, long)]
+ protocol: String,
+
+ /// Buffer size for serialization (default: 65536)
+ #[arg(short, long, default_value = "65536")]
+ buffer_size: usize,
+
+ /// Size of random byte vector for generation (default: 16384)
+ #[arg(long, default_value = "16384")]
+ random_size: usize,
+}
+
+fn serialize_fuzz_test(
+ fuzz_test: &FuzzTest,
+ protocol: &str,
+ buffer_size: usize,
+) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
+ let mut mem = TBufferChannel::with_capacity(buffer_size, buffer_size);
+ match protocol {
+ "binary" => {
+ let mut out_protocol = TBinaryOutputProtocol::new(&mut mem, true);
+ fuzz_test.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+ }
+ "compact" => {
+ let mut out_protocol = TCompactOutputProtocol::new(&mut mem);
+ fuzz_test.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+ }
+ _ => return Err("Invalid protocol specified. Use 'binary' or 'compact'".into()),
+ }
+ Ok(mem.write_bytes().to_vec())
+}
+
+fn convert_corpus_file(
+ input_path: &Path,
+ output_dir: &Path,
+ protocol: &str,
+ buffer_size: usize,
+) -> Result<(), Box<dyn std::error::Error>> {
+ // Read input file
+ let mut input_file = File::open(input_path)?;
+ let mut input_data = Vec::new();
+ input_file.read_to_end(&mut input_data)?;
+
+ // Create Unstructured instance for arbitrary
+ let mut unstructured = Unstructured::new(&input_data);
+
+ // Generate FuzzTest instance
+ if let Ok(fuzz_test) = FuzzTest::arbitrary(&mut unstructured) {
+ // Create output file path
+ let file_name = input_path
+ .file_name()
+ .ok_or("Invalid input filename")?
+ .to_str()
+ .ok_or("Invalid UTF-8 in filename")?;
+ let output_path = output_dir.join(file_name);
+
+ // Serialize and write to file
+ let serialized_data = serialize_fuzz_test(&fuzz_test, protocol, buffer_size)?;
+ fs::write(output_path, serialized_data)?;
+ }
+
+ Ok(())
+}
+
+fn generate_random_file(
+ output_dir: &Path,
+ index: usize,
+ protocol: &str,
+ buffer_size: usize,
+ random_size: usize,
+) -> Result<(), Box<dyn std::error::Error>> {
+ // Generate random bytes
+ let random_bytes: Vec<u8> = (0..random_size).map(|_| rand::random::<u8>()).collect();
+
+ // Create Unstructured instance for arbitrary
+ let mut unstructured = Unstructured::new(&random_bytes);
+
+ // Generate FuzzTest instance
+ if let Ok(fuzz_test) = FuzzTest::arbitrary(&mut unstructured) {
+ // Create output file path with index
+ let output_path = output_dir.join(format!("generated_{index}.bin"));
+
+ // Serialize and write to file
+ let serialized_data = serialize_fuzz_test(&fuzz_test, protocol, buffer_size)?;
+ fs::write(output_path, serialized_data)?;
+ }
+
+ Ok(())
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let args = Args::parse();
+
+ // Validate protocol
+ if args.protocol != "binary" && args.protocol != "compact" {
+ return Err("Invalid protocol specified. Use 'binary' or 'compact'".into());
+ }
+
+ // Create output directory if it doesn't exist
+ fs::create_dir_all(&args.output_dir)?;
+
+ match (args.input_dir, args.generate) {
+ (Some(input_dir), None) => {
+ // Process each file in the input directory
+ for entry in fs::read_dir(&input_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_file() {
+ if let Err(e) = convert_corpus_file(
+ &path,
+ Path::new(&args.output_dir),
+ &args.protocol,
+ args.buffer_size,
+ ) {
+ eprintln!("Error processing file {path:?}: {e}");
+ }
+ }
+ }
+ }
+ (None, Some(num_files)) => {
+ // Generate random files
+ for i in 0..num_files {
+ if let Err(e) = generate_random_file(
+ Path::new(&args.output_dir),
+ i,
+ &args.protocol,
+ args.buffer_size,
+ args.random_size,
+ ) {
+ eprintln!("Error generating file {i}: {e}");
+ }
+ }
+ }
+ _ => return Err("Must specify either --input-dir or --generate".into()),
+ }
+
+ Ok(())
+}
diff --git a/lib/rs/test/fuzz/fuzz_targets/parse_binary.rs b/lib/rs/test/fuzz/fuzz_targets/parse_binary.rs
new file mode 100644
index 0000000..9e1444a
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/parse_binary.rs
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::TBinaryInputProtocol;
+use thrift::protocol::TSerializable;
+use thrift::transport::TBufferChannel;
+use thrift::TConfiguration;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+fn run(data: &[u8]) -> thrift::Result<FuzzTest> {
+ // Add some limits to optimize fuzzing
+ let config = TConfiguration::builder()
+ .max_message_size(Some(data.len()))
+ .max_frame_size(Some(data.len()))
+ .max_container_size(Some(1024))
+ .max_string_size(Some(data.len()))
+ .build()
+ .unwrap();
+ // Create a buffer channel with enough capacity for our data
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ mem.set_readable_bytes(data);
+ let mut protocol = TBinaryInputProtocol::with_config(mem, true /* strict */, config);
+ FuzzTest::read_from_in_protocol(&mut protocol)
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+ let _ = run(data);
+});
diff --git a/lib/rs/test/fuzz/fuzz_targets/parse_compact.rs b/lib/rs/test/fuzz/fuzz_targets/parse_compact.rs
new file mode 100644
index 0000000..91cd532
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/parse_compact.rs
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::TCompactInputProtocol;
+use thrift::protocol::TSerializable;
+use thrift::transport::TBufferChannel;
+use thrift::TConfiguration;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+fn run(data: &[u8]) -> thrift::Result<FuzzTest> {
+ // Add some limits to optimize fuzzing
+ let config = TConfiguration::builder()
+ .max_message_size(Some(data.len()))
+ .max_frame_size(Some(data.len()))
+ .max_container_size(Some(1024))
+ .max_string_size(Some(data.len()))
+ .build()
+ .unwrap();
+ // Create a buffer channel with enough capacity for our data
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ mem.set_readable_bytes(data);
+ let mut protocol = TCompactInputProtocol::with_config(mem, config);
+ FuzzTest::read_from_in_protocol(&mut protocol)
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+ let _ = run(data);
+});
diff --git a/lib/rs/test/fuzz/fuzz_targets/roundtrip_binary.rs b/lib/rs/test/fuzz/fuzz_targets/roundtrip_binary.rs
new file mode 100644
index 0000000..41b302b
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/roundtrip_binary.rs
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::{
+ TBinaryInputProtocol, TBinaryOutputProtocol, TOutputProtocol, TSerializable,
+};
+use thrift::transport::TBufferChannel;
+use thrift::TConfiguration;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+fn run(data: &[u8]) -> thrift::Result<()> {
+ // Add some limits to optimize fuzzing
+ let config = TConfiguration::builder()
+ .max_message_size(Some(data.len()))
+ .max_frame_size(Some(data.len()))
+ .max_container_size(Some(1024))
+ .max_string_size(Some(data.len()))
+ .build()
+ .unwrap();
+ // First try to deserialize the raw input bytes
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ mem.set_readable_bytes(data);
+ let mut protocol =
+ TBinaryInputProtocol::with_config(mem, true /* strict */, config.clone());
+ let input = FuzzTest::read_from_in_protocol(&mut protocol)?;
+
+ // Now do the roundtrip test with the successfully deserialized object
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ let mut out_protocol = TBinaryOutputProtocol::new(&mut mem, true);
+ input.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+
+ // Get the serialized bytes
+ let serialized = mem.write_bytes();
+
+ // Deserialize again
+ let mut mem = TBufferChannel::with_capacity(serialized.len(), serialized.len());
+ mem.set_readable_bytes(&serialized);
+ let mut in_protocol = TBinaryInputProtocol::with_config(mem, true, config);
+ let obj = FuzzTest::read_from_in_protocol(&mut in_protocol)?;
+
+ assert_eq!(input, obj);
+
+ Ok(())
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+ let _ = run(data);
+});
diff --git a/lib/rs/test/fuzz/fuzz_targets/roundtrip_compact.rs b/lib/rs/test/fuzz/fuzz_targets/roundtrip_compact.rs
new file mode 100644
index 0000000..e8c8c33
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/roundtrip_compact.rs
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::{
+ TCompactInputProtocol, TCompactOutputProtocol, TOutputProtocol, TSerializable,
+};
+use thrift::transport::TBufferChannel;
+use thrift::TConfiguration;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+fn run(data: &[u8]) -> thrift::Result<()> {
+ // Add some limits to optimize fuzzing
+ let config = TConfiguration::builder()
+ .max_message_size(Some(data.len()))
+ .max_frame_size(Some(data.len()))
+ .max_container_size(Some(1024))
+ .max_string_size(Some(data.len()))
+ .build()
+ .unwrap();
+ // First try to deserialize the raw input bytes
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ mem.set_readable_bytes(data);
+ let mut protocol = TCompactInputProtocol::with_config(mem, config.clone());
+ let input = FuzzTest::read_from_in_protocol(&mut protocol)?;
+
+ // Now do the roundtrip test with the successfully deserialized object
+ let mut mem = TBufferChannel::with_capacity(data.len(), data.len());
+ let mut out_protocol = TCompactOutputProtocol::new(&mut mem);
+ input.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+
+ // Get the serialized bytes
+ let serialized = mem.write_bytes();
+
+ // Deserialize again
+ let mut mem = TBufferChannel::with_capacity(serialized.len(), serialized.len());
+ mem.set_readable_bytes(&serialized);
+ let mut in_protocol = TCompactInputProtocol::with_config(mem, config);
+ let obj = FuzzTest::read_from_in_protocol(&mut in_protocol)?;
+
+ assert_eq!(input, obj);
+
+ Ok(())
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+ let _ = run(data);
+});
diff --git a/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_binary.rs b/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_binary.rs
new file mode 100644
index 0000000..47631b5
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_binary.rs
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::{
+ TBinaryInputProtocol, TBinaryOutputProtocol, TOutputProtocol, TSerializable,
+};
+use thrift::transport::TBufferChannel;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+const BUFFER_CAPACITY: usize = 65536;
+
+fn run(input: FuzzTest) -> thrift::Result<()> {
+ // TODO: Figure out a way to do this without hardcoding the buffer size
+ // Serialize
+ let mut mem = TBufferChannel::with_capacity(BUFFER_CAPACITY, BUFFER_CAPACITY);
+ let mut out_protocol = TBinaryOutputProtocol::new(&mut mem, true);
+ input.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+
+ // Get the serialized bytes
+ let serialized = mem.write_bytes();
+
+ // Deserialize
+ let mut mem = TBufferChannel::with_capacity(serialized.len(), serialized.len());
+ mem.set_readable_bytes(&serialized);
+ let mut in_protocol = TBinaryInputProtocol::new(mem, true);
+ let obj = FuzzTest::read_from_in_protocol(&mut in_protocol)?;
+
+ assert_eq!(input, obj);
+
+ Ok(())
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|input: FuzzTest| {
+ let _ = run(input);
+});
diff --git a/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_compact.rs b/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_compact.rs
new file mode 100644
index 0000000..ac87eb1
--- /dev/null
+++ b/lib/rs/test/fuzz/fuzz_targets/structured_roundtrip_compact.rs
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![no_main]
+
+use thrift::protocol::{
+ TCompactInputProtocol, TCompactOutputProtocol, TOutputProtocol, TSerializable,
+};
+use thrift::transport::TBufferChannel;
+use thrift_fuzz::fuzz_test::FuzzTest;
+
+const BUFFER_CAPACITY: usize = 65536;
+
+fn run(input: FuzzTest) -> thrift::Result<()> {
+ // TODO: Figure out a way to do this without hardcoding the buffer size
+ // Serialize
+ let mut mem = TBufferChannel::with_capacity(BUFFER_CAPACITY, BUFFER_CAPACITY);
+ let mut out_protocol = TCompactOutputProtocol::new(&mut mem);
+ input.write_to_out_protocol(&mut out_protocol)?;
+ out_protocol.flush()?;
+
+ // Get the serialized bytes
+ let serialized = mem.write_bytes();
+
+ // Deserialize
+ let mut mem = TBufferChannel::with_capacity(serialized.len(), serialized.len());
+ mem.set_readable_bytes(&serialized);
+ let mut in_protocol = TCompactInputProtocol::new(mem);
+ let obj = FuzzTest::read_from_in_protocol(&mut in_protocol)?;
+
+ assert_eq!(input, obj);
+
+ Ok(())
+}
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|input: FuzzTest| {
+ let _ = run(input);
+});
diff --git a/lib/rs/test/fuzz/lib/mod.rs b/lib/rs/test/fuzz/lib/mod.rs
new file mode 100644
index 0000000..71e1735
--- /dev/null
+++ b/lib/rs/test/fuzz/lib/mod.rs
@@ -0,0 +1,18 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+pub mod fuzz_test;