ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
packet_transport.cpp
Go to the documentation of this file.
1#include "esphome/core/log.h"
4#include "packet_transport.h"
5
6#include <ranges>
7
9
10namespace esphome {
11namespace packet_transport {
12
13// Maximum bytes to log in hex output (168 * 3 = 504, under TX buffer size of 512)
14static constexpr size_t PACKET_MAX_LOG_BYTES = 168;
54static const char *const TAG = "packet_transport";
55
56static size_t round4(size_t value) { return (value + 3) & ~3; }
57
58union FuData {
59 uint32_t u32;
60 float f32;
61};
62
63static constexpr uint16_t MAGIC_NUMBER = 0x4553;
64static constexpr uint16_t MAGIC_PING = 0x5048;
65static constexpr uint32_t PREF_HASH = 0x45535043;
74
81
82static constexpr size_t MAX_PING_KEYS = 4;
83
84static inline void add(std::vector<uint8_t> &vec, uint32_t data) {
85 vec.push_back(data & 0xFF);
86 vec.push_back((data >> 8) & 0xFF);
87 vec.push_back((data >> 16) & 0xFF);
88 vec.push_back((data >> 24) & 0xFF);
89}
90
91class PacketDecoder {
92 public:
93 PacketDecoder(const uint8_t *buffer, size_t len) : buffer_(buffer), len_(len) {}
94
95 DecodeResult decode_string(char *data, size_t maxlen) {
96 if (this->position_ == this->len_)
97 return DECODE_EMPTY;
98 auto len = this->buffer_[this->position_];
99 if (len == 0 || this->position_ + 1 + len > this->len_ || len >= maxlen)
100 return DECODE_ERROR;
101 this->position_++;
102 memcpy(data, this->buffer_ + this->position_, len);
103 data[len] = 0;
104 this->position_ += len;
105 return DECODE_OK;
106 }
107
108 template<typename T> DecodeResult get(T &data) {
109 if (this->position_ + sizeof(T) > this->len_)
110 return DECODE_ERROR;
111 T value = 0;
112 for (size_t i = 0; i != sizeof(T); ++i) {
113 value += this->buffer_[this->position_++] << (i * 8);
114 }
115 data = value;
116 return DECODE_OK;
117 }
118
119 template<typename T> DecodeResult decode(uint8_t key, T &data) {
120 if (this->position_ == this->len_)
121 return DECODE_EMPTY;
122 if (this->buffer_[this->position_] != key)
123 return DECODE_UNMATCHED;
124 if (this->position_ + 1 + sizeof(T) > this->len_)
125 return DECODE_ERROR;
126 this->position_++;
127 T value = 0;
128 for (size_t i = 0; i != sizeof(T); ++i) {
129 value += this->buffer_[this->position_++] << (i * 8);
130 }
131 data = value;
132 return DECODE_OK;
133 }
134
135 template<typename T> DecodeResult decode(uint8_t key, char *buf, size_t buflen, T &data) {
136 if (this->position_ == this->len_)
137 return DECODE_EMPTY;
138 if (this->buffer_[this->position_] != key)
139 return DECODE_UNMATCHED;
140 if (this->position_ + 1 + sizeof(T) > this->len_)
141 return DECODE_ERROR;
142 this->position_++;
143 T value = 0;
144 for (size_t i = 0; i != sizeof(T); ++i) {
145 value += this->buffer_[this->position_++] << (i * 8);
146 }
147 data = value;
148 return this->decode_string(buf, buflen);
149 }
150
151 DecodeResult decode(uint8_t key) {
152 if (this->position_ == this->len_)
153 return DECODE_EMPTY;
154 if (this->buffer_[this->position_] != key)
155 return DECODE_UNMATCHED;
156 this->position_++;
157 return DECODE_OK;
158 }
159
160 size_t get_remaining_size() const { return this->len_ - this->position_; }
161
162 // align the pointer to the given byte boundary
163 bool bump_to(size_t boundary) {
164 auto newpos = this->position_;
165 auto offset = this->position_ % boundary;
166 if (offset != 0) {
167 newpos += boundary - offset;
168 }
169 if (newpos >= this->len_)
170 return false;
171 this->position_ = newpos;
172 return true;
173 }
174
175 bool decrypt(const uint32_t *key) const {
176 if (this->get_remaining_size() % 4 != 0) {
177 return false;
178 }
179 xxtea::decrypt((uint32_t *) (this->buffer_ + this->position_), this->get_remaining_size() / 4, key);
180 return true;
181 }
182
183 protected:
184 const uint8_t *buffer_;
185 size_t len_;
186 size_t position_{};
187};
188
189static inline void add(std::vector<uint8_t> &vec, uint8_t data) { vec.push_back(data); }
190static inline void add(std::vector<uint8_t> &vec, uint16_t data) {
191 vec.push_back((uint8_t) data);
192 vec.push_back((uint8_t) (data >> 8));
193}
194static inline void add(std::vector<uint8_t> &vec, DataKey data) { vec.push_back(data); }
195static void add(std::vector<uint8_t> &vec, const char *str) {
196 auto len = strlen(str);
197 vec.push_back(len);
198 for (size_t i = 0; i != len; i++) {
199 vec.push_back(*str++);
200 }
201}
202
204 this->name_ = App.get_name().c_str();
205 if (strlen(this->name_) > 255) {
206 this->status_set_error(LOG_STR("Device name exceeds 255 chars"));
207 this->mark_failed();
208 return;
209 }
211 this->pref_ = global_preferences->make_preference<uint32_t>(PREF_HASH, true);
212 if (this->rolling_code_enable_) {
213 // restore the upper 32 bits of the rolling code, increment and save.
214 this->pref_.load(&this->rolling_code_[1]);
215 this->rolling_code_[1]++;
216 this->pref_.save(&this->rolling_code_[1]);
217 // must make sure it's saved immediately
219 this->ping_key_ = random_uint32();
220 ESP_LOGV(TAG, "Rolling code incremented, upper part now %u", (unsigned) this->rolling_code_[1]);
221 }
222#ifdef USE_SENSOR
223 for (auto &sensor : this->sensors_) {
224 // [&sensor] is safe: sensor refers to a FixedVector element that never reallocates,
225 // so the reference remains valid for the component's lifetime.
226 sensor.sensor->add_on_state_callback([&sensor](float x) {
227 sensor.parent->updated_ = true;
228 sensor.updated = true;
229 });
230 }
231#endif
232#ifdef USE_BINARY_SENSOR
233 for (auto &sensor : this->binary_sensors_) {
234 // [&sensor] is safe: sensor refers to a FixedVector element that never reallocates,
235 // so the reference remains valid for the component's lifetime.
236 sensor.sensor->add_on_state_callback([&sensor](bool value) {
237 sensor.parent->updated_ = true;
238 sensor.updated = true;
239 });
240 }
241#endif
242 // initialise the header. This is invariant.
243 add(this->header_, MAGIC_NUMBER);
244 add(this->header_, this->name_);
245 // pad to a multiple of 4 bytes
246 while (this->header_.size() & 0x3)
247 this->header_.push_back(0);
248}
249
251 this->data_.clear();
252 if (this->rolling_code_enable_) {
253 add(this->data_, ROLLING_CODE_KEY);
254 add(this->data_, this->rolling_code_[0]);
255 add(this->data_, this->rolling_code_[1]);
256 this->increment_code_();
257 } else {
258 add(this->data_, DATA_KEY);
259 }
260 for (auto &value : this->ping_keys_ | std::views::values) {
261 add(this->data_, PING_KEY);
262 add(this->data_, value);
263 }
264}
265
267 if (!this->should_send() || this->data_.empty())
268 return;
269 auto header_len = round4(this->header_.size());
270 auto len = round4(data_.size());
271 auto encode_buffer = std::vector<uint8_t>(round4(header_len + len));
272 memcpy(encode_buffer.data(), this->header_.data(), this->header_.size());
273 memcpy(encode_buffer.data() + header_len, this->data_.data(), this->data_.size());
274 if (this->is_encrypted_()) {
275 xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4,
276 (uint32_t *) this->encryption_key_.data());
277 }
278 char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
279 ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty_to(hex_buf, encode_buffer.data(), encode_buffer.size()));
280 this->send_packet(encode_buffer);
281}
282
283void PacketTransport::add_binary_data_(uint8_t key, const char *id, bool data) {
284 auto len = 1 + 1 + 1 + strlen(id);
285 if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
286 this->flush_();
287 this->init_data_();
288 }
289 add(this->data_, key);
290 add(this->data_, (uint8_t) data);
291 add(this->data_, id);
292}
293void PacketTransport::add_data_(uint8_t key, const char *id, float data) {
294 FuData udata{.f32 = data};
295 this->add_data_(key, id, udata.u32);
296}
297
298void PacketTransport::add_data_(uint8_t key, const char *id, uint32_t data) {
299 auto len = 4 + 1 + 1 + strlen(id);
300 if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
301 this->flush_();
302 this->init_data_();
303 }
304 add(this->data_, key);
305 add(this->data_, data);
306 add(this->data_, id);
307}
309 if (!this->should_send())
310 return;
311 this->init_data_();
312#ifdef USE_SENSOR
313 for (auto &sensor : this->sensors_) {
314 if (all || sensor.updated) {
315 sensor.updated = false;
316 this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state());
317 }
318 }
319#endif
320#ifdef USE_BINARY_SENSOR
321 for (auto &sensor : this->binary_sensors_) {
322 if (all || sensor.updated) {
323 sensor.updated = false;
324 this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state);
325 }
326 }
327#endif
328 this->flush_();
329 this->updated_ = false;
330}
331
333 // resend all sensors if required
334 if (this->is_provider_)
335 this->send_data_(true);
336 if (!this->ping_pong_enable_) {
337 return;
338 }
339 uint32_t now = millis();
340 uint32_t ping_request_age = now - this->last_key_time_;
341 if (ping_request_age > this->ping_pong_recyle_time_ * 1000u) {
343 ESP_LOGV(TAG, "Ping request, age %" PRIu32, ping_request_age);
344 this->last_key_time_ = now;
345 }
346 for (const auto &provider : this->providers_) {
347 uint32_t key_response_age = now - provider.second.last_key_response_time;
348 if (key_response_age > (this->ping_pong_recyle_time_ * 2000u)) {
349#ifdef USE_STATUS_SENSOR
350 if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) {
351 ESP_LOGI(TAG, "Ping status for %s timeout at %" PRIu32 " with age %" PRIu32, provider.first.c_str(), now,
352 key_response_age);
353 provider.second.status_sensor->publish_state(false);
354 }
355#endif
356#ifdef USE_SENSOR
357 auto it = this->remote_sensors_.find(provider.first);
358 if (it != this->remote_sensors_.end()) {
359 for (auto &val : it->second | std::views::values) {
360 val->publish_state(NAN);
361 }
362 }
363#endif
364#ifdef USE_BINARY_SENSOR
365 auto bs_it = this->remote_binary_sensors_.find(provider.first);
366 if (bs_it != this->remote_binary_sensors_.end()) {
367 for (auto &val : bs_it->second | std::views::values) {
368 val->invalidate_state();
369 }
370 }
371#endif
372 } else {
373#ifdef USE_STATUS_SENSOR
374 if (provider.second.status_sensor != nullptr && !provider.second.status_sensor->state) {
375 ESP_LOGI(TAG, "Ping status for %s restored at %" PRIu32 " with age %" PRIu32, provider.first.c_str(), now,
376 key_response_age);
377 provider.second.status_sensor->publish_state(true);
378 }
379#endif
380 }
381 }
382}
383
384void PacketTransport::add_key_(const char *name, uint32_t key) {
385 if (!this->is_encrypted_())
386 return;
387 auto it = this->ping_keys_.find(name);
388 if (it == this->ping_keys_.end()) {
389 if (this->ping_keys_.size() == MAX_PING_KEYS) {
390 ESP_LOGW(TAG, "Ping key from %s discarded", name);
391 return;
392 }
393 this->ping_keys_.emplace(name, key); // allocates string key once only
394 } else {
395 it->second = key; // key string already exists in map, no allocation
396 }
397 this->updated_ = true;
398 ESP_LOGV(TAG, "Ping key from %s now %X", name, (unsigned) key);
399}
400
401static bool process_rolling_code(Provider &provider, PacketDecoder &decoder) {
402 uint32_t code0, code1;
403 if (decoder.get(code0) != DECODE_OK || decoder.get(code1) != DECODE_OK) {
404 ESP_LOGW(TAG, "Rolling code requires 8 bytes");
405 return false;
406 }
407 if (code1 < provider.last_code[1] || (code1 == provider.last_code[1] && code0 <= provider.last_code[0])) {
408 ESP_LOGW(TAG, "Rolling code for %s %08lX:%08lX is old", provider.name, (unsigned long) code1,
409 (unsigned long) code0);
410 return false;
411 }
412 provider.last_code[0] = code0;
413 provider.last_code[1] = code1;
414 ESP_LOGV(TAG, "Saw new rolling code for %s %08lX:%08lX", provider.name, (unsigned long) code1, (unsigned long) code0);
415 return true;
416}
417
421void PacketTransport::process_(std::span<const uint8_t> data) {
422 auto ping_key_seen = !this->ping_pong_enable_;
423 PacketDecoder decoder(data.data(), data.size());
424 char namebuf[256]{};
425 uint8_t byte;
426 FuData rdata{};
427 uint16_t magic;
428 if (decoder.get(magic) != DECODE_OK) {
429 ESP_LOGD(TAG, "Short buffer");
430 return;
431 }
432 if (magic != MAGIC_NUMBER && magic != MAGIC_PING) {
433 ESP_LOGV(TAG, "Bad magic %X", magic);
434 return;
435 }
436
437 if (decoder.decode_string(namebuf, sizeof namebuf) != DECODE_OK) {
438 ESP_LOGV(TAG, "Bad hostname length");
439 return;
440 }
441 if (strcmp(this->name_, namebuf) == 0) {
442 ESP_LOGVV(TAG, "Ignoring our own data");
443 return;
444 }
445 if (magic == MAGIC_PING) {
446 uint32_t key;
447 if (decoder.get(key) != DECODE_OK) {
448 ESP_LOGW(TAG, "Bad ping request");
449 return;
450 }
451 this->add_key_(namebuf, key);
452 ESP_LOGV(TAG, "Updated ping key for %s to %08X", namebuf, (unsigned) key);
453 return;
454 }
455
456 auto it = this->providers_.find(namebuf);
457 if (it == this->providers_.end()) {
458 ESP_LOGVV(TAG, "Unknown hostname %s", namebuf);
459 return;
460 }
461 auto &provider = it->second;
462 ESP_LOGV(TAG, "Found hostname %s", namebuf);
463
464#ifdef USE_SENSOR
465 auto &sensors = this->remote_sensors_.try_emplace(namebuf).first->second;
466#endif
467#ifdef USE_BINARY_SENSOR
468 auto &binary_sensors = this->remote_binary_sensors_.try_emplace(namebuf).first->second;
469#endif
470
471 if (!decoder.bump_to(4)) {
472 ESP_LOGW(TAG, "Bad packet length %zu", data.size());
473 }
474 auto len = decoder.get_remaining_size();
475 if (round4(len) != len) {
476 ESP_LOGW(TAG, "Bad payload length %zu", len);
477 return;
478 }
479
480 // if encryption not used with this host, ping check is pointless since it would be easily spoofed.
481 if (provider.encryption_key.empty())
482 ping_key_seen = true;
483
484 if (!provider.encryption_key.empty()) {
485 decoder.decrypt((const uint32_t *) provider.encryption_key.data());
486 }
487 if (decoder.get(byte) != DECODE_OK) {
488 ESP_LOGV(TAG, "No key byte");
489 return;
490 }
491
492 if (byte == ROLLING_CODE_KEY) {
493 if (!process_rolling_code(provider, decoder))
494 return;
495 } else if (byte != DATA_KEY) {
496 ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte);
497 return;
498 }
499 uint32_t key;
500 while (decoder.get_remaining_size() != 0) {
501 if (decoder.decode(ZERO_FILL_KEY) == DECODE_OK)
502 continue;
503 if (decoder.decode(PING_KEY, key) == DECODE_OK) {
504 if (key == this->ping_key_) {
505 ping_key_seen = true;
506 provider.last_key_response_time = millis();
507 ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time);
508 } else {
509 ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
510 }
511 continue;
512 }
513 if (!ping_key_seen) {
514 ESP_LOGW(TAG, "Ping key not seen");
515 this->resend_ping_key_ = true;
516 break;
517 }
518 if (decoder.decode(BINARY_SENSOR_KEY, namebuf, sizeof(namebuf), byte) == DECODE_OK) {
519 ESP_LOGV(TAG, "Got binary sensor %s %d", namebuf, byte);
520#ifdef USE_BINARY_SENSOR
521 auto bs = binary_sensors.find(namebuf);
522 if (bs != binary_sensors.end()) {
523 bs->second->publish_state(byte != 0);
524 }
525#endif
526 continue;
527 }
528 if (decoder.decode(SENSOR_KEY, namebuf, sizeof(namebuf), rdata.u32) == DECODE_OK) {
529 ESP_LOGV(TAG, "Got sensor %s %f", namebuf, rdata.f32);
530#ifdef USE_SENSOR
531 auto sensor_it = sensors.find(namebuf);
532 if (sensor_it != sensors.end())
533 sensor_it->second->publish_state(rdata.f32);
534#endif
535 continue;
536 }
537 if (decoder.get(byte) == DECODE_OK) {
538 ESP_LOGW(TAG, "Unknown key %X", byte);
539 char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
540 ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(),
541 format_hex_pretty_to(hex_buf, data.data(), data.size()));
542 }
543 break;
544 }
545}
546
548 ESP_LOGCONFIG(TAG,
549 "Packet Transport:\n"
550 " Platform: %s\n"
551 " Encrypted: %s\n"
552 " Ping-pong: %s",
553 this->platform_name_, YESNO(this->is_encrypted_()), YESNO(this->ping_pong_enable_));
554#ifdef USE_SENSOR
555 for (const auto &sensor : this->sensors_)
556 ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);
557#endif
558#ifdef USE_BINARY_SENSOR
559 for (const auto &sensor : this->binary_sensors_)
560 ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id);
561#endif
562 for (const auto &host : this->providers_) {
563 ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str());
564 ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(!host.second.encryption_key.empty()));
565#ifdef USE_SENSOR
566 auto rs = this->remote_sensors_.find(host.first.c_str());
567 if (rs != this->remote_sensors_.end()) {
568 for (const auto &key : rs->second | std::views::keys)
569 ESP_LOGCONFIG(TAG, " Sensor: %s", key.c_str());
570 }
571#endif
572#ifdef USE_BINARY_SENSOR
573 auto rbs = this->remote_binary_sensors_.find(host.first.c_str());
574 if (rbs != this->remote_binary_sensors_.end()) {
575 for (const auto &key : rbs->second | std::views::keys)
576 ESP_LOGCONFIG(TAG, " Binary Sensor: %s", key.c_str());
577 }
578#endif
579 }
580}
582 if (this->rolling_code_enable_) {
583 if (++this->rolling_code_[0] == 0) {
584 this->rolling_code_[1]++;
585 this->pref_.save(&this->rolling_code_[1]);
586 // must make sure it's saved immediately
588 }
589 }
590}
591
593 if (this->resend_ping_key_)
595 if (this->updated_) {
596 this->send_data_(false);
597 }
598}
599
601 if (!this->ping_pong_enable_ || !this->should_send())
602 return;
603 this->ping_key_ = random_uint32();
604 this->ping_header_.clear();
605 add(this->ping_header_, MAGIC_PING);
606 add(this->ping_header_, this->name_);
607 add(this->ping_header_, this->ping_key_);
608 this->send_packet(this->ping_header_);
609 this->resend_ping_key_ = false;
610 ESP_LOGV(TAG, "Sent new ping request %08X", (unsigned) this->ping_key_);
611}
612} // namespace packet_transport
613} // namespace esphome
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
void mark_failed()
Mark this component as failed.
constexpr const char * c_str() const
Definition string_ref.h:73
void add_data_(uint8_t key, const char *id, float data)
FixedVector< BinarySensor > binary_sensors_
void add_key_(const char *name, uint32_t key)
string_map_t< string_map_t< binary_sensor::BinarySensor * > > remote_binary_sensors_
void add_binary_data_(uint8_t key, const char *id, bool data)
void process_(std::span< const uint8_t > data)
Process a received packet.
virtual void send_packet(const std::vector< uint8_t > &buf) const =0
string_map_t< string_map_t< sensor::Sensor * > > remote_sensors_
mopeka_std_values val[3]
const std::vector< uint8_t > & data
void encrypt(uint32_t *v, size_t n, const uint32_t *k)
Encrypt a block of data in-place using XXTEA algorithm with 256-bit key.
Definition xxtea.cpp:9
void decrypt(uint32_t *v, size_t n, const uint32_t *k)
Decrypt a block of data in-place using XXTEA algorithm with 256-bit key.
Definition xxtea.cpp:29
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:1045
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:409
ESPPreferences * global_preferences
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:12
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1368
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24
bool sync()
Commit pending writes to flash.
Definition preferences.h:32
std::vector< uint8_t > encryption_key
uint16_t x
Definition tt21100.cpp:5