test(proto): add decoder fuzzing

Add isolated cargo-fuzz/libFuzzer infrastructure for irtt-proto, fuzzing
decode_request, decode_open_reply, decode_echo_reply, Params::decode(_with_presence),
and the varint codec against arbitrary and structurally-derived wire input.
Each target's core invariant is that arbitrary bytes must never panic; a
small clean-room seed corpus (generated from irtt-proto's own encoders) and
a bounded nightly smoke CI workflow are included. The fuzz project is its
own isolated cargo workspace and does not affect the root workspace, its
lockfile, or any published crate's dependency graph.
This commit is contained in:
Nils Andreas Svee committed 2026-08-19 23:33:27 +07:00
1 parent 9b70bd8b9d
commit 719073df1c
29 files changed
+766

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
target
artifacts
coverage
# The committed seed corpus under corpus/ is small and curated by hand; a
# local `cargo fuzz run` grows each target's corpus directory far beyond
# that, so ignore anything not already tracked instead of the directory
# itself.
corpus/*/*
!corpus/*/
+246
View File
@@ -0,0 +1,246 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "cc"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cmov"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "ctutils"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
dependencies = [
"cmov",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
"ctutils",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hmac"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [
"digest",
]
[[package]]
name = "hybrid-array"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
dependencies = [
"typenum",
]
[[package]]
name = "irtt-proto"
version = "0.5.0"
dependencies = [
"hmac",
"md-5",
"subtle",
"thiserror",
]
[[package]]
name = "irtt-proto-fuzz"
version = "0.0.0"
dependencies = [
"irtt-proto",
"libfuzzer-sys",
]
[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libfuzzer-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2"
dependencies = [
"arbitrary",
"cc",
]
[[package]]
name = "md-5"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+53
View File
@@ -0,0 +1,53 @@
[package]
name = "irtt-proto-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.irtt-proto]
path = "../crates/irtt-proto"
# Use independent workspace for fuzzers
[workspace]
members = ["."]
[[bin]]
name = "decode_request"
path = "fuzz_targets/decode_request.rs"
test = false
doc = false
bench = false
[[bin]]
name = "decode_open_reply"
path = "fuzz_targets/decode_open_reply.rs"
test = false
doc = false
bench = false
[[bin]]
name = "decode_echo_reply"
path = "fuzz_targets/decode_echo_reply.rs"
test = false
doc = false
bench = false
[[bin]]
name = "decode_params"
path = "fuzz_targets/decode_params.rs"
test = false
doc = false
bench = false
[[bin]]
name = "decode_varint"
path = "fuzz_targets/decode_varint.rs"
test = false
doc = false
bench = false
+77
View File
@@ -0,0 +1,77 @@
# `irtt-proto` fuzzing
Coverage-guided fuzzing of `irtt-proto`'s wire decoders via
[`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz)/libFuzzer.
This project is intentionally isolated from the normal stable/MSRV
workspace: it lives in its own `[workspace]` under `fuzz/`, is never a member
of the root workspace, requires nightly Rust, and is not built by
`cargo check`/`test`/`clippy`/`doc` at the workspace root.
## Prerequisites
- nightly Rust (`rustup toolchain install nightly`)
- `cargo-fuzz` (`cargo install cargo-fuzz --locked --version 0.13.1`)
## Targets
| Target | Exercises |
| ------------------- | ----------------------------------------------------------------- |
| `decode_request` | `decode_request`, `Params::decode` on a decoded Open, `verify_packet_hmac` |
| `decode_open_reply` | `decode_open_reply`, unauthenticated and HMAC-authenticated |
| `decode_echo_reply` | `decode_echo_reply` against a deterministic, bounded `Params` matrix, unauthenticated and HMAC-authenticated |
| `decode_params` | `Params::decode` and `Params::decode_with_presence`, plus an encode/decode round trip |
| `decode_varint` | `varint::decode_uvarint`/`decode_varint` and their round trip through `encode_uvarint`/`encode_varint` |
The invariant every target asserts is: **arbitrary bytes must never panic**.
Returning `Err` is normal and expected; a successful decode is also normal.
Every target bounds its input to 128 KiB (`MAX_INPUT_LEN` in each harness) so
libFuzzer spends its time exploring protocol structure rather than
allocating/copying megabytes — production IRTT datagrams are UDP-sized, well
under this bound.
## Running
```sh
cargo +nightly fuzz run decode_request
```
Bounded smoke run, e.g. 30 seconds:
```sh
cargo +nightly fuzz run decode_request -- -max_total_time=30
```
Build every target without running:
```sh
cargo +nightly fuzz build
```
## Corpus
Seed corpora live under `fuzz/corpus/<target>/`. They were generated once
from `irtt-proto`'s own public encoders (`encode_request`, `encode_open_reply`,
`encode_echo_reply`, `Params::encode`) by a throwaway local script, not copied
from any external source, and not derived from upstream `heistp/irtt`. Each
file is a small, representative wire packet (minimal Open, Close, Echo, HMAC
variants, ordinary/recv-count/recv-window/midpoint Echo replies, and a few
`Params` payloads including an unknown tag and `server_fill`).
## Crash artifacts
A failing case is written under `fuzz/artifacts/<target>/`. To replay it:
```sh
cargo +nightly fuzz run decode_request fuzz/artifacts/decode_request/<crash-file>
```
## Isolation notes
- `fuzz/Cargo.toml` declares its own `[workspace]`, so it is never picked up
by the root `[workspace]` in `../Cargo.toml`.
- `libfuzzer-sys` and `arbitrary` are fuzz-only dependencies; they never
appear in any published crate's dependency graph.
- `fuzz/Cargo.lock` is committed for reproducible fuzzing builds, independent
of the root `Cargo.lock`.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
§[ u9ü;dº%Íý¶Ä¢•oRq‡«¶–x€ø‚­€¨Ö¹
Binary file not shown.
@@ -0,0 +1 @@
ァ[Rq�カ堀€�ュ€ィヨケ
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
€�ュ€ィヨケ
@@ -0,0 +1 @@

+1
View File
@@ -0,0 +1 @@
 pattern:abc
+1
View File
@@ -0,0 +1 @@
c�
+1
View File
@@ -0,0 +1 @@
ァ[�fUD3"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
§[ Ай ;¦w6Џос†{‘в‚Ђш‚­ЂЁЦ№
+1
View File
@@ -0,0 +1 @@
ァ[
+1
View File
@@ -0,0 +1 @@
ァ[€�ュ€ィヨケ
+133
View File
@@ -0,0 +1,133 @@
#![no_main]
use irtt_proto::{
decode_echo_reply, echo_packet_len, hmac::compute_hmac_in_place, Clock, PacketLayout, Params,
ReceivedStats, StampAt, FLAG_HMAC, FLAG_REPLY, TIMESTAMP_SIZE,
};
use libfuzzer_sys::fuzz_target;
/// See `decode_request.rs` for the rationale behind this bound.
const MAX_INPUT_LEN: usize = 128 * 1024;
/// Mirrors `irtt-proto`'s crate-private HMAC field offset, the same way the
/// crate's own external integration tests do.
const HMAC_OFFSET: usize = 4;
const FUZZ_HMAC_KEY: &[u8] = b"fuzz-decode-echo-reply-key";
/// Negotiated-length vocabulary around the boundaries that matter for this
/// decoder: zero, tiny, MTU-ish, and modestly larger. Deliberately bounded so
/// we fuzz the decoder rather than benchmark OOM handling.
const LENGTH_BUCKETS: &[i64] = &[
0, 1, 63, 64, 65, 127, 128, 129, 511, 512, 513, 1199, 1200, 1201, 1471, 1472, 1473, 1499, 1500,
1501, 4096, 65507,
];
fn pick_received_stats(v: u8) -> ReceivedStats {
match v % 4 {
0 => ReceivedStats::None,
1 => ReceivedStats::Count,
2 => ReceivedStats::Window,
_ => ReceivedStats::Both,
}
}
fn pick_stamp_at(v: u8) -> StampAt {
match v % 5 {
0 => StampAt::None,
1 => StampAt::Send,
2 => StampAt::Receive,
3 => StampAt::Both,
_ => StampAt::Midpoint,
}
}
fn pick_clock(v: u8) -> Clock {
match v % 4 {
0 => Clock::Unspecified,
1 => Clock::Wall,
2 => Clock::Monotonic,
_ => Clock::Both,
}
}
fuzz_target!(|data: &[u8]| {
if data.len() > MAX_INPUT_LEN {
return;
}
// Need enough control bytes to derive Params before any packet bytes.
const CONTROL_LEN: usize = 6;
if data.len() < CONTROL_LEN {
return;
}
let (control, rest) = data.split_at(CONTROL_LEN);
let received_stats = pick_received_stats(control[0]);
let stamp_at = pick_stamp_at(control[1]);
let clock = pick_clock(control[2]);
let length_bucket = LENGTH_BUCKETS[control[3] as usize % LENGTH_BUCKETS.len()];
let hmac_present = control[4] & 1 == 1;
let use_compat_len = control[5] & 1 == 1;
let params = Params {
protocol_version: 1,
length: length_bucket,
received_stats,
stamp_at,
clock,
..Params::default()
};
let Ok(normal_len) = echo_packet_len(hmac_present, &params) else {
return;
};
let layout = PacketLayout::echo(hmac_present, &params);
let is_single_clock_midpoint =
stamp_at == StampAt::Midpoint && matches!(clock, Clock::Wall | Clock::Monotonic);
let target_len = if use_compat_len && is_single_clock_midpoint {
(layout.header_len() + TIMESTAMP_SIZE).max(normal_len)
} else {
normal_len
};
let flags = FLAG_REPLY | if hmac_present { FLAG_HMAC } else { 0 };
let mut packet = Vec::with_capacity(target_len);
packet.extend_from_slice(&irtt_proto::MAGIC);
packet.push(flags);
if hmac_present {
packet.extend_from_slice(&[0u8; 16]);
}
// Fill the remainder of the negotiated length from the fuzz-provided
// tail, truncating or zero-padding to hit the target length exactly so
// the decoder's field parsing gets real coverage rather than bailing out
// on a length mismatch every time.
let body_len = target_len.saturating_sub(packet.len());
if rest.len() >= body_len {
packet.extend_from_slice(&rest[..body_len]);
} else {
packet.extend_from_slice(rest);
packet.resize(target_len, 0);
}
packet.resize(target_len, 0);
if hmac_present {
if compute_hmac_in_place(FUZZ_HMAC_KEY, &mut packet, HMAC_OFFSET).is_err() {
return;
}
let reply = decode_echo_reply(&packet, &params, Some(FUZZ_HMAC_KEY));
if let Ok(reply) = reply {
assert!(reply.payload.len() <= packet.len());
}
} else {
let reply = decode_echo_reply(&packet, &params, None);
if let Ok(reply) = reply {
assert!(reply.payload.len() <= packet.len());
}
}
// Also exercise the raw fuzz bytes directly against the same negotiated
// Params, unmodified and un-lengthened, purely for the no-panic
// invariant on genuinely arbitrary bytes.
let _ = decode_echo_reply(data, &params, None);
});
+49
View File
@@ -0,0 +1,49 @@
#![no_main]
use irtt_proto::{
decode_open_reply, hmac::compute_hmac_in_place, FLAG_HMAC, FLAG_OPEN, FLAG_REPLY,
};
use libfuzzer_sys::fuzz_target;
/// See `decode_request.rs` for the rationale behind this bound.
const MAX_INPUT_LEN: usize = 128 * 1024;
/// Offset of the HMAC field, right after the 4-byte header. `irtt-proto`
/// keeps the real constant crate-private; this mirrors it the same way the
/// crate's own external integration tests do.
const HMAC_OFFSET: usize = 4;
const HMAC_SIZE: usize = 16;
const FUZZ_HMAC_KEY: &[u8] = b"fuzz-decode-open-reply-key";
fuzz_target!(|data: &[u8]| {
if data.len() > MAX_INPUT_LEN {
return;
}
// Unauthenticated decode path: no panic is the invariant, Ok/Err both
// acceptable.
let _ = decode_open_reply(data, None);
// Authenticated decode path against arbitrary bytes. Most inputs will
// fail on HMAC presence/verification before reaching the post-HMAC
// parser, which is expected and fine on its own.
let _ = decode_open_reply(data, Some(FUZZ_HMAC_KEY));
// To also give the post-HMAC parser sustained coverage, derive a packet
// that *will* authenticate: when the input is structurally capable of
// carrying an HMAC field and its flags already indicate HMAC, patch in a
// freshly-computed HMAC over the untouched body via the crate's own
// helper (never reimplemented here) and decode that.
if data.len() >= HMAC_OFFSET + HMAC_SIZE {
let flags = data[3];
let looks_like_open_reply =
flags & FLAG_OPEN != 0 && flags & FLAG_REPLY != 0 && flags & FLAG_HMAC != 0;
if looks_like_open_reply {
let mut signed = data.to_vec();
if compute_hmac_in_place(FUZZ_HMAC_KEY, &mut signed, HMAC_OFFSET).is_ok() {
let _ = decode_open_reply(&signed, Some(FUZZ_HMAC_KEY));
}
}
}
});
+46
View File
@@ -0,0 +1,46 @@
#![no_main]
use irtt_proto::Params;
use libfuzzer_sys::fuzz_target;
/// See `decode_request.rs` for the rationale behind this bound.
const MAX_INPUT_LEN: usize = 128 * 1024;
fuzz_target!(|data: &[u8]| {
if data.len() > MAX_INPUT_LEN {
return;
}
let decoded = Params::decode(data);
let decoded_with_presence = Params::decode_with_presence(data);
// Both entry points share one parser, so when both succeed they must
// agree on the decoded values.
match (&decoded, &decoded_with_presence) {
(Ok(params), Ok(with_presence)) => {
assert_eq!(params, &with_presence.params);
}
(Ok(_), Err(_)) | (Err(_), Ok(_)) => {
panic!("decode and decode_with_presence disagreed on success for {data:02x?}");
}
(Err(_), Err(_)) => {}
}
// No panic is the primary invariant for malformed input; unknown tags are
// tolerated and repeated known tags are last-value-wins by design, so we
// do not assert a particular failure mode here.
if let Ok(params) = decoded {
let encoded = params.encode();
// Re-encoding and decoding again must preserve the semantic value.
// Presence information does not round-trip through `encode` for
// absent/default fields, so we compare `Params` values, not the
// original wire bytes.
if let Ok(reencoded_params) = Params::decode(&encoded) {
assert_eq!(params, reencoded_params);
} else {
panic!(
"re-encoding a successfully decoded Params produced bytes that failed to decode"
);
}
}
});
+45
View File
@@ -0,0 +1,45 @@
#![no_main]
use irtt_proto::{decode_request, verify_packet_hmac, DecodedRequestKind, Params};
use libfuzzer_sys::fuzz_target;
/// Keeps individual cases bounded so libFuzzer explores protocol structure
/// instead of spending time allocating/copying megabytes. This is UDP wire
/// protocol code; production datagrams are well under this bound.
const MAX_INPUT_LEN: usize = 128 * 1024;
/// Fixed key used only to exercise the HMAC verification path. Never treated
/// as a secret.
const FUZZ_HMAC_KEY: &[u8] = b"fuzz-decode-request-key";
fuzz_target!(|data: &[u8]| {
if data.len() > MAX_INPUT_LEN {
return;
}
// The core invariant: arbitrary bytes must never panic. Ok/Err are both
// normal outcomes.
let decoded = match decode_request(data) {
Ok(decoded) => decoded,
Err(_) => return,
};
match decoded.kind {
DecodedRequestKind::Open { params, .. } => {
// Structural Open decoding deliberately permits malformed
// parameter bytes; both Ok and Err from the secondary parser are
// acceptable, and a structurally decoded Open is not required to
// contain valid Params.
let _ = Params::decode(params);
}
DecodedRequestKind::Close { .. } | DecodedRequestKind::Echo { .. } => {
// No token/session policy belongs here; that's above irtt-proto.
}
}
if decoded.hmac_present {
// Most arbitrary HMAC fields will fail authentication; both outcomes
// are acceptable, we're only exercising the verifier for panics.
let _ = verify_packet_hmac(FUZZ_HMAC_KEY, data);
}
});
+38
View File
@@ -0,0 +1,38 @@
#![no_main]
use irtt_proto::varint::{decode_uvarint, decode_varint, encode_uvarint, encode_varint};
use libfuzzer_sys::fuzz_target;
/// See `decode_request.rs` for the rationale behind this bound. Varint
/// grammar is trivial, but the bound is kept for consistency with the other
/// targets.
const MAX_INPUT_LEN: usize = 128 * 1024;
fuzz_target!(|data: &[u8]| {
if data.len() > MAX_INPUT_LEN {
return;
}
if let Ok((value, used)) = decode_uvarint(data) {
assert!(used > 0);
assert!(used <= data.len());
assert!(used <= 10);
let mut encoded = Vec::new();
encode_uvarint(value, &mut encoded);
// Canonical re-encoding need not reproduce noncanonical fuzz input,
// but it must decode back to the same value and consume exactly its
// own bytes.
assert_eq!(decode_uvarint(&encoded), Ok((value, encoded.len())));
}
if let Ok((value, used)) = decode_varint(data) {
assert!(used > 0);
assert!(used <= data.len());
assert!(used <= 10);
let mut encoded = Vec::new();
encode_varint(value, &mut encoded);
assert_eq!(decode_varint(&encoded), Ok((value, encoded.len())));
}
});