Initial commit

This commit is contained in:
Scott Powell
2025-01-13 14:07:48 +11:00
commit 6c7efdd0f6
59 changed files with 8604 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

30
README.md Normal file
View File

@@ -0,0 +1,30 @@
## About RippleCore
RippleCore is a portable C++ library which provides classes for adding multi-hop packet routing to embedded projects typically employing packet radios like LoRa.
It is commercially known as the 'R2' protocol, and is the successor LoRa routing engine to the ['Ripple' project](https://buymeacoffee.com/ripplebiz).
At present it is mostly aimed at Arduino projects, using the [PlatformIO](https://docs.platformio.org) tools, but could potentially be integrated into other environments.
## Getting Started
You'll need to install PlatformIO, and an IDE in which it runs, like VSCode. Once installed, you should just be able to open this folder as a project, and it will read the platformio.ini file, and bring in all the required dependencies.
There are a number of build environments defined in the platformio.ini file, all targeting the Heltec V3 LoRa boards. For example, **[env:Heltec_v3_chat_alice]** is the target for building/running the 'secure chat' sample app as the user 'Alice'. There is a similar env, configured to run the secure chat as the user 'Bob'.
Try running these two examples first, flashing to two separate Heltec V3's, and use the Serial Monitor to interact with the *very basic* command-line interface.
## Other Example Apps
There is also a pair of examples, **'ping_client'** and **'ping_server'** which has some very basic constructs for setting up a node in a 'server'-like role, and another as client.
There is also a **'simple_repeater'** example, which should function as a basic repeater to ALL of the various samples, like the chat ones. It also defines a few examples of some 'remote admin', like setting the clock. The **'test_admin'** example is an example of an app that remotely monitors and sends commands to the 'simple_repeater' nodes.
## To-Do's
Will hopefully figure out how to make this a registered PlatformIO library, so it can just be added in **lib_deps** in your own project.
## Acknowledgments
This project is insipired from/by the [Reticulum project, by Mark Qvist](https://reticulum.network/start.html). I recommend having a look at RNS (Reticulum Network Stack) and study, almost as a pre-requisite to this project.

View File

@@ -0,0 +1,149 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <SPIFFS.h>
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/StaticPoolPacketManager.h>
#include <helpers/SimpleSeenTable.h>
/* ------------------------------ Config -------------------------------- */
#ifdef HELTEC_LORA_V3
#include <helpers/HeltecV3Board.h>
static HeltecV3Board board;
#else
#error "need to provide a 'board' object"
#endif
/* ------------------------------ Code -------------------------------- */
class MyMesh : public mesh::Mesh {
SimpleSeenTable* _table;
uint32_t last_advert_timestamp = 0;
mesh::Identity server_id;
uint8_t server_secret[PUB_KEY_SIZE];
int server_path_len = -1;
uint8_t server_path[MAX_PATH_SIZE];
bool got_adv = false;
protected:
int searchPeersByHash(const uint8_t* hash) override {
if (got_adv && server_id.isHashMatch(hash)) {
return 1;
}
return 0; // not found
}
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
if (memcmp(app_data, "PING", 4) == 0) {
Serial.println("Received advertisement from a PING server");
// check for replay attacks
if (timestamp > last_advert_timestamp) {
last_advert_timestamp = timestamp;
server_id = id;
self_id.calcSharedSecret(server_secret, id); // calc ECDH shared secret
got_adv = true;
}
}
}
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_RESPONSE) {
if (_table->hasSeenPacket(packet)) return;
Serial.println("Received PING Reply!");
if (packet->isRouteFlood()) {
// let server know path TO here, so they can use sendDirect() for future ping responses
mesh::Packet* path = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
if (path) sendFlood(path);
}
}
}
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
if (_table->hasSeenPacket(packet)) return;
// must be from server_id
Serial.printf("PATH to server, path_len=%d\n", (uint32_t) path_len);
memcpy(server_path, path, server_path_len = path_len); // store a copy of path, for sendDirect()
if (packet->isRouteFlood()) {
// send a reciprocal return path to sender, but send DIRECTLY!
mesh::Packet* rpath = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
if (rpath) sendDirect(rpath, path, path_len);
}
if (extra_type == PAYLOAD_TYPE_RESPONSE) {
Serial.println("Received PING Reply!");
}
}
public:
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleSeenTable& table)
: mesh::Mesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16)), _table(&table)
{
}
void sendPingPacket() {
if (!got_adv) return; // have not received Advert yet
uint32_t now = getRTCClock()->getCurrentTime(); // important, need timestamp in packet, so that packet_hash will be unique
mesh::Packet* ping = createDatagram(PAYLOAD_TYPE_ANON_REQ, server_id, server_secret, (uint8_t *) &now, sizeof(now));
if (ping) {
if (server_path_len < 0) {
sendFlood(ping);
} else {
sendDirect(ping, server_path, server_path_len);
}
}
}
};
SPIClass spi;
StdRNG fast_rng;
SimpleSeenTable table;
SX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
MyMesh the_mesh(*new RadioLibWrapper(radio, board), fast_rng, *new VolatileRTCClock(), table);
unsigned long nextPing;
void halt() {
while (1) ;
}
void setup() {
Serial.begin(115200);
board.begin();
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
halt();
}
fast_rng.begin(radio.random(0x7FFFFFFF));
the_mesh.begin();
RadioNoiseListener true_rng(radio);
the_mesh.self_id = mesh::LocalIdentity(&true_rng); // create new random identity
nextPing = 0;
}
void loop() {
if (the_mesh.millisHasNowPassed(nextPing)) {
the_mesh.sendPingPacket();
nextPing = the_mesh.futureMillis(10000); // attempt ping every 10 seconds
}
the_mesh.loop();
}

View File

@@ -0,0 +1,174 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <SPIFFS.h>
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/StaticPoolPacketManager.h>
/* ------------------------------ Config -------------------------------- */
#ifdef HELTEC_LORA_V3
#include <helpers/HeltecV3Board.h>
static HeltecV3Board board;
#else
#error "need to provide a 'board' object"
#endif
/* ------------------------------ Code -------------------------------- */
struct ClientInfo {
mesh::Identity id;
uint32_t last_timestamp;
uint8_t secret[PUB_KEY_SIZE];
int out_path_len;
uint8_t out_path[MAX_PATH_SIZE];
};
#define MAX_CLIENTS 4
class MyMesh : public mesh::Mesh {
int num_clients;
ClientInfo known_clients[MAX_CLIENTS];
ClientInfo* putClient(const mesh::Identity& id) {
for (int i = 0; i < num_clients; i++) {
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
}
if (num_clients < MAX_CLIENTS) {
auto newClient = &known_clients[num_clients++];
newClient->id = id;
newClient->out_path_len = -1; // initially out_path is unknown
newClient->last_timestamp = 0;
self_id.calcSharedSecret(newClient->secret, id); // calc ECDH shared secret
return newClient;
}
return NULL; // table is full
}
protected:
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_ANON_REQ) { // received a PING!
uint32_t timestamp;
memcpy(&timestamp, data, 4);
auto client = putClient(sender); // add to known clients (if not already known)
if (client == NULL || timestamp <= client->last_timestamp) {
return; // FATAL: client table is full -OR- replay attack -OR- have seen this packet before
}
client->last_timestamp = timestamp;
uint32_t now = getRTCClock()->getCurrentTime(); // response packets always prefixed with timestamp
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the Ping response
mesh::Packet* path = createPathReturn(sender, client->secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, (uint8_t *) &now, sizeof(now));
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, (uint8_t *) &now, sizeof(now));
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len);
} else {
sendFlood(reply);
}
}
}
}
}
int matching_peer_indexes[MAX_CLIENTS];
int searchPeersByHash(const uint8_t* hash) override {
int n = 0;
for (int i = 0; i < num_clients; i++) {
if (known_clients[i].id.isHashMatch(hash)) {
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
}
}
return n;
}
// not needed for this example, but for sake of 'completeness' of Mesh impl
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override {
if (peer_idx >= 0 && peer_idx < MAX_CLIENTS) {
// lookup pre-calculated shared_secret
int i = matching_peer_indexes[peer_idx];
memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE);
} else {
MESH_DEBUG_PRINTLN("Invalid peer_idx: %d", peer_idx);
}
}
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
if (sender_idx >= 0 && sender_idx < MAX_CLIENTS) {
Serial.printf("PATH to client, path_len=%d\n", (uint32_t) path_len);
// TODO: prevent replay attacks
int i = matching_peer_indexes[sender_idx];
if (i >= 0 && i < num_clients) {
auto client = &known_clients[i]; // get from our known_clients table (sender SHOULD already be known in this context)
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
}
} else {
MESH_DEBUG_PRINTLN("Invalid sender_idx: %d", sender_idx);
}
// NOTE: no reciprocal path send!!
}
public:
MyMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(16))
{
num_clients = 0;
}
};
SPIClass spi;
StdRNG fast_rng;
SX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
MyMesh the_mesh(*new RadioLibWrapper(radio, board), *new ArduinoMillis(), fast_rng, *new VolatileRTCClock());
unsigned long nextAnnounce;
void halt() {
while (1) ;
}
void setup() {
Serial.begin(115200);
board.begin();
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
halt();
}
fast_rng.begin(radio.random(0x7FFFFFFF));
the_mesh.begin();
RadioNoiseListener true_rng(radio);
the_mesh.self_id = mesh::LocalIdentity(&true_rng); // create new random identity
nextAnnounce = 0;
}
void loop() {
if (the_mesh.millisHasNowPassed(nextAnnounce)) {
mesh::Packet* pkt = the_mesh.createAdvert(the_mesh.self_id, (const uint8_t *)"PING", 4);
if (pkt) the_mesh.sendFlood(pkt);
nextAnnounce = the_mesh.futureMillis(30000); // announce every 30 seconds (test only, don't do in production!)
}
the_mesh.loop();
// TODO: periodically check for OLD entries in known_clients[], and evict
}

View File

@@ -0,0 +1,338 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <SPIFFS.h>
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/StaticPoolPacketManager.h>
#include <helpers/SimpleMeshTables.h>
#include <helpers/IdentityStore.h>
/* ------------------------------ Config -------------------------------- */
#define ANNOUNCE_DATA "repeater:v1"
#define ADMIN_PASSWORD "h^(kl@#)"
#if defined(HELTEC_LORA_V3)
#include <helpers/HeltecV3Board.h>
static HeltecV3Board board;
#else
#error "need to provide a 'board' object"
#endif
/* ------------------------------ Code -------------------------------- */
#define CMD_GET_STATS 0x01
#define CMD_SET_CLOCK 0x02
#define CMD_SEND_ANNOUNCE 0x03
#define CMD_SET_CONFIG 0x04
struct RepeaterStats {
uint16_t batt_milli_volts;
uint16_t curr_tx_queue_len;
uint16_t curr_free_queue_len;
int16_t last_rssi;
uint32_t n_packets_recv;
uint32_t n_packets_sent;
uint32_t total_air_time_secs;
uint32_t total_up_time_secs;
};
struct ClientInfo {
mesh::Identity id;
uint32_t last_timestamp;
uint8_t secret[PUB_KEY_SIZE];
int out_path_len;
uint8_t out_path[MAX_PATH_SIZE];
};
#define MAX_CLIENTS 4
class MyMesh : public mesh::Mesh {
RadioLibWrapper* my_radio;
mesh::MeshTables* _tables;
float airtime_factor;
uint8_t reply_data[MAX_PACKET_PAYLOAD];
int num_clients;
ClientInfo known_clients[MAX_CLIENTS];
ClientInfo* putClient(const mesh::Identity& id) {
for (int i = 0; i < num_clients; i++) {
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
}
if (num_clients < MAX_CLIENTS) {
auto newClient = &known_clients[num_clients++];
newClient->id = id;
newClient->out_path_len = -1; // initially out_path is unknown
newClient->last_timestamp = 0;
self_id.calcSharedSecret(newClient->secret, id); // calc ECDH shared secret
return newClient;
}
return NULL; // table is full
}
int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) {
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
switch (payload[0]) {
case CMD_GET_STATS: {
uint32_t max_age_secs;
if (payload_len >= 5) {
memcpy(&max_age_secs, &payload[1], 4); // first param in request pkt
} else {
max_age_secs = 12*60*60; // default, 12 hours
}
RepeaterStats stats;
stats.batt_milli_volts = board.getBattMilliVolts();
stats.curr_tx_queue_len = _mgr->getOutboundCount();
stats.curr_free_queue_len = _mgr->getFreeCount();
stats.last_rssi = (int16_t) my_radio->getLastRSSI();
stats.n_packets_recv = my_radio->getPacketsRecv();
stats.n_packets_sent = my_radio->getPacketsSent();
stats.total_air_time_secs = getTotalAirTime() / 1000;
stats.total_up_time_secs = _ms->getMillis() / 1000;
memcpy(&reply_data[4], &stats, sizeof(stats));
return 4 + sizeof(stats); // reply_len
}
case CMD_SET_CLOCK: {
if (payload_len >= 5) {
uint32_t curr_epoch_secs;
memcpy(&curr_epoch_secs, &payload[1], 4); // first param is current UNIX time
if (curr_epoch_secs > now) { // time can only go forward!!
getRTCClock()->setCurrentTime(curr_epoch_secs);
memcpy(&reply_data[4], "OK", 2);
} else {
memcpy(&reply_data[4], "ER", 2);
}
return 4 + 2; // reply_len
}
return 0; // invalid request
}
case CMD_SEND_ANNOUNCE: {
// broadcast another self Advertisement
auto adv = createAdvert(self_id, (const uint8_t *)ANNOUNCE_DATA, strlen(ANNOUNCE_DATA));
if (adv) sendFlood(adv, 1500); // send after slight delay
memcpy(&reply_data[4], "OK", 2);
return 4 + 2; // reply_len
}
case CMD_SET_CONFIG: {
if (payload_len >= 4 && payload_len < 32 && memcmp(&payload[1], "AF", 2) == 0) {
payload[payload_len] = 0; // make it a C string
airtime_factor = atof((char *) &payload[3]);
memcpy(&reply_data[4], "OK", 2);
return 4 + 2; // reply_len
}
return 0; // unknown config var
}
}
// unknown command
return 0; // reply_len
}
protected:
float getAirtimeBudgetFactor() const override {
return airtime_factor;
}
bool allowPacketForward(mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
if (_tables->hasForwarded(hash)) return false; // has already been forwarded
_tables->setHasForwarded(hash); // mark packet as forwarded
return true; // Yes, allow packet to be forwarded
}
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
uint32_t timestamp;
memcpy(&timestamp, data, 4);
if (memcmp(&data[4], ADMIN_PASSWORD, 8) == 0) { // check for valid password
auto client = putClient(sender); // add to known clients (if not already known)
if (client == NULL || timestamp <= client->last_timestamp) {
return; // FATAL: client table is full -OR- replay attack -OR- have seen this packet before
}
client->last_timestamp = timestamp;
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
memcpy(&reply_data[4], "OK", 2);
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(sender, client->secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, 4 + 2);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 4 + 2);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len);
} else {
sendFlood(reply);
}
}
}
}
}
}
int matching_peer_indexes[MAX_CLIENTS];
int searchPeersByHash(const uint8_t* hash) override {
int n = 0;
for (int i = 0; i < num_clients; i++) {
if (known_clients[i].id.isHashMatch(hash)) {
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
}
}
return n;
}
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < num_clients) {
// lookup pre-calculated shared_secret
memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE);
} else {
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
}
}
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!)
int i = matching_peer_indexes[sender_idx];
if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context)
auto client = &known_clients[i];
uint32_t timestamp;
memcpy(&timestamp, data, 4);
if (timestamp > client->last_timestamp) { // prevent replay attacks AND receiving via multiple paths
int reply_len = handleRequest(client, &data[4], len - 4);
if (reply_len == 0) return; // invalid command
client->last_timestamp = timestamp;
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(client->id, client->secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, client->secret, reply_data, reply_len);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len);
} else {
sendFlood(reply);
}
}
}
}
} else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
}
}
}
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
// TODO: prevent replay attacks
int i = matching_peer_indexes[sender_idx];
if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context)
Serial.printf("PATH to client, path_len=%d\n", (uint32_t) path_len);
auto client = &known_clients[i];
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
} else {
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
}
// NOTE: no reciprocal path send!!
}
public:
MyMesh(RadioLibWrapper& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32)), _tables(&tables)
{
my_radio = &radio;
airtime_factor = 5.0; // 1/6th
num_clients = 0;
}
void sendSelfAdvertisement() {
mesh::Packet* pkt = createAdvert(self_id, (const uint8_t *)ANNOUNCE_DATA, strlen(ANNOUNCE_DATA));
if (pkt) {
sendFlood(pkt);
} else {
MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!");
}
}
};
#if defined(P_LORA_SCLK)
SPIClass spi;
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
StdRNG fast_rng;
SimpleMeshTables tables;
MyMesh the_mesh(*new CustomSX1262Wrapper(radio, board), *new ArduinoMillis(), fast_rng, *new VolatileRTCClock(), tables);
void halt() {
while (1) ;
}
void setup() {
Serial.begin(115200);
delay(5000);
board.begin();
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
#else
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
#endif
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
halt();
}
SPIFFS.begin(true);
IdentityStore store(SPIFFS, "/identity");
if (!store.load("_main", the_mesh.self_id)) {
the_mesh.self_id = mesh::LocalIdentity(the_mesh.getRNG()); // create new random identity
store.save("_main", the_mesh.self_id);
}
Serial.print("Repeater ID: ");
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
the_mesh.begin();
// send out initial Advertisement to the mesh
the_mesh.sendSelfAdvertisement();
}
void loop() {
the_mesh.loop();
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
}

