Introduce hex parsing & formatting helper functions (#2882)

This commit is contained in:
Oxan van Leeuwen 2021-12-12 21:15:23 +01:00 committed by GitHub
parent b2f05faee0
commit beeb0c7c5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 186 additions and 109 deletions

View File

@ -252,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif
frame->msg = std::move(rx_buf_);
// consume msg
@ -546,7 +546,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
@ -855,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif
frame->msg = std::move(rx_buf_);
// consume msg
@ -934,7 +935,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}

View File

@ -524,7 +524,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
}
for (auto &data : this->manufacturer_datas_) {
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str());
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str());
if (this->get_ibeacon().has_value()) {
auto ibeacon = this->get_ibeacon().value();
ESP_LOGVV(TAG, " iBeacon data:");
@ -537,10 +537,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
for (auto &data : this->service_datas_) {
ESP_LOGVV(TAG, " Service data:");
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str());
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
}
ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
#endif
}
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {

View File

@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1];
ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str());
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);

View File

@ -23,7 +23,7 @@ void MD5Digest::get_hex(char *output) {
}
}
bool MD5Digest::equals_bytes(const char *expected) {
bool MD5Digest::equals_bytes(const uint8_t *expected) {
for (size_t i = 0; i < 16; i++) {
if (expected[i] != this->digest_[i]) {
return false;
@ -33,18 +33,10 @@ bool MD5Digest::equals_bytes(const char *expected) {
}
bool MD5Digest::equals_hex(const char *expected) {
for (size_t i = 0; i < 16; i++) {
auto high = parse_hex(expected[i * 2]);
auto low = parse_hex(expected[i * 2 + 1]);
if (!high.has_value() || !low.has_value()) {
return false;
}
auto value = (*high << 4) | *low;
if (value != this->digest_[i]) {
return false;
}
}
return true;
uint8_t parsed[16];
if (!parse_hex(expected, parsed, 16))
return false;
return equals_bytes(parsed);
}
} // namespace md5

View File

@ -44,7 +44,7 @@ class MD5Digest {
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes).
bool equals_bytes(const char *expected);
bool equals_bytes(const uint8_t *expected);
/// Compare the digest against a provided hex-encoded digest (32 bytes).
bool equals_hex(const char *expected);

View File

@ -181,7 +181,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
this->flow_control_pin_->digital_write(false);
waiting_for_response = address;
last_send_ = millis();
ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str());
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str());
}
// Helper function for lambdas
@ -202,7 +202,7 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false);
waiting_for_response = payload[0];
ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str());
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str());
last_send_ = millis();
}

View File

@ -61,7 +61,7 @@ void ModbusSwitch::write_state(bool state) {
}
}
if (!data.empty()) {
ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str());
ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str());
cmd = ModbusCommandItem::create_custom_command(
this->parent_, data,
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {

View File

@ -26,7 +26,7 @@ bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
delay(2);
// First byte, communication mode: Write data
this->write_byte(0x01);
ESP_LOGV(TAG, "Writing data: %s", hexencode(data).c_str());
ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str());
this->write_array(data.data(), data.size());
this->disable();
@ -65,7 +65,7 @@ bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
this->read_array(data.data(), len);
this->disable();
data.insert(data.begin(), 0x01);
ESP_LOGV(TAG, "Read data: %s", hexencode(data).c_str());
ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str());
return true;
}
@ -97,7 +97,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
std::vector<uint8_t> header(7);
this->read_array(header.data(), 7);
ESP_LOGV(TAG, "Header data: %s", hexencode(header).c_str());
ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str());
if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) {
// invalid packet
@ -127,7 +127,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
this->read_array(data.data(), len + 1);
this->disable();
ESP_LOGV(TAG, "Response data: %s", hexencode(data).c_str());
ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str());
uint8_t checksum = header[5] + header[6]; // TFI + Command response code
for (int i = 0; i < len - 1; i++) {

View File

@ -26,7 +26,7 @@ class MideaData {
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
bool check_compliment(const MideaData &rhs) const;
std::string to_string() const { return hexencode(*this); }
std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); }
// compare only 40-bits
bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); }
enum MideaDataType : uint8_t {

View File

@ -37,9 +37,9 @@ void TuyaLight::setup() {
}
if (rgb_id_.has_value()) {
this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) {
auto red = parse_hex(datapoint.value_string, 0, 2);
auto green = parse_hex(datapoint.value_string, 2, 2);
auto blue = parse_hex(datapoint.value_string, 4, 2);
auto red = parse_hex<uint8_t>(datapoint.value_string.substr(0, 2));
auto green = parse_hex<uint8_t>(datapoint.value_string.substr(2, 2));
auto blue = parse_hex<uint8_t>(datapoint.value_string.substr(4, 2));
if (red.has_value() && green.has_value() && blue.has_value()) {
auto call = this->state_->make_call();
call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255);
@ -48,9 +48,9 @@ void TuyaLight::setup() {
});
} else if (hsv_id_.has_value()) {
this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) {
auto hue = parse_hex(datapoint.value_string, 0, 4);
auto saturation = parse_hex(datapoint.value_string, 4, 4);
auto value = parse_hex(datapoint.value_string, 8, 4);
auto hue = parse_hex<uint16_t>(datapoint.value_string.substr(0, 4));
auto saturation = parse_hex<uint16_t>(datapoint.value_string.substr(4, 4));
auto value = parse_hex<uint16_t>(datapoint.value_string.substr(8, 4));
if (hue.has_value() && saturation.has_value() && value.has_value()) {
float red, green, blue;
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);

View File

@ -14,7 +14,7 @@ void TuyaTextSensor::setup() {
this->publish_state(datapoint.value_string);
break;
case TuyaDatapointType::RAW: {
std::string data = hexencode(datapoint.value_raw);
std::string data = format_hex_pretty(datapoint.value_raw);
ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str());
this->publish_state(data);
break;

View File

@ -34,7 +34,7 @@ void Tuya::dump_config() {
}
for (auto &info : this->datapoints_) {
if (info.type == TuyaDatapointType::RAW)
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str());
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str());
else if (info.type == TuyaDatapointType::BOOLEAN)
ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
else if (info.type == TuyaDatapointType::INTEGER)
@ -104,7 +104,7 @@ bool Tuya::validate_message_() {
// valid message
const uint8_t *message_data = data + 6;
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
hexencode(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
this->handle_command_(command, version, message_data, length);
// return false to reset rx buffer
@ -253,7 +253,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_len);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, hexencode(datapoint.value_raw).c_str());
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
break;
case TuyaDatapointType::BOOLEAN:
if (data_len != 1) {
@ -348,7 +348,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
}
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
if (!command.payload.empty())
@ -526,7 +526,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
}
void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str());
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);

