The Solitary Engineer: Architecture and Mental Models BPP Blog
This is part 12 of the Black Phoenix Protocol (BPP) engineering blog series. Start with Day Zero if you are new here.
Most major networking protocols TLS, QUIC, WireGuard are designed by committees (IETF) and built by large teams of dedicated engineers over years. The Black Phoenix Protocol (BPP) was designed and implemented by one person in a few months.
This post isn't about code. It's about the mental models, the architectural decisions, and the psychological toll of undertaking complex, low-level systems engineering in isolation.
The Mental Shift: Bytes vs. Messages
The hardest part of writing a proxy protocol is unlearning high-level programming.
When you write a REST API in Node.js or Python, you deal with Messages (JSON objects, HTTP requests). The language runtime handles the framing.
When you write a custom protocol in Go over raw TCP sockets, there are no messages. There is only an infinite, flowing stream of bytes.
If you send a 1000-byte encrypted payload from the client, the server might read 200 bytes, then 500 bytes, then 300 bytes. Or it might read 1000 bytes and the first 50 bytes of the next payload all at once.
Building the mental model to handle buffering, frame length prefixes, and state machines over fragmented byte streams was the first major hurdle. I spent weeks staring at Wireshark dumps, trying to figure out why an AES-GCM decryption block was failing, only to realize I was trying to decrypt a payload that was cut in half by network congestion.
The Concurrency Nightmare
Go's goroutines make concurrency easy to write, and incredibly hard to debug.
A single BPP connection involves:
- A goroutine reading from the local application (e.g., the browser).
- A goroutine encrypting and writing to the remote relay.
- A goroutine reading from the remote relay.
- A goroutine decrypting and writing back to the local application.
- A multiplexer managing dozens of these connections over a single TCP socket.
When a timeout occurs, or the GFW drops a packet, how do you tear down all these goroutines cleanly without leaking memory?
I learned the hard way about deadlocks, where the read goroutine is waiting for the write goroutine, but the write goroutine is blocked on a full network buffer. Implementing context.Context everywhere and strictly adhering to cancellation propagation became my religion.
Adversarial Engineering: Breaking Your Own Mind
The most exhausting part of building a circumvention tool is the schizophrenic nature of the work. You have to be both the builder and the attacker.
I would spend three days writing a brilliant traffic-shaping algorithm (the Builder). Then, I would switch hats, boot up BPP Guardian (the Attacker), and spend two days writing Python scripts to absolutely destroy the algorithm I just built.
It is psychologically taxing to constantly tear down your own work. You must adopt a mindset of ruthless paranoia. Every time I wrote a Read() call, I had to ask: "What happens if the adversary sends exactly 1 byte every 10 seconds? Does my server hang forever? Do I run out of file descriptors?"
The Solitude
There is a profound loneliness in solitary engineering. When you hit a wall when the cryptography isn't lining up, when the memory profiler shows a leak you can't trace, when the protocol works in the lab but fails on a live VPS there is no senior engineer to ping on Slack. There is no team brainstorming session.
It's just you, the terminal, and the documentation.
But there is also immense clarity. Without design by committee, BPP is cohesive. The architecture reflects a singular vision. Every module, from the Vue.js Command Center down to the raw Curve25519 byte arrays, fits together because they all exist within one mental model.
Building BPP taught me that the limits of what a single, focused engineer can build are much further out than we usually assume.
In Part 13, we look to the horizon: why TCP is dying, and why QUIC and HTTP/3 are the next frontier for defeating censorship.