Skip to content

Commit f25d7a8

Browse files
committed
fix: Align channel data framing
ref: #1928
1 parent a21b83b commit f25d7a8

5 files changed

Lines changed: 63 additions & 56 deletions

File tree

docs/companion_protocol.md

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ Bytes 7+: Binary payload bytes (variable length)
303303
- Values other than `0xFF` are reserved for official protocol extensions.
304304

305305
**Limits**:
306-
- Maximum payload length is `163` bytes (`MAX_GROUP_DATA_LENGTH`).
306+
- Maximum payload length is `160` bytes.
307307
- Larger payloads are rejected with `PACKET_ERROR` / `ERR_CODE_ILLEGAL_ARG`.
308308

309309
**Response**: `PACKET_OK` (0x00) on success
@@ -326,7 +326,7 @@ Byte 0: 0x0A
326326

327327
**Response**:
328328
- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages
329-
- `PACKET_CHANNEL_DATA_RECV` (0x1B) or `PACKET_CHANNEL_DATA_RECV_V3` (0x1C) for channel data
329+
- `PACKET_CHANNEL_DATA_RECV` (0x1B) for channel data
330330
- `PACKET_CONTACT_MSG_RECV` (0x07) or `PACKET_CONTACT_MSG_RECV_V3` (0x10) for contact messages
331331
- `PACKET_NO_MORE_MSGS` (0x0A) if no messages available
332332

@@ -397,8 +397,7 @@ Messages are received via the TX characteristic (notifications). The device send
397397
- `PACKET_CHANNEL_MSG_RECV_V3` (0x11) - Version 3 with SNR
398398

399399
2. **Channel Data**:
400-
- `PACKET_CHANNEL_DATA_RECV` (0x1B) - Standard format
401-
- `PACKET_CHANNEL_DATA_RECV_V3` (0x1C) - Version 3 with SNR
400+
- `PACKET_CHANNEL_DATA_RECV` (0x1B) - Includes SNR and reserved bytes
402401

403402
3. **Contact Messages**:
404403
- `PACKET_CONTACT_MSG_RECV` (0x07) - Standard format
@@ -502,45 +501,39 @@ Bytes 11+: Payload bytes
502501

503502
### Channel Data Format
504503

505-
**Standard Format** (`PACKET_CHANNEL_DATA_RECV`, 0x1B):
504+
**Format** (`PACKET_CHANNEL_DATA_RECV`, 0x1B):
506505
```
507506
Byte 0: 0x1B (packet type)
508-
Byte 1: Channel Index (0-7)
509-
Byte 2: Path Length
510-
Byte 3: Data Type
511-
Bytes 4-7: Timestamp (32-bit little-endian)
512-
Bytes 8+: Payload bytes
513-
```
514-
515-
**V3 Format** (`PACKET_CHANNEL_DATA_RECV_V3`, 0x1C):
516-
```
517-
Byte 0: 0x1C (packet type)
518507
Byte 1: SNR (signed byte, multiplied by 4)
519508
Bytes 2-3: Reserved
520509
Byte 4: Channel Index (0-7)
521510
Byte 5: Path Length
522511
Byte 6: Data Type
523-
Bytes 7-10: Timestamp (32-bit little-endian)
524-
Bytes 11+: Payload bytes
512+
Byte 7: Data Length
513+
Bytes 8-11: Timestamp (32-bit little-endian)
514+
Bytes 12+: Payload bytes
525515
```
526516

527517
**Parsing Pseudocode**:
528518
```python
529519
def parse_channel_frame(data):
530520
packet_type = data[0]
531521
offset = 1
522+
snr = None
532523

533-
# Check for V3 format
534-
if packet_type in (0x11, 0x1C): # V3
524+
# Formats with explicit SNR/reserved bytes
525+
if packet_type in (0x11, 0x1B):
535526
snr_byte = data[offset]
536527
snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0)
537528
offset += 3 # Skip SNR + reserved
538529

539530
channel_idx = data[offset]
540531
path_len = data[offset + 1]
541532
item_type = data[offset + 2]
542-
timestamp = int.from_bytes(data[offset+3:offset+7], 'little')
543-
payload = data[offset+7:]
533+
data_len = data[offset + 3] if packet_type == 0x1B else None
534+
timestamp = int.from_bytes(data[offset+4:offset+8], 'little') if packet_type == 0x1B else int.from_bytes(data[offset+3:offset+7], 'little')
535+
payload_offset = offset + 8 if packet_type == 0x1B else offset + 7
536+
payload = data[payload_offset:payload_offset + data_len] if packet_type == 0x1B else data[payload_offset:]
544537
is_text = packet_type in (0x08, 0x11)
545538
if is_text and item_type == 0:
546539
message = payload.decode('utf-8')
@@ -553,7 +546,7 @@ def parse_channel_frame(data):
553546
'timestamp': timestamp,
554547
'payload': payload,
555548
'message': message,
556-
'snr': snr if packet_type in (0x11, 0x1C) else None
549+
'snr': snr
557550
}
558551
```
559552

@@ -590,8 +583,7 @@ Use `CMD_SEND_CHANNEL_TXT_MSG` for plain text, and `CMD_SEND_CHANNEL_DATA` for b
590583
| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
591584
| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
592585
| 0x12 | PACKET_CHANNEL_INFO | Channel information |
593-
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data (standard) |
594-
| 0x1C | PACKET_CHANNEL_DATA_RECV_V3| Channel data (V3 with SNR) |
586+
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data (includes SNR) |
595587
| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
596588
| 0x82 | PACKET_ACK | Acknowledgment |
597589
| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
@@ -892,7 +884,7 @@ def on_notification_received(data):
892884
packet_type = data[0]
893885

894886
if packet_type in (PACKET_CHANNEL_MSG_RECV, PACKET_CHANNEL_MSG_RECV_V3,
895-
PACKET_CHANNEL_DATA_RECV, PACKET_CHANNEL_DATA_RECV_V3):
887+
PACKET_CHANNEL_DATA_RECV):
896888
message = parse_channel_frame(data)
897889
handle_channel_message(message)
898890
elif packet_type == PACKET_MESSAGES_WAITING:

examples/companion_radio/MyMesh.cpp

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
#define RESP_CODE_AUTOADD_CONFIG 25
9494
#define RESP_ALLOWED_REPEAT_FREQ 26
9595
#define RESP_CODE_CHANNEL_DATA_RECV 27
96-
#define RESP_CODE_CHANNEL_DATA_RECV_V3 28
96+
97+
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 12)
9798

9899
#define SEND_TIMEOUT_BASE_MILLIS 500
99100
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
@@ -208,7 +209,7 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co
208209

209210
bool MyMesh::Frame::isChannelMsg() const {
210211
return buf[0] == RESP_CODE_CHANNEL_MSG_RECV || buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3 ||
211-
buf[0] == RESP_CODE_CHANNEL_DATA_RECV || buf[0] == RESP_CODE_CHANNEL_DATA_RECV_V3;
212+
buf[0] == RESP_CODE_CHANNEL_DATA_RECV;
212213
}
213214

