mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-11-01 14:22:12 +01:00
From the review of #99: - A sync could stall when a client took a slot the moment another left: loop() used up the new client's sync on the old one. The slots move to ProtoSlots, a pure module with native tests, and loop() takes a slot's wants only for the client it read (takeWants). - Deleting a group sends its shades, whose inGroup changed. Deleting a shade unlinks it from its groups and sends them, so a new shade given its id doesn't join them. - next and sync with an id get an ok reply, and a message in pieces gets one bad request, not one per piece. - Feeds check for loop() before they write the event buffer, and log an event too large, as events do. - editChanges() (ModelEditor, native tests) says what clients get after an edit; the edit result carries the shade a group gained or lost. order() takes a sort_list_t. - The frame writers move to ProtoFrame. Renames: pf to proto_field_t, the X-macro letters to TYPE/FIELD/ERR/FEED, proto_request_t to proto_message_t, REQ_* to WANTS_*, fmt to eventJson. The event bodies share sendItem(), the connection type name lives in ESPNetwork, and the loop() task check in SocketEmitter. - e2e: the heap floor check skips when the low since boot is already under it; tests for replies to next and sync and a long message. - SECURITY_PLAN.md splits step 1 into 1a-1d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.1 KiB
C++
79 lines
3.1 KiB
C++
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "ProtoSchema.h"
|
|
|
|
#define PROTO_FIELD_ROW(name, key, kind, def) {#name, key, kind, def},
|
|
static const proto_field_info_t FIELDS[] = { PROTO_FIELDS(PROTO_FIELD_ROW) };
|
|
#undef PROTO_FIELD_ROW
|
|
// Errors and feeds have no sender, so it is left empty and starts as 0.
|
|
#define PROTO_NAMED_ROW(name, number, ...) {number, #name, PROTO_SENDER(__VA_ARGS__, 0)},
|
|
#define PROTO_SENDER(sender, ...) sender
|
|
static const proto_named_t TYPES[] = { PROTO_TYPES(PROTO_NAMED_ROW) };
|
|
static const proto_named_t ERRORS[] = { PROTO_ERRORS(PROTO_NAMED_ROW) };
|
|
static const proto_named_t FEEDS[] = { PROTO_FEEDS(PROTO_NAMED_ROW) };
|
|
#undef PROTO_SENDER
|
|
#undef PROTO_NAMED_ROW
|
|
static_assert(sizeof(FIELDS) / sizeof(FIELDS[0]) == static_cast<size_t>(proto_field_t::count), "a field without a row");
|
|
|
|
const proto_field_info_t &protoField(proto_field_t field) { return FIELDS[static_cast<uint8_t>(field)]; }
|
|
const proto_field_info_t *protoFieldByKey(const char *key) {
|
|
for(const proto_field_info_t &f : FIELDS)
|
|
if(strcmp(f.key, key) == 0) return &f;
|
|
return nullptr;
|
|
}
|
|
uint8_t protoTypeCount() { return sizeof(TYPES) / sizeof(TYPES[0]); }
|
|
const proto_named_t &protoTypeAt(uint8_t index) { return TYPES[index]; }
|
|
uint8_t protoErrorCount() { return sizeof(ERRORS) / sizeof(ERRORS[0]); }
|
|
const proto_named_t &protoErrorAt(uint8_t index) { return ERRORS[index]; }
|
|
uint8_t protoFeedCount() { return sizeof(FEEDS) / sizeof(FEEDS[0]); }
|
|
const proto_named_t &protoFeedAt(uint8_t index) { return FEEDS[index]; }
|
|
const char *protoTypeName(uint8_t number) {
|
|
for(const proto_named_t &t : TYPES)
|
|
if(t.number == number) return t.name;
|
|
return nullptr;
|
|
}
|
|
bool protoClientMaySend(uint8_t type) {
|
|
for(const proto_named_t &t : TYPES)
|
|
if(t.number == type) return t.sender == 'c' || t.sender == 'b';
|
|
return false;
|
|
}
|
|
|
|
static uint32_t fnv(uint32_t hash, const char *text) {
|
|
for(; *text; text++) {
|
|
hash ^= (uint8_t)*text;
|
|
hash *= 16777619u;
|
|
}
|
|
return hash;
|
|
}
|
|
static uint32_t fnvNamed(uint32_t hash, const proto_named_t &row) {
|
|
char num[12];
|
|
snprintf(num, sizeof(num), "%u:", row.number);
|
|
hash = fnv(hash, num);
|
|
hash = fnv(hash, row.name);
|
|
return fnv(hash, ";");
|
|
}
|
|
const char *protoSchemaHash() {
|
|
static char hex[9] = {0};
|
|
if(hex[0]) return hex;
|
|
uint32_t hash = 2166136261u;
|
|
for(const proto_named_t &t : TYPES) hash = fnvNamed(hash, t);
|
|
for(const proto_field_info_t &f : FIELDS) {
|
|
char row[64];
|
|
snprintf(row, sizeof(row), "%s:%s:%c:%ld;", f.name, f.key, f.kind, (long)f.def);
|
|
hash = fnv(hash, row);
|
|
}
|
|
for(const proto_named_t &e : ERRORS) hash = fnvNamed(hash, e);
|
|
for(const proto_named_t &q : FEEDS) hash = fnvNamed(hash, q);
|
|
snprintf(hex, sizeof(hex), "%08lx", (unsigned long)hash);
|
|
return hex;
|
|
}
|
|
|
|
void ProtoWriter::addInt(const proto_field_info_t &f, int64_t value) {
|
|
if(value == f.def) return;
|
|
if(value >= INT32_MIN && value <= INT32_MAX) this->json.addElem(f.key, (int32_t)value);
|
|
else this->json.addElem(f.key, (uint32_t)value);
|
|
}
|
|
void ProtoWriter::item(int64_t value) {
|
|
if(value >= INT32_MIN && value <= INT32_MAX) this->json.addElem((int32_t)value);
|
|
else this->json.addElem((uint32_t)value);
|
|
}
|