Pakkit.net
← Back to blog

Engineering Practice

Both Ends Have to Share the Dictionary

Some protocols carry data as attributes defined in a shared dictionary, not in the packet itself — so if the two ends load different dictionaries, one side silently misreads the other, and anything it doesn't recognize just vanishes.

  • Engineering Practice
  • Protocols
  • Interoperability
  • Debugging

There’s a class of protocol where the packet on the wire doesn’t fully describe itself. It carries numbered attributes, and what those numbers mean — the name, the type, whether it’s a string or an integer or an IP — lives in a separate file called a dictionary that both ends are supposed to load. RADIUS works this way; so do SNMP’s MIBs and plenty of others. The elegant part is extensibility. The trap is that the dictionary is a hidden, out-of-band dependency, and if the two ends don’t share the same one, they’ll talk right past each other while both believe they’re being understood.

The data is in the dictionary, not the packet

When one side sends attribute number 26 with some bytes, the receiver can only make sense of it by looking up 26 in its dictionary. If both ends loaded the same dictionary, great — 26 means the same thing to both, and the bytes decode correctly. If they didn’t, the sender meant one thing and the receiver decodes another, or decodes nothing at all. The wire format is fine. The bytes arrived intact. The interpretation diverged, because interpretation was never in the packet — it was in a file each side loaded independently and assumed matched.

The packet tells you the number. The dictionary tells you what the number means. Ship mismatched dictionaries and you’ve built two systems speaking slightly different languages with the same accent.

This gets especially sharp with vendor-specific attributes, where different vendors define their own extensions. Send a vendor’s attribute to a receiver that only loaded a different vendor’s dictionary and it has no idea what you’re talking about — even though, structurally, the packet is perfectly valid.

The scariest failure: unknown means silently dropped

Here’s the part that turns a mismatch into a debugging nightmare. When a receiver hits an attribute it doesn’t have in its dictionary, the usual behavior isn’t to error — it’s to silently ignore it. That’s a deliberate feature for forward-compatibility: an old receiver shouldn’t choke on a new attribute it doesn’t understand. But it means a sender can include something meaningful, the receiver can drop it on the floor without a word, and both sides report success. The sender thinks it communicated. The receiver never saw the attribute. And nothing, anywhere, logged a problem.

So the symptom isn’t a crash. It’s a feature that “doesn’t work” for no visible reason — the attribute that was supposed to change behavior had no effect, because the far end quietly discarded it as unknown. If you’re debugging “I’m sending it but nothing happens,” a dictionary mismatch (or an attribute the other end simply doesn’t load) is high on the list, and it’s invisible unless you go looking.

The dictionary is a dependency you have to distribute and version

The mental correction is to treat the dictionary as what it is: a dependency, as real as a library, that both ends must have — at compatible versions — for the system to work. That means it has to be distributed (both sides actually get it) and versioned in lockstep (they get the same one). Add a new attribute? Both ends need the updated dictionary before either can use it, or the side that’s behind silently drops what the side that’s ahead sends. Rolling out a dictionary change is a coordinated deploy, not a one-sided edit — much like both ends of any wire format have to agree on the contract, except here the contract lives in a file you can forget to ship.

The pattern is everywhere shared vocabulary is out-of-band

Once you see it, this shows up any time meaning is defined outside the message:

  • SNMP MIBs — the numeric OIDs are meaningless without the MIB that names and types them.
  • Serialization schemas — a compiled message is bytes plus field numbers; the schema is the dictionary, and a consumer without it can’t interpret the fields.
  • Shared enums across services — if service A’s enum says 3 = SUSPENDED and service B’s copy says 3 = DELETED, they’ll “agree” on the number and disagree on reality.
  • Character encodings — the same bytes are different text under different encodings; the encoding is the dictionary both ends must share.

Every one of these fails the same way: the transport is fine, the meaning is not, and the divergence hides in a definition that lives outside the data.

So when two systems are exchanging structured data and one of them “isn’t getting” something the other swears it sent, I don’t start with the network. I ask whether both ends are reading from the same codebook — same dictionary, same schema, same enum, same version — because a shared vocabulary only works when it’s actually shared. If you’ve chased a bug down to two ends loading different dictionaries, I’d like to hear how it surfaced.