mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
Initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
## About RippleCore
|
||||||
|
|
||||||
|
RippleCore is a portable C++ library which provides classes for adding multi-hop packet routing to embedded projects typically employing packet radios like LoRa.
|
||||||
|
|
||||||
|
It is commercially known as the 'R2' protocol, and is the successor LoRa routing engine to the ['Ripple' project](https://buymeacoffee.com/ripplebiz).
|
||||||
|
|
||||||
|
At present it is mostly aimed at Arduino projects, using the [PlatformIO](https://docs.platformio.org) tools, but could potentially be integrated into other environments.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
You'll need to install PlatformIO, and an IDE in which it runs, like VSCode. Once installed, you should just be able to open this folder as a project, and it will read the platformio.ini file, and bring in all the required dependencies.
|
||||||
|
|
||||||
|
There are a number of build environments defined in the platformio.ini file, all targeting the Heltec V3 LoRa boards. For example, **[env:Heltec_v3_chat_alice]** is the target for building/running the 'secure chat' sample app as the user 'Alice'. There is a similar env, configured to run the secure chat as the user 'Bob'.
|
||||||
|
|
||||||
|
Try running these two examples first, flashing to two separate Heltec V3's, and use the Serial Monitor to interact with the *very basic* command-line interface.
|
||||||
|
|
||||||
|
## Other Example Apps
|
||||||
|
|
||||||
|
There is also a pair of examples, **'ping_client'** and **'ping_server'** which has some very basic constructs for setting up a node in a 'server'-like role, and another as client.
|
||||||
|
|
||||||
|
There is also a **'simple_repeater'** example, which should function as a basic repeater to ALL of the various samples, like the chat ones. It also defines a few examples of some 'remote admin', like setting the clock. The **'test_admin'** example is an example of an app that remotely monitors and sends commands to the 'simple_repeater' nodes.
|
||||||
|
|
||||||
|
## To-Do's
|
||||||
|
|
||||||
|
Will hopefully figure out how to make this a registered PlatformIO library, so it can just be added in **lib_deps** in your own project.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
This project is insipired from/by the [Reticulum project, by Mark Qvist](https://reticulum.network/start.html). I recommend having a look at RNS (Reticulum Network Stack) and study, almost as a pre-requisite to this project.
|
||||||
|
|
||||||
149
examples/ping_client/main.cpp
Normal file
149
examples/ping_client/main.cpp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/RadioLibWrappers.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
|
#include <helpers/SimpleSeenTable.h>
|
||||||
|
|
||||||
|
/* ------------------------------ Config -------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HELTEC_LORA_V3
|
||||||
|
#include <helpers/HeltecV3Board.h>
|
||||||
|
static HeltecV3Board board;
|
||||||
|
#else
|
||||||
|
#error "need to provide a 'board' object"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ------------------------------ Code -------------------------------- */
|
||||||
|
|
||||||
|
class MyMesh : public mesh::Mesh {
|
||||||
|
SimpleSeenTable* _table;
|
||||||
|
uint32_t last_advert_timestamp = 0;
|
||||||
|
mesh::Identity server_id;
|
||||||
|
uint8_t server_secret[PUB_KEY_SIZE];
|
||||||
|
int server_path_len = -1;
|
||||||
|
uint8_t server_path[MAX_PATH_SIZE];
|
||||||
|
bool got_adv = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int searchPeersByHash(const uint8_t* hash) override {
|
||||||
|
if (got_adv && server_id.isHashMatch(hash)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
|
||||||
|
if (memcmp(app_data, "PING", 4) == 0) {
|
||||||
|
Serial.println("Received advertisement from a PING server");
|
||||||
|
|
||||||
|
// check for replay attacks
|
||||||
|
if (timestamp > last_advert_timestamp) {
|
||||||
|
last_advert_timestamp = timestamp;
|
||||||
|
|
||||||
|
server_id = id;
|
||||||
|
self_id.calcSharedSecret(server_secret, id); // calc ECDH shared secret
|
||||||
|
got_adv = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
|
||||||
|
if (type == PAYLOAD_TYPE_RESPONSE) {
|
||||||
|
if (_table->hasSeenPacket(packet)) return;
|
||||||
|
|
||||||
|
Serial.println("Received PING Reply!");
|
||||||
|
|
||||||
|
if (packet->isRouteFlood()) {
|
||||||
|
// let server know path TO here, so they can use sendDirect() for future ping responses
|
||||||
|
mesh::Packet* path = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||||
|
if (path) sendFlood(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
|
||||||
|
if (_table->hasSeenPacket(packet)) return;
|
||||||
|
|
||||||
|
// must be from server_id
|
||||||
|
Serial.printf("PATH to server, path_len=%d\n", (uint32_t) path_len);
|
||||||
|
|
||||||
|
memcpy(server_path, path, server_path_len = path_len); // store a copy of path, for sendDirect()
|
||||||
|
|
||||||
|
if (packet->isRouteFlood()) {
|
||||||
|
// send a reciprocal return path to sender, but send DIRECTLY!
|
||||||
|
mesh::Packet* rpath = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||||
|
if (rpath) sendDirect(rpath, path, path_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra_type == PAYLOAD_TYPE_RESPONSE) {
|
||||||
|
Serial.println("Received PING Reply!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleSeenTable& table)
|
||||||
|
: mesh::Mesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16)), _table(&table)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPingPacket() {
|
||||||
|
if (!got_adv) return; // have not received Advert yet
|
||||||
|
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime(); // important, need timestamp in packet, so that packet_hash will be unique
|
||||||
|
mesh::Packet* ping = createDatagram(PAYLOAD_TYPE_ANON_REQ, server_id, server_secret, (uint8_t *) &now, sizeof(now));
|
||||||
|
|
||||||
|
if (ping) {
|
||||||
|
if (server_path_len < 0) {
|
||||||
|
sendFlood(ping);
|
||||||
|
} else {
|
||||||
|
sendDirect(ping, server_path, server_path_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
SPIClass spi;
|
||||||
|
StdRNG fast_rng;
|
||||||
|
SimpleSeenTable table;
|
||||||
|
SX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||||
|
MyMesh the_mesh(*new RadioLibWrapper(radio, board), fast_rng, *new VolatileRTCClock(), table);
|
||||||
|
unsigned long nextPing;
|
||||||
|
|
||||||
|
void halt() {
|
||||||
|
while (1) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
board.begin();
|
||||||
|
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||||
|
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
|
||||||
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.print("ERROR: radio init failed: ");
|
||||||
|
Serial.println(status);
|
||||||
|
halt();
|
||||||
|
}
|
||||||
|
fast_rng.begin(radio.random(0x7FFFFFFF));
|
||||||
|
the_mesh.begin();
|
||||||
|
|
||||||
|
RadioNoiseListener true_rng(radio);
|
||||||
|
the_mesh.self_id = mesh::LocalIdentity(&true_rng); // create new random identity
|
||||||
|
|
||||||
|
nextPing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (the_mesh.millisHasNowPassed(nextPing)) {
|
||||||
|
the_mesh.sendPingPacket();
|
||||||
|
|
||||||
|
nextPing = the_mesh.futureMillis(10000); // attempt ping every 10 seconds
|
||||||
|
}
|
||||||
|
the_mesh.loop();
|
||||||
|
}
|
||||||
174
examples/ping_server/main.cpp
Normal file
174
examples/ping_server/main.cpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/RadioLibWrappers.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
|
|
||||||
|
/* ------------------------------ Config -------------------------------- */
|
||||||
|
|
||||||
|
#ifdef HELTEC_LORA_V3
|
||||||
|
#include <helpers/HeltecV3Board.h>
|
||||||
|
static HeltecV3Board board;
|
||||||
|
#else
|
||||||
|
#error "need to provide a 'board' object"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ------------------------------ Code -------------------------------- */
|
||||||
|
|
||||||
|
struct ClientInfo {
|
||||||
|
mesh::Identity id;
|
||||||
|
uint32_t last_timestamp;
|
||||||
|
uint8_t secret[PUB_KEY_SIZE];
|
||||||
|
int out_path_len;
|
||||||
|
uint8_t out_path[MAX_PATH_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_CLIENTS 4
|
||||||
|
|
||||||
|
class MyMesh : public mesh::Mesh {
|
||||||
|
int num_clients;
|
||||||
|
ClientInfo known_clients[MAX_CLIENTS];
|
||||||
|
|
||||||
|
ClientInfo* putClient(const mesh::Identity& id) {
|
||||||
|
for (int i = 0; i < num_clients; i++) {
|
||||||
|
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
|
||||||
|
}
|
||||||
|
if (num_clients < MAX_CLIENTS) {
|
||||||
|
auto newClient = &known_clients[num_clients++];
|
||||||
|
newClient->id = id;
|
||||||
|
newClient->out_path_len = -1; // initially out_path is unknown
|
||||||
|
newClient->last_timestamp = 0;
|
||||||
|
self_id.calcSharedSecret(newClient->secret, id); // calc ECDH shared secret
|
||||||
|
return newClient;
|
||||||
|
}
|
||||||
|
return NULL; // table is full
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||||
|
if (type == PAYLOAD_TYPE_ANON_REQ) { // received a PING!
|
||||||
|
uint32_t timestamp;
|
||||||
|
memcpy(×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
|
||||||
|
}
|
||||||
338
examples/simple_repeater/main.cpp
Normal file
338
examples/simple_repeater/main.cpp
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/CustomSX1262Wrapper.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
|
#include <helpers/SimpleMeshTables.h>
|
||||||
|
#include <helpers/IdentityStore.h>
|
||||||
|
|
||||||
|
/* ------------------------------ Config -------------------------------- */
|
||||||
|
|
||||||
|
#define ANNOUNCE_DATA "repeater:v1"
|
||||||
|
|
||||||
|
#define ADMIN_PASSWORD "h^(kl@#)"
|
||||||
|
|
||||||
|
#if defined(HELTEC_LORA_V3)
|
||||||
|
#include <helpers/HeltecV3Board.h>
|
||||||
|
static HeltecV3Board board;
|
||||||
|
#else
|
||||||
|
#error "need to provide a 'board' object"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ------------------------------ Code -------------------------------- */
|
||||||
|
|
||||||
|
#define CMD_GET_STATS 0x01
|
||||||
|
#define CMD_SET_CLOCK 0x02
|
||||||
|
#define CMD_SEND_ANNOUNCE 0x03
|
||||||
|
#define CMD_SET_CONFIG 0x04
|
||||||
|
|
||||||
|
struct RepeaterStats {
|
||||||
|
uint16_t batt_milli_volts;
|
||||||
|
uint16_t curr_tx_queue_len;
|
||||||
|
uint16_t curr_free_queue_len;
|
||||||
|
int16_t last_rssi;
|
||||||
|
uint32_t n_packets_recv;
|
||||||
|
uint32_t n_packets_sent;
|
||||||
|
uint32_t total_air_time_secs;
|
||||||
|
uint32_t total_up_time_secs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientInfo {
|
||||||
|
mesh::Identity id;
|
||||||
|
uint32_t last_timestamp;
|
||||||
|
uint8_t secret[PUB_KEY_SIZE];
|
||||||
|
int out_path_len;
|
||||||
|
uint8_t out_path[MAX_PATH_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_CLIENTS 4
|
||||||
|
|
||||||
|
class MyMesh : public mesh::Mesh {
|
||||||
|
RadioLibWrapper* my_radio;
|
||||||
|
mesh::MeshTables* _tables;
|
||||||
|
float airtime_factor;
|
||||||
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
|
int num_clients;
|
||||||
|
ClientInfo known_clients[MAX_CLIENTS];
|
||||||
|
|
||||||
|
ClientInfo* putClient(const mesh::Identity& id) {
|
||||||
|
for (int i = 0; i < num_clients; i++) {
|
||||||
|
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
|
||||||
|
}
|
||||||
|
if (num_clients < MAX_CLIENTS) {
|
||||||
|
auto newClient = &known_clients[num_clients++];
|
||||||
|
newClient->id = id;
|
||||||
|
newClient->out_path_len = -1; // initially out_path is unknown
|
||||||
|
newClient->last_timestamp = 0;
|
||||||
|
self_id.calcSharedSecret(newClient->secret, id); // calc ECDH shared secret
|
||||||
|
return newClient;
|
||||||
|
}
|
||||||
|
return NULL; // table is full
|
||||||
|
}
|
||||||
|
|
||||||
|
int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) {
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
|
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||||
|
|
||||||
|
switch (payload[0]) {
|
||||||
|
case CMD_GET_STATS: {
|
||||||
|
uint32_t max_age_secs;
|
||||||
|
if (payload_len >= 5) {
|
||||||
|
memcpy(&max_age_secs, &payload[1], 4); // first param in request pkt
|
||||||
|
} else {
|
||||||
|
max_age_secs = 12*60*60; // default, 12 hours
|
||||||
|
}
|
||||||
|
|
||||||
|
RepeaterStats stats;
|
||||||
|
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||||
|
stats.curr_tx_queue_len = _mgr->getOutboundCount();
|
||||||
|
stats.curr_free_queue_len = _mgr->getFreeCount();
|
||||||
|
stats.last_rssi = (int16_t) my_radio->getLastRSSI();
|
||||||
|
stats.n_packets_recv = my_radio->getPacketsRecv();
|
||||||
|
stats.n_packets_sent = my_radio->getPacketsSent();
|
||||||
|
stats.total_air_time_secs = getTotalAirTime() / 1000;
|
||||||
|
stats.total_up_time_secs = _ms->getMillis() / 1000;
|
||||||
|
|
||||||
|
memcpy(&reply_data[4], &stats, sizeof(stats));
|
||||||
|
|
||||||
|
return 4 + sizeof(stats); // reply_len
|
||||||
|
}
|
||||||
|
case CMD_SET_CLOCK: {
|
||||||
|
if (payload_len >= 5) {
|
||||||
|
uint32_t curr_epoch_secs;
|
||||||
|
memcpy(&curr_epoch_secs, &payload[1], 4); // first param is current UNIX time
|
||||||
|
|
||||||
|
if (curr_epoch_secs > now) { // time can only go forward!!
|
||||||
|
getRTCClock()->setCurrentTime(curr_epoch_secs);
|
||||||
|
memcpy(&reply_data[4], "OK", 2);
|
||||||
|
} else {
|
||||||
|
memcpy(&reply_data[4], "ER", 2);
|
||||||
|
}
|
||||||
|
return 4 + 2; // reply_len
|
||||||
|
}
|
||||||
|
return 0; // invalid request
|
||||||
|
}
|
||||||
|
case CMD_SEND_ANNOUNCE: {
|
||||||
|
// broadcast another self Advertisement
|
||||||
|
auto adv = createAdvert(self_id, (const uint8_t *)ANNOUNCE_DATA, strlen(ANNOUNCE_DATA));
|
||||||
|
if (adv) sendFlood(adv, 1500); // send after slight delay
|
||||||
|
|
||||||
|
memcpy(&reply_data[4], "OK", 2);
|
||||||
|
return 4 + 2; // reply_len
|
||||||
|
}
|
||||||
|
case CMD_SET_CONFIG: {
|
||||||
|
if (payload_len >= 4 && payload_len < 32 && memcmp(&payload[1], "AF", 2) == 0) {
|
||||||
|
payload[payload_len] = 0; // make it a C string
|
||||||
|
airtime_factor = atof((char *) &payload[3]);
|
||||||
|
|
||||||
|
memcpy(&reply_data[4], "OK", 2);
|
||||||
|
return 4 + 2; // reply_len
|
||||||
|
}
|
||||||
|
return 0; // unknown config var
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unknown command
|
||||||
|
return 0; // reply_len
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float getAirtimeBudgetFactor() const override {
|
||||||
|
return airtime_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allowPacketForward(mesh::Packet* packet) override {
|
||||||
|
uint8_t hash[MAX_HASH_SIZE];
|
||||||
|
packet->calculatePacketHash(hash);
|
||||||
|
|
||||||
|
if (_tables->hasForwarded(hash)) return false; // has already been forwarded
|
||||||
|
|
||||||
|
_tables->setHasForwarded(hash); // mark packet as forwarded
|
||||||
|
return true; // Yes, allow packet to be forwarded
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||||
|
if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||||
|
uint32_t timestamp;
|
||||||
|
memcpy(×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
|
||||||
|
}
|
||||||
334
examples/simple_secure_chat/main.cpp
Normal file
334
examples/simple_secure_chat/main.cpp
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/RadioLibWrappers.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
|
#include <helpers/SimpleSeenTable.h>
|
||||||
|
|
||||||
|
/* ---------------------------------- CONFIGURATION ------------------------------------- */
|
||||||
|
|
||||||
|
//#define RUN_AS_ALICE true
|
||||||
|
|
||||||
|
#if RUN_AS_ALICE
|
||||||
|
const char* alice_private = "B8830658388B2DDF22C3A508F4386975970CDE1E2A2A495C8F3B5727957A97629255A1392F8BA4C26A023A0DAB78BFC64D261C8E51507496DD39AFE3707E7B42";
|
||||||
|
#else
|
||||||
|
const char *bob_private = "30BAA23CCB825D8020A59C936D0AB7773B07356020360FC77192813640BAD375E43BBF9A9A7537E4B9614610F1F2EF874AAB390BA9B0C2F01006B01FDDFEFF0C";
|
||||||
|
#endif
|
||||||
|
const char *alice_public = "106A5136EC0DD797650AD204C065CF9B66095F6ED772B0822187785D65E11B1F";
|
||||||
|
const char *bob_public = "020BCEDAC07D709BD8507EC316EB5A7FF2F0939AF5057353DCE7E4436A1B9681";
|
||||||
|
|
||||||
|
#ifdef HELTEC_LORA_V3
|
||||||
|
#include <helpers/HeltecV3Board.h>
|
||||||
|
static HeltecV3Board board;
|
||||||
|
#else
|
||||||
|
#error "need to provide a 'board' object"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define FLOOD_SEND_TIMEOUT_MILLIS 4000
|
||||||
|
#define DIRECT_SEND_TIMEOUT_MILLIS 2000
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static unsigned long txt_send_timeout;
|
||||||
|
|
||||||
|
#define MAX_CONTACTS 1
|
||||||
|
#define MAX_SEARCH_RESULTS 1
|
||||||
|
|
||||||
|
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1)
|
||||||
|
|
||||||
|
struct ContactInfo {
|
||||||
|
mesh::Identity id;
|
||||||
|
const char* name;
|
||||||
|
int out_path_len;
|
||||||
|
uint8_t out_path[MAX_PATH_SIZE];
|
||||||
|
uint32_t last_advert_timestamp;
|
||||||
|
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyMesh : public mesh::Mesh {
|
||||||
|
public:
|
||||||
|
SimpleSeenTable* _table;
|
||||||
|
mesh::LocalIdentity self_id;
|
||||||
|
ContactInfo contacts[MAX_CONTACTS];
|
||||||
|
int num_contacts;
|
||||||
|
|
||||||
|
void addContact(const char* name, const mesh::Identity& id) {
|
||||||
|
if (num_contacts < MAX_CONTACTS) {
|
||||||
|
contacts[num_contacts].id = id;
|
||||||
|
contacts[num_contacts].name = name;
|
||||||
|
contacts[num_contacts].last_advert_timestamp = 0;
|
||||||
|
contacts[num_contacts].out_path_len = -1;
|
||||||
|
// only need to calculate the shared_secret once, for better performance
|
||||||
|
self_id.calcSharedSecret(contacts[num_contacts].shared_secret, id);
|
||||||
|
num_contacts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int matching_peer_indexes[MAX_SEARCH_RESULTS];
|
||||||
|
|
||||||
|
int searchPeersByHash(const uint8_t* hash) override {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) {
|
||||||
|
if (contacts[i].id.isHashMatch(hash)) {
|
||||||
|
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
|
||||||
|
Serial.print("Valid Advertisement -> ");
|
||||||
|
mesh::Utils::printHex(Serial, id.pub_key, PUB_KEY_SIZE);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
for (int i = 0; i < num_contacts; i++) {
|
||||||
|
ContactInfo& from = contacts[i];
|
||||||
|
// check for replay attacks
|
||||||
|
if (id.matches(from.id) && timestamp > from.last_advert_timestamp) { // is from one of our contacts
|
||||||
|
from.last_advert_timestamp = timestamp;
|
||||||
|
Serial.printf(" From contact: %s\n", from.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override {
|
||||||
|
int i = matching_peer_indexes[peer_idx];
|
||||||
|
if (i >= 0 && i < num_contacts) {
|
||||||
|
// lookup pre-calculated shared_secret
|
||||||
|
memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE);
|
||||||
|
} else {
|
||||||
|
MESH_DEBUG_PRINTLN("getPeerSHharedSecret: Invalid peer idx: %d", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
|
||||||
|
if (type == PAYLOAD_TYPE_TXT_MSG) {
|
||||||
|
if (_table->hasSeenPacket(packet)) return;
|
||||||
|
|
||||||
|
int i = matching_peer_indexes[sender_idx];
|
||||||
|
if (i < 0 && i >= num_contacts) {
|
||||||
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactInfo& from = contacts[i];
|
||||||
|
|
||||||
|
uint32_t timestamp;
|
||||||
|
memcpy(×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();
|
||||||
|
}
|
||||||
304
examples/test_admin/main.cpp
Normal file
304
examples/test_admin/main.cpp
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <SPIFFS.h>
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/CustomSX1262Wrapper.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/SimpleSeenTable.h>
|
||||||
|
#include <helpers/StaticPoolPacketManager.h>
|
||||||
|
|
||||||
|
/* ---------------------------------- CONFIGURATION ------------------------------------- */
|
||||||
|
|
||||||
|
#define ADMIN_PASSWORD "h^(kl@#)"
|
||||||
|
|
||||||
|
#ifdef HELTEC_LORA_V3
|
||||||
|
#include <helpers/HeltecV3Board.h>
|
||||||
|
static HeltecV3Board board;
|
||||||
|
#else
|
||||||
|
#error "need to provide a 'board' object"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - FROM_HASH_LEN - CIPHER_MAC_SIZE - 1)
|
||||||
|
|
||||||
|
#define CMD_GET_STATS 0x01
|
||||||
|
#define CMD_SET_CLOCK 0x02
|
||||||
|
#define CMD_SEND_ANNOUNCE 0x03
|
||||||
|
#define CMD_SET_CONFIG 0x04
|
||||||
|
|
||||||
|
struct RepeaterStats {
|
||||||
|
uint16_t batt_milli_volts;
|
||||||
|
uint16_t curr_tx_queue_len;
|
||||||
|
uint16_t curr_free_queue_len;
|
||||||
|
int16_t last_rssi;
|
||||||
|
uint32_t n_packets_recv;
|
||||||
|
uint32_t n_packets_sent;
|
||||||
|
uint32_t total_air_time_secs;
|
||||||
|
uint32_t total_up_time_secs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MyMesh : public mesh::Mesh {
|
||||||
|
SimpleSeenTable* _table;
|
||||||
|
uint32_t last_advert_timestamp = 0;
|
||||||
|
mesh::Identity server_id;
|
||||||
|
uint8_t server_secret[PUB_KEY_SIZE];
|
||||||
|
int server_path_len = -1;
|
||||||
|
uint8_t server_path[MAX_PATH_SIZE];
|
||||||
|
bool got_adv = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int searchPeersByHash(const uint8_t* hash) override {
|
||||||
|
if (got_adv && server_id.isHashMatch(hash)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) override {
|
||||||
|
if (memcmp(app_data, "repeater:", 9) == 0) {
|
||||||
|
Serial.println("Received advertisement from a repeater!");
|
||||||
|
|
||||||
|
// check for replay attacks
|
||||||
|
if (timestamp > last_advert_timestamp) {
|
||||||
|
last_advert_timestamp = timestamp;
|
||||||
|
|
||||||
|
server_id = id;
|
||||||
|
self_id.calcSharedSecret(server_secret, id); // calc ECDH shared secret
|
||||||
|
got_adv = true;
|
||||||
|
|
||||||
|
// 'login' to repeater. (mainly lets it know our public key)
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime(); // important, need timestamp in packet, so that packet_hash will be unique
|
||||||
|
uint8_t temp[4 + 8];
|
||||||
|
memcpy(temp, &now, 4);
|
||||||
|
memcpy(&temp[4], ADMIN_PASSWORD, 8);
|
||||||
|
|
||||||
|
mesh::Packet* login = createDatagram(PAYLOAD_TYPE_ANON_REQ, server_id, server_secret, temp, sizeof(temp));
|
||||||
|
if (login) sendFlood(login); // server_path won't be known yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleResponse(const uint8_t* reply, size_t reply_len) {
|
||||||
|
if (reply_len >= 4 + sizeof(RepeaterStats)) { // got an GET_STATS reply from repeater
|
||||||
|
RepeaterStats stats;
|
||||||
|
memcpy(&stats, &reply[4], sizeof(stats));
|
||||||
|
Serial.println("Repeater Stats:");
|
||||||
|
Serial.printf(" battery: %d mV\n", (uint32_t) stats.batt_milli_volts);
|
||||||
|
Serial.printf(" tx queue: %d\n", (uint32_t) stats.curr_tx_queue_len);
|
||||||
|
Serial.printf(" free queue: %d\n", (uint32_t) stats.curr_free_queue_len);
|
||||||
|
Serial.printf(" last RSSI: %d\n", (int) stats.last_rssi);
|
||||||
|
Serial.printf(" num recv: %d\n", stats.n_packets_recv);
|
||||||
|
Serial.printf(" num sent: %d\n", stats.n_packets_sent);
|
||||||
|
Serial.printf(" air time (secs): %d\n", stats.total_air_time_secs);
|
||||||
|
Serial.printf(" up time (secs): %d\n", stats.total_up_time_secs);
|
||||||
|
} else if (reply_len > 4) { // got an SET_* reply from repeater
|
||||||
|
char tmp[MAX_PACKET_PAYLOAD];
|
||||||
|
memcpy(tmp, &reply[4], reply_len - 4);
|
||||||
|
tmp[reply_len - 4] = 0; // make a C string of reply
|
||||||
|
|
||||||
|
Serial.print("Reply: "); Serial.println(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) override {
|
||||||
|
if (type == PAYLOAD_TYPE_RESPONSE) {
|
||||||
|
if (_table->hasSeenPacket(packet)) return;
|
||||||
|
|
||||||
|
handleResponse(data, len);
|
||||||
|
|
||||||
|
if (packet->isRouteFlood()) {
|
||||||
|
// let server know path TO here, so they can use sendDirect() for future ping responses
|
||||||
|
mesh::Packet* path = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||||
|
if (path) sendFlood(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPeerPathRecv(mesh::Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override {
|
||||||
|
if (_table->hasSeenPacket(packet)) return;
|
||||||
|
|
||||||
|
// must be from server_id
|
||||||
|
Serial.printf("PATH to repeater, path_len=%d\n", (uint32_t) path_len);
|
||||||
|
|
||||||
|
memcpy(server_path, path, server_path_len = path_len); // store a copy of path, for sendDirect()
|
||||||
|
|
||||||
|
if (packet->isRouteFlood()) {
|
||||||
|
// send a reciprocal return path to sender, but send DIRECTLY!
|
||||||
|
mesh::Packet* rpath = createPathReturn(server_id, server_secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||||
|
if (rpath) sendDirect(rpath, path, path_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra_type == PAYLOAD_TYPE_RESPONSE) {
|
||||||
|
handleResponse(extra, extra_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleSeenTable& table)
|
||||||
|
: mesh::Mesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16)), _table(&table)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* createStatsRequest(uint32_t max_age) {
|
||||||
|
uint8_t payload[9];
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
|
memcpy(payload, &now, 4);
|
||||||
|
payload[4] = CMD_GET_STATS;
|
||||||
|
memcpy(&payload[5], &max_age, 4);
|
||||||
|
|
||||||
|
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* createSetClockRequest(uint32_t timestamp) {
|
||||||
|
uint8_t payload[9];
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
|
memcpy(payload, &now, 4);
|
||||||
|
payload[4] = CMD_SET_CLOCK;
|
||||||
|
memcpy(&payload[5], &now, 4); // repeated :-(
|
||||||
|
|
||||||
|
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* createSetAirtimeFactorRequest(float airtime_factor) {
|
||||||
|
uint8_t payload[16];
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
|
memcpy(payload, &now, 4);
|
||||||
|
payload[4] = CMD_SET_CONFIG;
|
||||||
|
sprintf((char *) &payload[5], "AF%f", airtime_factor);
|
||||||
|
|
||||||
|
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* createAnnounceRequest() {
|
||||||
|
uint8_t payload[5];
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
|
memcpy(payload, &now, 4);
|
||||||
|
payload[4] = CMD_SEND_ANNOUNCE;
|
||||||
|
|
||||||
|
return createDatagram(PAYLOAD_TYPE_REQ, server_id, server_secret, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* parseCommand(char* command) {
|
||||||
|
if (strcmp(command, "stats") == 0) {
|
||||||
|
return createStatsRequest(60*60); // max_age = one hour
|
||||||
|
} else if (memcmp(command, "setclock ", 9) == 0) {
|
||||||
|
uint32_t timestamp = atol(&command[9]);
|
||||||
|
return createSetClockRequest(timestamp);
|
||||||
|
} else if (memcmp(command, "set AF=", 7) == 0) {
|
||||||
|
float factor = atof(&command[7]);
|
||||||
|
return createSetAirtimeFactorRequest(factor);
|
||||||
|
} else if (strcmp(command, "ann") == 0) {
|
||||||
|
return createAnnounceRequest();
|
||||||
|
}
|
||||||
|
return NULL; // unknown command
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendCommand(mesh::Packet* pkt) {
|
||||||
|
if (server_path_len < 0) {
|
||||||
|
sendFlood(pkt);
|
||||||
|
} else {
|
||||||
|
sendDirect(pkt, server_path, server_path_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StdRNG fast_rng;
|
||||||
|
SimpleSeenTable table;
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
SPIClass spi;
|
||||||
|
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||||
|
#else
|
||||||
|
CustomSX1262 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||||
|
#endif
|
||||||
|
MyMesh the_mesh(*new CustomSX1262Wrapper(radio, board), fast_rng, *new VolatileRTCClock(), table);
|
||||||
|
|
||||||
|
void halt() {
|
||||||
|
while (1) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char command[MAX_TEXT_LEN+1];
|
||||||
|
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(5000);
|
||||||
|
|
||||||
|
board.begin();
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||||
|
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
|
||||||
|
#else
|
||||||
|
int status = radio.begin(915.0, 250, 9, 5, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, 22);
|
||||||
|
#endif
|
||||||
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.print("ERROR: radio init failed: ");
|
||||||
|
Serial.println(status);
|
||||||
|
halt();
|
||||||
|
}
|
||||||
|
|
||||||
|
fast_rng.begin(radio.random(0x7FFFFFFF));
|
||||||
|
|
||||||
|
/* add this to tests
|
||||||
|
uint8_t mac_encrypted[CIPHER_MAC_SIZE+CIPHER_BLOCK_SIZE];
|
||||||
|
const char *orig_msg = "original";
|
||||||
|
int enc_len = mesh::Utils::encryptThenMAC(mesh.admin_secret, mac_encrypted, (const uint8_t *) orig_msg, strlen(orig_msg));
|
||||||
|
char decrypted[CIPHER_BLOCK_SIZE*2];
|
||||||
|
int len = mesh::Utils::MACThenDecrypt(mesh.admin_secret, (uint8_t *)decrypted, mac_encrypted, enc_len);
|
||||||
|
if (len > 0) {
|
||||||
|
decrypted[len] = 0;
|
||||||
|
Serial.print("decrypted text: "); Serial.println(decrypted);
|
||||||
|
} else {
|
||||||
|
Serial.println("MACs DONT match!");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Serial.println("Help:");
|
||||||
|
Serial.println(" enter 'key' to generate new keypair");
|
||||||
|
Serial.println(" enter 'stats' to request repeater stats");
|
||||||
|
Serial.println(" enter 'setclock {unix-epoch-seconds}' to set repeater's clock");
|
||||||
|
Serial.println(" enter 'set AF={factor}' to set airtime budget factor");
|
||||||
|
Serial.println(" enter 'ann' to make repeater re-announce to mesh");
|
||||||
|
|
||||||
|
the_mesh.begin();
|
||||||
|
|
||||||
|
command[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
int len = strlen(command);
|
||||||
|
while (Serial.available() && len < sizeof(command)-1) {
|
||||||
|
char c = Serial.read();
|
||||||
|
if (c != '\n') {
|
||||||
|
command[len++] = c;
|
||||||
|
command[len] = 0;
|
||||||
|
}
|
||||||
|
Serial.print(c);
|
||||||
|
}
|
||||||
|
if (len == sizeof(command)-1) { // command buffer full
|
||||||
|
command[sizeof(command)-1] = '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||||
|
command[len - 1] = 0; // replace newline with C string null terminator
|
||||||
|
|
||||||
|
if (strcmp(command, "key") == 0) {
|
||||||
|
mesh::LocalIdentity new_id(the_mesh.getRNG());
|
||||||
|
new_id.printTo(Serial);
|
||||||
|
} else {
|
||||||
|
mesh::Packet* pkt = the_mesh.parseCommand(command);
|
||||||
|
if (pkt) {
|
||||||
|
the_mesh.sendCommand(pkt);
|
||||||
|
Serial.println(" (request sent)");
|
||||||
|
} else {
|
||||||
|
Serial.print(" ERROR: unknown command: "); Serial.println(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command[0] = 0; // reset command buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
the_mesh.loop();
|
||||||
|
}
|
||||||
39
include/README
Normal file
39
include/README
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
69
lib/ed25519/add_scalar.c
Normal file
69
lib/ed25519/add_scalar.c
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
#include "ge.h"
|
||||||
|
#include "sc.h"
|
||||||
|
#include "sha512.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* see http://crypto.stackexchange.com/a/6215/4697 */
|
||||||
|
void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) {
|
||||||
|
const unsigned char SC_1[32] = {1}; /* scalar with value 1 */
|
||||||
|
|
||||||
|
unsigned char n[32];
|
||||||
|
ge_p3 nB;
|
||||||
|
ge_p1p1 A_p1p1;
|
||||||
|
ge_p3 A;
|
||||||
|
ge_p3 public_key_unpacked;
|
||||||
|
ge_cached T;
|
||||||
|
|
||||||
|
sha512_context hash;
|
||||||
|
unsigned char hashbuf[64];
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* copy the scalar and clear highest bit */
|
||||||
|
for (i = 0; i < 31; ++i) {
|
||||||
|
n[i] = scalar[i];
|
||||||
|
}
|
||||||
|
n[31] = scalar[31] & 127;
|
||||||
|
|
||||||
|
/* private key: a = n + t */
|
||||||
|
if (private_key) {
|
||||||
|
sc_muladd(private_key, SC_1, n, private_key);
|
||||||
|
|
||||||
|
// https://github.com/orlp/ed25519/issues/3
|
||||||
|
sha512_init(&hash);
|
||||||
|
sha512_update(&hash, private_key + 32, 32);
|
||||||
|
sha512_update(&hash, scalar, 32);
|
||||||
|
sha512_final(&hash, hashbuf);
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
private_key[32 + i] = hashbuf[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public key: A = nB + T */
|
||||||
|
if (public_key) {
|
||||||
|
/* if we know the private key we don't need a point addition, which is faster */
|
||||||
|
/* using a "timing attack" you could find out wether or not we know the private
|
||||||
|
key, but this information seems rather useless - if this is important pass
|
||||||
|
public_key and private_key seperately in 2 function calls */
|
||||||
|
if (private_key) {
|
||||||
|
ge_scalarmult_base(&A, private_key);
|
||||||
|
} else {
|
||||||
|
/* unpack public key into T */
|
||||||
|
ge_frombytes_negate_vartime(&public_key_unpacked, public_key);
|
||||||
|
fe_neg(public_key_unpacked.X, public_key_unpacked.X); /* undo negate */
|
||||||
|
fe_neg(public_key_unpacked.T, public_key_unpacked.T); /* undo negate */
|
||||||
|
ge_p3_to_cached(&T, &public_key_unpacked);
|
||||||
|
|
||||||
|
/* calculate n*B */
|
||||||
|
ge_scalarmult_base(&nB, n);
|
||||||
|
|
||||||
|
/* A = n*B + T */
|
||||||
|
ge_add(&A_p1p1, &nB, &T);
|
||||||
|
ge_p1p1_to_p3(&A, &A_p1p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pack public key */
|
||||||
|
ge_p3_tobytes(public_key, &A);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
lib/ed25519/ed_25519.h
Normal file
40
lib/ed25519/ed_25519.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef ED25519_H
|
||||||
|
#define ED25519_H
|
||||||
|
|
||||||
|
// Nightcracker's Ed25519 - https://github.com/orlp/ed25519
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#if defined(ED25519_BUILD_DLL)
|
||||||
|
#define ED25519_DECLSPEC __declspec(dllexport)
|
||||||
|
#elif defined(ED25519_DLL)
|
||||||
|
#define ED25519_DECLSPEC __declspec(dllimport)
|
||||||
|
#else
|
||||||
|
#define ED25519_DECLSPEC
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define ED25519_DECLSPEC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ED25519_NO_SEED
|
||||||
|
int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed);
|
||||||
|
void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key);
|
||||||
|
int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key);
|
||||||
|
void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar);
|
||||||
|
void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
1491
lib/ed25519/fe.c
Normal file
1491
lib/ed25519/fe.c
Normal file
File diff suppressed because it is too large
Load Diff
41
lib/ed25519/fe.h
Normal file
41
lib/ed25519/fe.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef FE_H
|
||||||
|
#define FE_H
|
||||||
|
|
||||||
|
#include "fixedint.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
fe means field element.
|
||||||
|
Here the field is \Z/(2^255-19).
|
||||||
|
An element t, entries t[0]...t[9], represents the integer
|
||||||
|
t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9].
|
||||||
|
Bounds on each t[i] vary depending on context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef int32_t fe[10];
|
||||||
|
|
||||||
|
|
||||||
|
void fe_0(fe h);
|
||||||
|
void fe_1(fe h);
|
||||||
|
|
||||||
|
void fe_frombytes(fe h, const unsigned char *s);
|
||||||
|
void fe_tobytes(unsigned char *s, const fe h);
|
||||||
|
|
||||||
|
void fe_copy(fe h, const fe f);
|
||||||
|
int fe_isnegative(const fe f);
|
||||||
|
int fe_isnonzero(const fe f);
|
||||||
|
void fe_cmov(fe f, const fe g, unsigned int b);
|
||||||
|
void fe_cswap(fe f, fe g, unsigned int b);
|
||||||
|
|
||||||
|
void fe_neg(fe h, const fe f);
|
||||||
|
void fe_add(fe h, const fe f, const fe g);
|
||||||
|
void fe_invert(fe out, const fe z);
|
||||||
|
void fe_sq(fe h, const fe f);
|
||||||
|
void fe_sq2(fe h, const fe f);
|
||||||
|
void fe_mul(fe h, const fe f, const fe g);
|
||||||
|
void fe_mul121666(fe h, fe f);
|
||||||
|
void fe_pow22523(fe out, const fe z);
|
||||||
|
void fe_sub(fe h, const fe f, const fe g);
|
||||||
|
|
||||||
|
#endif
|
||||||
72
lib/ed25519/fixedint.h
Normal file
72
lib/ed25519/fixedint.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Portable header to provide the 32 and 64 bits type.
|
||||||
|
|
||||||
|
Not a compatible replacement for <stdint.h>, do not blindly use it as such.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED)
|
||||||
|
#include <stdint.h>
|
||||||
|
#define FIXEDINT_H_INCLUDED
|
||||||
|
|
||||||
|
#if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C)
|
||||||
|
#include <limits.h>
|
||||||
|
#define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FIXEDINT_H_INCLUDED
|
||||||
|
#define FIXEDINT_H_INCLUDED
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
/* (u)int32_t */
|
||||||
|
#ifndef uint32_t
|
||||||
|
#if (ULONG_MAX == 0xffffffffUL)
|
||||||
|
typedef unsigned long uint32_t;
|
||||||
|
#elif (UINT_MAX == 0xffffffffUL)
|
||||||
|
typedef unsigned int uint32_t;
|
||||||
|
#elif (USHRT_MAX == 0xffffffffUL)
|
||||||
|
typedef unsigned short uint32_t;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef int32_t
|
||||||
|
#if (LONG_MAX == 0x7fffffffL)
|
||||||
|
typedef signed long int32_t;
|
||||||
|
#elif (INT_MAX == 0x7fffffffL)
|
||||||
|
typedef signed int int32_t;
|
||||||
|
#elif (SHRT_MAX == 0x7fffffffL)
|
||||||
|
typedef signed short int32_t;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* (u)int64_t */
|
||||||
|
#if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L)
|
||||||
|
typedef long long int64_t;
|
||||||
|
typedef unsigned long long uint64_t;
|
||||||
|
|
||||||
|
#define UINT64_C(v) v ##ULL
|
||||||
|
#define INT64_C(v) v ##LL
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
__extension__ typedef long long int64_t;
|
||||||
|
__extension__ typedef unsigned long long uint64_t;
|
||||||
|
|
||||||
|
#define UINT64_C(v) v ##ULL
|
||||||
|
#define INT64_C(v) v ##LL
|
||||||
|
#elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC)
|
||||||
|
typedef long long int64_t;
|
||||||
|
typedef unsigned long long uint64_t;
|
||||||
|
|
||||||
|
#define UINT64_C(v) v ##ULL
|
||||||
|
#define INT64_C(v) v ##LL
|
||||||
|
#elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC)
|
||||||
|
typedef __int64 int64_t;
|
||||||
|
typedef unsigned __int64 uint64_t;
|
||||||
|
|
||||||
|
#define UINT64_C(v) v ##UI64
|
||||||
|
#define INT64_C(v) v ##I64
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
467
lib/ed25519/ge.c
Normal file
467
lib/ed25519/ge.c
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
#include "ge.h"
|
||||||
|
#include "precomp_data.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p + q
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
|
||||||
|
fe t0;
|
||||||
|
fe_add(r->X, p->Y, p->X);
|
||||||
|
fe_sub(r->Y, p->Y, p->X);
|
||||||
|
fe_mul(r->Z, r->X, q->YplusX);
|
||||||
|
fe_mul(r->Y, r->Y, q->YminusX);
|
||||||
|
fe_mul(r->T, q->T2d, p->T);
|
||||||
|
fe_mul(r->X, p->Z, q->Z);
|
||||||
|
fe_add(t0, r->X, r->X);
|
||||||
|
fe_sub(r->X, r->Z, r->Y);
|
||||||
|
fe_add(r->Y, r->Z, r->Y);
|
||||||
|
fe_add(r->Z, t0, r->T);
|
||||||
|
fe_sub(r->T, t0, r->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void slide(signed char *r, const unsigned char *a) {
|
||||||
|
int i;
|
||||||
|
int b;
|
||||||
|
int k;
|
||||||
|
|
||||||
|
for (i = 0; i < 256; ++i) {
|
||||||
|
r[i] = 1 & (a[i >> 3] >> (i & 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 256; ++i)
|
||||||
|
if (r[i]) {
|
||||||
|
for (b = 1; b <= 6 && i + b < 256; ++b) {
|
||||||
|
if (r[i + b]) {
|
||||||
|
if (r[i] + (r[i + b] << b) <= 15) {
|
||||||
|
r[i] += r[i + b] << b;
|
||||||
|
r[i + b] = 0;
|
||||||
|
} else if (r[i] - (r[i + b] << b) >= -15) {
|
||||||
|
r[i] -= r[i + b] << b;
|
||||||
|
|
||||||
|
for (k = i + b; k < 256; ++k) {
|
||||||
|
if (!r[k]) {
|
||||||
|
r[k] = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r[k] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = a * A + b * B
|
||||||
|
where a = a[0]+256*a[1]+...+256^31 a[31].
|
||||||
|
and b = b[0]+256*b[1]+...+256^31 b[31].
|
||||||
|
B is the Ed25519 base point (x,4/5) with x positive.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) {
|
||||||
|
signed char aslide[256];
|
||||||
|
signed char bslide[256];
|
||||||
|
ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
|
||||||
|
ge_p1p1 t;
|
||||||
|
ge_p3 u;
|
||||||
|
ge_p3 A2;
|
||||||
|
int i;
|
||||||
|
slide(aslide, a);
|
||||||
|
slide(bslide, b);
|
||||||
|
ge_p3_to_cached(&Ai[0], A);
|
||||||
|
ge_p3_dbl(&t, A);
|
||||||
|
ge_p1p1_to_p3(&A2, &t);
|
||||||
|
ge_add(&t, &A2, &Ai[0]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[1], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[1]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[2], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[2]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[3], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[3]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[4], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[4]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[5], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[5]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[6], &u);
|
||||||
|
ge_add(&t, &A2, &Ai[6]);
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_p3_to_cached(&Ai[7], &u);
|
||||||
|
ge_p2_0(r);
|
||||||
|
|
||||||
|
for (i = 255; i >= 0; --i) {
|
||||||
|
if (aslide[i] || bslide[i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i >= 0; --i) {
|
||||||
|
ge_p2_dbl(&t, r);
|
||||||
|
|
||||||
|
if (aslide[i] > 0) {
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_add(&t, &u, &Ai[aslide[i] / 2]);
|
||||||
|
} else if (aslide[i] < 0) {
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bslide[i] > 0) {
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_madd(&t, &u, &Bi[bslide[i] / 2]);
|
||||||
|
} else if (bslide[i] < 0) {
|
||||||
|
ge_p1p1_to_p3(&u, &t);
|
||||||
|
ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ge_p1p1_to_p2(r, &t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const fe d = {
|
||||||
|
-10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116
|
||||||
|
};
|
||||||
|
|
||||||
|
static const fe sqrtm1 = {
|
||||||
|
-32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482
|
||||||
|
};
|
||||||
|
|
||||||
|
int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) {
|
||||||
|
fe u;
|
||||||
|
fe v;
|
||||||
|
fe v3;
|
||||||
|
fe vxx;
|
||||||
|
fe check;
|
||||||
|
fe_frombytes(h->Y, s);
|
||||||
|
fe_1(h->Z);
|
||||||
|
fe_sq(u, h->Y);
|
||||||
|
fe_mul(v, u, d);
|
||||||
|
fe_sub(u, u, h->Z); /* u = y^2-1 */
|
||||||
|
fe_add(v, v, h->Z); /* v = dy^2+1 */
|
||||||
|
fe_sq(v3, v);
|
||||||
|
fe_mul(v3, v3, v); /* v3 = v^3 */
|
||||||
|
fe_sq(h->X, v3);
|
||||||
|
fe_mul(h->X, h->X, v);
|
||||||
|
fe_mul(h->X, h->X, u); /* x = uv^7 */
|
||||||
|
fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */
|
||||||
|
fe_mul(h->X, h->X, v3);
|
||||||
|
fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */
|
||||||
|
fe_sq(vxx, h->X);
|
||||||
|
fe_mul(vxx, vxx, v);
|
||||||
|
fe_sub(check, vxx, u); /* vx^2-u */
|
||||||
|
|
||||||
|
if (fe_isnonzero(check)) {
|
||||||
|
fe_add(check, vxx, u); /* vx^2+u */
|
||||||
|
|
||||||
|
if (fe_isnonzero(check)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_mul(h->X, h->X, sqrtm1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fe_isnegative(h->X) == (s[31] >> 7)) {
|
||||||
|
fe_neg(h->X, h->X);
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_mul(h->T, h->X, h->Y);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p + q
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
|
||||||
|
fe t0;
|
||||||
|
fe_add(r->X, p->Y, p->X);
|
||||||
|
fe_sub(r->Y, p->Y, p->X);
|
||||||
|
fe_mul(r->Z, r->X, q->yplusx);
|
||||||
|
fe_mul(r->Y, r->Y, q->yminusx);
|
||||||
|
fe_mul(r->T, q->xy2d, p->T);
|
||||||
|
fe_add(t0, p->Z, p->Z);
|
||||||
|
fe_sub(r->X, r->Z, r->Y);
|
||||||
|
fe_add(r->Y, r->Z, r->Y);
|
||||||
|
fe_add(r->Z, t0, r->T);
|
||||||
|
fe_sub(r->T, t0, r->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p - q
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
|
||||||
|
fe t0;
|
||||||
|
|
||||||
|
fe_add(r->X, p->Y, p->X);
|
||||||
|
fe_sub(r->Y, p->Y, p->X);
|
||||||
|
fe_mul(r->Z, r->X, q->yminusx);
|
||||||
|
fe_mul(r->Y, r->Y, q->yplusx);
|
||||||
|
fe_mul(r->T, q->xy2d, p->T);
|
||||||
|
fe_add(t0, p->Z, p->Z);
|
||||||
|
fe_sub(r->X, r->Z, r->Y);
|
||||||
|
fe_add(r->Y, r->Z, r->Y);
|
||||||
|
fe_sub(r->Z, t0, r->T);
|
||||||
|
fe_add(r->T, t0, r->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) {
|
||||||
|
fe_mul(r->X, p->X, p->T);
|
||||||
|
fe_mul(r->Y, p->Y, p->Z);
|
||||||
|
fe_mul(r->Z, p->Z, p->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) {
|
||||||
|
fe_mul(r->X, p->X, p->T);
|
||||||
|
fe_mul(r->Y, p->Y, p->Z);
|
||||||
|
fe_mul(r->Z, p->Z, p->T);
|
||||||
|
fe_mul(r->T, p->X, p->Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ge_p2_0(ge_p2 *h) {
|
||||||
|
fe_0(h->X);
|
||||||
|
fe_1(h->Y);
|
||||||
|
fe_1(h->Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = 2 * p
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) {
|
||||||
|
fe t0;
|
||||||
|
|
||||||
|
fe_sq(r->X, p->X);
|
||||||
|
fe_sq(r->Z, p->Y);
|
||||||
|
fe_sq2(r->T, p->Z);
|
||||||
|
fe_add(r->Y, p->X, p->Y);
|
||||||
|
fe_sq(t0, r->Y);
|
||||||
|
fe_add(r->Y, r->Z, r->X);
|
||||||
|
fe_sub(r->Z, r->Z, r->X);
|
||||||
|
fe_sub(r->X, t0, r->Y);
|
||||||
|
fe_sub(r->T, r->T, r->Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ge_p3_0(ge_p3 *h) {
|
||||||
|
fe_0(h->X);
|
||||||
|
fe_1(h->Y);
|
||||||
|
fe_1(h->Z);
|
||||||
|
fe_0(h->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = 2 * p
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) {
|
||||||
|
ge_p2 q;
|
||||||
|
ge_p3_to_p2(&q, p);
|
||||||
|
ge_p2_dbl(r, &q);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const fe d2 = {
|
||||||
|
-21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199
|
||||||
|
};
|
||||||
|
|
||||||
|
void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) {
|
||||||
|
fe_add(r->YplusX, p->Y, p->X);
|
||||||
|
fe_sub(r->YminusX, p->Y, p->X);
|
||||||
|
fe_copy(r->Z, p->Z);
|
||||||
|
fe_mul(r->T2d, p->T, d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) {
|
||||||
|
fe_copy(r->X, p->X);
|
||||||
|
fe_copy(r->Y, p->Y);
|
||||||
|
fe_copy(r->Z, p->Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) {
|
||||||
|
fe recip;
|
||||||
|
fe x;
|
||||||
|
fe y;
|
||||||
|
fe_invert(recip, h->Z);
|
||||||
|
fe_mul(x, h->X, recip);
|
||||||
|
fe_mul(y, h->Y, recip);
|
||||||
|
fe_tobytes(s, y);
|
||||||
|
s[31] ^= fe_isnegative(x) << 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static unsigned char equal(signed char b, signed char c) {
|
||||||
|
unsigned char ub = b;
|
||||||
|
unsigned char uc = c;
|
||||||
|
unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */
|
||||||
|
uint64_t y = x; /* 0: yes; 1..255: no */
|
||||||
|
y -= 1; /* large: yes; 0..254: no */
|
||||||
|
y >>= 63; /* 1: yes; 0: no */
|
||||||
|
return (unsigned char) y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char negative(signed char b) {
|
||||||
|
uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
|
||||||
|
x >>= 63; /* 1: yes; 0: no */
|
||||||
|
return (unsigned char) x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) {
|
||||||
|
fe_cmov(t->yplusx, u->yplusx, b);
|
||||||
|
fe_cmov(t->yminusx, u->yminusx, b);
|
||||||
|
fe_cmov(t->xy2d, u->xy2d, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void select(ge_precomp *t, int pos, signed char b) {
|
||||||
|
ge_precomp minust;
|
||||||
|
unsigned char bnegative = negative(b);
|
||||||
|
unsigned char babs = b - (((-bnegative) & b) << 1);
|
||||||
|
fe_1(t->yplusx);
|
||||||
|
fe_1(t->yminusx);
|
||||||
|
fe_0(t->xy2d);
|
||||||
|
cmov(t, &base[pos][0], equal(babs, 1));
|
||||||
|
cmov(t, &base[pos][1], equal(babs, 2));
|
||||||
|
cmov(t, &base[pos][2], equal(babs, 3));
|
||||||
|
cmov(t, &base[pos][3], equal(babs, 4));
|
||||||
|
cmov(t, &base[pos][4], equal(babs, 5));
|
||||||
|
cmov(t, &base[pos][5], equal(babs, 6));
|
||||||
|
cmov(t, &base[pos][6], equal(babs, 7));
|
||||||
|
cmov(t, &base[pos][7], equal(babs, 8));
|
||||||
|
fe_copy(minust.yplusx, t->yminusx);
|
||||||
|
fe_copy(minust.yminusx, t->yplusx);
|
||||||
|
fe_neg(minust.xy2d, t->xy2d);
|
||||||
|
cmov(t, &minust, bnegative);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
h = a * B
|
||||||
|
where a = a[0]+256*a[1]+...+256^31 a[31]
|
||||||
|
B is the Ed25519 base point (x,4/5) with x positive.
|
||||||
|
|
||||||
|
Preconditions:
|
||||||
|
a[31] <= 127
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) {
|
||||||
|
signed char e[64];
|
||||||
|
signed char carry;
|
||||||
|
ge_p1p1 r;
|
||||||
|
ge_p2 s;
|
||||||
|
ge_precomp t;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
e[2 * i + 0] = (a[i] >> 0) & 15;
|
||||||
|
e[2 * i + 1] = (a[i] >> 4) & 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* each e[i] is between 0 and 15 */
|
||||||
|
/* e[63] is between 0 and 7 */
|
||||||
|
carry = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 63; ++i) {
|
||||||
|
e[i] += carry;
|
||||||
|
carry = e[i] + 8;
|
||||||
|
carry >>= 4;
|
||||||
|
e[i] -= carry << 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
e[63] += carry;
|
||||||
|
/* each e[i] is between -8 and 8 */
|
||||||
|
ge_p3_0(h);
|
||||||
|
|
||||||
|
for (i = 1; i < 64; i += 2) {
|
||||||
|
select(&t, i / 2, e[i]);
|
||||||
|
ge_madd(&r, h, &t);
|
||||||
|
ge_p1p1_to_p3(h, &r);
|
||||||
|
}
|
||||||
|
|
||||||
|
ge_p3_dbl(&r, h);
|
||||||
|
ge_p1p1_to_p2(&s, &r);
|
||||||
|
ge_p2_dbl(&r, &s);
|
||||||
|
ge_p1p1_to_p2(&s, &r);
|
||||||
|
ge_p2_dbl(&r, &s);
|
||||||
|
ge_p1p1_to_p2(&s, &r);
|
||||||
|
ge_p2_dbl(&r, &s);
|
||||||
|
ge_p1p1_to_p3(h, &r);
|
||||||
|
|
||||||
|
for (i = 0; i < 64; i += 2) {
|
||||||
|
select(&t, i / 2, e[i]);
|
||||||
|
ge_madd(&r, h, &t);
|
||||||
|
ge_p1p1_to_p3(h, &r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
r = p - q
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
|
||||||
|
fe t0;
|
||||||
|
|
||||||
|
fe_add(r->X, p->Y, p->X);
|
||||||
|
fe_sub(r->Y, p->Y, p->X);
|
||||||
|
fe_mul(r->Z, r->X, q->YminusX);
|
||||||
|
fe_mul(r->Y, r->Y, q->YplusX);
|
||||||
|
fe_mul(r->T, q->T2d, p->T);
|
||||||
|
fe_mul(r->X, p->Z, q->Z);
|
||||||
|
fe_add(t0, r->X, r->X);
|
||||||
|
fe_sub(r->X, r->Z, r->Y);
|
||||||
|
fe_add(r->Y, r->Z, r->Y);
|
||||||
|
fe_sub(r->Z, t0, r->T);
|
||||||
|
fe_add(r->T, t0, r->T);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ge_tobytes(unsigned char *s, const ge_p2 *h) {
|
||||||
|
fe recip;
|
||||||
|
fe x;
|
||||||
|
fe y;
|
||||||
|
fe_invert(recip, h->Z);
|
||||||
|
fe_mul(x, h->X, recip);
|
||||||
|
fe_mul(y, h->Y, recip);
|
||||||
|
fe_tobytes(s, y);
|
||||||
|
s[31] ^= fe_isnegative(x) << 7;
|
||||||
|
}
|
||||||
74
lib/ed25519/ge.h
Normal file
74
lib/ed25519/ge.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef GE_H
|
||||||
|
#define GE_H
|
||||||
|
|
||||||
|
#include "fe.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
ge means group element.
|
||||||
|
|
||||||
|
Here the group is the set of pairs (x,y) of field elements (see fe.h)
|
||||||
|
satisfying -x^2 + y^2 = 1 + d x^2y^2
|
||||||
|
where d = -121665/121666.
|
||||||
|
|
||||||
|
Representations:
|
||||||
|
ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
|
||||||
|
ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
|
||||||
|
ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
|
||||||
|
ge_precomp (Duif): (y+x,y-x,2dxy)
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fe X;
|
||||||
|
fe Y;
|
||||||
|
fe Z;
|
||||||
|
} ge_p2;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fe X;
|
||||||
|
fe Y;
|
||||||
|
fe Z;
|
||||||
|
fe T;
|
||||||
|
} ge_p3;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fe X;
|
||||||
|
fe Y;
|
||||||
|
fe Z;
|
||||||
|
fe T;
|
||||||
|
} ge_p1p1;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fe yplusx;
|
||||||
|
fe yminusx;
|
||||||
|
fe xy2d;
|
||||||
|
} ge_precomp;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fe YplusX;
|
||||||
|
fe YminusX;
|
||||||
|
fe Z;
|
||||||
|
fe T2d;
|
||||||
|
} ge_cached;
|
||||||
|
|
||||||
|
void ge_p3_tobytes(unsigned char *s, const ge_p3 *h);
|
||||||
|
void ge_tobytes(unsigned char *s, const ge_p2 *h);
|
||||||
|
int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s);
|
||||||
|
|
||||||
|
void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
|
||||||
|
void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
|
||||||
|
void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b);
|
||||||
|
void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
|
||||||
|
void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
|
||||||
|
void ge_scalarmult_base(ge_p3 *h, const unsigned char *a);
|
||||||
|
|
||||||
|
void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p);
|
||||||
|
void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p);
|
||||||
|
void ge_p2_0(ge_p2 *h);
|
||||||
|
void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p);
|
||||||
|
void ge_p3_0(ge_p3 *h);
|
||||||
|
void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p);
|
||||||
|
void ge_p3_to_cached(ge_cached *r, const ge_p3 *p);
|
||||||
|
void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p);
|
||||||
|
|
||||||
|
#endif
|
||||||
79
lib/ed25519/key_exchange.c
Normal file
79
lib/ed25519/key_exchange.c
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
#include "fe.h"
|
||||||
|
|
||||||
|
void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) {
|
||||||
|
unsigned char e[32];
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
fe x1;
|
||||||
|
fe x2;
|
||||||
|
fe z2;
|
||||||
|
fe x3;
|
||||||
|
fe z3;
|
||||||
|
fe tmp0;
|
||||||
|
fe tmp1;
|
||||||
|
|
||||||
|
int pos;
|
||||||
|
unsigned int swap;
|
||||||
|
unsigned int b;
|
||||||
|
|
||||||
|
/* copy the private key and make sure it's valid */
|
||||||
|
for (i = 0; i < 32; ++i) {
|
||||||
|
e[i] = private_key[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
e[0] &= 248;
|
||||||
|
e[31] &= 63;
|
||||||
|
e[31] |= 64;
|
||||||
|
|
||||||
|
/* unpack the public key and convert edwards to montgomery */
|
||||||
|
/* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */
|
||||||
|
fe_frombytes(x1, public_key);
|
||||||
|
fe_1(tmp1);
|
||||||
|
fe_add(tmp0, x1, tmp1);
|
||||||
|
fe_sub(tmp1, tmp1, x1);
|
||||||
|
fe_invert(tmp1, tmp1);
|
||||||
|
fe_mul(x1, tmp0, tmp1);
|
||||||
|
|
||||||
|
fe_1(x2);
|
||||||
|
fe_0(z2);
|
||||||
|
fe_copy(x3, x1);
|
||||||
|
fe_1(z3);
|
||||||
|
|
||||||
|
swap = 0;
|
||||||
|
for (pos = 254; pos >= 0; --pos) {
|
||||||
|
b = e[pos / 8] >> (pos & 7);
|
||||||
|
b &= 1;
|
||||||
|
swap ^= b;
|
||||||
|
fe_cswap(x2, x3, swap);
|
||||||
|
fe_cswap(z2, z3, swap);
|
||||||
|
swap = b;
|
||||||
|
|
||||||
|
/* from montgomery.h */
|
||||||
|
fe_sub(tmp0, x3, z3);
|
||||||
|
fe_sub(tmp1, x2, z2);
|
||||||
|
fe_add(x2, x2, z2);
|
||||||
|
fe_add(z2, x3, z3);
|
||||||
|
fe_mul(z3, tmp0, x2);
|
||||||
|
fe_mul(z2, z2, tmp1);
|
||||||
|
fe_sq(tmp0, tmp1);
|
||||||
|
fe_sq(tmp1, x2);
|
||||||
|
fe_add(x3, z3, z2);
|
||||||
|
fe_sub(z2, z3, z2);
|
||||||
|
fe_mul(x2, tmp1, tmp0);
|
||||||
|
fe_sub(tmp1, tmp1, tmp0);
|
||||||
|
fe_sq(z2, z2);
|
||||||
|
fe_mul121666(z3, tmp1);
|
||||||
|
fe_sq(x3, x3);
|
||||||
|
fe_add(tmp0, tmp0, z3);
|
||||||
|
fe_mul(z3, x1, z2);
|
||||||
|
fe_mul(z2, tmp1, tmp0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_cswap(x2, x3, swap);
|
||||||
|
fe_cswap(z2, z3, swap);
|
||||||
|
|
||||||
|
fe_invert(z2, z2);
|
||||||
|
fe_mul(x2, x2, z2);
|
||||||
|
fe_tobytes(shared_secret, x2);
|
||||||
|
}
|
||||||
16
lib/ed25519/keypair.c
Normal file
16
lib/ed25519/keypair.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
#include "sha512.h"
|
||||||
|
#include "ge.h"
|
||||||
|
|
||||||
|
|
||||||
|
void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) {
|
||||||
|
ge_p3 A;
|
||||||
|
|
||||||
|
sha512(seed, 32, private_key);
|
||||||
|
private_key[0] &= 248;
|
||||||
|
private_key[31] &= 63;
|
||||||
|
private_key[31] |= 64;
|
||||||
|
|
||||||
|
ge_scalarmult_base(&A, private_key);
|
||||||
|
ge_p3_tobytes(public_key, &A);
|
||||||
|
}
|
||||||
16
lib/ed25519/license.txt
Normal file
16
lib/ed25519/license.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Copyright (c) 2015 Orson Peters <orsonpeters@gmail.com>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied warranty. In no event will the
|
||||||
|
authors be held liable for any damages arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose, including commercial
|
||||||
|
applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not claim that you wrote the
|
||||||
|
original software. If you use this software in a product, an acknowledgment in the product
|
||||||
|
documentation would be appreciated but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be misrepresented as
|
||||||
|
being the original software.
|
||||||
|
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
1391
lib/ed25519/precomp_data.h
Normal file
1391
lib/ed25519/precomp_data.h
Normal file
File diff suppressed because it is too large
Load Diff
809
lib/ed25519/sc.c
Normal file
809
lib/ed25519/sc.c
Normal file
@@ -0,0 +1,809 @@
|
|||||||
|
#include "fixedint.h"
|
||||||
|
#include "sc.h"
|
||||||
|
|
||||||
|
static uint64_t load_3(const unsigned char *in) {
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
result = (uint64_t) in[0];
|
||||||
|
result |= ((uint64_t) in[1]) << 8;
|
||||||
|
result |= ((uint64_t) in[2]) << 16;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t load_4(const unsigned char *in) {
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
result = (uint64_t) in[0];
|
||||||
|
result |= ((uint64_t) in[1]) << 8;
|
||||||
|
result |= ((uint64_t) in[2]) << 16;
|
||||||
|
result |= ((uint64_t) in[3]) << 24;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Input:
|
||||||
|
s[0]+256*s[1]+...+256^63*s[63] = s
|
||||||
|
|
||||||
|
Output:
|
||||||
|
s[0]+256*s[1]+...+256^31*s[31] = s mod l
|
||||||
|
where l = 2^252 + 27742317777372353535851937790883648493.
|
||||||
|
Overwrites s in place.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void sc_reduce(unsigned char *s) {
|
||||||
|
int64_t s0 = 2097151 & load_3(s);
|
||||||
|
int64_t s1 = 2097151 & (load_4(s + 2) >> 5);
|
||||||
|
int64_t s2 = 2097151 & (load_3(s + 5) >> 2);
|
||||||
|
int64_t s3 = 2097151 & (load_4(s + 7) >> 7);
|
||||||
|
int64_t s4 = 2097151 & (load_4(s + 10) >> 4);
|
||||||
|
int64_t s5 = 2097151 & (load_3(s + 13) >> 1);
|
||||||
|
int64_t s6 = 2097151 & (load_4(s + 15) >> 6);
|
||||||
|
int64_t s7 = 2097151 & (load_3(s + 18) >> 3);
|
||||||
|
int64_t s8 = 2097151 & load_3(s + 21);
|
||||||
|
int64_t s9 = 2097151 & (load_4(s + 23) >> 5);
|
||||||
|
int64_t s10 = 2097151 & (load_3(s + 26) >> 2);
|
||||||
|
int64_t s11 = 2097151 & (load_4(s + 28) >> 7);
|
||||||
|
int64_t s12 = 2097151 & (load_4(s + 31) >> 4);
|
||||||
|
int64_t s13 = 2097151 & (load_3(s + 34) >> 1);
|
||||||
|
int64_t s14 = 2097151 & (load_4(s + 36) >> 6);
|
||||||
|
int64_t s15 = 2097151 & (load_3(s + 39) >> 3);
|
||||||
|
int64_t s16 = 2097151 & load_3(s + 42);
|
||||||
|
int64_t s17 = 2097151 & (load_4(s + 44) >> 5);
|
||||||
|
int64_t s18 = 2097151 & (load_3(s + 47) >> 2);
|
||||||
|
int64_t s19 = 2097151 & (load_4(s + 49) >> 7);
|
||||||
|
int64_t s20 = 2097151 & (load_4(s + 52) >> 4);
|
||||||
|
int64_t s21 = 2097151 & (load_3(s + 55) >> 1);
|
||||||
|
int64_t s22 = 2097151 & (load_4(s + 57) >> 6);
|
||||||
|
int64_t s23 = (load_4(s + 60) >> 3);
|
||||||
|
int64_t carry0;
|
||||||
|
int64_t carry1;
|
||||||
|
int64_t carry2;
|
||||||
|
int64_t carry3;
|
||||||
|
int64_t carry4;
|
||||||
|
int64_t carry5;
|
||||||
|
int64_t carry6;
|
||||||
|
int64_t carry7;
|
||||||
|
int64_t carry8;
|
||||||
|
int64_t carry9;
|
||||||
|
int64_t carry10;
|
||||||
|
int64_t carry11;
|
||||||
|
int64_t carry12;
|
||||||
|
int64_t carry13;
|
||||||
|
int64_t carry14;
|
||||||
|
int64_t carry15;
|
||||||
|
int64_t carry16;
|
||||||
|
|
||||||
|
s11 += s23 * 666643;
|
||||||
|
s12 += s23 * 470296;
|
||||||
|
s13 += s23 * 654183;
|
||||||
|
s14 -= s23 * 997805;
|
||||||
|
s15 += s23 * 136657;
|
||||||
|
s16 -= s23 * 683901;
|
||||||
|
s23 = 0;
|
||||||
|
s10 += s22 * 666643;
|
||||||
|
s11 += s22 * 470296;
|
||||||
|
s12 += s22 * 654183;
|
||||||
|
s13 -= s22 * 997805;
|
||||||
|
s14 += s22 * 136657;
|
||||||
|
s15 -= s22 * 683901;
|
||||||
|
s22 = 0;
|
||||||
|
s9 += s21 * 666643;
|
||||||
|
s10 += s21 * 470296;
|
||||||
|
s11 += s21 * 654183;
|
||||||
|
s12 -= s21 * 997805;
|
||||||
|
s13 += s21 * 136657;
|
||||||
|
s14 -= s21 * 683901;
|
||||||
|
s21 = 0;
|
||||||
|
s8 += s20 * 666643;
|
||||||
|
s9 += s20 * 470296;
|
||||||
|
s10 += s20 * 654183;
|
||||||
|
s11 -= s20 * 997805;
|
||||||
|
s12 += s20 * 136657;
|
||||||
|
s13 -= s20 * 683901;
|
||||||
|
s20 = 0;
|
||||||
|
s7 += s19 * 666643;
|
||||||
|
s8 += s19 * 470296;
|
||||||
|
s9 += s19 * 654183;
|
||||||
|
s10 -= s19 * 997805;
|
||||||
|
s11 += s19 * 136657;
|
||||||
|
s12 -= s19 * 683901;
|
||||||
|
s19 = 0;
|
||||||
|
s6 += s18 * 666643;
|
||||||
|
s7 += s18 * 470296;
|
||||||
|
s8 += s18 * 654183;
|
||||||
|
s9 -= s18 * 997805;
|
||||||
|
s10 += s18 * 136657;
|
||||||
|
s11 -= s18 * 683901;
|
||||||
|
s18 = 0;
|
||||||
|
carry6 = (s6 + (1 << 20)) >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry8 = (s8 + (1 << 20)) >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry10 = (s10 + (1 << 20)) >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry12 = (s12 + (1 << 20)) >> 21;
|
||||||
|
s13 += carry12;
|
||||||
|
s12 -= carry12 << 21;
|
||||||
|
carry14 = (s14 + (1 << 20)) >> 21;
|
||||||
|
s15 += carry14;
|
||||||
|
s14 -= carry14 << 21;
|
||||||
|
carry16 = (s16 + (1 << 20)) >> 21;
|
||||||
|
s17 += carry16;
|
||||||
|
s16 -= carry16 << 21;
|
||||||
|
carry7 = (s7 + (1 << 20)) >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry9 = (s9 + (1 << 20)) >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry11 = (s11 + (1 << 20)) >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
carry13 = (s13 + (1 << 20)) >> 21;
|
||||||
|
s14 += carry13;
|
||||||
|
s13 -= carry13 << 21;
|
||||||
|
carry15 = (s15 + (1 << 20)) >> 21;
|
||||||
|
s16 += carry15;
|
||||||
|
s15 -= carry15 << 21;
|
||||||
|
s5 += s17 * 666643;
|
||||||
|
s6 += s17 * 470296;
|
||||||
|
s7 += s17 * 654183;
|
||||||
|
s8 -= s17 * 997805;
|
||||||
|
s9 += s17 * 136657;
|
||||||
|
s10 -= s17 * 683901;
|
||||||
|
s17 = 0;
|
||||||
|
s4 += s16 * 666643;
|
||||||
|
s5 += s16 * 470296;
|
||||||
|
s6 += s16 * 654183;
|
||||||
|
s7 -= s16 * 997805;
|
||||||
|
s8 += s16 * 136657;
|
||||||
|
s9 -= s16 * 683901;
|
||||||
|
s16 = 0;
|
||||||
|
s3 += s15 * 666643;
|
||||||
|
s4 += s15 * 470296;
|
||||||
|
s5 += s15 * 654183;
|
||||||
|
s6 -= s15 * 997805;
|
||||||
|
s7 += s15 * 136657;
|
||||||
|
s8 -= s15 * 683901;
|
||||||
|
s15 = 0;
|
||||||
|
s2 += s14 * 666643;
|
||||||
|
s3 += s14 * 470296;
|
||||||
|
s4 += s14 * 654183;
|
||||||
|
s5 -= s14 * 997805;
|
||||||
|
s6 += s14 * 136657;
|
||||||
|
s7 -= s14 * 683901;
|
||||||
|
s14 = 0;
|
||||||
|
s1 += s13 * 666643;
|
||||||
|
s2 += s13 * 470296;
|
||||||
|
s3 += s13 * 654183;
|
||||||
|
s4 -= s13 * 997805;
|
||||||
|
s5 += s13 * 136657;
|
||||||
|
s6 -= s13 * 683901;
|
||||||
|
s13 = 0;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = (s0 + (1 << 20)) >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry2 = (s2 + (1 << 20)) >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry4 = (s4 + (1 << 20)) >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry6 = (s6 + (1 << 20)) >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry8 = (s8 + (1 << 20)) >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry10 = (s10 + (1 << 20)) >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry1 = (s1 + (1 << 20)) >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry3 = (s3 + (1 << 20)) >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry5 = (s5 + (1 << 20)) >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry7 = (s7 + (1 << 20)) >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry9 = (s9 + (1 << 20)) >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry11 = (s11 + (1 << 20)) >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = s0 >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry1 = s1 >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry2 = s2 >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry3 = s3 >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry4 = s4 >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry5 = s5 >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry6 = s6 >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry7 = s7 >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry8 = s8 >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry9 = s9 >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry10 = s10 >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry11 = s11 >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = s0 >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry1 = s1 >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry2 = s2 >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry3 = s3 >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry4 = s4 >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry5 = s5 >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry6 = s6 >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry7 = s7 >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry8 = s8 >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry9 = s9 >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry10 = s10 >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
|
||||||
|
s[0] = (unsigned char) (s0 >> 0);
|
||||||
|
s[1] = (unsigned char) (s0 >> 8);
|
||||||
|
s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
|
||||||
|
s[3] = (unsigned char) (s1 >> 3);
|
||||||
|
s[4] = (unsigned char) (s1 >> 11);
|
||||||
|
s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
|
||||||
|
s[6] = (unsigned char) (s2 >> 6);
|
||||||
|
s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
|
||||||
|
s[8] = (unsigned char) (s3 >> 1);
|
||||||
|
s[9] = (unsigned char) (s3 >> 9);
|
||||||
|
s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
|
||||||
|
s[11] = (unsigned char) (s4 >> 4);
|
||||||
|
s[12] = (unsigned char) (s4 >> 12);
|
||||||
|
s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
|
||||||
|
s[14] = (unsigned char) (s5 >> 7);
|
||||||
|
s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
|
||||||
|
s[16] = (unsigned char) (s6 >> 2);
|
||||||
|
s[17] = (unsigned char) (s6 >> 10);
|
||||||
|
s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
|
||||||
|
s[19] = (unsigned char) (s7 >> 5);
|
||||||
|
s[20] = (unsigned char) (s7 >> 13);
|
||||||
|
s[21] = (unsigned char) (s8 >> 0);
|
||||||
|
s[22] = (unsigned char) (s8 >> 8);
|
||||||
|
s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
|
||||||
|
s[24] = (unsigned char) (s9 >> 3);
|
||||||
|
s[25] = (unsigned char) (s9 >> 11);
|
||||||
|
s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
|
||||||
|
s[27] = (unsigned char) (s10 >> 6);
|
||||||
|
s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
|
||||||
|
s[29] = (unsigned char) (s11 >> 1);
|
||||||
|
s[30] = (unsigned char) (s11 >> 9);
|
||||||
|
s[31] = (unsigned char) (s11 >> 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Input:
|
||||||
|
a[0]+256*a[1]+...+256^31*a[31] = a
|
||||||
|
b[0]+256*b[1]+...+256^31*b[31] = b
|
||||||
|
c[0]+256*c[1]+...+256^31*c[31] = c
|
||||||
|
|
||||||
|
Output:
|
||||||
|
s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
|
||||||
|
where l = 2^252 + 27742317777372353535851937790883648493.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) {
|
||||||
|
int64_t a0 = 2097151 & load_3(a);
|
||||||
|
int64_t a1 = 2097151 & (load_4(a + 2) >> 5);
|
||||||
|
int64_t a2 = 2097151 & (load_3(a + 5) >> 2);
|
||||||
|
int64_t a3 = 2097151 & (load_4(a + 7) >> 7);
|
||||||
|
int64_t a4 = 2097151 & (load_4(a + 10) >> 4);
|
||||||
|
int64_t a5 = 2097151 & (load_3(a + 13) >> 1);
|
||||||
|
int64_t a6 = 2097151 & (load_4(a + 15) >> 6);
|
||||||
|
int64_t a7 = 2097151 & (load_3(a + 18) >> 3);
|
||||||
|
int64_t a8 = 2097151 & load_3(a + 21);
|
||||||
|
int64_t a9 = 2097151 & (load_4(a + 23) >> 5);
|
||||||
|
int64_t a10 = 2097151 & (load_3(a + 26) >> 2);
|
||||||
|
int64_t a11 = (load_4(a + 28) >> 7);
|
||||||
|
int64_t b0 = 2097151 & load_3(b);
|
||||||
|
int64_t b1 = 2097151 & (load_4(b + 2) >> 5);
|
||||||
|
int64_t b2 = 2097151 & (load_3(b + 5) >> 2);
|
||||||
|
int64_t b3 = 2097151 & (load_4(b + 7) >> 7);
|
||||||
|
int64_t b4 = 2097151 & (load_4(b + 10) >> 4);
|
||||||
|
int64_t b5 = 2097151 & (load_3(b + 13) >> 1);
|
||||||
|
int64_t b6 = 2097151 & (load_4(b + 15) >> 6);
|
||||||
|
int64_t b7 = 2097151 & (load_3(b + 18) >> 3);
|
||||||
|
int64_t b8 = 2097151 & load_3(b + 21);
|
||||||
|
int64_t b9 = 2097151 & (load_4(b + 23) >> 5);
|
||||||
|
int64_t b10 = 2097151 & (load_3(b + 26) >> 2);
|
||||||
|
int64_t b11 = (load_4(b + 28) >> 7);
|
||||||
|
int64_t c0 = 2097151 & load_3(c);
|
||||||
|
int64_t c1 = 2097151 & (load_4(c + 2) >> 5);
|
||||||
|
int64_t c2 = 2097151 & (load_3(c + 5) >> 2);
|
||||||
|
int64_t c3 = 2097151 & (load_4(c + 7) >> 7);
|
||||||
|
int64_t c4 = 2097151 & (load_4(c + 10) >> 4);
|
||||||
|
int64_t c5 = 2097151 & (load_3(c + 13) >> 1);
|
||||||
|
int64_t c6 = 2097151 & (load_4(c + 15) >> 6);
|
||||||
|
int64_t c7 = 2097151 & (load_3(c + 18) >> 3);
|
||||||
|
int64_t c8 = 2097151 & load_3(c + 21);
|
||||||
|
int64_t c9 = 2097151 & (load_4(c + 23) >> 5);
|
||||||
|
int64_t c10 = 2097151 & (load_3(c + 26) >> 2);
|
||||||
|
int64_t c11 = (load_4(c + 28) >> 7);
|
||||||
|
int64_t s0;
|
||||||
|
int64_t s1;
|
||||||
|
int64_t s2;
|
||||||
|
int64_t s3;
|
||||||
|
int64_t s4;
|
||||||
|
int64_t s5;
|
||||||
|
int64_t s6;
|
||||||
|
int64_t s7;
|
||||||
|
int64_t s8;
|
||||||
|
int64_t s9;
|
||||||
|
int64_t s10;
|
||||||
|
int64_t s11;
|
||||||
|
int64_t s12;
|
||||||
|
int64_t s13;
|
||||||
|
int64_t s14;
|
||||||
|
int64_t s15;
|
||||||
|
int64_t s16;
|
||||||
|
int64_t s17;
|
||||||
|
int64_t s18;
|
||||||
|
int64_t s19;
|
||||||
|
int64_t s20;
|
||||||
|
int64_t s21;
|
||||||
|
int64_t s22;
|
||||||
|
int64_t s23;
|
||||||
|
int64_t carry0;
|
||||||
|
int64_t carry1;
|
||||||
|
int64_t carry2;
|
||||||
|
int64_t carry3;
|
||||||
|
int64_t carry4;
|
||||||
|
int64_t carry5;
|
||||||
|
int64_t carry6;
|
||||||
|
int64_t carry7;
|
||||||
|
int64_t carry8;
|
||||||
|
int64_t carry9;
|
||||||
|
int64_t carry10;
|
||||||
|
int64_t carry11;
|
||||||
|
int64_t carry12;
|
||||||
|
int64_t carry13;
|
||||||
|
int64_t carry14;
|
||||||
|
int64_t carry15;
|
||||||
|
int64_t carry16;
|
||||||
|
int64_t carry17;
|
||||||
|
int64_t carry18;
|
||||||
|
int64_t carry19;
|
||||||
|
int64_t carry20;
|
||||||
|
int64_t carry21;
|
||||||
|
int64_t carry22;
|
||||||
|
|
||||||
|
s0 = c0 + a0 * b0;
|
||||||
|
s1 = c1 + a0 * b1 + a1 * b0;
|
||||||
|
s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
|
||||||
|
s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
|
||||||
|
s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
|
||||||
|
s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
|
||||||
|
s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
|
||||||
|
s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
|
||||||
|
s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0;
|
||||||
|
s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
|
||||||
|
s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
|
||||||
|
s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
|
||||||
|
s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
|
||||||
|
s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
|
||||||
|
s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3;
|
||||||
|
s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
|
||||||
|
s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
|
||||||
|
s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
|
||||||
|
s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
|
||||||
|
s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
|
||||||
|
s20 = a9 * b11 + a10 * b10 + a11 * b9;
|
||||||
|
s21 = a10 * b11 + a11 * b10;
|
||||||
|
s22 = a11 * b11;
|
||||||
|
s23 = 0;
|
||||||
|
carry0 = (s0 + (1 << 20)) >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry2 = (s2 + (1 << 20)) >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry4 = (s4 + (1 << 20)) >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry6 = (s6 + (1 << 20)) >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry8 = (s8 + (1 << 20)) >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry10 = (s10 + (1 << 20)) >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry12 = (s12 + (1 << 20)) >> 21;
|
||||||
|
s13 += carry12;
|
||||||
|
s12 -= carry12 << 21;
|
||||||
|
carry14 = (s14 + (1 << 20)) >> 21;
|
||||||
|
s15 += carry14;
|
||||||
|
s14 -= carry14 << 21;
|
||||||
|
carry16 = (s16 + (1 << 20)) >> 21;
|
||||||
|
s17 += carry16;
|
||||||
|
s16 -= carry16 << 21;
|
||||||
|
carry18 = (s18 + (1 << 20)) >> 21;
|
||||||
|
s19 += carry18;
|
||||||
|
s18 -= carry18 << 21;
|
||||||
|
carry20 = (s20 + (1 << 20)) >> 21;
|
||||||
|
s21 += carry20;
|
||||||
|
s20 -= carry20 << 21;
|
||||||
|
carry22 = (s22 + (1 << 20)) >> 21;
|
||||||
|
s23 += carry22;
|
||||||
|
s22 -= carry22 << 21;
|
||||||
|
carry1 = (s1 + (1 << 20)) >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry3 = (s3 + (1 << 20)) >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry5 = (s5 + (1 << 20)) >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry7 = (s7 + (1 << 20)) >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry9 = (s9 + (1 << 20)) >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry11 = (s11 + (1 << 20)) >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
carry13 = (s13 + (1 << 20)) >> 21;
|
||||||
|
s14 += carry13;
|
||||||
|
s13 -= carry13 << 21;
|
||||||
|
carry15 = (s15 + (1 << 20)) >> 21;
|
||||||
|
s16 += carry15;
|
||||||
|
s15 -= carry15 << 21;
|
||||||
|
carry17 = (s17 + (1 << 20)) >> 21;
|
||||||
|
s18 += carry17;
|
||||||
|
s17 -= carry17 << 21;
|
||||||
|
carry19 = (s19 + (1 << 20)) >> 21;
|
||||||
|
s20 += carry19;
|
||||||
|
s19 -= carry19 << 21;
|
||||||
|
carry21 = (s21 + (1 << 20)) >> 21;
|
||||||
|
s22 += carry21;
|
||||||
|
s21 -= carry21 << 21;
|
||||||
|
s11 += s23 * 666643;
|
||||||
|
s12 += s23 * 470296;
|
||||||
|
s13 += s23 * 654183;
|
||||||
|
s14 -= s23 * 997805;
|
||||||
|
s15 += s23 * 136657;
|
||||||
|
s16 -= s23 * 683901;
|
||||||
|
s23 = 0;
|
||||||
|
s10 += s22 * 666643;
|
||||||
|
s11 += s22 * 470296;
|
||||||
|
s12 += s22 * 654183;
|
||||||
|
s13 -= s22 * 997805;
|
||||||
|
s14 += s22 * 136657;
|
||||||
|
s15 -= s22 * 683901;
|
||||||
|
s22 = 0;
|
||||||
|
s9 += s21 * 666643;
|
||||||
|
s10 += s21 * 470296;
|
||||||
|
s11 += s21 * 654183;
|
||||||
|
s12 -= s21 * 997805;
|
||||||
|
s13 += s21 * 136657;
|
||||||
|
s14 -= s21 * 683901;
|
||||||
|
s21 = 0;
|
||||||
|
s8 += s20 * 666643;
|
||||||
|
s9 += s20 * 470296;
|
||||||
|
s10 += s20 * 654183;
|
||||||
|
s11 -= s20 * 997805;
|
||||||
|
s12 += s20 * 136657;
|
||||||
|
s13 -= s20 * 683901;
|
||||||
|
s20 = 0;
|
||||||
|
s7 += s19 * 666643;
|
||||||
|
s8 += s19 * 470296;
|
||||||
|
s9 += s19 * 654183;
|
||||||
|
s10 -= s19 * 997805;
|
||||||
|
s11 += s19 * 136657;
|
||||||
|
s12 -= s19 * 683901;
|
||||||
|
s19 = 0;
|
||||||
|
s6 += s18 * 666643;
|
||||||
|
s7 += s18 * 470296;
|
||||||
|
s8 += s18 * 654183;
|
||||||
|
s9 -= s18 * 997805;
|
||||||
|
s10 += s18 * 136657;
|
||||||
|
s11 -= s18 * 683901;
|
||||||
|
s18 = 0;
|
||||||
|
carry6 = (s6 + (1 << 20)) >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry8 = (s8 + (1 << 20)) >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry10 = (s10 + (1 << 20)) >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry12 = (s12 + (1 << 20)) >> 21;
|
||||||
|
s13 += carry12;
|
||||||
|
s12 -= carry12 << 21;
|
||||||
|
carry14 = (s14 + (1 << 20)) >> 21;
|
||||||
|
s15 += carry14;
|
||||||
|
s14 -= carry14 << 21;
|
||||||
|
carry16 = (s16 + (1 << 20)) >> 21;
|
||||||
|
s17 += carry16;
|
||||||
|
s16 -= carry16 << 21;
|
||||||
|
carry7 = (s7 + (1 << 20)) >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry9 = (s9 + (1 << 20)) >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry11 = (s11 + (1 << 20)) >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
carry13 = (s13 + (1 << 20)) >> 21;
|
||||||
|
s14 += carry13;
|
||||||
|
s13 -= carry13 << 21;
|
||||||
|
carry15 = (s15 + (1 << 20)) >> 21;
|
||||||
|
s16 += carry15;
|
||||||
|
s15 -= carry15 << 21;
|
||||||
|
s5 += s17 * 666643;
|
||||||
|
s6 += s17 * 470296;
|
||||||
|
s7 += s17 * 654183;
|
||||||
|
s8 -= s17 * 997805;
|
||||||
|
s9 += s17 * 136657;
|
||||||
|
s10 -= s17 * 683901;
|
||||||
|
s17 = 0;
|
||||||
|
s4 += s16 * 666643;
|
||||||
|
s5 += s16 * 470296;
|
||||||
|
s6 += s16 * 654183;
|
||||||
|
s7 -= s16 * 997805;
|
||||||
|
s8 += s16 * 136657;
|
||||||
|
s9 -= s16 * 683901;
|
||||||
|
s16 = 0;
|
||||||
|
s3 += s15 * 666643;
|
||||||
|
s4 += s15 * 470296;
|
||||||
|
s5 += s15 * 654183;
|
||||||
|
s6 -= s15 * 997805;
|
||||||
|
s7 += s15 * 136657;
|
||||||
|
s8 -= s15 * 683901;
|
||||||
|
s15 = 0;
|
||||||
|
s2 += s14 * 666643;
|
||||||
|
s3 += s14 * 470296;
|
||||||
|
s4 += s14 * 654183;
|
||||||
|
s5 -= s14 * 997805;
|
||||||
|
s6 += s14 * 136657;
|
||||||
|
s7 -= s14 * 683901;
|
||||||
|
s14 = 0;
|
||||||
|
s1 += s13 * 666643;
|
||||||
|
s2 += s13 * 470296;
|
||||||
|
s3 += s13 * 654183;
|
||||||
|
s4 -= s13 * 997805;
|
||||||
|
s5 += s13 * 136657;
|
||||||
|
s6 -= s13 * 683901;
|
||||||
|
s13 = 0;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = (s0 + (1 << 20)) >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry2 = (s2 + (1 << 20)) >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry4 = (s4 + (1 << 20)) >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry6 = (s6 + (1 << 20)) >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry8 = (s8 + (1 << 20)) >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry10 = (s10 + (1 << 20)) >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry1 = (s1 + (1 << 20)) >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry3 = (s3 + (1 << 20)) >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry5 = (s5 + (1 << 20)) >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry7 = (s7 + (1 << 20)) >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry9 = (s9 + (1 << 20)) >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry11 = (s11 + (1 << 20)) >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = s0 >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry1 = s1 >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry2 = s2 >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry3 = s3 >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry4 = s4 >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry5 = s5 >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry6 = s6 >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry7 = s7 >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry8 = s8 >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry9 = s9 >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry10 = s10 >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
carry11 = s11 >> 21;
|
||||||
|
s12 += carry11;
|
||||||
|
s11 -= carry11 << 21;
|
||||||
|
s0 += s12 * 666643;
|
||||||
|
s1 += s12 * 470296;
|
||||||
|
s2 += s12 * 654183;
|
||||||
|
s3 -= s12 * 997805;
|
||||||
|
s4 += s12 * 136657;
|
||||||
|
s5 -= s12 * 683901;
|
||||||
|
s12 = 0;
|
||||||
|
carry0 = s0 >> 21;
|
||||||
|
s1 += carry0;
|
||||||
|
s0 -= carry0 << 21;
|
||||||
|
carry1 = s1 >> 21;
|
||||||
|
s2 += carry1;
|
||||||
|
s1 -= carry1 << 21;
|
||||||
|
carry2 = s2 >> 21;
|
||||||
|
s3 += carry2;
|
||||||
|
s2 -= carry2 << 21;
|
||||||
|
carry3 = s3 >> 21;
|
||||||
|
s4 += carry3;
|
||||||
|
s3 -= carry3 << 21;
|
||||||
|
carry4 = s4 >> 21;
|
||||||
|
s5 += carry4;
|
||||||
|
s4 -= carry4 << 21;
|
||||||
|
carry5 = s5 >> 21;
|
||||||
|
s6 += carry5;
|
||||||
|
s5 -= carry5 << 21;
|
||||||
|
carry6 = s6 >> 21;
|
||||||
|
s7 += carry6;
|
||||||
|
s6 -= carry6 << 21;
|
||||||
|
carry7 = s7 >> 21;
|
||||||
|
s8 += carry7;
|
||||||
|
s7 -= carry7 << 21;
|
||||||
|
carry8 = s8 >> 21;
|
||||||
|
s9 += carry8;
|
||||||
|
s8 -= carry8 << 21;
|
||||||
|
carry9 = s9 >> 21;
|
||||||
|
s10 += carry9;
|
||||||
|
s9 -= carry9 << 21;
|
||||||
|
carry10 = s10 >> 21;
|
||||||
|
s11 += carry10;
|
||||||
|
s10 -= carry10 << 21;
|
||||||
|
|
||||||
|
s[0] = (unsigned char) (s0 >> 0);
|
||||||
|
s[1] = (unsigned char) (s0 >> 8);
|
||||||
|
s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
|
||||||
|
s[3] = (unsigned char) (s1 >> 3);
|
||||||
|
s[4] = (unsigned char) (s1 >> 11);
|
||||||
|
s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
|
||||||
|
s[6] = (unsigned char) (s2 >> 6);
|
||||||
|
s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
|
||||||
|
s[8] = (unsigned char) (s3 >> 1);
|
||||||
|
s[9] = (unsigned char) (s3 >> 9);
|
||||||
|
s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
|
||||||
|
s[11] = (unsigned char) (s4 >> 4);
|
||||||
|
s[12] = (unsigned char) (s4 >> 12);
|
||||||
|
s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
|
||||||
|
s[14] = (unsigned char) (s5 >> 7);
|
||||||
|
s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
|
||||||
|
s[16] = (unsigned char) (s6 >> 2);
|
||||||
|
s[17] = (unsigned char) (s6 >> 10);
|
||||||
|
s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
|
||||||
|
s[19] = (unsigned char) (s7 >> 5);
|
||||||
|
s[20] = (unsigned char) (s7 >> 13);
|
||||||
|
s[21] = (unsigned char) (s8 >> 0);
|
||||||
|
s[22] = (unsigned char) (s8 >> 8);
|
||||||
|
s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
|
||||||
|
s[24] = (unsigned char) (s9 >> 3);
|
||||||
|
s[25] = (unsigned char) (s9 >> 11);
|
||||||
|
s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
|
||||||
|
s[27] = (unsigned char) (s10 >> 6);
|
||||||
|
s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
|
||||||
|
s[29] = (unsigned char) (s11 >> 1);
|
||||||
|
s[30] = (unsigned char) (s11 >> 9);
|
||||||
|
s[31] = (unsigned char) (s11 >> 17);
|
||||||
|
}
|
||||||
12
lib/ed25519/sc.h
Normal file
12
lib/ed25519/sc.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef SC_H
|
||||||
|
#define SC_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
The set of scalars is \Z/l
|
||||||
|
where l = 2^252 + 27742317777372353535851937790883648493.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void sc_reduce(unsigned char *s);
|
||||||
|
void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c);
|
||||||
|
|
||||||
|
#endif
|
||||||
40
lib/ed25519/seed.c
Normal file
40
lib/ed25519/seed.c
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
|
||||||
|
#ifndef ED25519_NO_SEED
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wincrypt.h>
|
||||||
|
#else
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int ed25519_create_seed(unsigned char *seed) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
HCRYPTPROV prov;
|
||||||
|
|
||||||
|
if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CryptGenRandom(prov, 32, seed)) {
|
||||||
|
CryptReleaseContext(prov, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptReleaseContext(prov, 0);
|
||||||
|
#else
|
||||||
|
FILE *f = fopen("/dev/urandom", "rb");
|
||||||
|
|
||||||
|
if (f == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(seed, 1, 32, f);
|
||||||
|
fclose(f);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
275
lib/ed25519/sha512.c
Normal file
275
lib/ed25519/sha512.c
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
|
||||||
|
*
|
||||||
|
* LibTomCrypt is a library that provides various cryptographic
|
||||||
|
* algorithms in a highly modular and flexible manner.
|
||||||
|
*
|
||||||
|
* The library is free for all purposes without any express
|
||||||
|
* guarantee it works.
|
||||||
|
*
|
||||||
|
* Tom St Denis, tomstdenis@gmail.com, http://libtom.org
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fixedint.h"
|
||||||
|
#include "sha512.h"
|
||||||
|
|
||||||
|
/* the K array */
|
||||||
|
static const uint64_t K[80] = {
|
||||||
|
UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd),
|
||||||
|
UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
|
||||||
|
UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
|
||||||
|
UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
|
||||||
|
UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe),
|
||||||
|
UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
|
||||||
|
UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1),
|
||||||
|
UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
|
||||||
|
UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
|
||||||
|
UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
|
||||||
|
UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483),
|
||||||
|
UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
|
||||||
|
UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210),
|
||||||
|
UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
|
||||||
|
UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
|
||||||
|
UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
|
||||||
|
UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926),
|
||||||
|
UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
|
||||||
|
UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8),
|
||||||
|
UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
|
||||||
|
UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001),
|
||||||
|
UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
|
||||||
|
UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910),
|
||||||
|
UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
|
||||||
|
UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53),
|
||||||
|
UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
|
||||||
|
UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
|
||||||
|
UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
|
||||||
|
UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60),
|
||||||
|
UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
|
||||||
|
UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9),
|
||||||
|
UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
|
||||||
|
UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
|
||||||
|
UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
|
||||||
|
UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6),
|
||||||
|
UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
|
||||||
|
UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493),
|
||||||
|
UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
|
||||||
|
UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
|
||||||
|
UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Various logical functions */
|
||||||
|
|
||||||
|
#define ROR64c(x, y) \
|
||||||
|
( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \
|
||||||
|
((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF))
|
||||||
|
|
||||||
|
#define STORE64H(x, y) \
|
||||||
|
{ (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \
|
||||||
|
(y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \
|
||||||
|
(y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \
|
||||||
|
(y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); }
|
||||||
|
|
||||||
|
#define LOAD64H(x, y) \
|
||||||
|
{ x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \
|
||||||
|
(((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \
|
||||||
|
(((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \
|
||||||
|
(((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); }
|
||||||
|
|
||||||
|
|
||||||
|
#define Ch(x,y,z) (z ^ (x & (y ^ z)))
|
||||||
|
#define Maj(x,y,z) (((x | y) & z) | (x & y))
|
||||||
|
#define S(x, n) ROR64c(x, n)
|
||||||
|
#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n))
|
||||||
|
#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39))
|
||||||
|
#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41))
|
||||||
|
#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7))
|
||||||
|
#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6))
|
||||||
|
#ifndef MIN
|
||||||
|
#define MIN(x, y) ( ((x)<(y))?(x):(y) )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* compress 1024-bits */
|
||||||
|
static int sha512_compress(sha512_context *md, unsigned char *buf)
|
||||||
|
{
|
||||||
|
uint64_t S[8], W[80], t0, t1;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* copy state into S */
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
S[i] = md->state[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy the state into 1024-bits into W[0..15] */
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
LOAD64H(W[i], buf + (8*i));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fill W[16..79] */
|
||||||
|
for (i = 16; i < 80; i++) {
|
||||||
|
W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compress */
|
||||||
|
#define RND(a,b,c,d,e,f,g,h,i) \
|
||||||
|
t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
|
||||||
|
t1 = Sigma0(a) + Maj(a, b, c);\
|
||||||
|
d += t0; \
|
||||||
|
h = t0 + t1;
|
||||||
|
|
||||||
|
for (i = 0; i < 80; i += 8) {
|
||||||
|
RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0);
|
||||||
|
RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1);
|
||||||
|
RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2);
|
||||||
|
RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3);
|
||||||
|
RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4);
|
||||||
|
RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5);
|
||||||
|
RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6);
|
||||||
|
RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef RND
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* feedback */
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
md->state[i] = md->state[i] + S[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initialize the hash state
|
||||||
|
@param md The hash state you wish to initialize
|
||||||
|
@return 0 if successful
|
||||||
|
*/
|
||||||
|
int sha512_init(sha512_context * md) {
|
||||||
|
if (md == NULL) return 1;
|
||||||
|
|
||||||
|
md->curlen = 0;
|
||||||
|
md->length = 0;
|
||||||
|
md->state[0] = UINT64_C(0x6a09e667f3bcc908);
|
||||||
|
md->state[1] = UINT64_C(0xbb67ae8584caa73b);
|
||||||
|
md->state[2] = UINT64_C(0x3c6ef372fe94f82b);
|
||||||
|
md->state[3] = UINT64_C(0xa54ff53a5f1d36f1);
|
||||||
|
md->state[4] = UINT64_C(0x510e527fade682d1);
|
||||||
|
md->state[5] = UINT64_C(0x9b05688c2b3e6c1f);
|
||||||
|
md->state[6] = UINT64_C(0x1f83d9abfb41bd6b);
|
||||||
|
md->state[7] = UINT64_C(0x5be0cd19137e2179);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Process a block of memory though the hash
|
||||||
|
@param md The hash state
|
||||||
|
@param in The data to hash
|
||||||
|
@param inlen The length of the data (octets)
|
||||||
|
@return 0 if successful
|
||||||
|
*/
|
||||||
|
int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen)
|
||||||
|
{
|
||||||
|
size_t n;
|
||||||
|
size_t i;
|
||||||
|
int err;
|
||||||
|
if (md == NULL) return 1;
|
||||||
|
if (in == NULL) return 1;
|
||||||
|
if (md->curlen > sizeof(md->buf)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
while (inlen > 0) {
|
||||||
|
if (md->curlen == 0 && inlen >= 128) {
|
||||||
|
if ((err = sha512_compress (md, (unsigned char *)in)) != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
md->length += 128 * 8;
|
||||||
|
in += 128;
|
||||||
|
inlen -= 128;
|
||||||
|
} else {
|
||||||
|
n = MIN(inlen, (128 - md->curlen));
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
md->buf[i + md->curlen] = in[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
md->curlen += n;
|
||||||
|
in += n;
|
||||||
|
inlen -= n;
|
||||||
|
if (md->curlen == 128) {
|
||||||
|
if ((err = sha512_compress (md, md->buf)) != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
md->length += 8*128;
|
||||||
|
md->curlen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Terminate the hash to get the digest
|
||||||
|
@param md The hash state
|
||||||
|
@param out [out] The destination of the hash (64 bytes)
|
||||||
|
@return 0 if successful
|
||||||
|
*/
|
||||||
|
int sha512_final(sha512_context * md, unsigned char *out)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (md == NULL) return 1;
|
||||||
|
if (out == NULL) return 1;
|
||||||
|
|
||||||
|
if (md->curlen >= sizeof(md->buf)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* increase the length of the message */
|
||||||
|
md->length += md->curlen * UINT64_C(8);
|
||||||
|
|
||||||
|
/* append the '1' bit */
|
||||||
|
md->buf[md->curlen++] = (unsigned char)0x80;
|
||||||
|
|
||||||
|
/* if the length is currently above 112 bytes we append zeros
|
||||||
|
* then compress. Then we can fall back to padding zeros and length
|
||||||
|
* encoding like normal.
|
||||||
|
*/
|
||||||
|
if (md->curlen > 112) {
|
||||||
|
while (md->curlen < 128) {
|
||||||
|
md->buf[md->curlen++] = (unsigned char)0;
|
||||||
|
}
|
||||||
|
sha512_compress(md, md->buf);
|
||||||
|
md->curlen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pad upto 120 bytes of zeroes
|
||||||
|
* note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash
|
||||||
|
* > 2^64 bits of data... :-)
|
||||||
|
*/
|
||||||
|
while (md->curlen < 120) {
|
||||||
|
md->buf[md->curlen++] = (unsigned char)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* store length */
|
||||||
|
STORE64H(md->length, md->buf+120);
|
||||||
|
sha512_compress(md, md->buf);
|
||||||
|
|
||||||
|
/* copy output */
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
STORE64H(md->state[i], out+(8*i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sha512(const unsigned char *message, size_t message_len, unsigned char *out)
|
||||||
|
{
|
||||||
|
sha512_context ctx;
|
||||||
|
int ret;
|
||||||
|
if ((ret = sha512_init(&ctx))) return ret;
|
||||||
|
if ((ret = sha512_update(&ctx, message, message_len))) return ret;
|
||||||
|
if ((ret = sha512_final(&ctx, out))) return ret;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
21
lib/ed25519/sha512.h
Normal file
21
lib/ed25519/sha512.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SHA512_H
|
||||||
|
#define SHA512_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "fixedint.h"
|
||||||
|
|
||||||
|
/* state */
|
||||||
|
typedef struct sha512_context_ {
|
||||||
|
uint64_t length, state[8];
|
||||||
|
size_t curlen;
|
||||||
|
unsigned char buf[128];
|
||||||
|
} sha512_context;
|
||||||
|
|
||||||
|
|
||||||
|
int sha512_init(sha512_context * md);
|
||||||
|
int sha512_final(sha512_context * md, unsigned char *out);
|
||||||
|
int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen);
|
||||||
|
int sha512(const unsigned char *message, size_t message_len, unsigned char *out);
|
||||||
|
|
||||||
|
#endif
|
||||||
31
lib/ed25519/sign.c
Normal file
31
lib/ed25519/sign.c
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
#include "sha512.h"
|
||||||
|
#include "ge.h"
|
||||||
|
#include "sc.h"
|
||||||
|
|
||||||
|
|
||||||
|
void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) {
|
||||||
|
sha512_context hash;
|
||||||
|
unsigned char hram[64];
|
||||||
|
unsigned char r[64];
|
||||||
|
ge_p3 R;
|
||||||
|
|
||||||
|
|
||||||
|
sha512_init(&hash);
|
||||||
|
sha512_update(&hash, private_key + 32, 32);
|
||||||
|
sha512_update(&hash, message, message_len);
|
||||||
|
sha512_final(&hash, r);
|
||||||
|
|
||||||
|
sc_reduce(r);
|
||||||
|
ge_scalarmult_base(&R, r);
|
||||||
|
ge_p3_tobytes(signature, &R);
|
||||||
|
|
||||||
|
sha512_init(&hash);
|
||||||
|
sha512_update(&hash, signature, 32);
|
||||||
|
sha512_update(&hash, public_key, 32);
|
||||||
|
sha512_update(&hash, message, message_len);
|
||||||
|
sha512_final(&hash, hram);
|
||||||
|
|
||||||
|
sc_reduce(hram);
|
||||||
|
sc_muladd(signature + 32, hram, private_key, r);
|
||||||
|
}
|
||||||
77
lib/ed25519/verify.c
Normal file
77
lib/ed25519/verify.c
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "ed_25519.h"
|
||||||
|
#include "sha512.h"
|
||||||
|
#include "ge.h"
|
||||||
|
#include "sc.h"
|
||||||
|
|
||||||
|
static int consttime_equal(const unsigned char *x, const unsigned char *y) {
|
||||||
|
unsigned char r = 0;
|
||||||
|
|
||||||
|
r = x[0] ^ y[0];
|
||||||
|
#define F(i) r |= x[i] ^ y[i]
|
||||||
|
F(1);
|
||||||
|
F(2);
|
||||||
|
F(3);
|
||||||
|
F(4);
|
||||||
|
F(5);
|
||||||
|
F(6);
|
||||||
|
F(7);
|
||||||
|
F(8);
|
||||||
|
F(9);
|
||||||
|
F(10);
|
||||||
|
F(11);
|
||||||
|
F(12);
|
||||||
|
F(13);
|
||||||
|
F(14);
|
||||||
|
F(15);
|
||||||
|
F(16);
|
||||||
|
F(17);
|
||||||
|
F(18);
|
||||||
|
F(19);
|
||||||
|
F(20);
|
||||||
|
F(21);
|
||||||
|
F(22);
|
||||||
|
F(23);
|
||||||
|
F(24);
|
||||||
|
F(25);
|
||||||
|
F(26);
|
||||||
|
F(27);
|
||||||
|
F(28);
|
||||||
|
F(29);
|
||||||
|
F(30);
|
||||||
|
F(31);
|
||||||
|
#undef F
|
||||||
|
|
||||||
|
return !r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) {
|
||||||
|
unsigned char h[64];
|
||||||
|
unsigned char checker[32];
|
||||||
|
sha512_context hash;
|
||||||
|
ge_p3 A;
|
||||||
|
ge_p2 R;
|
||||||
|
|
||||||
|
if (signature[63] & 224) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ge_frombytes_negate_vartime(&A, public_key) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sha512_init(&hash);
|
||||||
|
sha512_update(&hash, signature, 32);
|
||||||
|
sha512_update(&hash, public_key, 32);
|
||||||
|
sha512_update(&hash, message, message_len);
|
||||||
|
sha512_final(&hash, h);
|
||||||
|
|
||||||
|
sc_reduce(h);
|
||||||
|
ge_double_scalarmult_vartime(&R, h, &A, signature + 32);
|
||||||
|
ge_tobytes(checker, &R);
|
||||||
|
|
||||||
|
if (!consttime_equal(checker, signature)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
21
license.txt
Normal file
21
license.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Scott Powell
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
94
platformio.ini
Normal file
94
platformio.ini
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[arduino_base]
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
SPI
|
||||||
|
Wire
|
||||||
|
jgromes/RadioLib @ ^6.3.0
|
||||||
|
rweather/Crypto @ ^0.4.0
|
||||||
|
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1
|
||||||
|
build_src_filter = +<*.cpp> +<helpers/*.cpp>
|
||||||
|
|
||||||
|
[esp32_base]
|
||||||
|
extends = arduino_base
|
||||||
|
platform = espressif32
|
||||||
|
monitor_filters = esp32_exception_decoder
|
||||||
|
build_src_filter = ${arduino_base.build_src_filter}
|
||||||
|
|
||||||
|
[esp32_S3]
|
||||||
|
extends = esp32_base
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
build_src_filter = ${esp32_base.build_src_filter}
|
||||||
|
build_flags =
|
||||||
|
${esp32_base.build_flags}
|
||||||
|
lib_deps =
|
||||||
|
${esp32_base.lib_deps}
|
||||||
|
|
||||||
|
[Heltec_stick_lite]
|
||||||
|
extends = esp32_base
|
||||||
|
board = heltec_wireless_stick_lite
|
||||||
|
|
||||||
|
[Heltec_lora32_v2]
|
||||||
|
extends = esp32_base
|
||||||
|
board = heltec_wifi_lora_32_V2
|
||||||
|
|
||||||
|
; ================
|
||||||
|
[Heltec_lora32_v3]
|
||||||
|
extends = esp32_base
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
build_flags =
|
||||||
|
${esp32_base.build_flags}
|
||||||
|
-D HELTEC_LORA_V3
|
||||||
|
build_src_filter = ${esp32_base.build_src_filter}
|
||||||
|
|
||||||
|
[env:Heltec_v3_ping_server]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/ping_server/main.cpp>
|
||||||
|
|
||||||
|
[env:Heltec_v3_ping_client]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/ping_client/main.cpp>
|
||||||
|
|
||||||
|
[env:Heltec_v3_repeater]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_flags =
|
||||||
|
${Heltec_lora32_v3.build_flags}
|
||||||
|
; -D NODE_ID=2
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater/main.cpp>
|
||||||
|
|
||||||
|
[env:Heltec_v3_chat_alice]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_flags =
|
||||||
|
${Heltec_lora32_v3.build_flags}
|
||||||
|
-D RUN_AS_ALICE=true
|
||||||
|
; -D NODE_ID=1
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_secure_chat/main.cpp>
|
||||||
|
|
||||||
|
[env:Heltec_v3_chat_bob]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_flags =
|
||||||
|
${Heltec_lora32_v3.build_flags}
|
||||||
|
-D RUN_AS_ALICE=false
|
||||||
|
; -D NODE_ID=3
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_secure_chat/main.cpp>
|
||||||
|
|
||||||
|
[env:Heltec_v3_test_admin]
|
||||||
|
extends = Heltec_lora32_v3
|
||||||
|
build_flags =
|
||||||
|
${Heltec_lora32_v3.build_flags}
|
||||||
|
; -D NODE_ID=1
|
||||||
|
build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/test_admin/main.cpp>
|
||||||
|
|
||||||
|
; =============
|
||||||
|
|
||||||
29
src/Destination.cpp
Normal file
29
src/Destination.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "Destination.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
Destination::Destination(const Identity& identity, const char* name) {
|
||||||
|
uint8_t name_hash[MAX_HASH_SIZE];
|
||||||
|
Utils::sha256(name_hash, MAX_HASH_SIZE, (const uint8_t *)name, strlen(name));
|
||||||
|
|
||||||
|
Utils::sha256(hash, MAX_HASH_SIZE, name_hash, MAX_HASH_SIZE, identity.pub_key, PUB_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Destination::Destination(const char* name) {
|
||||||
|
uint8_t name_hash[MAX_HASH_SIZE];
|
||||||
|
Utils::sha256(name_hash, MAX_HASH_SIZE, (const uint8_t *)name, strlen(name));
|
||||||
|
|
||||||
|
Utils::sha256(hash, MAX_HASH_SIZE, name_hash, MAX_HASH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Destination::Destination() {
|
||||||
|
memset(hash, 0, MAX_HASH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Destination::matches(const uint8_t* other_hash) {
|
||||||
|
return memcmp(hash, other_hash, MAX_HASH_SIZE) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/Destination.h
Normal file
25
src/Destination.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <Identity.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Represents an end-point in the mesh, identified by a truncated SHA256 hash. (of DEST_HASH_SIZE)
|
||||||
|
* The hash is either from just a 'name' (C-string), and these can be thought of as 'broadcast' addresses,
|
||||||
|
* or can be the hash of name + Identity.public_key
|
||||||
|
*/
|
||||||
|
class Destination {
|
||||||
|
public:
|
||||||
|
uint8_t hash[MAX_HASH_SIZE];
|
||||||
|
|
||||||
|
Destination(const Identity& identity, const char* name);
|
||||||
|
Destination(const char* name);
|
||||||
|
Destination(const uint8_t desthash[]) { memcpy(hash, desthash, MAX_HASH_SIZE); }
|
||||||
|
Destination();
|
||||||
|
|
||||||
|
bool matches(const uint8_t* other_hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
161
src/Dispatcher.cpp
Normal file
161
src/Dispatcher.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "Dispatcher.h"
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
void Dispatcher::begin() {
|
||||||
|
_radio->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Dispatcher::getAirtimeBudgetFactor() const {
|
||||||
|
return 5.0; // default, 16.6% (1/6th)
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::loop() {
|
||||||
|
if (outbound) { // waiting for outbound send to be completed
|
||||||
|
if (_radio->isSendComplete()) {
|
||||||
|
long t = _ms->getMillis() - outbound_start;
|
||||||
|
total_air_time += t; // keep track of how much air time we are using
|
||||||
|
//Serial.print(" airtime="); Serial.println(t);
|
||||||
|
|
||||||
|
// will need radio silence up to next_tx_time
|
||||||
|
next_tx_time = futureMillis(t * getAirtimeBudgetFactor());
|
||||||
|
|
||||||
|
_radio->onSendFinished();
|
||||||
|
onPacketSent(outbound);
|
||||||
|
outbound = NULL;
|
||||||
|
} else if (millisHasNowPassed(outbound_expiry)) {
|
||||||
|
MESH_DEBUG_PRINTLN("Dispatcher::loop(): WARNING: outbound packed send timed out!");
|
||||||
|
//Serial.println(" timed out");
|
||||||
|
|
||||||
|
_radio->onSendFinished();
|
||||||
|
releasePacket(outbound); // return to pool
|
||||||
|
outbound = NULL;
|
||||||
|
} else {
|
||||||
|
return; // can't do any more radio activity until send is complete or timed out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRecv();
|
||||||
|
checkSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::onPacketSent(Packet* packet) {
|
||||||
|
releasePacket(packet); // default behaviour, return packet to pool
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::checkRecv() {
|
||||||
|
Packet* pkt;
|
||||||
|
{
|
||||||
|
uint8_t raw[MAX_TRANS_UNIT];
|
||||||
|
int len = _radio->recvRaw(raw, MAX_TRANS_UNIT);
|
||||||
|
if (len > 0) {
|
||||||
|
pkt = _mgr->allocNew();
|
||||||
|
if (pkt == NULL) {
|
||||||
|
MESH_DEBUG_PRINTLN("Dispatcher::checkRecv(): WARNING: received data, no unused packets available!");
|
||||||
|
} else {
|
||||||
|
int i = 0;
|
||||||
|
#ifdef NODE_ID
|
||||||
|
uint8_t sender_id = raw[i++];
|
||||||
|
if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1
|
||||||
|
} else {
|
||||||
|
_mgr->free(pkt); // put back into pool
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//Serial.print("LoRa recv: len="); Serial.println(len);
|
||||||
|
|
||||||
|
pkt->header = raw[i++];
|
||||||
|
pkt->path_len = raw[i++];
|
||||||
|
|
||||||
|
if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) {
|
||||||
|
MESH_DEBUG_PRINTLN("Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", len);
|
||||||
|
_mgr->free(pkt); // put back into pool
|
||||||
|
pkt = NULL;
|
||||||
|
} else {
|
||||||
|
memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len;
|
||||||
|
|
||||||
|
pkt->payload_len = len - i; // payload is remainder
|
||||||
|
memcpy(pkt->payload, &raw[i], pkt->payload_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pkt = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pkt) {
|
||||||
|
DispatcherAction action = onRecvPacket(pkt);
|
||||||
|
if (action == ACTION_RELEASE) {
|
||||||
|
_mgr->free(pkt);
|
||||||
|
} else if (action == ACTION_MANUAL_HOLD) {
|
||||||
|
// sub-class is wanting to manually hold Packet instance, and call releasePacket() at appropriate time
|
||||||
|
} else { // ACTION_RETRANSMIT*
|
||||||
|
uint8_t priority = (action >> 24) - 1;
|
||||||
|
uint32_t _delay = action & 0xFFFFFF;
|
||||||
|
|
||||||
|
_mgr->queueOutbound(pkt, priority, futureMillis(_delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::checkSend() {
|
||||||
|
if (_mgr->getOutboundCount() == 0) return; // nothing waiting to send
|
||||||
|
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
|
||||||
|
if (_radio->isReceiving()) return; // check if radio is currently mid-receive
|
||||||
|
|
||||||
|
outbound = _mgr->getNextOutbound(_ms->getMillis());
|
||||||
|
if (outbound) {
|
||||||
|
int len = 0;
|
||||||
|
uint8_t raw[MAX_TRANS_UNIT];
|
||||||
|
|
||||||
|
#ifdef NODE_ID
|
||||||
|
raw[len++] = NODE_ID;
|
||||||
|
#endif
|
||||||
|
raw[len++] = outbound->header;
|
||||||
|
raw[len++] = outbound->path_len;
|
||||||
|
memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len;
|
||||||
|
|
||||||
|
if (len + outbound->payload_len > MAX_TRANS_UNIT) {
|
||||||
|
MESH_DEBUG_PRINTLN("Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", len + outbound->payload_len);
|
||||||
|
_mgr->free(outbound);
|
||||||
|
outbound = NULL;
|
||||||
|
} else {
|
||||||
|
memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len;
|
||||||
|
|
||||||
|
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
|
||||||
|
outbound_start = _ms->getMillis();
|
||||||
|
_radio->startSendRaw(raw, len);
|
||||||
|
outbound_expiry = futureMillis(max_airtime);
|
||||||
|
|
||||||
|
//Serial.print("LoRa send: len="); Serial.print(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet* Dispatcher::obtainNewPacket() {
|
||||||
|
return _mgr->allocNew(); // TODO: zero out all fields
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::releasePacket(Packet* packet) {
|
||||||
|
_mgr->free(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
|
||||||
|
if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) {
|
||||||
|
MESH_DEBUG_PRINTLN("Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", (uint32_t) packet->path_len, (uint32_t) packet->payload_len);
|
||||||
|
_mgr->free(packet);
|
||||||
|
} else {
|
||||||
|
_mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function -- handles the case where millis() wraps around back to zero
|
||||||
|
// 2's complement arithmetic will handle any unsigned subtraction up to HALF the word size (32-bits in this case)
|
||||||
|
bool Dispatcher::millisHasNowPassed(unsigned long timestamp) const {
|
||||||
|
return (long)(_ms->getMillis() - timestamp) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long Dispatcher::futureMillis(int millis_from_now) const {
|
||||||
|
return _ms->getMillis() + millis_from_now;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
129
src/Dispatcher.h
Normal file
129
src/Dispatcher.h
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <Identity.h>
|
||||||
|
#include <Packet.h>
|
||||||
|
#include <Utils.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Abstraction of local/volatile clock with Millisecond granularity.
|
||||||
|
*/
|
||||||
|
class MillisecondClock {
|
||||||
|
public:
|
||||||
|
virtual unsigned long getMillis() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Abstraction of this device's packet radio.
|
||||||
|
*/
|
||||||
|
class Radio {
|
||||||
|
public:
|
||||||
|
virtual void begin() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief polls for incoming raw packet.
|
||||||
|
* \param bytes destination to store incoming raw packet.
|
||||||
|
* \param sz maximum packet size allowed.
|
||||||
|
* \returns 0 if no incoming data, otherwise length of complete packet received.
|
||||||
|
*/
|
||||||
|
virtual int recvRaw(uint8_t* bytes, int sz) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns estimated transmit air-time needed for packet of 'len_bytes', in milliseconds.
|
||||||
|
*/
|
||||||
|
virtual uint32_t getEstAirtimeFor(int len_bytes) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief starts the raw packet send. (no wait)
|
||||||
|
* \param bytes the raw packet data
|
||||||
|
* \param len the length in bytes
|
||||||
|
*/
|
||||||
|
virtual void startSendRaw(const uint8_t* bytes, int len) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns true if the previous 'startSendRaw()' completed successfully.
|
||||||
|
*/
|
||||||
|
virtual bool isSendComplete() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief a hook for doing any necessary clean up after transmit.
|
||||||
|
*/
|
||||||
|
virtual void onSendFinished() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns true if the radio is currently mid-receive of a packet.
|
||||||
|
*/
|
||||||
|
virtual bool isReceiving() { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief An abstraction for managing instances of Packets (eg. in a static pool),
|
||||||
|
* and for managing the outbound packet queue.
|
||||||
|
*/
|
||||||
|
class PacketManager {
|
||||||
|
public:
|
||||||
|
virtual Packet* allocNew() = 0;
|
||||||
|
virtual void free(Packet* packet) = 0;
|
||||||
|
|
||||||
|
virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||||
|
virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority
|
||||||
|
virtual int getOutboundCount() const = 0;
|
||||||
|
virtual int getFreeCount() const = 0;
|
||||||
|
virtual Packet* getOutboundByIdx(int i) = 0;
|
||||||
|
virtual Packet* removeOutboundByIdx(int i) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t DispatcherAction;
|
||||||
|
|
||||||
|
#define ACTION_RELEASE (0)
|
||||||
|
#define ACTION_MANUAL_HOLD (1)
|
||||||
|
#define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24)
|
||||||
|
#define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The low-level task that manages detecting incoming Packets, and the queueing
|
||||||
|
* and scheduling of outbound Packets.
|
||||||
|
*/
|
||||||
|
class Dispatcher {
|
||||||
|
Packet* outbound; // current outbound packet
|
||||||
|
unsigned long outbound_expiry, outbound_start, total_air_time;
|
||||||
|
unsigned long next_tx_time;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PacketManager* _mgr;
|
||||||
|
Radio* _radio;
|
||||||
|
MillisecondClock* _ms;
|
||||||
|
|
||||||
|
Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr)
|
||||||
|
: _radio(&radio), _ms(&ms), _mgr(&mgr)
|
||||||
|
{
|
||||||
|
outbound = NULL; total_air_time = 0; next_tx_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
|
||||||
|
virtual void onPacketSent(Packet* packet);
|
||||||
|
virtual float getAirtimeBudgetFactor() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void begin();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
Packet* obtainNewPacket();
|
||||||
|
void releasePacket(Packet* packet);
|
||||||
|
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
|
||||||
|
|
||||||
|
unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds
|
||||||
|
|
||||||
|
// helper methods
|
||||||
|
bool millisHasNowPassed(unsigned long timestamp) const;
|
||||||
|
unsigned long futureMillis(int millis_from_now) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkRecv();
|
||||||
|
void checkSend();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
70
src/Identity.cpp
Normal file
70
src/Identity.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#include "Identity.h"
|
||||||
|
#include <string.h>
|
||||||
|
#define ED25519_NO_SEED 1
|
||||||
|
#include <ed_25519.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
Identity::Identity() {
|
||||||
|
memset(pub_key, 0, sizeof(pub_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
Identity::Identity(const char* pub_hex) {
|
||||||
|
Utils::fromHex(pub_key, PUB_KEY_SIZE, pub_hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const {
|
||||||
|
return ed25519_verify(sig, message, msg_len, pub_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Identity::readFrom(Stream& s) {
|
||||||
|
return (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Identity::writeTo(Stream& s) const {
|
||||||
|
return (s.write(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Identity::printTo(Stream& s) const {
|
||||||
|
Utils::printHex(s, pub_key, PUB_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalIdentity::LocalIdentity() {
|
||||||
|
memset(prv_key, 0, sizeof(prv_key));
|
||||||
|
}
|
||||||
|
LocalIdentity::LocalIdentity(const char* prv_hex, const char* pub_hex) : Identity(pub_hex) {
|
||||||
|
Utils::fromHex(prv_key, PRV_KEY_SIZE, prv_hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalIdentity::LocalIdentity(RNG* rng) {
|
||||||
|
uint8_t seed[SEED_SIZE];
|
||||||
|
rng->random(seed, SEED_SIZE);
|
||||||
|
ed25519_create_keypair(pub_key, prv_key, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalIdentity::readFrom(Stream& s) {
|
||||||
|
bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalIdentity::writeTo(Stream& s) const {
|
||||||
|
bool success = (s.write(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
success = success && (s.write(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalIdentity::printTo(Stream& s) const {
|
||||||
|
s.print("pub_key: "); Utils::printHex(s, pub_key, PUB_KEY_SIZE); s.println();
|
||||||
|
s.print("prv_key: "); Utils::printHex(s, prv_key, PRV_KEY_SIZE); s.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) const {
|
||||||
|
ed25519_sign(sig, message, msg_len, pub_key, prv_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalIdentity::calcSharedSecret(uint8_t* secret, const Identity& other) {
|
||||||
|
ed25519_key_exchange(secret, other.pub_key, prv_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
src/Identity.h
Normal file
75
src/Identity.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Utils.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief An identity in the mesh, with given Ed25519 public key, ie. a party whose signatures can be VERIFIED.
|
||||||
|
*/
|
||||||
|
class Identity {
|
||||||
|
public:
|
||||||
|
uint8_t pub_key[PUB_KEY_SIZE];
|
||||||
|
|
||||||
|
Identity();
|
||||||
|
Identity(const char* pub_hex);
|
||||||
|
Identity(const uint8_t* _pub) { memcpy(pub_key, _pub, PUB_KEY_SIZE); }
|
||||||
|
|
||||||
|
int copyHashTo(uint8_t* dest) const {
|
||||||
|
memcpy(dest, pub_key, PATH_HASH_SIZE); // hash is just prefix of pub_key
|
||||||
|
return PATH_HASH_SIZE;
|
||||||
|
}
|
||||||
|
bool isHashMatch(const uint8_t* hash) const {
|
||||||
|
return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Performs Ed25519 signature verification.
|
||||||
|
* \param sig IN - must be SIGNATURE_SIZE buffer.
|
||||||
|
* \param message IN - the original message which was signed.
|
||||||
|
* \param msg_len IN - the length in bytes of message.
|
||||||
|
* \returns true, if signature is valid.
|
||||||
|
*/
|
||||||
|
bool verify(const uint8_t* sig, const uint8_t* message, int msg_len) const;
|
||||||
|
|
||||||
|
bool matches(const Identity& other) const { return memcmp(pub_key, other.pub_key, PUB_KEY_SIZE) == 0; }
|
||||||
|
bool matches(const uint8_t* other_pubkey) const { return memcmp(pub_key, other_pubkey, PUB_KEY_SIZE) == 0; }
|
||||||
|
|
||||||
|
bool readFrom(Stream& s);
|
||||||
|
bool writeTo(Stream& s) const;
|
||||||
|
void printTo(Stream& s) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief An Identity generated on THIS device, ie. with public/private Ed25519 key pair being on this device.
|
||||||
|
*/
|
||||||
|
class LocalIdentity : public Identity {
|
||||||
|
uint8_t prv_key[PRV_KEY_SIZE];
|
||||||
|
public:
|
||||||
|
LocalIdentity();
|
||||||
|
LocalIdentity(const char* prv_hex, const char* pub_hex);
|
||||||
|
LocalIdentity(RNG* rng); // create new random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Ed25519 digital signature.
|
||||||
|
* \param sig OUT - must be SIGNATURE_SIZE buffer.
|
||||||
|
* \param message IN - the raw message bytes to sign.
|
||||||
|
* \param msg_len IN - the length in bytes of message.
|
||||||
|
*/
|
||||||
|
void sign(uint8_t* sig, const uint8_t* message, int msg_len) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief the ECDH key exhange, with Ed25519 public key transposed to Ex25519.
|
||||||
|
* \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes)
|
||||||
|
* \param other IN - the second party in the exchange.
|
||||||
|
*/
|
||||||
|
void calcSharedSecret(uint8_t* secret, const Identity& other);
|
||||||
|
|
||||||
|
bool readFrom(Stream& s);
|
||||||
|
bool writeTo(Stream& s) const;
|
||||||
|
void printTo(Stream& s) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
392
src/Mesh.cpp
Normal file
392
src/Mesh.cpp
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
#include "Mesh.h"
|
||||||
|
//#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
void Mesh::begin() {
|
||||||
|
Dispatcher::begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::loop() {
|
||||||
|
Dispatcher::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mesh::allowPacketForward(mesh::Packet* packet) {
|
||||||
|
return false; // by default, Transport NOT enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
int Mesh::searchPeersByHash(const uint8_t* hash) {
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches) {
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||||
|
if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version
|
||||||
|
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): unsupported packet version");
|
||||||
|
return ACTION_RELEASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
||||||
|
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||||
|
// remove our hash from 'path', then re-broadcast
|
||||||
|
pkt->path_len -= PATH_HASH_SIZE;
|
||||||
|
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len);
|
||||||
|
return ACTION_RETRANSMIT(0); // Routed traffic is HIGHEST priority
|
||||||
|
}
|
||||||
|
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatcherAction action = ACTION_RELEASE;
|
||||||
|
|
||||||
|
switch (pkt->getPayloadType()) {
|
||||||
|
case PAYLOAD_TYPE_ACK: {
|
||||||
|
int i = 0;
|
||||||
|
uint32_t ack_crc;
|
||||||
|
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
|
||||||
|
if (i > pkt->payload_len) {
|
||||||
|
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete ACK packet");
|
||||||
|
} else {
|
||||||
|
onAckRecv(pkt, ack_crc);
|
||||||
|
action = routeRecvPacket(pkt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAYLOAD_TYPE_PATH:
|
||||||
|
case PAYLOAD_TYPE_REQ:
|
||||||
|
case PAYLOAD_TYPE_RESPONSE:
|
||||||
|
case PAYLOAD_TYPE_TXT_MSG: {
|
||||||
|
int i = 0;
|
||||||
|
uint8_t dest_hash = pkt->payload[i++];
|
||||||
|
uint8_t src_hash = pkt->payload[i++];
|
||||||
|
|
||||||
|
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
|
||||||
|
if (i + 2 >= pkt->payload_len) {
|
||||||
|
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
|
||||||
|
} else {
|
||||||
|
if (self_id.isHashMatch(&dest_hash)) {
|
||||||
|
// scan contacts DB, for all matching hashes of 'src_hash' (max 4 matches supported ATM)
|
||||||
|
int num = searchPeersByHash(&src_hash);
|
||||||
|
// for each matching contact, try to decrypt data
|
||||||
|
for (int j = 0; j < num; j++) {
|
||||||
|
uint8_t secret[PUB_KEY_SIZE];
|
||||||
|
getPeerSharedSecret(secret, j);
|
||||||
|
|
||||||
|
// decrypt, checking MAC is valid
|
||||||
|
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||||
|
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
|
||||||
|
if (len > 0) { // success!
|
||||||
|
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||||
|
int k = 0;
|
||||||
|
uint8_t path_len = data[k++];
|
||||||
|
uint8_t* path = &data[k]; k += path_len;
|
||||||
|
uint8_t extra_type = data[k++];
|
||||||
|
uint8_t* extra = &data[k];
|
||||||
|
uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!)
|
||||||
|
onPeerPathRecv(pkt, j, path, path_len, extra_type, extra, extra_len);
|
||||||
|
} else {
|
||||||
|
onPeerDataRecv(pkt, pkt->getPayloadType(), j, data, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action = routeRecvPacket(pkt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAYLOAD_TYPE_ANON_REQ: {
|
||||||
|
int i = 0;
|
||||||
|
uint8_t dest_hash = pkt->payload[i++];
|
||||||
|
uint8_t* sender_pub_key = &pkt->payload[i]; i += PUB_KEY_SIZE;
|
||||||
|
|
||||||
|
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
|
||||||
|
if (i + 2 >= pkt->payload_len) {
|
||||||
|
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
|
||||||
|
} else {
|
||||||
|
if (self_id.isHashMatch(&dest_hash)) {
|
||||||
|
Identity sender(sender_pub_key);
|
||||||
|
|
||||||
|
uint8_t secret[PUB_KEY_SIZE];
|
||||||
|
self_id.calcSharedSecret(secret, sender);
|
||||||
|
|
||||||
|
// decrypt, checking MAC is valid
|
||||||
|
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||||
|
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
|
||||||
|
if (len > 0) { // success!
|
||||||
|
onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action = routeRecvPacket(pkt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAYLOAD_TYPE_GRP_DATA:
|
||||||
|
case PAYLOAD_TYPE_GRP_TXT: {
|
||||||
|
int i = 0;
|
||||||
|
uint8_t channel_hash = pkt->payload[i++];
|
||||||
|
|
||||||
|
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
|
||||||
|
if (i + 2 >= pkt->payload_len) {
|
||||||
|
MESH_DEBUG_PRINTLN("Mesh::onRecvPacket(): incomplete data packet");
|
||||||
|
} else {
|
||||||
|
// scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM)
|
||||||
|
GroupChannel channels[2];
|
||||||
|
int num = searchChannelsByHash(&channel_hash, channels, 2);
|
||||||
|
// for each matching channel, try to decrypt data
|
||||||
|
for (int j = 0; j < num; j++) {
|
||||||
|
// decrypt, checking MAC is valid
|
||||||
|
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||||
|
int len = Utils::MACThenDecrypt(channels[j].secret, data, macAndData, pkt->payload_len - i);
|
||||||
|
if (len > 0) { // success!
|
||||||
|
onGroupDataRecv(pkt, pkt->getPayloadType(), channels[j], data, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action = routeRecvPacket(pkt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAYLOAD_TYPE_ADVERT: {
|
||||||
|
int i = 0;
|
||||||
|
Identity id;
|
||||||
|
memcpy(id.pub_key, &pkt->payload[i], PUB_KEY_SIZE); i += PUB_KEY_SIZE;
|
||||||
|
|
||||||
|
uint32_t timestamp;
|
||||||
|
memcpy(×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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
151
src/Mesh.h
Normal file
151
src/Mesh.h
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Dispatcher.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstraction of the device's Realtime Clock.
|
||||||
|
*/
|
||||||
|
class RTCClock {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \returns the current time. in UNIX epoch seconds.
|
||||||
|
*/
|
||||||
|
virtual uint32_t getCurrentTime() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \param time current time in UNIX epoch seconds.
|
||||||
|
*/
|
||||||
|
virtual void setCurrentTime(uint32_t time) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupChannel {
|
||||||
|
public:
|
||||||
|
uint8_t hash[PATH_HASH_SIZE];
|
||||||
|
uint8_t secret[PUB_KEY_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The next layer in the basic Dispatcher task, Mesh recognises the particular Payload TYPES,
|
||||||
|
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
|
||||||
|
*/
|
||||||
|
class Mesh : public Dispatcher {
|
||||||
|
RTCClock* _rtc;
|
||||||
|
RNG* _rng;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DispatcherAction onRecvPacket(Packet* pkt) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Decide what to do with received packet, ie. discard, forward, or hold
|
||||||
|
*/
|
||||||
|
DispatcherAction routeRecvPacket(Packet* packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
|
||||||
|
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
|
||||||
|
*/
|
||||||
|
virtual bool allowPacketForward(Packet* packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Perform search of local DB of peers/contacts.
|
||||||
|
* \returns Number of peers with matching hash
|
||||||
|
*/
|
||||||
|
virtual int searchPeersByHash(const uint8_t* hash);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief lookup the ECDH shared-secret between this node and peer by idx (calculate if necessary)
|
||||||
|
* \param dest_secret destination array to copy the secret (must be PUB_KEY_SIZE bytes)
|
||||||
|
* \param peer_idx index of peer, [0..n) where n is what searchPeersByHash() returned
|
||||||
|
*/
|
||||||
|
virtual void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A (now decrypted) data packet has been received (by a known peer).
|
||||||
|
* NOTE: these can be received multiple times (per sender/msg-id), via different routes
|
||||||
|
* \param type one of: PAYLOAD_TYPE_TXT_MSG, PAYLOAD_TYPE_REQ, PAYLOAD_TYPE_RESPONSE
|
||||||
|
* \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned
|
||||||
|
*/
|
||||||
|
virtual void onPeerDataRecv(Packet* packet, uint8_t type, int sender_idx, uint8_t* data, size_t len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A path TO peer (sender_idx) has been received. (also with optional 'extra' data encoded)
|
||||||
|
* NOTE: these can be received multiple times (per sender), via differen routes
|
||||||
|
* \param sender_idx index of peer, [0..n) where n is what searchPeersByHash() returned
|
||||||
|
*/
|
||||||
|
virtual void onPeerPathRecv(Packet* packet, int sender_idx, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
||||||
|
|
||||||
|
virtual int searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int max_matches);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A new incoming Advertisement has been received.
|
||||||
|
* NOTE: these can be received multiple times (per id/timestamp), via different routes
|
||||||
|
*/
|
||||||
|
virtual void onAdvertRecv(Packet* packet, const Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A (now decrypted) data packet has been received.
|
||||||
|
* NOTE: these can be received multiple times (per sender/contents), via different routes
|
||||||
|
* \param type one of: PAYLOAD_TYPE_ANON_REQ
|
||||||
|
* \param sender public key provided by sender
|
||||||
|
*/
|
||||||
|
virtual void onAnonDataRecv(Packet* packet, uint8_t type, const Identity& sender, uint8_t* data, size_t len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A path TO 'sender' has been received. (also with optional 'extra' data encoded)
|
||||||
|
* NOTE: these can be received multiple times (per sender), via differen routes
|
||||||
|
*/
|
||||||
|
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief An encrypted group data packet has been received.
|
||||||
|
* NOTE: the same payload can be received multiple times, via different routes
|
||||||
|
* \param type one of: PAYLOAD_TYPE_GRP_TXT, PAYLOAD_TYPE_GRP_DATA
|
||||||
|
* \param channel the matching GroupChannel
|
||||||
|
*/
|
||||||
|
virtual void onGroupDataRecv(Packet* packet, uint8_t type, const GroupChannel& channel, uint8_t* data, size_t len) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A simple ACK packet has been received.
|
||||||
|
* NOTE: same ACK can be received multiple times, via different routes
|
||||||
|
*/
|
||||||
|
virtual void onAckRecv(Packet* packet, uint32_t ack_crc) { }
|
||||||
|
|
||||||
|
Mesh(Radio& radio, MillisecondClock& ms, RNG& rng, RTCClock& rtc, PacketManager& mgr)
|
||||||
|
: Dispatcher(radio, ms, mgr), _rng(&rng), _rtc(&rtc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void begin();
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
LocalIdentity self_id;
|
||||||
|
|
||||||
|
RNG* getRNG() const { return _rng; }
|
||||||
|
RTCClock* getRTCClock() const { return _rtc; }
|
||||||
|
|
||||||
|
Packet* createAdvert(const LocalIdentity& id, const uint8_t* app_data=NULL, size_t app_data_len=0);
|
||||||
|
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len);
|
||||||
|
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
|
||||||
|
Packet* createAck(uint32_t ack_crc);
|
||||||
|
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief send a locally-generated Packet with flood routing
|
||||||
|
*/
|
||||||
|
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief send a locally-generated Packet with Direct routing
|
||||||
|
*/
|
||||||
|
void sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis=0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief send a locally-generated Packet to just neigbor nodes (zero hops)
|
||||||
|
*/
|
||||||
|
void sendZeroHop(Packet* packet, uint32_t delay_millis=0);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
46
src/MeshCore.h
Normal file
46
src/MeshCore.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MAX_HASH_SIZE 8
|
||||||
|
#define PUB_KEY_SIZE 32
|
||||||
|
#define PRV_KEY_SIZE 64
|
||||||
|
#define SEED_SIZE 32
|
||||||
|
#define SIGNATURE_SIZE 64
|
||||||
|
#define MAX_ADVERT_DATA_SIZE 32
|
||||||
|
#define CIPHER_KEY_SIZE 16
|
||||||
|
#define CIPHER_BLOCK_SIZE 16
|
||||||
|
|
||||||
|
// V1
|
||||||
|
#define CIPHER_MAC_SIZE 2
|
||||||
|
#define PATH_HASH_SIZE 1
|
||||||
|
|
||||||
|
#define MAX_PACKET_PAYLOAD 184
|
||||||
|
#define MAX_PATH_SIZE 64
|
||||||
|
#define MAX_TRANS_UNIT 255
|
||||||
|
|
||||||
|
#if MESH_DEBUG && ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#define MESH_DEBUG_PRINT(...) Serial.printf(__VA_ARGS__)
|
||||||
|
#define MESH_DEBUG_PRINTLN(F, ...) Serial.printf(F "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define MESH_DEBUG_PRINT(...) {}
|
||||||
|
#define MESH_DEBUG_PRINTLN(...) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
#define BD_STARTUP_NORMAL 0 // getStartupReason() codes
|
||||||
|
#define BD_STARTUP_RX_PACKET 1
|
||||||
|
|
||||||
|
class MainBoard {
|
||||||
|
public:
|
||||||
|
virtual uint16_t getBattMilliVolts() = 0;
|
||||||
|
virtual const char* getManufacturerName() const = 0;
|
||||||
|
virtual void onBeforeTransmit() { }
|
||||||
|
virtual void onAfterTransmit() { }
|
||||||
|
virtual void reboot() = 0;
|
||||||
|
virtual uint8_t getStartupReason() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
16
src/MeshTables.h
Normal file
16
src/MeshTables.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Mesh.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstraction of the data tables needed to be maintained, for the routing engine.
|
||||||
|
*/
|
||||||
|
class MeshTables {
|
||||||
|
public:
|
||||||
|
virtual bool hasForwarded(const uint8_t* packet_hash) const = 0;
|
||||||
|
virtual void setHasForwarded(const uint8_t* packet_hash) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
23
src/Packet.cpp
Normal file
23
src/Packet.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "Packet.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
Packet::Packet() {
|
||||||
|
header = 0;
|
||||||
|
path_len = 0;
|
||||||
|
payload_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Packet::calculatePacketHash(uint8_t* hash) const {
|
||||||
|
SHA256 sha;
|
||||||
|
uint8_t t = getPayloadType();
|
||||||
|
sha.update(&t, 1);
|
||||||
|
sha.update(payload, payload_len);
|
||||||
|
sha.finalize(hash, MAX_HASH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
73
src/Packet.h
Normal file
73
src/Packet.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
// Packet::header values
|
||||||
|
#define PH_ROUTE_MASK 0x03 // 2-bits
|
||||||
|
#define PH_TYPE_SHIFT 2
|
||||||
|
#define PH_TYPE_MASK 0x0F // 4-bits
|
||||||
|
#define PH_VER_SHIFT 6
|
||||||
|
#define PH_VER_MASK 0x03 // 2-bits
|
||||||
|
|
||||||
|
#define ROUTE_TYPE_RESERVED1 0x00 // FUTURE
|
||||||
|
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
|
||||||
|
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
|
||||||
|
#define ROUTE_TYPE_RESERVED2 0x03 // FUTURE
|
||||||
|
|
||||||
|
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||||
|
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||||
|
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
|
||||||
|
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack
|
||||||
|
#define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
|
||||||
|
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
|
||||||
|
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
|
||||||
|
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
|
||||||
|
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||||
|
//...
|
||||||
|
#define PAYLOAD_TYPE_RESERVEDM 0x0F // FUTURE
|
||||||
|
|
||||||
|
#define PAYLOAD_VER_1 0x00 // 1-byte src/dest hashes, 2-byte MAC
|
||||||
|
#define PAYLOAD_VER_2 0x01 // FUTURE (eg. 2-byte hashes, 4-byte MAC ??)
|
||||||
|
#define PAYLOAD_VER_3 0x02 // FUTURE
|
||||||
|
#define PAYLOAD_VER_4 0x03 // FUTURE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The fundamental transmission unit.
|
||||||
|
*/
|
||||||
|
class Packet {
|
||||||
|
public:
|
||||||
|
Packet();
|
||||||
|
|
||||||
|
uint8_t header;
|
||||||
|
uint16_t payload_len, path_len;
|
||||||
|
uint8_t path[MAX_PATH_SIZE];
|
||||||
|
uint8_t payload[MAX_PACKET_PAYLOAD];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief calculate the hash of payload + type
|
||||||
|
* \param dest_hash destination to store the hash (must be MAX_HASH_SIZE bytes)
|
||||||
|
*/
|
||||||
|
void calculatePacketHash(uint8_t* dest_hash) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns one of ROUTE_ values
|
||||||
|
*/
|
||||||
|
uint8_t getRouteType() const { return header & PH_ROUTE_MASK; }
|
||||||
|
|
||||||
|
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD; }
|
||||||
|
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns one of PAYLOAD_TYPE_ values
|
||||||
|
*/
|
||||||
|
uint8_t getPayloadType() const { return (header >> PH_TYPE_SHIFT) & PH_TYPE_MASK; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \returns one of PAYLOAD_VER_ values
|
||||||
|
*/
|
||||||
|
uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
149
src/Utils.cpp
Normal file
149
src/Utils.cpp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
#include <AES.h>
|
||||||
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) {
|
||||||
|
uint32_t num;
|
||||||
|
random((uint8_t *) &num, sizeof(num));
|
||||||
|
return (num % (_max - _min)) + _min;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) {
|
||||||
|
SHA256 sha;
|
||||||
|
sha.update(msg, msg_len);
|
||||||
|
sha.finalize(hash, hash_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) {
|
||||||
|
SHA256 sha;
|
||||||
|
sha.update(frag1, frag1_len);
|
||||||
|
sha.update(frag2, frag2_len);
|
||||||
|
sha.finalize(hash, hash_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
|
||||||
|
AES128 aes;
|
||||||
|
uint8_t* dp = dest;
|
||||||
|
const uint8_t* sp = src;
|
||||||
|
|
||||||
|
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
|
||||||
|
while (sp - src < src_len) {
|
||||||
|
aes.decryptBlock(dp, sp);
|
||||||
|
dp += 16; sp += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp - src; // will always be multiple of 16
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
|
||||||
|
AES128 aes;
|
||||||
|
uint8_t* dp = dest;
|
||||||
|
|
||||||
|
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
|
||||||
|
while (src_len >= 16) {
|
||||||
|
aes.encryptBlock(dp, src);
|
||||||
|
dp += 16; src += 16; src_len -= 16;
|
||||||
|
}
|
||||||
|
if (src_len > 0) { // remaining partial block
|
||||||
|
uint8_t tmp[16];
|
||||||
|
memset(tmp, 0, 16);
|
||||||
|
memcpy(tmp, src, src_len);
|
||||||
|
aes.encryptBlock(dp, tmp);
|
||||||
|
dp += 16;
|
||||||
|
}
|
||||||
|
return dp - dest; // will always be multiple of 16
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
|
||||||
|
int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len);
|
||||||
|
|
||||||
|
SHA256 sha;
|
||||||
|
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
|
||||||
|
sha.update(dest + CIPHER_MAC_SIZE, enc_len);
|
||||||
|
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE);
|
||||||
|
|
||||||
|
return CIPHER_MAC_SIZE + enc_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
|
||||||
|
if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes
|
||||||
|
|
||||||
|
uint8_t hmac[CIPHER_MAC_SIZE];
|
||||||
|
{
|
||||||
|
SHA256 sha;
|
||||||
|
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
|
||||||
|
sha.update(src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
|
||||||
|
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE);
|
||||||
|
}
|
||||||
|
if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) {
|
||||||
|
return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
|
||||||
|
}
|
||||||
|
return 0; // invalid HMAC
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char hex_chars[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
void Utils::toHex(char* dest, const uint8_t* src, size_t len) {
|
||||||
|
while (len > 0) {
|
||||||
|
uint8_t b = *src++;
|
||||||
|
*dest++ = hex_chars[b >> 4];
|
||||||
|
*dest++ = hex_chars[b & 0x0F];
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
*dest = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::printHex(Stream& s, const uint8_t* src, size_t len) {
|
||||||
|
while (len > 0) {
|
||||||
|
uint8_t b = *src++;
|
||||||
|
s.print(hex_chars[b >> 4]);
|
||||||
|
s.print(hex_chars[b & 0x0F]);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t hexVal(char c) {
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::fromHex(uint8_t* dest, int dest_size, const char *src_hex) {
|
||||||
|
int len = strlen(src_hex);
|
||||||
|
if (len != dest_size*2) return false; // incorrect length
|
||||||
|
|
||||||
|
uint8_t* dp = dest;
|
||||||
|
while (dp - dest < dest_size) {
|
||||||
|
char ch = *src_hex++;
|
||||||
|
char cl = *src_hex++;
|
||||||
|
*dp++ = (hexVal(ch) << 4) | hexVal(cl);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::parseTextParts(char* text, const char* parts[], int max_num, char separator) {
|
||||||
|
int num = 0;
|
||||||
|
char* sp = text;
|
||||||
|
while (*sp && num < max_num) {
|
||||||
|
parts[num++] = sp;
|
||||||
|
while (*sp && *sp != separator) sp++;
|
||||||
|
if (*sp) {
|
||||||
|
*sp++ = 0; // replace the seperator with a null, and skip past it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we hit the maximum parts, make sure LAST entry does NOT have separator
|
||||||
|
while (*sp && *sp != separator) sp++;
|
||||||
|
if (*sp) {
|
||||||
|
*sp = 0; // replace the separator with null
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
src/Utils.h
Normal file
81
src/Utils.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace mesh {
|
||||||
|
|
||||||
|
class RNG {
|
||||||
|
public:
|
||||||
|
virtual void random(uint8_t* dest, size_t sz) = 0;
|
||||||
|
uint32_t nextInt(uint32_t _min, uint32_t _max);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \brief calculates the SHA256 hash of 'msg', storing in 'hash' and truncating the hash to 'hash_len' bytes.
|
||||||
|
*/
|
||||||
|
static void sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief calculates the SHA256 hash of two fragments, 'frag1' and 'frag2' (in that order), storing in 'hash' and truncating.
|
||||||
|
*/
|
||||||
|
static void sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Encrypts the 'src' bytes using AES128 cipher, using 'shared_secret' as key, with key length fixed at CIPHER_KEY_SIZE.
|
||||||
|
* Final block is padded with zero bytes before encrypt. Result stored in 'dest'.
|
||||||
|
* \returns The length in bytes put into 'dest'. (rounded up to block size)
|
||||||
|
*/
|
||||||
|
static int encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Decrypt the 'src' bytes using AES128 cipher, using 'shared_secret' as key, with key length fixed at CIPHER_KEY_SIZE.
|
||||||
|
* 'src_len' should be multiple of block size, as returned by 'encrypt()'.
|
||||||
|
* \returns The length in bytes put into 'dest'. (dest may contain trailing zero bytes in final block)
|
||||||
|
*/
|
||||||
|
static int decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief encrypts bytes in src, then calculates MAC on ciphertext, inserting into leading bytes of 'dest'.
|
||||||
|
* \returns total length of bytes in 'dest' (MAC + ciphertext)
|
||||||
|
*/
|
||||||
|
static int encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief checks the MAC (in leading bytes of 'src'), then if valid, decrypts remaining bytes in src.
|
||||||
|
* \returns zero if MAC is invalid, otherwise the length of decrypted bytes in 'dest'
|
||||||
|
*/
|
||||||
|
static int MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief converts 'src' bytes with given length to Hex representation, and null terminates.
|
||||||
|
*/
|
||||||
|
static void toHex(char* dest, const uint8_t* src, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief converts 'src_hex' hexadecimal string (should be null term) back to raw bytes, storing in 'dest'.
|
||||||
|
* \param dest_size must be exactly the expected size in bytes.
|
||||||
|
* \returns true if successful
|
||||||
|
*/
|
||||||
|
static bool fromHex(uint8_t* dest, int dest_size, const char *src_hex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Prints the hexadecimal representation of 'src' bytes of given length, to Stream 's'.
|
||||||
|
*/
|
||||||
|
static void printHex(Stream& s, const uint8_t* src, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief parse 'text' into parts separated by 'separator' char.
|
||||||
|
* \param text the text to parse (note is MODIFIED!)
|
||||||
|
* \param parts destination array to store pointers to starts of parse parts
|
||||||
|
* \param max_num max elements to store in 'parts' array
|
||||||
|
* \param separator the separator character
|
||||||
|
* \returns the number of parts parsed (in 'parts')
|
||||||
|
*/
|
||||||
|
static int parseTextParts(char* text, const char* parts[], int max_num, char separator=',');
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
27
src/helpers/ArduinoHelpers.h
Normal file
27
src/helpers/ArduinoHelpers.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class VolatileRTCClock : public mesh::RTCClock {
|
||||||
|
long millis_offset;
|
||||||
|
public:
|
||||||
|
VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm
|
||||||
|
uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); }
|
||||||
|
void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArduinoMillis : public mesh::MillisecondClock {
|
||||||
|
public:
|
||||||
|
unsigned long getMillis() override { return millis(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class StdRNG : public mesh::RNG {
|
||||||
|
public:
|
||||||
|
void begin(long seed) { randomSeed(seed); }
|
||||||
|
void random(uint8_t* dest, size_t sz) override {
|
||||||
|
for (int i = 0; i < sz; i++) {
|
||||||
|
dest[i] = (::random(0, 256) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
16
src/helpers/CustomSX1262.h
Normal file
16
src/helpers/CustomSX1262.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||||
|
|
||||||
|
class CustomSX1262 : public SX1262 {
|
||||||
|
public:
|
||||||
|
CustomSX1262(Module *mod) : SX1262(mod) { }
|
||||||
|
|
||||||
|
bool isReceiving() {
|
||||||
|
uint16_t irq = getIrqStatus();
|
||||||
|
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
|
||||||
|
return hasPreamble;
|
||||||
|
}
|
||||||
|
};
|
||||||
12
src/helpers/CustomSX1262Wrapper.h
Normal file
12
src/helpers/CustomSX1262Wrapper.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CustomSX1262.h"
|
||||||
|
#include "RadioLibWrappers.h"
|
||||||
|
|
||||||
|
class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||||
|
public:
|
||||||
|
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||||
|
bool isReceiving() override { return ((CustomSX1262 *)_radio)->isReceiving(); }
|
||||||
|
float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); }
|
||||||
|
float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); }
|
||||||
|
};
|
||||||
16
src/helpers/CustomSX1268.h
Normal file
16
src/helpers/CustomSX1268.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||||
|
|
||||||
|
class CustomSX1268 : public SX1268 {
|
||||||
|
public:
|
||||||
|
CustomSX1268(Module *mod) : SX1268(mod) { }
|
||||||
|
|
||||||
|
bool isReceiving() {
|
||||||
|
uint16_t irq = getIrqStatus();
|
||||||
|
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
|
||||||
|
return hasPreamble;
|
||||||
|
}
|
||||||
|
};
|
||||||
12
src/helpers/CustomSX1268Wrapper.h
Normal file
12
src/helpers/CustomSX1268Wrapper.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CustomSX1268.h"
|
||||||
|
#include "RadioLibWrappers.h"
|
||||||
|
|
||||||
|
class CustomSX1268Wrapper : public RadioLibWrapper {
|
||||||
|
public:
|
||||||
|
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||||
|
bool isReceiving() override { return ((CustomSX1268 *)_radio)->isReceiving(); }
|
||||||
|
float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); }
|
||||||
|
float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); }
|
||||||
|
};
|
||||||
48
src/helpers/ESP32Board.h
Normal file
48
src/helpers/ESP32Board.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#if defined(ESP_PLATFORM)
|
||||||
|
|
||||||
|
#include <rom/rtc.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
class ESP32Board : public mesh::MainBoard { // abstract class
|
||||||
|
public:
|
||||||
|
void begin() {
|
||||||
|
// for future use, sub-classes SHOULD call this from their begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
void reboot() override {
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESP32RTCClock : public mesh::RTCClock {
|
||||||
|
public:
|
||||||
|
ESP32RTCClock() { }
|
||||||
|
void begin() {
|
||||||
|
esp_reset_reason_t reason = esp_reset_reason();
|
||||||
|
if (reason == ESP_RST_POWERON) {
|
||||||
|
// start with some date/time in the recent past
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
settimeofday(&tv, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t getCurrentTime() override {
|
||||||
|
time_t _now;
|
||||||
|
time(&_now);
|
||||||
|
return _now;
|
||||||
|
}
|
||||||
|
void setCurrentTime(uint32_t time) override {
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = time;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
settimeofday(&tv, NULL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
91
src/helpers/HeltecV3Board.h
Normal file
91
src/helpers/HeltecV3Board.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ESP32Board.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// LoRa radio module pins for Heltec V3
|
||||||
|
#define P_LORA_DIO_1 14
|
||||||
|
#define P_LORA_NSS 8
|
||||||
|
#define P_LORA_RESET RADIOLIB_NC
|
||||||
|
#define P_LORA_BUSY 13
|
||||||
|
#define P_LORA_SCLK 9
|
||||||
|
#define P_LORA_MISO 11
|
||||||
|
#define P_LORA_MOSI 10
|
||||||
|
|
||||||
|
// built-ins
|
||||||
|
#define PIN_VBAT_READ 1
|
||||||
|
#define PIN_ADC_CTRL 37
|
||||||
|
#define PIN_ADC_CTRL_ACTIVE LOW
|
||||||
|
#define PIN_ADC_CTRL_INACTIVE HIGH
|
||||||
|
#define PIN_LED_BUILTIN 35
|
||||||
|
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
|
||||||
|
class HeltecV3Board : public ESP32Board {
|
||||||
|
uint8_t startup_reason;
|
||||||
|
public:
|
||||||
|
void begin() {
|
||||||
|
startup_reason = BD_STARTUP_NORMAL;
|
||||||
|
ESP32Board::begin();
|
||||||
|
|
||||||
|
esp_reset_reason_t reason = esp_reset_reason();
|
||||||
|
if (reason == ESP_RST_DEEPSLEEP) {
|
||||||
|
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||||
|
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
||||||
|
startup_reason = BD_STARTUP_RX_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||||
|
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// battery read support
|
||||||
|
pinMode(PIN_VBAT_READ, INPUT);
|
||||||
|
adcAttachPin(PIN_VBAT_READ);
|
||||||
|
analogReadResolution(10);
|
||||||
|
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getStartupReason() const { return startup_reason; }
|
||||||
|
|
||||||
|
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||||
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
|
||||||
|
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||||
|
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||||
|
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||||
|
|
||||||
|
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||||
|
|
||||||
|
if (pin_wake_btn < 0) {
|
||||||
|
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||||
|
} else {
|
||||||
|
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secs > 0) {
|
||||||
|
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally set ESP32 into sleep
|
||||||
|
esp_deep_sleep_start(); // CPU halts here and never returns!
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getBattMilliVolts() override {
|
||||||
|
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_ACTIVE);
|
||||||
|
|
||||||
|
uint32_t raw = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
raw += analogRead(PIN_VBAT_READ);
|
||||||
|
}
|
||||||
|
raw = raw / 8;
|
||||||
|
|
||||||
|
digitalWrite(PIN_ADC_CTRL, PIN_ADC_CTRL_INACTIVE);
|
||||||
|
|
||||||
|
return (5.2 * (3.3 / 1024.0) * raw) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getManufacturerName() const override {
|
||||||
|
return "Heltec V3";
|
||||||
|
}
|
||||||
|
};
|
||||||
27
src/helpers/IdentityStore.cpp
Normal file
27
src/helpers/IdentityStore.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#include "IdentityStore.h"
|
||||||
|
|
||||||
|
bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) {
|
||||||
|
bool loaded = false;
|
||||||
|
char filename[40];
|
||||||
|
sprintf(filename, "%s/%s.id", _dir, name);
|
||||||
|
if (_fs->exists(filename)) {
|
||||||
|
File file = _fs->open(filename);
|
||||||
|
if (file) {
|
||||||
|
loaded = id.readFrom(file);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
|
||||||
|
char filename[40];
|
||||||
|
sprintf(filename, "%s/%s.id", _dir, name);
|
||||||
|
File file = _fs->open(filename, "w", true);
|
||||||
|
if (file) {
|
||||||
|
id.writeTo(file);
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
15
src/helpers/IdentityStore.h
Normal file
15
src/helpers/IdentityStore.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
#include <Identity.h>
|
||||||
|
|
||||||
|
class IdentityStore {
|
||||||
|
fs::FS* _fs;
|
||||||
|
const char* _dir;
|
||||||
|
public:
|
||||||
|
IdentityStore(fs::FS& fs, const char* dir): _fs(&fs), _dir(dir) { }
|
||||||
|
|
||||||
|
void begin() { _fs->mkdir(_dir); }
|
||||||
|
bool load(const char *name, mesh::LocalIdentity& id);
|
||||||
|
bool save(const char *name, const mesh::LocalIdentity& id);
|
||||||
|
};
|
||||||
93
src/helpers/RadioLibWrappers.cpp
Normal file
93
src/helpers/RadioLibWrappers.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include "RadioLibWrappers.h"
|
||||||
|
|
||||||
|
#define STATE_IDLE 0
|
||||||
|
#define STATE_RX 1
|
||||||
|
#define STATE_TX_WAIT 3
|
||||||
|
#define STATE_TX_DONE 4
|
||||||
|
#define STATE_INT_READY 16
|
||||||
|
|
||||||
|
static volatile uint8_t state = STATE_IDLE;
|
||||||
|
|
||||||
|
// this function is called when a complete packet
|
||||||
|
// is transmitted by the module
|
||||||
|
static
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
ICACHE_RAM_ATTR
|
||||||
|
#endif
|
||||||
|
void setFlag(void) {
|
||||||
|
// we sent a packet, set the flag
|
||||||
|
state |= STATE_INT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibWrapper::begin() {
|
||||||
|
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt
|
||||||
|
state = STATE_IDLE;
|
||||||
|
|
||||||
|
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
|
||||||
|
setFlag(); // LoRa packet is already received
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||||
|
if (state & STATE_INT_READY) {
|
||||||
|
int len = _radio->getPacketLength();
|
||||||
|
if (len > 0) {
|
||||||
|
if (len > sz) { len = sz; }
|
||||||
|
int err = _radio->readData(bytes, len);
|
||||||
|
if (err != RADIOLIB_ERR_NONE) {
|
||||||
|
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData()");
|
||||||
|
} else {
|
||||||
|
// Serial.print(" readData() -> "); Serial.println(len);
|
||||||
|
}
|
||||||
|
n_recv++;
|
||||||
|
}
|
||||||
|
state = STATE_IDLE; // need another startReceive()
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != STATE_RX) {
|
||||||
|
int err = _radio->startReceive();
|
||||||
|
if (err != RADIOLIB_ERR_NONE) {
|
||||||
|
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startReceive()");
|
||||||
|
}
|
||||||
|
state = STATE_RX;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
|
||||||
|
return _radio->getTimeOnAir(len_bytes) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
|
||||||
|
state = STATE_TX_WAIT;
|
||||||
|
_board->onBeforeTransmit();
|
||||||
|
int err = _radio->startTransmit((uint8_t *) bytes, len);
|
||||||
|
if (err != RADIOLIB_ERR_NONE) {
|
||||||
|
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RadioLibWrapper::isSendComplete() {
|
||||||
|
if (state & STATE_INT_READY) {
|
||||||
|
state = STATE_IDLE;
|
||||||
|
n_sent++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibWrapper::onSendFinished() {
|
||||||
|
_radio->finishTransmit();
|
||||||
|
_board->onAfterTransmit();
|
||||||
|
state = STATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float RadioLibWrapper::getLastRSSI() const {
|
||||||
|
return _radio->getRSSI();
|
||||||
|
}
|
||||||
|
float RadioLibWrapper::getLastSNR() const {
|
||||||
|
return _radio->getSNR();
|
||||||
|
}
|
||||||
42
src/helpers/RadioLibWrappers.h
Normal file
42
src/helpers/RadioLibWrappers.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Mesh.h>
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
class RadioLibWrapper : public mesh::Radio {
|
||||||
|
protected:
|
||||||
|
PhysicalLayer* _radio;
|
||||||
|
mesh::MainBoard* _board;
|
||||||
|
uint32_t n_recv, n_sent;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
|
||||||
|
|
||||||
|
void begin() override;
|
||||||
|
int recvRaw(uint8_t* bytes, int sz) override;
|
||||||
|
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||||
|
void startSendRaw(const uint8_t* bytes, int len) override;
|
||||||
|
bool isSendComplete() override;
|
||||||
|
void onSendFinished() override;
|
||||||
|
|
||||||
|
uint32_t getPacketsRecv() const { return n_recv; }
|
||||||
|
uint32_t getPacketsSent() const { return n_sent; }
|
||||||
|
virtual float getLastRSSI() const;
|
||||||
|
virtual float getLastSNR() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief an RNG impl using the noise from the LoRa radio as entropy.
|
||||||
|
* NOTE: this is VERY SLOW! Use only for things like creating new LocalIdentity
|
||||||
|
*/
|
||||||
|
class RadioNoiseListener : public mesh::RNG {
|
||||||
|
PhysicalLayer* _radio;
|
||||||
|
public:
|
||||||
|
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
|
||||||
|
|
||||||
|
void random(uint8_t* dest, size_t sz) override {
|
||||||
|
for (int i = 0; i < sz; i++) {
|
||||||
|
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
56
src/helpers/SimpleMeshTables.h
Normal file
56
src/helpers/SimpleMeshTables.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshTables.h>
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <FS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_PACKET_HASHES 64
|
||||||
|
|
||||||
|
class SimpleMeshTables : public mesh::MeshTables {
|
||||||
|
uint8_t _fwd_hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
|
||||||
|
int _next_fwd_idx;
|
||||||
|
|
||||||
|
int lookupHashIndex(const uint8_t* hash) const {
|
||||||
|
const uint8_t* sp = _fwd_hashes;
|
||||||
|
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||||
|
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimpleMeshTables() {
|
||||||
|
memset(_fwd_hashes, 0, sizeof(_fwd_hashes));
|
||||||
|
_next_fwd_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
void restoreFrom(File f) {
|
||||||
|
f.read(_fwd_hashes, sizeof(_fwd_hashes));
|
||||||
|
f.read((uint8_t *) &_next_fwd_idx, sizeof(_next_fwd_idx));
|
||||||
|
}
|
||||||
|
void saveTo(File f) {
|
||||||
|
f.write(_fwd_hashes, sizeof(_fwd_hashes));
|
||||||
|
f.write((const uint8_t *) &_next_fwd_idx, sizeof(_next_fwd_idx));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool hasForwarded(const uint8_t* packet_hash) const override {
|
||||||
|
int i = lookupHashIndex(packet_hash);
|
||||||
|
return i >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHasForwarded(const uint8_t* packet_hash) override {
|
||||||
|
int i = lookupHashIndex(packet_hash);
|
||||||
|
if (i >= 0) {
|
||||||
|
// already in table
|
||||||
|
} else {
|
||||||
|
memcpy(&_fwd_hashes[_next_fwd_idx*MAX_HASH_SIZE], packet_hash, MAX_HASH_SIZE);
|
||||||
|
|
||||||
|
_next_fwd_idx = (_next_fwd_idx + 1) % MAX_PACKET_HASHES; // cyclic table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
33
src/helpers/SimpleSeenTable.h
Normal file
33
src/helpers/SimpleSeenTable.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Packet.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MAX_PACKET_HASHES 64
|
||||||
|
|
||||||
|
class SimpleSeenTable {
|
||||||
|
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
|
||||||
|
int _next_idx;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimpleSeenTable() {
|
||||||
|
memset(_hashes, 0, sizeof(_hashes));
|
||||||
|
_next_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasSeenPacket(const mesh::Packet* packet) {
|
||||||
|
uint8_t hash[MAX_HASH_SIZE];
|
||||||
|
packet->calculatePacketHash(hash);
|
||||||
|
|
||||||
|
const uint8_t* sp = _hashes;
|
||||||
|
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||||
|
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
|
||||||
|
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
97
src/helpers/StaticPoolPacketManager.cpp
Normal file
97
src/helpers/StaticPoolPacketManager.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#include "StaticPoolPacketManager.h"
|
||||||
|
|
||||||
|
PacketQueue::PacketQueue(int max_entries) {
|
||||||
|
_table = new mesh::Packet*[max_entries];
|
||||||
|
_pri_table = new uint8_t[max_entries];
|
||||||
|
_schedule_table = new uint32_t[max_entries];
|
||||||
|
_size = max_entries;
|
||||||
|
_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* PacketQueue::get(uint32_t now) {
|
||||||
|
uint8_t min_pri = 0xFF;
|
||||||
|
int best_idx = -1;
|
||||||
|
for (int j = 0; j < _num; j++) {
|
||||||
|
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
|
||||||
|
if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries
|
||||||
|
min_pri = _pri_table[j];
|
||||||
|
best_idx = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_idx < 0) return NULL; // empty, or all items are still in the future
|
||||||
|
|
||||||
|
mesh::Packet* top = _table[best_idx];
|
||||||
|
int i = best_idx;
|
||||||
|
_num--;
|
||||||
|
while (i < _num) {
|
||||||
|
_table[i] = _table[i+1];
|
||||||
|
_pri_table[i] = _pri_table[i+1];
|
||||||
|
_schedule_table[i] = _schedule_table[i+1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* PacketQueue::removeByIdx(int i) {
|
||||||
|
if (i >= _num) return NULL; // invalid index
|
||||||
|
|
||||||
|
mesh::Packet* item = _table[i];
|
||||||
|
_num--;
|
||||||
|
while (i < _num) {
|
||||||
|
_table[i] = _table[i+1];
|
||||||
|
_pri_table[i] = _pri_table[i+1];
|
||||||
|
_schedule_table[i] = _schedule_table[i+1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||||
|
if (_num == _size) {
|
||||||
|
// TODO: log "FATAL: queue is full!"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_table[_num] = packet;
|
||||||
|
_pri_table[_num] = priority;
|
||||||
|
_schedule_table[_num] = scheduled_for;
|
||||||
|
_num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size) {
|
||||||
|
// load up our unusued Packet pool
|
||||||
|
for (int i = 0; i < pool_size; i++) {
|
||||||
|
unused.add(new mesh::Packet(), 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* StaticPoolPacketManager::allocNew() {
|
||||||
|
return unused.removeByIdx(0); // just get first one (returns NULL if empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticPoolPacketManager::free(mesh::Packet* packet) {
|
||||||
|
unused.add(packet, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||||
|
send_queue.add(packet, priority, scheduled_for);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
|
||||||
|
//send_queue.sort(); // sort by scheduled_for/priority first
|
||||||
|
return send_queue.get(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StaticPoolPacketManager::getOutboundCount() const {
|
||||||
|
return send_queue.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int StaticPoolPacketManager::getFreeCount() const {
|
||||||
|
return unused.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* StaticPoolPacketManager::getOutboundByIdx(int i) {
|
||||||
|
return send_queue.itemAt(i);
|
||||||
|
}
|
||||||
|
mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) {
|
||||||
|
return send_queue.removeByIdx(i);
|
||||||
|
}
|
||||||
34
src/helpers/StaticPoolPacketManager.h
Normal file
34
src/helpers/StaticPoolPacketManager.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Dispatcher.h>
|
||||||
|
|
||||||
|
class PacketQueue {
|
||||||
|
mesh::Packet** _table;
|
||||||
|
uint8_t* _pri_table;
|
||||||
|
uint32_t* _schedule_table;
|
||||||
|
int _size, _num;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PacketQueue(int max_entries);
|
||||||
|
mesh::Packet* get(uint32_t now);
|
||||||
|
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
|
||||||
|
int count() const { return _num; }
|
||||||
|
mesh::Packet* itemAt(int i) const { return _table[i]; }
|
||||||
|
mesh::Packet* removeByIdx(int i);
|
||||||
|
};
|
||||||
|
|
||||||
|
class StaticPoolPacketManager : public mesh::PacketManager {
|
||||||
|
PacketQueue unused, send_queue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StaticPoolPacketManager(int pool_size);
|
||||||
|
|
||||||
|
mesh::Packet* allocNew() override;
|
||||||
|
void free(mesh::Packet* packet) override;
|
||||||
|
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
|
||||||
|
mesh::Packet* getNextOutbound(uint32_t now) override;
|
||||||
|
int getOutboundCount() const override;
|
||||||
|
int getFreeCount() const override;
|
||||||
|
mesh::Packet* getOutboundByIdx(int i) override;
|
||||||
|
mesh::Packet* removeOutboundByIdx(int i) override;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user