[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 #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class HelloResponse final : public ProtoMessage { class HelloResponse final : public ProtoMessage {
public: public:
@@ -469,7 +468,7 @@ class DisconnectRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class DisconnectResponse final : public ProtoMessage {
public: public:
@@ -845,8 +844,7 @@ class CoverCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
@@ -920,9 +918,7 @@ class FanCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
@@ -1018,9 +1014,7 @@ class LightCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
@@ -1125,8 +1119,7 @@ class SwitchCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
@@ -1186,7 +1179,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class SubscribeLogsResponse final : public ProtoMessage {
public: public:
@@ -1229,7 +1222,7 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
public: public:
@@ -1323,8 +1316,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
@@ -1365,7 +1357,7 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
#endif #endif
protected: 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 #endif
class GetTimeRequest final : public ProtoMessage { class GetTimeRequest final : public ProtoMessage {
@@ -1394,7 +1386,7 @@ class DSTRule final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class ParsedTimezone final : public ProtoDecodableMessage {
public: public:
@@ -1407,8 +1399,7 @@ class ParsedTimezone final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class GetTimeResponse final : public ProtoDecodableMessage { class GetTimeResponse final : public ProtoDecodableMessage {
public: public:
@@ -1425,8 +1416,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
#ifdef USE_API_USER_DEFINED_ACTIONS #ifdef USE_API_USER_DEFINED_ACTIONS
class ListEntitiesServicesArgument final : public ProtoMessage { class ListEntitiesServicesArgument final : public ProtoMessage {
@@ -1494,9 +1484,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
class ExecuteServiceRequest final : public ProtoDecodableMessage { class ExecuteServiceRequest final : public ProtoDecodableMessage {
public: public:
@@ -1519,9 +1507,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -1612,7 +1598,7 @@ class CameraImageRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
@@ -1718,9 +1704,7 @@ class ClimateCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_WATER_HEATER #ifdef USE_WATER_HEATER
@@ -1792,8 +1776,7 @@ class WaterHeaterCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
@@ -1856,8 +1839,7 @@ class NumberCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
@@ -1915,9 +1897,7 @@ class SelectCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_SIREN #ifdef USE_SIREN
@@ -1983,9 +1963,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
@@ -2047,9 +2025,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
@@ -2085,8 +2061,7 @@ class ButtonCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
@@ -2172,9 +2147,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
@@ -2191,7 +2164,7 @@ class SubscribeBluetoothLEAdvertisementsRequest final : public ProtoDecodableMes
#endif #endif
protected: 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 { class BluetoothLERawAdvertisement final : public ProtoMessage {
public: public:
@@ -2245,7 +2218,7 @@ class BluetoothDeviceRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothDeviceConnectionResponse final : public ProtoMessage {
public: public:
@@ -2283,7 +2256,7 @@ class BluetoothGATTGetServicesRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothGATTDescriptor final : public ProtoMessage {
public: public:
@@ -2394,7 +2367,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothGATTReadResponse final : public ProtoMessage {
public: public:
@@ -2440,8 +2413,7 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
public: public:
@@ -2457,7 +2429,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public: public:
@@ -2475,8 +2447,7 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
public: public:
@@ -2493,7 +2464,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public: public:
@@ -2711,7 +2682,7 @@ class BluetoothScannerSetModeRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
@@ -2729,7 +2700,7 @@ class SubscribeVoiceAssistantRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class VoiceAssistantAudioSettings final : public ProtoMessage {
public: public:
@@ -2786,7 +2757,7 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class VoiceAssistantEventData final : public ProtoDecodableMessage {
public: public:
@@ -2797,7 +2768,7 @@ class VoiceAssistantEventData final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
public: public:
@@ -2813,8 +2784,7 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class VoiceAssistantAudio final : public ProtoDecodableMessage { class VoiceAssistantAudio final : public ProtoDecodableMessage {
public: public:
@@ -2839,8 +2809,7 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
public: public:
@@ -2860,8 +2829,7 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
public: public:
@@ -2879,8 +2847,7 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class VoiceAssistantAnnounceFinished final : public ProtoMessage { class VoiceAssistantAnnounceFinished final : public ProtoMessage {
public: public:
@@ -2933,8 +2900,7 @@ class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage { class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage {
public: public:
@@ -2949,7 +2915,7 @@ class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class VoiceAssistantConfigurationResponse final : public ProtoMessage {
public: public:
@@ -2986,7 +2952,7 @@ class VoiceAssistantSetConfiguration final : public ProtoDecodableMessage {
#endif #endif
protected: 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 #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
@@ -3046,9 +3012,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
@@ -3109,9 +3073,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
@@ -3172,8 +3134,7 @@ class DateCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
@@ -3234,8 +3195,7 @@ class TimeCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
@@ -3341,8 +3301,7 @@ class ValveCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
@@ -3399,8 +3358,7 @@ class DateTimeCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
@@ -3465,8 +3423,7 @@ class UpdateCommandRequest final : public CommandProtoMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue value) override;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
#endif #endif
#ifdef USE_ZWAVE_PROXY #ifdef USE_ZWAVE_PROXY
@@ -3490,7 +3447,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class ZWaveProxyRequest final : public ProtoDecodableMessage {
public: public:
@@ -3513,8 +3470,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class ZWaveProxyRequestResponse final : public ProtoMessage { class ZWaveProxyRequestResponse final : public ProtoMessage {
public: public:
@@ -3584,9 +3540,7 @@ class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
#endif #endif
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_field(uint32_t tag, uint32_t field_id, uint32_t wire_type, ProtoFieldValue 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;
}; };
class InfraredRFReceiveEvent final : public ProtoMessage { class InfraredRFReceiveEvent final : public ProtoMessage {
public: public:
@@ -3657,7 +3611,7 @@ class SerialProxyConfigureRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class SerialProxyDataReceived final : public ProtoMessage {
public: public:
@@ -3700,8 +3654,7 @@ class SerialProxyWriteRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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;
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
}; };
class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage { class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage {
public: public:
@@ -3717,7 +3670,7 @@ class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
public: public:
@@ -3732,7 +3685,7 @@ class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class SerialProxyGetModemPinsResponse final : public ProtoMessage {
public: public:
@@ -3770,7 +3723,7 @@ class SerialProxyRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class SerialProxyRequestResponse final : public ProtoMessage {
public: public:
@@ -3814,7 +3767,7 @@ class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage {
#endif #endif
protected: 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 { class BluetoothSetConnectionParamsResponse final : public ProtoMessage {
public: 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_type = tag & WIRE_TYPE_MASK;
uint32_t field_id = tag >> 3; uint32_t field_id = tag >> 3;
ptr += res.consumed; ptr += res.consumed;
ProtoFieldValue value;
switch (field_type) { switch (field_type) {
case WIRE_TYPE_VARINT: { // VarInt 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)); ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return; return;
} }
if (!this->decode_varint(field_id, res.value)) { value.varint_ = res.value;
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu64 "!", field_id,
static_cast<uint64_t>(res.value));
}
ptr += res.consumed; ptr += res.consumed;
break; 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)); ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return; return;
} }
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) { value.ld_.data = ptr;
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id); value.ld_.len = field_length;
}
ptr += field_length; ptr += field_length;
break; 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)); ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return; return;
} }
uint32_t val;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// Protobuf fixed32 is little-endian — direct load on LE platforms // Protobuf fixed32 is little-endian — direct load on LE platforms
memcpy(&val, ptr, 4); memcpy(&value.fixed32_, ptr, 4);
#else #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 #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; ptr += 4;
break; 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)); ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
return; 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 ProtoMessage;
class ProtoSize; class ProtoSize;
class ProtoLengthDelimited { // Generated decode_field() bodies switch on PROTO_DECODE_KEY and label each case with
public: // PROTO_DECODE_CASE. Embedded targets compile switches to compare chains (ESP-IDF passes
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} // -fno-jump-tables), so keying on the full wire tag costs one compare per field and needs no
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } // 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 /// Payload of one decoded field, handed to ProtoDecodableMessage::decode_field() together with
const uint8_t *data() const { return this->value_; } /// the field number and wire type. The wire type says which member is live; the accessors do not check.
size_t size() const { return this->length_; } /// 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 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: // Fixed32 accessors
const uint8_t *const value_; uint32_t as_fixed32() const { return this->fixed32_; }
const size_t length_; int32_t as_sfixed32() const { return static_cast<int32_t>(this->fixed32_); }
};
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_); }
float as_float() const { float as_float() const {
union { union {
uint32_t raw; uint32_t raw;
float value; float value;
} s{}; } s{};
s.raw = this->value_; s.raw = this->fixed32_;
return s.value; return s.value;
} }
protected:
const uint32_t value_;
}; };
// NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported // NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported
@@ -728,10 +747,16 @@ class ProtoDecodableMessage : public ProtoMessage {
protected: protected:
~ProtoDecodableMessage() = default; ~ProtoDecodableMessage() = default;
virtual bool decode_varint(uint32_t field_id, proto_varint_value_t value) { return false; } /// Store one decoded field. \p field_id and \p wire_type are \p tag split in two; the loop has all
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; } /// three at hand, so passing them costs nothing and the generated switch keys on whichever form is
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; } /// cheapest for the target (see PROTO_DECODE_KEY). \p wire_type selects the live ProtoFieldValue
// NOTE: decode_64bit removed - wire type 1 not supported /// 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 { 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); 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); template<typename T> const char *proto_enum_to_string(T value);
// ProtoService removed — its methods were inlined into APIConnection. // ProtoService removed — its methods were inlined into APIConnection.
+86 -131
View File
@@ -229,12 +229,27 @@ class TypeInfo(ABC):
def class_member(self) -> str: def class_member(self) -> str:
return f"{self.cpp_type} {self.field_name}{{{self.default_value}}};" 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 @property
def decode_varint_content(self) -> str: def decode_varint_content(self) -> str:
content = self.decode_varint content = self.decode_varint
if content is None: if content is None:
return 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 decode_varint = None
@@ -243,7 +258,9 @@ class TypeInfo(ABC):
content = self.decode_length content = self.decode_length
if content is None: if content is None:
return 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 decode_length = None
@@ -252,19 +269,12 @@ class TypeInfo(ABC):
content = self.decode_32bit content = self.decode_32bit
if content is None: if content is None:
return 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 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. # Mapping from encode_func to raw encode expression template.
# When a forced field has a single-byte tag, the code generator emits # 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, # 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 # Unsupported but defined for completeness
cpp_type = "double" cpp_type = "double"
default_value = "0.0" default_value = "0.0"
decode_64bit = "value.as_double()"
encode_func = "encode_double" encode_func = "encode_double"
wire_type = WireType.FIXED64 # Uses wire type 1 according to protobuf spec wire_type = WireType.FIXED64 # Uses wire type 1 according to protobuf spec
@@ -693,7 +702,7 @@ class Int64Type(VarintTypeMixin, TypeInfo):
cpp_type = "int64_t" cpp_type = "int64_t"
_varint_max_bits = 64 _varint_max_bits = 64
default_value = "0" default_value = "0"
decode_varint = "static_cast<int64_t>(value)" decode_varint = "static_cast<int64_t>(value.as_varint())"
encode_func = "encode_int64" encode_func = "encode_int64"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -714,7 +723,7 @@ class UInt64Type(VarintTypeMixin, TypeInfo):
cpp_type = "uint64_t" cpp_type = "uint64_t"
_varint_max_bits = 64 _varint_max_bits = 64
default_value = "0" default_value = "0"
decode_varint = "value" decode_varint = "value.as_varint()"
encode_func = "encode_uint64" encode_func = "encode_uint64"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -749,7 +758,7 @@ class Int32Type(VarintTypeMixin, TypeInfo):
cpp_type = "int32_t" cpp_type = "int32_t"
_varint_max_bits = 64 # int32 is sign-extended to 64 bits in protobuf _varint_max_bits = 64 # int32 is sign-extended to 64 bits in protobuf
default_value = "0" default_value = "0"
decode_varint = "static_cast<int32_t>(value)" decode_varint = "static_cast<int32_t>(value.as_varint())"
encode_func = "encode_int32" encode_func = "encode_int32"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -769,7 +778,6 @@ class Int32Type(VarintTypeMixin, TypeInfo):
class Fixed64Type(FixedSizeTypeMixin, TypeInfo): class Fixed64Type(FixedSizeTypeMixin, TypeInfo):
cpp_type = "uint64_t" cpp_type = "uint64_t"
default_value = "0" default_value = "0"
decode_64bit = "value.as_fixed64()"
encode_func = "encode_fixed64" encode_func = "encode_fixed64"
wire_type = WireType.FIXED64 # Uses wire type 1 wire_type = WireType.FIXED64 # Uses wire type 1
@@ -824,7 +832,7 @@ class BoolType(VarintTypeMixin, TypeInfo):
_varint_max_bits = 1 _varint_max_bits = 1
cpp_type = "bool" cpp_type = "bool"
default_value = "false" default_value = "false"
decode_varint = "value != 0" decode_varint = "value.as_varint() != 0"
encode_func = "encode_bool" encode_func = "encode_bool"
wire_type = WireType.VARINT # Uses wire type 0 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 # decode_to_message() cannot report failure, so setting the flag
# afterwards only documents intent; a status-returning decode could # afterwards only documents intent; a status-returning decode could
# gate it for real without touching callers. # gate it for real without touching callers.
return ( return self.decode_case(
f"case {self.number}:\n" WireType.LENGTH_DELIMITED,
f" value.decode_to_message(this->{self.field_name});\n" f"value.decode_to_message(this->{self.field_name});\n"
f" this->has_{self.name} = true;\n" f" this->has_{self.name} = true;",
f" break;"
) )
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: def dump(self, name: str) -> str:
return f"{name}.dump_to(out);" return f"{name}.dump_to(out);"
@@ -1216,11 +1226,11 @@ class PointerToBytesBufferType(PointerToBufferTypeBase):
@property @property
def decode_length_content(self) -> str | None: def decode_length_content(self) -> str | None:
return f"""case {self.number}: {{ return self.decode_case(
this->{self.field_name} = value.data(); WireType.LENGTH_DELIMITED,
this->{self.field_name}_len = value.size(); f"this->{self.field_name} = value.data();\n"
break; f" this->{self.field_name}_len = value.size();",
}}""" )
def dump(self, name: str) -> str: def dump(self, name: str) -> str:
return ( return (
@@ -1281,10 +1291,10 @@ class PointerToStringBufferType(PointerToBufferTypeBase):
@property @property
def decode_length_content(self) -> str | None: def decode_length_content(self) -> str | None:
return f"""case {self.number}: {{ return self.decode_case(
this->{self.field_name} = StringRef(reinterpret_cast<const char *>(value.data()), value.size()); WireType.LENGTH_DELIMITED,
break; f"this->{self.field_name} = StringRef(reinterpret_cast<const char *>(value.data()), value.size());",
}}""" )
def dump(self, name: str) -> str: def dump(self, name: str) -> str:
# Not used since we use dump_field, but required by abstract base class # Not used since we use dump_field, but required by abstract base class
@@ -1355,12 +1365,12 @@ class PackedBufferTypeInfo(TypeInfo):
@property @property
def decode_length_content(self) -> str: def decode_length_content(self) -> str:
"""Store pointer to buffer and calculate count of packed varints.""" """Store pointer to buffer and calculate count of packed varints."""
return f"""case {self.number}: {{ return self.decode_case(
this->{self.field_name}_data_ = value.data(); WireType.LENGTH_DELIMITED,
this->{self.field_name}_length_ = value.size(); f"this->{self.field_name}_data_ = value.data();\n"
this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size()); f" this->{self.field_name}_length_ = value.size();\n"
break; f" this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size());",
}}""" )
@property @property
def encode_content(self) -> str: def encode_content(self) -> str:
@@ -1446,16 +1456,13 @@ class FixedArrayBytesType(TypeInfo):
@property @property
def decode_length_content(self) -> str: def decode_length_content(self) -> str:
o = f"case {self.number}: {{\n" body = "const std::string &data_str = value.as_string();\n"
o += " const std::string &data_str = value.as_string();\n" body += f" this->{self.field_name}_len = data_str.size();\n"
o += f" this->{self.field_name}_len = data_str.size();\n" body += f" if (this->{self.field_name}_len > {self.array_size}) {{\n"
o += f" if (this->{self.field_name}_len > {self.array_size}) {{\n" body += f" this->{self.field_name}_len = {self.array_size};\n"
o += f" this->{self.field_name}_len = {self.array_size};\n" body += " }\n"
o += " }\n" body += f" memcpy(this->{self.field_name}, data_str.data(), this->{self.field_name}_len);"
o += f" memcpy(this->{self.field_name}, data_str.data(), this->{self.field_name}_len);\n" return self.decode_case(WireType.LENGTH_DELIMITED, body)
o += " break;\n"
o += "}"
return o
@property @property
def encode_content(self) -> str: def encode_content(self) -> str:
@@ -1518,7 +1525,7 @@ class UInt32Type(VarintTypeMixin, TypeInfo):
cpp_type = "uint32_t" cpp_type = "uint32_t"
_varint_max_bits = 32 _varint_max_bits = 32
default_value = "0" default_value = "0"
decode_varint = "value" decode_varint = "value.as_varint()"
encode_func = "encode_uint32" encode_func = "encode_uint32"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -1547,7 +1554,7 @@ class EnumType(VarintTypeMixin, TypeInfo):
@property @property
def decode_varint(self) -> str: 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 = "" default_value = ""
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -1620,7 +1627,6 @@ class SFixed32Type(FixedSizeTypeMixin, TypeInfo):
class SFixed64Type(FixedSizeTypeMixin, TypeInfo): class SFixed64Type(FixedSizeTypeMixin, TypeInfo):
cpp_type = "int64_t" cpp_type = "int64_t"
default_value = "0" default_value = "0"
decode_64bit = "value.as_sfixed64()"
encode_func = "encode_sfixed64" encode_func = "encode_sfixed64"
wire_type = WireType.FIXED64 # Uses wire type 1 wire_type = WireType.FIXED64 # Uses wire type 1
@@ -1647,7 +1653,7 @@ class SInt32Type(VarintTypeMixin, TypeInfo):
cpp_type = "int32_t" cpp_type = "int32_t"
_varint_max_bits = 32 # zigzag encoding keeps it 32-bit _varint_max_bits = 32 # zigzag encoding keeps it 32-bit
default_value = "0" 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" encode_func = "encode_sint32"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -1668,7 +1674,7 @@ class SInt64Type(VarintTypeMixin, TypeInfo):
cpp_type = "int64_t" cpp_type = "int64_t"
_varint_max_bits = 64 _varint_max_bits = 64
default_value = "0" default_value = "0"
decode_varint = "decode_zigzag64(value)" decode_varint = "decode_zigzag64(value.as_varint())"
encode_func = "encode_sint64" encode_func = "encode_sint64"
wire_type = WireType.VARINT # Uses wire type 0 wire_type = WireType.VARINT # Uses wire type 0
@@ -2138,8 +2144,8 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_varint content = self._ti.decode_varint
if content is None: if content is None:
return None return None
return ( return self.decode_case(
f"case {self.number}: this->{self.field_name}.push_back({content}); break;" WireType.VARINT, f"this->{self.field_name}.push_back({content});"
) )
@property @property
@@ -2150,11 +2156,15 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_length content = self._ti.decode_length
if content is None and isinstance(self._ti, MessageType): if content is None and isinstance(self._ti, MessageType):
# Special handling for non-template message decoding # 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: if content is None:
return None return None
return ( return self.decode_case(
f"case {self.number}: this->{self.field_name}.push_back({content}); break;" WireType.LENGTH_DELIMITED, f"this->{self.field_name}.push_back({content});"
) )
@property @property
@@ -2165,20 +2175,8 @@ class RepeatedTypeInfo(TypeInfo):
content = self._ti.decode_32bit content = self._ti.decode_32bit
if content is None: if content is None:
return None return None
return ( return self.decode_case(
f"case {self.number}: this->{self.field_name}.push_back({content}); break;" WireType.FIXED32, f"this->{self.field_name}.push_back({content});"
)
@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;"
) )
@property @property
@@ -2595,10 +2593,7 @@ def build_message_type(
) -> tuple[str, str, str]: ) -> tuple[str, str, str]:
public_content: list[str] = [] public_content: list[str] = []
protected_content: list[str] = [] protected_content: list[str] = []
decode_varint: list[str] = [] decode: list[str] = []
decode_length: list[str] = []
decode_32bit: list[str] = []
decode_64bit: list[str] = []
encode: list[str] = [] encode: list[str] = []
dump: list[str] = [] dump: list[str] = []
size_calc: list[str] = [] size_calc: list[str] = []
@@ -2727,22 +2722,13 @@ def build_message_type(
if field.options.HasExtension(pb.field_ifdef): if field.options.HasExtension(pb.field_ifdef):
field_ifdef = field.options.Extensions[pb.field_ifdef] field_ifdef = field.options.Extensions[pb.field_ifdef]
if ti.decode_varint_content: for case in (
decode_varint.extend( ti.decode_varint_content,
wrap_with_ifdef(ti.decode_varint_content, field_ifdef) ti.decode_length_content,
) ti.decode_32bit_content,
if ti.decode_length_content: ):
decode_length.extend( if case:
wrap_with_ifdef(ti.decode_length_content, field_ifdef) decode.extend(wrap_with_ifdef(case, 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)
)
if ti.dump_content: if ti.dump_content:
# Check for field_ifdef option for dump as well # Check for field_ifdef option for dump as well
field_ifdef = None field_ifdef = None
@@ -2752,49 +2738,18 @@ def build_message_type(
dump.extend(wrap_with_ifdef(ti.dump_content, field_ifdef)) dump.extend(wrap_with_ifdef(ti.dump_content, field_ifdef))
cpp = "" cpp = ""
if decode_varint: if decode:
o = f"bool {desc.name}::decode_varint(uint32_t field_id, proto_varint_value_t value) {{\n" # One virtual per message: the shared decode loop parses the payload for the wire
o += " switch (field_id) {\n" # type and hands it over with the tag, so a single switch covers every field.
o += indent("\n".join(decode_varint), " ") + "\n" 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 += " default: return false;\n"
o += " }\n" o += " }\n"
o += " return true;\n" o += " return true;\n"
o += "}\n" o += "}\n"
cpp += o cpp += o
prot = "bool decode_varint(uint32_t field_id, proto_varint_value_t 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)
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;"
protected_content.insert(0, prot) protected_content.insert(0, prot)
# Generate custom decode() override for messages with FixedVector fields # 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 import aioesphomeapi.api_options_pb2 as pb # noqa: E402
from api_protobuf import ( # noqa: E402 from api_protobuf import ( # noqa: E402
MAX_MESSAGE_ID, MAX_MESSAGE_ID,
SOURCE_CLIENT,
_make_ifdef_line, _make_ifdef_line,
build_message_type,
create_field_type_info, create_field_type_info,
get_varint64_ifdef, get_varint64_ifdef,
validate_message_id, 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) content = _encode_field(field_type, number=16)
assert "write_tag_and_fixed32" not in content, content assert "write_tag_and_fixed32" not in content, content
assert content.startswith("pos = ProtoEncode::encode_"), 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