View File

@@ -0,0 +1,334 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <SPIFFS.h>
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/StaticPoolPacketManager.h>
#include <helpers/SimpleSeenTable.h>
/* ---------------------------------- CONFIGURATION ------------------------------------- */
//#define RUN_AS_ALICE true
#if RUN_AS_ALICE
const char* alice_private = "B8830658388B2DDF22C3A508F4386975970CDE1E2A2A495C8F3B5727957A97629255A1392F8BA4C26A023A0DAB78BFC64D261C8E51507496DD39AFE3707E7B42";
#else
const char *bob_private = "30BAA23CCB825D8020A59C936D0AB7773B07356020360FC77192813640BAD375E43BBF9A9A7537E4B9614610F1F2EF874AAB390BA9B0C2F01006B01FDDFEFF0C";
#endif
const char *alice_public = "106A5136EC0DD797650AD204C065CF9B66095F6ED772B0822187785D65E11B1F";
const char *bob_public = "020BCEDAC07D709BD8507EC316EB5A7FF2F0939AF5057353DCE7E4436A1B9681";
#ifdef HELTEC_LORA_V3
#include <helpers/HeltecV3Board.h>
static HeltecV3Board board;
#else
#error "need to provide a 'board' object"
#endif
#define FLOOD_SEND_TIMEOUT_MILLIS 4000
#define DIRECT_SEND_TIMEOUT_MILLIS 2000
/* -------------------------------------------------------------------------------------- */
static unsigned long txt_send_timeout;
#define MAX_CONTACTS 1
#define MAX_SEARCH_RESULTS 1
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1)
struct ContactInfo {
mesh::Identity id;
const char* name;
int out_path_len;
uint8_t out_path[MAX_PATH_SIZE];
uint32_t last_advert_timestamp;
uint8_t shared_secret[PUB_KEY_SIZE];
};
class MyMesh : public mesh::Mesh {
public:
SimpleSeenTable* _table;
mesh::LocalIdentity self_id;
ContactInfo contacts[MAX_CONTACTS];
int num_contacts;
void addContact(const char* name, const mesh::Identity& id) {
if (num_contacts < MAX_CONTACTS) {
contacts[num_contacts].id = id;
contacts[num_contacts].name = name;
contacts[num_contacts].last_advert_timestamp = 0;
contacts[num_contacts].out_path_len = -1;
// only need to calculate the shared_secret once, for better performance
self_id.calcSharedSecret(contacts[num_contacts].shared_secret, id);
num_contacts++;
}
}
protected:
int matching_peer_indexes[MAX_SEARCH_RESULTS];
int searchPeersByHash(const uint8_t* hash) override {
int n = 0;
for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) {
if (contacts[i].id.isHashMatch(hash)) {
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
}
}
return n;
}
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
Serial.print("Valid Advertisement -> ");
mesh::Utils::printHex(Serial, id.pub_key, PUB_KEY_SIZE);
Serial.println();
for (int i = 0; i < num_contacts; i++) {
ContactInfo& from = contacts[i];
// check for replay attacks
if (id.matches(from.id) && timestamp > from.last_advert_timestamp) { // is from one of our contacts
from.last_advert_timestamp = timestamp;
Serial.printf(" From contact: %s\n", from.name);
}
}
}
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override {
int i = matching_peer_indexes[peer_idx];
if (i >= 0 && i < num_contacts) {
// lookup pre-calculated shared_secret
memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE);
} else {
MESH_DEBUG_PRINTLN("getPeerSHharedSecret: Invalid peer idx: %d", i);
}
}
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_TXT_MSG) {
if (_table->hasSeenPacket(packet)) return;
int i = matching_peer_indexes[sender_idx];
if (i < 0 && i >= num_contacts) {
MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i);
return;
}
ContactInfo& from = contacts[i];
uint32_t timestamp;
memcpy(&timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
// 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
Serial.print("MSG -> from ");
Serial.print(from.name);
Serial.print(": ");
Serial.println((const char *) &data[4]);
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, len, from.id.pub_key, PUB_KEY_SIZE);
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, from.shared_secret, packet->path, packet->path_len,
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
if (path) sendFlood(path);
} else {
mesh::Packet* ack = createAck(ack_hash);
if (ack) {
if (from.out_path_len < 0) {
sendFlood(ack);
} else {
sendDirect(ack, from.out_path, from.out_path_len);
}
}
}
}
}
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
if (_table->hasSeenPacket(packet)) return;
int i = matching_peer_indexes[sender_idx];
if (i < 0 && i >= num_contacts) {
MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i);
return;
}
ContactInfo& from = contacts[i];
Serial.printf("PATH to: %s, path_len=%d\n", from.name, (uint32_t) path_len);
memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect()
if (packet->isRouteFlood()) {
// send a reciprocal return path to sender, but send DIRECTLY!
mesh::Packet* rpath = createPathReturn(from.id, from.shared_secret, packet->path, packet->path_len, 0, NULL, 0);
if (rpath) sendDirect(rpath, path, path_len);
}
if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) {
// also got an encoded ACK!
processAck(extra);
}
}
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override {
processAck((uint8_t *)&ack_crc);
}
void processAck(const uint8_t *data) {
if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
Serial.println("Got ACK!");
// NOTE: the same ACK can be received multiple times!
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
txt_send_timeout = 0;
}
}
public:
uint32_t expected_ack_crc;
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleSeenTable& table)
: mesh::Mesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16)), _table(&table)
{
num_contacts = 0;
}
mesh::Packet* composeMsgPacket(ContactInfo& recipient, const char *text) {
int text_len = strlen(text);
if (text_len > MAX_TEXT_LEN) return NULL;
uint8_t temp[4+MAX_TEXT_LEN+1];
uint32_t timestamp = getRTCClock()->getCurrentTime();
memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
memcpy(&temp[4], text, text_len);
// calc expected ACK reply
mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, (const uint8_t *) temp, 4 + text_len, self_id.pub_key, PUB_KEY_SIZE);
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 4 + text_len);
}
void sendSelfAnnounce() {
mesh::Packet* announce = createAdvert(self_id);
if (announce) {
sendFlood(announce);
Serial.println(" (advert sent).");
} else {
Serial.println(" ERROR: unable to create packet.");
}
}
};
SPIClass spi;
StdRNG fast_rng;
SimpleSeenTable table;
SX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
MyMesh the_mesh(*new RadioLibWrapper(radio, board), fast_rng, *new VolatileRTCClock(), table);
void halt() {
while (1) ;
}
static char command[MAX_TEXT_LEN+1];
void setup() {
Serial.begin(115200);
board.begin();
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
halt();
}
fast_rng.begin(radio.random(0x7FFFFFFF));
#if RUN_AS_ALICE
Serial.println(" --- user: Alice ---");
the_mesh.self_id = mesh::LocalIdentity(alice_private, alice_public);
the_mesh.addContact("Bob", mesh::Identity(bob_public));
#else
Serial.println(" --- user: Bob ---");
the_mesh.self_id = mesh::LocalIdentity(bob_private, bob_public);
the_mesh.addContact("Alice", mesh::Identity(alice_public));
#endif
Serial.println("Help:");
Serial.println(" enter 'ann' to announce presence to mesh");
Serial.println(" enter 'send {message text}' to send a message");
the_mesh.begin();
command[0] = 0;
txt_send_timeout = 0;
// send out initial Announce to the mesh
the_mesh.sendSelfAnnounce();
}
void loop() {
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();
if (c != '\n') {
command[len++] = c;
command[len] = 0;
}
Serial.print(c);
}
if (len == sizeof(command)-1) { // command buffer full
command[sizeof(command)-1] = '\r';
}
if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator
if (memcmp(command, "send ", 5) == 0) {
// TODO: some way to select recipient??
ContactInfo& recipient = the_mesh.contacts[0]; // just send to first contact for now
const char *text = &command[5];
mesh::Packet* pkt = the_mesh.composeMsgPacket(recipient, text);
if (pkt) {
if (recipient.out_path_len < 0) {
the_mesh.sendFlood(pkt);
txt_send_timeout = the_mesh.futureMillis(FLOOD_SEND_TIMEOUT_MILLIS);
} else {
the_mesh.sendDirect(pkt, recipient.out_path, recipient.out_path_len);
txt_send_timeout = the_mesh.futureMillis(DIRECT_SEND_TIMEOUT_MILLIS);
}
Serial.println(" (message sent)");
} else {
Serial.println(" ERROR: unable to create packet.");
}
} else if (strcmp(command, "ann") == 0) {
the_mesh.sendSelfAnnounce();
} else if (strcmp(command, "key") == 0) {
mesh::LocalIdentity new_id(the_mesh.getRNG());
new_id.printTo(Serial);
} else {
Serial.print(" ERROR: unknown command: "); Serial.println(command);
}
command[0] = 0; // reset command buffer
}
if (txt_send_timeout && the_mesh.millisHasNowPassed(txt_send_timeout)) {
// failed to get an ACK
ContactInfo& recipient = the_mesh.contacts[0]; // just the one contact for now
Serial.println(" ERROR: timed out, no ACK.");
// path to our contact is now possibly broken, fallback to Flood mode
recipient.out_path_len = -1;
txt_send_timeout = 0;
}
the_mesh.loop();
}

View File

@@ -0,0 +1,304 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include <SPIFFS.h>
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/SimpleSeenTable.h>
#include <helpers/StaticPoolPacketManager.h>
/* ---------------------------------- CONFIGURATION ------------------------------------- */
#define ADMIN_PASSWORD "h^(kl@#)"
#ifdef HELTEC_LORA_V3
#include <helpers/HeltecV3Board.h>
static HeltecV3Board board;
#else
#error "need to provide a 'board' object"
#endif
/* -------------------------------------------------------------------------------------- */
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - FROM_HASH_LEN - CIPHER_MAC_SIZE - 1)
#define CMD_GET_STATS 0x01
#define CMD_SET_CLOCK 0x02
#define CMD_SEND_ANNOUNCE 0x03
#define CMD_SET_CONFIG 0x04
struct RepeaterStats {
uint16_t batt_milli_volts;
uint16_t curr_tx_queue_len;
uint16_t curr_free_queue_len;
int16_t last_rssi;
uint32_t n_packets_recv;
uint32_t n_packets_sent;
uint32_t total_air_time_secs;
uint32_t total_up_time_secs;
};
class MyMesh : public mesh::Mesh {
SimpleSeenTable* _table;
uint32_t last_advert_timestamp = 0;
mesh::Identity server_id;
uint8_t server_secret[PUB_KEY_SIZE];
int server_path_len = -1;
uint8_t server_path[MAX_PATH_SIZE];
bool got_adv = false;
protected:
int searchPeersByHash(const uint8_t* hash) override {
if (got_adv && server_id.isHashMatch(hash)) {
return 1;
}
return 0; // not found
}
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
if (memcmp(app_data, "repeater:", 9) == 0) {
Serial.println("Received advertisement from a repeater!");
// check for replay attacks
if (timestamp > last_advert_timestamp) {
last_advert_timestamp = timestamp;
server_id = id;
self_id.calcSharedSecret(server_secret, id); // calc ECDH shared secret
got_adv = true;
// 'login' to repeater. (mainly lets it know our public key)
uint32_t now = getRTCClock()->getCurrentTime(); // important, need timestamp in packet, so that packet_hash will be unique
uint8_t temp[4 + 8];
memcpy(temp, &now, 4);
memcpy(&temp[4], ADMIN_PASSWORD, 8);
mesh::Packet* login = createDatagram(PAYLOAD_TYPE_ANON_REQ, server_id, server_secret, temp, sizeof(temp));
if (login) sendFlood(login); // server_path won't be known yet
}
}
}
void handleResponse(const uint8_t* reply, size_t reply_len) {
if (reply_len >= 4 + sizeof(RepeaterStats)) { // got an GET_STATS reply from repeater
RepeaterStats stats;
memcpy(&stats, &reply[4], sizeof(stats));
Serial.println("Repeater Stats:");
Serial.printf(" battery: %d mV\n", (uint32_t) stats.batt_milli_volts);
Serial.printf(" tx queue: %d\n", (uint32_t) stats.curr_tx_queue_len);
Serial.printf(" free queue: %d\n", (uint32_t) stats.curr_free_queue_len);
Serial.printf(" last RSSI: %d\n", (int) stats.last_rssi);
Serial.printf(" num recv: %d\n", stats.n_packets_recv);
Serial.printf(" num sent: %d\n", stats.n_packets_sent);
Serial.printf(" air time (secs): %d\n", stats.total_air_time_secs);
Serial.printf(" up time (secs): %d\n", stats.total_up_time_secs);
} else if (reply_len > 4) { // got an SET_* reply from repeater
char tmp[MAX_PACKET_PAYLOAD];
memcpy(tmp, &reply[4], reply_len - 4);
tmp[reply_len - 4] = 0; // make a C string of reply
Serial.print("Reply: "); Serial.println(tmp);
}
}
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
if (type == PAYLOAD_TYPE_RESPONSE) {
if (_table->hasSeenPacket(packet)) return;
handleResponse(data, len);
if (packet->isRouteFlood()) {
// let server know path TO here, so they can use sendDirect() for future ping responses
mesh::Packet* path = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
if (path) sendFlood(path);
}
}
}
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
if (_table->hasSeenPacket(packet)) return;
// must be from server_id
Serial.printf("PATH to repeater, path_len=%d\n", (uint32_t) path_len);
memcpy(server_path, path, server_path_len = path_len); // store a copy of path, for sendDirect()
if (packet->isRouteFlood()) {
// send a reciprocal return path to sender, but send DIRECTLY!
mesh::Packet* rpath = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
if (rpath) sendDirect(rpath, path, path_len);
}
if (extra_type == PAYLOAD_TYPE_RESPONSE) {
handleResponse(extra, extra_len);
}
}
public:
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleSeenTable& table)
: mesh::Mesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16)), _table(&table)
{
}
mesh::Packet* createStatsRequest(uint32_t max_age) {
uint8_t payload[9];
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(payload, &now, 4);
payload[4] = CMD_GET_STATS;
memcpy(&payload[5], &max_age, 4);
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
}
mesh::Packet* createSetClockRequest(uint32_t timestamp) {
uint8_t payload[9];
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(payload, &now, 4);
payload[4] = CMD_SET_CLOCK;
memcpy(&payload[5], &now, 4); // repeated :-(
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
}
mesh::Packet* createSetAirtimeFactorRequest(float airtime_factor) {
uint8_t payload[16];
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(payload, &now, 4);
payload[4] = CMD_SET_CONFIG;
sprintf((char *) &payload[5], "AF%f", airtime_factor);
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
}
mesh::Packet* createAnnounceRequest() {
uint8_t payload[5];
uint32_t now = getRTCClock()->getCurrentTime();
memcpy(payload, &now, 4);
payload[4] = CMD_SEND_ANNOUNCE;
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
}
mesh::Packet* parseCommand(char* command) {
if (strcmp(command, "stats") == 0) {
return createStatsRequest(60*60); // max_age = one hour
} else if (memcmp(command, "setclock ", 9) == 0) {
uint32_t timestamp = atol(&command[9]);
return createSetClockRequest(timestamp);
} else if (memcmp(command, "set AF=", 7) == 0) {
float factor = atof(&command[7]);
return createSetAirtimeFactorRequest(factor);
} else if (strcmp(command, "ann") == 0) {
return createAnnounceRequest();
}
return NULL; // unknown command
}
void sendCommand(mesh::Packet* pkt) {
if (server_path_len < 0) {
sendFlood(pkt);
} else {
sendDirect(pkt, server_path, server_path_len);
}
}
};
StdRNG fast_rng;
SimpleSeenTable table;
#if defined(P_LORA_SCLK)
SPIClass spi;
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
MyMesh the_mesh(*new CustomSX1262Wrapper(radio, board), fast_rng, *new VolatileRTCClock(), table);
void halt() {
while (1) ;
}
static char command[MAX_TEXT_LEN+1];
#include <SHA256.h>
void setup() {
Serial.begin(115200);
delay(5000);
board.begin();
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
#else
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
#endif
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
halt();
}
fast_rng.begin(radio.random(0x7FFFFFFF));
/* add this to tests
uint8_t mac_encrypted[CIPHER_MAC_SIZE+CIPHER_BLOCK_SIZE];
const char *orig_msg = "original";
int enc_len = mesh::Utils::encryptThenMAC(mesh.admin_secret, mac_encrypted, (const uint8_t *) orig_msg, strlen(orig_msg));
char decrypted[CIPHER_BLOCK_SIZE*2];
int len = mesh::Utils::MACThenDecrypt(mesh.admin_secret, (uint8_t *)decrypted, mac_encrypted, enc_len);
if (len > 0) {
decrypted[len] = 0;
Serial.print("decrypted text: "); Serial.println(decrypted);
} else {
Serial.println("MACs DONT match!");
}
*/
Serial.println("Help:");
Serial.println(" enter 'key' to generate new keypair");
Serial.println(" enter 'stats' to request repeater stats");
Serial.println(" enter 'setclock {unix-epoch-seconds}' to set repeater's clock");
Serial.println(" enter 'set AF={factor}' to set airtime budget factor");
Serial.println(" enter 'ann' to make repeater re-announce to mesh");
the_mesh.begin();
command[0] = 0;
}
void loop() {
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();
if (c != '\n') {
command[len++] = c;
command[len] = 0;
}
Serial.print(c);
}
if (len == sizeof(command)-1) { // command buffer full
command[sizeof(command)-1] = '\r';
}
if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator
if (strcmp(command, "key") == 0) {
mesh::LocalIdentity new_id(the_mesh.getRNG());
new_id.printTo(Serial);
} else {
mesh::Packet* pkt = the_mesh.parseCommand(command);
if (pkt) {
the_mesh.sendCommand(pkt);
Serial.println(" (request sent)");
} else {
Serial.print(" ERROR: unknown command: "); Serial.println(command);
}
}
command[0] = 0; // reset command buffer
}
the_mesh.loop();
}

