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