Skip to content

0011. Build: pin nanopb at 0.4.9.1

Date: 2026-09-28

The nanopb runtime is vendored and the generator pinned at 0.4.9.1. The worry was that pinning would silently change the firmware already in the field; the evidence below, extracted verbatim from Tag Firmware Build Reproducibility, settled that it does not. The mismatch that made a pin necessary is in the build reproducibility investigation.

Evidence

The worry was that fixing on 0.4.9.1 would shift the .pb.* files away from what built the firmware now in the field, making the first commit of generated sources a silent behavioural change.

It does not. Both generators were run over six proto variants (the five distributed ones and bittag-legacy, distributed at the time of the test) from the repository's own .proto files and CombineFiles.cmake-combined options, and all 24 outputs compared:

  • 0.4.8 vs 0.4.9.1 -- identical but for the /* Generated by nanopb-... */ comment and some trailing blank lines. No declaration, field descriptor, size macro or PB_BIND entry differs in any of the 24 files compared.
  • 0.4.8 vs the tree's actual old generator (1f0c2e1, the 0.4.8-11-g1f0c2e1 snapshot) -- the only code change to nanopb_generator.py across those eleven commits is inside fields_declaration_cpp_lookup, emitted solely under --cpp-descriptors, which this project does not pass.

Moving to 0.4.9.1 from that snapshot also changes no runtime behaviour: pb_common.o and pb_encode.o are byte-identical when cross-compiled for cortex-m4 and cortex-m33, and the sole semantic difference -- a leak fix in pb_decode_ex under PB_ENABLE_MALLOC -- is in a function garbage-collected out of the linked images, which call plain pb_decode.