mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-11-01 14:22:12 +01:00
fix: protocol 2 core review findings
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>
This commit is contained in:
parent
d869bb53d5
commit
dc2f1478b6
29 changed files with 1087 additions and 670 deletions
62
src/ProtoSlots.cpp
Normal file
62
src/ProtoSlots.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "ProtoSlots.h"
|
||||
|
||||
proto_slot_t *claimSlot(proto_slot_t *slots, uint8_t count, uint32_t clientId, bool withSchema) {
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
uint32_t expected = 0;
|
||||
if(!slots[i].clientId.compare_exchange_strong(expected, clientId)) continue;
|
||||
slots[i].withSchema = withSchema;
|
||||
slots[i].feeds = 0;
|
||||
return &slots[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void startSync(proto_slot_t &slot) { slot.wants.fetch_or(WANTS_SYNC); }
|
||||
proto_slot_t *slotOf(proto_slot_t *slots, uint8_t count, uint32_t clientId) {
|
||||
for(uint8_t i = 0; i < count; i++)
|
||||
if(slots[i].clientId.load() == clientId) return &slots[i];
|
||||
return nullptr;
|
||||
}
|
||||
void freeSlot(proto_slot_t &slot) {
|
||||
// Cleared before the slot is free, so the next client starts clean.
|
||||
slot.wants = 0;
|
||||
slot.feeds = 0;
|
||||
slot.clientId = 0;
|
||||
}
|
||||
|
||||
uint32_t takeWants(proto_slot_t &slot, uint32_t seenId) {
|
||||
const uint32_t wants = slot.wants.exchange(0);
|
||||
if(slot.clientId.load() == seenId) return wants;
|
||||
// The client left and another took the slot after loop() read it: what was
|
||||
// taken may be the new client's, so it goes back for the next pass. A bit
|
||||
// the old client left behind does no harm, as the new one wants a sync.
|
||||
if(wants) slot.wants.fetch_or(wants);
|
||||
return 0;
|
||||
}
|
||||
sync_step_t nextSyncStep(proto_slot_t &slot, uint32_t now) {
|
||||
const uint32_t id = slot.clientId.load();
|
||||
if(id != slot.loopId) {
|
||||
// A client left or another took the slot.
|
||||
slot.loopId = id;
|
||||
slot.cur = sync_cursor_t();
|
||||
slot.awaitingNext = false;
|
||||
}
|
||||
if(!id) return sync_step_t::none;
|
||||
const uint32_t wants = takeWants(slot, id);
|
||||
if(wants & (WANTS_SYNC | WANTS_SYNC_AGAIN)) {
|
||||
// A client that just connected may skip the schema; one that asks again
|
||||
// never gets it.
|
||||
slot.cur = beginSync((wants & WANTS_SYNC) && slot.withSchema.load());
|
||||
return sync_step_t::send;
|
||||
}
|
||||
if((wants & WANTS_NEXT) && slot.awaitingNext) return sync_step_t::send;
|
||||
if(slot.awaitingNext && now - slot.sentAt > PROTO_SYNC_TIMEOUT_MS) {
|
||||
slot.awaitingNext = false;
|
||||
slot.cur = sync_cursor_t();
|
||||
return sync_step_t::timedOut;
|
||||
}
|
||||
return sync_step_t::none;
|
||||
}
|
||||
void syncFrameSent(proto_slot_t &slot, uint32_t now) {
|
||||
slot.awaitingNext = slot.cur.active();
|
||||
slot.sentAt = now;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue