case studies

EByte Case Study Comparing Sensor Captures Before and After a Firmware Update

A firmware update changes what a device sends, and the usual way to find out what changed is to capture its traffic before and after and stare at hex dumps. EByte 0.5.0 adds ebyte diff, which decodes two files through the same schema and lists the fields that differ, with each side’s offset and length and the old and new values of integers. This case study compared two packet captures of a small network sensor using the released ebyte 0.5.0 Linux binary, downloaded from GitHub. Its checksum matched SHA256SUMS before anything ran.

The captures are classic pcap files that stand in for a real device: a sensor at 10.0.0.42 sends a UDP status packet to port 5000 once a second, with the magic SNSR, a firmware version, a sequence number, a temperature in tenths of a degree and a humidity reading. A Python script built them with correct IP and UDP checksums. before.pcap holds five packets from firmware 0x0102, 374 bytes. after.pcap, captured an hour later, holds six packets from firmware 0x0103, 456 bytes. The dpkt library read both, counted the packets, read the firmware fields and found every checksum valid.

Terminal output of ebyte diff on the two captures: timestamps, derived lengths and checksums in grey, and in green the firmware change from 258 to 259, a new battery field in each packet and one added packet, ending with exit code 5.

First Pass: The Generic pcap Schema

The schema library now has a pcap schema that reads the global header and each packet’s timestamps, lengths and raw frame. Diffing with it gave 15 changed fields besides the timestamps, and one added packet. Every frame was two bytes longer, and every frame reported first_difference=17.

Byte 17 of the frame is the low byte of the IP total length, 40 before and 42 after. That’s true but not much help: the first differing byte is the first symptom, not the cause. A raw frame is one opaque field, so the diff can only say where it starts to differ.

Second Pass: Describe the Packet

So the second schema describes the whole frame: Ethernet addresses and type, the IPv4 header, the UDP header and the sensor’s own fields. Network integers are big-endian, so they override the file’s little-endian default. The lengths are derived as sizes of field ranges, which makes EByte check them on decode. The checksums are plain integers, because the IP and UDP ones-complement checksums aren’t among EByte’s algorithms. The packet ends like this:

{"name": "sensor_magic", "type": "bytes", "length": 4, "expect": "534e5352"},
{"name": "firmware", "type": "u16", "endian": "big"},
{"name": "sequence", "type": "u16", "endian": "big"},
{"name": "temperature_dc", "type": "i16", "endian": "big"},
{"name": "humidity_pct", "type": "u16", "endian": "big"},
{"name": "battery_mv", "type": "u16", "endian": "big", "when": {"field": "firmware", "equals": "259"}}

The last line is a guess you can test: maybe the new firmware sends something extra. Both files decoded with it, which already says the guess fits every packet. The diff then reported 40 changed fields and 6 added, and exited with code 5, meaning the files differ.

Per packet there were nine lines. The timestamp changed, because the second capture is an hour later. Four lengths changed, all marked derived: incl_len, orig_len, ip_length and udp_length, each two bytes longer. Both checksums changed, as checksums do. Then the two lines that explain everything: firmware went from 258 to 259, and battery_mv was added with the value 3650. Sequence numbers, temperature and humidity weren’t reported, because they’re the same in both files. The sixth packet was reported once, as added "/packets/5" struct, not field by field.

To keep only the cause, filter out the noise you understand:

ebyte diff --schema sensor-capture.schema.json --before before.pcap --after after.pcap --format text \
  | grep -v -e ts_sec -e derived -e checksum

That leaves 11 lines: five firmware changes, five new battery fields and the extra packet.

Where It Stops

Packets are compared by position: packet 0 with packet 0, and so on. That’s right for two captures of a regular stream. If the device had dropped one packet, every packet after the gap would be compared with its neighbour and reported as changed. EByte doesn’t line lists up by content. The diff also only sees what the schema describes. The first pass shows the price of a raw field, and fields a schema doesn’t name stay invisible. The diff contract has the details, including the JSON output for scripts.