QES record format

Interface control document, version 1.0.0, 2026-09-21 · Nolle Engineering GmbH

The normative document is qes-record-icd.json. This page is a reading companion: it says the same things with the colours and the worked examples that a JSON file cannot carry. Where the two disagree, the JSON is the specification. qes check validates a record against the JSON and fails on any field the document does not describe, so a disagreement between the document and the data is found rather than argued about.

A record is the byte stream an instrument emitted, kept as it came off the link. The copy on the card and the copy over the link are byte-identical: the firmware encodes once and writes the same bytes to both.

1  Transport

COBS frame  →  [ CBOR map ][ CRC-16 ]  →  0x00
FramingCOBS. 0x00 is the delimiter and appears nowhere else in the encoding, so splitting the file on 0x00 recovers the frames. A run of zero bytes inside a record is impossible in the encoding — though not in the medium, after a power cut.
PayloadCBOR, a flat map with short text keys. Major types used: 0, 1, 2, 3, 4, 5. A decoder that rejects arrays counts every science packet as corrupt.
ChecksumCRC-16/CCITT-FALSE — polynomial 0x1021, initial value 0xFFFF, not reflected, little byte first, over the CBOR bytes only and inside the COBS frame.
Console textA reply to a console command is framed on its own, so it never merges into a packet. A frame whose bytes are all printable ASCII is such a reply — count it separately, do not treat it as corruption.

The first byte of a frame is not a delimiter. COBS replaces every zero in the payload with a pointer to where the next one was, so a frame begins with a pointer and the only 0x00 in it is the one that ends it. A pointer of 05 says: the next four bytes contain no zero, and then there was one.

Row 1 and row 2 share a colour per COBS group. pointer a zero the pointer stood for frame delimiter
Row 3: CBOR map header key value CRC-16
Hover any byte to see what it is.

A complete packet, byte by byte

Real bytes, from the published null record at offset 81.

An event packet for a second in which that channel saw nothing. It is the smallest complete packet in the record, which makes it the clearest one to walk through: a present channel with an empty array says “nothing this second”, which is not the same as the channel being absent.

1 · on the wire, 25 bytes
05a461741b010901a0c2f086ae6169036173066165800dbb00
2 · after COBS, 23 bytes — same colours, and the zeros each pointer stood for
a461741b000001a0c2f086ae6169006173006165800dbb
3 · the same bytes, by field: a CBOR map and its CRC-16
a461741b000001a0c2f086ae6169006173006165800dbb
keyvalue
t1789976938158
i0
s0
earray of 0, first: []

A chain anchor

Real bytes, from the published null record at offset 312404.

h is SHA-256 of the previous anchor's hash concatenated with every link byte since that anchor's frame. o is this frame's offset in the stream, so a verifier can compare the difference between two anchors' o against the bytes it actually counted and measure anything inserted or dropped.

1 · on the wire, 101 bytes
13ab616918cd616b01616e184b617101616f1a07f0c45661741b014901a0c2f16f3461621aec5f14fa61631904996166187f62706b48083ad19f9de95d8b6168582063278715545820a65b27c118825d2004029c7d58487ebe14151beb7e053f8c7ff65c00
2 · after COBS, 99 bytes — same colours, and the zeros each pointer stood for
ab616918cd616b01616e184b617101616f1a00f0c45661741b000001a0c2f16f3461621aec5f14fa61631904996166187f62706b48083ad19f9de95d8b6168582063278715545820a65b27c118825d2004029c7d58487ebe14151beb7e053f8c7ff65c
3 · the same bytes, by field: a CBOR map and its CRC-16
ab616918cd616b01616e184b617101616f1a00f0c45661741b000001a0c2f16f3461621aec5f14fa61631904996166187f62706b48083ad19f9de95d8b6168582063278715545820a65b27c118825d2004029c7d58487ebe14151beb7e053f8c7ff65c
keyvalue
i205
k1
n75
q1
o15778902
t1789976997684
b3965654266
c1177
f127
pk083ad19f9de95d8b
h63278715545820a65b27c118825d2004029c7d58487ebe14151beb7e053f8c7f

The control channel

Real bytes, from the published null record at offset 4411.

Thirty-two bytes from the secure element's own conditioned generator, recorded in the same enclosure on the same supply through the same pipeline as the decay channels, so that any analysis can be run against something that should show nothing. Never an entropy source, and never in the product path.

1 · on the wire, 56 bytes
09a3616918ce61741b012d01a0c2f08ca761725820f822bf19b099a0c70db794bf8f8b96136798599bfa771f64670775cec96e9ba4e17200
2 · after COBS, 54 bytes — same colours, and the zeros each pointer stood for
a3616918ce61741b000001a0c2f08ca761725820f822bf19b099a0c70db794bf8f8b96136798599bfa771f64670775cec96e9ba4e172
3 · the same bytes, by field: a CBOR map and its CRC-16
a3616918ce61741b000001a0c2f08ca761725820f822bf19b099a0c70db794bf8f8b96136798599bfa771f64670775cec96e9ba4e172
keyvalue
i206
t1789976939687
rf822bf19b099a0c70db794bf8f8b96136798599bfa771f64670775cec96e9ba4

2  Channels

Every packet carries i. Dispatch on it before reading anything else: keys are scoped to a channel and are reused with different meanings on others.

inameclocknotes
0decay_ch0capture
1decay_ch1capture
2decay_ch2capture
8pps_ref_on_reference_clockreferencethe second-marker input as counted on the external reference timebase. Not wired on the bench instrument: the channel appears with an empty event array.
9pps_ref_on_core_clockcorethe same input on the core cycle counter. Not wired on the bench instrument: empty.
10gnss_pps_on_reference_clockreferencethe GNSS second marker on the external reference timebase, hardware-latched.
11gnss_pps_on_core_clockcorethe GNSS second marker read on the core cycle counter after the latch, so it carries interrupt latency. Superseded for anchoring by channel 13.
12reference_beatreferencea low-frequency beat between the two frequency references, hardware-latched on the reference timebase. Used to compare the two clocks against each other; not a measurement channel.
13gnss_ppscapturethe GNSS one-pulse-per-second edge captured on the same counter as the decays, so it shares their timebase
200healthcapturediagnostic, not measurement; a decoder may ignore it
205chaincapture
206controlcapture
250session_headercapture

Three clocks, and mixing them is the easy mistake. Only channels 0–2 and 13 share a timebase. Their ticks are not comparable with the others' without conversion.

clockrate, Hztickwraps after
capture150 000 0006.6667 ns28.6331 s
reference8 391 841119.16 ns511.8 s
core600 000 0001.6667 ns7.158 s

3  Reconstructing a timestamp

The value on the wire is the low 32 bits of the capture counter. The modular difference between consecutive events is the interval, and it is correct whenever the gap is shorter than one wrap — which it is for every decay interval at any usable rate.

interval_ticks = (t[k+1] - t[k]) & 0xFFFFFFFF

Worked, on real events

The first three events of a packet in the published null record.

e[0] = 630541745
e[1] = 631601455      (e[1] - e[0]) & 0xFFFFFFFF = 1 059 710 ticks = 7.065 ms
e[2] = 633426669      (e[2] - e[1]) & 0xFFFFFFFF = 1 825 214 ticks = 12.168 ms

For a gap longer than one wrap the packet's own t fixes the wrap count: the expected tick delta is Δt_ms × 1e-3 × 150e6, and the number of wraps is that minus the modular difference, divided by 232, rounded and clamped at zero.

6.667 ns is a quantisation step, not a timing accuracy. The detector front end contributes several microseconds of its own, which is three orders of magnitude larger. Nothing about absolute timing should be built on the step size.

4  Packet types

events — channels 0, 1, 2, 8, 9, 10, 11, 12, 13

Channels 0, 1, 2 and 13 are counted on the capture clock; 8 to 12 are not. Each channel's entry in `channels` names the clock its ticks belong to. A channel that is present with an empty `e` array is reporting that it saw nothing that second, which is different from the channel being absent.

keytypeunitmeaning
tuintmsthe instrument's unix time when the packet was built. Annotation only, never the measurement; it also fixes the wrap count of a long gap.
iuint—channel id
suint—status bits
earray of uintcapture ticksTHE MEASUREMENT: raw counter values, one per event, in order, low 32 bits. Nothing is derived on the instrument.

chain_anchor — channel 205

 

