Skip to content

Commit 91aed04

Browse files
authored
Merge pull request #1928 from dz0ny/feat/grp-data-upstream
feat: Add support for PAYLOAD_TYPE_GRP_DATA
2 parents 7d49faa + ae9fcb3 commit 91aed04

9 files changed

Lines changed: 161 additions & 10 deletions

File tree

docs/companion_protocol.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,33 @@ Bytes 7+: Message Text (UTF-8, variable length)
281281

282282
---
283283

284+
### 6. Send Channel Data Datagram
285+
286+
**Purpose**: Send binary datagram data to a channel.
287+
288+
**Command Format**:
289+
```
290+
Byte 0: 0x3E
291+
Bytes 1-2: Data Type (`data_type`, 16-bit little-endian)
292+
Byte 3: Channel Index (0-7)
293+
Bytes 4+: Binary payload bytes (variable length)
294+
```
295+
296+
**Data Type / Transport Mapping**:
297+
- `0x0000` is invalid for this command.
298+
- `0xFFFF` (`DATA_TYPE_DEV`) is the developer namespace for experimenting and developing apps.
299+
- Other non-zero values can be used as assigned application/community namespaces.
300+
301+
**Note**: Applications that need a timestamp should encode it inside the binary payload.
302+
303+
**Limits**:
304+
- Maximum payload length is `163` bytes.
305+
- Larger payloads are rejected with `PACKET_ERROR`.
306+
307+
**Response**: `PACKET_OK` (0x00) on success
308+
309+
---
310+
284311
### 6. Get Message
285312

286313
**Purpose**: Request the next queued message from the device.

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ https://github.com/meshcore-dev/MeshCore/blob/main/src/Packet.h#L19
386386
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
387387
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
388388
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
389-
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
389+
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type, data_len, blob)
390390
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
391391
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
392392

examples/companion_radio/MyMesh.cpp

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#define CMD_GET_AUTOADD_CONFIG 59
5959
#define CMD_GET_ALLOWED_REPEAT_FREQ 60
6060
#define CMD_SET_PATH_HASH_MODE 61
61+
#define CMD_SEND_CHANNEL_DATA 62
6162

6263
// Stats sub-types for CMD_GET_STATS
6364
#define STATS_TYPE_CORE 0
@@ -91,6 +92,9 @@
9192
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
9293
#define RESP_CODE_AUTOADD_CONFIG 25
9394
#define RESP_ALLOWED_REPEAT_FREQ 26
95+
#define RESP_CODE_CHANNEL_DATA_RECV 27
96+
97+
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9)
9498

9599
#define SEND_TIMEOUT_BASE_MILLIS 500
96100
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
@@ -204,7 +208,8 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co
204208
}
205209

206210
bool MyMesh::Frame::isChannelMsg() const {
207-
return buf[0] == RESP_CODE_CHANNEL_MSG_RECV || buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3;
211+
return buf[0] == RESP_CODE_CHANNEL_MSG_RECV || buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3 ||
212+
buf[0] == RESP_CODE_CHANNEL_DATA_RECV;
208213
}
209214

210215
void MyMesh::addToOfflineQueue(const uint8_t frame[], int len) {
@@ -564,6 +569,41 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
564569
#endif
565570
}
566571

572+
void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint16_t data_type,
573+
const uint8_t *data, size_t data_len) {
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;
578+
}
579+
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+
586+
uint8_t channel_idx = findChannelIdx(channel);
587+
out_frame[i++] = channel_idx;
588+
out_frame[i++] = pkt->isRouteFlood() ? pkt->path_len : 0xFF;
589+
out_frame[i++] = (uint8_t)(data_type & 0xFF);
590+
out_frame[i++] = (uint8_t)(data_type >> 8);
591+
out_frame[i++] = (uint8_t)data_len;
592+
593+
int copy_len = (int)data_len;
594+
if (copy_len > 0) {
595+
memcpy(&out_frame[i], data, copy_len);
596+
i += copy_len;
597+
}
598+
addToOfflineQueue(out_frame, i);
599+
600+
if (_serial->isConnected()) {
601+
uint8_t frame[1];
602+
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
603+
_serial->writeFrame(frame, 1);
604+
}
605+
}
606+
567607
uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
568608
uint8_t len, uint8_t *reply) {
569609
if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
@@ -1041,7 +1081,7 @@ void MyMesh::handleCmdFrame(size_t len) {
10411081
? ERR_CODE_NOT_FOUND
10421082
: ERR_CODE_UNSUPPORTED_CMD); // unknown recipient, or unsuported TXT_TYPE_*
10431083
}
1044-
} else if (cmd_frame[0] == CMD_SEND_CHANNEL_TXT_MSG) { // send GroupChannel msg
1084+
} else if (cmd_frame[0] == CMD_SEND_CHANNEL_TXT_MSG) { // send GroupChannel text msg
10451085
int i = 1;
10461086
uint8_t txt_type = cmd_frame[i++]; // should be TXT_TYPE_PLAIN
10471087
uint8_t channel_idx = cmd_frame[i++];
@@ -1061,6 +1101,31 @@ void MyMesh::handleCmdFrame(size_t len) {
10611101
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
10621102
}
10631103
}
1104+
} else if (cmd_frame[0] == CMD_SEND_CHANNEL_DATA) { // send GroupChannel datagram
1105+
if (len < 4) {
1106+
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
1107+
return;
1108+
}
1109+
int i = 1;
1110+
uint16_t data_type = ((uint16_t)cmd_frame[i]) | (((uint16_t)cmd_frame[i + 1]) << 8);
1111+
i += 2;
1112+
uint8_t channel_idx = cmd_frame[i++];
1113+
const uint8_t *payload = &cmd_frame[i];
1114+
int payload_len = (len > (size_t)i) ? (int)(len - i) : 0;
1115+
1116+
ChannelDetails channel;
1117+
if (!getChannel(channel_idx, channel)) {
1118+
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
1119+
} else if (data_type == 0) {
1120+
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
1121+
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
1122+
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
1123+
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
1124+
} else if (sendGroupData(channel.channel, data_type, payload, payload_len)) {
1125+
writeOKFrame();
1126+
} else {
1127+
writeErrFrame(ERR_CODE_TABLE_FULL);
1128+
}
10641129
} else if (cmd_frame[0] == CMD_GET_CONTACTS) { // get Contact list
10651130
if (_iter_started) {
10661131
writeErrFrame(ERR_CODE_BAD_STATE); // iterator is currently busy

examples/companion_radio/MyMesh.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
137137
const uint8_t *sender_prefix, const char *text) override;
138138
void onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp,
139139
const char *text) override;
140+
void onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint16_t data_type,
141+
const uint8_t *data, size_t data_len) override;
140142

