Repository navigation
fix(builtin): decoders skip unknown fields and read absent fields as zero (1.0.0) - #238
Merged
Merged
Conversation
…zero (1.0.0) Three wire decisions that a 1.0.0 host would otherwise have frozen. Unknown fields. decodePointField read a varint before it knew the field, then reported "unknown", and parseFields skipped a second field. A field added to PointField by a later SDK would have broken PointCloud, GridMap and VoxelGrid decoding on every 1.0.0 host. parseFields now has one rule: false with nothing read means unknown and is skipped; false after reading means a known field is malformed and the decode fails. decodePointField checks the field number before it reads. Absent fields. A standard proto3 encoder omits a zero, so an absent field is zero. Decoders that started from the C++ struct defaults turned is_dense = false into true, height 0 into 1, PointField.count 0 into 1, a cone into a cylinder, and a transparent annotation colour into an opaque one. They now start from the proto3 zero values; the empty PointCloud payload agrees. Bytes written by the SDK's own serializers are unaffected. A NaN colour channel decodes as 0 instead of reaching an undefined cast. Log.category moves from field 5 to 7, with 5 and 6 reserved: they are file and line in foxglove.Log, so a Foxglove log put its file path in category. Only 1.0.0-dev.1 shipped field 5. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…o rule Review findings on the previous commit. An absent annotation thickness, circle diameter or font size decoded as 2, 1 and 14. They are ordinary proto3 scalars, and a thickness of 0 is a real value (a filled shape with no outline) that a standard encoder omits. They now decode as 0; colours still keep their default while the whole colour message is absent. deserializeBuiltinObject(kMeshMap) returned a default-constructed MeshMap (position_type FLOAT32) for the empty payload, while a payload that carries any other field and omits position_type is rejected. The empty payload is now rejected the same way. Tests: both of the above, a cone with only top_scale written, and a malformed PointField followed by a well-formed field. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
facontidavide
force-pushed
the
fix/decoder-forward-compat
branch
from
November 14, 2026 08:31
25d4cb5 to
ce88a64
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why now
These three are decoder and schema decisions that 1.0.0 would freeze. Fixing them later changes how already-valid bytes are read.
1. Unknown fields inside a
PointFielddecodePointFieldread a varint before it knew the field number, then returned "unknown", andparseFieldsskipped a second field. Adding any varint field toPJ.PointFieldin a later release would have brokenPointCloud,GridMapandVoxelGriddecoding on every 1.0.0 host.parseFieldsnow applies one rule to every codec:truefalse, nothing readfalse, something readBefore, the third case skipped the bytes that followed and could decode as something else. I audited the handlers of all 20 decoders for a read that precedes the field check;
decodePointFieldwas the only one.2. Absent fields are the proto3 zero value
A standard protobuf encoder omits a zero. Decoders that started from the C++ struct defaults read:
PointCloud.is_dense = falsetruePointCloud.height = 01PointField.count = 01CylinderPrimitive.top_scale = 0(a cone)1(a cylinder)a = 0They now start from the zero values, and the empty
PointCloudpayload agrees with them.ImageAnnotationscolours use the shared colour decoder. A NaN colour channel decodes as 0 instead of reaching an undefined float-to-integer cast.Bytes written by the SDK's own serializers are unaffected: they write every one of these fields.
Also in this class, found by a Codex review of this PR:
thickness, circlediameterorfont_sizedecoded as 2, 1 and 14. They now decode as 0. A thickness of 0 is a real value (a filled shape with no outline) that a standard encoder omits. A consumer that wants a default for 0 applies it when drawing.deserializeBuiltinObject(kMeshMap, ...)accepted the empty payload as a default mesh withposition_typeFLOAT32, while any other payload that omits the field is rejected. The empty payload is now rejected too.One exception remains, on purpose: a colour (or
Mesh3D.scale) that is absent as a whole submessage keeps its documented default.3.
Log.categorymoves from field 5 to 7Fields 5 and 6 are
fileandlineinfoxglove.Log, so a Foxglove-encoded log decoded its file path as the category. Both are now reserved. Only1.0.0-dev.1shipped field 5; a log encoded by adev.1build loses its category.Verification
./build.sh --debug && ./test.shgives 107/107 under ASAN. Pre-commit is clean.parseFieldscall sites and the hand-written loops and found no handler the new rule misclassifies. Its two findings are the second commit../test_sdk_install.sh(no packaging change).docs/builtin_type.mdnow states the two decode rules.🤖 Generated with Claude Code