keytypeunitmeaning
iuint—205
nuint—anchor sequence: 0 at boot, +1 each anchor, strictly consecutive within a boot
kuint—kind
hbytes[32]—h_n = SHA-256(h_{n-1} || every link byte since anchor frame n-1 inclusive, excluding this frame). h_{-1} is 32 zero bytes.
ouintbyteslink bytes chained since boot before this anchor frame, i.e. its offset in the stream. The difference between two anchors' o, compared with the bytes actually counted, measures inserted or dropped bytes.
cuint—the secure element's monotonic counter after the increment made for this anchor; valid only when flag bit 3 is set. Never resettable.
fuint—flags
pkbytes[8]—first 8 bytes of SHA-256(0x04 || X || Y) of the signing public key; empty when there is none
quint—SD file sequence at emission
tuintmsinstrument unix time
buint—boot id: distinguishes two boots that report the same boot count
fwtext—build identifier (boot anchors only)
ihbytes[32]—SHA-256 of the firmware image read back from flash (boot anchors only)
iluintbyteslength of that image (boot anchors only)
rsttext—reset cause (boot anchors only)
bcuint—battery-backed boot count (boot anchors only)
snbytes[9]—secure element serial number (boot anchors only)
rtcuintsRTC epoch seconds (boot anchors only)

chain_signature — channel 205

Emitted only when the secure element is present and provisioned, i.e. anchor flag bit 4 is set. It follows its anchor by about 0.1 s, so it is usually several packets later in the stream. A missing signature for any anchor but the last is a verification failure; for the last it is the signature of a power cut.

keytypeunitmeaning
iuint—205
nuint—the anchor sequence number this signature covers
gbytes[64]—ECDSA-P256 signature, r || s, over SHA-256 of that anchor's CBOR payload exactly as framed: the map bytes only, without the CRC trailer

control — channel 206

A co-located control channel: same enclosure, temperature, supply and pipeline as the decay channels, but a conditioned opaque source, recorded so that any analysis can be run against something that should show nothing. Never an entropy source and never in the product path. Its absence from a record means the chip is unprovisioned or missing, never that it was quiet.

keytypeunitmeaning
iuint—206
tuintmsinstrument unix time
rbytes[32]—output of the secure element's conditioned random generator

health — channel 200

Documented field by field because an analyst building anything from the derived context table needs these, and 'documentation available on request' is the thing an interface control document exists to abolish. `scale` is the factor to MULTIPLY the wire value by to get the stated unit. Where it is absent the wire value is already in that unit. open_map stays true because the map is the instrument's diagnostic channel and grows: adding a counter must not break a decoder. `qes check` reports how many keys it skipped for that reason, so a growing gap is visible rather than silent.

