diff --git a/esphome/components/infrared/infrared.cpp b/esphome/components/infrared/infrared.cpp index 294d69e523..4431869951 100644 --- a/esphome/components/infrared/infrared.cpp +++ b/esphome/components/infrared/infrared.cpp @@ -19,12 +19,12 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) { InfraredCall &InfraredCall::set_raw_timings(const std::vector &timings) { this->raw_timings_ = &timings; this->packed_data_ = nullptr; - this->base85_ptr_ = nullptr; + this->base64url_ptr_ = nullptr; return *this; } -InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) { - this->base85_ptr_ = &base85; +InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) { + this->base64url_ptr_ = &base64url; this->raw_timings_ = nullptr; this->packed_data_ = nullptr; return *this; @@ -35,7 +35,7 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t this->packed_length_ = length; this->packed_count_ = count; this->raw_timings_ = nullptr; - this->base85_ptr_ = nullptr; + this->base64url_ptr_ = nullptr; return *this; } @@ -101,13 +101,22 @@ void Infrared::control(const InfraredCall &call) { call.get_packed_count()); ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(), call.get_repeat_count()); - } else if (call.is_base85()) { - // Decode base85 directly into transmit buffer (zero heap allocations) - if (!transmit_data->set_data_from_base85(call.get_base85_data())) { - ESP_LOGE(TAG, "Invalid base85 data"); + } else if (call.is_base64url()) { + // Decode base64url (URL-safe) into transmit buffer + if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) { + ESP_LOGE(TAG, "Invalid base64url data"); return; } - ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(), + // Sanity check: validate timing values are within reasonable bounds + constexpr int32_t max_timing_us = 500000; // 500ms absolute max + for (int32_t timing : transmit_data->get_data()) { + int32_t abs_timing = timing < 0 ? -timing : timing; + if (abs_timing > max_timing_us) { + ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us); + return; + } + } + ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(), call.get_repeat_count()); } else { // From vector (lambdas/automations) diff --git a/esphome/components/infrared/infrared.h b/esphome/components/infrared/infrared.h index ba426c9daa..59535f499a 100644 --- a/esphome/components/infrared/infrared.h +++ b/esphome/components/infrared/infrared.h @@ -40,11 +40,11 @@ class InfraredCall { /// @note Usage: Primarily for lambdas/automations where the vector is in scope. InfraredCall &set_raw_timings(const std::vector &timings); - /// Set the raw timings from base85-encoded int32 data + /// Set the raw timings from base64url-encoded little-endian int32 data /// @note Lifetime: Stores a pointer to the string. The string must outlive perform(). - /// @note Usage: For web_server where the encoded string is on the stack. + /// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_'). /// @note Decoding happens at perform() time, directly into the transmit buffer. - InfraredCall &set_raw_timings_base85(const std::string &base85); + InfraredCall &set_raw_timings_base64url(const std::string &base64url); /// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded) /// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform(). @@ -59,18 +59,18 @@ class InfraredCall { /// Get the carrier frequency const optional &get_carrier_frequency() const { return this->carrier_frequency_; } - /// Get the raw timings (only valid if set via set_raw_timings, not packed or base85) + /// Get the raw timings (only valid if set via set_raw_timings) const std::vector &get_raw_timings() const { return *this->raw_timings_; } - /// Check if raw timings have been set (vector, packed, or base85) + /// Check if raw timings have been set (any format) bool has_raw_timings() const { - return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr; + return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr; } /// Check if using packed data format bool is_packed() const { return this->packed_data_ != nullptr; } - /// Check if using base85 data format - bool is_base85() const { return this->base85_ptr_ != nullptr; } - /// Get the base85 data string - const std::string &get_base85_data() const { return *this->base85_ptr_; } + /// Check if using base64url data format + bool is_base64url() const { return this->base64url_ptr_ != nullptr; } + /// Get the base64url data string + const std::string &get_base64url_data() const { return *this->base64url_ptr_; } /// Get packed data (only valid if set via set_raw_timings_packed) const uint8_t *get_packed_data() const { return this->packed_data_; } uint16_t get_packed_length() const { return this->packed_length_; } @@ -84,8 +84,8 @@ class InfraredCall { optional carrier_frequency_; // Pointer to vector-based timings (caller-owned, must outlive perform()) const std::vector *raw_timings_{nullptr}; - // Pointer to base85-encoded string (caller-owned, must outlive perform()) - const std::string *base85_ptr_{nullptr}; + // Pointer to base64url-encoded string (caller-owned, must outlive perform()) + const std::string *base64url_ptr_{nullptr}; // Pointer to packed protobuf buffer (caller-owned, must outlive perform()) const uint8_t *packed_data_{nullptr}; uint16_t packed_length_{0}; diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 53c9c38c7d..b4a549f0be 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -2,8 +2,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include - namespace esphome { namespace remote_base { @@ -160,8 +158,8 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t } } -bool RemoteTransmitData::set_data_from_base85(const std::string &base85) { - return base85_decode_int32_vector(base85, this->data_); +bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) { + return base64_decode_int32_vector(base64url, this->data_); } /* RemoteTransmitterBase */ diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 2d7642cc31..0cac28506f 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -36,11 +36,11 @@ class RemoteTransmitData { /// @param len Length of the buffer in bytes /// @param count Number of values (for reserve optimization) void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count); - /// Set data from base85-encoded int32 values - /// Decodes directly into internal buffer (zero heap allocations) - /// @param base85 Base85-encoded string (5 chars per int32 value) + /// Set data from base64url-encoded little-endian int32 values + /// Base64url is URL-safe: uses '-' instead of '+', '_' instead of '/' + /// @param base64url Base64url-encoded string of little-endian int32 values /// @return true if successful, false if decode failed or invalid size - bool set_data_from_base85(const std::string &base85); + bool set_data_from_base64url(const std::string &base64url); void reset() { this->data_.clear(); this->carrier_frequency_ = 0;