A C++17 binary protocol implementation for studying untrusted lengths, checksum
validation, incremental parsing, fragment lifetimes, and state-machine testing.
The core library depends only on the C++ standard library.
Status
The library implementation, CLI, and two libFuzzer targets are present. This is
an unverified source draft: no build, test, static-analysis, or Fuzz result is
claimed. The current CMakeLists.txt references tests/test_packet.cpp, which
has not yet been written. Complete that test source or remove that test target
before using the build commands below. BUILD_TESTING=OFF alone does not remove
the current unconditional test executable declaration.
Seed and artifact directories in the examples are created by the displayed
commands. They are not a claim that a prepared binary corpus is already bundled.
Frame headers, TLV fields, encoding, decoding, and field validation
include/packet/stream.hpp, src/stream.cpp
Incremental frame decoding and optional alignment recovery
src/message.cpp
Ordered fragment reassembly, expiry, cancellation, and fragmentation
include/packet/capture.hpp, src/capture.cpp
Capture files, filtering, statistics, sequence validation, and replay
src/main.cpp
Command-line interface
fuzz/fuzz_decode.cpp
Frame/capture decoding and canonical wire roundtrips
fuzz/fuzz_stream.cpp
Streaming input and generated fragment/reassembly properties
The shared CMake helpers live in ../cmake/ProjectOptions.cmake; preserve that
relative layout when moving the project.
Wire Format
All fixed-width integers use big-endian byte order. A frame starts with a 24-byte
header, followed by a TLV payload:
Offset
Size
Meaning
0
3
ASCII magic FZP
3
1
Version, currently 1
4
1
Flags
5
1
Reserved; must be zero
6
2
Channel
8
4
Sequence number
12
4
Message ID
16
4
Payload length
20
4
CRC32
24
Variable
TLV payload
CRC32 uses the reflected polynomial 0xEDB88320, initial state 0xffffffff,
and final complement. Coverage is header bytes 0–19 followed by the payload;
the checksum field is excluded.
Flags are 0x01 for fragmentation, 0x02 for the final fragment, and 0x04
for a response. Other flag bits are rejected. Final requires fragmentation.
Each TLV contains a 2-byte type, a 4-byte length, and that many value bytes:
Type
Value representation
1: Text
Valid UTF-8, including an empty string
2: Number
Exactly 8 bytes, unsigned integer
3: Blob
Arbitrary bytes
4: Attribute
2-byte key length, nonempty UTF-8 key, UTF-8 value
5: Timestamp
Exactly 8 bytes, unsigned integer
6: Boolean
Exactly one byte: 0 or 1
Type zero is reserved. Unknown types below 0x8000 are preserved; unknown types
with the high bit set are treated as critical and rejected. Field ordering and
duplicate fields are preserved. There is no implicit application-level schema.
Fragmentation and Stream Semantics
Messages consist of exactly one Blob field per frame. A fragmented message starts
at sequence zero and continues without gaps or duplicates. Reassembly is keyed
by (channel, message ID). All fragments must agree on the response flag.
The final fragment completes and removes the pending message.
Limits bound individual fields, payloads, pending messages, fragment count, and
each reassembled message. Aggregate reassembly memory can reach
max_messages * max_message; there is no separate global memory budget.
Expiry uses caller-provided monotonically increasing ticks.
StreamDecoder::feed accepts partial or multiple frames. The feed plus buffered
data must fit max_buffer, so large files should be fed in chunks. In strict
mode, a malformed frame raises an exception. Recovery mode searches for the next
FZP alignment and counts discarded bytes and rejected candidates.
Recovery cannot guarantee progress past an apparently valid header advertising
a not-yet-complete frame. finish() rejects any trailing incomplete data,
including leftover bytes in recovery mode. Reset after a strict-mode failure
before reusing a decoder.
Capture Format
A capture begins with ASCII FZC1 and a 4-byte record count. Each record contains
an 8-byte timestamp, a 4-byte frame length, and the encoded frame. A trailing
4-byte CRC32 covers everything preceding it.
Capture inspection accepts general frames. Message replay additionally requires
the Blob-only message convention and rejects captures ending with incomplete
messages. Sequence validation is a separate check from wire-format validation.
Build and CLI
After resolving the missing test-source item in the status section, run from
this directory with CMake 3.16+, a C++17 compiler, and Ninja:
To split an arbitrary input into Blob messages and restore it:
printf 'a small message' > message.txt
build/packet_cli fragment message.txt fragments.fzc 4
build/packet_cli replay fragments.fzc restored.txt
The CLI’s encode command creates a Text frame. Such a frame can be inspected
and captured but is not accepted by Blob-message replay. Replay output requires
exactly one completed message. Exit status 1 indicates a protocol or I/O error;
status 2 indicates invalid command usage.
Clang and CodeChecker must be installed separately. CTU requires compatible
Clang mapping tools. Analyze bytes.cpp, codec.cpp, stream.cpp, and
message.cpp together to explore data flow across translation units. Static
diagnostic availability depends on the analyzer and enabled checkers.
ENABLE_FUZZING requires Clang/libFuzzer and enables ASan/UBSan. For a standalone
sanitizer build, use -DENABLE_SANITIZERS=ON in a separate directory. A replay
command is build-fuzz/packet_fuzz_decode artifacts/crash-<hash> or the matching
stream target. These campaigns have not been run.
Optional Demonstration Defects
Use a separate configuration with both -DENABLE_DEMO_BUGS=ON and
-DENABLE_FUZZING=ON. Normal CTest registration is disabled in this configuration.
ID
Location
Trigger
Expected detector
Repair
PKT-001
src/codec.cpp, decode_fields
Type 0x70 with more than 8 value bytes in a valid checksummed frame
ASan; possible static buffer warning
Decode directly into bounded dynamic storage
PKT-002
src/message.cpp, Reassembler::accept
A fragmented Blob message on channel 0xdead
ASan and potentially use-after-free analysis
Retain ownership until the final read; remove the stale cache pointer
PKT-003
src/message.cpp, finalization
A nonempty completed fragmented message
Reassembly equality oracle
Do not discard the final byte
PKT-001 can be constructed through the library without manually calculating CRC:
packet::Frame frame;
frame.fields.push_back({0x70, packet::Bytes(32, 'A')});
auto wire = packet::encode(frame);
auto decoded = packet::decode(packet::View(wire));
For PKT-002, create a packet::Message with channel 0xdead, a 64-byte body,
and fragment(message, 32), then feed the frames to Reassembler::accept.
For PKT-003, use an ordinary channel and compare the reassembled body with the
original. The stream Fuzz harness already contains this equality oracle.
Run each failure case separately. build-bugs must first be configured and built
with the options above. The current draft does not bundle a standalone PKT-001
fixture generator or a completed unit-test suite.
Intended Validation and Limitations
Planned tests include every truncated header length, impossible TLV lengths,
checksum changes, unknown critical types, invalid UTF-8, split-point invariance,
concatenated frames, out-of-order/duplicate fragments, expiry, capture corruption,
and randomized encode/decode and fragment/reassembly roundtrips.
The format is educational and is not compatible with an external network
protocol. CRC32 detects accidental corruption; it provides no authentication.
The implementation has no sockets, encryption, retransmission, or concurrent
access. Final compilation, tests, and the 3000–5000-line source acceptance check
remain pending. The shared counter is ../tools/count_loc.py.
Packet Codec: Static Analysis and Fuzzing Lab
A C++17 binary protocol implementation for studying untrusted lengths, checksum validation, incremental parsing, fragment lifetimes, and state-machine testing. The core library depends only on the C++ standard library.
Status
The library implementation, CLI, and two libFuzzer targets are present. This is an unverified source draft: no build, test, static-analysis, or Fuzz result is claimed. The current
CMakeLists.txtreferencestests/test_packet.cpp, which has not yet been written. Complete that test source or remove that test target before using the build commands below.BUILD_TESTING=OFFalone does not remove the current unconditional test executable declaration.Seed and artifact directories in the examples are created by the displayed commands. They are not a claim that a prepared binary corpus is already bundled.
Features and Layout
include/packet/bytes.hpp,src/bytes.cppinclude/packet/codec.hpp,src/codec.cppinclude/packet/stream.hpp,src/stream.cppsrc/message.cppinclude/packet/capture.hpp,src/capture.cppsrc/main.cppfuzz/fuzz_decode.cppfuzz/fuzz_stream.cppThe shared CMake helpers live in
../cmake/ProjectOptions.cmake; preserve that relative layout when moving the project.Wire Format
All fixed-width integers use big-endian byte order. A frame starts with a 24-byte header, followed by a TLV payload:
FZPCRC32 uses the reflected polynomial
0xEDB88320, initial state0xffffffff, and final complement. Coverage is header bytes 0–19 followed by the payload; the checksum field is excluded.Flags are
0x01for fragmentation,0x02for the final fragment, and0x04for a response. Other flag bits are rejected. Final requires fragmentation.Each TLV contains a 2-byte type, a 4-byte length, and that many value bytes:
Type zero is reserved. Unknown types below
0x8000are preserved; unknown types with the high bit set are treated as critical and rejected. Field ordering and duplicate fields are preserved. There is no implicit application-level schema.Fragmentation and Stream Semantics
Messages consist of exactly one Blob field per frame. A fragmented message starts at sequence zero and continues without gaps or duplicates. Reassembly is keyed by
(channel, message ID). All fragments must agree on the response flag. The final fragment completes and removes the pending message.Limits bound individual fields, payloads, pending messages, fragment count, and each reassembled message. Aggregate reassembly memory can reach
max_messages * max_message; there is no separate global memory budget. Expiry uses caller-provided monotonically increasing ticks.StreamDecoder::feedaccepts partial or multiple frames. The feed plus buffered data must fitmax_buffer, so large files should be fed in chunks. In strict mode, a malformed frame raises an exception. Recovery mode searches for the nextFZPalignment and counts discarded bytes and rejected candidates.Recovery cannot guarantee progress past an apparently valid header advertising a not-yet-complete frame.
finish()rejects any trailing incomplete data, including leftover bytes in recovery mode. Reset after a strict-mode failure before reusing a decoder.Capture Format
A capture begins with ASCII
FZC1and a 4-byte record count. Each record contains an 8-byte timestamp, a 4-byte frame length, and the encoded frame. A trailing 4-byte CRC32 covers everything preceding it.Capture inspection accepts general frames. Message replay additionally requires the Blob-only message convention and rejects captures ending with incomplete messages. Sequence validation is a separate check from wire-format validation.
Build and CLI
After resolving the missing test-source item in the status section, run from this directory with CMake 3.16+, a C++17 compiler, and Ninja:
To split an arbitrary input into Blob messages and restore it:
The CLI’s
encodecommand creates a Text frame. Such a frame can be inspected and captured but is not accepted by Blob-message replay. Replay output requires exactly one completed message. Exit status 1 indicates a protocol or I/O error; status 2 indicates invalid command usage.Static Analysis
Clang and CodeChecker must be installed separately. CTU requires compatible Clang mapping tools. Analyze
bytes.cpp,codec.cpp,stream.cpp, andmessage.cpptogether to explore data flow across translation units. Static diagnostic availability depends on the analyzer and enabled checkers.Sanitizers and Fuzzing
ENABLE_FUZZINGrequires Clang/libFuzzer and enables ASan/UBSan. For a standalone sanitizer build, use-DENABLE_SANITIZERS=ONin a separate directory. A replay command isbuild-fuzz/packet_fuzz_decode artifacts/crash-<hash>or the matching stream target. These campaigns have not been run.Optional Demonstration Defects
Use a separate configuration with both
-DENABLE_DEMO_BUGS=ONand-DENABLE_FUZZING=ON. Normal CTest registration is disabled in this configuration.src/codec.cpp,decode_fields0x70with more than 8 value bytes in a valid checksummed framesrc/message.cpp,Reassembler::accept0xdeadsrc/message.cpp, finalizationPKT-001 can be constructed through the library without manually calculating CRC:
For PKT-002, create a
packet::Messagewith channel0xdead, a 64-byte body, andfragment(message, 32), then feed the frames toReassembler::accept. For PKT-003, use an ordinary channel and compare the reassembled body with the original. The stream Fuzz harness already contains this equality oracle.Run each failure case separately.
build-bugsmust first be configured and built with the options above. The current draft does not bundle a standalone PKT-001 fixture generator or a completed unit-test suite.Intended Validation and Limitations
Planned tests include every truncated header length, impossible TLV lengths, checksum changes, unknown critical types, invalid UTF-8, split-point invariance, concatenated frames, out-of-order/duplicate fragments, expiry, capture corruption, and randomized encode/decode and fragment/reassembly roundtrips.
The format is educational and is not compatible with an external network protocol. CRC32 detects accidental corruption; it provides no authentication. The implementation has no sockets, encryption, retransmission, or concurrent access. Final compilation, tests, and the 3000–5000-line source acceptance check remain pending. The shared counter is
../tools/count_loc.py.