214215
void MyMesh::addToOfflineQueue(const uint8_t frame[], int len) {
@@ -570,28 +571,26 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
570571

571572
void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp, uint8_t data_type,
572573
const uint8_t *data, size_t data_len) {
573-
int i = 0;
574-
if (app_target_ver >= 3) {
575-
out_frame[i++] = RESP_CODE_CHANNEL_DATA_RECV_V3;
576-
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
577-
out_frame[i++] = 0; // reserved1
578-
out_frame[i++] = 0; // reserved2
579-
} else {
580-
out_frame[i++] = RESP_CODE_CHANNEL_DATA_RECV;
574+
if (data_len > MAX_CHANNEL_DATA_LENGTH) {
575+
MESH_DEBUG_PRINTLN("onChannelDataRecv: dropping payload_len=%d exceeds frame limit=%d",
576+
(uint32_t)data_len, (uint32_t)MAX_CHANNEL_DATA_LENGTH);
577+
return;
581578
}
582579

580+
int i = 0;
581+
out_frame[i++] = RESP_CODE_CHANNEL_DATA_RECV;
582+
out_frame[i++] = (int8_t)(pkt->getSNR() * 4);
583+
out_frame[i++] = 0; // reserved1
584+
out_frame[i++] = 0; // reserved2
585+
583586
uint8_t channel_idx = findChannelIdx(channel);
584587
out_frame[i++] = channel_idx;
585588
out_frame[i++] = pkt->isRouteFlood() ? pkt->path_len : 0xFF;
586589
out_frame[i++] = data_type;
590+
out_frame[i++] = (uint8_t)data_len;
587591
memcpy(&out_frame[i], &timestamp, 4);
588592
i += 4;
589593

590-
size_t available = MAX_FRAME_SIZE - i;
591-
if (data_len > available) {
592-
MESH_DEBUG_PRINTLN("onChannelDataRecv(): payload_len=%d exceeds frame space=%d, truncating", (uint32_t)data_len, (uint32_t)available);
593-
data_len = available;
594-
}
595594
int copy_len = (int)data_len;
596595
if (copy_len > 0) {
597596
memcpy(&out_frame[i], data, copy_len);
@@ -1108,8 +1107,8 @@ void MyMesh::handleCmdFrame(size_t len) {
11081107
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
11091108
} else if (data_type != DATA_TYPE_CUSTOM) {
11101109
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
1111-
} else if (payload_len > MAX_GROUP_DATA_LENGTH) {
1112-
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_GROUP_DATA_LENGTH);
1110+
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
1111+
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
11131112
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
11141113
} else if (sendGroupData(msg_timestamp, channel.channel, data_type, payload, payload_len)) {
11151114
writeOKFrame();

src/MeshCore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#define PATH_HASH_SIZE 1
1818

1919
#define MAX_PACKET_PAYLOAD 184
20-
#define MAX_GROUP_DATA_LENGTH (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE - 5)
20+
#define MAX_GROUP_DATA_LENGTH (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE - 6)
2121
#define MAX_PATH_SIZE 64
2222
#define MAX_TRANS_UNIT 255
2323

src/Packet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace mesh {
2222
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack
2323
#define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
2424
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
25-
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
25+
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, data_type, data_len, blob)
2626
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
2727
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
2828
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop

src/helpers/BaseChatMesh.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -353,15 +353,15 @@ int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel d
353353
#endif
354354

355355
void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) {
356-
if (len < 5) {
357-
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group payload len=%d", (uint32_t)len);
358-
return;
359-
}
360-
361-
uint8_t data_type = data[4];
362356
if (type == PAYLOAD_TYPE_GRP_TXT) {
363-
if ((data_type >> 2) != 0) {
364-
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping unsupported group text type=%d", (uint32_t)data_type);
357+
if (len < 5) {
358+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group text payload len=%d", (uint32_t)len);
359+
return;
360+
}
361+
362+
uint8_t txt_type = data[4];
363+
if ((txt_type >> 2) != 0) {
364+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping unsupported group text type=%d", (uint32_t)txt_type);
365365
return;
366366
}
367367

@@ -374,9 +374,24 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes
374374
// notify UI of this new message
375375
onChannelMessageRecv(channel, packet, timestamp, (const char *) &data[5]); // let UI know
376376
} else if (type == PAYLOAD_TYPE_GRP_DATA) {
377+
if (len < 6) {
378+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group data payload len=%d", (uint32_t)len);
379+
return;
380+
}
381+
377382
uint32_t timestamp;
378383
memcpy(&timestamp, data, 4);
379-
onChannelDataRecv(channel, packet, timestamp, data_type, &data[5], len - 5);
384+
uint8_t data_type = data[4];
385+
uint8_t data_len = data[5];
386+
size_t available_len = len - 6;
387+
388+
if (data_len > available_len) {
389+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping malformed group data type=%d len=%d available=%d",
390+
(uint32_t)data_type, (uint32_t)data_len, (uint32_t)available_len);
391+
return;
392+
}
393+
394+
onChannelDataRecv(channel, packet, timestamp, data_type, &data[6], data_len);
380395
}
381396
}
382397

@@ -478,12 +493,13 @@ bool BaseChatMesh::sendGroupData(uint32_t timestamp, mesh::GroupChannel& channel
478493
return false;
479494
}
480495

481-
uint8_t temp[5 + MAX_GROUP_DATA_LENGTH];
496+
uint8_t temp[6 + MAX_GROUP_DATA_LENGTH];
482497
memcpy(temp, &timestamp, 4);
483498
temp[4] = data_type;
484-
if (data_len > 0) memcpy(&temp[5], data, data_len);
499+
temp[5] = (uint8_t)data_len;
500+
if (data_len > 0) memcpy(&temp[6], data, data_len);
485501

486-
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 5 + data_len);
502+
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 6 + data_len);
487503
if (pkt == NULL) {
488504
MESH_DEBUG_PRINTLN("sendGroupData: unable to create group datagram, data_len=%d", data_len);
489505
return false;

0 commit comments

Comments
 (0)