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