mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
From the review of 7723711:
- The assembler is locked: a client closed for a full queue is closed
by the task that was sending, loop(), whose disconnect dropped the
buffer while async_tcp could be copying into it. A whole message is
taken out of it, so a disconnect can't free it while it is read.
- loop() expires a message stalled for 10 s, freeing its buffer and
telling its client, instead of holding it until another client came.
A binary message is refused before it can touch another's buffer.
- Each edit takes only the fields its route reads; one it would ignore
is refused by name. linkRemote's rolling code goes in under the
route's name, so applyModelEdit is unchanged again.
- An edit refused for an id that doesn't exist replies not found
(edit_result_t::missing). A reply that doesn't fit its buffer goes
as an internal error, so the op still gets one.
- The TYPE table says what becomes of each op (proto_op_t), which the
socket switches on. The reply address is one proto_reply_to_t. The
routes and the ops start and queue an edit the same way
(newModelEdit, submitModelEdit). The full-list texts and the offered
remote address rule live in ModelEditor.
- e2e: the sort order test skips a list that it would renumber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
4.7 KiB
C++
120 lines
4.7 KiB
C++
#include <unity.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "ProtoAssembly.h"
|
|
|
|
// Putting protocol 2 messages back together from their pieces: one message at
|
|
// a time, one reply-worthy result per message.
|
|
|
|
void setUp() {}
|
|
void tearDown() {}
|
|
|
|
static proto_piece_t piece(uint32_t client, const char *text, bool first, bool last, bool isText = true) {
|
|
proto_piece_t p;
|
|
p.clientId = client;
|
|
p.first = first;
|
|
p.last = last;
|
|
p.text = isText;
|
|
p.data = reinterpret_cast<const uint8_t *>(text);
|
|
p.len = strlen(text);
|
|
return p;
|
|
}
|
|
// Takes the complete message and checks it.
|
|
static void assertTaken(MessageAssembler &a, const char *expected) {
|
|
size_t len = 0;
|
|
char *message = a.take(len);
|
|
TEST_ASSERT_EQUAL_STRING(expected, message);
|
|
TEST_ASSERT_EQUAL_UINT32(strlen(expected), len);
|
|
free(message);
|
|
}
|
|
|
|
static void test_pieces_make_the_message() {
|
|
MessageAssembler a(64);
|
|
TEST_ASSERT_EQUAL(assembly_t::more, a.add(piece(1, "{\"v\":2,", true, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::more, a.add(piece(1, "\"t\":3", false, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(1, "}", false, true), 0));
|
|
assertTaken(a, "{\"v\":2,\"t\":3}");
|
|
}
|
|
static void test_nothing_is_held_once_the_message_is_taken() {
|
|
MessageAssembler a(64);
|
|
TEST_ASSERT_FALSE(a.holding());
|
|
a.add(piece(1, "ab", true, false), 0);
|
|
TEST_ASSERT_TRUE(a.holding());
|
|
a.add(piece(1, "c", false, true), 0);
|
|
assertTaken(a, "abc");
|
|
TEST_ASSERT_FALSE(a.holding());
|
|
// A client leaving now can't free what was taken.
|
|
a.drop(1);
|
|
}
|
|
static void test_another_clients_message_waits() {
|
|
MessageAssembler a(64);
|
|
a.add(piece(1, "ab", true, false), 0);
|
|
// One busy for the other client's message, then its pieces are let go.
|
|
TEST_ASSERT_EQUAL(assembly_t::busy, a.add(piece(2, "xy", true, false), 10));
|
|
TEST_ASSERT_EQUAL(assembly_t::ignored, a.add(piece(2, "z", false, true), 10));
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(1, "c", false, true), 20));
|
|
assertTaken(a, "abc");
|
|
}
|
|
static void test_too_large_is_said_once() {
|
|
MessageAssembler a(4);
|
|
TEST_ASSERT_EQUAL(assembly_t::more, a.add(piece(1, "abc", true, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::tooLarge, a.add(piece(1, "de", false, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::ignored, a.add(piece(1, "f", false, true), 0));
|
|
TEST_ASSERT_FALSE(a.holding());
|
|
}
|
|
static void test_a_binary_message_is_refused_once() {
|
|
MessageAssembler a(64);
|
|
TEST_ASSERT_EQUAL(assembly_t::notText, a.add(piece(1, "ab", true, false, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::ignored, a.add(piece(1, "c", false, true, false), 0));
|
|
}
|
|
static void test_a_binary_message_leaves_another_clients_alone() {
|
|
MessageAssembler a(64);
|
|
a.add(piece(1, "ab", true, false), 0);
|
|
TEST_ASSERT_EQUAL(assembly_t::notText, a.add(piece(2, "x", true, true, false), 0));
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(1, "c", false, true), 0));
|
|
assertTaken(a, "abc");
|
|
}
|
|
static void test_a_stalled_message_expires_and_its_client_is_owed_a_reply() {
|
|
MessageAssembler a(64);
|
|
a.add(piece(1, "ab", true, false), 0);
|
|
TEST_ASSERT_EQUAL_UINT32(0, a.expire(PROTO_ASSEMBLY_TIMEOUT_MS - 1));
|
|
TEST_ASSERT_TRUE(a.holding());
|
|
TEST_ASSERT_EQUAL_UINT32(1, a.expire(PROTO_ASSEMBLY_TIMEOUT_MS));
|
|
TEST_ASSERT_FALSE(a.holding());
|
|
TEST_ASSERT_EQUAL_UINT32(0, a.expire(2 * PROTO_ASSEMBLY_TIMEOUT_MS));
|
|
// Its late pieces were answered by the expiry.
|
|
TEST_ASSERT_EQUAL(assembly_t::ignored, a.add(piece(1, "c", false, true), PROTO_ASSEMBLY_TIMEOUT_MS + 1));
|
|
// And the buffer is free for anyone.
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(2, "xy", true, true), PROTO_ASSEMBLY_TIMEOUT_MS + 1));
|
|
assertTaken(a, "xy");
|
|
}
|
|
static void test_a_client_that_leaves_lets_go() {
|
|
MessageAssembler a(64);
|
|
a.add(piece(1, "ab", true, false), 0);
|
|
a.drop(1);
|
|
TEST_ASSERT_FALSE(a.holding());
|
|
TEST_ASSERT_EQUAL_UINT32(0, a.expire(PROTO_ASSEMBLY_TIMEOUT_MS));
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(2, "xy", true, true), 1));
|
|
assertTaken(a, "xy");
|
|
}
|
|
static void test_a_message_of_one_piece_is_whole() {
|
|
MessageAssembler a(64);
|
|
TEST_ASSERT_EQUAL(assembly_t::complete, a.add(piece(1, "{}", true, true), 0));
|
|
assertTaken(a, "{}");
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
(void)argc;
|
|
(void)argv;
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_pieces_make_the_message);
|
|
RUN_TEST(test_nothing_is_held_once_the_message_is_taken);
|
|
RUN_TEST(test_another_clients_message_waits);
|
|
RUN_TEST(test_too_large_is_said_once);
|
|
RUN_TEST(test_a_binary_message_is_refused_once);
|
|
RUN_TEST(test_a_binary_message_leaves_another_clients_alone);
|
|
RUN_TEST(test_a_stalled_message_expires_and_its_client_is_owed_a_reply);
|
|
RUN_TEST(test_a_client_that_leaves_lets_go);
|
|
RUN_TEST(test_a_message_of_one_piece_is_whole);
|
|
return UNITY_END();
|
|
}
|