eveofdarkness.org

Port and test, don't invent

Before writing a C# protocol library, we checked whether one already exists. The packet checksum and framing are the first things any DAoC client or server needs, so surely someone has written them, with tests.

What exists

Project Checksum and framing Tests License
OpenDAoC / DOLSharp Yes, C#, server side. The code the server actually runs No protocol tests GPL
OpenDAoC-ClientSimulator Yes, C#, client side None No license file
OnlyAModel C# protocol library (client 1.125) None No license file
Odin C# packet reader for a proxy Yes No license file
RustDAoC Rust, not C# Checksum tests and a recorded 1.127e session GPL

No published C# package exists either. So the code exists, but not as a tested library we could pick up.

Passing tests aren't proof

One of these projects has tests, and they pass. But they check a framing rule the real protocol doesn't follow: the code treats the length field as including the packet-type byte, in both directions. In recorded traffic the length counts only the payload, and packets from the client carry a 12-byte header and checksum on top of it. Code and tests were written from the same assumption, so they agree with each other, and real traffic disagrees with both.

Nothing against that project: it's a young proxy, and we came close to the same mistake in our own starting notes. The lesson is about where tests get their expected values. A test is only as good as its source of truth.

What we did instead

  • Ported the server's own code. OpenDAoC's checksum and packet parsing decides whether the server accepts us. Our client code is GPL too, so we ported it directly, with credit in the source files.
  • Tested against recorded traffic. RustDAoC's recorded 1.127e session supplies the expected values: all 2,057 packets split exactly, every client checksum is valid, and every packet rebuilds byte for byte.
  • Checked that the tests can fail. We broke the code on purpose in three realistic ways, including the framing assumption above, and the tests caught each one. One result stood out: a checksum that forgets to wrap at 256 still passes the two hand-worked examples we'd borrowed, because their inputs never wrap. Only the real traffic caught it.

Next is the part the server code doesn't contain: building the 1.127 login packets and talking to our test server.