feat(ui): DM auto-resend + incoming dedup + channel relay ring

DM auto-resend (delivery status follow-up):
- New pref dm_resend_count (0-5, default 2), Settings › Messages › Resend.
  Schema sentinel 0xC0DE0009 → 0xC0DE000A; old files clamp to the default.
- When a pending on-device DM passes its ACK deadline, tickDmResends()
  (driven from UITask::loop, so it runs in the background regardless of the
  active screen) re-sends with the next attempt# reusing the original
  timestamp, refreshing ack_tag/deadline, until resends run out → then ✗.
- dmEffectiveStatus keeps the entry pending while resends remain.

Incoming DM dedup:
- A retry reuses the sender timestamp + text but has a fresh packet hash, so
  the mesh dup-filter passes it. addDMMsg now takes sender_timestamp and drops
  copies matching prefix+timestamp+text. sender_timestamp plumbed from
  MyMesh::queueMessage through AbstractUITask/UITask.

Channel relay ring:
- Replace the single-slot relayed-into-mesh tracker with a 4-slot ring so a
  quick burst of channel sends are each matched to their repeater echo.
  Receive-path hashing still gated (now on _relay_active) so the hot flood
  path is untouched when idle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-14 15:13:29 +02:00
co-authored by Claude Opus 4.8
parent 54b53f7f0f
commit aa4bb490d0
9 changed files with 166 additions and 38 deletions
+17 -9
View File
@@ -297,15 +297,23 @@ private:
uint16_t _apc_flood_len; // its payload length — cheap pre-filter before hashing
uint32_t _apc_flood_deadline; // echo-wait deadline for that send
bool _apc_flood_pending; // a tracked flood send is awaiting its echo
// UI "relayed into mesh" tracker for channel sends — independent of APC, single
// slot (only the latest channel send is tracked). Hashing on receive only runs
// while a send is pending, so the hot flood-recv path is untouched otherwise.
uint8_t _relay_hash[MAX_HASH_SIZE];
uint16_t _relay_len;
uint32_t _relay_deadline;
uint32_t _relay_seq; // monotonic id of the pending tracked send
uint32_t _last_relay_seq; // seq of the most recent tracked send (for the UI to record)
bool _relay_pending;
// UI "relayed into mesh" tracker for channel sends — independent of APC. A small
// ring so a quick burst of channel sends are each tracked (not just the latest).
// Hashing on receive only runs while at least one slot is pending, so the hot
// flood-recv path is untouched otherwise.
static const int RELAY_RING = 4;
struct RelaySlot {
uint8_t hash[MAX_HASH_SIZE];
uint16_t len;
uint32_t deadline;
uint32_t seq;
bool pending;
};
RelaySlot _relay[RELAY_RING];
int _relay_head; // next ring slot to overwrite
int _relay_active; // number of slots currently pending (cheap gate)
uint32_t _relay_seq; // monotonic id counter for tracked sends
uint32_t _last_relay_seq; // seq of the most recent tracked send (for the UI to record)
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
char cli_command[80];
uint8_t app_target_ver;