Skip to content

Clock towers + pio testing support#2504

Open
mangelajo wants to merge 2 commits into
meshcore-dev:mainfrom
mangelajo:clock-towers
Open

Clock towers + pio testing support#2504
mangelajo wants to merge 2 commits into
meshcore-dev:mainfrom
mangelajo:clock-towers

Conversation

@mangelajo

@mangelajo mangelajo commented May 8, 2026

Copy link
Copy Markdown

Implement repeater clock trust update (see #2416 "Clock towers").

Adds an opt-in "trusted clock" list of advert pubkeys whose timestamps
may step the local RTC. Replay-guarded (advert_ts must be strictly newer
than the last accepted from the same key.

Last few sync events are kept in a small RAM ring queryable via the CLI.

Logic lives in TrustedClockTable (no Arduino deps): table ops, replay
predicate, threshold predicate, sync-event ring, and pure
serialize/deserialize. MyMesh holds it as a member and wraps file I/O
around the helper.

CLI under clock.trust:

      clock.trust list
      clock.trust events
      clock.trust add <pubkey> [name]
      clock.trust remove <pubkey-or-name>

Also adds a new setting clock.trust.thresh.

To make reviewing easy I have structured the PR in two parts:

  1. The functionality itself
  2. Unit tests for the feature + make test + pio test support, which may help adding tests for other parts of the code as we go.

This is an example of the firmware running on a local T114 repeater I have at home:

10:54:32 - 15/5/2024 U RAW: 0A41ED97D250BFB5D6C748C653EA62BE1074E131DD97CA7E
10:54:32 - 15/5/2024 U: RX, len=24 (type=2, route=D, payload_len=20) SNR=13 RSSI=-53 score=1000 time=259 hash=C50C189C44122D59 [50 -> D2]
10:54:32 - 15/5/2024 U: TX, len=22 (type=2, route=D, payload_len=20) [50 -> D2]
DEBUG: RadioLibWrapper: noise_floor = -115
10:54:33 - 15/5/2024 U RAW: 0A4050D29F18FB6E5FCAA4B6333CF3E9ABE1982A4731FCA9AF308C06FD298B2EF8415B6077CB
10:54:33 - 15/5/2024 U: RX, len=38 (type=2, route=D, payload_len=36) SNR=10 RSSI=-94 score=1000 time=320 hash=76C4462508AC411C [D2 -> 50]

advert from a trusted clock key received (it's another repeater with GPS clock sync):

10:54:35 - 15/5/2024 U RAW: 1140D2590AED87376D577891BDC956026E38412BD4E8E7A972D21A2EF47478F26ECB569FFD69FA8D3475A0C2F8CFA2E9B456E1DC1E329F66F122DDF401E4692C259707750057E9EF5E972B99D770A4EF3078967DE781A43041475FAD3BA901B7520F765DDF0D9261946802AA1AC7FF5075657274612064656C20C3816E67656C20F09FA59D
10:54:35 - 15/5/2024 U: RX, len=133 (type=4, route=F, payload_len=131) SNR=11 RSSI=-93 score=1000 time=812 hash=BF74AF4E5683B06D
DEBUG: 10:54:35 - 15/5/2024 U Dispatcher::checkRecv(), score delay below threshold (0)
DEBUG: 10:54:35 - 15/5/2024 U Mesh::onRecvPacket(): valid advertisement received!

clock gets updated: 10:54:35 - 15/5/2024 --> 08:31:18 - 8/5/2026

DEBUG: trusted-clock: stepping local clock by +62458603 secs from kiwi [D2590AED87376D57]
DEBUG: RadioLibWrapper: noise_floor = -110
08:31:18 - 8/5/2026 U RAW: 1141CEBAD2590AED87376D577891BDC956026E38412BD4E8E7A972D21A2EF47478F26ECB569FFD69FA8D3475A0C2F8CFA2E9B456E1DC1E329F66F122DDF401E4692C259707750057E9EF5E972B99D770A4EF3078967DE781A43041475FAD3BA901B7520F765DDF0D9261946802AA1AC7FF5075657274612064656C20C3816E67656C20F09FA59D
08:31:18 - 8/5/2026 U: RX, len=135 (type=4, route=F, payload_len=131) SNR=-4 RSSI=-114 score=271 time=832 hash=BF74AF4E5683B06D
DEBUG: 08:31:18 - 8/5/2026 U Dispatcher::checkRecv(), score delay below threshold (0)
08:31:19 - 8/5/2026 U: TX, len=135 (type=4, route=F, payload_len=131)
DEBUG: RadioLibWrapper: noise_floor = -115
DEBUG: RadioLibWrapper: noise_floor = -110
DEBUG: RadioLibWrapper: noise_floor = -110
image

mangelajo added 2 commits May 8, 2026 09:58
Adds an opt-in "trusted clock" list of advert pubkeys whose timestamps
may step the local RTC. Replay-guarded (advert_ts must be strictly newer
than the last accepted from the same key, RAM-only cursor). Steps only
when |advert_ts - local| >= clock.trust.thresh (default 60s, set 0 to
disable). Sources have an optional human-readable name. Last few sync
events are kept in a small RAM ring queryable via the CLI.

Logic lives in TrustedClockTable (no Arduino deps): table ops, replay
predicate, threshold predicate, sync-event ring, and pure
serialize/deserialize. MyMesh holds it as a member and wraps file I/O
around the helper.

CLI under clock.trust:
  clock.trust list
  clock.trust events
  clock.trust add <pubkey> [name]
  clock.trust remove <pubkey-or-name>

Persisted via NodePrefs.clock_trust_thresh (set/get clock.trust.thresh)
and /trusted_clocks file (u8 version, u8 count, count*{pubkey, name}).
Adds a PlatformIO native test env so the helper logic can be exercised
on the host without touching hardware. 11 Unity test cases cover
add/find/remove (incl. dedup, capacity, mid-array shift), replay and
threshold predicates at boundaries, sync-event ring eviction order,
serialize/deserialize round-trip, bad-version rejection, count
clamping, truncated records, and name-length truncation.

Layout follows PIO conventions: tests live under test/, run via
`pio test -e native` or `make test`. New host-tests.yml workflow
runs the same command on PRs/pushes that touch src, examples,
test, or platformio.ini.
@IoTThinks

Copy link
Copy Markdown
Contributor

This looks interesting.
I will follow the PR.

@Talie5in

Talie5in commented May 8, 2026

Copy link
Copy Markdown

Following and will be testing.

Appreciate the opt-in model and having to define the trusted clock source.

@vaizki

vaizki commented May 8, 2026

Copy link
Copy Markdown

My idea (not implemented yet) was to make a generic trust list that has a set of trust "scopes" for each trusted public key. Example based on your syntax:

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock
OK 
> trust list
kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 blacklist
OK 
> trust list
kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock,blacklist

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 *
OK 

This would contribute a generic and extendable trust mechanism.

The clock-specific commands would be under a different base command:

> clock events
admin-reset
admin-sync +431s
peer-sync kiwi +34643634s

etc..

@mangelajo

Copy link
Copy Markdown
Author

My idea (not implemented yet) was to make a generic trust list that has a set of trust "scopes" for each trusted public key. Example based on your syntax:

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock
OK 
> trust list
kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 blacklist
OK 
> trust list
kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 clock,blacklist

> trust add kiwi 55483a5d904d3c0a80f3c50fdd4de2a121eca0122f3f6b4ea8b6847df7f66a81 *
OK 

This would contribute a generic and extendable trust mechanism.

The clock-specific commands would be under a different base command:

> clock events
admin-reset
admin-sync +431s
peer-sync kiwi +34643634s

etc..

I am good with that idea if everybody else agrees I can switch to something like that :), would be more reusable as you say :)

@Talie5in

Talie5in commented May 8, 2026

Copy link
Copy Markdown

Curious if this couldn't use the same ACL used for repeater admin, but just as a new ACL flag (4) for trust-clock-sync or something?

Just because the ACL system is already there.

Eg, see PR #2438

@mangelajo

Copy link
Copy Markdown
Author

Curious if this couldn't use the same ACL used for repeater admin, but just as a new ACL flag (4) for trust-clock-sync or something?

Just because the ACL system is already there.

Eg, see PR #2438

Curious if this couldn't use the same ACL used for repeater admin, but just as a new ACL flag (4) for trust-clock-sync or something?

Just because the ACL system is already there.

Eg, see PR #2438

Thanks for the pointer. I will investigate, because this ACL list is for Clients instead of Repeaters.

It makes sense TBH, but I wonder if the code is structured in a way that we can reuse it for "repeaters" ACL.

I'll report back once I have time to investigate :-)

@hank

hank commented May 14, 2026

Copy link
Copy Markdown
Contributor

Curious if this couldn't use the same ACL used for repeater admin, but just as a new ACL flag (4) for trust-clock-sync or something?

Just because the ACL system is already there.

Eg, see PR #2438

i think this would be a very appropriate use case for ACL 4. In addition to region modification the clock trust is there. Maybe we should come up with a good name like "Trusted Third Party" that isn't as specific as "Region Manager"

@ryanbcox

ryanbcox commented Jun 4, 2026

Copy link
Copy Markdown

I'm not sure of the particulars of ACL 4, but I personally would want this separate from repeater admin. I may trust some random repeater with GPS enough to set the time on my own repeaters, but not enough to be an admin on my repeater. I don't know if repeaters themselves could do something malicious but (very unlikely) a malicious repeater owner could gain people's trust then take their repeater's private key and use it on a companion device from which they could do something malicious to repeaters that trust that key.

I was actually about to submit a PR very similar to this and I'm glad to see someone else had already been working on it. (I'll check Github better next time 🙂). A few things I had done in addition to this were:

  • Subtract .5 seconds from the advert time per hop to account for transmission time (seemed like a reasonable estimate)
  • I added a max hop count parameter in case you want to ignore adverts beyond a certain distance. Probably not the most useful parameter.
  • Limited to 2 devices, partially because of the next item so it could all fit in one packet:
  • Printed out the full key when listing so that a companion UI could more easily present accurate data (no collisions). Not sure if that's a real concern or not. Your naming idea might satisfy that.
  • Never allowed the time to be set to something older than the date I created the code (maybe firmware build date would be better if that's easily available in a file somewhere)

I like your code better overall and mention these in case you find any of the ideas useful. Personally I like accounting for time per hop but I'm guessing most people won't use time sources more than a few hops away anyway.

@Littleaton

Littleaton commented Jun 4, 2026

Copy link
Copy Markdown

This would be a great addition that could be connected up with the pymc linux nodes. they could be the trusted clock and keep all other repeaters clocks on sync.

@rightup @agessaman

@Talie5in

Talie5in commented Jun 4, 2026

Copy link
Copy Markdown

This would be a great addition that could be connected up with the pymc linux nodes. they could be the trusted clock and keep all other repeaters clocks on sync.

@rightup @agessaman

If the repeater trusts it as a source.

We don't want implicit trust.

@hank

hank commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

This would be a great addition that could be connected up with the pymc linux nodes. they could be the trusted clock and keep all other repeaters clocks on sync.
@rightup @agessaman

If the repeater trusts it as a source.

We don't want implicit trust.

yeah i think any clock source should be in the acl somewhere above guest. that way it's explicit trust only.

@mangelajo

Copy link
Copy Markdown
Author

I'm not sure of the particulars of ACL 4, but I personally would want this separate from repeater admin. I may trust some random repeater with GPS enough to set the time on my own repeaters, but not enough to be an admin on my repeater. I don't know if repeaters themselves could do something malicious but (very unlikely) a malicious repeater owner could gain people's trust then take their repeater's private key and use it on a companion device from which they could do something malicious to repeaters that trust that key.

Also ACLs are designed to be "trust Client" from this Repeater, not for trusting other repeaters.

Although what @Talie5in proposed was using a specific flag to only trust the clock, reusing the same list and extending it for this use case.

I was actually about to submit a PR very similar to this and I'm glad to see someone else had already been working on it. (I'll check Github better next time 🙂). A few things I had done in addition to this were:

  • Subtract .5 seconds from the advert time per hop to account for transmission time (seemed like a reasonable estimate)

Yeah, that sounds like a reasonable default in line with what I have experienced, we could perhaps make it a configurable parameter.

  • I added a max hop count parameter in case you want to ignore adverts beyond a certain distance. Probably not the most useful parameter.
    +1 Makes sense.
  • Limited to 2 devices, partially because of the next item so it could all fit in one packet:
  • Printed out the full key when listing so that a companion UI could more easily present accurate data (no collisions). Not sure if that's a real concern or not. Your naming idea might satisfy that.
  • Never allowed the time to be set to something older than the date I created the code (maybe firmware build date would be better if that's easily available in a file somewhere)
    +1

I like your code better overall and mention these in case you find any of the ideas useful. Personally I like accounting for time per hop but I'm guessing most people won't use time sources more than a few hops away anyway.

I will add this to the patch and test ASAP.

What I have not managed (I believe), is to get any feedback from core developers of meshcore. I'd like to waste a lot of time if this is going into an unwanted direction for them, do you know how to get them have an eye?

@ryanbcox

ryanbcox commented Jun 4, 2026

Copy link
Copy Markdown

What I have not managed (I believe), is to get any feedback from core developers of meshcore. I'd like to waste a lot of time if this is going into an unwanted direction for them, do you know how to get them have an eye?

I'm new to MeshCore so I don't know much about the actual project structure yet or how many developers are involved.

@mangelajo

Copy link
Copy Markdown
Author

@liamcottle I'd be grateful if you could have an eye to this PR, and offer some guidance. We are wondering if we should have a more generic trust mechanism, or if the ACL subsystem could be extended for this purpose. I am happy to finish the patch and implement the received feedback, but I'd love to know what is the preference of the project admins.

Best,
Miguel Ángel.

@ViezeVingertjes

ViezeVingertjes commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Interesting; I’ve implemented a clock sync before that worked by filtering out outliers from incoming packets to get an accurate time. That works until there are malicious actors, of course, but it was fine for some rogue nodes.

I don’t think ACL is the right place for this though, since it’s about companion permissions, and personally I find @vaizki 's idea cleaner in the long run. That said, this approach is also fine for what it’s supposed to do.

On the other hand… it does add quite a bit of code for something (accurate time) that repeaters don’t actually need to function in the first place.

What’s the real use case here? and does that justify the extra code/complexity?

@Avamander

Copy link
Copy Markdown

What’s the real use case here? and does that justify the extra code/complexity?

If someone operates more than one node, especially if they lack an RTC or GNSS module keeping time in sync becomes a bit annoying at times. This applies to both companions and repeaters. Being able to trust certain operators and their timekeeping would make the management of low-cost nodes easier.

@mangelajo

Copy link
Copy Markdown
Author

Ok, it seems like I may need to rebase the patch at this point…

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.

9 participants