ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
proto.cpp
Go to the documentation of this file.
1#include "proto.h"
2#include <cinttypes>
4#include "esphome/core/log.h"
5
6namespace esphome::api {
7
8static const char *const TAG = "api.proto";
9
10#ifdef USE_API_VARINT64
11optional<ProtoVarInt> ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed,
12 uint32_t result32) {
13 uint64_t result64 = result32;
14 uint32_t limit = std::min(len, uint32_t(10));
15 for (uint32_t i = 4; i < limit; i++) {
16 uint8_t val = buffer[i];
17 result64 |= uint64_t(val & 0x7F) << (i * 7);
18 if ((val & 0x80) == 0) {
19 *consumed = i + 1;
20 return ProtoVarInt(result64);
21 }
22 }
23 return {};
24}
25#endif
26
27uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id) {
28 uint32_t count = 0;
29 const uint8_t *ptr = buffer;
30 const uint8_t *end = buffer + length;
31
32 while (ptr < end) {
33 uint32_t consumed;
34
35 // Parse field header (tag)
36 auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
37 if (!res.has_value()) {
38 break; // Invalid data, stop counting
39 }
40
41 uint32_t tag = res->as_uint32();
42 uint32_t field_type = tag & WIRE_TYPE_MASK;
43 uint32_t field_id = tag >> 3;
44 ptr += consumed;
45
46 // Count if this is the target field
47 if (field_id == target_field_id) {
48 count++;
49 }
50
51 // Skip field data based on wire type
52 switch (field_type) {
53 case WIRE_TYPE_VARINT: { // VarInt - parse and skip
54 res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
55 if (!res.has_value()) {
56 return count; // Invalid data, return what we have
57 }
58 ptr += consumed;
59 break;
60 }
61 case WIRE_TYPE_LENGTH_DELIMITED: { // Length-delimited - parse length and skip data
62 res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
63 if (!res.has_value()) {
64 return count;
65 }
66 uint32_t field_length = res->as_uint32();
67 ptr += consumed;
68 if (field_length > static_cast<size_t>(end - ptr)) {
69 return count; // Out of bounds
70 }
71 ptr += field_length;
72 break;
73 }
74 case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
75 if (end - ptr < 4) {
76 return count;
77 }
78 ptr += 4;
79 break;
80 }
81 default:
82 // Unknown wire type, can't continue
83 return count;
84 }
85 }
86
87 return count;
88}
89
90#ifdef ESPHOME_DEBUG_API
91void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) {
92 if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) {
93 ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes,
94 this->pos_ - this->buffer_->data(), this->buffer_->size());
95 abort();
96 }
97}
98void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual) {
99 ESP_LOGE(TAG, "encode_message: size mismatch for field %" PRIu32 ": calculated=%" PRIu32 " actual=%td", field_id,
100 expected, actual);
101 abort();
102}
103#endif
104
105void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
106 const uint8_t *ptr = buffer;
107 const uint8_t *end = buffer + length;
108
109 while (ptr < end) {
110 uint32_t consumed;
111
112 // Parse field header
113 auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
114 if (!res.has_value()) {
115 ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
116 return;
117 }
118
119 uint32_t tag = res->as_uint32();
120 uint32_t field_type = tag & WIRE_TYPE_MASK;
121 uint32_t field_id = tag >> 3;
122 ptr += consumed;
123
124 switch (field_type) {
125 case WIRE_TYPE_VARINT: { // VarInt
126 res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
127 if (!res.has_value()) {
128 ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
129 return;
130 }
131 if (!this->decode_varint(field_id, *res)) {
132 ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
133 }
134 ptr += consumed;
135 break;
136 }
137 case WIRE_TYPE_LENGTH_DELIMITED: { // Length-delimited
138 res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
139 if (!res.has_value()) {
140 ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
141 return;
142 }
143 uint32_t field_length = res->as_uint32();
144 ptr += consumed;
145 if (field_length > static_cast<size_t>(end - ptr)) {
146 ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
147 return;
148 }
149 if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
150 ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
151 }
152 ptr += field_length;
153 break;
154 }
155 case WIRE_TYPE_FIXED32: { // 32-bit
156 if (end - ptr < 4) {
157 ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
158 return;
159 }
160 uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
161 if (!this->decode_32bit(field_id, Proto32Bit(val))) {
162 ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
163 }
164 ptr += 4;
165 break;
166 }
167 default:
168 ESP_LOGV(TAG, "Invalid field type %" PRIu32 " at offset %ld", field_type, (long) (ptr - buffer));
169 return;
170 }
171 }
172}
173
174} // namespace esphome::api
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value)
Definition proto.h:492
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value)
Definition proto.h:490
virtual void decode(const uint8_t *buffer, size_t length)
Definition proto.cpp:105
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value)
Definition proto.h:491
static uint32_t count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id)
Count occurrences of a repeated field in a protobuf buffer.
Definition proto.cpp:27
static optional< ProtoVarInt > parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed, uint32_t result32) __attribute__((noinline))
Continue parsing varint bytes 4-9 with 64-bit arithmetic.
Definition proto.cpp:11
static optional< ProtoVarInt > parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed)
Parse a varint from buffer. consumed must be a valid pointer (not null).
Definition proto.h:108
void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual)
Definition proto.cpp:98
void debug_check_bounds_(size_t bytes, const char *caller=__builtin_FUNCTION())
Definition proto.cpp:91
std::vector< uint8_t > * buffer_
Definition proto.h:378
mopeka_std_values val[4]
constexpr uint8_t WIRE_TYPE_VARINT
Definition proto.h:21
constexpr uint8_t WIRE_TYPE_MASK
Definition proto.h:24
constexpr uint8_t WIRE_TYPE_LENGTH_DELIMITED
Definition proto.h:22
constexpr uint8_t WIRE_TYPE_FIXED32
Definition proto.h:23
std::string size_t len
Definition helpers.h:817
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:661
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t length
Definition tt21100.cpp:0