- Fix get_varint64_ifdef() to return unconditional guard when any 64-bit
varint field has no ifdef (None in ifdefs set)
- Wrap USE_API_VARINT64 define with #ifndef to avoid redefinition warnings
when esphome/core/defines.h also defines it (test/IDE builds)
- Clarify proto.h comment about uint32_t shift behavior at byte 4
- Add namespace to generated api_pb2_defines.h for linter compliance
USE_API_VARINT64 was only defined in api_pb2.h, but proto.cpp (where
decode() and parse_wide() live) includes proto.h directly. This caused:
1. parse_wide() not compiled in proto.cpp (guarded by #ifdef)
2. decode() used 32-bit-only parse(), returning {} for varints > 5 bytes
3. ODR violation: ProtoVarInt was 4 bytes in proto.cpp but 8 bytes in
api_pb2*.cpp translation units
For bluetooth_proxy, 48-bit BLE addresses encode as 7-byte varints.
The failed parse caused decode() to return early, leaving request_type
at its default value of 0 (BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT),
producing spurious "V1 connections removed" errors.
Fix: Generate api_pb2_defines.h with the USE_API_VARINT64 define and
include it from proto.h, ensuring all translation units see a consistent
ProtoVarInt layout.
On 32-bit platforms (ESP32 Xtensa), 64-bit shifts in varint parsing
compile to __ashldi3 library calls. Since the vast majority of protobuf
varint fields (message types, sizes, enum values, sensor readings) fit
in 4 bytes, the 64-bit arithmetic is unnecessary overhead on the common
path.
Split parse() into two phases:
- Bytes 0-3: uint32_t loop with native 32-bit shifts (0, 7, 14, 21)
- Bytes 4-9: noinline parse_wide_() with uint64_t, only for BLE
addresses and other 64-bit fields
The code generator auto-detects which proto messages use int64/uint64/
sint64 fields and emits USE_API_VARINT64 conditionally. On non-BLE
configs, parse_wide_() and the 64-bit accessors (as_uint64, as_int64,
as_sint64) are compiled out entirely.
Saves ~40 bytes flash on non-BLE configs. Benchmark shows 25-50%
faster parsing for 1-4 byte varints (the common case).
Pass ProtoWriteBuffer by reference instead of by value in virtual
encode() methods. This avoids copying both buffer_ and pos_ pointers
for every encode call - only a single pointer is passed.
Also simplifies encode_message since child writes directly advance
the shared pos_. Adds debug_check_size_ to verify calculate_size
matches actual encode output.
ProtoSize::calculate_size() already computes the exact encoded size
before encode() runs and is the boundary validation. The buffer is
pre-sized to match. Since the buffer size is always correct,
push_back() capacity checks on every byte are redundant overhead.
Rename ProtoWriteBuffer to ProtoWritePreSizedBuffer to document the
contract. Write through a raw uint8_t* pointer instead of push_back().
Pre-resize the buffer to include payload space before encoding.
Add ESPHOME_DEBUG_API bounds checks to validate writes stay within the
pre-sized region during development and integration testing.
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>