onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

peers.h (4420B)


      1 #pragma once
      2 #include <Arduino.h>
      3 #include <string.h>
      4 #include "config.h"
      5 #include "pet.h"          // PET_FACE_MAX — peers carry the sender's pet face
      6 #include "encounters.h"   // Encounter struct (emitted when a peer leaves range)
      7 
      8 // One discovered friend, with the running "encounter" segment we're building.
      9 struct Peer {
     10   char     handle[HANDLE_MAX_LEN + 1];  // +1 for NUL so it's printable
     11   int8_t   rssi;                        // last seen signal strength (dBm)
     12   uint32_t last_seen;                   // millis() of last valid beacon (TTL)
     13   uint32_t counter;                     // last beacon counter (freshness)
     14   uint32_t first_epoch;                 // wall-clock start of this segment
     15   uint32_t last_epoch;                  // wall-clock of last sighting
     16   uint16_t sightings;                   // beacons heard this segment
     17   int8_t   rssi_max;                    // closest approach this segment
     18   uint8_t  pet;                         // sender's pet index (==PET_BUILTIN_N → custom)
     19   char     pet_face[PET_FACE_MAX + 1];  // sender's pet idle face (+1 NUL for printable)
     20 };
     21 
     22 // Bounded, flood-resistant table of nearby badges. Not internally locked —
     23 // the caller (main.cpp) guards access with a portMUX, since the ESP-NOW
     24 // receive callback and the main loop both touch it.
     25 class PeerTable {
     26 public:
     27   // Insert or refresh a peer. Evicts the stalest entry if full.
     28   void upsert(const char* handle, uint8_t hlen, int8_t rssi,
     29               uint32_t counter, uint32_t now_ms, uint32_t now_epoch,
     30               uint8_t pet, const char* pet_face) {
     31     int idx = find(handle, hlen);
     32     if (idx < 0) {                               // new segment begins
     33       idx = (_n < MAX_PEERS) ? _n++ : stalest();
     34       memset(&_peers[idx], 0, sizeof(Peer));
     35       memcpy(_peers[idx].handle, handle, hlen);
     36       _peers[idx].handle[hlen] = '\0';
     37       _peers[idx].first_epoch  = now_epoch;
     38       _peers[idx].rssi_max     = rssi;
     39     }
     40     Peer& p = _peers[idx];
     41     p.rssi       = rssi;
     42     p.last_seen  = now_ms;
     43     p.counter    = counter;
     44     p.last_epoch = now_epoch;
     45     p.sightings++;
     46     if (rssi > p.rssi_max) p.rssi_max = rssi;
     47     p.pet = pet;                                 // refresh pet each beacon (swaps live)
     48     memcpy(p.pet_face, pet_face, PET_FACE_MAX);
     49     p.pet_face[PET_FACE_MAX] = '\0';
     50   }
     51 
     52   // Drop peers idle > PEER_TTL_MS, emitting one Encounter per departure.
     53   void expire(uint32_t now_ms, Encounter* out, int& out_n, int max) {
     54     out_n = 0;
     55     for (int i = 0; i < _n; ) {
     56       if (now_ms - _peers[i].last_seen > PEER_TTL_MS) {
     57         if (out && out_n < max) to_encounter(_peers[i], out[out_n++]);
     58         _peers[i] = _peers[--_n];               // swap-remove
     59       } else {
     60         i++;
     61       }
     62     }
     63   }
     64 
     65   // Finalize all in-range peers as encounters (used when entering REPORT), then
     66   // start a fresh segment for each so they aren't double-counted on expiry.
     67   void flush(Encounter* out, int& out_n, int max) {
     68     out_n = 0;
     69     for (int i = 0; i < _n; i++) {
     70       if (out && out_n < max) to_encounter(_peers[i], out[out_n++]);
     71       _peers[i].first_epoch = _peers[i].last_epoch;
     72       _peers[i].sightings   = 1;
     73       _peers[i].rssi_max    = _peers[i].rssi;
     74     }
     75   }
     76 
     77   // Copy current peers into `out`, sorted by RSSI (closest first).
     78   int snapshot(Peer* out, int max) {
     79     int n = (_n < max) ? _n : max;
     80     for (int i = 0; i < n; i++) out[i] = _peers[i];
     81     for (int i = 1; i < n; i++) {               // insertion sort, n is tiny
     82       Peer key = out[i];
     83       int j = i - 1;
     84       while (j >= 0 && out[j].rssi < key.rssi) { out[j + 1] = out[j]; j--; }
     85       out[j + 1] = key;
     86     }
     87     return n;
     88   }
     89 
     90   int count() const { return _n; }
     91 
     92 private:
     93   Peer _peers[MAX_PEERS];
     94   int  _n = 0;
     95 
     96   static void to_encounter(const Peer& p, Encounter& e) {
     97     strncpy(e.handle, p.handle, HANDLE_MAX_LEN);
     98     e.handle[HANDLE_MAX_LEN] = '\0';
     99     e.first_epoch = p.first_epoch;
    100     e.last_epoch  = p.last_epoch;
    101     e.sightings   = p.sightings;
    102     e.rssi_max    = p.rssi_max;
    103   }
    104 
    105   int find(const char* handle, uint8_t hlen) {
    106     for (int i = 0; i < _n; i++)
    107       if (strlen(_peers[i].handle) == hlen &&
    108           memcmp(_peers[i].handle, handle, hlen) == 0)
    109         return i;
    110     return -1;
    111   }
    112 
    113   int stalest() {
    114     int idx = 0;
    115     for (int i = 1; i < _n; i++)
    116       if (_peers[i].last_seen < _peers[idx].last_seen) idx = i;
    117     return idx;
    118   }
    119 };