[api] Add max_data_length and force to DeviceInfoResponse/HelloResponse proto fields

Add max_data_length + force proto options to string fields in
HelloResponse, AreaInfo, DeviceInfo, and DeviceInfoResponse where
we have strong compile-time or codegen guarantees on max length.

This enables encode_short_string_force() (direct byte writes,
no varint branching) for fields with single-byte tags, and
fixed-formula size calculations for all annotated fields.

Add static_asserts to cross-validate C++ constexpr constants
(MAC_ADDRESS_PRETTY_BUFFER_SIZE, BUILD_TIME_STR_SIZE,
ESPHOME_VERSION, ESPHOME_MANUFACTURER) against the proto
max_data_length annotations.
This commit is contained in:
J. Nick Koston
2026-04-06 18:03:26 -10:00
parent e49384cd57
commit 95974358ab
3 changed files with 50 additions and 34 deletions
+21 -12
View File
@@ -129,11 +129,12 @@ message HelloResponse {
// A string identifying the server (ESP); like client info this may be empty
// and only exists for debugging/logging purposes.
// For example "ESPHome v1.10.0 on ESP8266"
string server_info = 3;
// Currently set to ESPHOME_VERSION string literal.
string server_info = 3 [(max_data_length) = 32, (force) = true];
// The name of the server (App.get_name())
string name = 4;
// max_data_length matches config_validation.NAME_MAX_LENGTH
string name = 4 [(max_data_length) = 120, (force) = true];
}
// DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported.
@@ -196,12 +197,14 @@ message DeviceInfoRequest {
message AreaInfo {
uint32 area_id = 1;
string name = 2;
// max_data_length matches core/config.FRIENDLY_NAME_MAX_LEN via AREA_SCHEMA
string name = 2 [(max_data_length) = 120, (force) = true];
}
message DeviceInfo {
uint32 device_id = 1;
string name = 2;
// max_data_length matches core/config.FRIENDLY_NAME_MAX_LEN via DEVICE_SCHEMA
string name = 2 [(max_data_length) = 120, (force) = true];
uint32 area_id = 3;
}
@@ -216,6 +219,12 @@ message SerialProxyInfo {
SerialProxyPortType port_type = 2; // Port type (RS232, RS485)
}
// DeviceInfoResponse max_data_length values:
// name/friendly_name = 120 (config_validation.NAME_MAX_LENGTH / core/config.FRIENDLY_NAME_MAX_LEN)
// mac_address/bluetooth_mac_address = 17 (MAC_ADDRESS_PRETTY_BUFFER_SIZE - 1, constexpr)
// esphome_version = 32 (ESPHOME_VERSION string literal)
// compilation_time = 25 (Application::BUILD_TIME_STR_SIZE - 1, constexpr)
// manufacturer = 20 (longest hardcoded literal: "Nordic Semiconductor")
message DeviceInfoResponse {
option (id) = 10;
option (source) = SOURCE_SERVER;
@@ -225,18 +234,18 @@ message DeviceInfoResponse {
bool uses_password = 1 [deprecated = true];
// The name of the node, given by "App.set_name()"
string name = 2;
string name = 2 [(max_data_length) = 120, (force) = true];
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
string mac_address = 3;
string mac_address = 3 [(max_data_length) = 17, (force) = true];
// A string describing the ESPHome version. For example "1.10.0"
string esphome_version = 4;
string esphome_version = 4 [(max_data_length) = 32, (force) = true];
// A string describing the date of compilation, this is generated by the compiler
// and therefore may not be in the same format all the time.
// If the user isn't using ESPHome, this will also not be set.
string compilation_time = 5;
string compilation_time = 5 [(max_data_length) = 25, (force) = true];
// The model of the board. For example NodeMCU
string model = 6;
@@ -253,9 +262,9 @@ message DeviceInfoResponse {
uint32 legacy_bluetooth_proxy_version = 11 [deprecated=true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
string manufacturer = 12;
string manufacturer = 12 [(max_data_length) = 20, (force) = true];
string friendly_name = 13;
string friendly_name = 13 [(max_data_length) = 120, (force) = true];
// Deprecated in API version 1.10
uint32 legacy_voice_assistant_version = 14 [deprecated=true, (field_ifdef) = "USE_VOICE_ASSISTANT"];
@@ -264,7 +273,7 @@ message DeviceInfoResponse {
string suggested_area = 16 [(field_ifdef) = "USE_AREAS"];
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
string bluetooth_mac_address = 18 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
string bluetooth_mac_address = 18 [(max_data_length) = 17, (force) = true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
// Supports receiving and saving api encryption key
bool api_encryption_supported = 19 [(field_ifdef) = "USE_API_NOISE"];
@@ -72,6 +72,12 @@ static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 60000;
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
// Cross-validate C++ constants against proto max_data_length annotations in api.proto
static_assert(MAC_ADDRESS_PRETTY_BUFFER_SIZE - 1 == 17,
"Update max_data_length for mac_address/bluetooth_mac_address in api.proto");
static_assert(Application::BUILD_TIME_STR_SIZE - 1 == 25, "Update max_data_length for compilation_time in api.proto");
static_assert(sizeof(ESPHOME_VERSION) - 1 <= 32, "Update max_data_length for esphome_version in api.proto");
static const char *const TAG = "api.connection";
#ifdef USE_CAMERA
static const int CAMERA_STOP_STREAM = 5000;
@@ -1716,6 +1722,7 @@ bool APIConnection::send_device_info_response_() {
static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
resp.manufacturer = MANUFACTURER;
#endif
static_assert(sizeof(ESPHOME_MANUFACTURER) - 1 <= 20, "Update max_data_length for manufacturer in api.proto");
#undef ESPHOME_MANUFACTURER
#ifdef USE_ESP8266
+22 -22
View File
@@ -35,29 +35,29 @@ uint8_t *HelloResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->api_version_major);
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->api_version_minor);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 3, this->server_info);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->name);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->server_info);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 34, this->name);
return pos;
}
uint32_t HelloResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->api_version_major);
size += ProtoSize::calc_uint32(1, this->api_version_minor);
size += ProtoSize::calc_length(1, this->server_info.size());
size += ProtoSize::calc_length(1, this->name.size());
size += 2 + this->server_info.size();
size += 2 + this->name.size();
return size;
}
#ifdef USE_AREAS
uint8_t *AreaInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->area_id);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->name);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name);
return pos;
}
uint32_t AreaInfo::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->area_id);
size += ProtoSize::calc_length(1, this->name.size());
size += 2 + this->name.size();
return size;
}
#endif
@@ -65,14 +65,14 @@ uint32_t AreaInfo::calculate_size() const {
uint8_t *DeviceInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->device_id);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->name);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name);
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->area_id);
return pos;
}
uint32_t DeviceInfo::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_uint32(1, this->device_id);
size += ProtoSize::calc_length(1, this->name.size());
size += 2 + this->name.size();
size += ProtoSize::calc_uint32(1, this->area_id);
return size;
}
@@ -93,10 +93,10 @@ uint32_t SerialProxyInfo::calculate_size() const {
#endif
uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
uint8_t *__restrict__ pos = buffer.get_pos();
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->name);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 3, this->mac_address);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->esphome_version);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->compilation_time);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->mac_address);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 34, this->esphome_version);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 42, this->compilation_time);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, this->model);
#ifdef USE_DEEP_SLEEP
ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->has_deep_sleep);
@@ -113,8 +113,8 @@ uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_
#ifdef USE_BLUETOOTH_PROXY
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 15, this->bluetooth_proxy_feature_flags);
#endif
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 12, this->manufacturer);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 13, this->friendly_name);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 98, this->manufacturer);
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 106, this->friendly_name);
#ifdef USE_VOICE_ASSISTANT
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 17, this->voice_assistant_feature_flags);
#endif
@@ -122,7 +122,7 @@ uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 16, this->suggested_area);
#endif
#ifdef USE_BLUETOOTH_PROXY
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 18, this->bluetooth_mac_address);
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 18, this->bluetooth_mac_address, true);
#endif
#ifdef USE_API_NOISE
ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 19, this->api_encryption_supported);
@@ -155,10 +155,10 @@ uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_
}
uint32_t DeviceInfoResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_length(1, this->mac_address.size());
size += ProtoSize::calc_length(1, this->esphome_version.size());
size += ProtoSize::calc_length(1, this->compilation_time.size());
size += 2 + this->name.size();
size += 2 + this->mac_address.size();
size += 2 + this->esphome_version.size();
size += 2 + this->compilation_time.size();
size += ProtoSize::calc_length(1, this->model.size());
#ifdef USE_DEEP_SLEEP
size += ProtoSize::calc_bool(1, this->has_deep_sleep);
@@ -175,8 +175,8 @@ uint32_t DeviceInfoResponse::calculate_size() const {
#ifdef USE_BLUETOOTH_PROXY
size += ProtoSize::calc_uint32(1, this->bluetooth_proxy_feature_flags);
#endif
size += ProtoSize::calc_length(1, this->manufacturer.size());
size += ProtoSize::calc_length(1, this->friendly_name.size());
size += 2 + this->manufacturer.size();
size += 2 + this->friendly_name.size();
#ifdef USE_VOICE_ASSISTANT
size += ProtoSize::calc_uint32(2, this->voice_assistant_feature_flags);
#endif
@@ -184,7 +184,7 @@ uint32_t DeviceInfoResponse::calculate_size() const {
size += ProtoSize::calc_length(2, this->suggested_area.size());
#endif
#ifdef USE_BLUETOOTH_PROXY
size += ProtoSize::calc_length(2, this->bluetooth_mac_address.size());
size += 3 + this->bluetooth_mac_address.size();
#endif
#ifdef USE_API_NOISE
size += ProtoSize::calc_bool(2, this->api_encryption_supported);