feat(#429): live channel-change push (0x91) + outgoing flag on device-composed V3 messages

Client half of the coordinated firmware+client change (firmware wadamesh#50,
verified against source).

Part A: handle push code 0x91 (pushCodeChannelsChanged) by re-polling
getChannels(force: true), so channels added/removed on the device appear in the
client without a reconnect.

Part B: read reserved1 bit0 of the V3 contact and channel message frames as an
outgoing flag. A message composed on the device (DM or channel) now renders as
sent-by-me (isOutgoing: true) instead of received. The channel self-echo guard
is bypassed for outgoing so device-composed channel messages are not dropped.
Removed the dead upstream hasPath/path-bytes branch in ChannelMessage.fromFrame
(no firmware, stock or wadamesh, appends path bytes to this frame; verified).

Backward-compatible: old firmware sends 0 (received, as today); an old client
ignores the bit.

Citadel: meshcore-open-p12
fix/445-windows-sqlite3-native-assets
Strycher 2 months ago
parent f4823cadb2
commit e8c756718a

@ -4794,6 +4794,11 @@ class MeshCoreConnector extends ChangeNotifier {
case pushCodeTelemetryResponse:
_handleSelfTelemetry(frame);
break;
case pushCodeChannelsChanged:
// Device's channel table changed (add/update/delete). Re-poll instead
// of only refreshing on reconnect (#429 part A).
unawaited(getChannels(force: true));
break;
case respCodeChannelInfo:
_handleChannelInfo(frame);
break;
@ -5910,11 +5915,15 @@ class MeshCoreConnector extends ChangeNotifier {
// Companion radio layout:
// [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
// double snr = 0;
bool isOutgoing = false;
if (code == respCodeContactMsgRecvV3) {
// Older firmware layout with SNR as a signed byte after the code
// snr = reader.readInt8().toDouble() * 4; // SNR in dB, scaled by 4
reader.skipBytes(1); // Skip SNR byte
reader.skipBytes(2); // Skip reserved bytes
// reserved1 bit0 = outgoing flag: set for a message composed on the
// device itself, so it renders as sent-by-me (#429 part B).
isOutgoing = (reader.readByte() & 0x01) != 0;
reader.skipBytes(1); // Skip reserved2
}
final senderPrefix = reader.readBytes(6);
@ -5974,13 +5983,13 @@ class MeshCoreConnector extends ChangeNotifier {
senderKey: contact.publicKey,
text: decodedText,
timestamp: timestamp,
isOutgoing: false,
isOutgoing: isOutgoing,
isCli: isCli,
status: MessageStatus.delivered,
status: isOutgoing ? MessageStatus.sent : MessageStatus.delivered,
pathLength: pathLength == 0xFF ? 0 : pathLength,
pathBytes: Uint8List(0),
fourByteRoomContactKey: roomAuthorPrefix,
rxTime: DateTime.now(),
rxTime: isOutgoing ? null : DateTime.now(),
);
} catch (e) {
appLogger.warn('Error parsing contact direct message: $e');
@ -6246,7 +6255,10 @@ class MeshCoreConnector extends ChangeNotifier {
}
final parsed = ChannelMessage.fromFrame(frame);
if (parsed != null && parsed.channelIndex != null) {
if (_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) {
// Device-composed (outgoing) messages legitimately carry our own name and
// no path; don't let the self-echo guard drop them (#429 part B).
if (!parsed.isOutgoing &&
_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) {
return;
}
_lastChannelMsgRxTime = DateTime.now();

@ -525,6 +525,8 @@ const int pushCodeTraceData = 0x89;
const int pushCodeNewAdvert = 0x8A;
const int pushCodeTelemetryResponse = 0x8B;
const int pushCodeBinaryResponse = 0x8C;
const int pushCodeChannelsChanged =
0x91; // #429 part A: device channel table changed; re-poll getChannels
// Contact/advertisement types
const int advTypeChat = 1;

@ -195,18 +195,17 @@ class ChannelMessage {
int txtType;
Uint8List pathBytes = Uint8List(0);
int channelIdx;
bool isOutgoing = false;
if (code == respCodeChannelMsgRecvV3) {
reader.skipBytes(1); // Skip SNR
final flags = reader.readByte();
final hasPath = (flags & 0x01) != 0;
reader.skipBytes(1); // Skip reserved byte
// reserved1 bit0 = outgoing flag: set for a message composed on the
// device itself, so it renders as sent-by-me (#429 part B). No firmware
// appends path bytes to this frame, so path_len is metadata only.
isOutgoing = (reader.readByte() & 0x01) != 0;
reader.skipBytes(1); // Skip reserved2
channelIdx = reader.readByte();
pathLen = reader.readInt8();
txtType = reader.readByte();
if (hasPath && pathLen > 0) {
reader.rewind(); // Rewind to read path length again for pathBytes
pathBytes = reader.readBytes(pathLen);
}
} else {
channelIdx = reader.readByte();
pathLen = reader.readInt8();
@ -244,12 +243,12 @@ class ChannelMessage {
senderName: senderName,
text: decodedText,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
isOutgoing: false,
isOutgoing: isOutgoing,
status: ChannelMessageStatus.sent,
pathLength: pathLen,
pathBytes: pathBytes,
channelIndex: channelIdx,
rxTime: DateTime.now(),
rxTime: isOutgoing ? null : DateTime.now(),
);
} catch (e) {
appLogger.error('Error parsing channel message frame: $e');

Loading…
Cancel
Save

Powered by TurnKey Linux.