[api] Collapse the three protobuf decode virtuals into one

Every decodable message overrode up to three virtuals, one per wire
type, so each carried a five slot vtable and up to three functions
with their own prologue and return tails. The shared decode loop now
parses the payload for the wire type into a ProtoFieldValue and calls
a single decode_field() virtual with the tag, the field number and the
wire type; the generated override is one switch.

The switch key is chosen per target through PROTO_DECODE_KEY. Embedded
builds compile switches to compare chains (ESP-IDF passes
-fno-jump-tables), so they key on the full wire tag, one compare per
field with no separate wire type check. The host compiler builds a
jump table for the dense field number switch, so there the key is the
field number and PROTO_DECODE_GUARD rejects a mismatched wire type.
Both forms drop a field that arrives with a wire type it does not
declare, exactly as the per wire type virtuals did.

Per decodable message the vtable shrinks from 20 to 12 bytes on
xtensa and the extra decode functions fold into one; the shared loop
shrinks as well. Host instruction counts per decoded field are
unchanged apart from the guard compare, which replaces the prologue of
the separate function it used to call.
This commit is contained in:
J. Nick Koston
2026-09-07 15:24:32 +02:00
parent acb6f911e5
commit bcdb384bbe
6 changed files with 1273 additions and 1363 deletions
File diff suppressed because it is too large Load Diff
+59 -106
View File
@@ -424,8 +424,7 @@ class HelloRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class HelloResponse final : public ProtoMessage {
public:
@@ -469,7 +468,7 @@ class DisconnectRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class DisconnectResponse final : public ProtoMessage {
public:
@@ -845,8 +844,7 @@ class CoverCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_FAN
@@ -920,9 +918,7 @@ class FanCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_LIGHT
@@ -1018,9 +1014,7 @@ class LightCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_SENSOR
@@ -1125,8 +1119,7 @@ class SwitchCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_TEXT_SENSOR
@@ -1186,7 +1179,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SubscribeLogsResponse final : public ProtoMessage {
public:
@@ -1229,7 +1222,7 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
public:
@@ -1323,8 +1316,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
@@ -1365,7 +1357,7 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
class GetTimeRequest final : public ProtoMessage {
@@ -1394,7 +1386,7 @@ class DSTRule final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class ParsedTimezone final : public ProtoDecodableMessage {
public:
@@ -1407,8 +1399,7 @@ class ParsedTimezone final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class GetTimeResponse final : public ProtoDecodableMessage {
public:
@@ -1425,8 +1416,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#ifdef USE_API_USER_DEFINED_ACTIONS
class ListEntitiesServicesArgument final : public ProtoMessage {
@@ -1494,9 +1484,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class ExecuteServiceRequest final : public ProtoDecodableMessage {
public:
@@ -1519,9 +1507,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -1612,7 +1598,7 @@ class CameraImageRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_CLIMATE
@@ -1718,9 +1704,7 @@ class ClimateCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_WATER_HEATER
@@ -1792,8 +1776,7 @@ class WaterHeaterCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_NUMBER
@@ -1856,8 +1839,7 @@ class NumberCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_SELECT
@@ -1915,9 +1897,7 @@ class SelectCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_SIREN
@@ -1983,9 +1963,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_LOCK
@@ -2047,9 +2025,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_BUTTON
@@ -2085,8 +2061,7 @@ class ButtonCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_MEDIA_PLAYER
@@ -2172,9 +2147,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_BLUETOOTH_PROXY
@@ -2191,7 +2164,7 @@ class SubscribeBluetoothLEAdvertisementsRequest final : public ProtoDecodableMes
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothLERawAdvertisement final : public ProtoMessage {
public:
@@ -2245,7 +2218,7 @@ class BluetoothDeviceRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothDeviceConnectionResponse final : public ProtoMessage {
public:
@@ -2283,7 +2256,7 @@ class BluetoothGATTGetServicesRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTDescriptor final : public ProtoMessage {
public:
@@ -2394,7 +2367,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTReadResponse final : public ProtoMessage {
public:
@@ -2440,8 +2413,7 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
public:
@@ -2457,7 +2429,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public:
@@ -2475,8 +2447,7 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
public:
@@ -2493,7 +2464,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public:
@@ -2711,7 +2682,7 @@ class BluetoothScannerSetModeRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -2729,7 +2700,7 @@ class SubscribeVoiceAssistantRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantAudioSettings final : public ProtoMessage {
public:
@@ -2786,7 +2757,7 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantEventData final : public ProtoDecodableMessage {
public:
@@ -2797,7 +2768,7 @@ class VoiceAssistantEventData final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
public:
@@ -2813,8 +2784,7 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantAudio final : public ProtoDecodableMessage {
public:
@@ -2839,8 +2809,7 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
public:
@@ -2860,8 +2829,7 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
public:
@@ -2879,8 +2847,7 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantAnnounceFinished final : public ProtoMessage {
public:
@@ -2933,8 +2900,7 @@ class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage {
public:
@@ -2949,7 +2915,7 @@ class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class VoiceAssistantConfigurationResponse final : public ProtoMessage {
public:
@@ -2986,7 +2952,7 @@ class VoiceAssistantSetConfiguration final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
@@ -3046,9 +3012,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_TEXT
@@ -3109,9 +3073,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_DATETIME_DATE
@@ -3172,8 +3134,7 @@ class DateCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_DATETIME_TIME
@@ -3234,8 +3195,7 @@ class TimeCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_EVENT
@@ -3341,8 +3301,7 @@ class ValveCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_DATETIME_DATETIME
@@ -3399,8 +3358,7 @@ class DateTimeCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_UPDATE
@@ -3465,8 +3423,7 @@ class UpdateCommandRequest final : public CommandProtoMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
#endif
#ifdef USE_ZWAVE_PROXY
@@ -3490,7 +3447,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class ZWaveProxyRequest final : public ProtoDecodableMessage {
public:
@@ -3513,8 +3470,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class ZWaveProxyRequestResponse final : public ProtoMessage {
public:
@@ -3584,9 +3540,7 @@ class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class InfraredRFReceiveEvent final : public ProtoMessage {
public:
@@ -3657,7 +3611,7 @@ class SerialProxyConfigureRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SerialProxyDataReceived final : public ProtoMessage {
public:
@@ -3700,8 +3654,7 @@ class SerialProxyWriteRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage {
public:
@@ -3717,7 +3670,7 @@ class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
public:
@@ -3732,7 +3685,7 @@ class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SerialProxyGetModemPinsResponse final : public ProtoMessage {
public:
@@ -3770,7 +3723,7 @@ class SerialProxyRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class SerialProxyRequestResponse final : public ProtoMessage {
public:
@@ -3814,7 +3767,7 @@ class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage {
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
};
class BluetoothSetConnectionParamsResponse final : public ProtoMessage {
public:
+9 -13
View File
@@ -226,6 +226,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t field_type = tag & WIRE_TYPE_MASK;
uint32_t field_id = tag >> 3;
ptr += res.consumed;
ProtoFieldValue value;
switch (field_type) {
case WIRE_TYPE_VARINT: { // VarInt
@@ -234,10 +235,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_varint(field_id, res.value)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu64 "!", field_id,
static_cast<uint64_t>(res.value));
}
value.varint_ = res.value;
ptr += res.consumed;
break;
}
@@ -253,9 +251,8 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return;
}
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
}
value.ld_.data = ptr;
value.ld_.len = field_length;
ptr += field_length;
break;
}
@@ -264,16 +261,12 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t val;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// Protobuf fixed32 is little-endian — direct load on LE platforms
memcpy(&val, ptr, 4);
memcpy(&value.fixed32_, ptr, 4);
#else
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
value.fixed32_ = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
#endif
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
ptr += 4;
break;
}
@@ -281,6 +274,9 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
return;
}
if (!this->decode_field(tag, field_id, field_type, value)) {
ESP_LOGV(TAG, "Cannot decode field %" PRIu32 " with wire type %" PRIu32 "!", field_id, field_type);
}
}
}
+52 -32
View File
@@ -170,40 +170,59 @@ class ProtoVarInt {
class ProtoMessage;
class ProtoSize;
class ProtoLengthDelimited {
public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
// Generated decode_field() bodies switch on PROTO_DECODE_KEY and label each case with
// PROTO_DECODE_CASE. Embedded targets compile switches to compare chains (ESP-IDF passes
// -fno-jump-tables), so keying on the full wire tag costs one compare per field and needs no
// separate wire type check. The host compiler turns the dense field number switch into a jump
// table, so there the key is the field number and PROTO_DECODE_GUARD rejects the wrong wire type
// before the field is read. Both forms drop a field that arrives with a wire type it does not
// declare, which is what the per wire type virtuals did before.
#ifdef USE_HOST
#define PROTO_DECODE_KEY(tag, field_id) (field_id)
#define PROTO_DECODE_CASE(field_id, wire_type) (field_id)
#define PROTO_DECODE_GUARD(wire_type, expected) \
if ((wire_type) != (expected)) \
return false
#else
#define PROTO_DECODE_KEY(tag, field_id) (tag)
#define PROTO_DECODE_CASE(field_id, wire_type) (((field_id) << 3) | (wire_type))
#define PROTO_DECODE_GUARD(wire_type, expected) (void) 0
#endif
// Direct access to raw data without string allocation
const uint8_t *data() const { return this->value_; }
size_t size() const { return this->length_; }
/// Payload of one decoded field, handed to ProtoDecodableMessage::decode_field() together with
/// the field number and wire type. The wire type says which member is live; the accessors do not check.
/// Eight bytes with or without USE_API_VARINT64, so it travels in two registers on every target.
struct ProtoFieldValue {
union {
proto_varint_value_t varint_;
struct {
const uint8_t *data;
uint32_t len;
} ld_;
uint32_t fixed32_;
};
/// Decode the length-delimited data into a message instance.
proto_varint_value_t as_varint() const { return this->varint_; }
// Length-delimited accessors
const uint8_t *data() const { return this->ld_.data; }
size_t size() const { return this->ld_.len; }
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->ld_.data), this->ld_.len); }
/// Decode the length-delimited payload into a message instance.
/// Template preserves concrete type so decode() resolves statically.
template<typename T> void decode_to_message(T &msg) const;
template<typename T> void decode_to_message(T &msg) const { msg.decode(this->ld_.data, this->ld_.len); }
protected:
const uint8_t *const value_;
const size_t length_;
};
class Proto32Bit {
public:
explicit Proto32Bit(uint32_t value) : value_(value) {}
uint32_t as_fixed32() const { return this->value_; }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
// Fixed32 accessors
uint32_t as_fixed32() const { return this->fixed32_; }
int32_t as_sfixed32() const { return static_cast<int32_t>(this->fixed32_); }
float as_float() const {
union {
uint32_t raw;
float value;
} s{};
s.raw = this->value_;
s.raw = this->fixed32_;
return s.value;
}
protected:
const uint32_t value_;
};
// NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported
@@ -728,10 +747,16 @@ class ProtoDecodableMessage : public ProtoMessage {
protected:
~ProtoDecodableMessage() = default;
virtual bool decode_varint(uint32_t field_id, proto_varint_value_t value) { return false; }
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
// NOTE: decode_64bit removed - wire type 1 not supported
/// Store one decoded field. \p field_id and \p wire_type are \p tag split in two; the loop has all
/// three at hand, so passing them costs nothing and the generated switch keys on whichever form is
/// cheapest for the target (see PROTO_DECODE_KEY). \p wire_type selects the live ProtoFieldValue
/// member; overrides reject a field that arrived with a wire type other than the one it declares.
/// Return false for unknown or mismatched fields.
/// One virtual instead of one per wire type keeps each message's vtable at a single slot.
// NOTE: wire type 1 (64-bit fixed) is not supported
virtual bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) {
return false;
}
};
class ProtoSize {
@@ -951,11 +976,6 @@ template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(u
this->encode_optional_sub_message(field_id, T::calc_size_msg(&value), &value, &T::encode_msg);
}
// Template decode_to_message - preserves concrete type so decode() resolves statically
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
msg.decode(this->value_, this->length_);
}
template<typename T> const char *proto_enum_to_string(T value);
// ProtoService removed — its methods were inlined into APIConnection.
+86 -131
View File
@@ -229,12 +229,27 @@ class TypeInfo(ABC):
def class_member(self) -> str:
return f"{self.cpp_type} {self.field_name}{{{self.default_value}}};"
# decode_field() cases are keyed through the PROTO_DECODE_* macros in proto.h: embedded
# targets switch on the full wire tag, the host switches on the field number and guards
# the wire type. Either way a field that arrives with the wrong wire type falls through
# to "return false" instead of being read from the wrong ProtoFieldValue member.
def decode_case(self, wire_type: WireType, body: str) -> str:
"""Emit one decode_field() case for a field and the wire type it expects."""
return (
f"case PROTO_DECODE_CASE({self.number}, {int(wire_type)}):\n"
f" PROTO_DECODE_GUARD(wire_type, {int(wire_type)});\n"
f" {body}\n"
f" break;"
)
@property
def decode_varint_content(self) -> str:
content = self.decode_varint
if content is None:
return None
return f"case {self.number}: this->{self.field_name} = {content}; break;"
return self.decode_case(
WireType.VARINT, f"this->{self.field_name} = {content};"
)
decode_varint = None
@@ -243,7 +258,9 @@ class TypeInfo(ABC):
content = self.decode_length
if content is None:
return None
return f"case {self.number}: this->{self.field_name} = {content}; break;"
return self.decode_case(
WireType.LENGTH_DELIMITED, f"this->{self.field_name} = {content};"
)
decode_length = None
@@ -252,19 +269,12 @@ class TypeInfo(ABC):
content = self.decode_32bit
if content is None:
return None
return f"case {self.number}: this->{self.field_name} = {content}; break;"
return self.decode_case(
WireType.FIXED32, f"this->{self.field_name} = {content};"
)
decode_32bit = None
@property
def decode_64bit_content(self) -> str:
content = self.decode_64bit
if content is None:
return None
return f"case {self.number}: this->{self.field_name} = {content}; break;"
decode_64bit = None
# Mapping from encode_func to raw encode expression template.
# When a forced field has a single-byte tag, the code generator emits
# write_raw_byte(tag) + raw encode instead of the full encode_* method,
@@ -638,7 +648,6 @@ class DoubleType(FixedSizeTypeMixin, TypeInfo):
# Unsupported but defined for completeness
cpp_type = "double"
default_value = "0.0"
decode_64bit = "value.as_double()"
encode_func = "encode_double"
wire_type = WireType.FIXED64 # Uses wire type 1 according to protobuf spec
@@ -693,7 +702,7 @@ class Int64Type(VarintTypeMixin, TypeInfo):
cpp_type = "int64_t"
_varint_max_bits = 64
default_value = "0"
decode_varint = "static_cast<int64_t>(value)"
decode_varint = "static_cast<int64_t>(value.as_varint())"
encode_func = "encode_int64"
wire_type = WireType.VARINT # Uses wire type 0
@@ -714,7 +723,7 @@ class UInt64Type(VarintTypeMixin, TypeInfo):
cpp_type = "uint64_t"
_varint_max_bits = 64
default_value = "0"
decode_varint = "value"
decode_varint = "value.as_varint()"
encode_func = "encode_uint64"
wire_type = WireType.VARINT # Uses wire type 0
@@ -749,7 +758,7 @@ class Int32Type(VarintTypeMixin, TypeInfo):
cpp_type = "int32_t"
_varint_max_bits = 64 # int32 is sign-extended to 64 bits in protobuf
default_value = "0"
decode_varint = "static_cast<int32_t>(value)"
decode_varint = "static_cast<int32_t>(value.as_varint())"
encode_func = "encode_int32"
wire_type = WireType.VARINT # Uses wire type 0
@@ -769,7 +778,6 @@ class Int32Type(VarintTypeMixin, TypeInfo):
class Fixed64Type(FixedSizeTypeMixin, TypeInfo):
cpp_type = "uint64_t"
default_value = "0"
decode_64bit = "value.as_fixed64()"
encode_func = "encode_fixed64"
wire_type = WireType.FIXED64 # Uses wire type 1
@@ -824,7 +832,7 @@ class BoolType(VarintTypeMixin, TypeInfo):
_varint_max_bits = 1
cpp_type = "bool"
default_value = "false"
decode_varint = "value != 0"
decode_varint = "value.as_varint() != 0"
encode_func = "encode_bool"
wire_type = WireType.VARINT # Uses wire type 0
@@ -1014,13 +1022,15 @@ class MessageType(TypeInfo):
# decode_to_message() cannot report failure, so setting the flag
# afterwards only documents intent; a status-returning decode could
# gate it for real without touching callers.
return (
f"case {self.number}:\n"
f" value.decode_to_message(this->{self.field_name});\n"
f" this->has_{self.name} = true;\n"
f" break;"
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"value.decode_to_message(this->{self.field_name});\n"
f" this->has_{self.name} = true;",
)
return f"case {self.number}: value.decode_to_message(this->{self.field_name}); break;"
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"value.decode_to_message(this->{self.field_name});",
)
def dump(self, name: str) -> str:
return f"{name}.dump_to(out);"
@@ -1216,11 +1226,11 @@ class PointerToBytesBufferType(PointerToBufferTypeBase):
@property
def decode_length_content(self) -> str | None:
return f"""case {self.number}: {{
this->{self.field_name} = value.data();
this->{self.field_name}_len = value.size();
break;
}}"""
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"this->{self.field_name} = value.data();\n"
f" this->{self.field_name}_len = value.size();",
)
def dump(self, name: str) -> str:
return (
@@ -1281,10 +1291,10 @@ class PointerToStringBufferType(PointerToBufferTypeBase):
@property
def decode_length_content(self) -> str | None:
return f"""case {self.number}: {{
this->{self.field_name} = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}}"""
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"this->{self.field_name} = StringRef(reinterpret_cast<const char *>(value.data()), value.size());",
)
def dump(self, name: str) -> str:
# Not used since we use dump_field, but required by abstract base class
@@ -1355,12 +1365,12 @@ class PackedBufferTypeInfo(TypeInfo):
@property
def decode_length_content(self) -> str:
"""Store pointer to buffer and calculate count of packed varints."""
return f"""case {self.number}: {{
this->{self.field_name}_data_ = value.data();
this->{self.field_name}_length_ = value.size();
this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size());
break;
}}"""
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"this->{self.field_name}_data_ = value.data();\n"
f" this->{self.field_name}_length_ = value.size();\n"
f" this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size());",
)
@property
def encode_content(self) -> str:
@@ -1446,16 +1456,13 @@ class FixedArrayBytesType(TypeInfo):
@property
def decode_length_content(self) -> str:
o = f"case {self.number}: {{\n"
o += " const std::string &data_str = value.as_string();\n"
o += f" this->{self.field_name}_len = data_str.size();\n"
o += f" if (this->{self.field_name}_len > {self.array_size}) {{\n"
o += f" this->{self.field_name}_len = {self.array_size};\n"
o += " }\n"
o += f" memcpy(this->{self.field_name}, data_str.data(), this->{self.field_name}_len);\n"
o += " break;\n"
o += "}"
return o
body = "const std::string &data_str = value.as_string();\n"
body += f" this->{self.field_name}_len = data_str.size();\n"
body += f" if (this->{self.field_name}_len > {self.array_size}) {{\n"
body += f" this->{self.field_name}_len = {self.array_size};\n"
body += " }\n"
body += f" memcpy(this->{self.field_name}, data_str.data(), this->{self.field_name}_len);"
return self.decode_case(WireType.LENGTH_DELIMITED, body)
@property
def encode_content(self) -> str:
@@ -1518,7 +1525,7 @@ class UInt32Type(VarintTypeMixin, TypeInfo):
cpp_type = "uint32_t"
_varint_max_bits = 32
default_value = "0"
decode_varint = "value"
decode_varint = "value.as_varint()"
encode_func = "encode_uint32"
wire_type = WireType.VARINT # Uses wire type 0
@@ -1547,7 +1554,7 @@ class EnumType(VarintTypeMixin, TypeInfo):
@property
def decode_varint(self) -> str:
return f"static_cast<{self.cpp_type}>(value)"
return f"static_cast<{self.cpp_type}>(value.as_varint())"
default_value = ""
wire_type = WireType.VARINT # Uses wire type 0
@@ -1620,7 +1627,6 @@ class SFixed32Type(FixedSizeTypeMixin, TypeInfo):
class SFixed64Type(FixedSizeTypeMixin, TypeInfo):
cpp_type = "int64_t"
default_value = "0"
decode_64bit = "value.as_sfixed64()"
encode_func = "encode_sfixed64"
wire_type = WireType.FIXED64 # Uses wire type 1
@@ -1647,7 +1653,7 @@ class SInt32Type(VarintTypeMixin, TypeInfo):
cpp_type = "int32_t"
_varint_max_bits = 32 # zigzag encoding keeps it 32-bit
default_value = "0"
decode_varint = "decode_zigzag32(static_cast<uint32_t>(value))"
decode_varint = "decode_zigzag32(static_cast<uint32_t>(value.as_varint()))"
encode_func = "encode_sint32"
wire_type = WireType.VARINT # Uses wire type 0
@@ -1668,7 +1674,7 @@ class SInt64Type(VarintTypeMixin, TypeInfo):
cpp_type = "int64_t"
_varint_max_bits = 64
default_value = "0"
decode_varint = "decode_zigzag64(value)"
decode_varint = "decode_zigzag64(value.as_varint())"
encode_func = "encode_sint64"
wire_type = WireType.VARINT # Uses wire type 0
@@ -2138,8 +2144,8 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_varint
if content is None:
return None
return (
f"case {self.number}: this->{self.field_name}.push_back({content}); break;"
return self.decode_case(
WireType.VARINT, f"this->{self.field_name}.push_back({content});"
)
@property
@@ -2150,11 +2156,15 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_length
if content is None and isinstance(self._ti, MessageType):
# Special handling for non-template message decoding
return f"case {self.number}: this->{self.field_name}.emplace_back(); value.decode_to_message(this->{self.field_name}.back()); break;"
return self.decode_case(
WireType.LENGTH_DELIMITED,
f"this->{self.field_name}.emplace_back();\n"
f" value.decode_to_message(this->{self.field_name}.back());",
)
if content is None:
return None
return (
f"case {self.number}: this->{self.field_name}.push_back({content}); break;"
return self.decode_case(
WireType.LENGTH_DELIMITED, f"this->{self.field_name}.push_back({content});"
)
@property
@@ -2165,20 +2175,8 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_32bit
if content is None:
return None
return (
f"case {self.number}: this->{self.field_name}.push_back({content}); break;"
)
@property
def decode_64bit_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_64bit
if content is None:
return None
return (
f"case {self.number}: this->{self.field_name}.push_back({content}); break;"
return self.decode_case(
WireType.FIXED32, f"this->{self.field_name}.push_back({content});"
)
@property
@@ -2595,10 +2593,7 @@ def build_message_type(
) -> tuple[str, str, str]:
public_content: list[str] = []
protected_content: list[str] = []
decode_varint: list[str] = []
decode_length: list[str] = []
decode_32bit: list[str] = []
decode_64bit: list[str] = []
decode: list[str] = []
encode: list[str] = []
dump: list[str] = []
size_calc: list[str] = []
@@ -2727,22 +2722,13 @@ def build_message_type(
if field.options.HasExtension(pb.field_ifdef):
field_ifdef = field.options.Extensions[pb.field_ifdef]
if ti.decode_varint_content:
decode_varint.extend(
wrap_with_ifdef(ti.decode_varint_content, field_ifdef)
)
if ti.decode_length_content:
decode_length.extend(
wrap_with_ifdef(ti.decode_length_content, field_ifdef)
)
if ti.decode_32bit_content:
decode_32bit.extend(
wrap_with_ifdef(ti.decode_32bit_content, field_ifdef)
)
if ti.decode_64bit_content:
decode_64bit.extend(
wrap_with_ifdef(ti.decode_64bit_content, field_ifdef)
)
for case in (
ti.decode_varint_content,
ti.decode_length_content,
ti.decode_32bit_content,
):
if case:
decode.extend(wrap_with_ifdef(case, field_ifdef))
if ti.dump_content:
# Check for field_ifdef option for dump as well
field_ifdef = None
@@ -2752,49 +2738,18 @@ def build_message_type(
dump.extend(wrap_with_ifdef(ti.dump_content, field_ifdef))
cpp = ""
if decode_varint:
o = f"bool {desc.name}::decode_varint(uint32_t field_id, proto_varint_value_t value) {{\n"
o += " switch (field_id) {\n"
o += indent("\n".join(decode_varint), " ") + "\n"
if decode:
# One virtual per message: the shared decode loop parses the payload for the wire
# type and hands it over with the tag, so a single switch covers every field.
o = f"bool {desc.name}::decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) {{\n"
o += " switch (PROTO_DECODE_KEY(tag, field_id)) {\n"
o += indent("\n".join(decode), " ") + "\n"
o += " default: return false;\n"
o += " }\n"
o += " return true;\n"
o += "}\n"
cpp += o
prot = "bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;"
protected_content.insert(0, prot)
if decode_length:
o = f"bool {desc.name}::decode_length(uint32_t field_id, ProtoLengthDelimited value) {{\n"
o += " switch (field_id) {\n"
o += indent("\n".join(decode_length), " ") + "\n"
o += " default: return false;\n"
o += " }\n"
o += " return true;\n"
o += "}\n"
cpp += o
prot = "bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;"
protected_content.insert(0, prot)
if decode_32bit:
o = f"bool {desc.name}::decode_32bit(uint32_t field_id, Proto32Bit value) {{\n"
o += " switch (field_id) {\n"
o += indent("\n".join(decode_32bit), " ") + "\n"
o += " default: return false;\n"
o += " }\n"
o += " return true;\n"
o += "}\n"
cpp += o
prot = "bool decode_32bit(uint32_t field_id, Proto32Bit value) override;"
protected_content.insert(0, prot)
if decode_64bit:
o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64Bit value) {{\n"
o += " switch (field_id) {\n"
o += indent("\n".join(decode_64bit), " ") + "\n"
o += " default: return false;\n"
o += " }\n"
o += " return true;\n"
o += "}\n"
cpp += o
prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;"
prot = "bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;"
protected_content.insert(0, prot)
# Generate custom decode() override for messages with FixedVector fields
@@ -18,7 +18,9 @@ sys.path.insert(0, str(Path(__file__).parents[4] / "script" / "api_protobuf"))
import aioesphomeapi.api_options_pb2 as pb # noqa: E402
from api_protobuf import ( # noqa: E402
MAX_MESSAGE_ID,
SOURCE_CLIENT,
_make_ifdef_line,
build_message_type,
create_field_type_info,
get_varint64_ifdef,
validate_message_id,
@@ -182,3 +184,68 @@ def test_multi_byte_tag_fixed32_falls_back_to_the_generic_helper(
content = _encode_field(field_type, number=16)
assert "write_tag_and_fixed32" not in content, content
assert content.startswith("pos = ProtoEncode::encode_"), content
def _decode_cases(field_type: int, number: int) -> list[str]:
"""Return the decode_field() case lines the generator emits for one decoded field."""
field = descriptor_pb2.FieldDescriptorProto(
name="value", number=number, type=field_type
)
ti = create_field_type_info(field, needs_decode=True, needs_encode=False)
return [
case
for case in (
ti.decode_varint_content,
ti.decode_length_content,
ti.decode_32bit_content,
)
if case
]
UINT32_T = descriptor_pb2.FieldDescriptorProto.TYPE_UINT32
STRING_T = descriptor_pb2.FieldDescriptorProto.TYPE_STRING
BOOL_T = descriptor_pb2.FieldDescriptorProto.TYPE_BOOL
@pytest.mark.parametrize(
("field_type", "number", "wire_type", "accessor"),
[
(UINT32_T, 2, 0, "value.as_varint()"),
(BOOL_T, 3, 0, "value.as_varint() != 0"),
(STRING_T, 1, 2, "value.data()"),
(FLOAT, 4, 5, "value.as_float()"),
(FIXED32, 5, 5, "value.as_fixed32()"),
],
)
def test_decode_cases_carry_field_number_and_wire_type(
field_type: int, number: int, wire_type: int, accessor: str
) -> None:
"""Each decoded field yields one case keyed on its number and declared wire type."""
cases = _decode_cases(field_type, number)
assert len(cases) == 1, cases
lines = cases[0].splitlines()
assert lines[0] == f"case PROTO_DECODE_CASE({number}, {wire_type}):", cases[0]
assert lines[1].strip() == f"PROTO_DECODE_GUARD(wire_type, {wire_type});", cases[0]
assert accessor in cases[0], cases[0]
def test_message_gets_a_single_decode_field_override() -> None:
"""All wire types of a decoded message land in one decode_field() switch."""
desc = descriptor_pb2.DescriptorProto(name="Mixed")
desc.field.add(name="name", number=1, type=STRING_T)
desc.field.add(name="count", number=2, type=UINT32_T)
desc.field.add(name="level", number=3, type=FLOAT)
header, cpp, _ = build_message_type(desc, {}, {"Mixed": SOURCE_CLIENT})
decl = "bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;"
assert header.count(decl) == 1
assert "decode_varint" not in header and "decode_length" not in header
assert (
cpp.count(
"bool Mixed::decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) {"
)
== 1
)
assert "switch (PROTO_DECODE_KEY(tag, field_id)) {" in cpp
for number, wire_type in ((1, 2), (2, 0), (3, 5)):
assert f"case PROTO_DECODE_CASE({number}, {wire_type}):" in cpp, cpp