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