* TxtDataHelpers.h, with standard TXT sub-types

* Terminal chat: added "time <epoch-secs>"
* repeater: now recognises either TXT_TYPE_PLAIN or TXT_TYPE_CLI_DATA for CLI commands
* Room server: now uses TXT_TYPE_SIGNED_PLAIN (for push/outbound), recognises TXT_TYPE_PLAIN (to add a Post), and TXT_TYPE_CLI_DATA for CLI command
This commit is contained in:
Scott Powell 2025-01-26 22:56:38 +11:00
parent 40c3dfa20b
commit 6c00653272
5 changed files with 55 additions and 40 deletions

View file

@ -14,6 +14,7 @@
#include <helpers/SimpleMeshTables.h> #include <helpers/SimpleMeshTables.h>
#include <helpers/IdentityStore.h> #include <helpers/IdentityStore.h>
#include <helpers/AdvertDataHelpers.h> #include <helpers/AdvertDataHelpers.h>
#include <helpers/TxtDataHelpers.h>
#include <RTClib.h> #include <RTClib.h>
/* ------------------------------ Config -------------------------------- */ /* ------------------------------ Config -------------------------------- */
@ -271,10 +272,10 @@ protected:
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command
uint32_t sender_timestamp; uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) memcpy(&sender_timestamp, 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
if (flags != 0) { if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported CLI text received: flags=%02x", (uint32_t)flags); MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
} else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks } else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks
client->last_timestamp = sender_timestamp; client->last_timestamp = sender_timestamp;
@ -303,7 +304,7 @@ protected:
timestamp++; timestamp++;
} }
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = 0; temp[4] = (TXT_TYPE_PLAIN << 2); // TODO: change this to TXT_TYPE_CLI_DATA soon
// calc expected ACK reply // calc expected ACK reply
//mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);

View file

