From 6b2bd09dd89bc3b26e1ce5631ebd8a8b9b366c0f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 13 Dec 2025 20:27:04 -0600 Subject: [PATCH] WIP sfpp --- src/mesh/Channels.h | 4 +- src/mesh/Router.cpp | 2 +- src/mesh/Router.h | 3 + src/mesh/generated/meshtastic/mesh.pb.cpp | 5 + src/mesh/generated/meshtastic/mesh.pb.h | 50 +++ src/mesh/generated/meshtastic/portnums.pb.h | 3 + src/modules/Modules.cpp | 2 + src/modules/Native/StoreForwardPlusPlus.cpp | 330 ++++++++++++++++++ src/modules/Native/StoreForwardPlusPlus.h | 47 +++ .../Native/StoreForwardPlusPlusSat.cpp | 235 +++++++++++++ src/modules/Native/StoreForwardPlusPlusSat.h | 41 +++ variants/native/portduino/platformio.ini | 1 + 12 files changed, 720 insertions(+), 3 deletions(-) create mode 100644 src/modules/Native/StoreForwardPlusPlus.cpp create mode 100644 src/modules/Native/StoreForwardPlusPlus.h create mode 100644 src/modules/Native/StoreForwardPlusPlusSat.cpp create mode 100644 src/modules/Native/StoreForwardPlusPlusSat.h diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index b53f552fa..a3cc7791c 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -96,6 +96,8 @@ class Channels bool setDefaultPresetCryptoForHash(ChannelHash channelHash); + int16_t getHash(ChannelIndex i) { return hashes[i]; } + private: /** Given a channel index, change to use the crypto key specified by that index * @@ -113,8 +115,6 @@ class Channels */ int16_t generateHash(ChannelIndex channelNum); - int16_t getHash(ChannelIndex i) { return hashes[i]; } - /** * Validate a channel, fixing any errors as needed */ diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 54a34fd35..85a172ad5 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -688,7 +688,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) // Store a copy of encrypted packet for MQTT DEBUG_HEAP_BEFORE; - meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p); + p_encrypted = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted); // Take those raw bytes and convert them back into a well structured protobuf we can understand diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 10a3771a7..ec9a08b16 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -91,6 +91,9 @@ class Router : protected concurrency::OSThread, protected PacketHistory before us */ uint32_t rxDupe = 0, txRelayCanceled = 0; + // pointer to the encrypted packet + meshtastic_MeshPacket *p_encrypted; + protected: friend class RoutingModule; diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index 9966e52f8..d8eee1203 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -24,6 +24,9 @@ PB_BIND(meshtastic_Data, meshtastic_Data, 2) PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO) +PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) + + PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) @@ -121,6 +124,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU + + diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 0c48a7891..b1d47ee72 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -478,6 +478,17 @@ typedef enum _meshtastic_Routing_Error { meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38 } meshtastic_Routing_Error; +/* enum message type? */ +typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type { + /* message hash without chain hash means that no, it is not on the chain */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE = 0, + meshtastic_StoreForwardPlusPlus_SFPP_message_type_CHAIN_QUERY = 1, + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_REQUEST = 3, + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE = 4, + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_FIRSTHALF = 5, + meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6 +} meshtastic_StoreForwardPlusPlus_SFPP_message_type; + /* The priority of this message for sending. Higher priorities are sent first (when managing the transmit queue). This field is never sent over the air, it is only used internally inside of a local device node. @@ -782,6 +793,20 @@ typedef struct _meshtastic_KeyVerification { meshtastic_KeyVerification_hash2_t hash2; } meshtastic_KeyVerification; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_message_hash_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_chain_hash_t; +typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_root_hash_t; +typedef PB_BYTES_ARRAY_T(240) meshtastic_StoreForwardPlusPlus_message_t; +/* The actual over-the-mesh message doing store and forward++ */ +typedef struct _meshtastic_StoreForwardPlusPlus { /* */ + meshtastic_StoreForwardPlusPlus_SFPP_message_type sfpp_message_type; + meshtastic_StoreForwardPlusPlus_message_hash_t message_hash; + meshtastic_StoreForwardPlusPlus_chain_hash_t chain_hash; + meshtastic_StoreForwardPlusPlus_root_hash_t root_hash; + /* encapsulated message to share (may be split in half) */ + meshtastic_StoreForwardPlusPlus_message_t message; +} meshtastic_StoreForwardPlusPlus; + /* Waypoint message, used to share arbitrary locations across the mesh */ typedef struct _meshtastic_Waypoint { /* Id of the waypoint */ @@ -1310,6 +1335,10 @@ extern "C" { #define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED #define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1)) +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF +#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1)) + #define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET #define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX #define _meshtastic_MeshPacket_Priority_ARRAYSIZE ((meshtastic_MeshPacket_Priority)(meshtastic_MeshPacket_Priority_MAX+1)) @@ -1338,6 +1367,8 @@ extern "C" { #define meshtastic_Data_portnum_ENUMTYPE meshtastic_PortNum +#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type + #define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority @@ -1380,6 +1411,7 @@ extern "C" { #define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}} #define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} +#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} @@ -1411,6 +1443,7 @@ extern "C" { #define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}} #define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0} #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} +#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} @@ -1489,6 +1522,11 @@ extern "C" { #define meshtastic_KeyVerification_nonce_tag 1 #define meshtastic_KeyVerification_hash1_tag 2 #define meshtastic_KeyVerification_hash2_tag 3 +#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_tag 1 +#define meshtastic_StoreForwardPlusPlus_message_hash_tag 2 +#define meshtastic_StoreForwardPlusPlus_chain_hash_tag 3 +#define meshtastic_StoreForwardPlusPlus_root_hash_tag 4 +#define meshtastic_StoreForwardPlusPlus_message_tag 5 #define meshtastic_Waypoint_id_tag 1 #define meshtastic_Waypoint_latitude_i_tag 2 #define meshtastic_Waypoint_longitude_i_tag 3 @@ -1705,6 +1743,15 @@ X(a, STATIC, SINGULAR, BYTES, hash2, 3) #define meshtastic_KeyVerification_CALLBACK NULL #define meshtastic_KeyVerification_DEFAULT NULL +#define meshtastic_StoreForwardPlusPlus_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, sfpp_message_type, 1) \ +X(a, STATIC, SINGULAR, BYTES, message_hash, 2) \ +X(a, STATIC, SINGULAR, BYTES, chain_hash, 3) \ +X(a, STATIC, SINGULAR, BYTES, root_hash, 4) \ +X(a, STATIC, SINGULAR, BYTES, message, 5) +#define meshtastic_StoreForwardPlusPlus_CALLBACK NULL +#define meshtastic_StoreForwardPlusPlus_DEFAULT NULL + #define meshtastic_Waypoint_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, id, 1) \ X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \ @@ -1980,6 +2027,7 @@ extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg; extern const pb_msgdesc_t meshtastic_Routing_msg; extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; +extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; extern const pb_msgdesc_t meshtastic_MeshPacket_msg; @@ -2013,6 +2061,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_Routing_fields &meshtastic_Routing_msg #define meshtastic_Data_fields &meshtastic_Data_msg #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg +#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg #define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg @@ -2069,6 +2118,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 +#define meshtastic_StoreForwardPlusPlus_size 347 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 115 #define meshtastic_Waypoint_size 165 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 67adc60cc..0156c697b 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -86,6 +86,9 @@ typedef enum _meshtastic_PortNum { /* Paxcounter lib included in the firmware ENCODING: protobuf */ meshtastic_PortNum_PAXCOUNTER_APP = 34, + /* Store and Forward++ module included in the firmware + ENCODING: protobuf */ + meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 63392f7e4..0b84e5ebd 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -61,6 +61,7 @@ #if ARCH_PORTDUINO #include "input/LinuxInputImpl.h" #include "input/SeesawRotary.h" +#include "modules/Native/StoreForwardPlusPlus.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -243,6 +244,7 @@ void setupModules() #endif #if ARCH_PORTDUINO new HostMetricsModule(); + new StoreForwardPlusPlusModule(); #endif #if HAS_TELEMETRY new DeviceTelemetryModule(); diff --git a/src/modules/Native/StoreForwardPlusPlus.cpp b/src/modules/Native/StoreForwardPlusPlus.cpp new file mode 100644 index 000000000..e1b851569 --- /dev/null +++ b/src/modules/Native/StoreForwardPlusPlus.cpp @@ -0,0 +1,330 @@ +// create second module for satellites? + +// The central node will generate a 256 or 128 bit value as its seed. This is the value other nodes subscribe to, and serves as +// the root of the chain + +// + +// Basic design: +// This module watches a channel for text messages. +// each message gets sha256 summed, and then appended to a git-style blockchain. Probably need a counter, too +// then the message, metadata, hash, and git hash information are saved. sqlite? + +// nodes/sub-controllers can subscribe to a database + +// A node can DM the controller, querying if a single message is on the chain, or asking for the last message hash + +// if the message is not on the chain, the node can resend the message + +// if the node lacks messages, it can request them + +// will need the concept of sub-controllers, that subscribe to the central controller, can help push updates + +// catch-up messages only go out when the mesh is low use % + +// Normal firmware will only attempt to sync the chain a few times, then just ask for the latest few messages. A phone app can try +// harder + +// host will periodically advertise its presence + +// at least initially, there can only be one authoritative host + +// first draft is just to save channel 0 in a git-style database + +// message objects get a hash value +// the message chain gets a commit hash +// + +#include "StoreForwardPlusPlus.h" +#include "MeshService.h" +#include "RTC.h" +#include "SHA256.h" +#include "meshUtils.h" + +StoreForwardPlusPlusModule::StoreForwardPlusPlusModule() + : ProtobufModule("StoreForward++", meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP, &meshtastic_StoreForwardPlusPlus_msg), + concurrency::OSThread("StoreForward++") +{ + LOG_WARN("StoreForwardPlusPlusModule init"); + int res = sqlite3_open("test.db", &ppDb); + LOG_WARN("Result1 %u", res); + char *err = nullptr; + res = sqlite3_exec(ppDb, " \ + CREATE TABLE channel_messages( \ + destination INT NOT NULL, \ + sender INT NOT NULL, \ + packet_id INT NOT NULL, \ + want_ack BOOL NOT NULL, \ + channel_hash INT NOT NULL, \ + encrypted_bytes BLOB NOT NULL, \ + message_hash BLOB NOT NULL, \ + rx_time INT NOT NULL, \ + commit_hash BLOB NOT NULL, \ + payload TEXT, \ + PRIMARY KEY (message_hash) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + + // create table DMs + res = sqlite3_exec(ppDb, " \ + CREATE TABLE direct_messages( \ + destination INT NOT NULL, \ + sender INT NOT NULL, \ + packet_id INT NOT NULL, \ + want_ack BOOL NOT NULL, \ + channel_hash INT NOT NULL, \ + commit_hash BLOB NOT NULL, \ + encrypted_bytes BLOB NOT NULL, \ + message_hash BLOB NOT NULL, \ + payload TEXT, \ + rx_time INT NOT NULL, \ + PRIMARY KEY (message_hash) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + + // create table mappings + // create table DMs + res = sqlite3_exec(ppDb, " \ + CREATE TABLE mappings( \ + chain_type INT NOT NULL, \ + identifier INT NOT NULL, \ + root_hash BLOB NOT NULL, \ + PRIMARY KEY (identifier) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + // type + // sha256hash + // channelhash or 64 bit combination + + // store schema version somewhere + + std::string insert_statement = "INSERT INTO channel_messages (destination, sender, packet_id, want_ack, channel_hash, \ + encrypted_bytes, message_hash, rx_time, commit_hash, payload) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + sqlite3_prepare(ppDb, insert_statement.c_str(), insert_statement.length(), &stmt, NULL); + + encryptedOk = true; + + this->setInterval(60 * 1000); +} + +int32_t StoreForwardPlusPlusModule::runOnce() +{ + LOG_WARN("StoreForward++ runONce"); + if (getRTCQuality() < RTCQualityNTP) { + LOG_WARN("StoreForward++ deferred due to time quality %u", getRTCQuality()); + return 60 * 60 * 1000; + } + // look up channel 0 hash + // look up root hash + int hash = channels.getHash(0); + // get tip of chain for this channel + // long term probably keep this in memory + std::string getEntry_string = + "select commit_hash, message_hash from channel_messages where channel_hash=? order by rowid desc LIMIT 1;"; + sqlite3_stmt *getEntry; + int rc = sqlite3_prepare(ppDb, getEntry_string.c_str(), getEntry_string.size(), &getEntry, NULL); + sqlite3_bind_int(getEntry, 1, hash); + sqlite3_step(getEntry); + uint8_t *last_message_chain_hash = (uint8_t *)sqlite3_column_blob(getEntry, 0); + uint8_t *last_message_hash = (uint8_t *)sqlite3_column_blob(getEntry, 0); + + if (last_message_chain_hash == nullptr || last_message_hash == nullptr) { + LOG_WARN("Store and Forward++ database lookup returned null"); + return 60 * 60 * 1000; + } + + meshtastic_StoreForwardPlusPlus storeforward = meshtastic_StoreForwardPlusPlus_init_zero; + storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE; + // set root hash + + // set message hash + storeforward.message_hash.size = 32; + memcpy(storeforward.message_hash.bytes, last_message_hash, 32); + + // set chain hash + storeforward.chain_hash.size = 32; + memcpy(storeforward.chain_hash.bytes, last_message_chain_hash, 32); + + // done with the sqlite3 allocated memory + sqlite3_finalize(getEntry); + // storeforward. + meshtastic_MeshPacket *p = allocDataProtobuf(storeforward); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + p->channel = 0; + LOG_INFO("Send packet to mesh"); + service->sendToMesh(p, RX_SRC_LOCAL, true); + + return 60 * 60 * 1000; +} + +bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) +{ + LOG_WARN("in handleReceivedProtobuf"); +} + +ProcessMessage StoreForwardPlusPlusModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + // To avoid terrible time problems, require NTP or GPS time + if (getRTCQuality() < RTCQualityNTP) { + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + } + + // the sender+destination pair is an interesting unique id (though ordering) (smaller one goes first?) + // so messages with a unique pair become a chain + // These get a table + + // message to broadcast get a chain per channel hash + // second table + // for now, channel messages are limited to decryptable + // limited to text messages + + // create a unique-from-nodenums() class that returns a 64-bit value + + SHA256 message_hash, chain_hash; + uint8_t message_hash_bytes[32] = {0}; + uint8_t chain_hash_bytes[32] = {0}; + + // For the moment, this is strictly LoRa + if (mp.transport_mechanism != meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) { + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + } + + // will eventually host DMs and other undecodable messages + if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + } + // refuse without valid time? + LOG_WARN("in handleReceived"); + if (mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && mp.decoded.dest == NODENUM_BROADCAST) { + // todo drop anything other than to broadcast + std::string getEntry_string = + "select commit_hash from channel_messages where channel_hash=? order by rowid desc LIMIT 1;"; + sqlite3_stmt *getEntry; + int rc = sqlite3_prepare(ppDb, getEntry_string.c_str(), getEntry_string.size(), &getEntry, NULL); + sqlite3_bind_int(getEntry, 1, router->p_encrypted->channel); + sqlite3_step(getEntry); + + // this is allocated by sqlite3 and will be deleted when finalize is called + uint8_t *last_message_hash = (uint8_t *)sqlite3_column_blob(getEntry, 0); + if (last_message_hash) { + printBytes("last message: 0x", last_message_hash, 32); + } else { + // generate root hash and populate lookup table + } + + // do not include rxtime in the message hash. We want these to match when more then one node receives and compares notes. + // feel free to include it in the commit hash + + message_hash.reset(); + message_hash.update(router->p_encrypted->encrypted.bytes, router->p_encrypted->encrypted.size); + message_hash.update(&mp.to, sizeof(mp.to)); + message_hash.update(&mp.from, sizeof(mp.from)); + message_hash.update(&mp.id, sizeof(mp.id)); + message_hash.finalize(message_hash_bytes, 32); + + chain_hash.reset(); + if (last_message_hash) { + chain_hash.update(last_message_hash, 32); + } + chain_hash.update(message_hash_bytes, 32); + // message_hash.update(&mp.rx_time, sizeof(mp.rx_time)); + chain_hash.finalize(chain_hash_bytes, 32); + + sqlite3_finalize(getEntry); + + // select HEX(commit_hash),HEX(channel_hash), payload, destination from channel_messages order by rowid desc; + + // push a message into the local chain DB + // destination + sqlite3_bind_int(stmt, 1, mp.to); + // sender + sqlite3_bind_int(stmt, 2, mp.from); + // packet_id + sqlite3_bind_int(stmt, 3, mp.id); + // want_ack + sqlite3_bind_int(stmt, 4, mp.want_ack); + // channel_hash + sqlite3_bind_int(stmt, 5, router->p_encrypted->channel); + // encrypted_bytes + sqlite3_bind_blob(stmt, 6, router->p_encrypted->encrypted.bytes, router->p_encrypted->encrypted.size, NULL); + + // message_hash + sqlite3_bind_blob(stmt, 7, message_hash_bytes, 32, NULL); + // rx_time + sqlite3_bind_int(stmt, 8, mp.rx_time); + + // commit_hash + sqlite3_bind_blob(stmt, 9, chain_hash_bytes, 32, NULL); + // payload + sqlite3_bind_text(stmt, 10, (char *)mp.decoded.payload.bytes, mp.decoded.payload.size, NULL); + + sqlite3_step(stmt); + sqlite3_reset(stmt); + + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + + // one of the command messages + } else if (mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP) { + // when we get an update this way, if the message isn't on the chain, this node hasn't seen it, and can rebroadcast. + } +} + +// announce latest hash +// chain_end_announce + +// check if hash is known +// hash_query + +// request next message +// link_request + +// send encapsulated message +// link_provide_whole +// link_provide_half1 +// link_provide_half2 + +// onboard request message? + +// get x from top? + +// messages +// Given this chain root, do you have a packet that matches this message hash? +// responds with chain hash etc + +// given this chain root, what is your last chain and message hash? +// given this chain root, what is your next message after this chain hash? (do we have an overhead problem here?) (blegh, +// fragmentation) (but also, trunking) + +// broadcast on this chain root, here is my last chain hash + +// consider third-order nodes + +// I can't talk directly to strata, I can talk to a satellite. Inform sat of a message. Sat stores it as if had seen it locally, +// and pushes it to central + +// message Eventually works out through chain + +// sat can capture time of receipt + +// terms: +// CANON +// stratum +// chain +// links on the chain \ No newline at end of file diff --git a/src/modules/Native/StoreForwardPlusPlus.h b/src/modules/Native/StoreForwardPlusPlus.h new file mode 100644 index 000000000..1e4838467 --- /dev/null +++ b/src/modules/Native/StoreForwardPlusPlus.h @@ -0,0 +1,47 @@ +#pragma once +#include "ProtobufModule.h" +#include "Router.h" +#include "SinglePortModule.h" +#include "sqlite3.h" + +/** + * A simple example module that just replies with "Message received" to any message it receives. + */ +class StoreForwardPlusPlusModule : public ProtobufModule, private concurrency::OSThread +{ + public: + /** Constructor + * name is for debugging output + */ + StoreForwardPlusPlusModule(); + + /* + -Override the wantPacket method. + */ + virtual bool wantPacket(const meshtastic_MeshPacket *p) override + { + // if encrypted but not too FFFF + // want + switch (p->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + case 35: + return true; + default: + return false; + } + } + + protected: + /** Called to handle a particular incoming message + @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for + it + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override; + + virtual int32_t runOnce() override; + + private: + sqlite3 *ppDb; + sqlite3_stmt *stmt; +}; diff --git a/src/modules/Native/StoreForwardPlusPlusSat.cpp b/src/modules/Native/StoreForwardPlusPlusSat.cpp new file mode 100644 index 000000000..42f03a102 --- /dev/null +++ b/src/modules/Native/StoreForwardPlusPlusSat.cpp @@ -0,0 +1,235 @@ +// create second module for satellites? + +// The central node will generate a 256 or 128 bit value as its seed. This is the value other nodes subscribe to, and serves as +// the root of the chain + +// + +// Basic design: +// This module watches a channel for text messages. +// each message gets sha256 summed, and then appended to a git-style blockchain. Probably need a counter, too +// then the message, metadata, hash, and git hash information are saved. sqlite? + +// nodes/sub-controllers can subscribe to a database + +// A node can DM the controller, querying if a single message is on the chain, or asking for the last message hash + +// if the message is not on the chain, the node can resend the message + +// if the node lacks messages, it can request them + +// will need the concept of sub-controllers, that subscribe to the central controller, can help push updates + +// catch-up messages only go out when the mesh is low use % + +// Normal firmware will only attempt to sync the chain a few times, then just ask for the latest few messages. A phone app can try +// harder + +// host will periodically advertise its presence + +// at least initially, there can only be one authoritative host + +// first draft is just to save channel 0 in a git-style database + +// message objects get a hash value +// the message chain gets a commit hash +// + +#include "StoreForwardPlusPlusSat.h" +#include "SHA256.h" +#include "meshUtils.h" + +StoreForwardPlusPlusSatModule::StoreForwardPlusPlusSatModule() : SinglePortModule("StoreForwardPlusPlus", (_meshtastic_PortNum)35) +{ + LOG_WARN("StoreForwardPlusPlusSatModule init"); + int res = sqlite3_open("test.db", &ppDb); + LOG_WARN("Result1 %u", res); + char *err = nullptr; + res = sqlite3_exec(ppDb, " \ + CREATE TABLE channel_messages( \ + destination INT NOT NULL, \ + sender INT NOT NULL, \ + packet_id INT NOT NULL, \ + want_ack BOOL NOT NULL, \ + channel_hash INT NOT NULL, \ + encrypted_bytes BLOB NOT NULL, \ + message_hash BLOB NOT NULL, \ + rx_time INT NOT NULL, \ + commit_hash BLOB NOT NULL, \ + payload TEXT, \ + PRIMARY KEY (message_hash) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + + // create table DMs + res = sqlite3_exec(ppDb, " \ + CREATE TABLE direct_messages( \ + destination INT NOT NULL, \ + sender INT NOT NULL, \ + packet_id INT NOT NULL, \ + want_ack BOOL NOT NULL, \ + channel_hash INT NOT NULL, \ + commit_hash BLOB NOT NULL, \ + encrypted_bytes BLOB NOT NULL, \ + message_hash BLOB NOT NULL, \ + payload TEXT, \ + rx_time INT NOT NULL, \ + PRIMARY KEY (message_hash) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + + // create table mappings + // create table DMs + res = sqlite3_exec(ppDb, " \ + CREATE TABLE mappings( \ + chain_type INT NOT NULL, \ + identifier INT NOT NULL, \ + root_hash BLOB NOT NULL, \ + PRIMARY KEY (identifier) \ + );", + NULL, NULL, &err); + LOG_WARN("Result2 %u", res); + if (err != nullptr) + ; + LOG_ERROR("%s", err); + sqlite3_free(err); + // type + // sha256hash + // channelhash or 64 bit combination + // The sat version needs a scratch database of messages that have not been checked in + + // store schema version somewhere + + std::string insert_statement = "INSERT INTO channel_messages (destination, sender, packet_id, want_ack, channel_hash, \ + encrypted_bytes, message_hash, rx_time, commit_hash, payload) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + sqlite3_prepare(ppDb, insert_statement.c_str(), insert_statement.length(), &stmt, NULL); + + encryptedOk = true; +} + +ProcessMessage StoreForwardPlusPlusSatModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + + // the sender+destination pair is an interesting unique id (though ordering) (smaller one goes first?) + // so messages with a unique pair become a chain + // These get a table + + // message to broadcast get a chain per channel hash + // second table + // for now, channel messages are limited to decryptable + // limited to text messages + + // create a unique-from-nodenums() class that returns a 64-bit value + + SHA256 message_hash, chain_hash; + uint8_t message_hash_bytes[32] = {0}; + uint8_t chain_hash_bytes[32] = {0}; + + // For the moment, this is strictly LoRa + if (mp.transport_mechanism != meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) { + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + } + + // will eventually host DMs and other undecodable messages + if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) { + return ProcessMessage::CONTINUE; // Let others look at this message also if they want + } + // refuse without valid time? + LOG_WARN("in handleReceived"); + std::string getEntry_string = "select commit_hash from channel_messages where channel_hash=? order by rowid desc LIMIT 1;"; + sqlite3_stmt *getEntry; + int rc = sqlite3_prepare(ppDb, getEntry_string.c_str(), getEntry_string.size(), &getEntry, NULL); + sqlite3_bind_int(getEntry, 1, router->p_encrypted->channel); + sqlite3_step(getEntry); + + // this is allocated by sqlite3 and will be deleted when finalize is called + uint8_t *last_message_hash = (uint8_t *)sqlite3_column_blob(getEntry, 0); + if (last_message_hash) { + printBytes("last message: 0x", last_message_hash, 32); + } else { + // generate root hash and populate lookup table + } + + // do not include rxtime in the message hash. We want these to match when more then one node receives and compares notes. + // feel free to include it in the commit hash + + message_hash.reset(); + message_hash.update(router->p_encrypted->encrypted.bytes, router->p_encrypted->encrypted.size); + message_hash.update(&mp.to, sizeof(mp.to)); + message_hash.update(&mp.from, sizeof(mp.from)); + message_hash.update(&mp.id, sizeof(mp.id)); + message_hash.finalize(message_hash_bytes, 32); + + chain_hash.reset(); + if (last_message_hash) { + chain_hash.update(last_message_hash, 32); + } + chain_hash.update(message_hash_bytes, 32); + // message_hash.update(&mp.rx_time, sizeof(mp.rx_time)); + chain_hash.finalize(chain_hash_bytes, 32); + + sqlite3_finalize(getEntry); + + // select HEX(commit_hash),HEX(channel_hash), payload, destination from channel_messages order by rowid desc; + + // push a message into the local chain DB + // destination + sqlite3_bind_int(stmt, 1, mp.to); + // sender + sqlite3_bind_int(stmt, 2, mp.from); + // packet_id + sqlite3_bind_int(stmt, 3, mp.id); + // want_ack + sqlite3_bind_int(stmt, 4, mp.want_ack); + // channel_hash + sqlite3_bind_int(stmt, 5, router->p_encrypted->channel); + // encrypted_bytes + sqlite3_bind_blob(stmt, 6, router->p_encrypted->encrypted.bytes, router->p_encrypted->encrypted.size, NULL); + + // message_hash + sqlite3_bind_blob(stmt, 7, message_hash_bytes, 32, NULL); + // rx_time + sqlite3_bind_int(stmt, 8, mp.rx_time); + + // commit_hash + sqlite3_bind_blob(stmt, 9, chain_hash_bytes, 32, NULL); + // payload + sqlite3_bind_text(stmt, 10, (char *)mp.decoded.payload.bytes, mp.decoded.payload.size, NULL); + + sqlite3_step(stmt); + sqlite3_reset(stmt); + + return ProcessMessage::CONTINUE; // Let others look at this message also if they want +} + +// when we get an update this way, if the message isn't on the chain or in the scratch table, this node hasn't seen it, and can +// rebroadcast. + +// messages +// Given this chain root, do you have a packet that matches this message hash? +// responds with chain hash etc + +// given this chain root, what is your last chain and message hash? +// given this chain root, what is your next message after this chain hash? (do we have an overhead problem here?) (blegh, +// fragmentation) (but also, trunking) + +// broadcast on this chain root, here is my last chain hash + +// consider third-order nodes + +// I can't talk directly to strata, I can talk to a satellite. Inform sat of a message. Sat stores it as if had seen it locally, +// and pushes it to central + +// message Eventually works out through chain + +// sat can capture time of receipt \ No newline at end of file diff --git a/src/modules/Native/StoreForwardPlusPlusSat.h b/src/modules/Native/StoreForwardPlusPlusSat.h new file mode 100644 index 000000000..d66dc5e07 --- /dev/null +++ b/src/modules/Native/StoreForwardPlusPlusSat.h @@ -0,0 +1,41 @@ +#pragma once +#include "Router.h" +#include "SinglePortModule.h" +#include "sqlite3.h" + +/** + * A simple example module that just replies with "Message received" to any message it receives. + */ +class StoreForwardPlusPlusSatModule : public SinglePortModule // should probably derive this from the main class +{ + public: + /** Constructor + * name is for debugging output + */ + StoreForwardPlusPlusSatModule(); + + /* + -Override the wantPacket method. + */ + virtual bool wantPacket(const meshtastic_MeshPacket *p) override + { + switch (p->decoded.portnum) { + case meshtastic_PortNum_TEXT_MESSAGE_APP: + case 35: + return true; + default: + return false; + } + } + + protected: + /** Called to handle a particular incoming message + @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for + it + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: + sqlite3 *ppDb; + sqlite3_stmt *stmt; +}; diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index 9cedfcc55..347813e2d 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -45,6 +45,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio !pkg-config --libs openssl --silence-errors || : !pkg-config --cflags --libs sdl2 --silence-errors || : !pkg-config --cflags --libs libbsd-overlay --silence-errors || : + !pkg-config --cflags --libs sqlite3 --silence-errors || : build_src_filter = ${native_base.build_src_filter}