39
include/README Normal file
View File

@@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

69
lib/ed25519/add_scalar.c Normal file
View File

@@ -0,0 +1,69 @@
#include "ed_25519.h"
#include "ge.h"
#include "sc.h"
#include "sha512.h"
/* see http://crypto.stackexchange.com/a/6215/4697 */
void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) {
const unsigned char SC_1[32] = {1}; /* scalar with value 1 */
unsigned char n[32];
ge_p3 nB;
ge_p1p1 A_p1p1;
ge_p3 A;
ge_p3 public_key_unpacked;
ge_cached T;
sha512_context hash;
unsigned char hashbuf[64];
int i;
/* copy the scalar and clear highest bit */
for (i = 0; i < 31; ++i) {
n[i] = scalar[i];
}
n[31] = scalar[31] & 127;
/* private key: a = n + t */
if (private_key) {
sc_muladd(private_key, SC_1, n, private_key);
// https://github.com/orlp/ed25519/issues/3
sha512_init(&hash);
sha512_update(&hash, private_key + 32, 32);
sha512_update(&hash, scalar, 32);
sha512_final(&hash, hashbuf);
for (i = 0; i < 32; ++i) {
private_key[32 + i] = hashbuf[i];
}
}
/* public key: A = nB + T */
if (public_key) {
/* if we know the private key we don't need a point addition, which is faster */
/* using a "timing attack" you could find out wether or not we know the private
key, but this information seems rather useless - if this is important pass
public_key and private_key seperately in 2 function calls */
if (private_key) {
ge_scalarmult_base(&A, private_key);
} else {
/* unpack public key into T */
ge_frombytes_negate_vartime(&public_key_unpacked, public_key);
fe_neg(public_key_unpacked.X, public_key_unpacked.X); /* undo negate */
fe_neg(public_key_unpacked.T, public_key_unpacked.T); /* undo negate */
ge_p3_to_cached(&T, &public_key_unpacked);
/* calculate n*B */
ge_scalarmult_base(&nB, n);
/* A = n*B + T */
ge_add(&A_p1p1, &nB, &T);
ge_p1p1_to_p3(&A, &A_p1p1);
}
/* pack public key */
ge_p3_tobytes(public_key, &A);
}
}

40
lib/ed25519/ed_25519.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef ED25519_H
#define ED25519_H
// Nightcracker's Ed25519 - https://github.com/orlp/ed25519
#include <stddef.h>
#if defined(_WIN32)
#if defined(ED25519_BUILD_DLL)
#define ED25519_DECLSPEC __declspec(dllexport)
#elif defined(ED25519_DLL)
#define ED25519_DECLSPEC __declspec(dllimport)
#else
#define ED25519_DECLSPEC
#endif
#else
#define ED25519_DECLSPEC
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ED25519_NO_SEED
int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed);
#endif
void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed);
void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key);
int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key);
void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar);
void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key);
#ifdef __cplusplus
}
#endif
#endif

1491
lib/ed25519/fe.c Normal file

File diff suppressed because it is too large Load Diff

41
lib/ed25519/fe.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef FE_H
#define FE_H
#include "fixedint.h"
/*
fe means field element.
Here the field is \Z/(2^255-19).
An element t, entries t[0]...t[9], represents the integer
t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9].
Bounds on each t[i] vary depending on context.
*/
typedef int32_t fe[10];
void fe_0(fe h);
void fe_1(fe h);
void fe_frombytes(fe h, const unsigned char *s);
void fe_tobytes(unsigned char *s, const fe h);
void fe_copy(fe h, const fe f);
int fe_isnegative(const fe f);
int fe_isnonzero(const fe f);
void fe_cmov(fe f, const fe g, unsigned int b);
void fe_cswap(fe f, fe g, unsigned int b);
void fe_neg(fe h, const fe f);
void fe_add(fe h, const fe f, const fe g);
void fe_invert(fe out, const fe z);
void fe_sq(fe h, const fe f);
void fe_sq2(fe h, const fe f);
void fe_mul(fe h, const fe f, const fe g);
void fe_mul121666(fe h, fe f);
void fe_pow22523(fe out, const fe z);
void fe_sub(fe h, const fe f, const fe g);
#endif

72
lib/ed25519/fixedint.h Normal file
View File

@@ -0,0 +1,72 @@
/*
Portable header to provide the 32 and 64 bits type.
Not a compatible replacement for <stdint.h>, do not blindly use it as such.
*/
#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED)
#include <stdint.h>
#define FIXEDINT_H_INCLUDED
#if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C)
#include <limits.h>
#define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX))
#endif
#endif
#ifndef FIXEDINT_H_INCLUDED
#define FIXEDINT_H_INCLUDED
#include <limits.h>
/* (u)int32_t */
#ifndef uint32_t
#if (ULONG_MAX == 0xffffffffUL)
typedef unsigned long uint32_t;
#elif (UINT_MAX == 0xffffffffUL)
typedef unsigned int uint32_t;
#elif (USHRT_MAX == 0xffffffffUL)
typedef unsigned short uint32_t;
#endif
#endif
#ifndef int32_t
#if (LONG_MAX == 0x7fffffffL)
typedef signed long int32_t;
#elif (INT_MAX == 0x7fffffffL)
typedef signed int int32_t;
#elif (SHRT_MAX == 0x7fffffffL)
typedef signed short int32_t;
#endif
#endif
/* (u)int64_t */
#if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L)
typedef long long int64_t;
typedef unsigned long long uint64_t;
#define UINT64_C(v) v ##ULL
#define INT64_C(v) v ##LL
#elif defined(__GNUC__)
__extension__ typedef long long int64_t;
__extension__ typedef unsigned long long uint64_t;
#define UINT64_C(v) v ##ULL
#define INT64_C(v) v ##LL
#elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC)
typedef long long int64_t;
typedef unsigned long long uint64_t;
#define UINT64_C(v) v ##ULL
#define INT64_C(v) v ##LL
#elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC)
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#define UINT64_C(v) v ##UI64
#define INT64_C(v) v ##I64
#endif
#endif

467
lib/ed25519/ge.c Normal file
View File

@@ -0,0 +1,467 @@
#include "ge.h"
#include "precomp_data.h"
/*
r = p + q
*/
void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
fe t0;
fe_add(r->X, p->Y, p->X);
fe_sub(r->Y, p->Y, p->X);
fe_mul(r->Z, r->X, q->YplusX);
fe_mul(r->Y, r->Y, q->YminusX);
fe_mul(r->T, q->T2d, p->T);
fe_mul(r->X, p->Z, q->Z);
fe_add(t0, r->X, r->X);
fe_sub(r->X, r->Z, r->Y);
fe_add(r->Y, r->Z, r->Y);
fe_add(r->Z, t0, r->T);
fe_sub(r->T, t0, r->T);
}
static void slide(signed char *r, const unsigned char *a) {
int i;
int b;
int k;
for (i = 0; i < 256; ++i) {
r[i] = 1 & (a[i >> 3] >> (i & 7));
}
for (i = 0; i < 256; ++i)
if (r[i]) {
for (b = 1; b <= 6 && i + b < 256; ++b) {
if (r[i + b]) {
if (r[i] + (r[i + b] << b) <= 15) {
r[i] += r[i + b] << b;
r[i + b] = 0;
} else if (r[i] - (r[i + b] << b) >= -15) {
r[i] -= r[i + b] << b;
for (k = i + b; k < 256; ++k) {
if (!r[k]) {
r[k] = 1;
break;
}
r[k] = 0;
}
} else {
break;
}
}
}
}
}
/*
r = a * A + b * B
where a = a[0]+256*a[1]+...+256^31 a[31].
and b = b[0]+256*b[1]+...+256^31 b[31].
B is the Ed25519 base point (x,4/5) with x positive.
*/
void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) {
signed char aslide[256];
signed char bslide[256];
ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
ge_p1p1 t;
ge_p3 u;
ge_p3 A2;
int i;
slide(aslide, a);
slide(bslide, b);
ge_p3_to_cached(&Ai[0], A);
ge_p3_dbl(&t, A);
ge_p1p1_to_p3(&A2, &t);
ge_add(&t, &A2, &Ai[0]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[1], &u);
ge_add(&t, &A2, &Ai[1]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[2], &u);
ge_add(&t, &A2, &Ai[2]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[3], &u);
ge_add(&t, &A2, &Ai[3]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[4], &u);
ge_add(&t, &A2, &Ai[4]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[5], &u);
ge_add(&t, &A2, &Ai[5]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[6], &u);
ge_add(&t, &A2, &Ai[6]);
ge_p1p1_to_p3(&u, &t);
ge_p3_to_cached(&Ai[7], &u);
ge_p2_0(r);
for (i = 255; i >= 0; --i) {
if (aslide[i] || bslide[i]) {
break;
}
}
for (; i >= 0; --i) {
ge_p2_dbl(&t, r);
if (aslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Ai[aslide[i] / 2]);
} else if (aslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
}
if (bslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_madd(&t, &u, &Bi[bslide[i] / 2]);
} else if (bslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
}
ge_p1p1_to_p2(r, &t);
}
}
static const fe d = {
-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116
};
static const fe sqrtm1 = {
-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482
};
int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) {
fe u;
fe v;
fe v3;
fe vxx;
fe check;
fe_frombytes(h->Y, s);
fe_1(h->Z);
fe_sq(u, h->Y);
fe_mul(v, u, d);
fe_sub(u, u, h->Z); /* u = y^2-1 */
fe_add(v, v, h->Z); /* v = dy^2+1 */
fe_sq(v3, v);
fe_mul(v3, v3, v); /* v3 = v^3 */
fe_sq(h->X, v3);
fe_mul(h->X, h->X, v);
fe_mul(h->X, h->X, u); /* x = uv^7 */
fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */
fe_mul(h->X, h->X, v3);
fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */
fe_sq(vxx, h->X);
fe_mul(vxx, vxx, v);
fe_sub(check, vxx, u); /* vx^2-u */
if (fe_isnonzero(check)) {
fe_add(check, vxx, u); /* vx^2+u */
if (fe_isnonzero(check)) {
return -1;
}
fe_mul(h->X, h->X, sqrtm1);
}
if (fe_isnegative(h->X) == (s[31] >> 7)) {
fe_neg(h->X, h->X);
}
fe_mul(h->T, h->X, h->Y);
return 0;
}
/*
r = p + q
*/
void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
fe t0;
fe_add(r->X, p->Y, p->X);
fe_sub(r->Y, p->Y, p->X);
fe_mul(r->Z, r->X, q->yplusx);
fe_mul(r->Y, r->Y, q->yminusx);
fe_mul(r->T, q->xy2d, p->T);
fe_add(t0, p->Z, p->Z);
fe_sub(r->X, r->Z, r->Y);
fe_add(r->Y, r->Z, r->Y);
fe_add(r->Z, t0, r->T);
fe_sub(r->T, t0, r->T);
}
/*
r = p - q
*/
void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
fe t0;
fe_add(r->X, p->Y, p->X);
fe_sub(r->Y, p->Y, p->X);
fe_mul(r->Z, r->X, q->yminusx);
fe_mul(r->Y, r->Y, q->yplusx);
fe_mul(r->T, q->xy2d, p->T);
fe_add(t0, p->Z, p->Z);
fe_sub(r->X, r->Z, r->Y);
fe_add(r->Y, r->Z, r->Y);
fe_sub(r->Z, t0, r->T);
fe_add(r->T, t0, r->T);
}
/*
r = p
*/
void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) {
fe_mul(r->X, p->X, p->T);
fe_mul(r->Y, p->Y, p->Z);
fe_mul(r->Z, p->Z, p->T);
}
/*
r = p
*/
void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) {
fe_mul(r->X, p->X, p->T);
fe_mul(r->Y, p->Y, p->Z);
fe_mul(r->Z, p->Z, p->T);
fe_mul(r->T, p->X, p->Y);
}
void ge_p2_0(ge_p2 *h) {
fe_0(h->X);
fe_1(h->Y);
fe_1(h->Z);
}
/*
r = 2 * p
*/
void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) {
fe t0;
fe_sq(r->X, p->X);
fe_sq(r->Z, p->Y);
fe_sq2(r->T, p->Z);
fe_add(r->Y, p->X, p->Y);
fe_sq(t0, r->Y);
fe_add(r->Y, r->Z, r->X);
fe_sub(r->Z, r->Z, r->X);
fe_sub(r->X, t0, r->Y);
fe_sub(r->T, r->T, r->Z);
}
void ge_p3_0(ge_p3 *h) {
fe_0(h->X);
fe_1(h->Y);
fe_1(h->Z);
fe_0(h->T);
}
/*
r = 2 * p
*/
void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) {
ge_p2 q;
ge_p3_to_p2(&q, p);
ge_p2_dbl(r, &q);
}
/*
r = p
*/
static const fe d2 = {
-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199
};
void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) {
fe_add(r->YplusX, p->Y, p->X);
fe_sub(r->YminusX, p->Y, p->X);
fe_copy(r->Z, p->Z);
fe_mul(r->T2d, p->T, d2);
}
/*
r = p
*/
void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) {
fe_copy(r->X, p->X);
fe_copy(r->Y, p->Y);
fe_copy(r->Z, p->Z);
}
void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) {
fe recip;
fe x;
fe y;
fe_invert(recip, h->Z);
fe_mul(x, h->X, recip);
fe_mul(y, h->Y, recip);
fe_tobytes(s, y);
s[31] ^= fe_isnegative(x) << 7;
}
static unsigned char equal(signed char b, signed char c) {
unsigned char ub = b;
unsigned char uc = c;
unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */
uint64_t y = x; /* 0: yes; 1..255: no */
y -= 1; /* large: yes; 0..254: no */
y >>= 63; /* 1: yes; 0: no */
return (unsigned char) y;
}
static unsigned char negative(signed char b) {
uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
x >>= 63; /* 1: yes; 0: no */
return (unsigned char) x;
}
static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) {
fe_cmov(t->yplusx, u->yplusx, b);
fe_cmov(t->yminusx, u->yminusx, b);
fe_cmov(t->xy2d, u->xy2d, b);
}
static void select(ge_precomp *t, int pos, signed char b) {
ge_precomp minust;
unsigned char bnegative = negative(b);
unsigned char babs = b - (((-bnegative) & b) << 1);
fe_1(t->yplusx);
fe_1(t->yminusx);
fe_0(t->xy2d);
cmov(t, &base[pos][0], equal(babs, 1));
cmov(t, &base[pos][1], equal(babs, 2));
cmov(t, &base[pos][2], equal(babs, 3));
cmov(t, &base[pos][3], equal(babs, 4));
cmov(t, &base[pos][4], equal(babs, 5));
cmov(t, &base[pos][5], equal(babs, 6));
cmov(t, &base[pos][6], equal(babs, 7));
cmov(t, &base[pos][7], equal(babs, 8));
fe_copy(minust.yplusx, t->yminusx);
fe_copy(minust.yminusx, t->yplusx);
fe_neg(minust.xy2d, t->xy2d);
cmov(t, &minust, bnegative);
}
/*
h = a * B
where a = a[0]+256*a[1]+...+256^31 a[31]
B is the Ed25519 base point (x,4/5) with x positive.
Preconditions:
a[31] <= 127
*/
void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) {
signed char e[64];
signed char carry;
ge_p1p1 r;
ge_p2 s;
ge_precomp t;
int i;
for (i = 0; i < 32; ++i) {
e[2 * i + 0] = (a[i] >> 0) & 15;
e[2 * i + 1] = (a[i] >> 4) & 15;
}
/* each e[i] is between 0 and 15 */
/* e[63] is between 0 and 7 */
carry = 0;
for (i = 0; i < 63; ++i) {
e[i] += carry;
carry = e[i] + 8;
carry >>= 4;
e[i] -= carry << 4;
}
e[63] += carry;
/* each e[i] is between -8 and 8 */
ge_p3_0(h);
for (i = 1; i < 64; i += 2) {
select(&t, i / 2, e[i]);
ge_madd(&r, h, &t);
ge_p1p1_to_p3(h, &r);
}
ge_p3_dbl(&r, h);
ge_p1p1_to_p2(&s, &r);
ge_p2_dbl(&r, &s);
ge_p1p1_to_p2(&s, &r);
ge_p2_dbl(&r, &s);
ge_p1p1_to_p2(&s, &r);
ge_p2_dbl(&r, &s);
ge_p1p1_to_p3(h, &r);
for (i = 0; i < 64; i += 2) {
select(&t, i / 2, e[i]);
ge_madd(&r, h, &t);
ge_p1p1_to_p3(h, &r);
}
}
/*
r = p - q
*/
void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
fe t0;
fe_add(r->X, p->Y, p->X);
fe_sub(r->Y, p->Y, p->X);
fe_mul(r->Z, r->X, q->YminusX);
fe_mul(r->Y, r->Y, q->YplusX);
fe_mul(r->T, q->T2d, p->T);
fe_mul(r->X, p->Z, q->Z);
fe_add(t0, r->X, r->X);
fe_sub(r->X, r->Z, r->Y);
fe_add(r->Y, r->Z, r->Y);
fe_sub(r->Z, t0, r->T);
fe_add(r->T, t0, r->T);
}
void ge_tobytes(unsigned char *s, const ge_p2 *h) {
fe recip;
fe x;
fe y;
fe_invert(recip, h->Z);
fe_mul(x, h->X, recip);
fe_mul(y, h->Y, recip);
fe_tobytes(s, y);
s[31] ^= fe_isnegative(x) << 7;
}