View File

@ -217,7 +217,7 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) {
if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) {
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size());
ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str());
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
return false;
}
@ -274,12 +274,12 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
memcpy(mac_address + 4, mac_reverse + 1, 1);
memcpy(mac_address + 5, mac_reverse, 1);
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str());
ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str());
ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str());
ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str());
ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str());
ESP_LOGVV(TAG, " Tag : %s", hexencode(vector.tag, vector.tagsize).c_str());
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str());
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str());
ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str());
mbedtls_ccm_free(&ctx);
return false;
}
@ -295,7 +295,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
raw[0] &= ~0x08;
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed.");
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(),
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(),
static_cast<int>(raw[4]));
mbedtls_ccm_free(&ctx);

View File

@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgd1";
void XiaomiCGD1::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi CGD1");
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgdk2";
void XiaomiCGDK2::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi CGDK2");
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgg1";
void XiaomiCGG1::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi CGG1");
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_lywsd03mmc";
void XiaomiLYWSD03MMC::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC");
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_mhoc401";
void XiaomiMHOC401::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi MHOC401");
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@ -244,38 +244,6 @@ std::string to_string(long double val) {
return buf;
}
optional<int> parse_hex(const char chr) {
int out = chr;
if (out >= '0' && out <= '9')
return (out - '0');
if (out >= 'A' && out <= 'F')
return (10 + (out - 'A'));
if (out >= 'a' && out <= 'f')
return (10 + (out - 'a'));
return {};
}
optional<int> parse_hex(const std::string &str, size_t start, size_t length) {
if (str.length() < start) {
return {};
}
size_t end = start + length;
if (str.length() < end) {
return {};
}
int out = 0;
for (size_t i = start; i < end; i++) {
char chr = str[i];
auto digit = parse_hex(chr);
if (!digit.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr);
return {};
}
out = (out << 4) | *digit;
}
return out;
}
uint32_t fnv1_hash(const std::string &str) {
uint32_t hash = 2166136261UL;
for (char c : str) {
@ -355,22 +323,6 @@ std::string str_sprintf(const char *fmt, ...) {
return str;
}
std::string hexencode(const uint8_t *data, uint32_t len) {
char buf[20];
std::string res;
for (size_t i = 0; i < len; i++) {
if (i + 1 != len) {
sprintf(buf, "%02X.", data[i]);
} else {
sprintf(buf, "%02X ", data[i]);
}
res += buf;
}
sprintf(buf, "(%u)", len);
res += buf;
return res;
}
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) {
float max_color_value = std::max(std::max(red, green), blue);
float min_color_value = std::min(std::min(red, green), blue);
@ -445,6 +397,8 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
// ---------------------------------------------------------------------------------------------------------------------
// Strings
std::string str_truncate(const std::string &str, size_t length) {
return str.length() > length ? str.substr(0, length) : str;
}
@ -468,4 +422,53 @@ std::string str_sanitize(const std::string &str) {
return out;
}
// Parsing & formatting
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
uint8_t val;
size_t chars = std::min(length, 2 * count);
for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) {
if (*str >= '0' && *str <= '9')
val = *str - '0';
else if (*str >= 'A' && *str <= 'F')
val = 10 + (*str - 'A');
else if (*str >= 'a' && *str <= 'f')
val = 10 + (*str - 'a');
else
return 0;
data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val;
}
return chars;
}
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
std::string format_hex(const uint8_t *data, size_t length) {
std::string ret;
ret.resize(length * 2);
for (size_t i = 0; i < length; i++) {
ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4);
ret[2 * i + 1] = format_hex_char(data[i] & 0x0F);
}
return ret;
}
std::string format_hex(std::vector<uint8_t> data) { return format_hex(data.data(), data.size()); }
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
std::string format_hex_pretty(const uint8_t *data, size_t length) {
if (length == 0)
return "";
std::string ret;
ret.resize(3 * length - 1);
for (size_t i = 0; i < length; i++) {
ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
if (i != length - 1)
ret[3 * i + 2] = '.';
}
if (length > 4)
return ret + " (" + to_string(length) + ")";
return ret;
}
std::string format_hex_pretty(std::vector<uint8_t> data) { return format_hex_pretty(data.data(), data.size()); }
} // namespace esphome

View File

@ -1,6 +1,7 @@
#pragma once
#include <cmath>
#include <cstring>
#include <string>
#include <functional>
@ -45,8 +46,6 @@ std::string to_string(unsigned long long val); // NOLINT
std::string to_string(float val);
std::string to_string(double val);
std::string to_string(long double val);
optional<int> parse_hex(const std::string &str, size_t start, size_t length);
optional<int> parse_hex(char chr);
/// Compare string a to string b (ignoring case) and return whether they are equal.
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
@ -186,10 +185,6 @@ enum ParseOnOffState {
ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr);
// Encode raw data to a human-readable string (for debugging)
std::string hexencode(const uint8_t *data, uint32_t len);
template<typename T> std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); }
// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
template<int...> struct seq {}; // NOLINT
template<int N, int... S> struct gens : gens<N - 1, N - 1, S...> {}; // NOLINT
@ -408,6 +403,77 @@ optional<T> parse_number(const std::string &str) {
return parse_number<T>(str.c_str());
}
/** Parse bytes from a hex-encoded string into a byte array.
*
* When \p len is less than \p 2*count, the result is written to the back of \p data (i.e. this function treats \p str
* as if it were padded with zeros at the front).
*
* @param str String to read from.
* @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed.
* @param data Byte array to write to.
* @param count Length of \p data.
* @return The number of characters parsed from \p str.
*/
size_t parse_hex(const char *str, size_t len, uint8_t *data, size_t count);
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data.
inline bool parse_hex(const char *str, uint8_t *data, size_t count) {
return parse_hex(str, strlen(str), data, count) == 2 * count;
}
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data.
inline bool parse_hex(const std::string &str, uint8_t *data, size_t count) {
return parse_hex(str.c_str(), str.length(), data, count) == 2 * count;
}
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data.
inline bool parse_hex(const char *str, std::vector<uint8_t> &data, size_t count) {
data.resize(count);
return parse_hex(str, strlen(str), data.data(), count) == 2 * count;
}
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data.
inline bool parse_hex(const std::string &str, std::vector<uint8_t> &data, size_t count) {
data.resize(count);
return parse_hex(str.c_str(), str.length(), data.data(), count) == 2 * count;
}
/** Parse a hex-encoded string into an unsigned integer.
*
* @param str String to read from, starting with the most significant byte.
* @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed.
*/
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
optional<T> parse_hex(const char *str, size_t len) {
T val = 0;
if (len > 2 * sizeof(T) || parse_hex(str, len, reinterpret_cast<uint8_t *>(&val), sizeof(T)) == 0)
return {};
return convert_big_endian(val);
}
/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<T> parse_hex(const char *str) {
return parse_hex<T>(str, strlen(str));
}
/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<T> parse_hex(const std::string &str) {
return parse_hex<T>(str.c_str(), str.length());
}
/// Format the byte array \p data of length \p len in lowercased hex.
std::string format_hex(const uint8_t *data, size_t length);
/// Format the vector \p data in lowercased hex.
std::string format_hex(std::vector<uint8_t> data);
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
val = convert_big_endian(val);
return format_hex(reinterpret_cast<uint8_t *>(&val), sizeof(T));
}
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
std::string format_hex_pretty(const uint8_t *data, size_t length);
/// Format the vector \p data in pretty-printed, human-readable hex.
std::string format_hex_pretty(std::vector<uint8_t> data);
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
val = convert_big_endian(val);
return format_hex_pretty(reinterpret_cast<uint8_t *>(&val), sizeof(T));
}
///@}
/// @name Number manipulation
@ -420,4 +486,18 @@ template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max
///@}
/// @name Deprecated functions
///@{
ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1")
inline std::string hexencode(const uint8_t *data, uint32_t len) { return format_hex_pretty(data, len); }
template<typename T>
ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1")
std::string hexencode(const T &data) {
return hexencode(data.data(), data.size());
}
///@}
} // namespace esphome