diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 3205c0fe..a27468cb 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -45,6 +45,7 @@ #define DIRECT_SEND_PERHOP_FACTOR 4.0f #define DIRECT_SEND_PERHOP_EXTRA_MILLIS 200 +#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg==" #if defined(HELTEC_LORA_V3) #include @@ -74,6 +75,7 @@ static int curr_contact_idx = 0; class MyMesh : public BaseChatMesh, ContactVisitor { FILESYSTEM* _fs; uint32_t expected_ack_crc; + mesh::GroupChannel* _public; unsigned long last_msg_sent; ContactInfo* curr_recipient; char command[MAX_TEXT_LEN+1]; @@ -191,6 +193,15 @@ protected: } } + void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override { + if (in_path_len < 0) { + Serial.printf("PUBLIC CHANNEL MSG -> (Direct!)\n"); + } else { + Serial.printf("PUBLIC CHANNEL MSG -> (Flood) hops %d\n", in_path_len); + } + Serial.printf(" %s\n", text); + } + uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } @@ -226,6 +237,7 @@ public: } loadContacts(); + _public = addChannel(PUBLIC_GROUP_PSK); // pre-configure Andy's public channel } void showWelcome() { @@ -261,6 +273,23 @@ public: } else { Serial.println(" ERROR: no recipient selected (use 'to' cmd)."); } + } else if (memcmp(command, "public ", 7) == 0) { // send GroupChannel msg + uint8_t temp[5+MAX_TEXT_LEN+32]; + uint32_t timestamp = getRTCClock()->getCurrentTime(); + memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique + temp[4] = 0; // attempt and flags + + sprintf((char *) &temp[5], "%s: %s", self_name, &command[7]); // : + temp[5 + MAX_TEXT_LEN] = 0; // truncate if too long + + int len = strlen((char *) &temp[5]); + auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, *_public, temp, 5 + len); + if (pkt) { + sendFlood(pkt); + Serial.println(" Sent."); + } else { + Serial.println(" ERROR: unable to send"); + } } else if (memcmp(command, "list", 4) == 0) { // show Contact list, by most recent int n = 0; if (command[4] == ' ') { // optional param, last 'N' @@ -313,6 +342,7 @@ public: Serial.println(" send "); Serial.println(" advert"); Serial.println(" reset path"); + Serial.println(" public "); } else { Serial.print(" ERROR: unknown command: "); Serial.println(command); } diff --git a/platformio.ini b/platformio.ini index b42d75ec..f58aff12 100644 --- a/platformio.ini +++ b/platformio.ini @@ -96,12 +96,14 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=100 + -D MAX_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 [env:Heltec_v3_test_admin] extends = Heltec_lora32_v3 @@ -261,9 +263,11 @@ extends = rak4631 build_flags = ${rak4631.build_flags} -D MAX_CONTACTS=100 + -D MAX_CHANNELS=1 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${rak4631.lib_deps} + densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 067496a4..e9ea6673 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -1,4 +1,6 @@ #include +#include +#include mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; @@ -150,6 +152,30 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { } } +int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) { + int n = 0; + for (int i = 0; i < num_channels && n < max_matches; i++) { + if (channels[i].hash[0] == hash[0]) { + dest[n++] = channels[i]; + } + } + return n; +} + +void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) { + uint8_t txt_type = data[4]; + if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { // 0 = plain text msg + uint32_t timestamp; + memcpy(×tamp, data, 4); + + // len can be > original length, but 'text' will be padded with zeroes + data[len] = 0; // need to make a C string again, with null terminator + + // notify UI of this new message + onChannelMessageRecv(channel, packet->isRouteFlood() ? packet->path_len : -1, timestamp, (const char *) &data[5]); // let UI know + } +} + mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint8_t attempt, const char *text, uint32_t& expected_ack) { int text_len = strlen(text); if (text_len > MAX_TEXT_LEN) return NULL; @@ -240,6 +266,21 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { return false; } +mesh::GroupChannel* BaseChatMesh::addChannel(const char* psk_base64) { + if (num_channels < MAX_CHANNELS) { + auto dest = &channels[num_channels]; + + memset(dest->secret, 0, sizeof(dest->secret)); + int len = decode_base64((unsigned char *) psk_base64, strlen(psk_base64), dest->secret); + if (len == 32 || len == 16) { + mesh::Utils::sha256(dest->hash, sizeof(dest->hash), dest->secret, len); + num_channels++; + return dest; + } + } + return NULL; +} + bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) { if (next_idx >= mesh->num_contacts) return false; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 84312d15..f3a35be7 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -48,6 +48,8 @@ class BaseChatMesh : public mesh::Mesh { int sort_array[MAX_CONTACTS]; int matching_peer_indexes[MAX_SEARCH_RESULTS]; unsigned long txt_send_timeout; + mesh::GroupChannel channels[MAX_CHANNELS]; + int num_channels; mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint8_t attempt, const char *text, uint32_t& expected_ack); @@ -56,6 +58,7 @@ protected: : mesh::Mesh(radio, ms, rng, rtc, mgr, tables) { num_contacts = 0; + num_channels = 0; txt_send_timeout = 0; } @@ -67,6 +70,7 @@ protected: virtual uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const = 0; virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0; virtual void onSendTimeout() = 0; + virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) = 0; // Mesh overrides void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override; @@ -75,6 +79,8 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; + int searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel channels[], int max_matches) override; + void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override; public: mesh::Packet* createSelfAdvert(const char* name); @@ -83,6 +89,7 @@ public: void scanRecentContacts(int last_n, ContactVisitor* visitor); ContactInfo* searchContactsByPrefix(const char* name_prefix); bool addContact(const ContactInfo& contact); + mesh::GroupChannel* addChannel(const char* psk_base64); void loop(); };