From 2e3d2d13b276d7496dea37ae9c7bf37cefc40da8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 29 Jan 2025 13:14:42 +1100 Subject: [PATCH] * room_server: login response now with a 'keep alive' interval suggestion * room_server: new Request payload sub-type: REQ_TYPE_KEEP_ALIVE -> RESP_SERVER_KEEP_ALIVE --- examples/simple_room_server/main.cpp | 52 +++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index c19750dd..cfcc2365 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -19,7 +19,7 @@ /* ------------------------------ Config -------------------------------- */ -#define FIRMWARE_VER_TEXT "v2 (build: 27 Jan 2025)" +#define FIRMWARE_VER_TEXT "v3 (build: 29 Jan 2025)" #ifndef LORA_FREQ #define LORA_FREQ 915.0 @@ -124,6 +124,13 @@ struct PostInfo { #define PUSH_TIMEOUT_BASE 4000 #define PUSH_ACK_TIMEOUT_FACTOR 2000 +#define CLIENT_KEEP_ALIVE_SECS 128 + +#define REQ_TYPE_KEEP_ALIVE 1 + +#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define RESP_SERVER_KEEP_ALIVE 1 // response to REQ_TYPE_KEEP_ALIVE + struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; @@ -282,8 +289,11 @@ protected: memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? - memset(&reply_data[4], 0, 4); // FUTURE: reserve 4 bytes - memcpy(&reply_data[8], "OK", 2); + reply_data[4] = RESP_SERVER_LOGIN_OK; + reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) + reply_data[6] = 0; // FUTURE: reserved + reply_data[7] = 0; // FUTURE: reserved + memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response @@ -332,7 +342,7 @@ protected: return; } auto client = &known_clients[i]; - if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command + if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) uint flags = (data[4] >> 2); // message attempt number, and other flags @@ -395,6 +405,34 @@ protected: } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); } + } else if (type == PAYLOAD_TYPE_REQ && len >= 5) { + uint32_t sender_timestamp; + memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) + if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type + if (len >= 9) { // optional - last post_timestamp client received + memcpy(&client->sync_since, &data[5], 4); // force-update the 'sync since' + } + + uint32_t now = getRTCClock()->getCurrentTime(); + client->last_activity = now; // <-- THIS will keep client connection alive + client->push_failures = 0; // reset so push can resume (if prev failed) + + // TODO: Throttle KEEP_ALIVE requests! + // if client sends too quickly, evict() + + // RULE: only send keep_alive response DIRECT! + if (client->out_path_len >= 0) { + uint8_t temp[8]; + memcpy(temp, &now, 4); // all responses start with sender timestamp + temp[4] = RESP_SERVER_KEEP_ALIVE; + memcpy(&temp[5], &client->sync_since, 4); // let client know what server thinks is their 'since' + + auto reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, temp, 9); + if (reply) { + sendDirect(reply, client->out_path, client->out_path_len); + } + } + } } } @@ -571,15 +609,11 @@ public: c->push_failures++; c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); - - if (c->push_failures >= 3) { - evict(c); - } } } // check next Round-Robin client, and sync next new post auto client = &known_clients[next_client_idx]; - if (client->pending_ack == 0 && client->last_activity != 0) { // not already waiting for ACK, AND not evicted + if (client->pending_ack == 0 && client->last_activity != 0 && client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t) client->id.pub_key[0]); for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) { if (posts[idx].post_timestamp > client->sync_since // is new post for this Client?