mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
Every append found the end of the buffer with strlen(), up to four times per append and four appends per field, so building a response cost the square of its size. /discovery fills a 4 KB buffer and spent about 195 ms of its ~210 ms there (shades 126 ms, groups 69 ms), all on the async_tcp task that serves every socket and web request. Keep the length in the formatter and write every append at the end through one helper. The web response and socket event subclasses and ControllerChunker use the tracked length too. Output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
220 lines
7.3 KiB
C++
220 lines
7.3 KiB
C++
#include <unity.h>
|
|
#include "JsonFormatter.h"
|
|
|
|
// The formatter only gets told about the first BUFF_SIZE bytes. The rest is a
|
|
// canary that must never be written.
|
|
#define STORAGE_SIZE 160
|
|
#define BUFF_SIZE 128
|
|
#define CANARY 'X'
|
|
|
|
static char storage[STORAGE_SIZE];
|
|
static BufferedJsonFormatter fmt;
|
|
|
|
static void useBuffer(size_t size) {
|
|
memset(storage, CANARY, sizeof(storage));
|
|
fmt.setBuffer(storage, size);
|
|
}
|
|
static void assertCanary(size_t from) {
|
|
for(size_t i = from; i < STORAGE_SIZE; i++) {
|
|
char msg[48];
|
|
snprintf(msg, sizeof(msg), "wrote past the buffer at byte %u", (unsigned)i);
|
|
TEST_ASSERT_EQUAL_HEX8_MESSAGE(CANARY, storage[i], msg);
|
|
}
|
|
}
|
|
// The JSON written for a single top level string value.
|
|
static const char *escaped(const char *val) {
|
|
useBuffer(BUFF_SIZE);
|
|
fmt.addElem(val);
|
|
return storage;
|
|
}
|
|
|
|
void setUp() { useBuffer(BUFF_SIZE); }
|
|
void tearDown() { assertCanary(BUFF_SIZE); }
|
|
|
|
/*********************************************************************
|
|
* Structure
|
|
********************************************************************/
|
|
static void test_empty_object() {
|
|
fmt.beginObject();
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_STRING("{}", storage);
|
|
}
|
|
static void test_nested_objects_and_arrays() {
|
|
fmt.beginObject();
|
|
fmt.addElem("a", (uint8_t)1);
|
|
fmt.beginObject("b");
|
|
fmt.addElem("c", true);
|
|
fmt.endObject();
|
|
fmt.beginArray("d");
|
|
fmt.addElem((uint8_t)1);
|
|
fmt.addElem((uint8_t)2);
|
|
fmt.beginObject();
|
|
fmt.addElem("e", "x");
|
|
fmt.endObject();
|
|
fmt.endArray();
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_STRING("{\"a\":1,\"b\":{\"c\":true},\"d\":[1,2,{\"e\":\"x\"}]}", storage);
|
|
}
|
|
static void test_array_of_objects() {
|
|
fmt.beginArray();
|
|
fmt.beginObject();
|
|
fmt.endObject();
|
|
fmt.beginObject();
|
|
fmt.endObject();
|
|
fmt.endArray();
|
|
TEST_ASSERT_EQUAL_STRING("[{},{}]", storage);
|
|
}
|
|
static void test_named_object_after_element() {
|
|
fmt.beginObject();
|
|
fmt.addElem("a", (uint8_t)1);
|
|
fmt.beginObject("o");
|
|
fmt.endObject();
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_STRING("{\"a\":1,\"o\":{}}", storage);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Values
|
|
********************************************************************/
|
|
static void test_number_and_bool_values() {
|
|
fmt.beginArray();
|
|
fmt.addElem((int8_t)-5);
|
|
fmt.addElem((uint8_t)255);
|
|
fmt.addElem((int32_t)-100000);
|
|
fmt.addElem((uint32_t)4000000000u);
|
|
fmt.addElem(1.5f);
|
|
fmt.addElem(true);
|
|
fmt.addElem(false);
|
|
fmt.endArray();
|
|
TEST_ASSERT_EQUAL_STRING("[-5,255,-100000,4000000000,1.5000,true,false]", storage);
|
|
}
|
|
static void test_named_uint64() {
|
|
fmt.beginObject();
|
|
fmt.addElem("u", (uint64_t)18446744073709551615ULL);
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_STRING("{\"u\":18446744073709551615}", storage);
|
|
}
|
|
static void test_null_string_is_skipped() {
|
|
fmt.beginObject();
|
|
fmt.addElem("a", (uint8_t)1);
|
|
fmt.addElem("n", (const char *)nullptr);
|
|
fmt.addElem("b", (uint8_t)2);
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_STRING("{\"a\":1,\"b\":2}", storage);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Escaping
|
|
********************************************************************/
|
|
static void test_escapes_special_characters() {
|
|
TEST_ASSERT_EQUAL_STRING("\"plain\"", escaped("plain"));
|
|
TEST_ASSERT_EQUAL_STRING("\"\\\"\"", escaped("\""));
|
|
TEST_ASSERT_EQUAL_STRING("\"\\\\\"", escaped("\\"));
|
|
TEST_ASSERT_EQUAL_STRING("\"\\/\"", escaped("/"));
|
|
TEST_ASSERT_EQUAL_STRING("\"\\n\\t\\r\\b\\f\"", escaped("\n\t\r\b\f"));
|
|
TEST_ASSERT_EQUAL_STRING("\"Living \\\"Room\\\"\"", escaped("Living \"Room\""));
|
|
}
|
|
static void test_escapes_other_control_characters() {
|
|
// Raw control characters are not allowed in JSON strings.
|
|
TEST_ASSERT_EQUAL_STRING("\"a\\u0001b\"", escaped("a\x01" "b"));
|
|
TEST_ASSERT_EQUAL_STRING("\"\\u001f\"", escaped("\x1f"));
|
|
}
|
|
static void test_escaped_length() {
|
|
TEST_ASSERT_EQUAL_UINT32(0, fmt.calcEscapedLength(""));
|
|
TEST_ASSERT_EQUAL_UINT32(2, fmt.calcEscapedLength("ab"));
|
|
TEST_ASSERT_EQUAL_UINT32(4, fmt.calcEscapedLength("a\"b"));
|
|
}
|
|
static void test_escaped_length_counts_first_character() {
|
|
TEST_ASSERT_EQUAL_UINT32(4, fmt.calcEscapedLength("\"ab"));
|
|
TEST_ASSERT_EQUAL_UINT32(2, fmt.calcEscapedLength("/"));
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Buffer limits
|
|
********************************************************************/
|
|
static void test_value_needing_one_byte_too_many_is_refused() {
|
|
// "\/abcdefg" with its quotes is 11 characters, so with the terminator it
|
|
// needs 12 bytes and must not be written into 11.
|
|
useBuffer(11);
|
|
fmt.addElem("/abcdefg");
|
|
TEST_ASSERT_TRUE(strlen(storage) < 11);
|
|
assertCanary(11);
|
|
}
|
|
static void test_overflow_is_flagged() {
|
|
useBuffer(16);
|
|
fmt.beginObject();
|
|
fmt.addElem("name", "fits");
|
|
TEST_ASSERT_FALSE(fmt.overflowed());
|
|
fmt.addElem("long", "this value does not fit");
|
|
TEST_ASSERT_TRUE(fmt.overflowed());
|
|
// What was already written (here up to the comma) stays intact and terminated.
|
|
TEST_ASSERT_EQUAL_STRING("{\"name\":\"fits\",", storage);
|
|
assertCanary(16);
|
|
}
|
|
static void test_setBuffer_clears_overflow() {
|
|
useBuffer(4);
|
|
fmt.addElem("too long");
|
|
TEST_ASSERT_TRUE(fmt.overflowed());
|
|
useBuffer(BUFF_SIZE);
|
|
TEST_ASSERT_FALSE(fmt.overflowed());
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Tracked length
|
|
********************************************************************/
|
|
static void test_length_matches_what_was_written() {
|
|
fmt.beginObject();
|
|
fmt.addElem("name", "Living \"Room\"\n");
|
|
fmt.addElem("pos", 41.5f);
|
|
fmt.beginArray("ids");
|
|
fmt.addElem((uint8_t)1);
|
|
fmt.addElem((int32_t)-2);
|
|
fmt.endArray();
|
|
fmt.addElem("on", true);
|
|
fmt.endObject();
|
|
TEST_ASSERT_EQUAL_UINT32(strlen(storage), fmt.length());
|
|
TEST_ASSERT_EQUAL_STRING("{\"name\":\"Living \\\"Room\\\"\\n\",\"pos\":41.5000,\"ids\":[1,-2],\"on\":true}", storage);
|
|
}
|
|
static void test_refused_append_leaves_length_alone() {
|
|
useBuffer(16);
|
|
fmt.beginObject();
|
|
fmt.addElem("name", "fits");
|
|
const size_t before = fmt.length();
|
|
fmt.addElem("x", "this value does not fit");
|
|
TEST_ASSERT_TRUE(fmt.overflowed());
|
|
// The comma and key still fit; the value was refused whole.
|
|
TEST_ASSERT_EQUAL_UINT32(strlen(storage), fmt.length());
|
|
TEST_ASSERT_TRUE(fmt.length() >= before);
|
|
TEST_ASSERT_TRUE(fmt.length() < 16);
|
|
}
|
|
static void test_setBuffer_resets_length() {
|
|
fmt.addElem("something");
|
|
TEST_ASSERT_TRUE(fmt.length() > 0);
|
|
useBuffer(BUFF_SIZE);
|
|
TEST_ASSERT_EQUAL_UINT32(0, fmt.length());
|
|
TEST_ASSERT_EQUAL_STRING("", storage);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
(void)argc;
|
|
(void)argv;
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_empty_object);
|
|
RUN_TEST(test_nested_objects_and_arrays);
|
|
RUN_TEST(test_array_of_objects);
|
|
RUN_TEST(test_named_object_after_element);
|
|
RUN_TEST(test_number_and_bool_values);
|
|
RUN_TEST(test_named_uint64);
|
|
RUN_TEST(test_null_string_is_skipped);
|
|
RUN_TEST(test_escapes_special_characters);
|
|
RUN_TEST(test_escapes_other_control_characters);
|
|
RUN_TEST(test_escaped_length);
|
|
RUN_TEST(test_escaped_length_counts_first_character);
|
|
RUN_TEST(test_value_needing_one_byte_too_many_is_refused);
|
|
RUN_TEST(test_overflow_is_flagged);
|
|
RUN_TEST(test_setBuffer_clears_overflow);
|
|
RUN_TEST(test_length_matches_what_was_written);
|
|
RUN_TEST(test_refused_append_leaves_length_alone);
|
|
RUN_TEST(test_setBuffer_resets_length);
|
|
return UNITY_END();
|
|
}
|