Bug
buildUpdateContactPathFrame in lib/connector/meshcore_protocol.dart:723-747 emits two consecutive 8-byte position blocks instead of one. The frame ends up 8 bytes longer than the documented layout, and any caller that also passes lastModified writes the timestamp 8 bytes deeper than the firmware expects.
Frame layout per the doc comment
The function is documented (line 695) as producing:
So the optional tail is 8 bytes of position + optional 4-byte timestamp — 12 bytes max.
What it actually emits
Two back-to-back blocks both write position data:
// Block 1 (lines 723-736) — runs unconditionally
if ((lat == null || lon == null) && lastModified != null) {
writer.writeInt32LE(0);
writer.writeInt32LE(0);
} else {
final latitude = lat ?? 0.0;
writer.writeInt32LE((latitude * 1e6).round());
final longitude = lon ?? 0.0;
writer.writeInt32LE((longitude * 1e6).round());
}
// Block 2 (lines 738-747) — runs whenever location OR lastModified is set
final hasLocation = lat != null && lon != null;
if (hasLocation || lastModified != null) {
writer.writeInt32LE(hasLocation ? (lat * 1e6).round() : 0);
writer.writeInt32LE(hasLocation ? (lon * 1e6).round() : 0);
if (lastModified != null) {
final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000;
writer.writeUInt32LE(lastModifiedTimestamp);
}
}
Block 1 runs unconditionally and emits 8 bytes. Block 2 then adds another 8 bytes whenever location or lastModified is set, plus the optional 4-byte timestamp.
For buildUpdateContactPathFrame(pk, path, n, lat: 49.1, lon: -123.1, lastModified: now):
| Expected |
Actual |
[lat x4][lon x4][ts x4] (12 bytes) |
[lat x4][lon x4][lat x4][lon x4][ts x4] (20 bytes) |
The firmware parses the second lat field as the timestamp, getting wildly wrong values.
Reproduction
final frame = buildUpdateContactPathFrame(
Uint8List(32), Uint8List(0), 0,
lat: 49.0, lon: -123.0, lastModified: DateTime.now(),
);
// Base frame is 136 bytes (cmd + pubKey + type + flags + pathLen + path + name + timestamp)
expect(frame.length, 148); // 136 + 8 lat/lon + 4 timestamp
// Actual: 156 (= 136 + 16 lat/lon + 4 timestamp)
Proposed fix
Delete block 1; keep only block 2. Block 2 is structurally correct: skips the optional tail when neither location nor lastModified is set, zero-fills position slots when only lastModified is present, appends the optional timestamp when applicable.
I have a fix + 5-test suite (covering all four optional-tail combinations) ready in a downstream fork. Happy to open a PR if you'd like — let me know.
Context
Caught while adding wire-protocol tests to a downstream fork. There were no tests covering this function previously, which is why the bug wasn't surfaced earlier.
Affected user-facing path: MeshCoreConnector.importDiscoveredContact() at line 3085 is the only caller that passes all three optional args (lat, lon, lastModified). That means every imported discovered contact gets a corrupted timestamp on the radio — probably manifesting as wildly wrong "last seen" values in the UI.
Bug
buildUpdateContactPathFrameinlib/connector/meshcore_protocol.dart:723-747emits two consecutive 8-byte position blocks instead of one. The frame ends up 8 bytes longer than the documented layout, and any caller that also passeslastModifiedwrites the timestamp 8 bytes deeper than the firmware expects.Frame layout per the doc comment
The function is documented (line 695) as producing:
So the optional tail is 8 bytes of position + optional 4-byte timestamp — 12 bytes max.
What it actually emits
Two back-to-back blocks both write position data:
Block 1 runs unconditionally and emits 8 bytes. Block 2 then adds another 8 bytes whenever location or
lastModifiedis set, plus the optional 4-byte timestamp.For
buildUpdateContactPathFrame(pk, path, n, lat: 49.1, lon: -123.1, lastModified: now):[lat x4][lon x4][ts x4](12 bytes)[lat x4][lon x4][lat x4][lon x4][ts x4](20 bytes)The firmware parses the second
latfield as the timestamp, getting wildly wrong values.Reproduction
Proposed fix
Delete block 1; keep only block 2. Block 2 is structurally correct: skips the optional tail when neither location nor
lastModifiedis set, zero-fills position slots when onlylastModifiedis present, appends the optional timestamp when applicable.I have a fix + 5-test suite (covering all four optional-tail combinations) ready in a downstream fork. Happy to open a PR if you'd like — let me know.
Context
Caught while adding wire-protocol tests to a downstream fork. There were no tests covering this function previously, which is why the bug wasn't surfaced earlier.
Affected user-facing path: MeshCoreConnector.importDiscoveredContact() at line 3085 is the only caller that passes all three optional args (lat, lon, lastModified). That means every imported discovered contact gets a corrupted timestamp on the radio — probably manifesting as wildly wrong "last seen" values in the UI.