Skip to content

Repeater: discover and subscribe to the regions of neighboring repeaters - #3086

Draft
agessaman wants to merge 4 commits into
meshcore-dev:devfrom
agessaman:feat/scope-inherit
Draft

Repeater: discover and subscribe to the regions of neighboring repeaters#3086
agessaman wants to merge 4 commits into
meshcore-dev:devfrom
agessaman:feat/scope-inherit

Conversation

@agessaman

Copy link
Copy Markdown
Contributor

Addresses #2852.

A repeater can already answer the question "which regions do you flood?"—handleAnonRegionsReq has been in the tree for a while—but only companions ever ask it. This adds the repeater side and two commands built on it, so an operator can see what their neighbors flood and keep a node in step with one of them.

Command Purpose
discover.regions Ask each heard neighbour which regions it floods, one request at a time. Results print to serial as they arrive.
discover.regions list The last run's results, for remote CLI.
regions.subscribe <pubkey> Take regions from a nominated repeater. A prefix works if it uniquely matches a neighbour.
regions.subscribe / now / off Status, force a fetch, unsubscribe.

Subscribing is additive. A region already on the node keeps its flags and its place in the hierarchy—a region the operator has denied stays denied—and nothing is ever removed. New names are added flood-allowed under the wildcard. Private ($) regions are skipped, since their transport keys aren't derived from the name. The fetch repeats every 72 h (2 min after boot, immediately on change), retrying after 1 min for the first 5 attempts and every 12 h after that.

The merge itself lives in RegionMap::importNamesFrom() so it's reusable by the room server and sensor, which also hold a RegionMap.

Also fixes two pre-existing bugs this feature depends on—happy to split these into their own PR if preferred:

  • handleAnonRegionsReq() could build a reply larger than createDatagram() accepts (>159 chars of names), so a node with a large region map answered nothing at all. The exporter is now budgeted from the transport limit.
  • discovery_mod_timestamp was written and saved but never serialized in NodePrefs::structure(), so it reset to zero on every reboot — region save was affected the same way.

Testing: subscribed and merge verified on hardware (2 regions added, 6 already known, existing regions untouched). Builds clean for ESP32-S3 repeater, nRF52 repeater, and room server. Costs ~3.5 KB flash and ~230 bytes RAM.

Adds the client side of the existing anon-regions request, which until now
only had a server side (handleAnonRegionsReq), and uses it two ways:

- `discover.regions` asks each heard neighbor in turn which regions it
  floods, printing results to serial as they arrive. `discover.regions list`
  reports the last run for remote CLI users.

- `regions.subscribe <pubkey>` nominates a repeater to take regions from.
  The node fetches its regions on change, 2 minutes after boot, and every
  72 hours after that, adding any region it doesn't already have as a
  flood-allowed child of the wildcard. An unanswered fetch is retried after
  a minute for the first few attempts, then every 12 hours.

Subscribing is additive: a region already on this node keeps its flags and
its place in the hierarchy, so a region the operator has denied stays
denied, and nothing is ever removed. Private ($) regions are skipped, as
their transport keys are not derived from the name.

Only one request is in flight at a time. The queried node is matched in
searchPeersByHash after the ACL clients, so that querying a node which is
also a client cannot swallow that client's own traffic.

Also fixes two pre-existing issues this feature depends on:
 - handleAnonRegionsReq() could build a reply larger than createDatagram()
   accepts, so a node with a large region map answered nothing at all.
 - discovery_mod_timestamp was saved but never serialized, so it reset to
   zero on every reboot.
The reply is capped at 134 chars, so a pass over several neighbours with
long region lists silently showed only the first few. Count what doesn't
fit and append '+N more', so a short list isn't mistaken for the whole
pass. The serial output is unchanged, and still shows every result.
Three ways an incomplete result was presented as a whole one:

- 'discover.regions' built each entry in a 64 byte buffer, so a neighbour
  with more than ~54 chars of region names was cut short with no marker,
  in the serial log as well as the list. The serial log now prints the
  whole reply, and an entry that does not fit the list is counted in the
  '+N more' total rather than stored truncated.

- importNamesFrom() stops when the region table is full, and saveRegions()
  can fail; both were reported as a successful fetch, which cleared the
  failure count and stood down for 72 hours. A full table is now flagged
  in the reply, and an unsaved import keeps retrying: the regions are in
  RAM, where a later fetch counts them as 'known' and would never retry
  the save on its own.

- An automatic fetch could land in the middle of an interactive
  'region load', whose temp map then overwrote the imported regions after
  they had been reported and saved. Fetches now stand aside while a load
  is active.
Bring the new code in line with the surrounding style, which comments
lightly. Drops the function-header blocks, keeping the short notes that
record something non-obvious: the peer match ordering, the cipher block
padding, and the reply size limit.

Also collapses the duplicated no-reply/reply branches in
advanceRegionDiscover(), and hoists the repeated prefix out of the
'regions.subscribe' status reply. No behaviour change.

Includes the ambiguous-prefix check for 'regions.subscribe {prefix}',
which previously took whichever neighbour matched first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant