From beef75f31c6f78055897f9d6473212d7ee54d9b8 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 22 Jul 2025 14:31:24 +0200 Subject: [PATCH] Added Message Builder with Tests --- main/main.c | 77 +++++-- main/message_builder.c | 76 ++++++ main/message_builder.h | 18 ++ tests/.gitignore | 3 + tests/Makefile | 7 + tests/test_message_builder.c | 433 +++++++++++++++++++++++++++++++++++ 6 files changed, 594 insertions(+), 20 deletions(-) create mode 100644 main/message_builder.c create mode 100644 main/message_builder.h create mode 100644 tests/.gitignore create mode 100644 tests/test_message_builder.c diff --git a/main/main.c b/main/main.c index c191a44..062816e 100644 --- a/main/main.c +++ b/main/main.c @@ -7,6 +7,8 @@ #include "esp_wifi.h" #include "freertos/idf_additions.h" #include "hal/uart_types.h" +#include "message_handler.h" +#include "message_parser.h" #include "nvs_flash.h" #include "communication_handler.h" @@ -14,22 +16,44 @@ #include "portmacro.h" #include "uart_handler.h" #include +#include +#include +#include +#include + +#include "message_builder.h" static const char *TAG = "ALOX - MAIN"; +static const uint16_t version = 0x0001; -void SendClientInfoTask() { - int clientId = 0; - while (1) { - ClientInfo info = *get_client_info(clientId); - send_client_info(clientId, info.isAvailable, info.lastPing); - clientId += 1; - clientId = clientId%20; - vTaskDelay(100 / portTICK_PERIOD_MS); - } +void echoCallback(uint8_t msgid, const uint8_t *payload, size_t payload_len) { + // Code für den Button-Press + ESP_LOGI(TAG, "Echo command 0x01..."); + uint8_t send_buffer[64]; + size_t len = build_message(0x01, payload, payload_len, send_buffer, + sizeof(send_buffer)); + uart_write_bytes(MASTER_UART, send_buffer, len); +} + +void versionCallback(uint8_t msgid, const uint8_t *payload, + size_t payload_len) { + // Code für den Button-Press + ESP_LOGI(TAG, "Version command 0x02..."); + uint8_t send_buffer[64]; + size_t git_build_hash_len = strlen(BUILD_GIT_HASH); + + uint8_t send_payload[2 + git_build_hash_len]; + send_payload[0] = (uint8_t)(version & 0xFF); + send_payload[1] = (uint8_t)((version >> 8) & 0xFF); + memcpy(&send_payload[2], &BUILD_GIT_HASH, git_build_hash_len); + + size_t len = build_message(0x02, send_payload, sizeof(send_payload), + send_buffer, sizeof(send_buffer)); + uart_write_bytes(MASTER_UART, send_buffer, len); } void app_main(void) { -ESP_LOGI(TAG, "Starting Alox Powerpod Version %d Build: %s", version, + ESP_LOGI(TAG, "Starting Alox Powerpod Version %d Build: %s", version, BUILD_GIT_HASH); esp_err_t ret = nvs_flash_init(); @@ -74,17 +98,30 @@ ESP_LOGI(TAG, "Starting Alox Powerpod Version %d Build: %s", version, if (isMaster) { ESP_LOGI(TAG, "Started in Mastermode"); add_peer(broadcast_address); - xTaskCreate(master_broadcast_task, "MasterBroadcast", 4096, NULL, 1, NULL); - // xTaskCreate(master_ping_task, "MasterPing", 4096, NULL, 1, NULL); - xTaskCreate(master_broadcast_ping, "MasterBroadcastPing", 4096, NULL, 1, - NULL); - xTaskCreate(client_monitor_task, "MonitorClientTask", 4096, NULL, 1, NULL); - init_uart(); - //xTaskCreate(uart_status_task, "MasterUartStatusTask", 4096, NULL, 1, NULL); - xTaskCreate(SendClientInfoTask, "SendCientInfo", 4096, NULL, 1, NULL); + // xTaskCreate(master_broadcast_task, "MasterBroadcast", 4096, NULL, 1, + // NULL); + // xTaskCreate(master_ping_task, "MasterPing", 4096, NULL, 1, NULL); + // xTaskCreate(master_broadcast_ping, "MasterBroadcastPing", 4096, NULL, 1, + // NULL); + // xTaskCreate(client_monitor_task, "MonitorClientTask", 4096, NULL, 1, + // NULL); + QueueHandle_t parsed_message_queue = + xQueueCreate(10, sizeof(ParsedMessage_t)); + init_uart(parsed_message_queue); + InitMessageBroker(); + + xTaskCreate(MessageBrokerTask, "message_handler_task", 4096, + (void *)&parsed_message_queue, 5, NULL); + + RegisterCallback(0x01, echoCallback); + RegisterCallback(0x02, versionCallback); + + // xTaskCreate(uart_status_task, "MasterUartStatusTask", 4096, NULL, 1, + // NULL); xTaskCreate(SendClientInfoTask, "SendCientInfo", 4096, NULL, 1, + // NULL); } else { ESP_LOGI(TAG, "Started in Slavemode"); - xTaskCreate(client_data_sending_task, "ClientDataSending", 4096, NULL, 1, - NULL); + //xTaskCreate(client_data_sending_task, "ClientDataSending", 4096, NULL, 1, + // NULL); } } diff --git a/main/message_builder.c b/main/message_builder.c new file mode 100644 index 0000000..e29b8c5 --- /dev/null +++ b/main/message_builder.c @@ -0,0 +1,76 @@ +#include "message_builder.h" +#include "message_parser.h" +#include +#include + +bool needs_stuffing_byte(uint8_t byte) { + return (byte == StartByte || byte == EscapeByte || byte == EndByte); +} + +bool add_byte_with_length_check(uint8_t byte, uint8_t write_index, + uint8_t *data, uint8_t max_length) { + if (write_index >= max_length) { + return false; + } + data[write_index] = byte; + return true; +} + +int build_message(uint8_t msgid, const uint8_t *payload, size_t payload_len, + uint8_t *msg_buffer, size_t msg_buffer_size) { + + if (payload_len + 4 > msg_buffer_size) { + return PayloadBiggerThenBuffer; + } + + uint8_t checksum = 0; + size_t write_index = 0; + msg_buffer[write_index++] = StartByte; + + if (needs_stuffing_byte(msgid)) { + if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer, + msg_buffer_size)) { + return BufferOverFlow; + } + write_index++; + } + if (!add_byte_with_length_check(msgid, write_index, msg_buffer, + msg_buffer_size)) { + return BufferOverFlow; + } + write_index++; + checksum ^= msgid; + + for (size_t i = 0; i < payload_len; i++) { + if (needs_stuffing_byte(payload[i])) { + if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer, + msg_buffer_size)) { + return BufferOverFlow; + } + write_index++; + } + if (!add_byte_with_length_check(payload[i], write_index, msg_buffer, + msg_buffer_size)) { + return BufferOverFlow; + } + + write_index++; + checksum ^= payload[i]; + } + + if (needs_stuffing_byte(checksum)) { + if (!add_byte_with_length_check(EscapeByte, write_index, msg_buffer, + msg_buffer_size)) { + return BufferOverFlow; + } + write_index++; + } + + if (write_index + 2 > msg_buffer_size) { + return BufferOverFlow; + } + msg_buffer[write_index++] = checksum; + msg_buffer[write_index++] = EndByte; + + return write_index; +} diff --git a/main/message_builder.h b/main/message_builder.h new file mode 100644 index 0000000..1122998 --- /dev/null +++ b/main/message_builder.h @@ -0,0 +1,18 @@ +#ifndef _MESSAGE_BUILDER_HEADER +#define _MESSAGE_BUILDER_HEADER + +#include "message_parser.h" +#include +#include + +enum BuildMessageErrors { + NoBuildError = 0, + PayloadBiggerThenBuffer = -1, + BufferOverFlow = -2, +}; + +// returns the length of msg_buffer +int build_message(uint8_t msgid, const uint8_t *payload, size_t payload_len, + uint8_t *msg_buffer, size_t msg_buffer_length); + +#endif diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..f8f73b7 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +Unity/* +test_runner +test_builder diff --git a/tests/Makefile b/tests/Makefile index 859c6cf..64c0706 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -4,11 +4,18 @@ SRC = Unity/src/unity.c test_parser.c \ ../main/message_parser.c TARGET = test_runner +.PHONY: test_builder + all: $(TARGET) $(TARGET): $(SRC) $(CC) $(CFLAGS) -o $@ $^ ./$(TARGET) +test_builder: Unity/src/unity.c test_message_builder.c ../main/message_parser.c ../main/message_builder.c + $(CC) $(CFLAGS) -o $@ $^ + @echo "--- Running Builder Tests ---" + ./$(BUILDER_TEST_TARGET) + clean: rm -f $(TARGET) diff --git a/tests/test_message_builder.c b/tests/test_message_builder.c new file mode 100644 index 0000000..28def92 --- /dev/null +++ b/tests/test_message_builder.c @@ -0,0 +1,433 @@ +#include "Unity/src/unity.h" +#include "message_builder.h" // Stellt sicher, dass deine Header-Datei message_parser.h die Definitionen für build_message, StartByte, EscapeByte, EndByte, MAX_MESSAGE_PAYLOAD_LENGTH und Fehlercodes enthält +#include +#include // Für memcpy, memset + +// --- Globale Konstanten (Annahmen aus vorheriger Konversation) --- +// Wenn diese in message_parser.h nicht definiert sind, musst du sie hier +// definieren: #define StartByte 0xAA #define EscapeByte 0xBB #define EndByte +// 0xCC + +// #define MAX_MESSAGE_PAYLOAD_LENGTH 250 // Beispiel: Max. Payload-Länge + +// Fehlercodes für den Builder (sollten mit deiner build_message Implementierung +// übereinstimmen) #define BUILD_ERROR_BUFFER_TOO_SMALL_INITIAL_CHECK -1 #define +// BUILD_ERROR_BUFFER_OVERFLOW -2 #define PayloadBiggerThenBuffer -3 + +// --- UNITY SETUP/TEARDOWN --- +void setUp(void) { + // Nichts Besonderes für den Builder-Test zu resetten +} + +void tearDown(void) {} // optional + +static bool needs_stuffing_byte(uint8_t byte) { + return (byte == StartByte || byte == EscapeByte || byte == EndByte); +} + +// --- Hilfsfunktion zur Berechnung des *zu sendenden* Checksummen-Bytes --- +// Dies ist der Wert, der im Frame an der Checksummen-Position steht, +// sodass die finale XOR-Summe der Nutzdaten (MSGID + Payload + dieses Byte) +// 0x00 ergibt. +uint8_t calculate_payload_checksum_byte(uint8_t msgid, const uint8_t *payload, + size_t payload_len) { + uint8_t cs = msgid; + for (size_t i = 0; i < payload_len; ++i) { + cs ^= payload[i]; + } + return cs; // Dies ist der Wert, der gesendet werden muss +} + +// --- Hilfsfunktion zum Vergleichen von Hex-Arrays und Debug-Ausgabe --- +void TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(const uint8_t *expected, + const uint8_t *actual, size_t len) { + char expected_str[len * 3 + 1]; + char actual_str[len * 3 + 1]; + int offset_exp = 0; + int offset_act = 0; + + for (size_t i = 0; i < len; ++i) { + offset_exp += + snprintf(expected_str + offset_exp, sizeof(expected_str) - offset_exp, + "%02X ", expected[i]); + offset_act += snprintf(actual_str + offset_act, + sizeof(actual_str) - offset_act, "%02X ", actual[i]); + } + // Stelle sicher, dass die Strings nullterminiert sind, falls der Puffer genau + // gefüllt wurde + if (offset_exp > 0) + expected_str[offset_exp - 1] = '\0'; + else + expected_str[0] = '\0'; // Remove last space, null-terminate + if (offset_act > 0) + actual_str[offset_act - 1] = '\0'; + else + actual_str[0] = '\0'; + + printf("\n"); // Neue Zeile für bessere Lesbarkeit + printf(" Expected: %s\n", expected_str); + printf(" Actual: %s\n", actual_str); + + TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len); +} + +// --- TESTFÄLLE FÜR build_message --- + +// Test 1: Gültige Nachricht ohne Escaping +void test_builder_1_basic_message_no_escaping(void) { + uint8_t msgid = 0x01; + uint8_t payload[] = {0x10, 0x20, 0x30, 0x40}; + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; // Ausreichend großer Puffer + size_t output_buffer_size = sizeof(output_buffer); + + // Erwarteter Checksummen-Wert + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + // Erwartete fertige Nachricht + uint8_t expected_message[] = { + StartByte, msgid, 0x10, 0x20, 0x30, 0x40, // Payload + expected_checksum, EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 2: Gültige Nachricht mit leerem Payload +void test_builder_2_empty_payload(void) { + uint8_t msgid = 0x02; + uint8_t payload[] = {}; + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + uint8_t expected_message[] = {StartByte, msgid, expected_checksum, EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 3: MSGID muss escapet werden (StartByte als MSGID) +void test_builder_3_escaped_msgid(void) { + uint8_t msgid = StartByte; // MSGID = 0xAA + uint8_t payload[] = {0x11, 0x22}; + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + uint8_t expected_message[] = {StartByte, + EscapeByte, + StartByte, // Escaped MSGID + 0x11, + 0x22, // Payload + expected_checksum, + EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 4: Payload-Byte muss escapet werden (EndByte im Payload) +void test_builder_4_escaped_payload_byte(void) { + uint8_t msgid = 0x04; + uint8_t payload[] = {0x01, EndByte, 0x03}; // EndByte = 0xCC im Payload + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + uint8_t expected_message[] = {StartByte, msgid, + 0x01, EscapeByte, + EndByte, // Escaped payload byte + 0x03, expected_checksum, + EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 5: Checksummen-Byte muss escapet werden (EscapeByte als Checksumme) +void test_builder_5_escaped_checksum_byte(void) { + uint8_t msgid = 0x05; + // Payload so wählen, dass msgid ^ payload[0] = EscapeByte (0xBB) + uint8_t payload[] = {msgid ^ EscapeByte}; // Payload ist 0x05 ^ 0xBB = 0xBE + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + TEST_ASSERT_EQUAL_UINT8(EscapeByte, expected_checksum); // Sanity check + + uint8_t expected_message[] = {StartByte, + msgid, + payload[0], + EscapeByte, + expected_checksum, // Escaped checksum byte + EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); +} + +// Test 6: Mehrere Escapings in einer Nachricht +void test_builder_6_multiple_escapings(void) { + uint8_t msgid = StartByte; // 0xAA + uint8_t payload[] = {EscapeByte, 0x01, EndByte, StartByte, 0x02}; + size_t payload_len = sizeof(payload); + + uint8_t output_buffer[64]; + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + // Angenommen, die berechnete Checksumme selbst ist KEIN Steuerzeichen, + // sonst müsste sie auch escapet werden. + // Wenn doch, würde expected_message noch ein EscapeByte vor der Checksumme + // bekommen. + + uint8_t expected_message[] = { + StartByte, EscapeByte, StartByte, // Escaped MSGID + EscapeByte, EscapeByte, // Escaped payload[0] + 0x01, EscapeByte, EndByte, // Escaped payload[2] + EscapeByte, StartByte, // Escaped payload[3] + 0x02, expected_checksum, EndByte}; + size_t expected_len = sizeof(expected_message); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 7: Puffer zu klein - initiale Prüfung (Payload ist zu lang für den +// Puffer) +void test_builder_7_buffer_too_small_initial(void) { + uint8_t msgid = 0x07; + // Payload, das auch im besten Fall (ohne Stuffing) nicht in einen + // 10-Byte-Puffer passt. Minimale Länge wäre 1+1+5+1+1 = 9 Bytes (Start, ID, + // Payload, CRC, End) + uint8_t payload[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + size_t payload_len = sizeof(payload); // 8 Bytes + + uint8_t output_buffer[10]; // Ein Puffer, der zu klein ist für 8 Payload-Bytes + // + Rahmung + CRC + size_t output_buffer_size = sizeof(output_buffer); // 10 Bytes + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + // Die Berechnung für PayloadBiggerThenBuffer sollte hier greifen. + // payload_len * 2 + 5 (worst case) = 8*2+5 = 21. 21 ist > 10. + TEST_ASSERT_EQUAL_INT(PayloadBiggerThenBuffer, actual_len); +} + +// Test 8: Puffer zu klein - Überlauf beim Schreiben (knapper Puffer, z.B. bei +// Stuffing) +void test_builder_8_buffer_overflow_during_build(void) { + uint8_t msgid = 0x08; + // Payload so wählen, dass Stuffing stattfindet + uint8_t payload[] = {0x01, StartByte, 0x02}; // StartByte im Payload + size_t payload_len = sizeof(payload); // 3 Bytes + + // Minimaler Puffer für diese Nachricht OHNE Stuffing: + // Start (1) + MSGID (1) + Payload (3) + CRC (1) + End (1) = 7 Bytes + // Mit Stuffing für payload[1] (StartByte) wird es: + // 1 (Start) + 1 (MSGID) + 1 (0x01) + 2 (Escape+StartByte) + 1 (0x02) + 1 + // (CRC) + 1 (End) = 8 Bytes + uint8_t output_buffer[7]; // Puffer ist 1 Byte zu klein für die gestuffte + // Nachricht + size_t output_buffer_size = sizeof(output_buffer); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(BufferOverFlow, actual_len); +} + +// Test 9: Puffer genau groß genug (Randfall) +void test_builder_9_buffer_just_enough(void) { + uint8_t msgid = 0x09; + uint8_t payload[] = {0x01, 0x02, 0x03, 0x04}; // 4 Bytes + size_t payload_len = sizeof(payload); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + uint8_t expected_message[] = { + StartByte, msgid, 0x01, 0x02, 0x03, 0x04, expected_checksum, EndByte}; + size_t expected_len = sizeof(expected_message); // 1 + 1 + 4 + 1 + 1 = 8 Bytes + + uint8_t output_buffer[expected_len]; // Puffer GENAU der erwarteten Größe + size_t output_buffer_size = sizeof(output_buffer); + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 10: Maximale Payload-Länge ohne Stuffing +void test_builder_10_max_payload_no_stuffing(void) { + uint8_t msgid = 0x10; + uint8_t payload[MAX_MESSAGE_PAYLOAD_LENGTH]; + for (size_t i = 0; i < MAX_MESSAGE_PAYLOAD_LENGTH; ++i) { + // Sicherstellen, dass keine Steuerzeichen dabei sind + payload[i] = (uint8_t)(i % 0xF0 + 0x01); // Vermeidet 0xAA, 0xBB, 0xCC + } + size_t payload_len = MAX_MESSAGE_PAYLOAD_LENGTH; + + // Geschätzte maximale Puffergröße für worst-case (alle Bytes gestuffed) + // 1 (Start) + 1 (MSGID) + MAX_PAYLOAD_LEN*2 (Payload worst-case) + 1 (CRC) + + // 1 (End) + // + 2 (potential stuffing for MSGID/CRC) = 2*MAX_MESSAGE_PAYLOAD_LENGTH + 5 + // Aber da wir hier KEIN Stuffing haben, ist es einfacher: 1 + 1 + + // MAX_PAYLOAD_LEN + 1 + 1 + size_t max_buffer_needed = 1 + 1 + MAX_MESSAGE_PAYLOAD_LENGTH + 1 + 1; + + uint8_t output_buffer[max_buffer_needed + 10]; // Etwas Puffer extra + size_t output_buffer_size = sizeof(output_buffer); + + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + + // Erstelle erwartete Nachricht manuell oder dynamisch + uint8_t expected_message[max_buffer_needed]; + size_t exp_idx = 0; + expected_message[exp_idx++] = StartByte; + expected_message[exp_idx++] = msgid; + memcpy(&expected_message[exp_idx], payload, payload_len); + exp_idx += payload_len; + expected_message[exp_idx++] = expected_checksum; + expected_message[exp_idx++] = EndByte; + size_t expected_len = exp_idx; + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// Test 11: Maximale Payload-Länge mit Stuffing (Worst-Case Szenario) +void test_builder_11_max_payload_with_stuffing(void) { + uint8_t msgid = StartByte; // MSGID muss gestuffed werden + uint8_t payload[MAX_MESSAGE_PAYLOAD_LENGTH]; + for (size_t i = 0; i < MAX_MESSAGE_PAYLOAD_LENGTH; ++i) { + // Alle Bytes sind Steuerzeichen, müssen gestuffed werden + payload[i] = + (i % 3 == 0) ? StartByte : ((i % 3 == 1) ? EscapeByte : EndByte); + } + size_t payload_len = MAX_MESSAGE_PAYLOAD_LENGTH; + + // Worst-Case Puffergröße: + // 1 (Start) + 2 (Escaped MSGID) + MAX_PAYLOAD_LEN * 2 (Escaped Payload) + 2 + // (Escaped CRC) + 1 (End) + size_t max_buffer_needed_worst_case = + 1 + 2 + (MAX_MESSAGE_PAYLOAD_LENGTH * 2) + 2 + 1; + + uint8_t + output_buffer[max_buffer_needed_worst_case + 10]; // Etwas Puffer extra + size_t output_buffer_size = sizeof(output_buffer); + + // Build the expected message manually to account for all stuffing + uint8_t expected_message[max_buffer_needed_worst_case]; + size_t exp_idx = 0; + + // StartByte + expected_message[exp_idx++] = StartByte; + + // MSGID (escaped) + expected_message[exp_idx++] = EscapeByte; + expected_message[exp_idx++] = msgid; + + // Payload (all bytes escaped) + for (size_t i = 0; i < payload_len; ++i) { + expected_message[exp_idx++] = EscapeByte; + expected_message[exp_idx++] = payload[i]; + } + + // Checksumme (kann auch escapet sein) + uint8_t expected_checksum = + calculate_payload_checksum_byte(msgid, payload, payload_len); + if (needs_stuffing_byte(expected_checksum)) { + expected_message[exp_idx++] = EscapeByte; + } + expected_message[exp_idx++] = expected_checksum; + + // EndByte + expected_message[exp_idx++] = EndByte; + size_t expected_len = exp_idx; + + int actual_len = build_message(msgid, payload, payload_len, output_buffer, + output_buffer_size); + + TEST_ASSERT_EQUAL_INT(expected_len, actual_len); + TEST_ASSERT_EQUAL_UINT8_ARRAY_HEX(expected_message, output_buffer, + actual_len); +} + +// --- MAIN TEST RUNNER --- +int main(void) { + UNITY_BEGIN(); + + // Run Builder Tests + RUN_TEST(test_builder_1_basic_message_no_escaping); + RUN_TEST(test_builder_2_empty_payload); + RUN_TEST(test_builder_3_escaped_msgid); + RUN_TEST(test_builder_4_escaped_payload_byte); + RUN_TEST(test_builder_5_escaped_checksum_byte); + RUN_TEST(test_builder_6_multiple_escapings); + RUN_TEST(test_builder_7_buffer_too_small_initial); + RUN_TEST(test_builder_8_buffer_overflow_during_build); + RUN_TEST(test_builder_9_buffer_just_enough); + RUN_TEST(test_builder_10_max_payload_no_stuffing); + RUN_TEST(test_builder_11_max_payload_with_stuffing); + + return UNITY_END(); +}