Leave room for the answer: ALEX2ESP_MAX_MESSAGE defaults to ALEX2ESP_MAX_DIRECTIVE + 1024

Both limits were 2047 bytes. An answer repeats the correlationToken of its directive, which is most of a large directive, and adds 140 to 170 bytes for every property. So a directive between about 1,750 and 2,047 bytes was accepted, the sketch acted on it, and the answer was refused: the lamp switched and Alexa reported a device that does not respond.

The limit for messages now follows the limit for directives unless it is set: 2047 + 1024 = 3071 bytes, room for six properties on top of the largest directive. Both defines moved to src/AlexaLimits.h, next to each other, with the relation in the comment. The limit also applies to the discovery object of a device, which may now be 3071 bytes.

For a sketch: a report between 2,048 and 3,071 bytes is sent, where it was refused with an error. A project that sets ALEX2ESP_MAX_MESSAGE keeps its value.

Measured with the bridge built for the host against a fake MQTT client (not in the repository), a TurnOn of 2,047 bytes answered with two properties:
  8ea7778      handler ran, send() false, "2374 bytes not sent, the limit is 2047"
  this commit  handler ran, send() true, 2,374 bytes published

Tests: test_largest_directive_can_be_answered puts a directive of ALEX2ESP_MAX_DIRECTIVE bytes through the receive buffer, builds the Response of a lamp to it and checks it against ALEX2ESP_MAX_MESSAGE, also with six properties. 33 host tests pass.

examples/basicLight.cpp for d1_mini, static RAM / flash in bytes: 34,116 / 336,757 -> 34,116 / 336,757, no warnings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 15:38:46 +00:00
parent 46a27c26e9
commit e48f8580f9
5 changed files with 102 additions and 17 deletions

View file

@ -7,17 +7,19 @@
#include <string.h>
#include <string>
#include "AlexaBridgeLogic.h"
#include "AlexaLimits.h"
typedef AlexaDirectiveBuffer::Result Result;
#define ASSERT_RESULT(expected, actual) TEST_ASSERT_EQUAL_INT(static_cast<int>(expected), static_cast<int>(actual))
// A directive as Alex2MQTT publishes it on <root>/<endpointId>/alexaDirective
static std::string directive(const char *name, const char *messageId)
static std::string directive(const char *name, const char *messageId,
const std::string &correlationToken = "AAAAAAAAAQBnlqNbYnB0dHNmYW5zbGF0ZQ==")
{
return std::string("{\"header\":{\"namespace\":\"Alexa.PowerController\",\"name\":\"") + name +
"\",\"payloadVersion\":\"3\",\"messageId\":\"" + messageId +
"\",\"correlationToken\":\"AAAAAAAAAQBnlqNbYnB0dHNmYW5zbGF0ZQ==\"},"
"\",\"correlationToken\":\"" + correlationToken + "\"},"
"\"endpoint\":{\"endpointId\":\"ESP-01\",\"cookie\":{}},\"payload\":{}}";
}
@ -444,6 +446,74 @@ void test_document_that_ran_out_of_memory_is_not_published()
TEST_ASSERT_EQUAL_UINT(0, length); // 0 tells the two reasons for TOO_LARGE apart
}
// --- the limit of a directive and the limit of its answer ---
// One property of a report as AlexaStatusMessage::AddProperty writes it
static void addProperty(JsonArray properties, const char *interfaceName, const char *name, const char *value)
{
JsonObject property = properties.add<JsonObject>();
property["namespace"] = interfaceName;
property["name"] = name;
property["value"] = value;
property["timeOfSample"] = "2026-09-28T13:05:09Z";
property["uncertaintyInMilliseconds"] = 0;
}
// The Response of a lamp as AlexaStatusMessage builds it (src/AlexaStatusMessage.cpp): the correlationToken of the
// directive, the health of the endpoint and the power state
static void buildResponse(JsonDocument &answer, const JsonDocument &received)
{
JsonObject header = answer["event"]["header"].to<JsonObject>();
header["namespace"] = "Alexa";
header["name"] = "Response";
header["payloadVersion"] = "3";
header["messageId"] = "OQpZQ2l8Pr2f9kkS8g6ffwpx7bJgJARngGUEQ"; // 37 characters, as generateMessageId() returns
header["correlationToken"] = received["header"]["correlationToken"];
answer["event"]["endpoint"]["endpointId"] = received["endpoint"]["endpointId"];
answer["event"]["payload"].to<JsonObject>();
JsonArray properties = answer["context"]["properties"].to<JsonArray>();
JsonObject health = properties.add<JsonObject>();
health["namespace"] = "Alexa.EndpointHealth";
health["name"] = "connectivity";
health["value"]["value"] = "OK";
health["timeOfSample"] = "2026-09-28T13:05:09Z";
health["uncertaintyInMilliseconds"] = 0;
addProperty(properties, "Alexa.PowerController", "powerState", "ON");
}
void test_largest_directive_can_be_answered()
{
// The correlationToken is what makes a directive large, and the answer repeats it
std::string withoutToken = directive("TurnOn", ID_1, "");
std::string largest = directive("TurnOn", ID_1, std::string(ALEX2ESP_MAX_DIRECTIVE - withoutToken.size(), 'T'));
TEST_ASSERT_EQUAL_UINT(ALEX2ESP_MAX_DIRECTIVE, largest.size());
AlexaDirectiveBuffer buffer(ALEX2ESP_MAX_DIRECTIVE);
ASSERT_RESULT(Result::COMPLETE, deliver(buffer, largest, 536));
JsonDocument received;
TEST_ASSERT_TRUE(deserializeJson(received, buffer.data(), buffer.length()) == DeserializationError::Ok);
buffer.release();
JsonDocument answer;
buildResponse(answer, received);
size_t length = 0;
ASSERT_RESULT(AlexaSendResult::OK, AlexaBridgeLogic::checkMessage(answer, ALEX2ESP_MAX_MESSAGE, &length));
// The answer is the larger of the two: a limit for messages as low as the limit for directives refuses it,
// after the sketch has switched the lamp
TEST_ASSERT_GREATER_THAN_UINT(ALEX2ESP_MAX_DIRECTIVE, length);
ASSERT_RESULT(AlexaSendResult::TOO_LARGE, AlexaBridgeLogic::checkMessage(answer, ALEX2ESP_MAX_DIRECTIVE, &length));
// The default leaves room for six properties
JsonArray properties = answer["context"]["properties"];
addProperty(properties, "Alexa.BrightnessController", "brightness", "100");
addProperty(properties, "Alexa.ColorTemperatureController", "colorTemperatureInKelvin", "2700");
addProperty(properties, "Alexa.ToggleController", "toggleState", "ON");
addProperty(properties, "Alexa.ToggleController", "toggleState", "OFF");
TEST_ASSERT_EQUAL_UINT(6, properties.size());
ASSERT_RESULT(AlexaSendResult::OK, AlexaBridgeLogic::checkMessage(answer, ALEX2ESP_MAX_MESSAGE, &length));
}
// --- topics ---
void test_endpoint_is_taken_from_the_directive_topic()
@ -564,6 +634,8 @@ int main(int, char **)
RUN_TEST(test_empty_document_is_not_published);
RUN_TEST(test_document_that_ran_out_of_memory_is_not_published);
RUN_TEST(test_largest_directive_can_be_answered);
RUN_TEST(test_endpoint_is_taken_from_the_directive_topic);
RUN_TEST(test_other_topics_are_not_directive_topics);