keytypeunitmeaning
tuintmsthe instrument's own unix time when the packet was built. An annotation, never a measurement
iuint—200
suint—status bits, as on the event channels
fwtext—firmware build identifier, sent every second so any slice of a record names its build
qeintpsquantisation error of the NEXT pulse: the sawtooth correction
wkuintweekGPS week number of the next pulse
towuintmsGPS time of week of the NEXT pulse
fxuint—receiver fix type; 3 is a 3D fix
fokuint—receiver reports the fix usable
svuint—satellites tracked
nusuint—satellites used in the solution
tduint—time dilution of precision, x100 (multiply the wire value by 0.01)
pduint—position dilution of precision, x100 (multiply the wire value by 0.01)
hduint—horizontal dilution of precision, x100 (multiply the wire value by 0.01)
gduint—geometric dilution of precision, x100 (multiply the wire value by 0.01)
agcuint—receiver automatic gain control count, 0 to 8191
jamuint—receiver jamming indicator, 0 to 255
noiuintper msreceiver noise level
cxuintdBHzhighest carrier-to-noise over the satellites used
cmuintdBHzmean carrier-to-noise over the satellites used
tauintnsthe receiver's own estimate of its time accuracy; 0xFFFFFFFF means it reports none
tpcuint—pulse-timing messages seen: pulse liveness
latintdeglatitude, degrees x 1e7. For a stationary instrument, wander here is a multipath indicator that shows before the accuracy estimate does (multiply the wire value by 1e-07)
lonintdeglongitude, degrees x 1e7 (multiply the wire value by 1e-07)
altintmmheight above mean sea level; altitude sets the cosmic-ray background
helintmmheight above the ellipsoid
hacuintmmhorizontal accuracy estimate
vacuintmmvertical accuracy estimate
pruintPaambient pressure. Present only when the environmental sensor is fitted
tcintdegCAMBIENT temperature, centi-degrees. Distinct from tdie (multiply the wire value by 0.01)
rhuint%relative humidity, hundredths of a percent (multiply the wire value by 0.01)
tdieintdegCon-die temperature, centi-degrees. Self-heated to about 48 C; tracks ambient, is not ambient (multiply the wire value by 0.01)
ppsuintreference ticksthe GNSS pulse latched on the reference timebase
pdluintreference ticksticks since the previous pulse
pnuint—pulses captured since boot. THE CONTINUITY KEY: use it as the pulse index, never the position of an edge in an array
tmuintcapture ticksraw 16-bit capture count read at the pulse; a coherence check only, no domain transfer
btuintreference ticksclock-comparison beat edge
bdluintreference ticksticks since the previous accepted beat edge
bnuint—accepted beat edges since boot; 0 means the comparison is not running
bchuint—captures inside the holdoff, cumulative: chatter, not new edges
bfuintreference ticksfirst edge of the last completed beat burst
bluintreference tickslast edge of that burst
bnnuint—edge count of that burst
bsuintreference tickssum of (edge - bf) over that burst, so the centroid is bf + bs/bnn
bpuint—parity of that burst: which beat edge it was
lpsuint1/smain-loop iterations in the last second
lmxuintusworst single loop iteration in the last second
stkuintbyteslowest free stack ever seen, by paint and scan
hpuintbytesfree heap
upuintsuptime
qunuint—events anchored while the service gap exceeded half a capture span, summed over channels. Must stay 0
qgsuint—service periods within an eighth of the capture span: the real-time margin. Must stay 0
qrsuint—capture-ring flushes made to recover from persistent desync
qovuint—events the capture path discarded, summed over channels
qoluint—packet-FIFO overflows, summed over channels
lovuint—frames that exceeded the encode buffer and reached NEITHER sink. Must stay 0: non-zero means the record has holes
lswuint—USB writes that returned short because the host stopped consuming. Non-zero means the USB copy and the card copy are no longer byte-identical. Must stay 0
ltduintbytesbytes discarded with those short writes
crsuintbyteslength of an unread crash report from the previous boot, held in RAM. Non-zero means the instrument restarted on a fault and still holds the reason
sscuint—times the stack scan reached the top of RAM instead of finding its mark. Must stay 0
teeuint—encodes the instrument refused: a wrong map entry count, or a payload that would not fit. Must stay 0
qn0uint—events the capture path handed to channel 0's packet FIFO since boot
qn1uint—the same for channel 1
qn2uint—the same for channel 2
qls0uint—fine/coarse lockstep faults on channel 0. A desynced channel still counts correctly and only timestamps wrongly, so this is the only place a marginal input shows
qls1uint—the same for channel 1
qls2uint—the same for channel 2
qcs0uint—events on channel 0 whose coarse capture disagreed with the fine-only reconstruction by a whole fine span or more
qcs1uint—the same for channel 1
qcs2uint—the same for channel 2
qcs3uint—the same for the pulse channel
sbsuint—card writes deferred because the card was busy
sbfuint—writes forced after 2000 ms of deferral
lsduintusworst card-service visit in the last second
lgpuintusworst receiver-service visit in the last second
ltxuintusworst transmit visit in the last second
lqcuintusworst capture-service visit in the last second
sdduintbytescard bytes dropped by ring overflow; 0xFFFFFFFF means no card
sdfuintbytesfree space, measured at boot and rotation and decremented by writes
sdwuintbytesbytes written this boot
sdquint—file sequence number
chnuint—chain anchors emitted since boot
siguint—signatures that failed. Must stay 0 on a provisioned instrument; stays 0 on one without a chip because nothing is attempted, and the anchors' flag byte says which
gpruint—battery-backed general-purpose register read back after write: 0 means the register is not retaining

session_header — channel 250

What the instrument was when it started: build, reset cause, RTC time, SD file sequence.

keytypeunitmeaning
iuint—250

5  Rules a decoder can rely on

  1. Raw counters only on the wire: no derived time, rate or correction ever appears.
  2. Adding a key is allowed; renaming or repurposing one is not. Match on the key and old records stay readable.
  3. Derive rates from cumulative counters, never by differencing two packets: the packet cadence is 1 Hz and the events are not.
  4. Every lossy mechanism counts its own loss and reports it, so a gap can be attributed rather than guessed at.

6  What is not covered

A recording starts and stops between anchors, so the bytes before the first verified anchor and after the last are covered by no hash and can be altered without any check failing. qes verify --covered-range prints the range that is covered; qes recompute --signed-only restricts itself to it. Treat everything outside it as unattested.