141143
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
142144
uint8_t len, uint8_t *reply) override;

src/MeshCore.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +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 - 3)
2021
#define MAX_PATH_SIZE 64
2122
#define MAX_TRANS_UNIT 255
2223

@@ -100,4 +101,4 @@ class RTCClock {
100101
}
101102
};
102103

103-
}
104+
}

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: data_type(uint16), 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: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,18 @@ 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-
uint8_t txt_type = data[4];
357-
if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { // 0 = plain text msg
356+
if (type == PAYLOAD_TYPE_GRP_TXT) {
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);
365+
return;
366+
}
367+
358368
uint32_t timestamp;
359369
memcpy(&timestamp, data, 4);
360370

@@ -363,6 +373,23 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes
363373

364374
// notify UI of this new message
365375
onChannelMessageRecv(channel, packet, timestamp, (const char *) &data[5]); // let UI know
376+
} else if (type == PAYLOAD_TYPE_GRP_DATA) {
377+
if (len < 3) {
378+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group data payload len=%d", (uint32_t)len);
379+
return;
380+
}
381+
382+
uint16_t data_type = ((uint16_t)data[0]) | (((uint16_t)data[1]) << 8);
383+
uint8_t data_len = data[2];
384+
size_t available_len = len - 3;
385+
386+
if (data_len > available_len) {
387+
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping malformed group data type=%d len=%d available=%d",
388+
(uint32_t)data_type, (uint32_t)data_len, (uint32_t)available_len);
389+
return;
390+
}
391+
392+
onChannelDataRecv(channel, packet, data_type, &data[3], data_len);
366393
}
367394
}
368395

@@ -454,6 +481,31 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
454481
return false;
455482
}
456483

484+
bool BaseChatMesh::sendGroupData(mesh::GroupChannel& channel, uint16_t data_type, const uint8_t* data, int data_len) {
485+
if (data_len < 0) {
486+
MESH_DEBUG_PRINTLN("sendGroupData: invalid negative data_len=%d", data_len);
487+
return false;
488+
}
489+
if (data_len > MAX_GROUP_DATA_LENGTH) {
490+
MESH_DEBUG_PRINTLN("sendGroupData: data_len=%d exceeds max=%d", data_len, MAX_GROUP_DATA_LENGTH);
491+
return false;
492+
}
493+
494+
uint8_t temp[3 + MAX_GROUP_DATA_LENGTH];
495+
temp[0] = (uint8_t)(data_type & 0xFF);
496+
temp[1] = (uint8_t)(data_type >> 8);
497+
temp[2] = (uint8_t)data_len;
498+
if (data_len > 0) memcpy(&temp[3], data, data_len);
499+
500+
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 3 + data_len);
501+
if (pkt == NULL) {
502+
MESH_DEBUG_PRINTLN("sendGroupData: unable to create group datagram, data_len=%d", data_len);
503+
return false;
504+
}
505+
sendFloodScoped(channel, pkt);
506+
return true;
507+
}
508+
457509
bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) {
458510
int plen = getBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, temp_buf); // retrieve last raw advert packet
459511
if (plen == 0) return false; // not found

src/helpers/BaseChatMesh.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ class BaseChatMesh : public mesh::Mesh {
111111
virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
112112
virtual void onSendTimeout() = 0;
113113
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
114+
virtual void onChannelDataRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint16_t data_type,
115+
const uint8_t* data, size_t data_len) {}
114116
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
115117
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
116118
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
@@ -148,6 +150,7 @@ class BaseChatMesh : public mesh::Mesh {
148150
int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout);
149151
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
150152
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
153+
bool sendGroupData(mesh::GroupChannel& channel, uint16_t data_type, const uint8_t* data, int data_len);
151154
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
152155
int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout);
153156
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);

src/helpers/TxtDataHelpers.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
#include <stddef.h>
44
#include <stdint.h>
55

6-
#define TXT_TYPE_PLAIN 0 // a plain text message
7-
#define TXT_TYPE_CLI_DATA 1 // a CLI command
8-
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
6+
#define TXT_TYPE_PLAIN 0 // a plain text message
7+
#define TXT_TYPE_CLI_DATA 1 // a CLI command
8+
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
9+
#define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps
910

1011
class StrHelper {
1112
public:

0 commit comments

Comments
 (0)