From 1209d54d2ee29e6ca6001c498c52d37bfc2cf3cf Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 27 Feb 2025 12:51:00 +1100 Subject: [PATCH] * various changes for CLI support via companion radio --- examples/companion_radio/main.cpp | 22 +++++++++++--- examples/simple_repeater/main.cpp | 10 +++++-- examples/simple_room_server/main.cpp | 10 +++++-- examples/simple_secure_chat/main.cpp | 3 ++ src/helpers/BaseChatMesh.cpp | 43 ++++++++++++++++++++++++++-- src/helpers/BaseChatMesh.h | 2 ++ 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 07b6e507..151f90c3 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -438,12 +438,12 @@ protected: return false; } - void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { + void queueMessage(const ContactInfo& from, uint8_t txt_type, uint8_t path_len, uint32_t sender_timestamp, const char *text) { int i = 0; out_frame[i++] = RESP_CODE_CONTACT_MSG_RECV; memcpy(&out_frame[i], from.id.pub_key, 6); i += 6; // just 6-byte prefix out_frame[i++] = path_len; - out_frame[i++] = TXT_TYPE_PLAIN; + out_frame[i++] = txt_type; memcpy(&out_frame[i], &sender_timestamp, 4); i += 4; int tlen = strlen(text); // TODO: UTF-8 ?? if (i + tlen > MAX_FRAME_SIZE) { @@ -461,6 +461,14 @@ protected: } } + void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { + queueMessage(from, TXT_TYPE_PLAIN, path_len, sender_timestamp, text); + } + + void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { + queueMessage(from, TXT_TYPE_CLI_DATA, path_len, sender_timestamp, text); + } + void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override { int i = 0; out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV; @@ -677,12 +685,18 @@ public: memcpy(&msg_timestamp, &cmd_frame[i], 4); i += 4; uint8_t* pub_key_prefix = &cmd_frame[i]; i += 6; ContactInfo* recipient = lookupContactByPubKey(pub_key_prefix, 6); - if (recipient && attempt < 4 && txt_type == TXT_TYPE_PLAIN) { + if (recipient && attempt < 4 && (txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_CLI_DATA)) { char *text = (char *) &cmd_frame[i]; int tlen = len - i; uint32_t est_timeout; text[tlen] = 0; // ensure null - int result = sendMessage(*recipient, msg_timestamp, attempt, text, expected_ack_crc, est_timeout); + int result; + if (txt_type == TXT_TYPE_CLI_DATA) { + result = sendCommandData(*recipient, msg_timestamp, attempt, text, est_timeout); + expected_ack_crc = 0; // no Ack expected + } else { + result = sendMessage(*recipient, msg_timestamp, attempt, text, expected_ack_crc, est_timeout); + } // TODO: add expected ACK to table if (result == MSG_SEND_FAILED) { writeErrFrame(); diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 4f1bdde3..7f974d29 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -469,7 +469,7 @@ protected: timestamp++; } memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (TXT_TYPE_PLAIN << 2); // TODO: change this to TXT_TYPE_CLI_DATA soon + temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN // calc expected ACK reply //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); @@ -581,9 +581,15 @@ public: } } - void handleCommand(uint32_t sender_timestamp, const char* command, char reply[]) { + void handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { while (*command == ' ') command++; // skip leading spaces + if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) + memcpy(reply, command, 3); // reflect the prefix back + reply += 3; + command += 3; + } + if (memcmp(command, "reboot", 6) == 0) { board.reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 5978be7f..f653d4cb 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -423,6 +423,7 @@ protected: if (flags == TXT_TYPE_CLI_DATA) { if (client->is_admin) { handleAdminCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) send_ack = true; } else { temp[5] = 0; // no reply @@ -452,7 +453,6 @@ protected: now++; } memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (TXT_TYPE_PLAIN << 2); // attempt and flags // calc expected ACK reply //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); @@ -604,9 +604,15 @@ public: } } - void handleAdminCommand(uint32_t sender_timestamp, const char* command, char reply[]) { + void handleAdminCommand(uint32_t sender_timestamp, const char* command, char* reply) { while (*command == ' ') command++; // skip leading spaces + if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) + memcpy(reply, command, 3); // reflect the prefix back + reply += 3; + command += 3; + } + if (memcmp(command, "reboot", 6) == 0) { board.reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index b48b7ab6..ef2432fd 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -259,6 +259,9 @@ protected: } } + void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { + } + 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"); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index bc0457ef..410e27fe 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -99,13 +99,13 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t timestamp; memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4]; // message attempt number, and other flags + uint flags = data[4] >> 2; // message attempt number, and other flags // 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 //if ( ! alreadyReceived timestamp ) { - if ((flags >> 2) == TXT_TYPE_PLAIN) { + if (flags == TXT_TYPE_PLAIN) { onMessageRecv(from, packet->isRouteFlood() ? packet->path_len : 0xFF, timestamp, (const char *) &data[5]); // let UI know uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it @@ -126,8 +126,19 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } } + } else if (flags == TXT_TYPE_CLI_DATA) { + onCommandDataRecv(from, packet->isRouteFlood() ? packet->path_len : 0xFF, timestamp, (const char *) &data[5]); // let UI know + // NOTE: no ack expected for CLI_DATA replies + + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK + mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); + if (path) sendFlood(path); + } + } else if (flags == TXT_TYPE_SIGNED_PLAIN) { + // TODO } else { - MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) (flags >> 2)); + MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); } } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { onContactResponse(from, data, len); @@ -228,6 +239,32 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, return rc; } +int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout) { + int text_len = strlen(text); + if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED; + + uint8_t temp[5+MAX_TEXT_LEN+1]; + memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique + temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); + memcpy(&temp[5], text, text_len + 1); + + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); + if (pkt == NULL) return MSG_SEND_FAILED; + + uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); + int rc; + if (recipient.out_path_len < 0) { + sendFlood(pkt); + txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); + rc = MSG_SEND_SENT_FLOOD; + } else { + sendDirect(pkt, recipient.out_path, recipient.out_path_len); + txt_send_timeout = futureMillis(est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len)); + rc = MSG_SEND_SENT_DIRECT; + } + return rc; +} + bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len) { uint8_t temp[5+MAX_TEXT_LEN+32]; memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 96671f94..a809c48c 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -86,6 +86,7 @@ protected: virtual bool processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; virtual void onMessageRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0; + virtual void onCommandDataRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0; 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; @@ -111,6 +112,7 @@ protected: public: mesh::Packet* createSelfAdvert(const char* name, double lat=0.0, double lon=0.0); int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); + int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout);