mirror of https://github.com/meshcore-dev/MeshCore
commit
6c7efdd0f6
59 changed files with 8604 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||
|
.pio |
||||
|
.vscode/.browse.c_cpp.db* |
||||
|
.vscode/c_cpp_properties.json |
||||
|
.vscode/launch.json |
||||
|
.vscode/ipch |
||||
@ -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" |
||||
|
] |
||||
|
} |
||||
@ -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. |
||||
|
|
||||
@ -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(); |
||||
|
} |
||||
@ -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(×tamp, 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
|
||||
|
} |
||||
@ -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(×tamp, 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(×tamp, 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
|
||||
|
} |
||||
@ -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(×tamp, 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, ×tamp, 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(); |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
@ -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 |
||||
@ -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 |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
File diff suppressed because it is too large
@ -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 |
||||
@ -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 |
||||
@ -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; |
||||
|
} |
||||
@ -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 |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
Copyright (c) 2015 Orson Peters <[email protected]> |
||||
|
|
||||
|
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. |
||||
File diff suppressed because it is too large
@ -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); |
||||
|
} |
||||
@ -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 |
||||
@ -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 |
||||
@ -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; |
||||
|
} |
||||
@ -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 |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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. |
||||
@ -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> |
||||
|
|
||||
|
; ============= |
||||
|
|
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
||||
@ -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(×tamp, &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], ×tamp, 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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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; } |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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=','); |
||||
|
}; |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
|
}; |
||||
@ -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(); } |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
|
}; |
||||
@ -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(); } |
||||
|
}; |
||||
@ -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 |
||||
@ -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"; |
||||
|
} |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
@ -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); |
||||
|
}; |
||||
@ -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(); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
@ -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
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
}; |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
Loading…
Reference in new issue