74
lib/ed25519/ge.h Normal file
View File

@@ -0,0 +1,74 @@
#ifndef GE_H
#define GE_H
#include "fe.h"
/*
ge means group element.
Here the group is the set of pairs (x,y) of field elements (see fe.h)
satisfying -x^2 + y^2 = 1 + d x^2y^2
where d = -121665/121666.
Representations:
ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
ge_precomp (Duif): (y+x,y-x,2dxy)
*/
typedef struct {
fe X;
fe Y;
fe Z;
} ge_p2;
typedef struct {
fe X;
fe Y;
fe Z;
fe T;
} ge_p3;
typedef struct {
fe X;
fe Y;
fe Z;
fe T;
} ge_p1p1;
typedef struct {
fe yplusx;
fe yminusx;
fe xy2d;
} ge_precomp;
typedef struct {
fe YplusX;
fe YminusX;
fe Z;
fe T2d;
} ge_cached;
void ge_p3_tobytes(unsigned char *s, const ge_p3 *h);
void ge_tobytes(unsigned char *s, const ge_p2 *h);
int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s);
void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b);
void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
void ge_scalarmult_base(ge_p3 *h, const unsigned char *a);
void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p);
void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p);
void ge_p2_0(ge_p2 *h);
void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p);
void ge_p3_0(ge_p3 *h);
void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p);
void ge_p3_to_cached(ge_cached *r, const ge_p3 *p);
void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p);
#endif

View File

@@ -0,0 +1,79 @@
#include "ed_25519.h"
#include "fe.h"
void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) {
unsigned char e[32];
unsigned int i;
fe x1;
fe x2;
fe z2;
fe x3;
fe z3;
fe tmp0;
fe tmp1;
int pos;
unsigned int swap;
unsigned int b;
/* copy the private key and make sure it's valid */
for (i = 0; i < 32; ++i) {
e[i] = private_key[i];
}
e[0] &= 248;
e[31] &= 63;
e[31] |= 64;
/* unpack the public key and convert edwards to montgomery */
/* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */
fe_frombytes(x1, public_key);
fe_1(tmp1);
fe_add(tmp0, x1, tmp1);
fe_sub(tmp1, tmp1, x1);
fe_invert(tmp1, tmp1);
fe_mul(x1, tmp0, tmp1);
fe_1(x2);
fe_0(z2);
fe_copy(x3, x1);
fe_1(z3);
swap = 0;
for (pos = 254; pos >= 0; --pos) {
b = e[pos / 8] >> (pos & 7);
b &= 1;
swap ^= b;
fe_cswap(x2, x3, swap);
fe_cswap(z2, z3, swap);
swap = b;
/* from montgomery.h */
fe_sub(tmp0, x3, z3);
fe_sub(tmp1, x2, z2);
fe_add(x2, x2, z2);
fe_add(z2, x3, z3);
fe_mul(z3, tmp0, x2);
fe_mul(z2, z2, tmp1);
fe_sq(tmp0, tmp1);
fe_sq(tmp1, x2);
fe_add(x3, z3, z2);
fe_sub(z2, z3, z2);
fe_mul(x2, tmp1, tmp0);
fe_sub(tmp1, tmp1, tmp0);
fe_sq(z2, z2);
fe_mul121666(z3, tmp1);
fe_sq(x3, x3);
fe_add(tmp0, tmp0, z3);
fe_mul(z3, x1, z2);
fe_mul(z2, tmp1, tmp0);
}
fe_cswap(x2, x3, swap);
fe_cswap(z2, z3, swap);
fe_invert(z2, z2);
fe_mul(x2, x2, z2);
fe_tobytes(shared_secret, x2);
}

16
lib/ed25519/keypair.c Normal file
View File

@@ -0,0 +1,16 @@
#include "ed_25519.h"
#include "sha512.h"
#include "ge.h"
void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) {
ge_p3 A;
sha512(seed, 32, private_key);
private_key[0] &= 248;
private_key[31] &= 63;
private_key[31] |= 64;
ge_scalarmult_base(&A, private_key);
ge_p3_tobytes(public_key, &A);
}

16
lib/ed25519/license.txt Normal file
View File

@@ -0,0 +1,16 @@
Copyright (c) 2015 Orson Peters <orsonpeters@gmail.com>
This software is provided 'as-is', without any express or implied warranty. In no event will the
authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial
applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the
original software. If you use this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as
being the original software.
3. This notice may not be removed or altered from any source distribution.

1391
lib/ed25519/precomp_data.h Normal file

File diff suppressed because it is too large Load Diff

809
lib/ed25519/sc.c Normal file
View File

@@ -0,0 +1,809 @@
#include "fixedint.h"
#include "sc.h"
static uint64_t load_3(const unsigned char *in) {
uint64_t result;
result = (uint64_t) in[0];
result |= ((uint64_t) in[1]) << 8;
result |= ((uint64_t) in[2]) << 16;
return result;
}
static uint64_t load_4(const unsigned char *in) {
uint64_t result;
result = (uint64_t) in[0];
result |= ((uint64_t) in[1]) << 8;
result |= ((uint64_t) in[2]) << 16;
result |= ((uint64_t) in[3]) << 24;
return result;
}
/*
Input:
s[0]+256*s[1]+...+256^63*s[63] = s
Output:
s[0]+256*s[1]+...+256^31*s[31] = s mod l
where l = 2^252 + 27742317777372353535851937790883648493.
Overwrites s in place.
*/
void sc_reduce(unsigned char *s) {
int64_t s0 = 2097151 & load_3(s);
int64_t s1 = 2097151 & (load_4(s + 2) >> 5);
int64_t s2 = 2097151 & (load_3(s + 5) >> 2);
int64_t s3 = 2097151 & (load_4(s + 7) >> 7);
int64_t s4 = 2097151 & (load_4(s + 10) >> 4);
int64_t s5 = 2097151 & (load_3(s + 13) >> 1);
int64_t s6 = 2097151 & (load_4(s + 15) >> 6);
int64_t s7 = 2097151 & (load_3(s + 18) >> 3);
int64_t s8 = 2097151 & load_3(s + 21);
int64_t s9 = 2097151 & (load_4(s + 23) >> 5);
int64_t s10 = 2097151 & (load_3(s + 26) >> 2);
int64_t s11 = 2097151 & (load_4(s + 28) >> 7);
int64_t s12 = 2097151 & (load_4(s + 31) >> 4);
int64_t s13 = 2097151 & (load_3(s + 34) >> 1);
int64_t s14 = 2097151 & (load_4(s + 36) >> 6);
int64_t s15 = 2097151 & (load_3(s + 39) >> 3);
int64_t s16 = 2097151 & load_3(s + 42);
int64_t s17 = 2097151 & (load_4(s + 44) >> 5);
int64_t s18 = 2097151 & (load_3(s + 47) >> 2);
int64_t s19 = 2097151 & (load_4(s + 49) >> 7);
int64_t s20 = 2097151 & (load_4(s + 52) >> 4);
int64_t s21 = 2097151 & (load_3(s + 55) >> 1);
int64_t s22 = 2097151 & (load_4(s + 57) >> 6);
int64_t s23 = (load_4(s + 60) >> 3);
int64_t carry0;
int64_t carry1;
int64_t carry2;
int64_t carry3;
int64_t carry4;
int64_t carry5;
int64_t carry6;
int64_t carry7;
int64_t carry8;
int64_t carry9;
int64_t carry10;
int64_t carry11;
int64_t carry12;
int64_t carry13;
int64_t carry14;
int64_t carry15;
int64_t carry16;
s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
s23 = 0;
s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
s22 = 0;
s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
s21 = 0;
s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
s20 = 0;
s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
s19 = 0;
s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
s18 = 0;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
s17 = 0;
s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
s16 = 0;
s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
s15 = 0;
s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
s14 = 0;
s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
s13 = 0;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry11 = s11 >> 21;
s12 += carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
s[0] = (unsigned char) (s0 >> 0);
s[1] = (unsigned char) (s0 >> 8);
s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
s[3] = (unsigned char) (s1 >> 3);
s[4] = (unsigned char) (s1 >> 11);
s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
s[6] = (unsigned char) (s2 >> 6);
s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
s[8] = (unsigned char) (s3 >> 1);
s[9] = (unsigned char) (s3 >> 9);
s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
s[11] = (unsigned char) (s4 >> 4);
s[12] = (unsigned char) (s4 >> 12);
s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
s[14] = (unsigned char) (s5 >> 7);
s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
s[16] = (unsigned char) (s6 >> 2);
s[17] = (unsigned char) (s6 >> 10);
s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
s[19] = (unsigned char) (s7 >> 5);
s[20] = (unsigned char) (s7 >> 13);
s[21] = (unsigned char) (s8 >> 0);
s[22] = (unsigned char) (s8 >> 8);
s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
s[24] = (unsigned char) (s9 >> 3);
s[25] = (unsigned char) (s9 >> 11);
s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
s[27] = (unsigned char) (s10 >> 6);
s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
s[29] = (unsigned char) (s11 >> 1);
s[30] = (unsigned char) (s11 >> 9);
s[31] = (unsigned char) (s11 >> 17);
}
/*
Input:
a[0]+256*a[1]+...+256^31*a[31] = a
b[0]+256*b[1]+...+256^31*b[31] = b
c[0]+256*c[1]+...+256^31*c[31] = c
Output:
s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
where l = 2^252 + 27742317777372353535851937790883648493.
*/
void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) {
int64_t a0 = 2097151 & load_3(a);
int64_t a1 = 2097151 & (load_4(a + 2) >> 5);
int64_t a2 = 2097151 & (load_3(a + 5) >> 2);
int64_t a3 = 2097151 & (load_4(a + 7) >> 7);
int64_t a4 = 2097151 & (load_4(a + 10) >> 4);
int64_t a5 = 2097151 & (load_3(a + 13) >> 1);
int64_t a6 = 2097151 & (load_4(a + 15) >> 6);
int64_t a7 = 2097151 & (load_3(a + 18) >> 3);
int64_t a8 = 2097151 & load_3(a + 21);
int64_t a9 = 2097151 & (load_4(a + 23) >> 5);
int64_t a10 = 2097151 & (load_3(a + 26) >> 2);
int64_t a11 = (load_4(a + 28) >> 7);
int64_t b0 = 2097151 & load_3(b);
int64_t b1 = 2097151 & (load_4(b + 2) >> 5);
int64_t b2 = 2097151 & (load_3(b + 5) >> 2);
int64_t b3 = 2097151 & (load_4(b + 7) >> 7);
int64_t b4 = 2097151 & (load_4(b + 10) >> 4);
int64_t b5 = 2097151 & (load_3(b + 13) >> 1);
int64_t b6 = 2097151 & (load_4(b + 15) >> 6);
int64_t b7 = 2097151 & (load_3(b + 18) >> 3);
int64_t b8 = 2097151 & load_3(b + 21);
int64_t b9 = 2097151 & (load_4(b + 23) >> 5);
int64_t b10 = 2097151 & (load_3(b + 26) >> 2);
int64_t b11 = (load_4(b + 28) >> 7);
int64_t c0 = 2097151 & load_3(c);
int64_t c1 = 2097151 & (load_4(c + 2) >> 5);
int64_t c2 = 2097151 & (load_3(c + 5) >> 2);
int64_t c3 = 2097151 & (load_4(c + 7) >> 7);
int64_t c4 = 2097151 & (load_4(c + 10) >> 4);
int64_t c5 = 2097151 & (load_3(c + 13) >> 1);
int64_t c6 = 2097151 & (load_4(c + 15) >> 6);
int64_t c7 = 2097151 & (load_3(c + 18) >> 3);
int64_t c8 = 2097151 & load_3(c + 21);
int64_t c9 = 2097151 & (load_4(c + 23) >> 5);
int64_t c10 = 2097151 & (load_3(c + 26) >> 2);
int64_t c11 = (load_4(c + 28) >> 7);
int64_t s0;
int64_t s1;
int64_t s2;
int64_t s3;
int64_t s4;
int64_t s5;
int64_t s6;
int64_t s7;
int64_t s8;
int64_t s9;
int64_t s10;
int64_t s11;
int64_t s12;
int64_t s13;
int64_t s14;
int64_t s15;
int64_t s16;
int64_t s17;
int64_t s18;
int64_t s19;
int64_t s20;
int64_t s21;
int64_t s22;
int64_t s23;
int64_t carry0;
int64_t carry1;
int64_t carry2;
int64_t carry3;
int64_t carry4;
int64_t carry5;
int64_t carry6;
int64_t carry7;
int64_t carry8;
int64_t carry9;
int64_t carry10;
int64_t carry11;
int64_t carry12;
int64_t carry13;
int64_t carry14;
int64_t carry15;
int64_t carry16;
int64_t carry17;
int64_t carry18;
int64_t carry19;
int64_t carry20;
int64_t carry21;
int64_t carry22;
s0 = c0 + a0 * b0;
s1 = c1 + a0 * b1 + a1 * b0;
s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0;
s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3;
s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
s20 = a9 * b11 + a10 * b10 + a11 * b9;
s21 = a10 * b11 + a11 * b10;
s22 = a11 * b11;
s23 = 0;
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry18 = (s18 + (1 << 20)) >> 21;
s19 += carry18;
s18 -= carry18 << 21;
carry20 = (s20 + (1 << 20)) >> 21;
s21 += carry20;
s20 -= carry20 << 21;
carry22 = (s22 + (1 << 20)) >> 21;
s23 += carry22;
s22 -= carry22 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
carry17 = (s17 + (1 << 20)) >> 21;
s18 += carry17;
s17 -= carry17 << 21;
carry19 = (s19 + (1 << 20)) >> 21;
s20 += carry19;
s19 -= carry19 << 21;
carry21 = (s21 + (1 << 20)) >> 21;
s22 += carry21;
s21 -= carry21 << 21;
s11 += s23 * 666643;
s12 += s23 * 470296;
s13 += s23 * 654183;
s14 -= s23 * 997805;
s15 += s23 * 136657;
s16 -= s23 * 683901;
s23 = 0;
s10 += s22 * 666643;
s11 += s22 * 470296;
s12 += s22 * 654183;
s13 -= s22 * 997805;
s14 += s22 * 136657;
s15 -= s22 * 683901;
s22 = 0;
s9 += s21 * 666643;
s10 += s21 * 470296;
s11 += s21 * 654183;
s12 -= s21 * 997805;
s13 += s21 * 136657;
s14 -= s21 * 683901;
s21 = 0;
s8 += s20 * 666643;
s9 += s20 * 470296;
s10 += s20 * 654183;
s11 -= s20 * 997805;
s12 += s20 * 136657;
s13 -= s20 * 683901;
s20 = 0;
s7 += s19 * 666643;
s8 += s19 * 470296;
s9 += s19 * 654183;
s10 -= s19 * 997805;
s11 += s19 * 136657;
s12 -= s19 * 683901;
s19 = 0;
s6 += s18 * 666643;
s7 += s18 * 470296;
s8 += s18 * 654183;
s9 -= s18 * 997805;
s10 += s18 * 136657;
s11 -= s18 * 683901;
s18 = 0;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry12 = (s12 + (1 << 20)) >> 21;
s13 += carry12;
s12 -= carry12 << 21;
carry14 = (s14 + (1 << 20)) >> 21;
s15 += carry14;
s14 -= carry14 << 21;
carry16 = (s16 + (1 << 20)) >> 21;
s17 += carry16;
s16 -= carry16 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
carry13 = (s13 + (1 << 20)) >> 21;
s14 += carry13;
s13 -= carry13 << 21;
carry15 = (s15 + (1 << 20)) >> 21;
s16 += carry15;
s15 -= carry15 << 21;
s5 += s17 * 666643;
s6 += s17 * 470296;
s7 += s17 * 654183;
s8 -= s17 * 997805;
s9 += s17 * 136657;
s10 -= s17 * 683901;
s17 = 0;
s4 += s16 * 666643;
s5 += s16 * 470296;
s6 += s16 * 654183;
s7 -= s16 * 997805;
s8 += s16 * 136657;
s9 -= s16 * 683901;
s16 = 0;
s3 += s15 * 666643;
s4 += s15 * 470296;
s5 += s15 * 654183;
s6 -= s15 * 997805;
s7 += s15 * 136657;
s8 -= s15 * 683901;
s15 = 0;
s2 += s14 * 666643;
s3 += s14 * 470296;
s4 += s14 * 654183;
s5 -= s14 * 997805;
s6 += s14 * 136657;
s7 -= s14 * 683901;
s14 = 0;
s1 += s13 * 666643;
s2 += s13 * 470296;
s3 += s13 * 654183;
s4 -= s13 * 997805;
s5 += s13 * 136657;
s6 -= s13 * 683901;
s13 = 0;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = (s0 + (1 << 20)) >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry2 = (s2 + (1 << 20)) >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry4 = (s4 + (1 << 20)) >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry6 = (s6 + (1 << 20)) >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry8 = (s8 + (1 << 20)) >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry10 = (s10 + (1 << 20)) >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry1 = (s1 + (1 << 20)) >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry3 = (s3 + (1 << 20)) >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry5 = (s5 + (1 << 20)) >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry7 = (s7 + (1 << 20)) >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry9 = (s9 + (1 << 20)) >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry11 = (s11 + (1 << 20)) >> 21;
s12 += carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
carry11 = s11 >> 21;
s12 += carry11;
s11 -= carry11 << 21;
s0 += s12 * 666643;
s1 += s12 * 470296;
s2 += s12 * 654183;
s3 -= s12 * 997805;
s4 += s12 * 136657;
s5 -= s12 * 683901;
s12 = 0;
carry0 = s0 >> 21;
s1 += carry0;
s0 -= carry0 << 21;
carry1 = s1 >> 21;
s2 += carry1;
s1 -= carry1 << 21;
carry2 = s2 >> 21;
s3 += carry2;
s2 -= carry2 << 21;
carry3 = s3 >> 21;
s4 += carry3;
s3 -= carry3 << 21;
carry4 = s4 >> 21;
s5 += carry4;
s4 -= carry4 << 21;
carry5 = s5 >> 21;
s6 += carry5;
s5 -= carry5 << 21;
carry6 = s6 >> 21;
s7 += carry6;
s6 -= carry6 << 21;
carry7 = s7 >> 21;
s8 += carry7;
s7 -= carry7 << 21;
carry8 = s8 >> 21;
s9 += carry8;
s8 -= carry8 << 21;
carry9 = s9 >> 21;
s10 += carry9;
s9 -= carry9 << 21;
carry10 = s10 >> 21;
s11 += carry10;
s10 -= carry10 << 21;
s[0] = (unsigned char) (s0 >> 0);
s[1] = (unsigned char) (s0 >> 8);
s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
s[3] = (unsigned char) (s1 >> 3);
s[4] = (unsigned char) (s1 >> 11);
s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
s[6] = (unsigned char) (s2 >> 6);
s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
s[8] = (unsigned char) (s3 >> 1);
s[9] = (unsigned char) (s3 >> 9);
s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
s[11] = (unsigned char) (s4 >> 4);
s[12] = (unsigned char) (s4 >> 12);
s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
s[14] = (unsigned char) (s5 >> 7);
s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
s[16] = (unsigned char) (s6 >> 2);
s[17] = (unsigned char) (s6 >> 10);
s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
s[19] = (unsigned char) (s7 >> 5);
s[20] = (unsigned char) (s7 >> 13);
s[21] = (unsigned char) (s8 >> 0);
s[22] = (unsigned char) (s8 >> 8);
s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
s[24] = (unsigned char) (s9 >> 3);
s[25] = (unsigned char) (s9 >> 11);
s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
s[27] = (unsigned char) (s10 >> 6);
s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
s[29] = (unsigned char) (s11 >> 1);
s[30] = (unsigned char) (s11 >> 9);
s[31] = (unsigned char) (s11 >> 17);
}

12
lib/ed25519/sc.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef SC_H
#define SC_H
/*
The set of scalars is \Z/l
where l = 2^252 + 27742317777372353535851937790883648493.
*/
void sc_reduce(unsigned char *s);
void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c);
#endif

40
lib/ed25519/seed.c Normal file
View File

@@ -0,0 +1,40 @@
#include "ed_25519.h"
#ifndef ED25519_NO_SEED
#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#else
#include <stdio.h>
#endif
int ed25519_create_seed(unsigned char *seed) {
#ifdef _WIN32
HCRYPTPROV prov;
if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return 1;
}
if (!CryptGenRandom(prov, 32, seed)) {
CryptReleaseContext(prov, 0);
return 1;
}
CryptReleaseContext(prov, 0);
#else
FILE *f = fopen("/dev/urandom", "rb");
if (f == NULL) {
return 1;
}
fread(seed, 1, 32, f);
fclose(f);
#endif
return 0;
}
#endif

275
lib/ed25519/sha512.c Normal file
View File

@@ -0,0 +1,275 @@
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
*
* LibTomCrypt is a library that provides various cryptographic
* algorithms in a highly modular and flexible manner.
*
* The library is free for all purposes without any express
* guarantee it works.
*
* Tom St Denis, tomstdenis@gmail.com, http://libtom.org
*/
#include "fixedint.h"
#include "sha512.h"
/* the K array */
static const uint64_t K[80] = {
UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd),
UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe),
UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1),
UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483),
UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210),
UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926),
UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8),
UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001),
UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910),
UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53),
UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60),
UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9),
UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6),
UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493),
UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817)
};
/* Various logical functions */
#define ROR64c(x, y) \
( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \
((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF))
#define STORE64H(x, y) \
{ (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \
(y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \
(y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \
(y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); }
#define LOAD64H(x, y) \
{ x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \
(((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \
(((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \
(((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); }
#define Ch(x,y,z) (z ^ (x & (y ^ z)))
#define Maj(x,y,z) (((x | y) & z) | (x & y))
#define S(x, n) ROR64c(x, n)
#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n))
#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39))
#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41))
#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7))
#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6))
#ifndef MIN
#define MIN(x, y) ( ((x)<(y))?(x):(y) )
#endif
/* compress 1024-bits */
static int sha512_compress(sha512_context *md, unsigned char *buf)
{
uint64_t S[8], W[80], t0, t1;
int i;
/* copy state into S */
for (i = 0; i < 8; i++) {
S[i] = md->state[i];
}
/* copy the state into 1024-bits into W[0..15] */
for (i = 0; i < 16; i++) {
LOAD64H(W[i], buf + (8*i));
}
/* fill W[16..79] */
for (i = 16; i < 80; i++) {
W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
}
/* Compress */
#define RND(a,b,c,d,e,f,g,h,i) \
t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
t1 = Sigma0(a) + Maj(a, b, c);\
d += t0; \
h = t0 + t1;
for (i = 0; i < 80; i += 8) {
RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0);
RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1);
RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2);
RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3);
RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4);
RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5);
RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6);
RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7);
}
#undef RND
/* feedback */
for (i = 0; i < 8; i++) {
md->state[i] = md->state[i] + S[i];
}
return 0;
}
/**
Initialize the hash state
@param md The hash state you wish to initialize
@return 0 if successful
*/
int sha512_init(sha512_context * md) {
if (md == NULL) return 1;
md->curlen = 0;
md->length = 0;
md->state[0] = UINT64_C(0x6a09e667f3bcc908);
md->state[1] = UINT64_C(0xbb67ae8584caa73b);
md->state[2] = UINT64_C(0x3c6ef372fe94f82b);
md->state[3] = UINT64_C(0xa54ff53a5f1d36f1);
md->state[4] = UINT64_C(0x510e527fade682d1);
md->state[5] = UINT64_C(0x9b05688c2b3e6c1f);
md->state[6] = UINT64_C(0x1f83d9abfb41bd6b);
md->state[7] = UINT64_C(0x5be0cd19137e2179);
return 0;
}
/**
Process a block of memory though the hash
@param md The hash state
@param in The data to hash
@param inlen The length of the data (octets)
@return 0 if successful
*/
int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen)
{
size_t n;
size_t i;
int err;
if (md == NULL) return 1;
if (in == NULL) return 1;
if (md->curlen > sizeof(md->buf)) {
return 1;
}
while (inlen > 0) {
if (md->curlen == 0 && inlen >= 128) {
if ((err = sha512_compress (md, (unsigned char *)in)) != 0) {
return err;
}
md->length += 128 * 8;
in += 128;
inlen -= 128;
} else {
n = MIN(inlen, (128 - md->curlen));
for (i = 0; i < n; i++) {
md->buf[i + md->curlen] = in[i];
}
md->curlen += n;
in += n;
inlen -= n;
if (md->curlen == 128) {
if ((err = sha512_compress (md, md->buf)) != 0) {
return err;
}
md->length += 8*128;
md->curlen = 0;
}
}
}
return 0;
}
/**
Terminate the hash to get the digest
@param md The hash state
@param out [out] The destination of the hash (64 bytes)
@return 0 if successful
*/
int sha512_final(sha512_context * md, unsigned char *out)
{
int i;
if (md == NULL) return 1;
if (out == NULL) return 1;
if (md->curlen >= sizeof(md->buf)) {
return 1;
}
/* increase the length of the message */
md->length += md->curlen * UINT64_C(8);
/* append the '1' bit */
md->buf[md->curlen++] = (unsigned char)0x80;
/* if the length is currently above 112 bytes we append zeros
* then compress. Then we can fall back to padding zeros and length
* encoding like normal.
*/
if (md->curlen > 112) {
while (md->curlen < 128) {
md->buf[md->curlen++] = (unsigned char)0;
}
sha512_compress(md, md->buf);
md->curlen = 0;
}
/* pad upto 120 bytes of zeroes
* note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash
* > 2^64 bits of data... :-)
*/
while (md->curlen < 120) {
md->buf[md->curlen++] = (unsigned char)0;
}
/* store length */
STORE64H(md->length, md->buf+120);
sha512_compress(md, md->buf);
/* copy output */
for (i = 0; i < 8; i++) {
STORE64H(md->state[i], out+(8*i));
}
return 0;
}
int sha512(const unsigned char *message, size_t message_len, unsigned char *out)
{
sha512_context ctx;
int ret;
if ((ret = sha512_init(&ctx))) return ret;
if ((ret = sha512_update(&ctx, message, message_len))) return ret;
if ((ret = sha512_final(&ctx, out))) return ret;
return 0;
}

21
lib/ed25519/sha512.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef SHA512_H
#define SHA512_H
#include <stddef.h>
#include "fixedint.h"
/* state */
typedef struct sha512_context_ {
uint64_t length, state[8];
size_t curlen;
unsigned char buf[128];
} sha512_context;
int sha512_init(sha512_context * md);
int sha512_final(sha512_context * md, unsigned char *out);
int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen);
int sha512(const unsigned char *message, size_t message_len, unsigned char *out);
#endif

31
lib/ed25519/sign.c Normal file
View File

@@ -0,0 +1,31 @@
#include "ed_25519.h"
#include "sha512.h"
#include "ge.h"
#include "sc.h"
void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) {
sha512_context hash;
unsigned char hram[64];
unsigned char r[64];
ge_p3 R;
sha512_init(&hash);
sha512_update(&hash, private_key + 32, 32);
sha512_update(&hash, message, message_len);
sha512_final(&hash, r);
sc_reduce(r);
ge_scalarmult_base(&R, r);
ge_p3_tobytes(signature, &R);
sha512_init(&hash);
sha512_update(&hash, signature, 32);
sha512_update(&hash, public_key, 32);
sha512_update(&hash, message, message_len);
sha512_final(&hash, hram);
sc_reduce(hram);
sc_muladd(signature + 32, hram, private_key, r);
}

77
lib/ed25519/verify.c Normal file
View File

@@ -0,0 +1,77 @@
#include "ed_25519.h"
#include "sha512.h"
#include "ge.h"
#include "sc.h"
static int consttime_equal(const unsigned char *x, const unsigned char *y) {
unsigned char r = 0;
r = x[0] ^ y[0];
#define F(i) r |= x[i] ^ y[i]
F(1);
F(2);
F(3);
F(4);
F(5);
F(6);
F(7);
F(8);
F(9);
F(10);
F(11);
F(12);
F(13);
F(14);
F(15);
F(16);
F(17);
F(18);
F(19);
F(20);
F(21);
F(22);
F(23);
F(24);
F(25);
F(26);
F(27);
F(28);
F(29);
F(30);
F(31);
#undef F
return !r;
}
int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) {
unsigned char h[64];
unsigned char checker[32];
sha512_context hash;
ge_p3 A;
ge_p2 R;
if (signature[63] & 224) {
return 0;
}
if (ge_frombytes_negate_vartime(&A, public_key) != 0) {
return 0;
}
sha512_init(&hash);
sha512_update(&hash, signature, 32);
sha512_update(&hash, public_key, 32);
sha512_update(&hash, message, message_len);
sha512_final(&hash, h);
sc_reduce(h);
ge_double_scalarmult_vartime(&R, h, &A, signature + 32);
ge_tobytes(checker, &R);
if (!consttime_equal(checker, signature)) {
return 0;
}
return 1;
}

21
license.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Scott Powell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

94
platformio.ini Normal file
View File

@@ -0,0 +1,94 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[arduino_base]
framework = arduino
monitor_speed = 115200
lib_deps =
SPI
Wire
jgromes/RadioLib @ ^6.3.0
rweather/Crypto @ ^0.4.0
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1
build_src_filter = +<*.cpp> +<helpers/*.cpp>
[esp32_base]
extends = arduino_base
platform = espressif32
monitor_filters = esp32_exception_decoder
build_src_filter = ${arduino_base.build_src_filter}
[esp32_S3]
extends = esp32_base
platform = espressif32
board = esp32-s3-devkitc-1
build_src_filter = ${esp32_base.build_src_filter}
build_flags =
${esp32_base.build_flags}
lib_deps =
${esp32_base.lib_deps}
[Heltec_stick_lite]
extends = esp32_base
board = heltec_wireless_stick_lite
[Heltec_lora32_v2]
extends = esp32_base
board = heltec_wifi_lora_32_V2
; ================
[Heltec_lora32_v3]
extends = esp32_base
board = esp32-s3-devkitc-1
build_flags =
${esp32_base.build_flags}
-D HELTEC_LORA_V3
build_src_filter = ${esp32_base.build_src_filter}
[env:Heltec_v3_ping_server]
extends = Heltec_lora32_v3
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/ping_server/main.cpp>
[env:Heltec_v3_ping_client]
extends = Heltec_lora32_v3
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/ping_client/main.cpp>
[env:Heltec_v3_repeater]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
; -D NODE_ID=2
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater/main.cpp>
[env:Heltec_v3_chat_alice]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
-D RUN_AS_ALICE=true
; -D NODE_ID=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_secure_chat/main.cpp>
[env:Heltec_v3_chat_bob]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
-D RUN_AS_ALICE=false
; -D NODE_ID=3
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_secure_chat/main.cpp>
[env:Heltec_v3_test_admin]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
; -D NODE_ID=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/test_admin/main.cpp>
; =============

29
src/Destination.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "Destination.h"
#include "Utils.h"
#include <string.h>
namespace mesh {
Destination::Destination(const Identity& identity, const char* name) {
uint8_t name_hash[MAX_HASH_SIZE];
Utils::sha256(name_hash, MAX_HASH_SIZE, (const uint8_t *)name, strlen(name));
Utils::sha256(hash, MAX_HASH_SIZE, name_hash, MAX_HASH_SIZE, identity.pub_key, PUB_KEY_SIZE);
}
Destination::Destination(const char* name) {
uint8_t name_hash[MAX_HASH_SIZE];
Utils::sha256(name_hash, MAX_HASH_SIZE, (const uint8_t *)name, strlen(name));
Utils::sha256(hash, MAX_HASH_SIZE, name_hash, MAX_HASH_SIZE);
}
Destination::Destination() {
memset(hash, 0, MAX_HASH_SIZE);
}
bool Destination::matches(const uint8_t* other_hash) {
return memcmp(hash, other_hash, MAX_HASH_SIZE) == 0;
}
}

25
src/Destination.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <MeshCore.h>
#include <Identity.h>
namespace mesh {
/**
* \brief Represents an end-point in the mesh, identified by a truncated SHA256 hash. (of DEST_HASH_SIZE)
* The hash is either from just a 'name' (C-string), and these can be thought of as 'broadcast' addresses,
* or can be the hash of name + Identity.public_key
*/
class Destination {
public:
uint8_t hash[MAX_HASH_SIZE];
Destination(const Identity& identity, const char* name);
Destination(const char* name);
Destination(const uint8_t desthash[]) { memcpy(hash, desthash, MAX_HASH_SIZE); }
Destination();
bool matches(const uint8_t* other_hash);
};
}

161
src/Dispatcher.cpp Normal file
View File

@@ -0,0 +1,161 @@
#include "Dispatcher.h"
namespace mesh {
void Dispatcher::begin() {
_radio->begin();
}
float Dispatcher::getAirtimeBudgetFactor() const {
return 5.0; // default, 16.6% (1/6th)
}
void Dispatcher::loop() {
if (outbound) { // waiting for outbound send to be completed
if (_radio->isSendComplete()) {
long t = _ms->getMillis() - outbound_start;
total_air_time += t; // keep track of how much air time we are using
//Serial.print(" airtime="); Serial.println(t);
// will need radio silence up to next_tx_time
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
_radio->onSendFinished();
onPacketSent(outbound);
outbound = NULL;
} else if (millisHasNowPassed(outbound_expiry)) {
MESH_DEBUG_PRINTLN("Dispatcher::loop(): WARNING: outbound packed send timed out!");
//Serial.println(" timed out");
_radio->onSendFinished();
releasePacket(outbound); // return to pool
outbound = NULL;
} else {
return; // can't do any more radio activity until send is complete or timed out
}
}
checkRecv();
checkSend();
}
void Dispatcher::onPacketSent(Packet* packet) {
releasePacket(packet); // default behaviour, return packet to pool
}
void Dispatcher::checkRecv() {
Packet* pkt;
{
uint8_t raw[MAX_TRANS_UNIT];
int len = _radio->recvRaw(raw, MAX_TRANS_UNIT);
if (len > 0) {
pkt = _mgr->allocNew();
if (pkt == NULL) {
MESH_DEBUG_PRINTLN("Dispatcher::checkRecv(): WARNING: received data, no unused packets available!");
} else {
int i = 0;
#ifdef NODE_ID
uint8_t sender_id = raw[i++];
if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1
} else {
_mgr->free(pkt); // put back into pool
return;
}
#endif
//Serial.print("LoRa recv: len="); Serial.println(len);
pkt->header = raw[i++];
pkt->path_len = raw[i++];
if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) {
MESH_DEBUG_PRINTLN("Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", len);
_mgr->free(pkt); // put back into pool
pkt = NULL;
} else {
memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len;
pkt->payload_len = len - i; // payload is remainder
memcpy(pkt->payload, &raw[i], pkt->payload_len);
}
}
} else {
pkt = NULL;
}
}
if (pkt) {
DispatcherAction action = onRecvPacket(pkt);
if (action == ACTION_RELEASE) {
_mgr->free(pkt);
} else if (action == ACTION_MANUAL_HOLD) {
// sub-class is wanting to manually hold Packet instance, and call releasePacket() at appropriate time
} else { // ACTION_RETRANSMIT*
uint8_t priority = (action >> 24) - 1;
uint32_t _delay = action & 0xFFFFFF;
_mgr->queueOutbound(pkt, priority, futureMillis(_delay));
}
}
}
void Dispatcher::checkSend() {
if (_mgr->getOutboundCount() == 0) return; // nothing waiting to send
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
if (_radio->isReceiving()) return; // check if radio is currently mid-receive
outbound = _mgr->getNextOutbound(_ms->getMillis());
if (outbound) {
int len = 0;
uint8_t raw[MAX_TRANS_UNIT];
#ifdef NODE_ID
raw[len++] = NODE_ID;
#endif
raw[len++] = outbound->header;
raw[len++] = outbound->path_len;
memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len;
if (len + outbound->payload_len > MAX_TRANS_UNIT) {
MESH_DEBUG_PRINTLN("Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", len + outbound->payload_len);
_mgr->free(outbound);
outbound = NULL;
} else {
memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len;
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
outbound_start = _ms->getMillis();
_radio->startSendRaw(raw, len);
outbound_expiry = futureMillis(max_airtime);
//Serial.print("LoRa send: len="); Serial.print(len);
}
}
}
Packet* Dispatcher::obtainNewPacket() {
return _mgr->allocNew(); // TODO: zero out all fields
}
void Dispatcher::releasePacket(Packet* packet) {
_mgr->free(packet);
}
void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) {
MESH_DEBUG_PRINTLN("Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", (uint32_t) packet->path_len, (uint32_t) packet->payload_len);
_mgr->free(packet);
} else {
_mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
}
}
// Utility function -- handles the case where millis() wraps around back to zero
// 2's complement arithmetic will handle any unsigned subtraction up to HALF the word size (32-bits in this case)
bool Dispatcher::millisHasNowPassed(unsigned long timestamp) const {
return (long)(_ms->getMillis() - timestamp) > 0;
}
unsigned long Dispatcher::futureMillis(int millis_from_now) const {
return _ms->getMillis() + millis_from_now;
}
}

129
src/Dispatcher.h Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include <MeshCore.h>
#include <Identity.h>
#include <Packet.h>
#include <Utils.h>
#include <string.h>
namespace mesh {
/**
* \brief Abstraction of local/volatile clock with Millisecond granularity.
*/
class MillisecondClock {
public:
virtual unsigned long getMillis() = 0;
};
/**
* \brief Abstraction of this device's packet radio.
*/
class Radio {
public:
virtual void begin() { }
/**
* \brief polls for incoming raw packet.
* \param bytes destination to store incoming raw packet.
* \param sz maximum packet size allowed.
* \returns 0 if no incoming data, otherwise length of complete packet received.
*/
virtual int recvRaw(uint8_t* bytes, int sz) = 0;
/**
* \returns estimated transmit air-time needed for packet of 'len_bytes', in milliseconds.
*/
virtual uint32_t getEstAirtimeFor(int len_bytes) = 0;
/**
* \brief starts the raw packet send. (no wait)
* \param bytes the raw packet data
* \param len the length in bytes
*/
virtual void startSendRaw(const uint8_t* bytes, int len) = 0;
/**
* \returns true if the previous 'startSendRaw()' completed successfully.
*/
virtual bool isSendComplete() = 0;
/**
* \brief a hook for doing any necessary clean up after transmit.
*/
virtual void onSendFinished() = 0;
/**
* \returns true if the radio is currently mid-receive of a packet.
*/
virtual bool isReceiving() { return false; }
};
/**
* \brief An abstraction for managing instances of Packets (eg. in a static pool),
* and for managing the outbound packet queue.
*/
class PacketManager {
public:
virtual Packet* allocNew() = 0;
virtual void free(Packet* packet) = 0;
virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority
virtual int getOutboundCount() const = 0;
virtual int getFreeCount() const = 0;
virtual Packet* getOutboundByIdx(int i) = 0;
virtual Packet* removeOutboundByIdx(int i) = 0;
};
typedef uint32_t DispatcherAction;
#define ACTION_RELEASE (0)
#define ACTION_MANUAL_HOLD (1)
#define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24)
#define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay))
/**
* \brief The low-level task that manages detecting incoming Packets, and the queueing
* and scheduling of outbound Packets.
*/
class Dispatcher {
Packet* outbound; // current outbound packet
unsigned long outbound_expiry, outbound_start, total_air_time;
unsigned long next_tx_time;
protected:
PacketManager* _mgr;
Radio* _radio;
MillisecondClock* _ms;
Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr)
: _radio(&radio), _ms(&ms), _mgr(&mgr)
{
outbound = NULL; total_air_time = 0; next_tx_time = 0;
}
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
virtual void onPacketSent(Packet* packet);
virtual float getAirtimeBudgetFactor() const;
public:
void begin();
void loop();
Packet* obtainNewPacket();
void releasePacket(Packet* packet);
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds
// helper methods
bool millisHasNowPassed(unsigned long timestamp) const;
unsigned long futureMillis(int millis_from_now) const;
private:
void checkRecv();
void checkSend();
};
}

70
src/Identity.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "Identity.h"
#include <string.h>
#define ED25519_NO_SEED 1
#include <ed_25519.h>
namespace mesh {
Identity::Identity() {
memset(pub_key, 0, sizeof(pub_key));
}
Identity::Identity(const char* pub_hex) {
Utils::fromHex(pub_key, PUB_KEY_SIZE, pub_hex);
}
bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const {
return ed25519_verify(sig, message, msg_len, pub_key);
}
bool Identity::readFrom(Stream& s) {
return (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
}
bool Identity::writeTo(Stream& s) const {
return (s.write(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
}
void Identity::printTo(Stream& s) const {
Utils::printHex(s, pub_key, PUB_KEY_SIZE);
}
LocalIdentity::LocalIdentity() {
memset(prv_key, 0, sizeof(prv_key));
}
LocalIdentity::LocalIdentity(const char* prv_hex, const char* pub_hex) : Identity(pub_hex) {
Utils::fromHex(prv_key, PRV_KEY_SIZE, prv_hex);
}
LocalIdentity::LocalIdentity(RNG* rng) {
uint8_t seed[SEED_SIZE];
rng->random(seed, SEED_SIZE);
ed25519_create_keypair(pub_key, prv_key, seed);
}
bool LocalIdentity::readFrom(Stream& s) {
bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
return success;
}
bool LocalIdentity::writeTo(Stream& s) const {
bool success = (s.write(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
success = success && (s.write(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
return success;
}
void LocalIdentity::printTo(Stream& s) const {
s.print("pub_key: "); Utils::printHex(s, pub_key, PUB_KEY_SIZE); s.println();
s.print("prv_key: "); Utils::printHex(s, prv_key, PRV_KEY_SIZE); s.println();
}
void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) const {
ed25519_sign(sig, message, msg_len, pub_key, prv_key);
}
void LocalIdentity::calcSharedSecret(uint8_t* secret, const Identity& other) {
ed25519_key_exchange(secret, other.pub_key, prv_key);
}
}

75
src/Identity.h Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
#include <Utils.h>
#include <Stream.h>
namespace mesh {
/**
* \brief An identity in the mesh, with given Ed25519 public key, ie. a party whose signatures can be VERIFIED.
*/
class Identity {
public:
uint8_t pub_key[PUB_KEY_SIZE];
Identity();
Identity(const char* pub_hex);
Identity(const uint8_t* _pub) { memcpy(pub_key, _pub, PUB_KEY_SIZE); }
int copyHashTo(uint8_t* dest) const {
memcpy(dest, pub_key, PATH_HASH_SIZE); // hash is just prefix of pub_key
return PATH_HASH_SIZE;
}
bool isHashMatch(const uint8_t* hash) const {
return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0;
}
/**
* \brief Performs Ed25519 signature verification.
* \param sig IN - must be SIGNATURE_SIZE buffer.
* \param message IN - the original message which was signed.
* \param msg_len IN - the length in bytes of message.
* \returns true, if signature is valid.
*/
bool verify(const uint8_t* sig, const uint8_t* message, int msg_len) const;
bool matches(const Identity& other) const { return memcmp(pub_key, other.pub_key, PUB_KEY_SIZE) == 0; }
bool matches(const uint8_t* other_pubkey) const { return memcmp(pub_key, other_pubkey, PUB_KEY_SIZE) == 0; }
bool readFrom(Stream& s);
bool writeTo(Stream& s) const;
void printTo(Stream& s) const;
};
/**
* \brief An Identity generated on THIS device, ie. with public/private Ed25519 key pair being on this device.
*/
class LocalIdentity : public Identity {
uint8_t prv_key[PRV_KEY_SIZE];
public:
LocalIdentity();
LocalIdentity(const char* prv_hex, const char* pub_hex);
LocalIdentity(RNG* rng); // create new random
/**
* \brief Ed25519 digital signature.
* \param sig OUT - must be SIGNATURE_SIZE buffer.
* \param message IN - the raw message bytes to sign.
* \param msg_len IN - the length in bytes of message.
*/
void sign(uint8_t* sig, const uint8_t* message, int msg_len) const;
/**
* \brief the ECDH key exhange, with Ed25519 public key transposed to Ex25519.
* \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes)
* \param other IN - the second party in the exchange.
*/
void calcSharedSecret(uint8_t* secret, const Identity& other);
bool readFrom(Stream& s);
bool writeTo(Stream& s) const;
void printTo(Stream& s) const;
};
}

392
src/Mesh.cpp Normal file
View File

@@ -0,0 +1,392 @@
#include "Mesh.h"
//#include <Arduino.h>
namespace mesh {
void Mesh::begin() {
Dispatcher::begin();
}
void Mesh::loop() {
Dispatcher::loop();
}
bool Mesh::allowPacketForward(mesh::Packet* packet) {
return false; // by default, Transport NOT enabled
}
int Mesh::searchPeersByHash(const uint8_t* hash) {
return 0; // not found
}
int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches) {
return 0; // not found
}
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): unsupported packet version");
return ACTION_RELEASE;
}
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
// remove our hash from 'path', then re-broadcast
pkt->path_len -= PATH_HASH_SIZE;
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len);
return ACTION_RETRANSMIT(0); // Routed traffic is HIGHEST priority
}
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
}
DispatcherAction action = ACTION_RELEASE;
switch (pkt->getPayloadType()) {
case PAYLOAD_TYPE_ACK: {
int i = 0;
uint32_t ack_crc;
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
if (i > pkt->payload_len) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete ACK packet");
} else {
onAckRecv(pkt, ack_crc);
action = routeRecvPacket(pkt);
}
break;
}
case PAYLOAD_TYPE_PATH:
case PAYLOAD_TYPE_REQ:
case PAYLOAD_TYPE_RESPONSE:
case PAYLOAD_TYPE_TXT_MSG: {
int i = 0;
uint8_t dest_hash = pkt->payload[i++];
uint8_t src_hash = pkt->payload[i++];
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
} else {
if (self_id.isHashMatch(&dest_hash)) {
// scan contacts DB, for all matching hashes of 'src_hash' (max 4 matches supported ATM)
int num = searchPeersByHash(&src_hash);
// for each matching contact, try to decrypt data
for (int j = 0; j < num; j++) {
uint8_t secret[PUB_KEY_SIZE];
getPeerSharedSecret(secret, j);
// decrypt, checking MAC is valid
uint8_t data[MAX_PACKET_PAYLOAD];
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
if (len > 0) { // success!
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
int k = 0;
uint8_t path_len = data[k++];
uint8_t* path = &data[k]; k += path_len;
uint8_t extra_type = data[k++];
uint8_t* extra = &data[k];
uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!)
onPeerPathRecv(pkt, j, path, path_len, extra_type, extra, extra_len);
} else {
onPeerDataRecv(pkt, pkt->getPayloadType(), j, data, len);
}
break;
}
}
}
action = routeRecvPacket(pkt);
}
break;
}
case PAYLOAD_TYPE_ANON_REQ: {
int i = 0;
uint8_t dest_hash = pkt->payload[i++];
uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE;
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
} else {
if (self_id.isHashMatch(&dest_hash)) {
Identity sender(sender_pub_key);
uint8_t secret[PUB_KEY_SIZE];
self_id.calcSharedSecret(secret, sender);
// decrypt, checking MAC is valid
uint8_t data[MAX_PACKET_PAYLOAD];
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
if (len > 0) { // success!
onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len);
}
}
action = routeRecvPacket(pkt);
}
break;
}
case PAYLOAD_TYPE_GRP_DATA:
case PAYLOAD_TYPE_GRP_TXT: {
int i = 0;
uint8_t channel_hash = pkt->payload[i++];
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
} else {
// scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM)
GroupChannel channels[2];
int num = searchChannelsByHash(&channel_hash, channels, 2);
// for each matching channel, try to decrypt data
for (int j = 0; j < num; j++) {
// decrypt, checking MAC is valid
uint8_t data[MAX_PACKET_PAYLOAD];
int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i);
if (len > 0) { // success!
onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len);
break;
}
}
action = routeRecvPacket(pkt);
}
break;
}
case PAYLOAD_TYPE_ADVERT: {
int i = 0;
Identity id;
memcpy(id.pub_key, &pkt->payload[i], PUB_KEY_SIZE); i += PUB_KEY_SIZE;
uint32_t timestamp;
memcpy(&timestamp, &pkt->payload[i], 4); i += 4;
uint8_t* signature = &pkt->payload[i]; i += SIGNATURE_SIZE;
if (i > pkt->payload_len) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete advertisement packet");
} else {
uint8_t* app_data = &pkt->payload[i];
int app_data_len = pkt->payload_len - i;
if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; }
// check that signature is valid
bool is_ok;
{
uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE];
int msg_len = 0;
memcpy(&message[msg_len], id.pub_key, PUB_KEY_SIZE); msg_len += PUB_KEY_SIZE;
memcpy(&message[msg_len], &timestamp, 4); msg_len += 4;
memcpy(&message[msg_len], app_data, app_data_len); msg_len += app_data_len;
is_ok = id.verify(signature, message, msg_len);
}
if (is_ok) {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): valid advertisement received!");
onAdvertRecv(pkt, id, timestamp, app_data, app_data_len);
action = routeRecvPacket(pkt);
} else {
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): received advertisement with forged signature!");
}
}
break;
}
default:
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): unknown payload type, header: %d", (int) pkt->header);
action = routeRecvPacket(pkt);
break;
}
return action;
}
DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
if (packet->isRouteFlood() && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) {
// append this node's hash to 'path'
packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]);
// as this propagates outwards, give it lower and lower priority
return ACTION_RETRANSMIT(packet->path_len); // give priority to closer sources, than ones further away
}
return ACTION_RELEASE;
}
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL;
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("Mesh::createAdvert(): error, packet pool empty");
return NULL;
}
packet->header = (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT); // ROUTE_TYPE_* is set later
packet->path_len = 0;
int len = 0;
memcpy(&packet->payload[len], id.pub_key, PUB_KEY_SIZE); len += PUB_KEY_SIZE;
uint32_t emitted_timestamp = _rtc->getCurrentTime();
memcpy(&packet->payload[len], &emitted_timestamp, 4); len += 4;
uint8_t* signature = &packet->payload[len]; len += SIGNATURE_SIZE; // will fill this in later
memcpy(&packet->payload[len], app_data, app_data_len); len += app_data_len;
packet->payload_len = len;
{
uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE];
int msg_len = 0;
memcpy(&message[msg_len], id.pub_key, PUB_KEY_SIZE); msg_len += PUB_KEY_SIZE;
memcpy(&message[msg_len], &emitted_timestamp, 4); msg_len += 4;
memcpy(&message[msg_len], app_data, app_data_len); msg_len += app_data_len;
id.sign(signature, message, msg_len);
}
return packet;
}
#define MAX_COMBINED_PATH (MAX_PACKET_PAYLOAD - 2 - CIPHER_BLOCK_SIZE)
Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) {
if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!!
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("Mesh::createPathReturn(): error, packet pool empty");
return NULL;
}
packet->header = (PAYLOAD_TYPE_PATH << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
packet->path_len = 0;
int len = 0;
len += dest.copyHashTo(&packet->payload[len]); // dest hash
len += self_id.copyHashTo(&packet->payload[len]); // src hash
{
int data_len = 0;
uint8_t data[MAX_PACKET_PAYLOAD];
data[data_len++] = path_len;
memcpy(&data[data_len], path, path_len); data_len += path_len;
if (extra_len > 0) {
data[data_len++] = extra_type;
memcpy(&data[data_len], extra, extra_len); data_len += extra_len;
} else {
// append a timestamp, or random blob (to make packet_hash unique)
data[data_len++] = 0xFF; // dummy payload type
getRNG()->random(&data[data_len], 4); data_len += 4;
}
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
}
packet->payload_len = len;
return packet;
}
Packet* Mesh::createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len) {
if (type == PAYLOAD_TYPE_ANON_REQ) {
if (data_len + 1 + PUB_KEY_SIZE + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
} else if (type == PAYLOAD_TYPE_TXT_MSG || type == PAYLOAD_TYPE_REQ || type == PAYLOAD_TYPE_RESPONSE) {
if (data_len + 2 + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL;
} else {
return NULL; // invalid type
}
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("Mesh::createDatagram(): error, packet pool empty");
return NULL;
}
packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
packet->path_len = 0;
int len = 0;
if (type == PAYLOAD_TYPE_ANON_REQ) {
len += dest.copyHashTo(&packet->payload[len]); // dest hash
memcpy(&packet->payload[len], self_id.pub_key, PUB_KEY_SIZE); len += PUB_KEY_SIZE; // sender pub_key
} else {
len += dest.copyHashTo(&packet->payload[len]); // dest hash
len += self_id.copyHashTo(&packet->payload[len]); // src hash
}
len += Utils::encryptThenMAC(secret, &packet->payload[len], data, data_len);
packet->payload_len = len;
return packet;
}
Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len) {
if (!(type == PAYLOAD_TYPE_GRP_TXT || type == PAYLOAD_TYPE_GRP_DATA)) return NULL; // invalid type
if (data_len + 1 + CIPHER_BLOCK_SIZE-1 > MAX_PACKET_PAYLOAD) return NULL; // too long
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("Mesh::createGroupDatagram(): error, packet pool empty");
return NULL;
}
packet->header = (type << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
packet->path_len = 0;
int len = 0;
memcpy(&packet->payload[len], channel.hash, PATH_HASH_SIZE); len += PATH_HASH_SIZE;
len += Utils::encryptThenMAC(channel.secret, &packet->payload[len], data, data_len);
packet->payload_len = len;
return packet;
}
Packet* Mesh::createAck(uint32_t ack_crc) {
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("Mesh::createAck(): error, packet pool empty");
return NULL;
}
packet->header = (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
packet->path_len = 0;
memcpy(packet->payload, &ack_crc, 4);
packet->payload_len = 4;
return packet;
}
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
packet->header &= ~PH_ROUTE_MASK;
packet->header |= ROUTE_TYPE_FLOOD;
allowPacketForward(packet); // mark this packet as already sent in case it is rebroadcast back to us
uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
pri = 2;
} else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
pri = 3; // de-prioritie these
} else {
pri = 1;
}
sendPacket(packet, pri, delay_millis);
}
void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) {
packet->header &= ~PH_ROUTE_MASK;
packet->header |= ROUTE_TYPE_DIRECT;
memcpy(packet->path, path, packet->path_len = path_len);
allowPacketForward(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
packet->header &= ~PH_ROUTE_MASK;
packet->header |= ROUTE_TYPE_DIRECT;
packet->path_len = 0; // path_len of zero means Zero Hop
allowPacketForward(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
}

151
src/Mesh.h Normal file
View File

@@ -0,0 +1,151 @@
#pragma once
#include <Dispatcher.h>
namespace mesh {
/**
* An abstraction of the device's Realtime Clock.
*/
class RTCClock {
public:
/**
* \returns the current time. in UNIX epoch seconds.
*/
virtual uint32_t getCurrentTime() = 0;
/**
* \param time current time in UNIX epoch seconds.
*/
virtual void setCurrentTime(uint32_t time) = 0;
};
class GroupChannel {
public:
uint8_t hash[PATH_HASH_SIZE];
uint8_t secret[PUB_KEY_SIZE];
};
/**
* \brief The next layer in the basic Dispatcher task, Mesh recognises the particular Payload TYPES,
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
*/
class Mesh : public Dispatcher {
RTCClock* _rtc;
RNG* _rng;
protected:
DispatcherAction onRecvPacket(Packet* pkt) override;
/**
* \brief Decide what to do with received packet, ie. discard, forward, or hold
*/
DispatcherAction routeRecvPacket(Packet* packet);
/**
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
*/
virtual bool allowPacketForward(Packet* packet);
/**
* \brief Perform search of local DB of peers/contacts.
* \returns Number of peers with matching hash
*/
virtual int searchPeersByHash(const uint8_t* hash);
/**
* \brief lookup the ECDH shared-secret between this node and peer by idx (calculate if necessary)
* \param dest_secret destination array to copy the secret (must be PUB_KEY_SIZE bytes)
* \param peer_idx index of peer, [0..n) where n is what searchPeersByHash() returned
*/
virtual void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { }
/**
* \brief A (now decrypted) data packet has been received (by a known peer).
* NOTE: these can be received multiple times (per sender/msg-id), via different routes
* \param type one of: PAYLOAD_TYPE_TXT_MSG, PAYLOAD_TYPE_REQ, PAYLOAD_TYPE_RESPONSE
* \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned
*/
virtual void onPeerDataRecv(Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) { }
/**
* \brief A path TO peer (sender_idx) has been received. (also with optional 'extra' data encoded)
* NOTE: these can be received multiple times (per sender), via differen routes
* \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned
*/
virtual void onPeerPathRecv(Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
virtual int searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches);
/**
* \brief A new incoming Advertisement has been received.
* NOTE: these can be received multiple times (per id/timestamp), via different routes
*/
virtual void onAdvertRecv(Packet* packet, const Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { }
/**
* \brief A (now decrypted) data packet has been received.
* NOTE: these can be received multiple times (per sender/contents), via different routes
* \param type one of: PAYLOAD_TYPE_ANON_REQ
* \param sender public key provided by sender
*/
virtual void onAnonDataRecv(Packet* packet, uint8_t type, const Identity& sender, uint8_t* data, size_t len) { }
/**
* \brief A path TO 'sender' has been received. (also with optional 'extra' data encoded)
* NOTE: these can be received multiple times (per sender), via differen routes
*/
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
/**
* \brief An encrypted group data packet has been received.
* NOTE: the same payload can be received multiple times, via different routes
* \param type one of: PAYLOAD_TYPE_GRP_TXT, PAYLOAD_TYPE_GRP_DATA
* \param channel the matching GroupChannel
*/
virtual void onGroupDataRecv(Packet* packet, uint8_t type, const GroupChannel& channel, uint8_t* data, size_t len) { }
/**
* \brief A simple ACK packet has been received.
* NOTE: same ACK can be received multiple times, via different routes
*/
virtual void onAckRecv(Packet* packet, uint32_t ack_crc) { }
Mesh(Radio& radio, MillisecondClock& ms, RNG& rng, RTCClock& rtc, PacketManager& mgr)
: Dispatcher(radio, ms, mgr), _rng(&rng), _rtc(&rtc)
{
}
public:
void begin();
void loop();
LocalIdentity self_id;
RNG* getRNG() const { return _rng; }
RTCClock* getRTCClock() const { return _rtc; }
Packet* createAdvert(const LocalIdentity& id, const uint8_t* app_data=NULL, size_t app_data_len=0);
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len);
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
Packet* createAck(uint32_t ack_crc);
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
/**
* \brief send a locally-generated Packet with flood routing
*/
void sendFlood(Packet* packet, uint32_t delay_millis=0);
/**
* \brief send a locally-generated Packet with Direct routing
*/
void sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis=0);
/**
* \brief send a locally-generated Packet to just neigbor nodes (zero hops)
*/
void sendZeroHop(Packet* packet, uint32_t delay_millis=0);
};
}

46
src/MeshCore.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <stdint.h>
#define MAX_HASH_SIZE 8
#define PUB_KEY_SIZE 32
#define PRV_KEY_SIZE 64
#define SEED_SIZE 32
#define SIGNATURE_SIZE 64
#define MAX_ADVERT_DATA_SIZE 32
#define CIPHER_KEY_SIZE 16
#define CIPHER_BLOCK_SIZE 16
// V1
#define CIPHER_MAC_SIZE 2
#define PATH_HASH_SIZE 1
#define MAX_PACKET_PAYLOAD 184
#define MAX_PATH_SIZE 64
#define MAX_TRANS_UNIT 255
#if MESH_DEBUG && ARDUINO
#include <Arduino.h>
#define MESH_DEBUG_PRINT(...) Serial.printf(__VA_ARGS__)
#define MESH_DEBUG_PRINTLN(F, ...) Serial.printf(F "\n", ##__VA_ARGS__)
#else
#define MESH_DEBUG_PRINT(...) {}
#define MESH_DEBUG_PRINTLN(...) {}
#endif
namespace mesh {
#define BD_STARTUP_NORMAL 0 // getStartupReason() codes
#define BD_STARTUP_RX_PACKET 1
class MainBoard {
public:
virtual uint16_t getBattMilliVolts() = 0;
virtual const char* getManufacturerName() const = 0;
virtual void onBeforeTransmit() { }
virtual void onAfterTransmit() { }
virtual void reboot() = 0;
virtual uint8_t getStartupReason() const = 0;
};
}

16
src/MeshTables.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <Mesh.h>
namespace mesh {
/**
* An abstraction of the data tables needed to be maintained, for the routing engine.
*/
class MeshTables {
public:
virtual bool hasForwarded(const uint8_t* packet_hash) const = 0;
virtual void setHasForwarded(const uint8_t* packet_hash) = 0;
};
}

23
src/Packet.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "Packet.h"
#include <string.h>
#include <SHA256.h>
namespace mesh {
Packet::Packet() {
header = 0;
path_len = 0;
payload_len = 0;
}
void Packet::calculatePacketHash(uint8_t* hash) const {
SHA256 sha;
uint8_t t = getPayloadType();
sha.update(&t, 1);
sha.update(payload, payload_len);
sha.finalize(hash, MAX_HASH_SIZE);
}
}

73
src/Packet.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <MeshCore.h>
namespace mesh {
// Packet::header values
#define PH_ROUTE_MASK 0x03 // 2-bits
#define PH_TYPE_SHIFT 2
#define PH_TYPE_MASK 0x0F // 4-bits
#define PH_VER_SHIFT 6
#define PH_VER_MASK 0x03 // 2-bits
#define ROUTE_TYPE_RESERVED1 0x00 // FUTURE
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
#define ROUTE_TYPE_RESERVED2 0x03 // FUTURE
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack
#define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
//...
#define PAYLOAD_TYPE_RESERVEDM 0x0F // FUTURE
#define PAYLOAD_VER_1 0x00 // 1-byte src/dest hashes, 2-byte MAC
#define PAYLOAD_VER_2 0x01 // FUTURE (eg. 2-byte hashes, 4-byte MAC ??)
#define PAYLOAD_VER_3 0x02 // FUTURE
#define PAYLOAD_VER_4 0x03 // FUTURE
/**
* \brief The fundamental transmission unit.
*/
class Packet {
public:
Packet();
uint8_t header;
uint16_t payload_len, path_len;
uint8_t path[MAX_PATH_SIZE];
uint8_t payload[MAX_PACKET_PAYLOAD];
/**
* \brief calculate the hash of payload + type
* \param dest_hash destination to store the hash (must be MAX_HASH_SIZE bytes)
*/
void calculatePacketHash(uint8_t* dest_hash) const;
/**
* \returns one of ROUTE_ values
*/
uint8_t getRouteType() const { return header & PH_ROUTE_MASK; }
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD; }
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT; }
/**
* \returns one of PAYLOAD_TYPE_ values
*/
uint8_t getPayloadType() const { return (header >> PH_TYPE_SHIFT) & PH_TYPE_MASK; }
/**
* \returns one of PAYLOAD_VER_ values
*/
uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; }
};
}

149
src/Utils.cpp Normal file
View File

@@ -0,0 +1,149 @@
#include "Utils.h"
#include <AES.h>
#include <SHA256.h>
#ifdef ARDUINO
#include <Arduino.h>
#endif
namespace mesh {
uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) {
uint32_t num;
random((uint8_t *) &num, sizeof(num));
return (num % (_max - _min)) + _min;
}
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) {
SHA256 sha;
sha.update(msg, msg_len);
sha.finalize(hash, hash_len);
}
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) {
SHA256 sha;
sha.update(frag1, frag1_len);
sha.update(frag2, frag2_len);
sha.finalize(hash, hash_len);
}
int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
AES128 aes;
uint8_t* dp = dest;
const uint8_t* sp = src;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (sp - src < src_len) {
aes.decryptBlock(dp, sp);
dp += 16; sp += 16;
}
return sp - src; // will always be multiple of 16
}
int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
AES128 aes;
uint8_t* dp = dest;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (src_len >= 16) {
aes.encryptBlock(dp, src);
dp += 16; src += 16; src_len -= 16;
}
if (src_len > 0) { // remaining partial block
uint8_t tmp[16];
memset(tmp, 0, 16);
memcpy(tmp, src, src_len);
aes.encryptBlock(dp, tmp);
dp += 16;
}
return dp - dest; // will always be multiple of 16
}
int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len);
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(dest + CIPHER_MAC_SIZE, enc_len);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE);
return CIPHER_MAC_SIZE + enc_len;
}
int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes
uint8_t hmac[CIPHER_MAC_SIZE];
{
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE);
}
if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) {
return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
}
return 0; // invalid HMAC
}
static const char hex_chars[] = "0123456789ABCDEF";
void Utils::toHex(char* dest, const uint8_t* src, size_t len) {
while (len > 0) {
uint8_t b = *src++;
*dest++ = hex_chars[b >> 4];
*dest++ = hex_chars[b & 0x0F];
len--;
}
*dest = 0;
}
void Utils::printHex(Stream& s, const uint8_t* src, size_t len) {
while (len > 0) {
uint8_t b = *src++;
s.print(hex_chars[b >> 4]);
s.print(hex_chars[b & 0x0F]);
len--;
}
}
static uint8_t hexVal(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0';
return 0;
}
bool Utils::fromHex(uint8_t* dest, int dest_size, const char *src_hex) {
int len = strlen(src_hex);
if (len != dest_size*2) return false; // incorrect length
uint8_t* dp = dest;
while (dp - dest < dest_size) {
char ch = *src_hex++;
char cl = *src_hex++;
*dp++ = (hexVal(ch) << 4) | hexVal(cl);
}
return true;
}
int Utils::parseTextParts(char* text, const char* parts[], int max_num, char separator) {
int num = 0;
char* sp = text;
while (*sp && num < max_num) {
parts[num++] = sp;
while (*sp && *sp != separator) sp++;
if (*sp) {
*sp++ = 0; // replace the seperator with a null, and skip past it
}
}
// if we hit the maximum parts, make sure LAST entry does NOT have separator
while (*sp && *sp != separator) sp++;
if (*sp) {
*sp = 0; // replace the separator with null
}
return num;
}
}

