Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
log_frame.h
Go to the documentation of this file.
1#pragma once
2
3/// @file log_frame.h
4/// @brief Shared frame logging helpers for IO-Homecontrol.
5/// @ingroup hioc_protocol
6
7#include "proto_frame.h"
8#include "redaction.h"
9
10#include "esphome/core/log.h"
11#include <cinttypes>
12#include <cstddef>
13#include <cstdint>
14#include <cstdio>
15
16namespace esphome {
17namespace home_io_control {
18
19inline constexpr size_t FRAME_LOG_HEX_BUFFER_SIZE = 220; ///< Fits a full 32-byte frame rendered as spaced hex text.
20inline constexpr size_t FRAME_LOG_FLAGS_BUFFER_SIZE = 32; ///< Buffer for decoded CTRL1 flag annotations.
21
22inline void bytes_to_hex(const uint8_t *data, uint8_t len, char *out, size_t out_size) {
23 size_t pos = 0;
24 if (out_size == 0)
25 return;
26 out[0] = '\0';
27 for (uint8_t i = 0; i < len && pos + 4 < out_size; i++)
28 pos += snprintf(out + pos, out_size - pos, "%02X ", data[i]);
29}
30
31/// @brief Render decoded CTRL1 flags into a short annotation string.
32/// @param ctrl1 The CTRL1 byte from a frame.
33/// @param out Buffer to write into (at least FRAME_LOG_FLAGS_BUFFER_SIZE bytes).
34/// @param out_size Size of the output buffer.
35inline void decode_ctrl1_flags(uint8_t ctrl1, char *out, size_t out_size) {
36 size_t pos = 0;
37 if (out_size == 0)
38 return;
39 out[0] = '\0';
40 if ((ctrl1 & CTRL1_ACK) != 0)
41 pos += snprintf(out + pos, out_size - pos, "[ACK]");
42 if ((ctrl1 & CTRL1_LOW_POWER) != 0)
43 pos += snprintf(out + pos, out_size - pos, "[LPM]");
44 if ((ctrl1 & CTRL1_PRIORITY) != 0)
45 pos += snprintf(out + pos, out_size - pos, "[PRIO]");
46 if ((ctrl1 & CTRL1_ROUTED) != 0)
47 pos += snprintf(out + pos, out_size - pos, "[R]");
48 if ((ctrl1 & CTRL1_BEACON) != 0)
49 snprintf(out + pos, out_size - pos, "[B]");
50}
51
52/// @brief Render a frame's bytes as spaced hex text, masking the payload when the command
53/// carries key material (see command_carries_key_material()).
54///
55/// Kept outside the IOHOME_FRAME_LOG guard so the redaction behavior is unit-testable
56/// independent of the firmware-only logging build flag.
57///
58/// @warning `IOHOME_UNSAFE_LOG_KEY_MATERIAL` disables the masking below entirely. It exists
59/// solely so a maintainer can capture a genuine pairing exchange for `tests/corpus/` (see
60/// `tests/corpus/README.md`'s key-hygiene section) when the normal `ingest.py --rekey` flow has
61/// no other way to obtain the raw bytes. Never set it outside a deliberate, local,
62/// own-hardware capture session: build with it, capture the one exchange you need, `--rekey`
63/// immediately, then rebuild without it. Never commit a config with it enabled, never use it on
64/// a device whose logs anyone else can see, and never paste output captured under it into an
65/// issue/chat before re-keying. hub_core.cpp's setup() logs a loud warning every boot while this
66/// is defined specifically so an accidentally-left-on build can't stay quiet.
67/// @param data Raw serialized frame bytes (header + payload, no CRC).
68/// @param len Total length of data.
69/// @param out Buffer to write the rendered text into.
70/// @param out_size Size of out.
71inline void render_frame_hex_redacted(const uint8_t *data, uint8_t len, char *out, size_t out_size) {
72 if (out_size == 0)
73 return;
74#ifndef IOHOME_UNSAFE_LOG_KEY_MATERIAL
76 // Sized for FRAME_MIN_SIZE bytes of "XX " hex (not the general FRAME_LOG_HEX_BUFFER_SIZE) so
77 // GCC's -Wformat-truncation can prove the snprintf() below never truncates into `out`.
78 // +4 rather than +1: bytes_to_hex()'s loop guard (`pos + 4 < out_size`) needs headroom beyond
79 // the last byte's own 3 chars, not just space for the content + null terminator.
80 char header_hex[(FRAME_MIN_SIZE * 3) + 4];
81 bytes_to_hex(data, FRAME_MIN_SIZE, header_hex, sizeof(header_hex));
82 snprintf(out, out_size, "%s[%u bytes masked]", header_hex, static_cast<unsigned>(len - FRAME_MIN_SIZE));
83 return;
84 }
85#endif
86 bytes_to_hex(data, len, out, out_size);
87}
88
89#ifdef IOHOME_FRAME_LOG
90inline void log_frame(const char *prefix, const uint8_t *data, uint8_t len, uint32_t freq, uint16_t preamble = 0) {
92 render_frame_hex_redacted(data, len, hex, sizeof(hex));
93
94 // Decode command name and CTRL1 flags for enhanced readability
95 char flags[FRAME_LOG_FLAGS_BUFFER_SIZE] = {0};
96 const char *cmd_str = "";
97 if (len >= FRAME_MIN_SIZE) {
98 decode_ctrl1_flags(data[1], flags, sizeof(flags));
99 cmd_str = command_name(data[FRAME_CMD_OFFSET]);
100 }
101
102 if (preamble > 0)
103 ESP_LOGI("io_frame", "%s [%u bytes] freq=%" PRIu32 " preamble=%u cmd=%s %s: %s", prefix, len, freq, preamble,
104 cmd_str, flags, hex);
105 else
106 ESP_LOGI("io_frame", "%s [%u bytes] freq=%" PRIu32 " cmd=%s %s: %s", prefix, len, freq, cmd_str, flags, hex);
107}
108#endif
109
110} // namespace home_io_control
111} // namespace esphome
void bytes_to_hex(const uint8_t *data, uint8_t len, char *out, size_t out_size)
Definition log_frame.h:22
static constexpr uint8_t FRAME_MIN_SIZE
Minimum frame: CTRL0+CTRL1+DST(3)+SRC(3)+CMD(1).
Definition proto_sizes.h:29
void decode_ctrl1_flags(uint8_t ctrl1, char *out, size_t out_size)
Render decoded CTRL1 flags into a short annotation string.
Definition log_frame.h:35
const char * command_name(uint8_t cmd)
Get a human-readable name for any IO-Homecontrol command ID.
static constexpr uint8_t CTRL1_ROUTED
Bit 6: frame was relayed through a repeater.
Definition proto_frame.h:58
void render_frame_hex_redacted(const uint8_t *data, uint8_t len, char *out, size_t out_size)
Render a frame's bytes as spaced hex text, masking the payload when the command carries key material ...
Definition log_frame.h:71
constexpr size_t FRAME_LOG_HEX_BUFFER_SIZE
Fits a full 32-byte frame rendered as spaced hex text.
Definition log_frame.h:19
constexpr size_t FRAME_LOG_FLAGS_BUFFER_SIZE
Buffer for decoded CTRL1 flag annotations.
Definition log_frame.h:20
static constexpr uint8_t CTRL1_PRIORITY
Bit 2: high-priority frame.
Definition proto_frame.h:55
static constexpr uint8_t CTRL1_ACK
Bit 4: sender can handle 2W responses (ACK-capable).
Definition proto_frame.h:56
bool command_carries_key_material(uint8_t cmd)
Whether a frame command's payload carries key material that must never be logged or reported verbatim...
Definition redaction.h:46
static constexpr uint8_t CTRL1_LOW_POWER
Bit 5: low-power device (e.g., solar-powered).
Definition proto_frame.h:57
static constexpr uint8_t CTRL1_BEACON
Bit 7: beacon announcement frame.
Definition proto_frame.h:59
static constexpr uint8_t FRAME_CMD_OFFSET
Byte offset of the command ID in a raw wire buffer.
Definition proto_sizes.h:32
IO-Homecontrol 2W frame container: control bytes, IoFrame and (de)serialization.
Key-material redaction helpers shared by every surface that can emit frame bytes or debug text (frame...