@ -14,6 +14,7 @@
#include <helpers/SimpleMeshTables.h> #include <helpers/SimpleMeshTables.h>
#include <helpers/IdentityStore.h> #include <helpers/IdentityStore.h>
#include <helpers/AdvertDataHelpers.h> #include <helpers/AdvertDataHelpers.h>
#include <helpers/TxtDataHelpers.h>
#include <RTClib.h> #include <RTClib.h>
/* ------------------------------ Config -------------------------------- */ /* ------------------------------ Config -------------------------------- */
@ -171,22 +172,13 @@ class MyMesh : public mesh::Mesh {
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS);
} }
void handleCommand(ClientInfo* client, const char* command, char reply[]) {
if (*command == '+') {
addPost(client, &command[1], reply);
} else {
strcpy(reply, "?"); // unknown command
}
}
void pushPostToClient(ClientInfo* client, PostInfo& post) { void pushPostToClient(ClientInfo* client, PostInfo& post) {
int len = 0; int len = 0;
memcpy(&reply_data[len], &post.post_timestamp, 4); len += 4; // this is a PAST timestamp... but should be accepted by client memcpy(&reply_data[len], &post.post_timestamp, 4); len += 4; // this is a PAST timestamp... but should be accepted by client
reply_data[len++] = 0; // plain text reply_data[len++] = (TXT_TYPE_SIGNED_PLAIN << 2); // 'signed' plain text
// encode prefix of post.author.pub_key (in hex) // encode prefix of post.author.pub_key
mesh::Utils::toHex((char *) &reply_data[len], post.author.pub_key, 4); len += 8; // just first 4 bytes (8 hex chars) memcpy(&reply_data[len], post.author.pub_key, 4); len += 4; // just first 4 bytes
reply_data[len++] = ':';
int text_len = strlen(post.text); int text_len = strlen(post.text);
memcpy(&reply_data[len], post.text, text_len); len += text_len; memcpy(&reply_data[len], post.text, text_len); len += text_len;
@ -324,10 +316,10 @@ protected:
if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command
uint32_t sender_timestamp; uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) memcpy(&sender_timestamp, 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
if (flags != 0) { if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command type received: flags=%02x", (uint32_t)flags); MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags);
} else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks } else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks
client->last_timestamp = sender_timestamp; client->last_timestamp = sender_timestamp;
@ -350,12 +342,14 @@ protected:
} }
uint8_t temp[166]; uint8_t temp[166];
if (client->is_admin) { if (flags == TXT_TYPE_CLI_DATA) {
if (!handleAdminCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5])) { if (client->is_admin) {
handleCommand(client, (const char *) &data[5], (char *) &temp[5]); handleAdminCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]);
} else {
strcpy((char *) &temp[5], "auth-err");
} }
} else { } else { // TXT_TYPE_PLAIN
handleCommand(client, (const char *) &data[5], (char *) &temp[5]); addPost(client, (const char *) &data[5], (char *) &temp[5]);
} }
int text_len = strlen((char *) &temp[5]); int text_len = strlen((char *) &temp[5]);
@ -365,7 +359,7 @@ protected:
now++; now++;
} }
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = 0; // attempt and flags temp[4] = (TXT_TYPE_PLAIN << 2); // attempt and flags
// calc expected ACK reply // calc expected ACK reply
//mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
@ -441,7 +435,7 @@ public:
} }
} }
bool 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 while (*command == ' ') command++; // skip leading spaces
if (memcmp(command, "reboot", 6) == 0) { if (memcmp(command, "reboot", 6) == 0) {
@ -471,11 +465,8 @@ public:
} else if (memcmp(command, "ver", 3) == 0) { } else if (memcmp(command, "ver", 3) == 0) {
strcpy(reply, FIRMWARE_VER_TEXT); strcpy(reply, FIRMWARE_VER_TEXT);
} else { } else {
// unknown command strcpy(reply, "?"); // unknown command
reply[0] = 0;
return false;
} }
return true;
} }
void loop() { void loop() {

View file

@ -68,9 +68,17 @@
#error "need to provide a 'board' object" #error "need to provide a 'board' object"
#endif #endif
/* -------------------------------------------------------------------------------------- */ // Believe it or not, this std C function is busted on some platforms!
static uint32_t _atoi(const char* sp) {
uint32_t n = 0;
while (*sp && *sp >= '0' && *sp <= '9') {
n *= 10;
n += (*sp++ - '0');
}
return n;
}
static int curr_contact_idx = 0; /* -------------------------------------------------------------------------------------- */
class MyMesh : public BaseChatMesh, ContactVisitor { class MyMesh : public BaseChatMesh, ContactVisitor {
FILESYSTEM* _fs; FILESYSTEM* _fs;
@ -148,6 +156,16 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
} }
} }
void setClock(uint32_t timestamp) {
uint32_t curr = getRTCClock()->getCurrentTime();
if (timestamp > curr) {
getRTCClock()->setCurrentTime(timestamp);
Serial.println(" (OK - clock set!)");
} else {
Serial.println(" (ERR: clock cannot go backwards)");
}
}
protected: protected:
void onDiscoveredContact(ContactInfo& contact, bool is_new) override { void onDiscoveredContact(ContactInfo& contact, bool is_new) override {
// TODO: if not in favs, prompt to add as fav(?) // TODO: if not in favs, prompt to add as fav(?)
@ -183,13 +201,7 @@ protected:
Serial.printf(" %s\n", text); Serial.printf(" %s\n", text);
if (strcmp(text, "clock sync") == 0) { // special text command if (strcmp(text, "clock sync") == 0) { // special text command
uint32_t curr = getRTCClock()->getCurrentTime(); setClock(sender_timestamp + 1);
if (sender_timestamp > curr) {
getRTCClock()->setCurrentTime(sender_timestamp + 1);
Serial.println(" (OK - clock set!)");
} else {
Serial.println(" (ERR: clock cannot go backwards)");
}
} }
} }
@ -305,6 +317,9 @@ public:
self_name[sizeof(self_name)-1] = 0; self_name[sizeof(self_name)-1] = 0;
IdentityStore store(*_fs, "/identity"); // update IdentityStore IdentityStore store(*_fs, "/identity"); // update IdentityStore
store.save("_main", self_id, self_name); store.save("_main", self_id, self_name);
} else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
uint32_t secs = _atoi(&command[5]);
setClock(secs);
} else if (memcmp(command, "to ", 3) == 0) { // set current recipient } else if (memcmp(command, "to ", 3) == 0) { // set current recipient
curr_recipient = searchContactsByPrefix(&command[3]); curr_recipient = searchContactsByPrefix(&command[3]);
if (curr_recipient) { if (curr_recipient) {
@ -336,6 +351,7 @@ public:
Serial.printf("Hello %s, Commands:\n", self_name); Serial.printf("Hello %s, Commands:\n", self_name);
Serial.println(" name <your name>"); Serial.println(" name <your name>");
Serial.println(" clock"); Serial.println(" clock");
Serial.println(" time <epoch-seconds>");
Serial.println(" list {n}"); Serial.println(" list {n}");
Serial.println(" to <recipient name or prefix>"); Serial.println(" to <recipient name or prefix>");
Serial.println(" to"); Serial.println(" to");

View file

@ -46,7 +46,6 @@
if (nlen > 0) { if (nlen > 0) {
memcpy(_name, &app_data[i], nlen); memcpy(_name, &app_data[i], nlen);
_name[nlen] = 0; // set null terminator _name[nlen] = 0; // set null terminator
MESH_DEBUG_PRINTLN("AdvertDataParser: _flags=%u, _name=%s", (uint32_t)_flags, _name);
} }
_valid = true; _valid = true;
} }

View file

@ -0,0 +1,8 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#define TXT_TYPE_PLAIN 0 // a plain text message
#define TXT_TYPE_CLI_DATA 1 // a CLI command
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender