feat(#550): monotonic channel-send timestamp + CoreScope message fields
Unifies the previously-duplicated send timestamps (frame builder vs outgoing message each called now() separately) into one monotonic-per-channel value, so every channel send has a unique (ts, channel_idx) key. That is the client-side guarantee the 0xC6 correlation needs (VioletBarn caught that AES-128-ECB determinism would otherwise make two same-second messages a wrong-hash query). Adds transient onAirHash + coreScopeObserverCount to ChannelMessage. Part of #524. Agent: QuietSnow (session 31eaba02)pull/556/head
parent
f2feb0863a
commit
63fc6a06c4
@ -0,0 +1,17 @@
|
||||
/// Returns a channel-send timestamp (Unix seconds) strictly greater than
|
||||
/// [lastSecs] for the same channel, so no two sends on one channel ever share a
|
||||
/// `(ts, channel_idx)` key.
|
||||
///
|
||||
/// This is a correctness requirement for the 0xC6 packet-hash correlation
|
||||
/// (#524/#611): `msg_timestamp` is only second-resolution and MeshCore channel
|
||||
/// encryption is deterministic (AES-128-ECB), so two different messages on the
|
||||
/// same channel within one second would otherwise map to the same key but
|
||||
/// different hashes, and the client would query CoreScope with the wrong hash.
|
||||
///
|
||||
/// Uses [nowSecs] unless it would collide with or precede [lastSecs], in which
|
||||
/// case it bumps to `lastSecs + 1`. Mirrors the firmware's own
|
||||
/// `getCurrentTimeUnique()`.
|
||||
int monotonicChannelSendTs(int nowSecs, int? lastSecs) {
|
||||
if (lastSecs != null && nowSecs <= lastSecs) return lastSecs + 1;
|
||||
return nowSecs;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_open/helpers/channel_send_timestamp.dart';
|
||||
|
||||
void main() {
|
||||
group('monotonicChannelSendTs', () {
|
||||
test('first send on a channel uses now', () {
|
||||
expect(monotonicChannelSendTs(1000, null), 1000);
|
||||
});
|
||||
|
||||
test('bumps by 1 on a same-second collision', () {
|
||||
expect(monotonicChannelSendTs(1000, 1000), 1001);
|
||||
});
|
||||
|
||||
test('uses now when it has advanced past the last send', () {
|
||||
expect(monotonicChannelSendTs(1005, 1000), 1005);
|
||||
});
|
||||
|
||||
test('bumps past the last send if the clock went backwards', () {
|
||||
expect(monotonicChannelSendTs(1000, 1005), 1006);
|
||||
});
|
||||
|
||||
test('a rapid burst stays strictly increasing', () {
|
||||
int? last;
|
||||
final produced = <int>[];
|
||||
// Same wall-clock second for the whole burst.
|
||||
for (var i = 0; i < 5; i++) {
|
||||
last = monotonicChannelSendTs(1000, last);
|
||||
produced.add(last);
|
||||
}
|
||||
expect(produced, [1000, 1001, 1002, 1003, 1004]);
|
||||
expect(produced.toSet().length, produced.length); // all unique
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in new issue