mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
When something didn't fit, the formatters skipped it silently, closing brackets included, and clients got malformed JSON. Two writes could also go past the buffer: - AsyncJsonResp flushed when full and then wrote the value without checking again, so a value bigger than the whole buffer overflowed. It is now streamed straight out instead. - JsonSockEvent::closeEvent added the closing ] when only one byte was left, putting the terminator one byte past the buffer. The formatters now set an overflowed() flag and log an error, and a socket event that overflowed is logged and not sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
5.9 KiB
C++
181 lines
5.9 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());
|
|
}
|
|
|
|
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);
|
|
return UNITY_END();
|
|
}
|