81
src/Utils.h Normal file
View File

@@ -0,0 +1,81 @@
#pragma once
#include <MeshCore.h>
#include <Stream.h>
#include <string.h>
namespace mesh {
class RNG {
public:
virtual void random(uint8_t* dest, size_t sz) = 0;
uint32_t nextInt(uint32_t _min, uint32_t _max);
};
class Utils {
public:
/**
* \brief calculates the SHA256 hash of 'msg', storing in 'hash' and truncating the hash to 'hash_len' bytes.
*/
static void sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len);
/**
* \brief calculates the SHA256 hash of two fragments, 'frag1' and 'frag2' (in that order), storing in 'hash' and truncating.
*/
static void sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len);
/**
* \brief Encrypts the 'src' bytes using AES128 cipher, using 'shared_secret' as key, with key length fixed at CIPHER_KEY_SIZE.
* Final block is padded with zero bytes before encrypt. Result stored in 'dest'.
* \returns The length in bytes put into 'dest'. (rounded up to block size)
*/
static int encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
/**
* \brief Decrypt the 'src' bytes using AES128 cipher, using 'shared_secret' as key, with key length fixed at CIPHER_KEY_SIZE.
* 'src_len' should be multiple of block size, as returned by 'encrypt()'.
* \returns The length in bytes put into 'dest'. (dest may contain trailing zero bytes in final block)
*/
static int decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
/**
* \brief encrypts bytes in src, then calculates MAC on ciphertext, inserting into leading bytes of 'dest'.
* \returns total length of bytes in 'dest' (MAC + ciphertext)
*/
static int encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
/**
* \brief checks the MAC (in leading bytes of 'src'), then if valid, decrypts remaining bytes in src.
* \returns zero if MAC is invalid, otherwise the length of decrypted bytes in 'dest'
*/
static int MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
/**
* \brief converts 'src' bytes with given length to Hex representation, and null terminates.
*/
static void toHex(char* dest, const uint8_t* src, size_t len);
/**
* \brief converts 'src_hex' hexadecimal string (should be null term) back to raw bytes, storing in 'dest'.
* \param dest_size must be exactly the expected size in bytes.
* \returns true if successful
*/
static bool fromHex(uint8_t* dest, int dest_size, const char *src_hex);
/**
* \brief Prints the hexadecimal representation of 'src' bytes of given length, to Stream 's'.
*/
static void printHex(Stream& s, const uint8_t* src, size_t len);
/**
* \brief parse 'text' into parts separated by 'separator' char.
* \param text the text to parse (note is MODIFIED!)
* \param parts destination array to store pointers to starts of parse parts
* \param max_num max elements to store in 'parts' array
* \param separator the separator character
* \returns the number of parts parsed (in 'parts')
*/
static int parseTextParts(char* text, const char* parts[], int max_num, char separator=',');
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <Mesh.h>
#include <Arduino.h>
class VolatileRTCClock : public mesh::RTCClock {
long millis_offset;
public:
VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm
uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); }
void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; }
};
class ArduinoMillis : public mesh::MillisecondClock {
public:
unsigned long getMillis() override { return millis(); }
};
class StdRNG : public mesh::RNG {
public:
void begin(long seed) { randomSeed(seed); }
void random(uint8_t* dest, size_t sz) override {
for (int i = 0; i < sz; i++) {
dest[i] = (::random(0, 256) & 0xFF);
}
}
};

View File

@@ -0,0 +1,16 @@
#pragma once
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
class CustomSX1262 : public SX1262 {
public:
CustomSX1262(Module *mod) : SX1262(mod) { }
bool isReceiving() {
uint16_t irq = getIrqStatus();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
return hasPreamble;
}
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include "CustomSX1262.h"
#include "RadioLibWrappers.h"
class CustomSX1262Wrapper : public RadioLibWrapper {
public:
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
bool isReceiving() override { return ((CustomSX1262 *)_radio)->isReceiving(); }
float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); }
};

View File

@@ -0,0 +1,16 @@
#pragma once
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
class CustomSX1268 : public SX1268 {
public:
CustomSX1268(Module *mod) : SX1268(mod) { }
bool isReceiving() {
uint16_t irq = getIrqStatus();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
return hasPreamble;
}
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include "CustomSX1268.h"
#include "RadioLibWrappers.h"
class CustomSX1268Wrapper : public RadioLibWrapper {
public:
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
bool isReceiving() override { return ((CustomSX1268 *)_radio)->isReceiving(); }
float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); }
};

48
src/helpers/ESP32Board.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
#if defined(ESP_PLATFORM)
#include <rom/rtc.h>
#include <sys/time.h>
class ESP32Board : public mesh::MainBoard { // abstract class
public:
void begin() {
// for future use, sub-classes SHOULD call this from their begin()
}
void reboot() override {
esp_restart();
}
};
class ESP32RTCClock : public mesh::RTCClock {
public:
ESP32RTCClock() { }
void begin() {
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_POWERON) {
// start with some date/time in the recent past
struct timeval tv;
tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
}
uint32_t getCurrentTime() override {
time_t _now;
time(&_now);
return _now;
}
void setCurrentTime(uint32_t time) override {
struct timeval tv;
tv.tv_sec = time;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
};
#endif

View File

@@ -0,0 +1,91 @@
#pragma once
#include "ESP32Board.h"
#include <Arduino.h>
// LoRa radio module pins for Heltec V3
#define P_LORA_DIO_1 14
#define P_LORA_NSS 8
#define P_LORA_RESET RADIOLIB_NC
#define P_LORA_BUSY 13
#define P_LORA_SCLK 9
#define P_LORA_MISO 11
#define P_LORA_MOSI 10
// built-ins
#define PIN_VBAT_READ 1
#define PIN_ADC_CTRL 37
#define PIN_ADC_CTRL_ACTIVE LOW
#define PIN_ADC_CTRL_INACTIVE HIGH
#define PIN_LED_BUILTIN 35
#include <driver/rtc_io.h>
class HeltecV3Board : public ESP32Board {
uint8_t startup_reason;
public:
void begin() {
startup_reason = BD_STARTUP_NORMAL;
ESP32Board::begin();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
// battery read support
pinMode(PIN_VBAT_READ, INPUT);
adcAttachPin(PIN_VBAT_READ);
analogReadResolution(10);
pinMode(PIN_ADC_CTRL, OUTPUT);
}
uint8_t getStartupReason() const { return startup_reason; }
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
uint16_t getBattMilliVolts() override {
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_ACTIVE);
uint32_t raw = 0;
for (int i = 0; i < 8; i++) {
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / 8;
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_INACTIVE);
return (5.2 * (3.3 / 1024.0) * raw) * 1000;
}
const char* getManufacturerName() const override {
return "Heltec V3";
}
};

View File

@@ -0,0 +1,27 @@
#include "IdentityStore.h"
bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) {
bool loaded = false;
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
if (_fs->exists(filename)) {
File file = _fs->open(filename);
if (file) {
loaded = id.readFrom(file);
file.close();
}
}
return loaded;
}
bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
File file = _fs->open(filename, "w", true);
if (file) {
id.writeTo(file);
file.close();
return true;
}
return false;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <FS.h>
#include <Identity.h>
class IdentityStore {
fs::FS* _fs;
const char* _dir;
public:
IdentityStore(fs::FS& fs, const char* dir): _fs(&fs), _dir(dir) { }
void begin() { _fs->mkdir(_dir); }
bool load(const char *name, mesh::LocalIdentity& id);
bool save(const char *name, const mesh::LocalIdentity& id);
};

View File

@@ -0,0 +1,93 @@
#define RADIOLIB_STATIC_ONLY 1
#include "RadioLibWrappers.h"
#define STATE_IDLE 0
#define STATE_RX 1
#define STATE_TX_WAIT 3
#define STATE_TX_DONE 4
#define STATE_INT_READY 16
static volatile uint8_t state = STATE_IDLE;
// this function is called when a complete packet
// is transmitted by the module
static
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void) {
// we sent a packet, set the flag
state |= STATE_INT_READY;
}
void RadioLibWrapper::begin() {
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt
state = STATE_IDLE;
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
setFlag(); // LoRa packet is already received
}
}
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
if (state & STATE_INT_READY) {
int len = _radio->getPacketLength();
if (len > 0) {
if (len > sz) { len = sz; }
int err = _radio->readData(bytes, len);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData()");
} else {
// Serial.print(" readData() -> "); Serial.println(len);
}
n_recv++;
}
state = STATE_IDLE; // need another startReceive()
return len;
}
if (state != STATE_RX) {
int err = _radio->startReceive();
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive()");
}
state = STATE_RX;
}
return 0;
}
uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
return _radio->getTimeOnAir(len_bytes) / 1000;
}
void RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
state = STATE_TX_WAIT;
_board->onBeforeTransmit();
int err = _radio->startTransmit((uint8_t *) bytes, len);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit()");
}
}
bool RadioLibWrapper::isSendComplete() {
if (state & STATE_INT_READY) {
state = STATE_IDLE;
n_sent++;
return true;
}
return false;
}
void RadioLibWrapper::onSendFinished() {
_radio->finishTransmit();
_board->onAfterTransmit();
state = STATE_IDLE;
}
float RadioLibWrapper::getLastRSSI() const {
return _radio->getRSSI();
}
float RadioLibWrapper::getLastSNR() const {
return _radio->getSNR();
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <Mesh.h>
#include <RadioLib.h>
class RadioLibWrapper : public mesh::Radio {
protected:
PhysicalLayer* _radio;
mesh::MainBoard* _board;
uint32_t n_recv, n_sent;
public:
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
void begin() override;
int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override;
void startSendRaw(const uint8_t* bytes, int len) override;
bool isSendComplete() override;
void onSendFinished() override;
uint32_t getPacketsRecv() const { return n_recv; }
uint32_t getPacketsSent() const { return n_sent; }
virtual float getLastRSSI() const;
virtual float getLastSNR() const;
};
/**
* \brief an RNG impl using the noise from the LoRa radio as entropy.
* NOTE: this is VERY SLOW! Use only for things like creating new LocalIdentity
*/
class RadioNoiseListener : public mesh::RNG {
PhysicalLayer* _radio;
public:
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
void random(uint8_t* dest, size_t sz) override {
for (int i = 0; i < sz; i++) {
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
}
}
};

View File

@@ -0,0 +1,56 @@
#pragma once
#include <MeshTables.h>
#ifdef ESP32
#include <FS.h>
#endif
#define MAX_PACKET_HASHES 64
class SimpleMeshTables : public mesh::MeshTables {
uint8_t _fwd_hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
int _next_fwd_idx;
int lookupHashIndex(const uint8_t* hash) const {
const uint8_t* sp = _fwd_hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) return i;
}
return -1;
}
public:
SimpleMeshTables() {
memset(_fwd_hashes, 0, sizeof(_fwd_hashes));
_next_fwd_idx = 0;
}
#ifdef ESP32
void restoreFrom(File f) {
f.read(_fwd_hashes, sizeof(_fwd_hashes));
f.read((uint8_t *) &_next_fwd_idx, sizeof(_next_fwd_idx));
}
void saveTo(File f) {
f.write(_fwd_hashes, sizeof(_fwd_hashes));
f.write((const uint8_t *) &_next_fwd_idx, sizeof(_next_fwd_idx));
}
#endif
bool hasForwarded(const uint8_t* packet_hash) const override {
int i = lookupHashIndex(packet_hash);
return i >= 0;
}
void setHasForwarded(const uint8_t* packet_hash) override {
int i = lookupHashIndex(packet_hash);
if (i >= 0) {
// already in table
} else {
memcpy(&_fwd_hashes[_next_fwd_idx*MAX_HASH_SIZE], packet_hash, MAX_HASH_SIZE);
_next_fwd_idx = (_next_fwd_idx + 1) % MAX_PACKET_HASHES; // cyclic table
}
}
};

View File

@@ -0,0 +1,33 @@
#pragma once
#include <Packet.h>
#include <string.h>
#define MAX_PACKET_HASHES 64
class SimpleSeenTable {
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
int _next_idx;
public:
SimpleSeenTable() {
memset(_hashes, 0, sizeof(_hashes));
_next_idx = 0;
}
bool hasSeenPacket(const mesh::Packet* packet) {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
const uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) return true;
}
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
return false;
}
};

View File

@@ -0,0 +1,97 @@
#include "StaticPoolPacketManager.h"
PacketQueue::PacketQueue(int max_entries) {
_table = new mesh::Packet*[max_entries];
_pri_table = new uint8_t[max_entries];
_schedule_table = new uint32_t[max_entries];
_size = max_entries;
_num = 0;
}
mesh::Packet* PacketQueue::get(uint32_t now) {
uint8_t min_pri = 0xFF;
int best_idx = -1;
for (int j = 0; j < _num; j++) {
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries
min_pri = _pri_table[j];
best_idx = j;
}
}
if (best_idx < 0) return NULL; // empty, or all items are still in the future
mesh::Packet* top = _table[best_idx];
int i = best_idx;
_num--;
while (i < _num) {
_table[i] = _table[i+1];
_pri_table[i] = _pri_table[i+1];
_schedule_table[i] = _schedule_table[i+1];
i++;
}
return top;
}
mesh::Packet* PacketQueue::removeByIdx(int i) {
if (i >= _num) return NULL; // invalid index
mesh::Packet* item = _table[i];
_num--;
while (i < _num) {
_table[i] = _table[i+1];
_pri_table[i] = _pri_table[i+1];
_schedule_table[i] = _schedule_table[i+1];
i++;
}
return item;
}
void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
if (_num == _size) {
// TODO: log "FATAL: queue is full!"
return;
}
_table[_num] = packet;
_pri_table[_num] = priority;
_schedule_table[_num] = scheduled_for;
_num++;
}
StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size) {
// load up our unusued Packet pool
for (int i = 0; i < pool_size; i++) {
unused.add(new mesh::Packet(), 0, 0);
}
}
mesh::Packet* StaticPoolPacketManager::allocNew() {
return unused.removeByIdx(0); // just get first one (returns NULL if empty)
}
void StaticPoolPacketManager::free(mesh::Packet* packet) {
unused.add(packet, 0, 0);
}
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
send_queue.add(packet, priority, scheduled_for);
}
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
//send_queue.sort(); // sort by scheduled_for/priority first
return send_queue.get(now);
}
int StaticPoolPacketManager::getOutboundCount() const {
return send_queue.count();
}
int StaticPoolPacketManager::getFreeCount() const {
return unused.count();
}
mesh::Packet* StaticPoolPacketManager::getOutboundByIdx(int i) {
return send_queue.itemAt(i);
}
mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) {
return send_queue.removeByIdx(i);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <Dispatcher.h>
class PacketQueue {
mesh::Packet** _table;
uint8_t* _pri_table;
uint32_t* _schedule_table;
int _size, _num;
public:
PacketQueue(int max_entries);
mesh::Packet* get(uint32_t now);
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
int count() const { return _num; }
mesh::Packet* itemAt(int i) const { return _table[i]; }
mesh::Packet* removeByIdx(int i);
};
class StaticPoolPacketManager : public mesh::PacketManager {
PacketQueue unused, send_queue;
public:
StaticPoolPacketManager(int pool_size);
mesh::Packet* allocNew() override;
void free(mesh::Packet* packet) override;
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
mesh::Packet* getNextOutbound(uint32_t now) override;
int getOutboundCount() const override;
int getFreeCount() const override;
mesh::Packet* getOutboundByIdx(int i) override;
mesh::Packet* removeOutboundByIdx(int i) override;
};