TCP Group Chat + Protocol Fuzzer
A thread-per-connection TCP server implementing a custom binary framing protocol over raw sockets, built alongside a fuzzing client that generates unpredictable payloads from real entropy to probe how the server handles malformed input.
- Role
- Solo project
- Timeline
- Oct - Dec 2025
- Impact
- A multi-client TCP chat server with a hand-rolled binary protocol, plus a fuzzing client that throws entropy-sourced garbage at it.
reassembling a message from partial TCP reads
Concepts demonstrated
- Networking & protocol design
- Concurrency
- Fuzz testing
- Defensive network programming
Highlights
- Implemented correct TCP stream reassembly: a growable buffer reconstructs message boundaries from an arbitrary sequence of partial reads, rather than assuming one read() call maps to one message. That's the detail that separates working network code from code that only works in a demo.
- Ran one detached pthread per connected client, with a broadcast mutex protecting the shared client list separated from a second mutex coordinating a synchronized multi-client shutdown handshake.
- Built the fuzz client to source payload entropy from getentropy() rather than a seeded PRNG, so fuzzing runs aren't accidentally reproducible in a way that hides real bugs.
Problem
TCP is a byte stream, not a message stream. A naive server that assumes one read() equals one client message will silently corrupt or drop data the moment a message spans two packets. Building a chat server that's actually correct under real network conditions, and then adversarially testing it, meant confronting that directly.
Engineering approach
- Design a minimal binary framing protocol (type byte + payload + delimiter) simple enough to fuzz meaningfully.
- Implement stream reassembly explicitly: accumulate partial reads into a growable buffer and only extract a complete message once a full frame is present.
- Build a separate fuzzing client that generates payloads from real system entropy and exercises the server concurrently from multiple connections.
Architecture
- server.c: accept() loop spawning one detached pthread per client; a broadcast path guarded by one mutex, a termination-counter handshake guarded by a second.
- client.c: a background reader thread plus a main send thread per client, so sending and receiving fuzz traffic don't block each other.
- hex_string.c: entropy-sourced payload generation and hex encoding for reproducible logging of exactly what was sent.
Technical challenges
- Coordinating a clean shutdown across an unknown number of concurrently connected clients without a central client waiting alone, solved with a dedicated mutex-protected counter that only triggers broadcast shutdown once every connected client has sent its termination message.
- Keeping the broadcast lock and the shutdown-coordination lock separate, so a client mid-handshake doesn't block unrelated broadcast traffic to everyone else.
Decisions
- Used a thread-per-connection model over an event loop. For the connection counts this was designed around, it's simpler to reason about and keeps the framing/reassembly logic linear per client.
- Sourced fuzz entropy from getentropy() instead of rand() with a fixed seed, favoring genuinely unpredictable inputs over convenient reproducibility.
Result
A working concurrent chat server with a self-designed protocol and stream reassembly done correctly, exercised by a purpose-built fuzzing client rather than just manual testing.
Learnings
- Separating the broadcast mutex from the shutdown-coordination mutex is what actually made the concurrency model reasonable to think about. An earlier version that conflated the two was the first design that broke.
- The fuzzer targets the framing layer, not the application logic sitting on top of it. Extending it to fuzz message content against the chat command parser itself is the obvious next target.