v1.0.0: Boundary mode with bidirectional LoRa↔TCP transport
Vendor microReticulum library with boundary mode transport fixes: - Two-whitelist system gates backbone traffic (local addresses + mentioned addresses from local devices) - Allow control_hashes and local destinations through boundary filter (fixes backbone→LoRa path discovery) - Fix get_cached_packet() to call unpack() instead of update_hash() (fixes empty destination_hash in path responses) - LRPROOF Identity::recall null guard - remaining_hops HEADER_1/BROADCAST fix for final-hop delivery - PROOF packets excluded from boundary wrapping - Iterator invalidation fix in transport table cleanup - is_backbone flag replaces string matching for interface identification Firmware changes: - Set is_backbone(true) on backbone TCP interface - Rename default TcpInterface name to BackboneInterface - Update comments for dual-use TcpInterface (backbone + local AP) - Use vendored lib/microReticulum instead of PlatformIO registry
This commit is contained in:
201
lib/microReticulum/src/Bytes.cpp
Executable file
201
lib/microReticulum/src/Bytes.cpp
Executable file
@@ -0,0 +1,201 @@
|
||||
#include "Bytes.h"
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
// Creates new shared data for instance
|
||||
// - If capacity is specified (>0) then create empty shared data with initial reserved capacity
|
||||
// - If capacity is not specified (<=0) then create empty shared data with no initial capacity
|
||||
void Bytes::newData(size_t capacity /*= 0*/) {
|
||||
//MEMF("Bytes is creating own data with capacity %u", capacity);
|
||||
//MEM("newData: Creating new data...");
|
||||
Data* data = new Data();
|
||||
if (data == nullptr) {
|
||||
ERROR("Bytes failed to allocate empty data buffer");
|
||||
throw std::runtime_error("Failed to allocate empty data buffer");
|
||||
}
|
||||
//MEM("newData: Created new data");
|
||||
if (capacity > 0) {
|
||||
//MEMF("newData: Reserving data capacity of %u...", capacity);
|
||||
data->reserve(capacity);
|
||||
//MEM("newData: Reserved data capacity");
|
||||
}
|
||||
//MEM("newData: Assigning data to shared data pointer...");
|
||||
_data = SharedData(data);
|
||||
//MEM("newData: Assigned data to shared data pointer");
|
||||
_exclusive = true;
|
||||
}
|
||||
|
||||
// Ensures that instance has exclusive shared data
|
||||
// - If instance has no shared data then create new shared data
|
||||
// - If instance does not have exclusive on shared data that is not empty then make a copy of shared data (if requests) and reserve capacity (if requested)
|
||||
// - If instance does not have exclusive on shared data that is empty then create new shared data
|
||||
// - If instance already has exclusive on shared data then do nothing except reserve capacity (if requested)
|
||||
void Bytes::exclusiveData(bool copy /*= true*/, size_t capacity /*= 0*/) {
|
||||
if (!_data) {
|
||||
newData(capacity);
|
||||
}
|
||||
else if (!_exclusive) {
|
||||
if (copy && !_data->empty()) {
|
||||
//TRACE("Bytes is creating a writable copy of its shared data");
|
||||
//Data* data = new Data(*_data.get());
|
||||
//MEM("exclusiveData: Creating new data...");
|
||||
Data* data = new Data();
|
||||
if (data == nullptr) {
|
||||
ERROR("Bytes failed to duplicate data buffer");
|
||||
throw std::runtime_error("Failed to duplicate data buffer");
|
||||
}
|
||||
//MEM("exclusiveData: Created new data");
|
||||
if (capacity > 0) {
|
||||
//MEMF("exclusiveData: Reserving data capacity of %u...", capacity);
|
||||
// if requested capacity < existing size then reserve capacity for existing size instead
|
||||
data->reserve((capacity > _data->size()) ? capacity : _data->size());
|
||||
//MEM("exclusiveData: Reserved data capacity");
|
||||
}
|
||||
else {
|
||||
data->reserve(_data->size());
|
||||
}
|
||||
//MEM("exclusiveData: Copying existing data...");
|
||||
data->insert(data->begin(), _data->begin(), _data->end());
|
||||
//MEM("exclusiveData: Copied existing data");
|
||||
//MEM("exclusiveData: Assigning data to shared data pointer...");
|
||||
_data = SharedData(data);
|
||||
//MEM("exclusiveData: Assigned data to shared data pointer");
|
||||
_exclusive = true;
|
||||
}
|
||||
else {
|
||||
//MEM("Bytes is creating its own data because shared is empty");
|
||||
//data = new Data();
|
||||
//if (data == nullptr) {
|
||||
// ERROR("Bytes failed to allocate empty data buffer");
|
||||
// throw std::runtime_error("Failed to allocate empty data buffer");
|
||||
//}
|
||||
//_data = SharedData(data);
|
||||
//_exclusive = true;
|
||||
//MEM("exclusiveData: Creating new empty data...");
|
||||
newData(capacity);
|
||||
//MEM("exclusiveData: Created new empty data");
|
||||
}
|
||||
}
|
||||
else if (capacity > 0 && capacity > size()) {
|
||||
reserve(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
int Bytes::compare(const Bytes& bytes) const {
|
||||
if (_data == bytes._data) {
|
||||
return 0;
|
||||
}
|
||||
else if (!_data) {
|
||||
return -1;
|
||||
}
|
||||
else if (!bytes._data) {
|
||||
return 1;
|
||||
}
|
||||
else if (*_data < *(bytes._data)) {
|
||||
return -1;
|
||||
}
|
||||
else if (*_data > *(bytes._data)) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Bytes::compare(const uint8_t* buf, size_t size) const {
|
||||
if (!_data && size == 0) {
|
||||
return 0;
|
||||
}
|
||||
else if (!_data) {
|
||||
return -1;
|
||||
}
|
||||
int cmp = memcmp(_data->data(), buf, (_data->size() < size) ? _data->size() : size);
|
||||
if (cmp == 0 && _data->size() < size) {
|
||||
return -1;
|
||||
}
|
||||
else if (cmp == 0 && _data->size() > size) {
|
||||
return 1;
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
void Bytes::assignHex(const uint8_t* hex, size_t hex_size) {
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (hex == nullptr || hex_size <= 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
exclusiveData(false, hex_size / 2);
|
||||
// need to clear data since we're appending below
|
||||
_data->clear();
|
||||
for (size_t i = 0; i < hex_size; i += 2) {
|
||||
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
|
||||
_data->push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
void Bytes::appendHex(const uint8_t* hex, size_t hex_size) {
|
||||
// if append is empty then do nothing
|
||||
if (hex == nullptr || hex_size <= 0) {
|
||||
return;
|
||||
}
|
||||
exclusiveData(true, size() + (hex_size / 2));
|
||||
for (size_t i = 0; i < hex_size; i += 2) {
|
||||
uint8_t byte = (hex[i] % 32 + 9) % 25 * 16 + (hex[i+1] % 32 + 9) % 25;
|
||||
_data->push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) {
|
||||
std::string hex;
|
||||
if (upper) {
|
||||
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
|
||||
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
|
||||
}
|
||||
else {
|
||||
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
|
||||
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
std::string Bytes::toHex(bool upper /*= false*/) const {
|
||||
if (!_data) {
|
||||
return "";
|
||||
}
|
||||
std::string hex;
|
||||
for (uint8_t byte : *_data) {
|
||||
if (upper) {
|
||||
hex += hex_upper_chars[ (byte& 0xF0) >> 4];
|
||||
hex += hex_upper_chars[ (byte& 0x0F) >> 0];
|
||||
}
|
||||
else {
|
||||
hex += hex_lower_chars[ (byte& 0xF0) >> 4];
|
||||
hex += hex_lower_chars[ (byte& 0x0F) >> 0];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
// mid
|
||||
Bytes Bytes::mid(size_t beginpos, size_t len) const {
|
||||
if (!_data || beginpos >= size()) {
|
||||
return NONE;
|
||||
}
|
||||
if ((beginpos + len) >= size()) {
|
||||
len = (size() - beginpos);
|
||||
}
|
||||
return {data() + beginpos, len};
|
||||
}
|
||||
|
||||
// to end
|
||||
Bytes Bytes::mid(size_t beginpos) const {
|
||||
if (!_data || beginpos >= size()) {
|
||||
return NONE;
|
||||
}
|
||||
return {data() + beginpos, size() - beginpos};
|
||||
}
|
||||
447
lib/microReticulum/src/Bytes.h
Executable file
447
lib/microReticulum/src/Bytes.h
Executable file
@@ -0,0 +1,447 @@
|
||||
#pragma once
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
// Finds the start of the first occurrence of the substring needle of length needlelen in the memory area haystack of length haystacklen.
|
||||
inline void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len) {
|
||||
const unsigned char* h = (const unsigned char*)haystack;
|
||||
const unsigned char* n = (const unsigned char*)needle;
|
||||
|
||||
if (needle_len == 0)
|
||||
return (void*)h;
|
||||
|
||||
if (haystack_len < needle_len)
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i <= haystack_len - needle_len; i++) {
|
||||
if (h[i] == n[0] && memcmp(&h[i], n, needle_len) == 0) {
|
||||
return (void*)&h[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace RNS {
|
||||
|
||||
#define COW
|
||||
|
||||
class Bytes {
|
||||
|
||||
private:
|
||||
//typedef std::vector<uint8_t> Data;
|
||||
using Data = std::vector<uint8_t>;
|
||||
//typedef std::shared_ptr<Data> SharedData;
|
||||
using SharedData = std::shared_ptr<Data>;
|
||||
|
||||
public:
|
||||
enum NoneConstructor {
|
||||
NONE
|
||||
};
|
||||
|
||||
public:
|
||||
Bytes() {
|
||||
MEMF("Bytes object created from default, this: %lu, data: %lu", this, _data.get());
|
||||
}
|
||||
Bytes(const NoneConstructor none) {
|
||||
MEMF("Bytes object created from NONE, this: %lu, data: %lu", this, _data.get());
|
||||
}
|
||||
Bytes(const Bytes& bytes) {
|
||||
//MEM("Bytes is using shared data");
|
||||
assign(bytes);
|
||||
MEMF("Bytes object copy created from bytes \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
// Construct from std::vector<uint8_t>
|
||||
Bytes(const Data& data) {
|
||||
MEM("Creating from data-copy...");
|
||||
assign(data);
|
||||
MEMF("Bytes object created from data-copy \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
// Construct from rvalue std::vector<uint8_t> (move)
|
||||
Bytes(Data&& rdata) {
|
||||
MEM("Creating from data-move...");
|
||||
assign(std::move(rdata));
|
||||
MEMF("Bytes object created from data-move \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
Bytes(const uint8_t* chunk, size_t size) {
|
||||
assign(chunk, size);
|
||||
MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
Bytes(const void* chunk, size_t size) {
|
||||
assign(chunk, size);
|
||||
MEMF("Bytes object created from chunk \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
Bytes(const char* string) {
|
||||
assign(string);
|
||||
MEMF("Bytes object created from string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
Bytes(const std::string& string) {
|
||||
assign(string);
|
||||
MEMF("Bytes object created from std::string \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
Bytes(size_t capacity) {
|
||||
newData(capacity);
|
||||
MEMF("Bytes object created with capacity %u, this: %lu, data: %lu", capacity, this, _data.get());
|
||||
}
|
||||
virtual ~Bytes() {
|
||||
MEMF("Bytes object destroyed \"%s\", this: %lu, data: %lu", toString().c_str(), this, _data.get());
|
||||
}
|
||||
|
||||
inline const Bytes& operator = (const Bytes& bytes) {
|
||||
assign(bytes);
|
||||
return *this;
|
||||
}
|
||||
inline const Bytes& operator += (const Bytes& bytes) {
|
||||
append(bytes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// CBA TODO Resolve ambiguity in JsonVariantConst assignments before enabling the following
|
||||
/*
|
||||
// Assignment from std::vector<uint8_t>
|
||||
inline const Bytes& operator = (const Data& data) {
|
||||
assign(data);
|
||||
return *this;
|
||||
}
|
||||
// Move assignment from std::vector<uint8_t>
|
||||
inline const Bytes& operator = (Data&& rdata) {
|
||||
assign(std::move(rdata));
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
inline const Bytes& operator += (const Data& data) {
|
||||
append(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Bytes operator + (const Bytes& bytes) const {
|
||||
Bytes newbytes(*this);
|
||||
newbytes.append(bytes);
|
||||
return newbytes;
|
||||
}
|
||||
inline bool operator == (const Bytes& bytes) const {
|
||||
return compare(bytes) == 0;
|
||||
}
|
||||
inline bool operator != (const Bytes& bytes) const {
|
||||
return compare(bytes) != 0;
|
||||
}
|
||||
inline bool operator < (const Bytes& bytes) const {
|
||||
return compare(bytes) < 0;
|
||||
}
|
||||
inline bool operator > (const Bytes& bytes) const {
|
||||
return compare(bytes) > 0;
|
||||
}
|
||||
|
||||
inline uint8_t& operator[](size_t index) {
|
||||
if (!_data || index >= _data->size()) {
|
||||
throw std::out_of_range("Index out of bounds");
|
||||
}
|
||||
return (*_data)[index];
|
||||
}
|
||||
|
||||
inline const uint8_t& operator[](size_t index) const {
|
||||
if (!_data || index >= _data->size()) {
|
||||
throw std::out_of_range("Index out of bounds");
|
||||
}
|
||||
return (*_data)[index];
|
||||
}
|
||||
|
||||
inline operator bool() const {
|
||||
return (_data && !_data->empty());
|
||||
}
|
||||
inline operator const Data() const {
|
||||
if (!_data)
|
||||
return Data();
|
||||
return *_data.get();
|
||||
}
|
||||
// CBA NOTE: Following cast operators can cause issues with ambiguity from other libraries
|
||||
/*
|
||||
inline operator const uint8_t*() const {
|
||||
if (!_data)
|
||||
return nullptr;
|
||||
return _data->data();
|
||||
}
|
||||
inline operator const void*() const {
|
||||
if (!_data)
|
||||
return nullptr;
|
||||
return _data->data();
|
||||
}
|
||||
*/
|
||||
|
||||
private:
|
||||
inline SharedData shareData() const {
|
||||
//MEM("Bytes is sharing its own data");
|
||||
_exclusive = false;
|
||||
return _data;
|
||||
}
|
||||
void newData(size_t capacity = 0);
|
||||
void exclusiveData(bool copy = true, size_t capacity = 0);
|
||||
|
||||
public:
|
||||
inline void clear() {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
}
|
||||
|
||||
inline void assign(const Bytes& bytes) {
|
||||
#ifdef COW
|
||||
_data = bytes.shareData();
|
||||
_exclusive = false;
|
||||
#else
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (bytes.size() <= 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
exclusiveData(false, bytes._data->size());
|
||||
//_data->insert(_data->begin(), bytes._data->begin(), bytes._data->end());
|
||||
//_data->assign(bytes._data->begin(), bytes._data->end());
|
||||
*_data = bytes._data;
|
||||
#endif
|
||||
}
|
||||
inline void assign(const Data& data) {
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (data.size() <= 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
exclusiveData(false, data.size());
|
||||
//_data->assign(data.begin(), data.end());
|
||||
*_data = data;
|
||||
}
|
||||
inline void assign(Data&& rdata) {
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (rdata.size() <= 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
exclusiveData(false);
|
||||
*_data = std::move(rdata);
|
||||
}
|
||||
inline void assign(const uint8_t* chunk, size_t chunk_size) {
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (chunk == nullptr || chunk_size <= 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
exclusiveData(false, chunk_size);
|
||||
//_data->insert(_data->begin(), chunk, chunk + chunk_size);
|
||||
_data->assign(chunk, chunk + chunk_size);
|
||||
}
|
||||
inline void assign(const void* chunk, size_t chunk_size) {
|
||||
assign((uint8_t*)chunk, chunk_size);
|
||||
}
|
||||
inline void assign(const char* string) {
|
||||
// if assignment is empty then clear data and don't bother creating new
|
||||
if (string == nullptr || string[0] == 0) {
|
||||
_data = nullptr;
|
||||
_exclusive = true;
|
||||
return;
|
||||
}
|
||||
size_t string_size = strlen(string);
|
||||
exclusiveData(false, string_size);
|
||||
//_data->insert(_data->begin(), (uint8_t* )string, (uint8_t* )string + string_size);
|
||||
_data->assign((uint8_t* )string, (uint8_t* )string + string_size);
|
||||
}
|
||||
//inline void assign(const std::string& string) { assign(string.c_str()); }
|
||||
inline void assign(const std::string& string) { assign((uint8_t*)string.c_str(), string.length()); }
|
||||
void assignHex(const uint8_t* hex, size_t hex_size);
|
||||
inline void assignHex(const char* hex) { assignHex((uint8_t*)hex, strlen(hex)); }
|
||||
|
||||
inline void append(const Bytes& bytes) {
|
||||
// if append is empty then do nothing
|
||||
if (bytes.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
exclusiveData(true, size() + bytes.size());
|
||||
_data->insert(_data->end(), bytes._data->begin(), bytes._data->end());
|
||||
}
|
||||
inline void append(const Data& data) {
|
||||
// if append is empty then do nothing
|
||||
if (data.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
exclusiveData(true, size() + data.size());
|
||||
_data->insert(_data->end(), data.begin(), data.end());
|
||||
}
|
||||
inline void append(const uint8_t* chunk, size_t chunk_size) {
|
||||
// if append is empty then do nothing
|
||||
if (chunk == nullptr || chunk_size <= 0) {
|
||||
return;
|
||||
}
|
||||
exclusiveData(true, size() + chunk_size);
|
||||
_data->insert(_data->end(), chunk, chunk + chunk_size);
|
||||
}
|
||||
inline void append(const void* chunk, size_t chunk_size) {
|
||||
append((uint8_t*)chunk, chunk_size);
|
||||
}
|
||||
inline void append(const char* string) {
|
||||
// if append is empty then do nothing
|
||||
if (string == nullptr || string[0] == 0) {
|
||||
return;
|
||||
}
|
||||
size_t string_size = strlen(string);
|
||||
exclusiveData(true, size() + string_size);
|
||||
_data->insert(_data->end(), (uint8_t* )string, (uint8_t* )string + string_size);
|
||||
}
|
||||
inline void append(uint8_t byte) {
|
||||
exclusiveData(true, size() + 1);
|
||||
_data->push_back(byte);
|
||||
}
|
||||
//inline void append(const std::string& string) { append(string.c_str()); }
|
||||
inline void append(const std::string& string) { append((uint8_t*)string.c_str(), string.length()); }
|
||||
void appendHex(const uint8_t* hex, size_t hex_size);
|
||||
inline void appendHex(const char* hex) { appendHex((uint8_t*)hex, strlen(hex)); }
|
||||
|
||||
inline uint8_t* writable(size_t size) {
|
||||
if (size > 0) {
|
||||
// create exclusive data with reserved capacity
|
||||
exclusiveData(false, size);
|
||||
// actually expand data to requested size
|
||||
resize(size);
|
||||
return _data->data();
|
||||
}
|
||||
else if (_data) {
|
||||
size_t current_size = _data->size();
|
||||
// create exclusive data with reserved capacity
|
||||
exclusiveData(false, current_size);
|
||||
// actually expand data to current size
|
||||
resize(current_size);
|
||||
return _data->data();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline void resize(size_t newsize) {
|
||||
// if size is unchanged then do nothing
|
||||
if (newsize == size()) {
|
||||
return;
|
||||
}
|
||||
// CBA TODO Determine whether or not to reserve capacity here since when size is shrunk the call to
|
||||
// exclusive data may copy all data firt which will effectively grow the data again before being
|
||||
// shrunk below (inefficient).
|
||||
exclusiveData(true);
|
||||
_data->resize(newsize);
|
||||
}
|
||||
|
||||
public:
|
||||
int compare(const Bytes& bytes) const;
|
||||
int compare(const uint8_t* buf, size_t size) const;
|
||||
inline int compare(const char* str) const { return compare((const uint8_t*)str, strlen(str)); }
|
||||
inline size_t size() const { if (!_data) return 0; return _data->size(); }
|
||||
inline bool empty() const { if (!_data) return true; return _data->empty(); }
|
||||
inline size_t capacity() const { if (!_data) return 0; return _data->capacity(); }
|
||||
inline void reserve(size_t capacity) const { if (!_data) return; _data->reserve(capacity); }
|
||||
inline const uint8_t* data() const { if (!_data) return nullptr; return _data->data(); }
|
||||
inline const Data collection() const { if (!_data) return Data(); return *_data.get(); }
|
||||
|
||||
inline std::string toString() const { if (!_data) return ""; return {(const char*)data(), size()}; }
|
||||
std::string toHex(bool upper = false) const;
|
||||
Bytes mid(size_t beginpos, size_t len) const;
|
||||
Bytes mid(size_t beginpos) const;
|
||||
inline Bytes left(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data(), len}; }
|
||||
inline Bytes right(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data() + (size() - len), len}; }
|
||||
inline int find(int pos, const char* str) {
|
||||
if (!_data || _data->data() == nullptr || (size_t)pos >= _data->size()) {
|
||||
return -1;
|
||||
}
|
||||
//const char* ptr = strnstr((const char*)(_data->data() + pos), str, (_data->size() - pos));
|
||||
void* ptr = memmem((const void*)(_data->data() + pos), (_data->size() - pos), (const void*)str, strlen(str));
|
||||
if (ptr == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
return (int)((uint8_t*)ptr - _data->data());
|
||||
}
|
||||
inline int find(const char* str) { return find(0, str); }
|
||||
|
||||
// Python array indexing
|
||||
// [8:16]
|
||||
// pos 8 to pos 16
|
||||
// mid(8, 8)
|
||||
// [:16]
|
||||
// start to pos 16 (same as first 16)
|
||||
// left(16)
|
||||
// [16:]
|
||||
// pos 16 to end
|
||||
// mid(16)
|
||||
// [-16:]
|
||||
// last 16
|
||||
// right(16)
|
||||
// [:-16]
|
||||
// all except the last 16
|
||||
// left(size()-16)
|
||||
// mid(0, size()-16)
|
||||
// [-1]
|
||||
// last element
|
||||
// [-2]
|
||||
// second to last element
|
||||
|
||||
private:
|
||||
SharedData _data;
|
||||
mutable bool _exclusive = true;
|
||||
|
||||
};
|
||||
|
||||
// following array function doesn't work without size since it's passed as a pointer to the array so sizeof() is of the pointer
|
||||
//inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); }
|
||||
//inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return Bytes(ptr, len); }
|
||||
inline Bytes bytesFromChunk(const uint8_t* ptr, size_t len) { return {ptr, len}; }
|
||||
//inline Bytes bytesFromString(const char* str) { return Bytes((uint8_t*)str, strlen(str)); }
|
||||
inline Bytes bytesFromString(const char* str) { return {(uint8_t*)str, strlen(str)}; }
|
||||
//z inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; }
|
||||
|
||||
inline std::string stringFromBytes(const Bytes& bytes) { return bytes.toString(); }
|
||||
inline std::string hexFromBytes(const Bytes& bytes) { return bytes.toHex(); }
|
||||
std::string hexFromByte(uint8_t byte, bool upper = true);
|
||||
|
||||
}
|
||||
|
||||
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const RNS::Bytes& rhbytes) {
|
||||
//MEM("Appending right-hand Bytes to left-hand Bytes");
|
||||
lhbytes.append(rhbytes);
|
||||
//MEM("Returning left-hand Bytes");
|
||||
return lhbytes;
|
||||
}
|
||||
|
||||
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, uint8_t rhbyte) {
|
||||
//MEM("Appending right-hand byte to left-hand Bytes");
|
||||
lhbytes.append(rhbyte);
|
||||
//MEM("Returning left-hand Bytes");
|
||||
return lhbytes;
|
||||
}
|
||||
|
||||
inline RNS::Bytes& operator << (RNS::Bytes& lhbytes, const char* rhstr) {
|
||||
//MEM("Appending right-hand str to left-hand Bytes");
|
||||
lhbytes.append(rhstr);
|
||||
//MEM("Returning left-hand Bytes");
|
||||
return lhbytes;
|
||||
}
|
||||
|
||||
namespace ArduinoJson {
|
||||
// Serialize
|
||||
inline bool convertToJson(const RNS::Bytes& src, JsonVariant dst) {
|
||||
return dst.set(src.toHex());
|
||||
}
|
||||
// Deserialize
|
||||
inline void convertFromJson(JsonVariantConst src, RNS::Bytes& dst) {
|
||||
dst.assignHex(src.as<const char*>());
|
||||
}
|
||||
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Bytes&) {
|
||||
return src.is<const char*>();
|
||||
}
|
||||
}
|
||||
12
lib/microReticulum/src/Channel.cpp
Executable file
12
lib/microReticulum/src/Channel.cpp
Executable file
@@ -0,0 +1,12 @@
|
||||
#include "Channel.h"
|
||||
|
||||
#include "Reticulum.h"
|
||||
#include "Transport.h"
|
||||
#include "Packet.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Channel;
|
||||
using namespace RNS::Utilities;
|
||||
57
lib/microReticulum/src/Channel.h
Executable file
57
lib/microReticulum/src/Channel.h
Executable file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "Destination.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Channel {
|
||||
|
||||
public:
|
||||
Channel(Type::NoneConstructor none) {
|
||||
MEM("Channel NONE object created");
|
||||
}
|
||||
Channel(const Channel& resource) : _object(resource._object) {
|
||||
MEM("Channel object copy created");
|
||||
}
|
||||
virtual ~Channel(){
|
||||
MEM("Channel object destroyed");
|
||||
}
|
||||
|
||||
Channel& operator = (const Channel& resource) {
|
||||
_object = resource._object;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
bool operator < (const Channel& resource) const {
|
||||
return _object.get() < resource._object.get();
|
||||
//return _object->_hash < resource._object->_hash;
|
||||
}
|
||||
|
||||
public:
|
||||
void _shutdown() {}
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object() { MEM("Channel::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
virtual ~Object() { MEM("Channel::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
|
||||
private:
|
||||
|
||||
friend class Channel;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class ChannelAdvertisement {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
89
lib/microReticulum/src/Cryptography/AES.h
Executable file
89
lib/microReticulum/src/Cryptography/AES.h
Executable file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include "CBC.h"
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <AES.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
class AES_128_CBC {
|
||||
|
||||
public:
|
||||
static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES128> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
Bytes ciphertext;
|
||||
cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES128> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
Bytes plaintext;
|
||||
cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
// EXPERIMENTAL - overwrites passed buffer
|
||||
static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES128> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
|
||||
}
|
||||
|
||||
// EXPERIMENTAL - overwrites passed buffer
|
||||
static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES128> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class AES_256_CBC {
|
||||
|
||||
public:
|
||||
static inline const Bytes encrypt(const Bytes& plaintext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES256> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
Bytes ciphertext;
|
||||
cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size());
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
static inline const Bytes decrypt(const Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES256> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
Bytes plaintext;
|
||||
cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size());
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
// EXPERIMENTAL - overwrites passed buffer
|
||||
static inline void inplace_encrypt(Bytes& plaintext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES256> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size());
|
||||
}
|
||||
|
||||
// EXPERIMENTAL - overwrites passed buffer
|
||||
static inline void inplace_decrypt(Bytes& ciphertext, const Bytes& key, const Bytes& iv) {
|
||||
CBC<AES256> cbc;
|
||||
cbc.setKey(key.data(), key.size());
|
||||
cbc.setIV(iv.data(), iv.size());
|
||||
cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
171
lib/microReticulum/src/Cryptography/CBC.cpp
Executable file
171
lib/microReticulum/src/Cryptography/CBC.cpp
Executable file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "CBC.h"
|
||||
#include <Crypto.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* \class CBCCommon CBC.h <CBC.h>
|
||||
* \brief Concrete base class to assist with implementing CBC for
|
||||
* 128-bit block ciphers.
|
||||
*
|
||||
* Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
*
|
||||
* \sa CBC
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief Constructs a new cipher in CBC mode.
|
||||
*
|
||||
* This constructor should be followed by a call to setBlockCipher().
|
||||
*/
|
||||
CBCCommon::CBCCommon()
|
||||
: blockCipher(0)
|
||||
, posn(16)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Destroys this cipher object after clearing sensitive information.
|
||||
*/
|
||||
CBCCommon::~CBCCommon()
|
||||
{
|
||||
clean(iv);
|
||||
clean(temp);
|
||||
}
|
||||
|
||||
size_t CBCCommon::keySize() const
|
||||
{
|
||||
return blockCipher->keySize();
|
||||
}
|
||||
|
||||
size_t CBCCommon::ivSize() const
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
bool CBCCommon::setKey(const uint8_t* key, size_t len)
|
||||
{
|
||||
// Verify the cipher's block size, just in case.
|
||||
if (blockCipher->blockSize() != 16)
|
||||
return false;
|
||||
|
||||
// Set the key on the underlying block cipher.
|
||||
return blockCipher->setKey(key, len);
|
||||
}
|
||||
|
||||
bool CBCCommon::setIV(const uint8_t* iv, size_t len)
|
||||
{
|
||||
if (len != 16)
|
||||
return false;
|
||||
memcpy(this->iv, iv, 16);
|
||||
posn = 16;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBCCommon::encrypt(uint8_t* output, const uint8_t* input, size_t len)
|
||||
{
|
||||
uint8_t posn;
|
||||
while (len >= 16) {
|
||||
for (posn = 0; posn < 16; ++posn)
|
||||
iv[posn] ^= *input++;
|
||||
blockCipher->encryptBlock(iv, iv);
|
||||
for (posn = 0; posn < 16; ++posn)
|
||||
*output++ = iv[posn];
|
||||
len -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
void CBCCommon::decrypt(uint8_t* output, const uint8_t* input, size_t len)
|
||||
{
|
||||
uint8_t posn;
|
||||
while (len >= 16) {
|
||||
blockCipher->decryptBlock(temp, input);
|
||||
for (posn = 0; posn < 16; ++posn) {
|
||||
uint8_t in = *input++;
|
||||
*output++ = temp[posn] ^ iv[posn];
|
||||
iv[posn] = in;
|
||||
}
|
||||
len -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
void CBCCommon::clear()
|
||||
{
|
||||
blockCipher->clear();
|
||||
clean(iv);
|
||||
clean(temp);
|
||||
posn = 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn void CBCCommon::setBlockCipher(BlockCipher *cipher)
|
||||
* \brief Sets the block cipher to use for this CBC object.
|
||||
*
|
||||
* \param cipher The block cipher to use to implement CBC mode,
|
||||
* which must have a block size of 16 bytes (128 bits).
|
||||
*/
|
||||
|
||||
/**
|
||||
* \class CBC CBC.h <CBC.h>
|
||||
* \brief Implementation of the Cipher Block Chaining (CBC) mode for
|
||||
* 128-bit block ciphers.
|
||||
*
|
||||
* The template parameter T must be a concrete subclass of BlockCipher
|
||||
* indicating the specific block cipher to use. T must have a block size
|
||||
* of 16 bytes (128 bits).
|
||||
*
|
||||
* For example, the following creates a CBC object using AES192 as the
|
||||
* underlying cipher:
|
||||
*
|
||||
* \code
|
||||
* CBC<AES192> cbc;
|
||||
* cbc.setKey(key, 24);
|
||||
* cbc.setIV(iv, 16);
|
||||
* cbc.encrypt(output, input, len);
|
||||
* \endcode
|
||||
*
|
||||
* Decryption is similar:
|
||||
*
|
||||
* \code
|
||||
* CBC<AES192> cbc;
|
||||
* cbc.setKey(key, 24);
|
||||
* cbc.setIV(iv, 16);
|
||||
* cbc.decrypt(output, input, len);
|
||||
* \endcode
|
||||
*
|
||||
* The size of the ciphertext will always be the same as the size of
|
||||
* the plaintext. Also, the length of the plaintext/ciphertext must be a
|
||||
* multiple of 16. Extra bytes are ignored and not encrypted. The caller
|
||||
* is responsible for padding the underlying data to a multiple of 16
|
||||
* using an appropriate padding scheme for the application.
|
||||
*
|
||||
* Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
*
|
||||
* \sa CTR, CFB, OFB
|
||||
*/
|
||||
|
||||
/**
|
||||
* \fn CBC::CBC()
|
||||
* \brief Constructs a new CBC object for the block cipher T.
|
||||
*/
|
||||
66
lib/microReticulum/src/Cryptography/CBC.h
Executable file
66
lib/microReticulum/src/Cryptography/CBC.h
Executable file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef CRYPTO_CBC_h
|
||||
#define CRYPTO_CBC_h
|
||||
|
||||
#include <Cipher.h>
|
||||
#include <BlockCipher.h>
|
||||
|
||||
class CBCCommon : public Cipher
|
||||
{
|
||||
public:
|
||||
virtual ~CBCCommon();
|
||||
|
||||
size_t keySize() const;
|
||||
size_t ivSize() const;
|
||||
|
||||
bool setKey(const uint8_t* key, size_t len);
|
||||
bool setIV(const uint8_t* iv, size_t len);
|
||||
|
||||
void encrypt(uint8_t* output, const uint8_t* input, size_t len);
|
||||
void decrypt(uint8_t* output, const uint8_t* input, size_t len);
|
||||
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
CBCCommon();
|
||||
void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
|
||||
|
||||
private:
|
||||
BlockCipher *blockCipher;
|
||||
uint8_t iv[16];
|
||||
uint8_t temp[16];
|
||||
uint8_t posn;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class CBC : public CBCCommon
|
||||
{
|
||||
public:
|
||||
CBC() { setBlockCipher(&cipher); }
|
||||
|
||||
private:
|
||||
T cipher;
|
||||
};
|
||||
|
||||
#endif
|
||||
3
lib/microReticulum/src/Cryptography/Ed25519.cpp
Executable file
3
lib/microReticulum/src/Cryptography/Ed25519.cpp
Executable file
@@ -0,0 +1,3 @@
|
||||
#include "Ed25519.h"
|
||||
|
||||
using namespace RNS::Cryptography;
|
||||
102
lib/microReticulum/src/Cryptography/Ed25519.h
Executable file
102
lib/microReticulum/src/Cryptography/Ed25519.h
Executable file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include "Bytes.h"
|
||||
|
||||
#include <Ed25519.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/*
|
||||
|
||||
Note that the library currently in use for Ed25519 does not support generating keys from a seed.
|
||||
|
||||
*/
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
class Ed25519PublicKey {
|
||||
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Ed25519PublicKey>;
|
||||
|
||||
public:
|
||||
Ed25519PublicKey(const Bytes& publicKey) {
|
||||
_publicKey = publicKey;
|
||||
}
|
||||
~Ed25519PublicKey() {}
|
||||
|
||||
public:
|
||||
// creates a new instance with specified seed
|
||||
static inline Ptr from_public_bytes(const Bytes& publicKey) {
|
||||
return Ptr(new Ed25519PublicKey(publicKey));
|
||||
}
|
||||
|
||||
inline const Bytes& public_bytes() {
|
||||
return _publicKey;
|
||||
}
|
||||
|
||||
inline bool verify(const Bytes& signature, const Bytes& message) {
|
||||
return Ed25519::verify(signature.data(), _publicKey.data(), message.data(), message.size());
|
||||
}
|
||||
|
||||
private:
|
||||
Bytes _publicKey;
|
||||
|
||||
};
|
||||
|
||||
class Ed25519PrivateKey {
|
||||
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Ed25519PrivateKey>;
|
||||
|
||||
public:
|
||||
Ed25519PrivateKey(const Bytes& privateKey) {
|
||||
if (privateKey) {
|
||||
// use specified private key
|
||||
_privateKey = privateKey;
|
||||
}
|
||||
else {
|
||||
// create random private key
|
||||
Ed25519::generatePrivateKey(_privateKey.writable(32));
|
||||
}
|
||||
// derive public key from private key
|
||||
Ed25519::derivePublicKey(_publicKey.writable(32), _privateKey.data());
|
||||
}
|
||||
~Ed25519PrivateKey() {}
|
||||
|
||||
public:
|
||||
// creates a new instance with a random seed
|
||||
static inline Ptr generate() {
|
||||
// CBA TODO determine why below is confused with (implicit) copy constructor
|
||||
//return Ptr(new Ed25519PrivateKey({Bytes::NONE}));
|
||||
return Ptr(new Ed25519PrivateKey(Bytes::NONE));
|
||||
}
|
||||
|
||||
// creates a new instance with specified seed
|
||||
static inline Ptr from_private_bytes(const Bytes& privateKey) {
|
||||
return Ptr(new Ed25519PrivateKey(privateKey));
|
||||
}
|
||||
|
||||
inline const Bytes& private_bytes() {
|
||||
return _privateKey;
|
||||
}
|
||||
|
||||
// creates a new instance of public key for this private key
|
||||
inline Ed25519PublicKey::Ptr public_key() {
|
||||
return Ed25519PublicKey::from_public_bytes(_publicKey);
|
||||
}
|
||||
|
||||
inline const Bytes sign(const Bytes& message) {
|
||||
//z return _sk.sign(message);
|
||||
Bytes signature;
|
||||
Ed25519::sign(signature.writable(64), _privateKey.data(), _publicKey.data(), message.data(), message.size());
|
||||
return signature;
|
||||
}
|
||||
|
||||
private:
|
||||
Bytes _privateKey;
|
||||
Bytes _publicKey;
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
116
lib/microReticulum/src/Cryptography/Fernet.cpp
Executable file
116
lib/microReticulum/src/Cryptography/Fernet.cpp
Executable file
@@ -0,0 +1,116 @@
|
||||
#include "Fernet.h"
|
||||
|
||||
#include "HMAC.h"
|
||||
#include "PKCS7.h"
|
||||
#include "AES.h"
|
||||
#include "../Log.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <time.h>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Cryptography;
|
||||
|
||||
Fernet::Fernet(const Bytes& key) {
|
||||
|
||||
if (!key) {
|
||||
throw std::invalid_argument("Fernet key cannot be None");
|
||||
}
|
||||
|
||||
if (key.size() != 32) {
|
||||
throw std::invalid_argument("Fernet key must be 32 bytes, not " + std::to_string(key.size()));
|
||||
}
|
||||
|
||||
//self._signing_key = key[:16]
|
||||
_signing_key = key.left(16);
|
||||
//self._encryption_key = key[16:]
|
||||
_encryption_key = key.mid(16);
|
||||
|
||||
MEM("Fernet object created");
|
||||
}
|
||||
|
||||
Fernet::~Fernet() {
|
||||
MEM("Fernet object destroyed");
|
||||
}
|
||||
|
||||
bool Fernet::verify_hmac(const Bytes& token) {
|
||||
|
||||
if (token.size() <= 32) {
|
||||
throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
|
||||
}
|
||||
|
||||
//received_hmac = token[-32:]
|
||||
Bytes received_hmac = token.right(32);
|
||||
DEBUG("Fernet::verify_hmac: received_hmac: " + received_hmac.toHex());
|
||||
//expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
|
||||
Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
|
||||
DEBUG("Fernet::verify_hmac: expected_hmac: " + expected_hmac.toHex());
|
||||
|
||||
return (received_hmac == expected_hmac);
|
||||
}
|
||||
|
||||
const Bytes Fernet::encrypt(const Bytes& data) {
|
||||
|
||||
DEBUG("Fernet::encrypt: plaintext length: " + std::to_string(data.size()));
|
||||
Bytes iv = random(16);
|
||||
//double current_time = OS::time();
|
||||
TRACE("Fernet::encrypt: iv: " + iv.toHex());
|
||||
|
||||
TRACE("Fernet::encrypt: plaintext: " + data.toHex());
|
||||
Bytes ciphertext = AES_128_CBC::encrypt(
|
||||
PKCS7::pad(data),
|
||||
_encryption_key,
|
||||
iv
|
||||
);
|
||||
DEBUG("Fernet::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
|
||||
TRACE("Fernet::encrypt: ciphertext: " + ciphertext.toHex());
|
||||
|
||||
Bytes signed_parts = iv + ciphertext;
|
||||
|
||||
//return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
|
||||
Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
|
||||
TRACE("Fernet::encrypt: sig: " + sig.toHex());
|
||||
Bytes token(signed_parts + sig);
|
||||
DEBUG("Fernet::encrypt: token length: " + std::to_string(token.size()));
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
const Bytes Fernet::decrypt(const Bytes& token) {
|
||||
|
||||
DEBUG("Fernet::decrypt: token length: " + std::to_string(token.size()));
|
||||
if (token.size() < 48) {
|
||||
throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
|
||||
}
|
||||
|
||||
if (!verify_hmac(token)) {
|
||||
throw std::invalid_argument("Fernet token HMAC was invalid");
|
||||
}
|
||||
|
||||
//iv = token[:16]
|
||||
Bytes iv = token.left(16);
|
||||
TRACE("Fernet::decrypt: iv: " + iv.toHex());
|
||||
|
||||
//ciphertext = token[16:-32]
|
||||
Bytes ciphertext = token.mid(16, token.size()-48);
|
||||
TRACE("Fernet::decrypt: ciphertext: " + ciphertext.toHex());
|
||||
|
||||
try {
|
||||
Bytes plaintext = PKCS7::unpad(
|
||||
AES_128_CBC::decrypt(
|
||||
ciphertext,
|
||||
_encryption_key,
|
||||
iv
|
||||
)
|
||||
);
|
||||
DEBUG("Fernet::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
|
||||
TRACE("Fernet::decrypt: plaintext: " + plaintext.toHex());
|
||||
|
||||
DEBUG("Fernet::decrypt: plaintext length: " + std::to_string(plaintext.size()));
|
||||
return plaintext;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
WARNING("Could not decrypt Fernet token");
|
||||
throw std::runtime_error("Could not decrypt Fernet token");
|
||||
}
|
||||
}
|
||||
41
lib/microReticulum/src/Cryptography/Fernet.h
Executable file
41
lib/microReticulum/src/Cryptography/Fernet.h
Executable file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "Random.h"
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
/*
|
||||
This class provides a slightly modified implementation of the Fernet spec
|
||||
found at: https://github.com/fernet/spec/blob/master/Spec.md
|
||||
|
||||
According to the spec, a Fernet token includes a one byte VERSION and
|
||||
eight byte TIMESTAMP field at the start of each token. These fields are
|
||||
not relevant to Reticulum. They are therefore stripped from this
|
||||
implementation, since they incur overhead and leak initiator metadata.
|
||||
*/
|
||||
class Fernet {
|
||||
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Fernet>;
|
||||
|
||||
public:
|
||||
static inline const Bytes generate_key() { return random(32); }
|
||||
|
||||
public:
|
||||
Fernet(const Bytes& key);
|
||||
~Fernet();
|
||||
|
||||
public:
|
||||
bool verify_hmac(const Bytes& token);
|
||||
const Bytes encrypt(const Bytes& data);
|
||||
const Bytes decrypt(const Bytes& token);
|
||||
|
||||
private:
|
||||
Bytes _signing_key;
|
||||
Bytes _encryption_key;
|
||||
};
|
||||
|
||||
} }
|
||||
28
lib/microReticulum/src/Cryptography/HKDF.cpp
Executable file
28
lib/microReticulum/src/Cryptography/HKDF.cpp
Executable file
@@ -0,0 +1,28 @@
|
||||
#include "HKDF.h"
|
||||
|
||||
#include <HKDF.h>
|
||||
#include <SHA256.h>
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
const Bytes RNS::Cryptography::hkdf(size_t length, const Bytes& derive_from, const Bytes& salt /*= {Bytes::NONE}*/, const Bytes& context /*= {Bytes::NONE}*/) {
|
||||
|
||||
if (length <= 0) {
|
||||
throw std::invalid_argument("Invalid output key length");
|
||||
}
|
||||
|
||||
if (!derive_from) {
|
||||
throw std::invalid_argument("Cannot derive key from empty input material");
|
||||
}
|
||||
|
||||
HKDF<SHA256> hkdf;
|
||||
if (salt) {
|
||||
hkdf.setKey(derive_from.data(), derive_from.size(), salt.data(), salt.size());
|
||||
}
|
||||
else {
|
||||
hkdf.setKey(derive_from.data(), derive_from.size());
|
||||
}
|
||||
Bytes derived;
|
||||
hkdf.extract(derived.writable(length), length);
|
||||
return derived;
|
||||
}
|
||||
9
lib/microReticulum/src/Cryptography/HKDF.h
Executable file
9
lib/microReticulum/src/Cryptography/HKDF.h
Executable file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
const Bytes hkdf(size_t length, const Bytes& derive_from, const Bytes& salt = {Bytes::NONE}, const Bytes& context = {Bytes::NONE});
|
||||
|
||||
} }
|
||||
109
lib/microReticulum/src/Cryptography/HMAC.h
Executable file
109
lib/microReticulum/src/Cryptography/HMAC.h
Executable file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <Hash.h>
|
||||
#include <SHA256.h>
|
||||
#include <SHA512.h>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
class HMAC {
|
||||
|
||||
public:
|
||||
enum Digest {
|
||||
DIGEST_NONE,
|
||||
DIGEST_SHA256,
|
||||
DIGEST_SHA512,
|
||||
};
|
||||
|
||||
using Ptr = std::shared_ptr<HMAC>;
|
||||
|
||||
public:
|
||||
/*
|
||||
Create a new HMAC object.
|
||||
key: bytes or buffer, key for the keyed hash object.
|
||||
msg: bytes or buffer, Initial input for the hash or None.
|
||||
digest: The underlying hash algorithm to use
|
||||
*/
|
||||
HMAC(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
|
||||
|
||||
if (digest == DIGEST_NONE) {
|
||||
throw std::invalid_argument("Cannot derive key from empty input material");
|
||||
}
|
||||
|
||||
switch (digest) {
|
||||
case DIGEST_SHA256:
|
||||
_hash = std::unique_ptr<Hash>(new SHA256());
|
||||
break;
|
||||
case DIGEST_SHA512:
|
||||
_hash = std::unique_ptr<Hash>(new SHA512());
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("Unknown ior unsuppored digest");
|
||||
}
|
||||
|
||||
_key = key;
|
||||
_hash->resetHMAC(key.data(), key.size());
|
||||
|
||||
if (msg) {
|
||||
update(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Feed data from msg into this hashing object.
|
||||
*/
|
||||
void update(const Bytes& msg) {
|
||||
assert(_hash);
|
||||
_hash->update(msg.data(), msg.size());
|
||||
}
|
||||
|
||||
/*
|
||||
Return the hash value of this hashing object.
|
||||
This returns the hmac value as bytes. The object is
|
||||
not altered in any way by this function; you can continue
|
||||
updating the object after calling this function.
|
||||
*/
|
||||
Bytes digest() {
|
||||
assert(_hash);
|
||||
Bytes result;
|
||||
_hash->finalizeHMAC(_key.data(), _key.size(), result.writable(_hash->hashSize()), _hash->hashSize());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
Create a new hashing object and return it.
|
||||
key: bytes or buffer, The starting key for the hash.
|
||||
msg: bytes or buffer, Initial input for the hash, or None.
|
||||
digest: The underlying hash algorithm to use.
|
||||
You can now feed arbitrary bytes into the object using its update()
|
||||
method, and can ask for the hash value at any time by calling its digest()
|
||||
method.
|
||||
*/
|
||||
static inline Ptr generate(const Bytes& key, const Bytes& msg = {Bytes::NONE}, Digest digest = DIGEST_SHA256) {
|
||||
return Ptr(new HMAC(key, msg, digest));
|
||||
}
|
||||
|
||||
private:
|
||||
Bytes _key;
|
||||
std::unique_ptr<Hash> _hash;
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
Fast inline implementation of HMAC.
|
||||
key: bytes or buffer, The key for the keyed hash object.
|
||||
msg: bytes or buffer, Input message.
|
||||
digest: The underlying hash algorithm to use.
|
||||
*/
|
||||
inline const Bytes digest(const Bytes& key, const Bytes& msg, HMAC::Digest digest = HMAC::DIGEST_SHA256) {
|
||||
HMAC hmac(key, msg, digest);
|
||||
hmac.update(msg);
|
||||
return hmac.digest();
|
||||
}
|
||||
|
||||
} }
|
||||
36
lib/microReticulum/src/Cryptography/Hashes.cpp
Executable file
36
lib/microReticulum/src/Cryptography/Hashes.cpp
Executable file
@@ -0,0 +1,36 @@
|
||||
#include "Hashes.h"
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <SHA256.h>
|
||||
#include <SHA512.h>
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
/*
|
||||
The SHA primitives are abstracted here to allow platform-
|
||||
aware hardware acceleration in the future. Currently only
|
||||
uses Python's internal SHA-256 implementation. All SHA-256
|
||||
calls in RNS end up here.
|
||||
*/
|
||||
|
||||
const Bytes RNS::Cryptography::sha256(const Bytes& data) {
|
||||
//TRACE("Cryptography::sha256: data: " + data.toHex() );
|
||||
SHA256 digest;
|
||||
digest.reset();
|
||||
digest.update(data.data(), data.size());
|
||||
Bytes hash;
|
||||
digest.finalize(hash.writable(32), 32);
|
||||
//TRACE("Cryptography::sha256: hash: " + hash.toHex() );
|
||||
return hash;
|
||||
}
|
||||
|
||||
const Bytes RNS::Cryptography::sha512(const Bytes& data) {
|
||||
SHA512 digest;
|
||||
digest.reset();
|
||||
digest.update(data.data(), data.size());
|
||||
Bytes hash;
|
||||
digest.finalize(hash.writable(64), 64);
|
||||
//TRACE("Cryptography::sha512: hash: " + hash.toHex() );
|
||||
return hash;
|
||||
}
|
||||
12
lib/microReticulum/src/Cryptography/Hashes.h
Executable file
12
lib/microReticulum/src/Cryptography/Hashes.h
Executable file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
const Bytes sha256(const Bytes& data);
|
||||
const Bytes sha512(const Bytes& data);
|
||||
|
||||
} }
|
||||
66
lib/microReticulum/src/Cryptography/PKCS7.h
Executable file
66
lib/microReticulum/src/Cryptography/PKCS7.h
Executable file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Bytes.h"
|
||||
//#include "../Log.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
class PKCS7 {
|
||||
|
||||
public:
|
||||
|
||||
static const size_t BLOCKSIZE = 16;
|
||||
|
||||
static inline const Bytes pad(const Bytes& data, size_t bs = BLOCKSIZE) {
|
||||
Bytes padded(data);
|
||||
inplace_pad(padded, bs);
|
||||
return padded;
|
||||
}
|
||||
|
||||
static inline const Bytes unpad(const Bytes& data, size_t bs = BLOCKSIZE) {
|
||||
Bytes unpadded(data);
|
||||
inplace_unpad(unpadded, bs);
|
||||
return unpadded;
|
||||
}
|
||||
|
||||
// updates passed buffer
|
||||
static inline void inplace_pad(Bytes& data, size_t bs = BLOCKSIZE) {
|
||||
size_t len = data.size();
|
||||
//DEBUG("PKCS7::pad: len: " + std::to_string(len));
|
||||
size_t padlen = bs - (len % bs);
|
||||
//DEBUG("PKCS7::pad: pad len: " + std::to_string(padlen));
|
||||
// create zero-filled byte padding array of size padlen
|
||||
//p v = bytes([padlen])
|
||||
//uint8_t pad[padlen] = {0};
|
||||
uint8_t pad[padlen];
|
||||
memset(pad, 0, padlen);
|
||||
// set last byte of padding array to size of padding
|
||||
pad[padlen-1] = (uint8_t)padlen;
|
||||
// concatenate data with padding
|
||||
//p return data+v*padlen
|
||||
data.append(pad, padlen);
|
||||
//DEBUG("PKCS7::pad: data size: " + std::to_string(data.size()));
|
||||
}
|
||||
|
||||
// updates passed buffer
|
||||
static inline void inplace_unpad(Bytes& data, size_t bs = BLOCKSIZE) {
|
||||
size_t len = data.size();
|
||||
//DEBUG("PKCS7::unpad: len: " + std::to_string(len));
|
||||
// read last byte which is pad length
|
||||
//pad = data[-1]
|
||||
size_t padlen = (size_t)data.data()[data.size()-1];
|
||||
//DEBUG("PKCS7::unpad: pad len: " + std::to_string(padlen));
|
||||
if (padlen > bs) {
|
||||
throw std::runtime_error("Cannot unpad, invalid padding length of " + std::to_string(padlen) + " bytes");
|
||||
}
|
||||
// truncate data to strip padding
|
||||
//return data[:len-padlen]
|
||||
data.resize(len - padlen);
|
||||
//DEBUG("PKCS7::unpad: data size: " + std::to_string(data.size()));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
38
lib/microReticulum/src/Cryptography/Random.h
Executable file
38
lib/microReticulum/src/Cryptography/Random.h
Executable file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include <RNG.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
// return vector specified length of random bytes
|
||||
inline const Bytes random(size_t length) {
|
||||
Bytes rand;
|
||||
RNG.rand(rand.writable(length), length);
|
||||
return rand;
|
||||
}
|
||||
|
||||
// return 32 bit random unigned int
|
||||
inline uint32_t randomnum() {
|
||||
Bytes rand;
|
||||
RNG.rand(rand.writable(4), 4);
|
||||
uint32_t randnum = uint32_t((unsigned char)(rand.data()[0]) << 24 |
|
||||
(unsigned char)(rand.data()[0]) << 16 |
|
||||
(unsigned char)(rand.data()[0]) << 8 |
|
||||
(unsigned char)(rand.data()[0]));
|
||||
return randnum;
|
||||
}
|
||||
|
||||
// return 32 bit random unsigned int between 0 and specified value
|
||||
inline uint32_t randomnum(uint32_t max) {
|
||||
return randomnum() % max;
|
||||
}
|
||||
|
||||
// return random float value from 0 to 1
|
||||
inline float random() {
|
||||
return (float)(randomnum() / (float)0xffffffff);
|
||||
}
|
||||
|
||||
} }
|
||||
159
lib/microReticulum/src/Cryptography/Token.cpp
Executable file
159
lib/microReticulum/src/Cryptography/Token.cpp
Executable file
@@ -0,0 +1,159 @@
|
||||
#include "Token.h"
|
||||
|
||||
#include "HMAC.h"
|
||||
#include "PKCS7.h"
|
||||
#include "AES.h"
|
||||
#include "../Log.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <time.h>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Cryptography;
|
||||
using namespace RNS::Type::Cryptography::Token;
|
||||
|
||||
Token::Token(const Bytes& key, token_mode mode /*= AES*/) {
|
||||
|
||||
if (!key) {
|
||||
throw std::invalid_argument("Token key cannot be None");
|
||||
}
|
||||
|
||||
if (mode == MODE_AES) {
|
||||
if (key.size() == 32) {
|
||||
_mode = MODE_AES_128_CBC;
|
||||
//p self._signing_key = key[:16]
|
||||
_signing_key = key.left(16);
|
||||
//p self._encryption_key = key[16:]
|
||||
_encryption_key = key.mid(16);
|
||||
}
|
||||
else if (key.size() == 64) {
|
||||
_mode = MODE_AES_256_CBC;
|
||||
//p self._signing_key = key[:32]
|
||||
_signing_key = key.left(32);
|
||||
//p self._encryption_key = key[32:]
|
||||
_encryption_key = key.mid(32);
|
||||
}
|
||||
else {
|
||||
throw std::invalid_argument("Token key must be 128 or 256 bits, not " + std::to_string(key.size()*8));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw std::invalid_argument("Invalid token mode: " + std::to_string(mode));
|
||||
}
|
||||
|
||||
MEM("Token object created");
|
||||
}
|
||||
|
||||
Token::~Token() {
|
||||
MEM("Token object destroyed");
|
||||
}
|
||||
|
||||
bool Token::verify_hmac(const Bytes& token) {
|
||||
|
||||
if (token.size() <= 32) {
|
||||
throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes");
|
||||
}
|
||||
|
||||
//received_hmac = token[-32:]
|
||||
Bytes received_hmac = token.right(32);
|
||||
DEBUG("Token::verify_hmac: received_hmac: " + received_hmac.toHex());
|
||||
//expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
|
||||
Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest();
|
||||
DEBUG("Token::verify_hmac: expected_hmac: " + expected_hmac.toHex());
|
||||
|
||||
return (received_hmac == expected_hmac);
|
||||
}
|
||||
|
||||
const Bytes Token::encrypt(const Bytes& data) {
|
||||
|
||||
DEBUG("Token::encrypt: plaintext length: " + std::to_string(data.size()));
|
||||
Bytes iv = random(16);
|
||||
//double current_time = OS::time();
|
||||
TRACE("Token::encrypt: iv: " + iv.toHex());
|
||||
|
||||
TRACE("Token::encrypt: plaintext: " + data.toHex());
|
||||
Bytes ciphertext;
|
||||
if (_mode == MODE_AES_128_CBC) {
|
||||
ciphertext = AES_128_CBC::encrypt(
|
||||
PKCS7::pad(data),
|
||||
_encryption_key,
|
||||
iv
|
||||
);
|
||||
}
|
||||
else if (_mode == MODE_AES_256_CBC) {
|
||||
ciphertext = AES_256_CBC::encrypt(
|
||||
PKCS7::pad(data),
|
||||
_encryption_key,
|
||||
iv
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
|
||||
}
|
||||
DEBUG("Token::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size()));
|
||||
TRACE("Token::encrypt: ciphertext: " + ciphertext.toHex());
|
||||
|
||||
Bytes signed_parts = iv + ciphertext;
|
||||
|
||||
//return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest();
|
||||
Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest());
|
||||
TRACE("Token::encrypt: sig: " + sig.toHex());
|
||||
Bytes token(signed_parts + sig);
|
||||
DEBUG("Token::encrypt: token length: " + std::to_string(token.size()));
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
const Bytes Token::decrypt(const Bytes& token) {
|
||||
|
||||
DEBUG("Token::decrypt: token length: " + std::to_string(token.size()));
|
||||
if (token.size() < 48) {
|
||||
throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes");
|
||||
}
|
||||
|
||||
if (!verify_hmac(token)) {
|
||||
throw std::invalid_argument("Token token HMAC was invalid");
|
||||
}
|
||||
|
||||
//iv = token[:16]
|
||||
Bytes iv = token.left(16);
|
||||
TRACE("Token::decrypt: iv: " + iv.toHex());
|
||||
|
||||
//ciphertext = token[16:-32]
|
||||
Bytes ciphertext = token.mid(16, token.size()-48);
|
||||
TRACE("Token::decrypt: ciphertext: " + ciphertext.toHex());
|
||||
|
||||
try {
|
||||
Bytes plaintext;
|
||||
if (_mode == MODE_AES_128_CBC) {
|
||||
plaintext = PKCS7::unpad(
|
||||
AES_128_CBC::decrypt(
|
||||
ciphertext,
|
||||
_encryption_key,
|
||||
iv
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (_mode == MODE_AES_256_CBC) {
|
||||
plaintext = PKCS7::unpad(
|
||||
AES_256_CBC::decrypt(
|
||||
ciphertext,
|
||||
_encryption_key,
|
||||
iv
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw new std::invalid_argument("Invalid token mode "+std::to_string(_mode));
|
||||
}
|
||||
DEBUG("Token::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size()));
|
||||
TRACE("Token::decrypt: plaintext: " + plaintext.toHex());
|
||||
|
||||
DEBUG("Token::decrypt: plaintext length: " + std::to_string(plaintext.size()));
|
||||
return plaintext;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
WARNING("Could not decrypt Token token");
|
||||
throw std::runtime_error("Could not decrypt Token token");
|
||||
}
|
||||
}
|
||||
47
lib/microReticulum/src/Cryptography/Token.h
Executable file
47
lib/microReticulum/src/Cryptography/Token.h
Executable file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "Random.h"
|
||||
#include "../Bytes.h"
|
||||
#include "../Type.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
/*
|
||||
This class provides a slightly modified implementation of the Fernet spec
|
||||
found at: https://github.com/fernet/spec/blob/master/Spec.md
|
||||
|
||||
According to the spec, a Fernet token includes a one byte VERSION and
|
||||
eight byte TIMESTAMP field at the start of each token. These fields are
|
||||
not relevant to Reticulum. They are therefore stripped from this
|
||||
implementation, since they incur overhead and leak initiator metadata.
|
||||
*/
|
||||
class Token {
|
||||
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Token>;
|
||||
|
||||
public:
|
||||
static inline const Bytes generate_key(RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC) {
|
||||
if (mode == RNS::Type::Cryptography::Token::MODE_AES_128_CBC) return random(32);
|
||||
else if (mode == RNS::Type::Cryptography::Token::MODE_AES_256_CBC) return random(64);
|
||||
else throw new std::invalid_argument("Invalid token mode: " + std::to_string(mode));
|
||||
}
|
||||
|
||||
public:
|
||||
Token(const Bytes& key, RNS::Type::Cryptography::Token::token_mode mode = RNS::Type::Cryptography::Token::MODE_AES);
|
||||
~Token();
|
||||
|
||||
public:
|
||||
bool verify_hmac(const Bytes& token);
|
||||
const Bytes encrypt(const Bytes& data);
|
||||
const Bytes decrypt(const Bytes& token);
|
||||
|
||||
private:
|
||||
RNS::Type::Cryptography::Token::token_mode _mode = RNS::Type::Cryptography::Token::MODE_AES_256_CBC;
|
||||
Bytes _signing_key;
|
||||
Bytes _encryption_key;
|
||||
};
|
||||
|
||||
} }
|
||||
3
lib/microReticulum/src/Cryptography/X25519.cpp
Executable file
3
lib/microReticulum/src/Cryptography/X25519.cpp
Executable file
@@ -0,0 +1,3 @@
|
||||
#include "X25519.h"
|
||||
|
||||
using namespace RNS::Cryptography;
|
||||
203
lib/microReticulum/src/Cryptography/X25519.h
Executable file
203
lib/microReticulum/src/Cryptography/X25519.h
Executable file
@@ -0,0 +1,203 @@
|
||||
#pragma once
|
||||
|
||||
#include "Bytes.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <Curve25519.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS { namespace Cryptography {
|
||||
|
||||
class X25519PublicKey {
|
||||
|
||||
public:
|
||||
using Ptr = std::shared_ptr<X25519PublicKey>;
|
||||
|
||||
public:
|
||||
/*
|
||||
X25519PublicKey(const Bytes& x) {
|
||||
_x = x;
|
||||
}
|
||||
*/
|
||||
X25519PublicKey(const Bytes& publicKey) {
|
||||
_publicKey = publicKey;
|
||||
}
|
||||
~X25519PublicKey() {}
|
||||
|
||||
public:
|
||||
// creates a new instance with specified seed
|
||||
/*
|
||||
static inline Ptr from_public_bytes(const Bytes& data) {
|
||||
return Ptr(new X25519PublicKey(_unpack_number(data)));
|
||||
}
|
||||
*/
|
||||
static inline Ptr from_public_bytes(const Bytes& publicKey) {
|
||||
return Ptr(new X25519PublicKey(publicKey));
|
||||
}
|
||||
|
||||
/*
|
||||
Bytes public_bytes() {
|
||||
return _pack_number(_x);
|
||||
}
|
||||
*/
|
||||
Bytes public_bytes() {
|
||||
return _publicKey;
|
||||
}
|
||||
|
||||
private:
|
||||
//Bytes _x;
|
||||
Bytes _publicKey;
|
||||
|
||||
};
|
||||
|
||||
class X25519PrivateKey {
|
||||
|
||||
public:
|
||||
const float MIN_EXEC_TIME = 2; // in milliseconds
|
||||
const float MAX_EXEC_TIME = 500; // in milliseconds
|
||||
const uint8_t DELAY_WINDOW = 10;
|
||||
|
||||
//z T_CLEAR = None
|
||||
const uint8_t T_MAX = 0;
|
||||
|
||||
using Ptr = std::shared_ptr<X25519PrivateKey>;
|
||||
|
||||
public:
|
||||
/*
|
||||
X25519PrivateKey(const Bytes& a) {
|
||||
_a = a;
|
||||
}
|
||||
*/
|
||||
X25519PrivateKey(const Bytes& privateKey) {
|
||||
if (privateKey) {
|
||||
// use specified private key
|
||||
_privateKey = privateKey;
|
||||
// similar to derive public key from private key
|
||||
// second param "f" is secret
|
||||
//eval(uint8_t result[32], const uint8_t s[32], const uint8_t x[32])
|
||||
// derive public key from private key
|
||||
Curve25519::eval(_publicKey.writable(32), _privateKey.data(), 0);
|
||||
}
|
||||
else {
|
||||
// create random private key and derive public key
|
||||
// second param "f" is secret
|
||||
//dh1(uint8_t k[32], uint8_t f[32])
|
||||
Curve25519::dh1(_publicKey.writable(32), _privateKey.writable(32));
|
||||
}
|
||||
}
|
||||
~X25519PrivateKey() {}
|
||||
|
||||
public:
|
||||
// creates a new instance with a random seed
|
||||
/*
|
||||
static inline Ptr generate() {
|
||||
return from_private_bytes(os.urandom(32));
|
||||
}
|
||||
*/
|
||||
static inline Ptr generate() {
|
||||
return from_private_bytes({Bytes::NONE});
|
||||
}
|
||||
|
||||
// creates a new instance with specified seed
|
||||
/*
|
||||
static inline Ptr from_private_bytes(const Bytes& data) {
|
||||
return Ptr(new X25519PrivateKey(_fix_secret(_unpack_number(data))));
|
||||
}
|
||||
*/
|
||||
static inline Ptr from_private_bytes(const Bytes& privateKey) {
|
||||
return Ptr(new X25519PrivateKey(privateKey));
|
||||
}
|
||||
|
||||
/*
|
||||
inline const Bytes private_bytes() {
|
||||
return _pack_number(_a);
|
||||
}
|
||||
*/
|
||||
inline const Bytes& private_bytes() {
|
||||
return _privateKey;
|
||||
}
|
||||
|
||||
// creates a new instance of public key for this private key
|
||||
/*
|
||||
inline X25519PublicKey::Ptr public_key() {
|
||||
return X25519PublicKey::from_public_bytes(_pack_number(_raw_curve25519(9, _a)));
|
||||
}
|
||||
*/
|
||||
inline X25519PublicKey::Ptr public_key() {
|
||||
return X25519PublicKey::from_public_bytes(_publicKey);
|
||||
}
|
||||
|
||||
/*
|
||||
inline const Bytes exchange(const Bytes& peer_public_key) {
|
||||
if isinstance(peer_public_key, bytes):
|
||||
peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key)
|
||||
|
||||
start = OS::time()
|
||||
|
||||
shared = _pack_number(_raw_curve25519(peer_public_key.x, _a))
|
||||
|
||||
end = OS::time()
|
||||
duration = end-start
|
||||
|
||||
if X25519PrivateKey.T_CLEAR == None:
|
||||
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
|
||||
|
||||
if end > X25519PrivateKey.T_CLEAR:
|
||||
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
|
||||
X25519PrivateKey.T_MAX = 0
|
||||
|
||||
if duration < X25519PrivateKey.T_MAX or duration < X25519PrivateKey.MIN_EXEC_TIME:
|
||||
target = start+X25519PrivateKey.T_MAX
|
||||
|
||||
if target > start+X25519PrivateKey.MAX_EXEC_TIME:
|
||||
target = start+X25519PrivateKey.MAX_EXEC_TIME
|
||||
|
||||
if target < start+X25519PrivateKey.MIN_EXEC_TIME:
|
||||
target = start+X25519PrivateKey.MIN_EXEC_TIME
|
||||
|
||||
try:
|
||||
OS::sleep(target-OS::time())
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
elif duration > X25519PrivateKey.T_MAX:
|
||||
X25519PrivateKey.T_MAX = duration
|
||||
|
||||
return shared
|
||||
}
|
||||
*/
|
||||
inline const Bytes exchange(const Bytes& peer_public_key) {
|
||||
DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
|
||||
Bytes sharedKey;
|
||||
if (!Curve25519::eval(sharedKey.writable(32), _privateKey.data(), peer_public_key.data())) {
|
||||
throw std::runtime_error("Peer key is invalid");
|
||||
}
|
||||
DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
|
||||
return sharedKey;
|
||||
}
|
||||
|
||||
inline bool verify(const Bytes& peer_public_key) {
|
||||
DEBUG("X25519PublicKey::exchange: public key: " + _publicKey.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex());
|
||||
Bytes sharedKey(peer_public_key);
|
||||
bool success = Curve25519::dh2(sharedKey.writable(32), _privateKey.writable(32));
|
||||
DEBUG("X25519PublicKey::exchange: shared key: " + sharedKey.toHex());
|
||||
DEBUG("X25519PublicKey::exchange: post private key: " + _privateKey.toHex());
|
||||
return success;
|
||||
}
|
||||
|
||||
private:
|
||||
//Bytes _a;
|
||||
Bytes _privateKey;
|
||||
Bytes _publicKey;
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
486
lib/microReticulum/src/Destination.cpp
Executable file
486
lib/microReticulum/src/Destination.cpp
Executable file
@@ -0,0 +1,486 @@
|
||||
#include "Destination.h"
|
||||
|
||||
#include "Transport.h"
|
||||
#include "Interface.h"
|
||||
#include "Packet.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Destination;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
Destination::Destination(const Identity& identity, const directions direction, const types type, const char* app_name, const char* aspects) : _object(new Object(identity)) {
|
||||
assert(_object);
|
||||
MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
|
||||
// Check input values and build name string
|
||||
if (strchr(app_name, '.') != nullptr) {
|
||||
throw std::invalid_argument("Dots can't be used in app names");
|
||||
}
|
||||
//TRACE("Destination::Destination: app name: " + std::string(app_name));
|
||||
|
||||
_object->_type = type;
|
||||
_object->_direction = direction;
|
||||
|
||||
std::string fullaspects(aspects);
|
||||
if (!identity && direction == IN && _object->_type != PLAIN) {
|
||||
TRACE("Destination::Destination: identity not provided, creating new one");
|
||||
_object->_identity = Identity();
|
||||
// CBA TODO determine why identity.hexhash is added both here and by expand_name called below
|
||||
fullaspects += "." + _object->_identity.hexhash();
|
||||
}
|
||||
//TRACE("Destination::Destination: full aspects: " + fullaspects);
|
||||
|
||||
if (_object->_identity && _object->_type == PLAIN) {
|
||||
throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
|
||||
}
|
||||
|
||||
_object->_name = expand_name(_object->_identity, app_name, fullaspects.c_str());
|
||||
//TRACE("Destination::Destination: name: " + _object->_name);
|
||||
|
||||
// Generate the destination address hash
|
||||
//TRACE("Destination::Destination: creating hash...");
|
||||
_object->_hash = hash(_object->_identity, app_name, fullaspects.c_str());
|
||||
_object->_hexhash = _object->_hash.toHex();
|
||||
TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
|
||||
//TRACE("Destination::Destination: creating name hash...");
|
||||
//p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
|
||||
_object->_name_hash = name_hash(app_name, aspects);
|
||||
//TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
|
||||
|
||||
//TRACE("Destination::Destination: calling register_destination");
|
||||
Transport::register_destination(*this);
|
||||
|
||||
MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
Destination::Destination(const Identity& identity, const Type::Destination::directions direction, const Type::Destination::types type, const Bytes& hash) : _object(new Object(identity)) {
|
||||
assert(_object);
|
||||
MEM("Destination object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
|
||||
_object->_type = type;
|
||||
_object->_direction = direction;
|
||||
|
||||
if (_object->_identity && _object->_type == PLAIN) {
|
||||
throw std::invalid_argument("Selected destination type PLAIN cannot hold an identity");
|
||||
}
|
||||
|
||||
_object->_hash = hash;
|
||||
_object->_hexhash = _object->_hash.toHex();
|
||||
TRACE("Destination::Destination: hash: " + _object->_hash.toHex());
|
||||
//TRACE("Destination::Destination: creating name hash...");
|
||||
//p self.name_hash = RNS.Identity.full_hash(self.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
|
||||
_object->_name_hash = name_hash("unknown", "unknown");
|
||||
//TRACE("Destination::Destination: name hash: " + _object->_name_hash.toHex());
|
||||
|
||||
//TRACE("Destination::Destination: calling register_destination");
|
||||
Transport::register_destination(*this);
|
||||
|
||||
MEM("Destination object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
/*virtual*/ Destination::~Destination() {
|
||||
MEM("Destination object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
if (_object && _object.use_count() == 1) {
|
||||
MEM("Destination object has last data reference");
|
||||
|
||||
// CBA Can't call deregister_destination here because it's possible (likely even) that Destination
|
||||
// is being destructed from that same collection which will result in a llop and memory errors.
|
||||
//TRACE("Destination::~Destination: calling deregister_destination");
|
||||
//Transport::deregister_destination(*this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: A destination name in adressable hash form, for an app_name and a number of aspects.
|
||||
*/
|
||||
/*static*/ Bytes Destination::hash(const Identity& identity, const char* app_name, const char* aspects) {
|
||||
//p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
|
||||
//p addr_hash_material = name_hash
|
||||
Bytes addr_hash_material = name_hash(app_name, aspects);
|
||||
if (identity) {
|
||||
addr_hash_material << identity.hash();
|
||||
}
|
||||
|
||||
//p return RNS.Identity.full_hash(addr_hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
// CBA TODO valid alternative?
|
||||
//return Identity::full_hash(addr_hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
|
||||
return Identity::truncated_hash(addr_hash_material);
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: A name in hash form, for an app_name and a number of aspects.
|
||||
*/
|
||||
/*static*/ Bytes Destination::name_hash(const char* app_name, const char* aspects) {
|
||||
//p name_hash = Identity::full_hash(Destination.expand_name(None, app_name, *aspects).encode("utf-8"))[:(RNS.Identity.NAME_HASH_LENGTH//8)]
|
||||
return Identity::full_hash(expand_name({Type::NONE}, app_name, aspects)).left(Type::Identity::NAME_HASH_LENGTH/8);
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: A tuple containing the app name and a list of aspects, for a full-name string.
|
||||
*/
|
||||
/*static*/ std::vector<std::string> Destination::app_and_aspects_from_name(const char* full_name) {
|
||||
//p components = full_name.split(".")
|
||||
//p return (components[0], components[1:])
|
||||
std::vector<std::string> components;
|
||||
std::string name(full_name);
|
||||
std::size_t pos = name.find('.');
|
||||
components.push_back(name.substr(0, pos));
|
||||
if (pos != std::string::npos) {
|
||||
components.push_back(name.substr(pos+1));
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: A destination name in adressable hash form, for a full name string and Identity instance.
|
||||
*/
|
||||
/*static*/ Bytes Destination::hash_from_name_and_identity(const char* full_name, const Identity& identity) {
|
||||
//p app_name, aspects = Destination.app_and_aspects_from_name(full_name)
|
||||
//p return Destination.hash(identity, app_name, *aspects)
|
||||
std::vector<std::string> components = app_and_aspects_from_name(full_name);
|
||||
if (components.size() == 0) {
|
||||
return {Bytes::NONE};
|
||||
}
|
||||
if (components.size() == 1) {
|
||||
return hash(identity, components[0].c_str(), "");
|
||||
}
|
||||
return hash(identity, components[0].c_str(), components[1].c_str());
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: A string containing the full human-readable name of the destination, for an app_name and a number of aspects.
|
||||
*/
|
||||
/*static*/ std::string Destination::expand_name(const Identity& identity, const char* app_name, const char* aspects) {
|
||||
|
||||
if (strchr(app_name, '.') != nullptr) {
|
||||
throw std::invalid_argument("Dots can't be used in app names");
|
||||
}
|
||||
|
||||
std::string name(app_name);
|
||||
|
||||
if (aspects != nullptr) {
|
||||
name += std::string(".") + aspects;
|
||||
}
|
||||
|
||||
if (identity) {
|
||||
name += "." + identity.hexhash();
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
Creates an announce packet for this destination and broadcasts it on all
|
||||
relevant interfaces. Application specific data can be added to the announce.
|
||||
|
||||
:param app_data: *bytes* containing the app_data.
|
||||
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
*/
|
||||
//Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/, const Interface& attached_interface /*= {Type::NONE}*/, const Bytes& tag /*= {}*/, bool send /*= true*/) {
|
||||
Packet Destination::announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag /*= {}*/, bool send /*= true*/) {
|
||||
assert(_object);
|
||||
TRACE("Destination::announce: announcing destination...");
|
||||
|
||||
if (_object->_type != SINGLE) {
|
||||
throw std::invalid_argument("Only SINGLE destination types can be announced");
|
||||
}
|
||||
|
||||
if (_object->_direction != IN) {
|
||||
throw std::invalid_argument("Only IN destination types can be announced");
|
||||
}
|
||||
|
||||
double now = OS::time();
|
||||
auto it = _object->_path_responses.begin();
|
||||
while (it != _object->_path_responses.end()) {
|
||||
// vector
|
||||
//Response& entry = *it;
|
||||
// map
|
||||
PathResponse& entry = (*it).second;
|
||||
if (now > (entry.first + PR_TAG_WINDOW)) {
|
||||
it = _object->_path_responses.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
Bytes announce_data;
|
||||
|
||||
/*
|
||||
// CBA TEST
|
||||
TRACE("Destination::announce: performing path test...");
|
||||
TRACE("Destination::announce: inserting path...");
|
||||
_object->_path_responses.insert({Bytes("foo_tag"), {0, Bytes("this is foo tag")}});
|
||||
TRACE("Destination::announce: inserting path...");
|
||||
_object->_path_responses.insert({Bytes("test_tag"), {0, Bytes("this is test tag")}});
|
||||
if (path_response) {
|
||||
TRACE("Destination::announce: path_response is true");
|
||||
}
|
||||
if (!tag.empty()) {
|
||||
TRACE("Destination::announce: tag is specified");
|
||||
std::string tagstr((const char*)tag.data(), tag.size());
|
||||
DEBUG(std::string("Destination::announce: tag: ") + tagstr);
|
||||
DEBUG(std::string("Destination::announce: tag len: ") + std::to_string(tag.size()));
|
||||
TRACE("Destination::announce: searching for tag...");
|
||||
if (_object->_path_responses.find(tag) != _object->_path_responses.end()) {
|
||||
TRACE("Destination::announce: found tag in _path_responses");
|
||||
DEBUG(std::string("Destination::announce: data: ") +_object->_path_responses[tag].second.toString());
|
||||
}
|
||||
else {
|
||||
TRACE("Destination::announce: tag not found in _path_responses");
|
||||
}
|
||||
}
|
||||
TRACE("Destination::announce: path test finished");
|
||||
*/
|
||||
|
||||
if (path_response && !tag.empty() && _object->_path_responses.find(tag) != _object->_path_responses.end()) {
|
||||
// This code is currently not used, since Transport will block duplicate
|
||||
// path requests based on tags. When multi-path support is implemented in
|
||||
// Transport, this will allow Transport to detect redundant paths to the
|
||||
// same destination, and select the best one based on chosen criteria,
|
||||
// since it will be able to detect that a single emitted announce was
|
||||
// received via multiple paths. The difference in reception time will
|
||||
// potentially also be useful in determining characteristics of the
|
||||
// multiple available paths, and to choose the best one.
|
||||
//z TRACE("Using cached announce data for answering path request with tag "+RNS.prettyhexrep(tag));
|
||||
announce_data << _object->_path_responses[tag].second;
|
||||
}
|
||||
else {
|
||||
Bytes destination_hash = _object->_hash;
|
||||
//p random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big")
|
||||
// CBA TODO add in time to random hash
|
||||
Bytes random_hash = Cryptography::random(Type::Identity::RANDOM_HASH_LENGTH/8);
|
||||
|
||||
Bytes new_app_data(app_data);
|
||||
if (new_app_data.empty() && !_object->_default_app_data.empty()) {
|
||||
new_app_data = _object->_default_app_data;
|
||||
}
|
||||
|
||||
Bytes signed_data;
|
||||
//TRACE("Destination::announce: hash: " + _object->_hash.toHex());
|
||||
//TRACE("Destination::announce: public key: " + _object->_identity.get_public_key().toHex());
|
||||
//TRACE("Destination::announce: name hash: " + _object->_name_hash.toHex());
|
||||
//TRACE("Destination::announce: random hash: " + random_hash.toHex());
|
||||
//TRACE("Destination::announce: app data: " + new_app_data.toHex());
|
||||
//TRACE("Destination::announce: app data text:" + new_app_data.toString());
|
||||
signed_data << _object->_hash << _object->_identity.get_public_key() << _object->_name_hash << random_hash;
|
||||
if (new_app_data) {
|
||||
signed_data << new_app_data;
|
||||
}
|
||||
//TRACE("Destination::announce: signed data: " + signed_data.toHex());
|
||||
|
||||
Bytes signature(_object->_identity.sign(signed_data));
|
||||
//TRACE("Destination::announce: signature: " + signature.toHex());
|
||||
|
||||
announce_data << _object->_identity.get_public_key() << _object->_name_hash << random_hash << signature;
|
||||
|
||||
if (new_app_data) {
|
||||
announce_data << new_app_data;
|
||||
}
|
||||
|
||||
// CBA ACCUMULATES
|
||||
_object->_path_responses.insert({tag, {OS::time(), announce_data}});
|
||||
}
|
||||
//TRACE("Destination::announce: announce_data:" + announce_data.toHex());
|
||||
|
||||
Type::Packet::context_types announce_context = Type::Packet::CONTEXT_NONE;
|
||||
if (path_response) {
|
||||
announce_context = Type::Packet::PATH_RESPONSE;
|
||||
}
|
||||
|
||||
//TRACE("Destination::announce: creating announce packet...");
|
||||
//p announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
|
||||
//Packet announce_packet(*this, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, nullptr, attached_interface);
|
||||
Packet announce_packet(*this, attached_interface, announce_data, Type::Packet::ANNOUNCE, announce_context, Type::Transport::BROADCAST, Type::Packet::HEADER_1);
|
||||
|
||||
if (send) {
|
||||
TRACE("Destination::announce: sending announce packet...");
|
||||
announce_packet.send();
|
||||
return {Type::NONE};
|
||||
}
|
||||
else {
|
||||
return announce_packet;
|
||||
}
|
||||
}
|
||||
|
||||
Packet Destination::announce(const Bytes& app_data /*= {}*/, bool path_response /*= false*/) {
|
||||
return announce(app_data, path_response, {Type::NONE});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Registers a request handler.
|
||||
|
||||
:param path: The path for the request handler to be registered.
|
||||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
|
||||
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
|
||||
:raises: ``ValueError`` if any of the supplied arguments are invalid.
|
||||
*/
|
||||
/*
|
||||
void Destination::register_request_handler(const Bytes& path, response_generator = None, request_policies allow = ALLOW_NONE, allowed_list = None) {
|
||||
if path == None or path == "":
|
||||
raise ValueError("Invalid path specified")
|
||||
elif not callable(response_generator):
|
||||
raise ValueError("Invalid response generator specified")
|
||||
elif not allow in Destination.request_policies:
|
||||
raise ValueError("Invalid request policy")
|
||||
else:
|
||||
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
|
||||
request_handler = [path, response_generator, allow, allowed_list]
|
||||
self.request_handlers[path_hash] = request_handler
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
Deregisters a request handler.
|
||||
|
||||
:param path: The path for the request handler to be deregistered.
|
||||
:returns: True if the handler was deregistered, otherwise False.
|
||||
*/
|
||||
/*
|
||||
bool Destination::deregister_request_handler(const Bytes& path) {
|
||||
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
|
||||
if path_hash in self.request_handlers:
|
||||
self.request_handlers.pop(path_hash)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
}
|
||||
*/
|
||||
|
||||
void Destination::receive(const Packet& packet) {
|
||||
assert(_object);
|
||||
if (packet.packet_type() == Type::Packet::LINKREQUEST) {
|
||||
Bytes plaintext(packet.data());
|
||||
incoming_link_request(plaintext, packet);
|
||||
}
|
||||
else {
|
||||
// CBA TODO Why isn't the Packet decrypting itself?
|
||||
Bytes plaintext(decrypt(packet.data()));
|
||||
//TRACE("Destination::receive: decrypted data: " + plaintext.toHex());
|
||||
if (plaintext) {
|
||||
if (packet.packet_type() == Type::Packet::DATA) {
|
||||
if (_object->_callbacks._packet) {
|
||||
try {
|
||||
_object->_callbacks._packet(plaintext, packet);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
DEBUG("Error while executing receive callback from " + toString() + ". The contained exception was: " + e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Destination::incoming_link_request(const Bytes& data, const Packet& packet) {
|
||||
assert(_object);
|
||||
if (_object->_accept_link_requests) {
|
||||
TRACE("***** Accepting link request");
|
||||
RNS::Link link = Link::validate_request(*this, data, packet);
|
||||
if (link) {
|
||||
_object->_links.insert(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||
|
||||
:param plaintext: A *bytes-like* containing the plaintext to be encrypted.
|
||||
:raises: ``ValueError`` if destination does not hold a necessary key for encryption.
|
||||
*/
|
||||
/*virtual*/ const Bytes Destination::encrypt(const Bytes& data) {
|
||||
assert(_object);
|
||||
TRACE("Destination::encrypt: encrypting data...");
|
||||
|
||||
if (_object->_type == PLAIN) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (_object->_type == SINGLE && _object->_identity) {
|
||||
return _object->_identity.encrypt(data);
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*
|
||||
if (_object->_type == GROUP {
|
||||
if hasattr(self, "prv") and self.prv != None:
|
||||
try:
|
||||
return self.prv.encrypt(plaintext)
|
||||
except Exception as e:
|
||||
RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||
}
|
||||
*/
|
||||
|
||||
// CBA Reference implementation does not handle this default case
|
||||
// CBA TODO Determine of returning plaintext is appropriate here (for now it's necessary for PROOF packets)
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||
|
||||
:param ciphertext: *Bytes* containing the ciphertext to be decrypted.
|
||||
:raises: ``ValueError`` if destination does not hold a necessary key for decryption.
|
||||
*/
|
||||
/*virtual*/ const Bytes Destination::decrypt(const Bytes& data) {
|
||||
assert(_object);
|
||||
TRACE("Destination::decrypt: decrypting data...");
|
||||
|
||||
if (_object->_type == PLAIN) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (_object->_type == SINGLE && _object->_identity) {
|
||||
return _object->_identity.decrypt(data);
|
||||
}
|
||||
|
||||
/*
|
||||
if (_object->_type == GROUP) {
|
||||
if hasattr(self, "prv") and self.prv != None:
|
||||
try:
|
||||
return self.prv.decrypt(ciphertext)
|
||||
except Exception as e:
|
||||
RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||
}
|
||||
*/
|
||||
// MOCK
|
||||
return {Bytes::NONE};
|
||||
}
|
||||
|
||||
/*
|
||||
Signs information for ``RNS.Destination.SINGLE`` type destination.
|
||||
|
||||
:param message: *Bytes* containing the message to be signed.
|
||||
:returns: A *bytes-like* containing the message signature, or *None* if the destination could not sign the message.
|
||||
*/
|
||||
/*virtual*/ const Bytes Destination::sign(const Bytes& message) {
|
||||
assert(_object);
|
||||
if (_object->_type == SINGLE && _object->_identity) {
|
||||
return _object->_identity.sign(message);
|
||||
}
|
||||
return {Bytes::NONE};
|
||||
}
|
||||
|
||||
bool Destination::has_link(const Link& link) {
|
||||
assert(_object);
|
||||
return (_object->_links.count(link) > 0);
|
||||
}
|
||||
|
||||
void Destination::remove_link(const Link& link) {
|
||||
assert(_object);
|
||||
_object->_links.erase(link);
|
||||
}
|
||||
259
lib/microReticulum/src/Destination.h
Executable file
259
lib/microReticulum/src/Destination.h
Executable file
@@ -0,0 +1,259 @@
|
||||
#pragma once
|
||||
|
||||
//#include "Reticulum.h"
|
||||
//#include "Link.h"
|
||||
//#include "Interface.h"
|
||||
#include "Identity.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Interface;
|
||||
class Link;
|
||||
class Packet;
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
//p response_generator(path, data, request_id, link_id, remote_identity, requested_at)
|
||||
using response_generator = Bytes(*)(const Bytes& path, const Bytes& data, const Bytes& request_id, const Bytes& link_id, const Identity& remote_identity, double requested_at);
|
||||
public:
|
||||
RequestHandler(const RequestHandler& handler) {
|
||||
_path = handler._path;
|
||||
_response_generator = handler._response_generator;
|
||||
_allow = handler._allow;
|
||||
_allowed_list = handler._allowed_list;
|
||||
}
|
||||
Bytes _path;
|
||||
response_generator _response_generator = nullptr;
|
||||
Type::Destination::request_policies _allow = Type::Destination::ALLOW_NONE;
|
||||
std::set<Bytes> _allowed_list;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A class used to describe endpoints in a Reticulum Network. Destination
|
||||
* instances are used both to create outgoing and incoming endpoints. The
|
||||
* destination type will decide if encryption, and what type, is used in
|
||||
* communication with the endpoint. A destination can also announce its
|
||||
* presence on the network, which will also distribute necessary keys for
|
||||
* encrypted communication with it.
|
||||
*
|
||||
* @param identity An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
|
||||
* @param direction ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
|
||||
* @param type ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
|
||||
* @param app_name A string specifying the app name.
|
||||
* @param aspects Any non-zero number of string arguments.
|
||||
*/
|
||||
class Destination {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
using link_established = void(*)(Link& link);
|
||||
//using packet = void(*)(uint8_t* data, uint16_t data_len, Packet *packet);
|
||||
using packet = void(*)(const Bytes& data, const Packet& packet);
|
||||
using proof_requested = bool(*)(const Packet& packet);
|
||||
public:
|
||||
link_established _link_established = nullptr;
|
||||
packet _packet = nullptr;
|
||||
proof_requested _proof_requested = nullptr;
|
||||
friend class Destination;
|
||||
};
|
||||
|
||||
using PathResponse = std::pair<double, Bytes>;
|
||||
|
||||
public:
|
||||
Destination(Type::NoneConstructor none) {
|
||||
MEM("Destination NONE object created, this: " + std::to_string((uintptr_t)this));
|
||||
}
|
||||
Destination(const Destination& destination) : _object(destination._object) {
|
||||
MEM("Destination object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
Destination(
|
||||
const Identity& identity,
|
||||
const Type::Destination::directions direction,
|
||||
const Type::Destination::types type,
|
||||
const char* app_name,
|
||||
const char* aspects
|
||||
);
|
||||
Destination(
|
||||
const Identity& identity,
|
||||
const Type::Destination::directions direction,
|
||||
const Type::Destination::types type,
|
||||
const Bytes& hash
|
||||
);
|
||||
virtual ~Destination();
|
||||
|
||||
inline Destination& operator = (const Destination& destination) {
|
||||
_object = destination._object;
|
||||
MEM("Destination object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const Destination& destination) const {
|
||||
return _object.get() < destination._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
static std::string expand_name(const Identity& identity, const char* app_name, const char* aspects);
|
||||
static Bytes hash(const Identity& identity, const char* app_name, const char* aspects);
|
||||
static Bytes name_hash(const char* app_name, const char* aspects);
|
||||
static std::vector<std::string> app_and_aspects_from_name(const char* full_name);
|
||||
static Bytes hash_from_name_and_identity(const char* full_name, const Identity& identity);
|
||||
|
||||
public:
|
||||
//Packet announce(const Bytes& app_data = {}, bool path_response = false, const Interface& attached_interface = {Type::NONE}, const Bytes& tag = {}, bool send = true);
|
||||
Packet announce(const Bytes& app_data, bool path_response, const Interface& attached_interface, const Bytes& tag = {}, bool send = true);
|
||||
Packet announce(const Bytes& app_data = {}, bool path_response = false);
|
||||
|
||||
/*
|
||||
Set or query whether the destination accepts incoming link requests.
|
||||
|
||||
:param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests.
|
||||
:returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``.
|
||||
*/
|
||||
inline void accepts_links(bool accepts) { assert(_object); _object->_accept_link_requests = accepts; }
|
||||
inline bool accepts_links() { assert(_object); return _object->_accept_link_requests; }
|
||||
|
||||
/*
|
||||
Registers a function to be called when a link has been established to
|
||||
this destination.
|
||||
|
||||
:param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination.
|
||||
*/
|
||||
inline void set_link_established_callback(Callbacks::link_established callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._link_established = callback;
|
||||
}
|
||||
/*
|
||||
Registers a function to be called when a packet has been received by
|
||||
this destination.
|
||||
|
||||
:param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet.
|
||||
*/
|
||||
inline void set_packet_callback(Callbacks::packet callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._packet = callback;
|
||||
}
|
||||
/*
|
||||
Registers a function to be called when a proof has been requested for
|
||||
a packet sent to this destination. Allows control over when and if
|
||||
proofs should be returned for received packets.
|
||||
|
||||
:param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
|
||||
*/
|
||||
inline void set_proof_requested_callback(Callbacks::proof_requested callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._proof_requested = callback;
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the destinations proof strategy.
|
||||
|
||||
:param proof_strategy: One of ``RNS.Destination.PROVE_NONE``, ``RNS.Destination.PROVE_ALL`` or ``RNS.Destination.PROVE_APP``. If ``RNS.Destination.PROVE_APP`` is set, the `proof_requested_callback` will be called to determine whether a proof should be sent or not.
|
||||
*/
|
||||
inline void set_proof_strategy(Type::Destination::proof_strategies proof_strategy) {
|
||||
assert(_object);
|
||||
//if (proof_strategy <= PROOF_NONE) {
|
||||
// throw throw std::invalid_argument("Unsupported proof strategy");
|
||||
//}
|
||||
_object->_proof_strategy = proof_strategy;
|
||||
}
|
||||
|
||||
void receive(const Packet& packet);
|
||||
void incoming_link_request(const Bytes& data, const Packet& packet);
|
||||
|
||||
virtual const Bytes encrypt(const Bytes& data);
|
||||
virtual const Bytes decrypt(const Bytes& data);
|
||||
virtual const Bytes sign(const Bytes& message);
|
||||
|
||||
// CBA
|
||||
bool has_link(const Link& link);
|
||||
void remove_link(const Link& link);
|
||||
|
||||
inline std::string toString() const { if (!_object) return ""; return "{Destination:" + _object->_hash.toHex() + "}"; }
|
||||
|
||||
// getters
|
||||
inline Type::Destination::types type() const { assert(_object); return _object->_type; }
|
||||
inline Type::Destination::directions direction() const { assert(_object); return _object->_direction; }
|
||||
inline Type::Destination::proof_strategies proof_strategy() const { assert(_object); return _object->_proof_strategy; }
|
||||
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
|
||||
inline uint16_t mtu() const { assert(_object); return _object->_mtu; }
|
||||
// CBA LINK
|
||||
//inline const Bytes& link_id() const { assert(_object); return _object->_link_id; }
|
||||
//inline Type::Link::status status() const { assert(_object); return _object->_status; }
|
||||
inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
|
||||
inline const Identity& identity() const { assert(_object); return _object->_identity; }
|
||||
inline const std::map<Bytes, PathResponse>& path_responses() const { assert(_object); return _object->_path_responses; }
|
||||
inline const std::map<Bytes, RequestHandler>& request_handlers() const { assert(_object); return _object->_request_handlers; }
|
||||
|
||||
// setters
|
||||
// CBA Don't allow changing destination hash after construction since it's used as key in collections
|
||||
//inline void hash(const Bytes& hash) { assert(_object); _object->_hash = hash; _object->_hexhash = _object->_hash.toHex(); }
|
||||
inline void type(Type::Destination::types type) { assert(_object); _object->_type = type; }
|
||||
inline void mtu(uint16_t mtu) { assert(_object); _object->_mtu = mtu; }
|
||||
// CBA LINK
|
||||
//inline void link_id(const Bytes& id) { assert(_object); _object->_link_id = id; }
|
||||
//inline void last_outbound(double time) { assert(_object); _object->_last_outbound = time; }
|
||||
//inline void increment_tx() { assert(_object); ++_object->_tx; }
|
||||
//inline void increment_txbytes(uint16_t bytes) { assert(_object); _object->_txbytes += bytes; }
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object(const Identity& identity) : _identity(identity) { MEM("Destination::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
virtual ~Object() { MEM("Destination::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
|
||||
private:
|
||||
bool _accept_link_requests = true;
|
||||
Callbacks _callbacks;
|
||||
std::map<Bytes, RequestHandler> _request_handlers;
|
||||
Type::Destination::types _type;
|
||||
Type::Destination::directions _direction;
|
||||
Type::Destination::proof_strategies _proof_strategy = Type::Destination::PROVE_NONE;
|
||||
uint16_t _mtu = 0;
|
||||
|
||||
std::map<Bytes, PathResponse> _path_responses;
|
||||
std::set<Link> _links;
|
||||
|
||||
Identity _identity;
|
||||
std::string _name;
|
||||
|
||||
// Generate the destination address hash
|
||||
Bytes _hash;
|
||||
Bytes _name_hash;
|
||||
std::string _hexhash;
|
||||
|
||||
// CBA TODO when is _default_app_data a "callable"?
|
||||
Bytes _default_app_data;
|
||||
//z _callback = None
|
||||
//z _proofcallback = None
|
||||
|
||||
// CBA LINK
|
||||
// CBA _link_id is expected by Packet but only present in Link
|
||||
// CBA TODO determine if Link needs to inherit from Destination or vice-versa
|
||||
//Bytes _link_id;
|
||||
|
||||
//Type::Link::status _status;
|
||||
|
||||
//double _last_outbound = 0.0;
|
||||
//uint16_t _tx = 0;
|
||||
//uint32_t _txbytes = 0;
|
||||
|
||||
friend class Destination;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
144
lib/microReticulum/src/FileStream.h
Executable file
144
lib/microReticulum/src/FileStream.h
Executable file
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/Crc.h"
|
||||
#include "Log.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Stream.h>
|
||||
#else
|
||||
#include "Utilities/Stream.h"
|
||||
#endif
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class FileStreamImpl {
|
||||
|
||||
protected:
|
||||
FileStreamImpl() { MEMF("FileStreamImpl object created, this: 0x%X", this); }
|
||||
public:
|
||||
virtual ~FileStreamImpl() { MEMF("FileStreamImpl object destroyed, this: 0x%X", this); }
|
||||
|
||||
protected:
|
||||
// CBA TODO Can these be pure-virtual???
|
||||
virtual const char* name() = 0;
|
||||
virtual size_t size() = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
// Print overrides
|
||||
virtual size_t write(uint8_t byte) = 0;
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) = 0;
|
||||
|
||||
// Stream overrides
|
||||
virtual int available() = 0;
|
||||
virtual int read() = 0;
|
||||
virtual int peek() = 0;
|
||||
virtual void flush() = 0;
|
||||
|
||||
friend class FileStream;
|
||||
};
|
||||
|
||||
class FileStream : public Stream {
|
||||
|
||||
public:
|
||||
enum MODE {
|
||||
MODE_READ,
|
||||
MODE_WRITE,
|
||||
MODE_APPEND,
|
||||
};
|
||||
|
||||
public:
|
||||
FileStream(Type::NoneConstructor none) {
|
||||
MEMF("FileStream NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
FileStream(const FileStream& obj) : _impl(obj._impl), _crc(obj._crc) {
|
||||
MEMF("FileStream object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
FileStream(FileStreamImpl* impl) : _impl(impl) {
|
||||
MEMF("FileStream object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
virtual ~FileStream() {
|
||||
MEMF("FileStream object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
|
||||
inline virtual FileStream& operator = (const FileStream& obj) {
|
||||
_impl = obj._impl;
|
||||
_crc = obj._crc;
|
||||
MEMF("FileStream object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline FileStream& operator = (FileStreamImpl* impl) {
|
||||
_impl.reset(impl);
|
||||
MEMF("FileStream object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
MEMF("FileStream object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const FileStream& obj) const {
|
||||
MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() < obj._impl.get();
|
||||
}
|
||||
inline bool operator > (const FileStream& obj) const {
|
||||
MEMF("FileStream object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() > obj._impl.get();
|
||||
}
|
||||
inline bool operator == (const FileStream& obj) const {
|
||||
MEMF("FileStream object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() == obj._impl.get();
|
||||
}
|
||||
inline bool operator != (const FileStream& obj) const {
|
||||
MEMF("FileStream object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != obj._impl.get();
|
||||
}
|
||||
inline FileStreamImpl* get() {
|
||||
return _impl.get();
|
||||
}
|
||||
inline void clear() {
|
||||
_impl.reset();
|
||||
}
|
||||
|
||||
public:
|
||||
inline uint32_t crc() { return _crc; }
|
||||
inline size_t write(const char* str) { return write((const uint8_t*)str, strlen(str)); }
|
||||
|
||||
// File overrides
|
||||
inline const char* name() { assert(_impl); return _impl->name(); }
|
||||
inline size_t size() { assert(_impl); return _impl->size(); }
|
||||
inline void close() { assert(_impl); _impl->close(); }
|
||||
|
||||
// Print overrides
|
||||
inline size_t write(uint8_t byte) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, byte); return _impl->write(byte); }
|
||||
inline size_t write(const uint8_t *buffer, size_t size) { assert(_impl); _crc = Utilities::Crc::crc32(_crc, buffer, size); return _impl->write(buffer, size); }
|
||||
|
||||
// Stream overrides
|
||||
inline int available() { assert(_impl); return _impl->available(); }
|
||||
inline int read() { assert(_impl); if (_impl->available() <= 0) return EOF; int ch = _impl->read(); uint8_t byte = (uint8_t)ch; _crc = Utilities::Crc::crc32(_crc, byte); return ch; }
|
||||
inline int peek() { assert(_impl); return _impl->peek(); }
|
||||
inline void flush() { assert(_impl); _impl->flush(); }
|
||||
|
||||
// getters/setters
|
||||
protected:
|
||||
public:
|
||||
|
||||
#ifndef NDEBUG
|
||||
inline std::string debugString() const {
|
||||
std::string dump;
|
||||
dump = "FileStream object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::shared_ptr<FileStreamImpl> _impl;
|
||||
uint32_t _crc = 0;
|
||||
};
|
||||
|
||||
}
|
||||
129
lib/microReticulum/src/FileSystem.h
Executable file
129
lib/microReticulum/src/FileSystem.h
Executable file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include "FileStream.h"
|
||||
#include "Log.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class FileSystemImpl {
|
||||
|
||||
protected:
|
||||
FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object created, this: 0x%X", this); }
|
||||
public:
|
||||
virtual ~FileSystemImpl() { MEMF("FileSystem::FileSystemImpl object destroyed, this: 0x%X", this); }
|
||||
|
||||
protected:
|
||||
virtual bool init() { return true; }
|
||||
virtual void loop() {}
|
||||
virtual bool file_exists(const char* file_path) = 0;
|
||||
virtual size_t read_file(const char* file_path, Bytes& data) = 0;
|
||||
virtual size_t write_file(const char* file_path, const Bytes& data) = 0;
|
||||
virtual FileStream open_file(const char* file_path, FileStream::MODE file_mode) = 0;
|
||||
virtual bool remove_file(const char* file_path) = 0;
|
||||
virtual bool rename_file(const char* from_file_path, const char* to_file_path) = 0;
|
||||
virtual bool directory_exists(const char* directory_path) = 0;
|
||||
virtual bool create_directory(const char* directory_path) = 0;
|
||||
virtual bool remove_directory(const char* directory_path) = 0;
|
||||
virtual std::list<std::string> list_directory(const char* directory_path) = 0;
|
||||
virtual size_t storage_size() = 0;
|
||||
virtual size_t storage_available() = 0;
|
||||
|
||||
friend class FileSystem;
|
||||
};
|
||||
|
||||
class FileSystem {
|
||||
|
||||
public:
|
||||
FileSystem(Type::NoneConstructor none) {
|
||||
MEMF("FileSystem NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
FileSystem(const FileSystem& obj) : _impl(obj._impl) {
|
||||
MEMF("FileSystem object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
FileSystem(FileSystemImpl* impl) : _impl(impl) {
|
||||
MEMF("FileSystem object impl created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
virtual ~FileSystem() {
|
||||
MEMF("FileSystem object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
|
||||
inline FileSystem& operator = (const FileSystem& obj) {
|
||||
_impl = obj._impl;
|
||||
MEMF("FileSystem object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline FileSystem& operator = (FileSystemImpl* impl) {
|
||||
_impl.reset(impl);
|
||||
MEMF("FileSystem object copy created by impl assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
MEMF("FileSystem object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const FileSystem& obj) const {
|
||||
MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() < obj._impl.get();
|
||||
}
|
||||
inline bool operator > (const FileSystem& obj) const {
|
||||
MEMF("FileSystem object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() > obj._impl.get();
|
||||
}
|
||||
inline bool operator == (const FileSystem& obj) const {
|
||||
MEMF("FileSystem object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() == obj._impl.get();
|
||||
}
|
||||
inline bool operator != (const FileSystem& obj) const {
|
||||
MEMF("FileSystem object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != obj._impl.get();
|
||||
}
|
||||
inline FileSystemImpl* get() {
|
||||
return _impl.get();
|
||||
}
|
||||
inline void clear() {
|
||||
_impl.reset();
|
||||
}
|
||||
|
||||
public:
|
||||
inline bool init() { assert(_impl); return _impl->init(); }
|
||||
inline void loop() { assert(_impl); return _impl->loop(); }
|
||||
inline bool file_exists(const char* file_path) { assert(_impl); return _impl->file_exists(file_path); }
|
||||
inline size_t read_file(const char* file_path, Bytes& data) { assert(_impl); return _impl->read_file(file_path, data); }
|
||||
inline size_t write_file(const char* file_path, const Bytes& data) { assert(_impl); return _impl->write_file(file_path, data); }
|
||||
inline FileStream open_file(const char* file_path, FileStream::MODE file_mode) { return _impl->open_file(file_path, file_mode); }
|
||||
inline bool remove_file(const char* file_path) { assert(_impl); return _impl->remove_file(file_path); }
|
||||
inline bool rename_file(const char* from_file_path, const char* to_file_path) { assert(_impl); return _impl->rename_file(from_file_path, to_file_path); }
|
||||
inline bool directory_exists(const char* directory_path) { assert(_impl); return _impl->directory_exists(directory_path); }
|
||||
inline bool create_directory(const char* directory_path) { assert(_impl); return _impl->create_directory(directory_path); }
|
||||
inline bool remove_directory(const char* directory_path) { assert(_impl); return _impl->remove_directory(directory_path); }
|
||||
inline std::list<std::string> list_directory(const char* directory_path) { assert(_impl); return _impl->list_directory(directory_path); }
|
||||
inline size_t storage_size() { assert(_impl); return _impl->storage_size(); }
|
||||
inline size_t storage_available() { assert(_impl); return _impl->storage_available(); }
|
||||
|
||||
private:
|
||||
std::list<std::string> _empty;
|
||||
|
||||
// getters/setters
|
||||
protected:
|
||||
public:
|
||||
|
||||
#ifndef NDEBUG
|
||||
inline std::string debugString() const {
|
||||
std::string dump;
|
||||
dump = "FileSystem object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::shared_ptr<FileSystemImpl> _impl;
|
||||
};
|
||||
|
||||
}
|
||||
661
lib/microReticulum/src/Identity.cpp
Executable file
661
lib/microReticulum/src/Identity.cpp
Executable file
@@ -0,0 +1,661 @@
|
||||
#include "Identity.h"
|
||||
|
||||
#include "Reticulum.h"
|
||||
#include "Transport.h"
|
||||
#include "Packet.h"
|
||||
#include "Log.h"
|
||||
#include "Utilities/OS.h"
|
||||
#include "Cryptography/Ed25519.h"
|
||||
#include "Cryptography/X25519.h"
|
||||
#include "Cryptography/HKDF.h"
|
||||
#include "Cryptography/Token.h"
|
||||
#include "Cryptography/Random.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Identity;
|
||||
using namespace RNS::Cryptography;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
/*static*/ std::map<Bytes, Identity::IdentityEntry> Identity::_known_destinations;
|
||||
/*static*/ bool Identity::_saving_known_destinations = false;
|
||||
// CBA
|
||||
// CBA ACCUMULATES
|
||||
/*static*/ //uint16_t Identity::_known_destinations_maxsize = 100;
|
||||
/*static*/ uint16_t Identity::_known_destinations_maxsize = 100;
|
||||
|
||||
Identity::Identity(bool create_keys /*= true*/) : _object(new Object()) {
|
||||
if (create_keys) {
|
||||
createKeys();
|
||||
}
|
||||
MEM("Identity object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
void Identity::createKeys() {
|
||||
assert(_object);
|
||||
|
||||
// CRYPTO: create encryption private keys
|
||||
_object->_prv = Cryptography::X25519PrivateKey::generate();
|
||||
_object->_prv_bytes = _object->_prv->private_bytes();
|
||||
//TRACE("Identity::createKeys: prv bytes: " + _object->_prv_bytes.toHex());
|
||||
|
||||
// CRYPTO: create signature private keys
|
||||
_object->_sig_prv = Cryptography::Ed25519PrivateKey::generate();
|
||||
_object->_sig_prv_bytes = _object->_sig_prv->private_bytes();
|
||||
//TRACE("Identity::createKeys: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
|
||||
|
||||
// CRYPTO: create encryption public keys
|
||||
_object->_pub = _object->_prv->public_key();
|
||||
_object->_pub_bytes = _object->_pub->public_bytes();
|
||||
//TRACE("Identity::createKeys: pub bytes: " + _object->_pub_bytes.toHex());
|
||||
|
||||
// CRYPTO: create signature public keys
|
||||
_object->_sig_pub = _object->_sig_prv->public_key();
|
||||
_object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
|
||||
//TRACE("Identity::createKeys: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
|
||||
|
||||
update_hashes();
|
||||
|
||||
VERBOSE("Identity keys created for " + _object->_hash.toHex());
|
||||
}
|
||||
|
||||
/*
|
||||
Load a private key into the instance.
|
||||
|
||||
:param prv_bytes: The private key as *bytes*.
|
||||
:returns: True if the key was loaded, otherwise False.
|
||||
*/
|
||||
bool Identity::load_private_key(const Bytes& prv_bytes) {
|
||||
assert(_object);
|
||||
|
||||
try {
|
||||
|
||||
//p self.prv_bytes = prv_bytes[:Identity.KEYSIZE//8//2]
|
||||
_object->_prv_bytes = prv_bytes.left(Type::Identity::KEYSIZE/8/2);
|
||||
_object->_prv = X25519PrivateKey::from_private_bytes(_object->_prv_bytes);
|
||||
//TRACE("Identity::load_private_key: prv bytes: " + _object->_prv_bytes.toHex());
|
||||
|
||||
//p self.sig_prv_bytes = prv_bytes[Identity.KEYSIZE//8//2:]
|
||||
_object->_sig_prv_bytes = prv_bytes.mid(Type::Identity::KEYSIZE/8/2);
|
||||
_object->_sig_prv = Ed25519PrivateKey::from_private_bytes(_object->_sig_prv_bytes);
|
||||
//TRACE("Identity::load_private_key: sig prv bytes: " + _object->_sig_prv_bytes.toHex());
|
||||
|
||||
_object->_pub = _object->_prv->public_key();
|
||||
_object->_pub_bytes = _object->_pub->public_bytes();
|
||||
//TRACE("Identity::load_private_key: pub bytes: " + _object->_pub_bytes.toHex());
|
||||
|
||||
_object->_sig_pub = _object->_sig_prv->public_key();
|
||||
_object->_sig_pub_bytes = _object->_sig_pub->public_bytes();
|
||||
//TRACE("Identity::load_private_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
|
||||
|
||||
update_hashes();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
//p raise e
|
||||
ERROR("Failed to load identity key");
|
||||
ERRORF("The contained exception was: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Load a public key into the instance.
|
||||
|
||||
:param pub_bytes: The public key as *bytes*.
|
||||
:returns: True if the key was loaded, otherwise False.
|
||||
*/
|
||||
void Identity::load_public_key(const Bytes& pub_bytes) {
|
||||
assert(_object);
|
||||
|
||||
try {
|
||||
|
||||
//_pub_bytes = pub_bytes[:Identity.KEYSIZE//8//2]
|
||||
_object->_pub_bytes = pub_bytes.left(Type::Identity::KEYSIZE/8/2);
|
||||
//TRACE("Identity::load_public_key: pub bytes: " + _object->_pub_bytes.toHex());
|
||||
|
||||
//_sig_pub_bytes = pub_bytes[Identity.KEYSIZE//8//2:]
|
||||
_object->_sig_pub_bytes = pub_bytes.mid(Type::Identity::KEYSIZE/8/2);
|
||||
//TRACE("Identity::load_public_key: sig pub bytes: " + _object->_sig_pub_bytes.toHex());
|
||||
|
||||
_object->_pub = X25519PublicKey::from_public_bytes(_object->_pub_bytes);
|
||||
_object->_sig_pub = Ed25519PublicKey::from_public_bytes(_object->_sig_pub_bytes);
|
||||
|
||||
update_hashes();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Error while loading public key, the contained exception was: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool Identity::load(const char* path) {
|
||||
TRACE("Reading identity key from storage...");
|
||||
#if defined(RNS_USE_FS)
|
||||
try {
|
||||
Bytes prv_bytes;
|
||||
if (OS::read_file(path, prv_bytes) > 0) {
|
||||
return load_private_key(prv_bytes);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error while loading identity from " + std::string(path));
|
||||
ERRORF("The contained exception was: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Saves the identity to a file. This will write the private key to disk,
|
||||
and anyone with access to this file will be able to decrypt all
|
||||
communication for the identity. Be very careful with this method.
|
||||
|
||||
:param path: The full path specifying where to save the identity.
|
||||
:returns: True if the file was saved, otherwise False.
|
||||
*/
|
||||
bool Identity::to_file(const char* path) {
|
||||
TRACE("Writing identity key to storage...");
|
||||
#if defined(RNS_USE_FS)
|
||||
try {
|
||||
return (OS::write_file(path, get_private_key()) == get_private_key().size());
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Error while saving identity to %s", path);
|
||||
ERRORF("The contained exception was: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Create a new :ref:`RNS.Identity<api-identity>` instance from a file.
|
||||
Can be used to load previously created and saved identities into Reticulum.
|
||||
|
||||
:param path: The full path to the saved :ref:`RNS.Identity<api-identity>` data
|
||||
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the loaded data was invalid.
|
||||
*/
|
||||
/*static*/ const Identity Identity::from_file(const char* path) {
|
||||
Identity identity(false);
|
||||
if (identity.load(path)) {
|
||||
return identity;
|
||||
}
|
||||
return {Type::NONE};
|
||||
}
|
||||
|
||||
/*static*/ void Identity::remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data /*= {Bytes::NONE}*/) {
|
||||
if (public_key.size() != Type::Identity::KEYSIZE/8) {
|
||||
throw std::invalid_argument("Can't remember " + destination_hash.toHex() + ", the public key size of " + std::to_string(public_key.size()) + " is not valid.");
|
||||
}
|
||||
else {
|
||||
//p _known_destinations[destination_hash] = {OS::time(), packet_hash, public_key, app_data};
|
||||
// CBA ACCUMULATES
|
||||
_known_destinations.insert({destination_hash, {OS::time(), packet_hash, public_key, app_data}});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Recall identity for a destination hash.
|
||||
|
||||
:param destination_hash: Destination hash as *bytes*.
|
||||
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
|
||||
*/
|
||||
/*static*/ Identity Identity::recall(const Bytes& destination_hash) {
|
||||
TRACE("Identity::recall...");
|
||||
auto iter = _known_destinations.find(destination_hash);
|
||||
if (iter != _known_destinations.end()) {
|
||||
TRACE("Identity::recall: Found identity entry for destination " + destination_hash.toHex());
|
||||
const IdentityEntry& identity_data = (*iter).second;
|
||||
Identity identity(false);
|
||||
identity.load_public_key(identity_data._public_key);
|
||||
identity.app_data(identity_data._app_data);
|
||||
return identity;
|
||||
}
|
||||
else {
|
||||
TRACE("Identity::recall: Unable to find identity entry for destination " + destination_hash.toHex() + ", performing destination lookup...");
|
||||
Destination registered_destination(Transport::find_destination_from_hash(destination_hash));
|
||||
if (registered_destination) {
|
||||
TRACE("Identity::recall: Found destination " + destination_hash.toHex());
|
||||
Identity identity(false);
|
||||
identity.load_public_key(registered_destination.identity().get_public_key());
|
||||
identity.app_data({Bytes::NONE});
|
||||
return identity;
|
||||
}
|
||||
TRACE("Identity::recall: Unable to find destination " + destination_hash.toHex());
|
||||
return {Type::NONE};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Recall last heard app_data for a destination hash.
|
||||
|
||||
:param destination_hash: Destination hash as *bytes*.
|
||||
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
|
||||
*/
|
||||
/*static*/ Bytes Identity::recall_app_data(const Bytes& destination_hash) {
|
||||
TRACE("Identity::recall_app_data...");
|
||||
auto iter = _known_destinations.find(destination_hash);
|
||||
if (iter != _known_destinations.end()) {
|
||||
TRACE("Identity::recall_app_data: Found identity entry for destination " + destination_hash.toHex());
|
||||
const IdentityEntry& identity_data = (*iter).second;
|
||||
return identity_data._app_data;
|
||||
}
|
||||
else {
|
||||
TRACE("Identity::recall_app_data: Unable to find identity entry for destination " + destination_hash.toHex());
|
||||
return {Bytes::NONE};
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ bool Identity::save_known_destinations() {
|
||||
// TODO: Improve the storage method so we don't have to
|
||||
// deserialize and serialize the entire table on every
|
||||
// save, but the only changes. It might be possible to
|
||||
// simply overwrite on exit now that every local client
|
||||
// disconnect triggers a data persist.
|
||||
|
||||
bool success = false;
|
||||
try {
|
||||
if (_saving_known_destinations) {
|
||||
double wait_interval = 0.2;
|
||||
double wait_timeout = 5;
|
||||
double wait_start = OS::time();
|
||||
while (_saving_known_destinations) {
|
||||
OS::sleep(wait_interval);
|
||||
if (OS::time() > (wait_start + wait_timeout)) {
|
||||
ERROR("Could not save known destinations to storage, waiting for previous save operation timed out.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_saving_known_destinations = true;
|
||||
double save_start = OS::time();
|
||||
|
||||
std::map<Bytes, IdentityEntry> storage_known_destinations;
|
||||
// TODO
|
||||
/*
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
file.close()
|
||||
except:
|
||||
pass
|
||||
*/
|
||||
|
||||
for (auto& [destination_hash, identity_entry] : storage_known_destinations) {
|
||||
if (_known_destinations.find(destination_hash) == _known_destinations.end()) {
|
||||
//_known_destinations[destination_hash] = storage_known_destinations[destination_hash];
|
||||
//_known_destinations[destination_hash] = identity_entry;
|
||||
// CBA ACCUMULATES
|
||||
_known_destinations.insert({destination_hash, identity_entry});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*
|
||||
DEBUG("Saving " + std::to_string(_known_destinations.size()) + " known destinations to storage...");
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
||||
umsgpack.dump(Identity.known_destinations, file)
|
||||
file.close()
|
||||
*/
|
||||
|
||||
std::string time_str;
|
||||
double save_time = OS::time() - save_start;
|
||||
if (save_time < 1) {
|
||||
time_str = std::to_string((int)(save_time*1000)) + " ms";
|
||||
}
|
||||
else {
|
||||
time_str = std::to_string(OS::round(save_time, 1)) + " s";
|
||||
}
|
||||
|
||||
DEBUG("Saved known destinations to storage in " + time_str);
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Error while saving known destinations to disk, the contained exception was: %s", e.what());
|
||||
}
|
||||
|
||||
_saving_known_destinations = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/*static*/ void Identity::load_known_destinations() {
|
||||
// TODO
|
||||
/*
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||
loaded_known_destinations = umsgpack.load(file)
|
||||
file.close()
|
||||
|
||||
Identity.known_destinations = {}
|
||||
for known_destination in loaded_known_destinations:
|
||||
if len(known_destination) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
|
||||
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
|
||||
|
||||
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
|
||||
except:
|
||||
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
|
||||
else:
|
||||
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/*static*/ void Identity::cull_known_destinations() {
|
||||
TRACE("Transport::cull_path_table()");
|
||||
if (_known_destinations.size() > _known_destinations_maxsize) {
|
||||
// prune by age
|
||||
uint16_t count = 0;
|
||||
std::vector<std::pair<Bytes, IdentityEntry>> sorted_pairs;
|
||||
// Copy key/value pairs from map into vector
|
||||
std::for_each(_known_destinations.begin(), _known_destinations.end(), [&](const std::pair<const Bytes, IdentityEntry>& ref) {
|
||||
sorted_pairs.push_back(ref);
|
||||
});
|
||||
// Sort vector using specified comparator
|
||||
std::sort(sorted_pairs.begin(), sorted_pairs.end(), [](const std::pair<Bytes, IdentityEntry> &left, const std::pair<Bytes, IdentityEntry> &right) {
|
||||
return left.second._timestamp < right.second._timestamp;
|
||||
});
|
||||
// Iterate vector of sorted values
|
||||
for (auto& [destination_hash, identity_entry] : sorted_pairs) {
|
||||
TRACE("Transport::cull_path_table: Removing destination " + destination_hash.toHex() + " from known destinations");
|
||||
// Remove destination from known destinations
|
||||
if (_known_destinations.erase(destination_hash) < 1) {
|
||||
WARNING("Failed to remove destination " + destination_hash.toHex() + " from known destinations");
|
||||
}
|
||||
++count;
|
||||
if (_known_destinations.size() <= _known_destinations_maxsize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
DEBUG("Removed " + std::to_string(count) + " path(s) from known destinations");
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ bool Identity::validate_announce(const Packet& packet) {
|
||||
try {
|
||||
if (packet.packet_type() == Type::Packet::ANNOUNCE) {
|
||||
Bytes destination_hash = packet.destination_hash();
|
||||
//TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
|
||||
Bytes public_key = packet.data().left(KEYSIZE/8);
|
||||
//TRACE("Identity::validate_announce: public_key: " + public_key.toHex());
|
||||
Bytes name_hash = packet.data().mid(KEYSIZE/8, NAME_HASH_LENGTH/8);
|
||||
//TRACE("Identity::validate_announce: name_hash: " + name_hash.toHex());
|
||||
Bytes random_hash = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8, RANDOM_HASH_LENGTH/8);
|
||||
//TRACE("Identity::validate_announce: random_hash: " + random_hash.toHex());
|
||||
Bytes signature = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8, SIGLENGTH/8);
|
||||
//TRACE("Identity::validate_announce: signature: " + signature.toHex());
|
||||
Bytes app_data;
|
||||
if (packet.data().size() > (KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8)) {
|
||||
app_data = packet.data().mid(KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8);
|
||||
}
|
||||
//TRACE("Identity::validate_announce: app_data: " + app_data.toHex());
|
||||
//TRACE("Identity::validate_announce: app_data text: " + app_data.toString());
|
||||
|
||||
Bytes signed_data;
|
||||
signed_data << packet.destination_hash() << public_key << name_hash << random_hash+app_data;
|
||||
//TRACE("Identity::validate_announce: signed_data: " + signed_data.toHex());
|
||||
|
||||
if (packet.data().size() <= KEYSIZE/8 + NAME_HASH_LENGTH/8 + RANDOM_HASH_LENGTH/8 + SIGLENGTH/8) {
|
||||
app_data.clear();
|
||||
}
|
||||
|
||||
Identity announced_identity(false);
|
||||
announced_identity.load_public_key(public_key);
|
||||
|
||||
if (announced_identity.pub() && announced_identity.validate(signature, signed_data)) {
|
||||
Bytes hash_material = name_hash << announced_identity.hash();
|
||||
Bytes expected_hash = full_hash(hash_material).left(Type::Reticulum::TRUNCATED_HASHLENGTH/8);
|
||||
//TRACE("Identity::validate_announce: destination_hash: " + packet.destination_hash().toHex());
|
||||
//TRACE("Identity::validate_announce: expected_hash: " + expected_hash.toHex());
|
||||
|
||||
if (packet.destination_hash() == expected_hash) {
|
||||
// Check if we already have a public key for this destination
|
||||
// and make sure the public key is not different.
|
||||
auto iter = _known_destinations.find(packet.destination_hash());
|
||||
if (iter != _known_destinations.end()) {
|
||||
IdentityEntry& identity_entry = (*iter).second;
|
||||
if (public_key != identity_entry._public_key) {
|
||||
// In reality, this should never occur, but in the odd case
|
||||
// that someone manages a hash collision, we reject the announce.
|
||||
CRITICAL("Received announce with valid signature and destination hash, but announced public key does not match already known public key.");
|
||||
CRITICAL("This may indicate an attempt to modify network paths, or a random hash collision. The announce was rejected.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
remember(packet.get_hash(), packet.destination_hash(), public_key, app_data);
|
||||
//p del announced_identity
|
||||
|
||||
std::string signal_str;
|
||||
// TODO
|
||||
/*
|
||||
if packet.rssi != None or packet.snr != None:
|
||||
signal_str = " ["
|
||||
if packet.rssi != None:
|
||||
signal_str += "RSSI "+str(packet.rssi)+"dBm"
|
||||
if packet.snr != None:
|
||||
signal_str += ", "
|
||||
if packet.snr != None:
|
||||
signal_str += "SNR "+str(packet.snr)+"dB"
|
||||
signal_str += "]"
|
||||
else:
|
||||
signal_str = ""
|
||||
*/
|
||||
|
||||
if (packet.transport_id()) {
|
||||
TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received via " + packet.transport_id().toHex() + " on " + packet.receiving_interface().toString() + signal_str);
|
||||
}
|
||||
else {
|
||||
TRACE("Valid announce for " + packet.destination_hash().toHex() + " " + std::to_string(packet.hops()) + " hops away, received on " + packet.receiving_interface().toString() + signal_str);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Destination mismatch.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
DEBUG("Received invalid announce for " + packet.destination_hash().toHex() + ": Invalid signature.");
|
||||
//p del announced_identity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error occurred while validating announce. The contained exception was: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static*/ void Identity::persist_data() {
|
||||
if (!Transport::reticulum() || !Transport::reticulum().is_connected_to_shared_instance()) {
|
||||
save_known_destinations();
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ void Identity::exit_handler() {
|
||||
persist_data();
|
||||
}
|
||||
|
||||
/*
|
||||
Encrypts information for the identity.
|
||||
|
||||
:param plaintext: The plaintext to be encrypted as *bytes*.
|
||||
:returns: Ciphertext token as *bytes*.
|
||||
:raises: *KeyError* if the instance does not hold a public key.
|
||||
*/
|
||||
const Bytes Identity::encrypt(const Bytes& plaintext) const {
|
||||
assert(_object);
|
||||
TRACE("Identity::encrypt: encrypting data...");
|
||||
if (!_object->_pub) {
|
||||
throw std::runtime_error("Encryption failed because identity does not hold a public key");
|
||||
}
|
||||
Cryptography::X25519PrivateKey::Ptr ephemeral_key = Cryptography::X25519PrivateKey::generate();
|
||||
Bytes ephemeral_pub_bytes = ephemeral_key->public_key()->public_bytes();
|
||||
TRACE("Identity::encrypt: ephemeral public key: " + ephemeral_pub_bytes.toHex());
|
||||
|
||||
// CRYPTO: create shared key for key exchange using own public key
|
||||
//shared_key = ephemeral_key.exchange(self.pub)
|
||||
Bytes shared_key = ephemeral_key->exchange(_object->_pub_bytes);
|
||||
TRACE("Identity::encrypt: shared key: " + shared_key.toHex());
|
||||
|
||||
Bytes derived_key = Cryptography::hkdf(
|
||||
DERIVED_KEY_LENGTH,
|
||||
shared_key,
|
||||
get_salt(),
|
||||
get_context()
|
||||
);
|
||||
TRACE("Identity::encrypt: derived key: " + derived_key.toHex());
|
||||
|
||||
Cryptography::Token token(derived_key);
|
||||
TRACE("Identity::encrypt: Token encrypting data of length " + std::to_string(plaintext.size()));
|
||||
TRACE("Identity::encrypt: plaintext: " + plaintext.toHex());
|
||||
Bytes ciphertext = token.encrypt(plaintext);
|
||||
TRACE("Identity::encrypt: ciphertext: " + ciphertext.toHex());
|
||||
|
||||
return ephemeral_pub_bytes + ciphertext;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Decrypts information for the identity.
|
||||
|
||||
:param ciphertext: The ciphertext to be decrypted as *bytes*.
|
||||
:returns: Plaintext as *bytes*, or *None* if decryption fails.
|
||||
:raises: *KeyError* if the instance does not hold a private key.
|
||||
*/
|
||||
const Bytes Identity::decrypt(const Bytes& ciphertext_token) const {
|
||||
assert(_object);
|
||||
TRACE("Identity::decrypt: decrypting data...");
|
||||
if (!_object->_prv) {
|
||||
throw std::runtime_error("Decryption failed because identity does not hold a private key");
|
||||
}
|
||||
if (ciphertext_token.size() <= Type::Identity::KEYSIZE/8/2) {
|
||||
DEBUG("Decryption failed because the token size " + std::to_string(ciphertext_token.size()) + " was invalid.");
|
||||
return {Bytes::NONE};
|
||||
}
|
||||
Bytes plaintext;
|
||||
try {
|
||||
//peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
|
||||
Bytes peer_pub_bytes = ciphertext_token.left(Type::Identity::KEYSIZE/8/2);
|
||||
//peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
|
||||
//Cryptography::X25519PublicKey::Ptr peer_pub = Cryptography::X25519PublicKey::from_public_bytes(peer_pub_bytes);
|
||||
TRACE("Identity::decrypt: peer public key: " + peer_pub_bytes.toHex());
|
||||
|
||||
// CRYPTO: create shared key for key exchange using peer public key
|
||||
//shared_key = _object->_prv->exchange(peer_pub);
|
||||
Bytes shared_key = _object->_prv->exchange(peer_pub_bytes);
|
||||
TRACE("Identity::decrypt: shared key: " + shared_key.toHex());
|
||||
|
||||
Bytes derived_key = Cryptography::hkdf(
|
||||
DERIVED_KEY_LENGTH,
|
||||
shared_key,
|
||||
get_salt(),
|
||||
get_context()
|
||||
);
|
||||
TRACE("Identity::decrypt: derived key: " + derived_key.toHex());
|
||||
|
||||
Cryptography::Token token(derived_key);
|
||||
//ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
|
||||
Bytes ciphertext(ciphertext_token.mid(Type::Identity::KEYSIZE/8/2));
|
||||
TRACE("Identity::decrypt: Token decrypting data of length " + std::to_string(ciphertext.size()));
|
||||
TRACE("Identity::decrypt: ciphertext: " + ciphertext.toHex());
|
||||
plaintext = token.decrypt(ciphertext);
|
||||
TRACE("Identity::decrypt: plaintext: " + plaintext.toHex());
|
||||
//TRACE("Identity::decrypt: Token decrypted data of length " + std::to_string(plaintext.size()));
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
DEBUG("Decryption by " + toString() + " failed: " + e.what());
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/*
|
||||
Signs information by the identity.
|
||||
|
||||
:param message: The message to be signed as *bytes*.
|
||||
:returns: Signature as *bytes*.
|
||||
:raises: *KeyError* if the instance does not hold a private key.
|
||||
*/
|
||||
const Bytes Identity::sign(const Bytes& message) const {
|
||||
assert(_object);
|
||||
if (!_object->_sig_prv) {
|
||||
throw std::runtime_error("Signing failed because identity does not hold a private key");
|
||||
}
|
||||
try {
|
||||
return _object->_sig_prv->sign(message);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("The identity " + toString() + " could not sign the requested message. The contained exception was: " + e.what());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Validates the signature of a signed message.
|
||||
|
||||
:param signature: The signature to be validated as *bytes*.
|
||||
:param message: The message to be validated as *bytes*.
|
||||
:returns: True if the signature is valid, otherwise False.
|
||||
:raises: *KeyError* if the instance does not hold a public key.
|
||||
*/
|
||||
bool Identity::validate(const Bytes& signature, const Bytes& message) const {
|
||||
assert(_object);
|
||||
if (_object->_pub) {
|
||||
try {
|
||||
TRACE("Identity::validate: Attempting to verify signature: " + signature.toHex() + " and message: " + message.toHex());
|
||||
_object->_sig_pub->verify(signature, message);
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Signature validation failed because identity does not hold a public key");
|
||||
}
|
||||
}
|
||||
|
||||
void Identity::prove(const Packet& packet, const Destination& destination /*= {Type::NONE}*/) const {
|
||||
assert(_object);
|
||||
Bytes signature(sign(packet.packet_hash()));
|
||||
Bytes proof_data;
|
||||
if (RNS::Reticulum::should_use_implicit_proof()) {
|
||||
proof_data = signature;
|
||||
TRACE("Identity::prove: implicit proof data: " + proof_data.toHex());
|
||||
}
|
||||
else {
|
||||
proof_data = packet.packet_hash() + signature;
|
||||
TRACE("Identity::prove: explicit proof data: " + proof_data.toHex());
|
||||
}
|
||||
|
||||
if (!destination) {
|
||||
TRACE("Identity::prove: proving packet with proof destination...");
|
||||
ProofDestination proof_destination = packet.generate_proof_destination();
|
||||
Packet proof(proof_destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
|
||||
proof.send();
|
||||
}
|
||||
else {
|
||||
TRACE("Identity::prove: proving packet with specified destination...");
|
||||
Packet proof(destination, packet.receiving_interface(), proof_data, Type::Packet::PROOF);
|
||||
proof.send();
|
||||
}
|
||||
}
|
||||
|
||||
void Identity::prove(const Packet& packet) const {
|
||||
prove(packet, {Type::NONE});
|
||||
}
|
||||
200
lib/microReticulum/src/Identity.h
Executable file
200
lib/microReticulum/src/Identity.h
Executable file
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include "Log.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
#include "Cryptography/Hashes.h"
|
||||
#include "Cryptography/Ed25519.h"
|
||||
#include "Cryptography/X25519.h"
|
||||
#include "Cryptography/Token.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Destination;
|
||||
class Packet;
|
||||
|
||||
class Identity {
|
||||
|
||||
private:
|
||||
class IdentityEntry {
|
||||
public:
|
||||
IdentityEntry(double timestamp, const Bytes& packet_hash, const Bytes& public_key, const Bytes& app_data) :
|
||||
_timestamp(timestamp),
|
||||
_packet_hash(packet_hash),
|
||||
_public_key(public_key),
|
||||
_app_data(app_data)
|
||||
{
|
||||
}
|
||||
public:
|
||||
double _timestamp = 0;
|
||||
Bytes _packet_hash;
|
||||
Bytes _public_key;
|
||||
Bytes _app_data;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::map<Bytes, IdentityEntry> _known_destinations;
|
||||
static bool _saving_known_destinations;
|
||||
// CBA
|
||||
static uint16_t _known_destinations_maxsize;
|
||||
|
||||
public:
|
||||
Identity(bool create_keys = true);
|
||||
Identity(Type::NoneConstructor none) {
|
||||
MEM("Identity NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
Identity(const Identity& identity) : _object(identity._object) {
|
||||
MEM("Identity object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
virtual ~Identity() {
|
||||
MEM("Identity object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
inline Identity& operator = (const Identity& identity) {
|
||||
_object = identity._object;
|
||||
MEM("Identity object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const Identity& identity) const {
|
||||
return _object.get() < identity._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
void createKeys();
|
||||
|
||||
/*
|
||||
:returns: The private key as *bytes*
|
||||
*/
|
||||
inline const Bytes get_private_key() const {
|
||||
assert(_object);
|
||||
return _object->_prv_bytes + _object->_sig_prv_bytes;
|
||||
}
|
||||
/*
|
||||
:returns: The public key as *bytes*
|
||||
*/
|
||||
inline const Bytes get_public_key() const {
|
||||
assert(_object);
|
||||
return _object->_pub_bytes + _object->_sig_pub_bytes;
|
||||
}
|
||||
bool load_private_key(const Bytes& prv_bytes);
|
||||
void load_public_key(const Bytes& pub_bytes);
|
||||
inline void update_hashes() {
|
||||
assert(_object);
|
||||
_object->_hash = truncated_hash(get_public_key());
|
||||
TRACE("Identity::update_hashes: hash: " + _object->_hash.toHex());
|
||||
_object->_hexhash = _object->_hash.toHex();
|
||||
};
|
||||
bool load(const char* path);
|
||||
bool to_file(const char* path);
|
||||
|
||||
inline const Bytes& get_salt() const { assert(_object); return _object->_hash; }
|
||||
inline const Bytes get_context() const { return {Bytes::NONE}; }
|
||||
|
||||
const Bytes encrypt(const Bytes& plaintext) const;
|
||||
const Bytes decrypt(const Bytes& ciphertext_token) const;
|
||||
const Bytes sign(const Bytes& message) const;
|
||||
bool validate(const Bytes& signature, const Bytes& message) const;
|
||||
// CBA following default for reference value requires inclusiion of header
|
||||
//void prove(const Packet& packet, const Destination& destination = {Type::NONE}) const;
|
||||
void prove(const Packet& packet, const Destination& destination) const;
|
||||
void prove(const Packet& packet) const;
|
||||
|
||||
static const Identity from_file(const char* path);
|
||||
static void remember(const Bytes& packet_hash, const Bytes& destination_hash, const Bytes& public_key, const Bytes& app_data = {Bytes::NONE});
|
||||
static Identity recall(const Bytes& destination_hash);
|
||||
static Bytes recall_app_data(const Bytes& destination_hash);
|
||||
static bool save_known_destinations();
|
||||
static void load_known_destinations();
|
||||
// CBA
|
||||
static void cull_known_destinations();
|
||||
|
||||
/*
|
||||
Get a SHA-256 hash of passed data.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: SHA-256 hash as *bytes*
|
||||
*/
|
||||
static inline const Bytes full_hash(const Bytes& data) {
|
||||
return Cryptography::sha256(data);
|
||||
}
|
||||
|
||||
/*
|
||||
Get a truncated SHA-256 hash of passed data.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: Truncated SHA-256 hash as *bytes*
|
||||
*/
|
||||
static inline const Bytes truncated_hash(const Bytes& data) {
|
||||
//p return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
|
||||
return full_hash(data).left(Type::Identity::TRUNCATED_HASHLENGTH/8);
|
||||
}
|
||||
|
||||
/*
|
||||
Get a random SHA-256 hash.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: Truncated SHA-256 hash of random data as *bytes*
|
||||
*/
|
||||
static inline const Bytes get_random_hash() {
|
||||
return truncated_hash(Cryptography::random(Type::Identity::TRUNCATED_HASHLENGTH/8));
|
||||
}
|
||||
|
||||
static bool validate_announce(const Packet& packet);
|
||||
static void persist_data();
|
||||
static void exit_handler();
|
||||
|
||||
// getters/setters
|
||||
inline const Bytes& encryptionPrivateKey() const { assert(_object); return _object->_prv_bytes; }
|
||||
inline const Bytes& signingPrivateKey() const { assert(_object); return _object->_sig_prv_bytes; }
|
||||
inline const Bytes& encryptionPublicKey() const { assert(_object); return _object->_pub_bytes; }
|
||||
inline const Bytes& signingPublicKey() const { assert(_object); return _object->_sig_pub_bytes; }
|
||||
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
|
||||
inline std::string hexhash() const { assert(_object); return _object->_hexhash; }
|
||||
inline const Bytes& app_data() const { assert(_object); return _object->_app_data; }
|
||||
inline void app_data(const Bytes& app_data) { assert(_object); _object->_app_data = app_data; }
|
||||
inline const Cryptography::X25519PrivateKey::Ptr prv() const { assert(_object); return _object->_prv; }
|
||||
inline const Cryptography::Ed25519PrivateKey::Ptr sig_prv() const { assert(_object); return _object->_sig_prv; }
|
||||
inline const Cryptography::X25519PublicKey::Ptr pub() const { assert(_object); return _object->_pub; }
|
||||
inline const Cryptography::Ed25519PublicKey::Ptr sig_pub() const { assert(_object); return _object->_sig_pub; }
|
||||
|
||||
inline std::string toString() const { if (!_object) return ""; return "{Identity:" + _object->_hash.toHex() + "}"; }
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object() { MEM("Identity::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
virtual ~Object() { MEM("Identity::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
|
||||
private:
|
||||
|
||||
Cryptography::X25519PrivateKey::Ptr _prv;
|
||||
Bytes _prv_bytes;
|
||||
|
||||
Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
|
||||
Bytes _sig_prv_bytes;
|
||||
|
||||
Cryptography::X25519PublicKey::Ptr _pub;
|
||||
Bytes _pub_bytes;
|
||||
|
||||
Cryptography::Ed25519PublicKey::Ptr _sig_pub;
|
||||
Bytes _sig_pub_bytes;
|
||||
|
||||
Bytes _hash;
|
||||
std::string _hexhash;
|
||||
|
||||
Bytes _app_data;
|
||||
|
||||
friend class Identity;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
104
lib/microReticulum/src/Interface.cpp
Executable file
104
lib/microReticulum/src/Interface.cpp
Executable file
@@ -0,0 +1,104 @@
|
||||
#include "Interface.h"
|
||||
|
||||
#include "Identity.h"
|
||||
#include "Transport.h"
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Interface;
|
||||
|
||||
/*static*/ uint8_t Interface::DISCOVER_PATHS_FOR = MODE_ACCESS_POINT | MODE_GATEWAY;
|
||||
|
||||
void InterfaceImpl::handle_outgoing(const Bytes& data) {
|
||||
//TRACE("InterfaceImpl.handle_outgoing: data: " + data.toHex());
|
||||
TRACE("InterfaceImpl.handle_outgoing");
|
||||
_txb += data.size();
|
||||
}
|
||||
|
||||
void InterfaceImpl::handle_incoming(const Bytes& data) {
|
||||
//TRACE("InterfaceImpl.handle_incoming: data: " + data.toHex());
|
||||
TRACE("InterfaceImpl.handle_incoming");
|
||||
_rxb += data.size();
|
||||
// Create temporary Interface encapsulating our own shared impl
|
||||
std::shared_ptr<InterfaceImpl> self = shared_from_this();
|
||||
Interface interface(self);
|
||||
// Pass data on to transport for handling
|
||||
Transport::inbound(data, interface);
|
||||
}
|
||||
|
||||
void Interface::handle_incoming(const Bytes& data) {
|
||||
//TRACE("Interface.handle_incoming: data: " + data.toHex());
|
||||
TRACE("Interface.handle_incoming");
|
||||
assert(_impl);
|
||||
/*
|
||||
_impl->_rxb += data.size();
|
||||
// Pass data on to transport for handling
|
||||
Transport::inbound(data, *this);
|
||||
*/
|
||||
_impl->handle_incoming(data);
|
||||
}
|
||||
|
||||
void Interface::process_announce_queue() {
|
||||
/*
|
||||
if not hasattr(self, "announce_cap"):
|
||||
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
|
||||
|
||||
if hasattr(self, "announce_queue"):
|
||||
try:
|
||||
now = time.time()
|
||||
stale = []
|
||||
for a in self.announce_queue:
|
||||
if now > a["time"]+RNS.Reticulum.QUEUED_ANNOUNCE_LIFE:
|
||||
stale.append(a)
|
||||
|
||||
for s in stale:
|
||||
if s in self.announce_queue:
|
||||
self.announce_queue.remove(s)
|
||||
|
||||
if len(self.announce_queue) > 0:
|
||||
min_hops = min(entry["hops"] for entry in self.announce_queue)
|
||||
entries = list(filter(lambda e: e["hops"] == min_hops, self.announce_queue))
|
||||
entries.sort(key=lambda e: e["time"])
|
||||
selected = entries[0]
|
||||
|
||||
double now = OS::time();
|
||||
uint32_t wait_time = 0;
|
||||
if (_impl->_bitrate > 0 && _impl->_announce_cap > 0) {
|
||||
uint32_t tx_time = (len(selected["raw"])*8) / _impl->_bitrate;
|
||||
wait_time = (tx_time / _impl->_announce_cap);
|
||||
}
|
||||
_impl->_announce_allowed_at = now + wait_time;
|
||||
|
||||
self.on_outgoing(selected["raw"])
|
||||
|
||||
if selected in self.announce_queue:
|
||||
self.announce_queue.remove(selected)
|
||||
|
||||
if len(self.announce_queue) > 0:
|
||||
timer = threading.Timer(wait_time, self.process_announce_queue)
|
||||
timer.start()
|
||||
|
||||
except Exception as e:
|
||||
self.announce_queue = []
|
||||
RNS.log("Error while processing announce queue on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR)
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Interface& dst) {
|
||||
TRACE(">>> Deserializing Interface");
|
||||
TRACE(">>> Interface pre: " + dst.debugString());
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
TRACE(">>> Querying Transport for Interface hash " + hash.toHex());
|
||||
// Query transport for matching interface
|
||||
dst = Transport::find_interface_from_hash(hash);
|
||||
TRACE(">>> Interface post: " + dst.debugString());
|
||||
}
|
||||
else {
|
||||
dst = {RNS::Type::NONE};
|
||||
TRACE(">>> Interface post: " + dst.debugString());
|
||||
}
|
||||
}
|
||||
*/
|
||||
268
lib/microReticulum/src/Interface.h
Executable file
268
lib/microReticulum/src/Interface.h
Executable file
@@ -0,0 +1,268 @@
|
||||
#pragma once
|
||||
|
||||
#include "Identity.h"
|
||||
#include "Log.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Interface;
|
||||
using HInterface = std::shared_ptr<Interface>;
|
||||
|
||||
class AnnounceEntry {
|
||||
public:
|
||||
AnnounceEntry() {}
|
||||
AnnounceEntry(const Bytes& destination, double time, uint8_t hops, double emitted, const Bytes& raw) :
|
||||
_destination(destination),
|
||||
_time(time),
|
||||
_hops(hops),
|
||||
_emitted(emitted),
|
||||
_raw(raw) {}
|
||||
public:
|
||||
Bytes _destination;
|
||||
double _time = 0;
|
||||
uint8_t _hops = 0;
|
||||
uint64_t _emitted = 0;
|
||||
Bytes _raw;
|
||||
};
|
||||
|
||||
class InterfaceImpl : public std::enable_shared_from_this<InterfaceImpl> {
|
||||
|
||||
protected:
|
||||
InterfaceImpl() { MEMF("InterfaceImpl object created, this: 0x%X", this); }
|
||||
InterfaceImpl(const char* name) : _name(name) { MEMF("InterfaceImpl object created, this: 0x%X", this); }
|
||||
public:
|
||||
virtual ~InterfaceImpl() { MEMF("InterfaceImpl object destroyed, this: 0x%X", this); }
|
||||
|
||||
protected:
|
||||
virtual bool start() { return true; }
|
||||
virtual void stop() {}
|
||||
virtual void loop() {}
|
||||
|
||||
// CBA Virtual override method for custom interface to send outgoing data
|
||||
virtual void send_outgoing(const Bytes& data) = 0;
|
||||
|
||||
// CBA Internal method to handle housekeeping for data going out on interface
|
||||
void handle_outgoing(const Bytes& data);
|
||||
// CBA Internal method to handle data coming in on interface and pass on to transport
|
||||
virtual void handle_incoming(const Bytes& data);
|
||||
|
||||
virtual const Bytes get_hash() const {
|
||||
return Identity::full_hash({toString()});
|
||||
}
|
||||
|
||||
virtual inline std::string toString() const { return "Interface[" + _name + "]"; }
|
||||
|
||||
protected:
|
||||
Interface* _parent = nullptr;
|
||||
bool _IN = false;
|
||||
bool _OUT = false;
|
||||
bool _FWD = false;
|
||||
bool _RPT = false;
|
||||
std::string _name;
|
||||
size_t _rxb = 0;
|
||||
size_t _txb = 0;
|
||||
bool _online = false;
|
||||
Bytes _ifac_identity;
|
||||
Type::Interface::modes _mode = Type::Interface::MODE_NONE;
|
||||
uint32_t _bitrate = 0;
|
||||
uint16_t _HW_MTU = 0;
|
||||
bool _AUTOCONFIGURE_MTU = false;
|
||||
bool _FIXED_MTU = false;
|
||||
double _announce_allowed_at = 0;
|
||||
float _announce_cap = 0.0;
|
||||
std::list<AnnounceEntry> _announce_queue;
|
||||
bool _is_connected_to_shared_instance = false;
|
||||
bool _is_local_shared_instance = false;
|
||||
bool _is_backbone = false;
|
||||
//Bytes _hash;
|
||||
HInterface _parent_interface;
|
||||
//Transport& _owner;
|
||||
|
||||
friend class Interface;
|
||||
};
|
||||
|
||||
class Interface {
|
||||
|
||||
public:
|
||||
// Which interface modes a Transport Node
|
||||
// should actively discover paths for.
|
||||
static uint8_t DISCOVER_PATHS_FOR;
|
||||
|
||||
public:
|
||||
Interface(Type::NoneConstructor none) {
|
||||
MEMF("Interface NONE object created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
Interface(const Interface& obj) : _impl(obj._impl) {
|
||||
MEMF("Interface object copy created, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
Interface(std::shared_ptr<InterfaceImpl>& impl) : _impl(impl) {
|
||||
MEMF("Interface object created with shared impl, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
Interface(InterfaceImpl* impl) : _impl(impl) {
|
||||
MEMF("Interface object created with new impl, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
virtual ~Interface() {
|
||||
MEMF("Interface object destroyed, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
}
|
||||
|
||||
inline Interface& operator = (const Interface& obj) {
|
||||
_impl = obj._impl;
|
||||
MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline Interface& operator = (InterfaceImpl* impl) {
|
||||
_impl.reset(impl);
|
||||
MEMF("Interface object copy created by assignment, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
MEMF("Interface object bool, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const Interface& obj) const {
|
||||
MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() < obj._impl.get();
|
||||
}
|
||||
inline bool operator > (const Interface& obj) const {
|
||||
MEMF("Interface object <, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() > obj._impl.get();
|
||||
}
|
||||
inline bool operator == (const Interface& obj) const {
|
||||
MEMF("Interface object ==, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() == obj._impl.get();
|
||||
}
|
||||
inline bool operator != (const Interface& obj) const {
|
||||
MEMF("Interface object !=, this: 0x%X, impl: 0x%X", this, _impl.get());
|
||||
return _impl.get() != obj._impl.get();
|
||||
}
|
||||
inline InterfaceImpl* get() {
|
||||
return _impl.get();
|
||||
}
|
||||
inline void clear() {
|
||||
_impl.reset();
|
||||
}
|
||||
|
||||
public:
|
||||
inline bool start() { assert(_impl); return _impl->start(); }
|
||||
inline void stop() { assert(_impl); return _impl->stop(); }
|
||||
inline void loop() { assert(_impl); return _impl->loop(); }
|
||||
inline const Bytes get_hash() const { assert(_impl); return _impl->get_hash(); }
|
||||
void process_announce_queue();
|
||||
|
||||
// CBA ACCUMULATES
|
||||
inline void add_announce(AnnounceEntry& entry) { assert(_impl); _impl->_announce_queue.push_back(entry); }
|
||||
|
||||
protected:
|
||||
inline void send_outgoing(const Bytes& data) { assert(_impl); _impl->send_outgoing(data); }
|
||||
public:
|
||||
//inline void handle_incoming(const Bytes& data) { assert(_impl); _impl->handle_incoming(data); }
|
||||
// Public method to handle data coming in on interface and pass on to impl
|
||||
void handle_incoming(const Bytes& data);
|
||||
|
||||
protected:
|
||||
// setters
|
||||
inline void IN(bool IN) { assert(_impl); _impl->_IN = IN; }
|
||||
inline void OUT(bool OUT) { assert(_impl); _impl->_OUT = OUT; }
|
||||
inline void FWD(bool FWD) { assert(_impl); _impl->_FWD = FWD; }
|
||||
inline void RPT(bool RPT) { assert(_impl); _impl->_RPT = RPT; }
|
||||
inline void name(const char* name) { assert(_impl); _impl->_name = name; }
|
||||
inline void bitrate(uint32_t bitrate) { assert(_impl); _impl->_bitrate = bitrate; }
|
||||
inline void online(bool online) { assert(_impl); _impl->_online = online; }
|
||||
inline void announce_allowed_at(double announce_allowed_at) { assert(_impl); _impl->_announce_allowed_at = announce_allowed_at; }
|
||||
public:
|
||||
// getters
|
||||
inline bool IN() const { assert(_impl); return _impl->_IN; }
|
||||
inline bool OUT() const { assert(_impl); return _impl->_OUT; }
|
||||
inline bool FWD() const { assert(_impl); return _impl->_FWD; }
|
||||
inline bool RPT() const { assert(_impl); return _impl->_RPT; }
|
||||
inline bool online() const { assert(_impl); return _impl->_online; }
|
||||
inline std::string name() const { assert(_impl); return _impl->_name; }
|
||||
inline const Bytes& ifac_identity() const { assert(_impl); return _impl->_ifac_identity; }
|
||||
inline Type::Interface::modes mode() const { assert(_impl); return _impl->_mode; }
|
||||
inline void mode(Type::Interface::modes mode) { assert(_impl); _impl->_mode = mode; }
|
||||
inline uint32_t bitrate() const { assert(_impl); return _impl->_bitrate; }
|
||||
inline uint16_t HW_MTU() const { assert(_impl); return _impl->_HW_MTU; }
|
||||
inline bool AUTOCONFIGURE_MTU() const { assert(_impl); return _impl->_AUTOCONFIGURE_MTU; }
|
||||
inline bool FIXED_MTU() const { assert(_impl); return _impl->_FIXED_MTU; }
|
||||
inline double announce_allowed_at() const { assert(_impl); return _impl->_announce_allowed_at; }
|
||||
inline float announce_cap() const { assert(_impl); return _impl->_announce_cap; }
|
||||
inline std::list<AnnounceEntry>& announce_queue() const { assert(_impl); return _impl->_announce_queue; }
|
||||
inline bool is_connected_to_shared_instance() const { assert(_impl); return _impl->_is_connected_to_shared_instance; }
|
||||
inline bool is_local_shared_instance() const { assert(_impl); return _impl->_is_local_shared_instance; }
|
||||
inline bool is_backbone() const { assert(_impl); return _impl->_is_backbone; }
|
||||
inline void is_backbone(bool val) { assert(_impl); _impl->_is_backbone = val; }
|
||||
inline HInterface parent_interface() const { assert(_impl); return _impl->_parent_interface; }
|
||||
|
||||
virtual inline std::string toString() const { if (!_impl) return ""; return _impl->toString(); }
|
||||
|
||||
#ifndef NDEBUG
|
||||
inline std::string debugString() const {
|
||||
std::string dump;
|
||||
dump = "Interface object, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_impl.get());
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::shared_ptr<InterfaceImpl> _impl;
|
||||
|
||||
friend class Transport;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
namespace ArduinoJson {
|
||||
inline bool convertToJson(const RNS::Interface& src, JsonVariant dst) {
|
||||
TRACE("<<< Serializing Interface");
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
TRACE("<<< Interface hash " + src.get_hash().toHex());
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
void convertFromJson(JsonVariantConst src, RNS::Interface& dst);
|
||||
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Interface&) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
namespace ArduinoJson {
|
||||
template <>
|
||||
struct Converter<RNS::Interface> {
|
||||
static bool toJson(const RNS::Interface& src, JsonVariant dst) {
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
static RNS::Interface fromJson(JsonVariantConst src) {
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
TRACE(">>> Deserialized interface hash " + hash.toHex());
|
||||
TRACE(">>> Querying transport for interface");
|
||||
// Query transport for matching interface
|
||||
return RNS::Interface::find_interface_from_hash(hash);
|
||||
}
|
||||
else {
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
1836
lib/microReticulum/src/Link.cpp
Executable file
1836
lib/microReticulum/src/Link.cpp
Executable file
File diff suppressed because it is too large
Load Diff
270
lib/microReticulum/src/Link.h
Executable file
270
lib/microReticulum/src/Link.h
Executable file
@@ -0,0 +1,270 @@
|
||||
#pragma once
|
||||
|
||||
#include "Destination.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class ResourceRequest;
|
||||
class ResourceResponse;
|
||||
class RequestReceipt;
|
||||
class Link;
|
||||
|
||||
class LinkData;
|
||||
class RequestReceiptData;
|
||||
class Resource;
|
||||
class Packet;
|
||||
class Destination;
|
||||
class ResourceAdvertisement;
|
||||
class PacketReceipt;
|
||||
|
||||
class ResourceRequest {
|
||||
public:
|
||||
double _requested_at = 0.0;
|
||||
Bytes _path_hash;
|
||||
Bytes _request_data;
|
||||
};
|
||||
|
||||
class ResourceResponse {
|
||||
Bytes request_id;
|
||||
Bytes response_data;
|
||||
};
|
||||
|
||||
/*
|
||||
An instance of this class is returned by the ``request`` method of ``RNS.Link``
|
||||
instances. It should never be instantiated manually. It provides methods to
|
||||
check status, response time and response data when the request concludes.
|
||||
*/
|
||||
class RequestReceipt {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
using response = void(*)(const RequestReceipt& packet_receipt);
|
||||
using failed = void(*)(const RequestReceipt& packet_receipt);
|
||||
using progress = void(*)(const RequestReceipt& packet_receipt);
|
||||
public:
|
||||
response _response = nullptr;
|
||||
failed _failed = nullptr;
|
||||
progress _progress = nullptr;
|
||||
friend class RequestReceipt;
|
||||
};
|
||||
|
||||
public:
|
||||
RequestReceipt(Type::NoneConstructor none) {}
|
||||
RequestReceipt(const RequestReceipt& request_receipt) : _object(request_receipt._object) {}
|
||||
//RequestReceipt(const Link& link, const PacketReceipt& packet_receipt = {Type::NONE}, const Resource& resource = {Type::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
|
||||
RequestReceipt(const Link& link, const PacketReceipt& packet_receipt, const Resource& resource, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int request_size = 0);
|
||||
|
||||
inline RequestReceipt& operator = (const RequestReceipt& packet_receipt) {
|
||||
_object = packet_receipt._object;
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const RequestReceipt& packet_receipt) const {
|
||||
return _object.get() < packet_receipt._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
void request_resource_concluded(const Resource& resource);
|
||||
void __response_timeout_job();
|
||||
void request_timed_out(const PacketReceipt& packet_receipt);
|
||||
void response_resource_progress(const Resource& resource);
|
||||
void response_received(const Bytes& response);
|
||||
const Bytes& get_request_id() const;
|
||||
Type::RequestReceipt::status get_status() const;
|
||||
float get_progress() const;
|
||||
const Bytes get_response() const;
|
||||
double get_response_time() const;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
// getters
|
||||
const Bytes& hash() const;
|
||||
const Bytes& request_id() const;
|
||||
size_t response_transfer_size() const;
|
||||
|
||||
// setters
|
||||
void response_size(size_t size);
|
||||
void response_transfer_size(size_t size);
|
||||
|
||||
private:
|
||||
std::shared_ptr<RequestReceiptData> _object;
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
This class is used to establish and manage links to other peers. When a
|
||||
link instance is created, Reticulum will attempt to establish verified
|
||||
and encrypted connectivity with the specified destination.
|
||||
|
||||
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
|
||||
:param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
|
||||
:param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
|
||||
*/
|
||||
class Link {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
using established = void(*)(Link& link);
|
||||
using closed = void(*)(Link& link);
|
||||
using packet = void(*)(const Bytes& plaintext, const Packet& packet);
|
||||
using remote_identified = void(*)(const Link& link, const Identity& remote_identity);
|
||||
using resource = void(*)(const ResourceAdvertisement& resource_advertisement);
|
||||
using resource_started = void(*)(const Resource& resource);
|
||||
using resource_concluded = void(*)(const Resource& resource);
|
||||
public:
|
||||
established _established = nullptr;
|
||||
closed _closed = nullptr;
|
||||
packet _packet = nullptr;
|
||||
remote_identified _remote_identified = nullptr;
|
||||
resource _resource = nullptr;
|
||||
resource_started _resource_started = nullptr;
|
||||
resource_concluded _resource_concluded = nullptr;
|
||||
friend class Link;
|
||||
};
|
||||
|
||||
public:
|
||||
static uint8_t resource_strategies;
|
||||
static std::set<RNS::Type::Link::link_mode> ENABLED_MODES;
|
||||
static RNS::Type::Link::link_mode MODE_DEFAULT;
|
||||
|
||||
public:
|
||||
Link(Type::NoneConstructor none) {
|
||||
MEM("Link NONE object created");
|
||||
}
|
||||
Link(const Link& link) : _object(link._object) {
|
||||
MEM("Link object copy created");
|
||||
}
|
||||
Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
|
||||
//Link(const Destination& destination = {Type::NONE}, Callbacks::established established_callback = nullptr, Callbacks::closed closed_callback = nullptr, const Destination& owner = {Type::NONE}, const Bytes& peer_pub_bytes = {Bytes::NONE}, const Bytes& peer_sig_pub_bytes = {Bytes::NONE}, RNS::Type::Link::link_mode mode = MODE_DEFAULT);
|
||||
virtual ~Link(){
|
||||
MEM("Link object destroyed");
|
||||
}
|
||||
|
||||
Link& operator = (const Link& link) {
|
||||
_object = link._object;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
bool operator < (const Link& link) const {
|
||||
return _object.get() < link._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
static Bytes signalling_bytes(uint16_t mtu, RNS::Type::Link::link_mode mode);
|
||||
static uint16_t mtu_from_lr_packet(const Packet& packet);
|
||||
static uint16_t mtu_from_lp_packet(const Packet& packet);
|
||||
static uint8_t mode_byte(RNS::Type::Link::link_mode mode);
|
||||
static RNS::Type::Link::link_mode mode_from_lr_packet(const Packet& packet);
|
||||
static RNS::Type::Link::link_mode mode_from_lp_packet(const Packet& packet);
|
||||
static Bytes link_id_from_lr_packet(const Packet& packet);
|
||||
static Link validate_request( const Destination& owner, const Bytes& data, const Packet& packet);
|
||||
|
||||
public:
|
||||
void load_peer(const Bytes& peer_pub_bytes, const Bytes& peer_sig_pub_bytes);
|
||||
void set_link_id(const Packet& packet);
|
||||
void handshake();
|
||||
void prove();
|
||||
void prove_packet(const Packet& packet);
|
||||
void validate_proof(const Packet& packet);
|
||||
void identify(const Identity& identity);
|
||||
const RequestReceipt request(const Bytes& path, const Bytes& data = {Bytes::NONE}, RequestReceipt::Callbacks::response response_callback = nullptr, RequestReceipt::Callbacks::failed failed_callback = nullptr, RequestReceipt::Callbacks::progress progress_callback = nullptr, double timeout = 0.0);
|
||||
void update_mdu();
|
||||
void rtt_packet(const Packet& packet);
|
||||
float get_establishment_rate();
|
||||
uint16_t get_mtu();
|
||||
uint16_t get_mdu();
|
||||
float get_expected_rate();
|
||||
RNS::Type::Link::link_mode get_mode();
|
||||
const Bytes& get_salt();
|
||||
const Bytes get_context();
|
||||
double get_age();
|
||||
double no_inbound_for();
|
||||
double no_outbound_for();
|
||||
double no_data_for();
|
||||
double inactive_for();
|
||||
const Identity& get_remote_identity();
|
||||
void had_outbound(bool is_keepalive = false);
|
||||
void teardown();
|
||||
void teardown_packet(const Packet& packet);
|
||||
void link_closed();
|
||||
void start_watchdog();
|
||||
void __watchdog_job();
|
||||
void send_keepalive();
|
||||
void handle_request(const Bytes& request_id, const ResourceRequest& unpacked_request);
|
||||
void handle_response(const Bytes& request_id, const Bytes& response_data, size_t response_size, size_t response_transfer_size);
|
||||
void request_resource_concluded(const Resource& resource);
|
||||
void response_resource_concluded(const Resource& resource);
|
||||
//z const Channel& get_channel();
|
||||
void receive(const Packet& packet);
|
||||
const Bytes encrypt(const Bytes& plaintext);
|
||||
const Bytes decrypt(const Bytes& ciphertext);
|
||||
const Bytes sign(const Bytes& message);
|
||||
bool validate(const Bytes& signature, const Bytes& message);
|
||||
void set_link_established_callback(Callbacks::established callback);
|
||||
void set_link_closed_callback(Callbacks::closed callback);
|
||||
void set_packet_callback(Callbacks::packet callback);
|
||||
void set_remote_identified_callback(Callbacks::remote_identified callback);
|
||||
void set_resource_callback(Callbacks::resource callback);
|
||||
void set_resource_started_callback(Callbacks::resource_started callback);
|
||||
void set_resource_concluded_callback(Callbacks::resource_concluded callback);
|
||||
void resource_concluded(const Resource& resource);
|
||||
void set_resource_strategy(Type::Link::resource_strategy strategy);
|
||||
void register_outgoing_resource(const Resource& resource);
|
||||
void register_incoming_resource(const Resource& resource);
|
||||
bool has_incoming_resource(const Resource& resource);
|
||||
void cancel_outgoing_resource(const Resource& resource);
|
||||
void cancel_incoming_resource(const Resource& resource);
|
||||
bool ready_for_new_resource();
|
||||
|
||||
//void __str__();
|
||||
std::string toString() const;
|
||||
|
||||
// getters
|
||||
double rtt() const;
|
||||
const Destination& destination() const;
|
||||
// CBA LINK
|
||||
//const Destination& link_destination() const;
|
||||
const Interface& attached_interface() const;
|
||||
const Bytes& link_id() const;
|
||||
const Bytes& hash() const;
|
||||
uint16_t mtu() const;
|
||||
Type::Link::status status() const;
|
||||
double establishment_timeout() const;
|
||||
uint16_t establishment_cost() const;
|
||||
uint8_t traffic_timeout_factor() const;
|
||||
double request_time() const;
|
||||
double last_inbound() const;
|
||||
std::set<RequestReceipt>& pending_requests() const;
|
||||
Type::Link::teardown_reason teardown_reason() const;
|
||||
bool initiator() const;
|
||||
|
||||
// setters
|
||||
void destination(const Destination& destination);
|
||||
void attached_interface(const Interface& interface);
|
||||
void establishment_timeout(double timeout);
|
||||
void establishment_cost(uint16_t cost);
|
||||
void request_time(double time);
|
||||
void last_inbound(double time);
|
||||
void last_outbound(double time);
|
||||
void increment_tx();
|
||||
void increment_txbytes(uint16_t bytes);
|
||||
void status(Type::Link::status status);
|
||||
void mtu(uint16_t mtu);
|
||||
void mode(RNS::Type::Link::link_mode mode);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<LinkData> _object;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
135
lib/microReticulum/src/LinkData.h
Executable file
135
lib/microReticulum/src/LinkData.h
Executable file
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
//#include "LinkCallbacks.h"
|
||||
#include "Link.h"
|
||||
|
||||
#include "Resource.h"
|
||||
#include "Channel.h"
|
||||
#include "Interface.h"
|
||||
#include "Packet.h"
|
||||
#include "Destination.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
#include "Cryptography/Token.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class LinkData {
|
||||
public:
|
||||
LinkData(const Destination& destination) : _destination(destination) {
|
||||
MEM("LinkData object copy created");
|
||||
}
|
||||
virtual ~LinkData() {
|
||||
MEM("LinkData object destroyed");
|
||||
}
|
||||
private:
|
||||
Destination _destination;
|
||||
|
||||
// CBA LINK
|
||||
//Destination _link_destination = {Type::NONE};
|
||||
|
||||
Bytes _link_id;
|
||||
Bytes _hash;
|
||||
Type::Link::status _status = Type::Link::PENDING;
|
||||
|
||||
RNS::Type::Link::link_mode _mode = Link::MODE_DEFAULT;
|
||||
double _rtt = 0.0;
|
||||
uint16_t _mtu = RNS::Type::Reticulum::MTU;
|
||||
uint16_t _mdu = 0;
|
||||
uint16_t _establishment_cost = 0;
|
||||
Link::Callbacks _callbacks;
|
||||
Type::Link::resource_strategy _resource_strategy = Type::Link::ACCEPT_NONE;
|
||||
double _last_inbound = 0.0;
|
||||
double _last_outbound = 0.0;
|
||||
double _last_keepalive = 0.0;
|
||||
double _last_proof = 0.0;
|
||||
double _last_data = 0.0;
|
||||
uint16_t _tx = 0;
|
||||
uint16_t _rx = 0;
|
||||
uint16_t _txbytes = 0;
|
||||
uint16_t _rxbytes = 0;
|
||||
float _rssi = 0.0;
|
||||
float _snr = 0.0;
|
||||
float _q = 0.0;
|
||||
uint8_t _traffic_timeout_factor = Type::Link::TRAFFIC_TIMEOUT_FACTOR;
|
||||
uint16_t _keepalive_timeout_factor = Type::Link::KEEPALIVE_TIMEOUT_FACTOR;
|
||||
uint16_t _keepalive = Type::Link::KEEPALIVE;
|
||||
uint16_t _stale_time = Type::Link::STALE_TIME;
|
||||
bool _watchdog_lock = false;
|
||||
double _activated_at = 0.0;
|
||||
// CBA LINK
|
||||
//Type::Destination::types _type = Type::Destination::LINK;
|
||||
Destination _owner = {Type::NONE};
|
||||
bool _initiator = false;
|
||||
uint8_t _expected_hops = 0;
|
||||
Interface _attached_interface = {Type::NONE};
|
||||
Identity __remote_identity = {Type::NONE};
|
||||
Channel _channel = {Type::NONE};
|
||||
double _establishment_timeout = 0.0;
|
||||
Bytes _request_data;
|
||||
Packet _packet = {Type::NONE};
|
||||
double _request_time = 0.0;
|
||||
float _establishment_rate = 0.0;
|
||||
float _expected_rate = 0.0;
|
||||
Type::Link::teardown_reason _teardown_reason = Type::Link::TEARDOWN_NONE;
|
||||
|
||||
Cryptography::Token::Ptr _token;
|
||||
|
||||
Cryptography::X25519PrivateKey::Ptr _prv;
|
||||
Bytes _prv_bytes;
|
||||
|
||||
Cryptography::Ed25519PrivateKey::Ptr _sig_prv;
|
||||
Bytes _sig_prv_bytes;
|
||||
|
||||
Cryptography::X25519PublicKey::Ptr _pub;
|
||||
Bytes _pub_bytes;
|
||||
|
||||
Cryptography::Ed25519PublicKey::Ptr _sig_pub;
|
||||
Bytes _sig_pub_bytes;
|
||||
|
||||
Cryptography::X25519PublicKey::Ptr _peer_pub;
|
||||
Bytes _peer_pub_bytes;
|
||||
|
||||
Cryptography::Ed25519PublicKey::Ptr _peer_sig_pub;
|
||||
Bytes _peer_sig_pub_bytes;
|
||||
|
||||
Bytes _shared_key;
|
||||
Bytes _derived_key;
|
||||
|
||||
std::set<Resource> _incoming_resources;
|
||||
std::set<Resource> _outgoing_resources;
|
||||
std::set<RNS::RequestReceipt> _pending_requests;
|
||||
|
||||
friend class Link;
|
||||
};
|
||||
|
||||
class RequestReceiptData {
|
||||
public:
|
||||
RequestReceiptData() {}
|
||||
virtual ~RequestReceiptData() {}
|
||||
private:
|
||||
Bytes _hash;
|
||||
PacketReceipt _packet_receipt = {Type::NONE};
|
||||
Resource _resource = {Type::NONE};
|
||||
Link _link;
|
||||
double _started_at = 0.0;
|
||||
Bytes _request_id;
|
||||
int _request_size = 0;
|
||||
Bytes _response;
|
||||
size_t _response_transfer_size = 0;
|
||||
size_t _response_size = 0;
|
||||
Type::RequestReceipt::status _status = Type::RequestReceipt::SENT;
|
||||
double _sent_at = 0.0;
|
||||
int _progress = 0;
|
||||
double _concluded_at = 0.0;
|
||||
double _response_concluded_at = 0.0;
|
||||
double _timeout = 0.0;
|
||||
double _resource_response_timeout = 0.0;
|
||||
double ___resource_response_timeout = 0.0;
|
||||
RequestReceipt::Callbacks _callbacks;
|
||||
friend class RequestReceipt;
|
||||
};
|
||||
|
||||
}
|
||||
111
lib/microReticulum/src/Log.cpp
Executable file
111
lib/microReticulum/src/Log.cpp
Executable file
@@ -0,0 +1,111 @@
|
||||
#include "Log.h"
|
||||
|
||||
#include "Utilities/OS.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
//LogLevel _level = LOG_VERBOSE;
|
||||
LogLevel _level = LOG_TRACE;
|
||||
//LogLevel _level = LOG_MEM;
|
||||
RNS::log_callback _on_log = nullptr;
|
||||
char _datetime[20];
|
||||
|
||||
const char* RNS::getLevelName(LogLevel level) {
|
||||
switch (level) {
|
||||
case LOG_CRITICAL:
|
||||
return "!!!";
|
||||
case LOG_ERROR:
|
||||
return "ERR";
|
||||
case LOG_WARNING:
|
||||
return "WRN";
|
||||
case LOG_NOTICE:
|
||||
return "NOT";
|
||||
case LOG_INFO:
|
||||
return "INF";
|
||||
case LOG_VERBOSE:
|
||||
return "VRB";
|
||||
case LOG_DEBUG:
|
||||
return "DBG";
|
||||
case LOG_TRACE:
|
||||
return "---";
|
||||
case LOG_MEM:
|
||||
return "...";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* RNS::getTimeString() {
|
||||
#ifdef ARDUINO
|
||||
uint64_t time = Utilities::OS::ltime();
|
||||
if (time < 86400000) {
|
||||
snprintf(_datetime, sizeof(_datetime), "%02d:%02d:%02d.%03d", (int)(time/3600000), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
|
||||
}
|
||||
else {
|
||||
snprintf(_datetime, sizeof(_datetime), "%02d-%02d:%02d:%02d.%03d", (int)(time/86400000), (int)((time/3600000)%24), (int)((time/60000)%60), (int)((time/1000)%60), (int)(time%1000));
|
||||
}
|
||||
return _datetime;
|
||||
#else
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
int millis = lrint(tv.tv_usec/1000.0);
|
||||
if (millis >= 1000) {
|
||||
millis -= 1000;
|
||||
tv.tv_sec++;
|
||||
}
|
||||
struct tm* tm = localtime(&tv.tv_sec);
|
||||
size_t len = strftime(_datetime, sizeof(_datetime), "%Y-%m-%d %H:%M:%S", tm);
|
||||
snprintf(_datetime+len, sizeof(_datetime)-len, ".%03d", millis);
|
||||
return _datetime;
|
||||
#endif
|
||||
}
|
||||
|
||||
void RNS::loglevel(LogLevel level) {
|
||||
_level = level;
|
||||
}
|
||||
|
||||
LogLevel RNS::loglevel() {
|
||||
return _level;
|
||||
}
|
||||
|
||||
void RNS::setLogCallback(log_callback on_log /*= nullptr*/) {
|
||||
_on_log = on_log;
|
||||
}
|
||||
|
||||
void RNS::doLog(LogLevel level, const char* msg) {
|
||||
if (level > _level) {
|
||||
return;
|
||||
}
|
||||
if (_on_log != nullptr) {
|
||||
_on_log(msg, level);
|
||||
return;
|
||||
}
|
||||
#ifdef ARDUINO
|
||||
if (Serial) {
|
||||
Serial.print(getTimeString());
|
||||
Serial.print(" [");
|
||||
Serial.print(getLevelName(level));
|
||||
Serial.print("] ");
|
||||
Serial.println(msg);
|
||||
Serial.flush();
|
||||
}
|
||||
#else
|
||||
printf("%s [%s] %s\n", getTimeString(), getLevelName(level), msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void HEAD(const char* msg, LogLevel level) {
|
||||
if (level > _level) {
|
||||
return;
|
||||
}
|
||||
#ifdef ARDUINO
|
||||
Serial.println("");
|
||||
#else
|
||||
printf("\n");
|
||||
#endif
|
||||
doLog(level, msg);
|
||||
}
|
||||
124
lib/microReticulum/src/Log.h
Executable file
124
lib/microReticulum/src/Log.h
Executable file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define LOG(msg, level) (RNS::log(msg, level))
|
||||
#define LOGF(level, msg, ...) (RNS::logf(level, msg, __VA_ARGS__))
|
||||
#define HEAD(msg, level) (RNS::head(msg, level))
|
||||
#define HEADF(level, msg, ...) (RNS::headf(level, msg, __VA_ARGS__))
|
||||
#define CRITICAL(msg) (RNS::critical(msg))
|
||||
#define CRITICALF(msg, ...) (RNS::criticalf(msg, __VA_ARGS__))
|
||||
#define ERROR(msg) (RNS::error(msg))
|
||||
#define ERRORF(msg, ...) (RNS::errorf(msg, __VA_ARGS__))
|
||||
#define WARNING(msg) (RNS::warning(msg))
|
||||
#define WARNINGF(msg, ...) (RNS::warningf(msg, __VA_ARGS__))
|
||||
#define NOTICE(msg) (RNS::notice(msg))
|
||||
#define NOTICEF(msg, ...) (RNS::noticef(msg, __VA_ARGS__))
|
||||
#define INFO(msg) (RNS::info(msg))
|
||||
#define INFOF(msg, ...) (RNS::infof(msg, __VA_ARGS__))
|
||||
#define VERBOSE(msg) (RNS::verbose(msg))
|
||||
#define VERBOSEF(msg, ...) (RNS::verbosef(msg, __VA_ARGS__))
|
||||
#ifndef NDEBUG
|
||||
#define DEBUG(msg) (RNS::debug(msg))
|
||||
#define DEBUGF(msg, ...) (RNS::debugf(msg, __VA_ARGS__))
|
||||
#define TRACE(msg) (RNS::trace(msg))
|
||||
#define TRACEF(msg, ...) (RNS::tracef(msg, __VA_ARGS__))
|
||||
#if defined(RNS_MEM_LOG)
|
||||
#define MEM(msg) (RNS::mem(msg))
|
||||
#define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__))
|
||||
#else
|
||||
#define MEM(ignore) ((void)0)
|
||||
#define MEMF(...) ((void)0)
|
||||
#endif
|
||||
#else
|
||||
#define DEBUG(ignore) ((void)0)
|
||||
#define DEBUGF(...) ((void)0)
|
||||
#define TRACE(...) ((void)0)
|
||||
#define TRACEF(...) ((void)0)
|
||||
#define MEM(ignore) ((void)0)
|
||||
#define MEMF(...) ((void)0)
|
||||
#endif
|
||||
|
||||
namespace RNS {
|
||||
|
||||
enum LogLevel {
|
||||
LOG_NONE = 0,
|
||||
LOG_CRITICAL = 1,
|
||||
LOG_ERROR = 2,
|
||||
LOG_WARNING = 3,
|
||||
LOG_NOTICE = 4,
|
||||
LOG_INFO = 5,
|
||||
LOG_VERBOSE = 6,
|
||||
LOG_DEBUG = 7,
|
||||
LOG_TRACE = 8,
|
||||
LOG_MEM = 9
|
||||
};
|
||||
|
||||
using log_callback = void(*)(const char* msg, LogLevel level);
|
||||
|
||||
const char* getLevelName(LogLevel level);
|
||||
const char* getTimeString();
|
||||
|
||||
void loglevel(LogLevel level);
|
||||
LogLevel loglevel();
|
||||
|
||||
void setLogCallback(log_callback on_log = nullptr);
|
||||
|
||||
void doLog(LogLevel level, const char* msg);
|
||||
inline void doLog(LogLevel level, const char* msg, va_list vlist) { char buf[1024]; vsnprintf(buf, sizeof(buf), msg, vlist); doLog(level, buf); }
|
||||
|
||||
inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(level, msg); }
|
||||
#ifdef ARDUINO
|
||||
inline void log(const String& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
|
||||
inline void log(const StringSumHelper& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
|
||||
#endif
|
||||
inline void log(const std::string& msg, LogLevel level = LOG_NOTICE) { doLog(level, msg.c_str()); }
|
||||
inline void logf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
|
||||
|
||||
void head(const char* msg, LogLevel level = LOG_NOTICE);
|
||||
inline void head(const std::string& msg, LogLevel level = LOG_NOTICE) { head(msg.c_str(), level); }
|
||||
inline void headf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void critical(const char* msg) { doLog(LOG_CRITICAL, msg); }
|
||||
inline void critical(const std::string& msg) { doLog(LOG_CRITICAL, msg.c_str()); }
|
||||
inline void criticalf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_CRITICAL, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void error(const char* msg) { doLog(LOG_ERROR, msg); }
|
||||
inline void error(const std::string& msg) { doLog(LOG_ERROR, msg.c_str()); }
|
||||
inline void errorf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_ERROR, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void warning(const char* msg) { doLog(LOG_WARNING, msg); }
|
||||
inline void warning(const std::string& msg) { doLog(LOG_WARNING, msg.c_str()); }
|
||||
inline void warningf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_WARNING, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void notice(const char* msg) { doLog(LOG_NOTICE, msg); }
|
||||
inline void notice(const std::string& msg) { doLog(LOG_NOTICE, msg.c_str()); }
|
||||
inline void noticef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_NOTICE, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void info(const char* msg) { doLog(LOG_INFO, msg); }
|
||||
inline void info(const std::string& msg) { doLog(LOG_INFO, msg.c_str()); }
|
||||
inline void infof(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_INFO, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void verbose(const char* msg) { doLog(LOG_VERBOSE, msg); }
|
||||
inline void verbose(const std::string& msg) { doLog(LOG_VERBOSE, msg.c_str()); }
|
||||
inline void verbosef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_VERBOSE, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void debug(const char* msg) { doLog(LOG_DEBUG, msg); }
|
||||
inline void debug(const std::string& msg) { doLog(LOG_DEBUG, msg.c_str()); }
|
||||
inline void debugf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_DEBUG, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void trace(const char* msg) { doLog(LOG_TRACE, msg); }
|
||||
inline void trace(const std::string& msg) { doLog(LOG_TRACE, msg.c_str()); }
|
||||
inline void tracef(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_TRACE, msg, vlist); va_end(vlist); }
|
||||
|
||||
inline void mem(const char* msg) { doLog(LOG_MEM, msg); }
|
||||
inline void mem(const std::string& msg) { doLog(LOG_MEM, msg.c_str()); }
|
||||
inline void memf(const char* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(LOG_MEM, msg, vlist); va_end(vlist); }
|
||||
|
||||
}
|
||||
995
lib/microReticulum/src/Packet.cpp
Executable file
995
lib/microReticulum/src/Packet.cpp
Executable file
@@ -0,0 +1,995 @@
|
||||
#include "Packet.h"
|
||||
|
||||
#include "Reticulum.h"
|
||||
#include "Transport.h"
|
||||
#include "Identity.h"
|
||||
#include "Log.h"
|
||||
#include "Utilities/OS.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::PacketReceipt;
|
||||
using namespace RNS::Type::Packet;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
ProofDestination::ProofDestination(const Packet& packet) : Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::SINGLE, packet.get_hash().left(Type::Reticulum::TRUNCATED_HASHLENGTH/8))
|
||||
{
|
||||
}
|
||||
|
||||
Packet::Packet(const Destination& destination, const Interface& attached_interface, const Bytes& data, types packet_type /*= DATA*/, context_types context /*= CONTEXT_NONE*/, Type::Transport::types transport_type /*= Type::Transport::BROADCAST*/, header_types header_type /*= HEADER_1*/, const Bytes& transport_id /*= {Bytes::NONE}*/, bool create_receipt /*= true*/, context_flags context_flag /*= FLAG_UNSET*/) :
|
||||
_object(new Object(destination, attached_interface))
|
||||
{
|
||||
|
||||
if (_object->_destination) {
|
||||
TRACE("Creating packet with destination...");
|
||||
// CBA Should never see empty transport_type
|
||||
//if (transport_type == NONE) {
|
||||
// transport_type = Type::Transport::BROADCAST;
|
||||
//}
|
||||
// following moved to object constructor to avoid extra NONE object
|
||||
//_destination = destination;
|
||||
_object->_header_type = header_type;
|
||||
_object->_packet_type = packet_type;
|
||||
_object->_transport_type = transport_type;
|
||||
_object->_context = context;
|
||||
_object->_context_flag = context_flag;
|
||||
|
||||
_object->_transport_id = transport_id;
|
||||
_object->_data = data;
|
||||
if (_object->_data.size() > MDU) {
|
||||
_object->_truncated = true;
|
||||
_object->_data.resize(MDU);
|
||||
}
|
||||
_object->_flags = get_packed_flags();
|
||||
|
||||
_object->_create_receipt = create_receipt;
|
||||
}
|
||||
else {
|
||||
TRACE("Creating packet without destination...");
|
||||
// CBA NOTE: This variant is for creating a new packet from a received raw buffer
|
||||
_object->_raw = data;
|
||||
_object->_packed = true;
|
||||
_object->_fromPacked = true;
|
||||
_object->_create_receipt = false;
|
||||
}
|
||||
|
||||
MEM("Packet object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
// CBA LINK
|
||||
Packet::Packet(const Link& link, const Bytes& data, Type::Packet::types packet_type /*= Type::Packet::DATA*/, Type::Packet::context_types context /*= Type::Packet::CONTEXT_NONE*/, context_flags context_flag /*= FLAG_UNSET*/) :
|
||||
//_object(new Object(link))
|
||||
//Packet(link.destination(), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
|
||||
// CBA Must use a destination that targets the Link itself instead of the original destination used to create the link
|
||||
Packet(Destination({Type::NONE}, Type::Destination::OUT, Type::Destination::LINK, link.hash()), data, packet_type, context, Type::Transport::BROADCAST, Type::Packet::HEADER_1, {Bytes::NONE}, true, context_flag)
|
||||
{
|
||||
TRACE("Creating packet with link...");
|
||||
_object->_destination_link = link;
|
||||
_object->_MTU = link.mtu();
|
||||
// CBA HACK: Need to re-build packed flags since Link was assigned
|
||||
_object->_flags = get_packed_flags();
|
||||
MEM("Packet link object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
|
||||
uint8_t Packet::get_packed_flags() {
|
||||
assert(_object);
|
||||
uint8_t packed_flags = 0;
|
||||
if (_object->_context == LRPROOF) {
|
||||
TRACE("***** Packing with LINK type");
|
||||
packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (Type::Destination::LINK << 2) | _object->_packet_type;
|
||||
}
|
||||
else {
|
||||
if (!_object->_destination) {
|
||||
throw std::logic_error("Packet destination is required");
|
||||
}
|
||||
TRACEF("***** Packing with %d type", _object->_destination.type());
|
||||
packed_flags = (_object->_header_type << 6) | (_object->_context_flag << 5) | (_object->_transport_type << 4) | (_object->_destination.type() << 2) | _object->_packet_type;
|
||||
}
|
||||
return packed_flags;
|
||||
}
|
||||
|
||||
void Packet::unpack_flags(uint8_t flags) {
|
||||
assert(_object);
|
||||
_object->_header_type = static_cast<header_types>((flags & 0b01000000) >> 6);
|
||||
_object->_context_flag = static_cast<Type::Packet::context_flags>((flags & 0b00100000) >> 5);
|
||||
_object->_transport_type = static_cast<Type::Transport::types>((flags & 0b00010000) >> 4);
|
||||
_object->_destination_type = static_cast<Type::Destination::types>((flags & 0b00001100) >> 2);
|
||||
_object->_packet_type = static_cast<types>(flags & 0b00000011);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
== Reticulum Wire Format ======
|
||||
|
||||
A Reticulum packet is composed of the following fields:
|
||||
|
||||
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
|
||||
|
||||
* The HEADER field is 2 bytes long.
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
|
||||
* Byte 2: Number of hops
|
||||
|
||||
* Interface Access Code field if the IFAC flag was set.
|
||||
* The length of the Interface Access Code can vary from
|
||||
1 to 64 bytes according to physical interface
|
||||
capabilities and configuration.
|
||||
|
||||
* The ADDRESSES field contains either 1 or 2 addresses.
|
||||
* Each address is 16 bytes long.
|
||||
* The Header Type flag in the HEADER field determines
|
||||
whether the ADDRESSES field contains 1 or 2 addresses.
|
||||
* Addresses are SHA-256 hashes truncated to 16 bytes.
|
||||
|
||||
* The CONTEXT field is 1 byte.
|
||||
* It is used by Reticulum to determine packet context.
|
||||
|
||||
* The DATA field is between 0 and 465 bytes.
|
||||
* It contains the packets data payload.
|
||||
|
||||
IFAC Flag
|
||||
-----------------
|
||||
open 0 Packet for publically accessible interface
|
||||
authenticated 1 Interface authentication is included in packet
|
||||
|
||||
|
||||
Header Types
|
||||
-----------------
|
||||
type 1 0 Two byte header, one 16 byte address field
|
||||
type 2 1 Two byte header, two 16 byte address fields
|
||||
|
||||
|
||||
Propagation Types
|
||||
-----------------
|
||||
broadcast 00
|
||||
transport 01
|
||||
reserved 10
|
||||
reserved 11
|
||||
|
||||
|
||||
Destination Types
|
||||
-----------------
|
||||
single 00
|
||||
group 01
|
||||
plain 10
|
||||
link 11
|
||||
|
||||
|
||||
Packet Types
|
||||
-----------------
|
||||
data 00
|
||||
announce 01
|
||||
link request 10
|
||||
proof 11
|
||||
|
||||
|
||||
+- Packet Example -+
|
||||
|
||||
HEADER FIELD DESTINATION FIELDS CONTEXT FIELD DATA FIELD
|
||||
_______|_______ ________________|________________ ________|______ __|_
|
||||
| | | | | | | |
|
||||
01010000 00000100 [HASH1, 16 bytes] [HASH2, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 4
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = TRANSPORT
|
||||
|+------------- Header Type = HEADER_2 (two byte header, two address fields)
|
||||
+-------------- Access Codes = DISABLED
|
||||
|
||||
|
||||
+- Packet Example -+
|
||||
|
||||
HEADER FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
|
||||
_______|_______ _______|_______ ________|______ __|_
|
||||
| | | | | | | |
|
||||
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
|+------------- Header Type = HEADER_1 (two byte header, one address field)
|
||||
+-------------- Access Codes = DISABLED
|
||||
|
||||
|
||||
+- Packet Example -+
|
||||
|
||||
HEADER FIELD IFAC FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
|
||||
_______|_______ ______|______ _______|_______ ________|______ __|_
|
||||
| | | | | | | | | |
|
||||
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|
||||
|| | | | |
|
||||
|| | | | +-- Hops = 7
|
||||
|| | | +------- Packet Type = DATA
|
||||
|| | +--------- Destination Type = SINGLE
|
||||
|| +----------- Propagation Type = BROADCAST
|
||||
|+------------- Header Type = HEADER_1 (two byte header, one address field)
|
||||
+-------------- Access Codes = ENABLED
|
||||
|
||||
|
||||
Size examples of different packet types
|
||||
---------------------------------------
|
||||
|
||||
The following table lists example sizes of various
|
||||
packet types. The size listed are the complete on-
|
||||
wire size counting all fields including headers,
|
||||
but excluding any interface access codes.
|
||||
|
||||
- Path Request : 51 bytes
|
||||
- Announce : 167 bytes
|
||||
- Link Request : 83 bytes
|
||||
- Link Proof : 115 bytes
|
||||
- Link RTT packet : 99 bytes
|
||||
- Link keepalive : 20 bytes
|
||||
*/
|
||||
|
||||
|
||||
// Reticulum Packet Structure
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |I|H|PRT|DT |PT | hops | destination... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination | context | data ... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |I|H|PRT|DT |PT | hops | destination_1... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_1... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_1... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_1... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_1 | destination_2... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_2... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_2... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_2... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ...destination_2 | context | data ... |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
void Packet::pack() {
|
||||
assert(_object);
|
||||
TRACE("Packet::pack: packing packet...");
|
||||
|
||||
if (!_object->_destination) {
|
||||
throw std::logic_error("Packet destination is required");
|
||||
}
|
||||
_object->_destination_hash = _object->_destination.hash();
|
||||
|
||||
_object->_header.clear();
|
||||
_object->_encrypted = false;
|
||||
|
||||
_object->_header << _object->_flags;
|
||||
_object->_header << _object->_hops;
|
||||
|
||||
// CBA LINK
|
||||
if (_object->_context == LRPROOF) {
|
||||
if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
|
||||
TRACE("Packet::pack: destination link id: " + _object->_destination_link.link_id().toHex() );
|
||||
_object->_header << _object->_destination_link.link_id();
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else {
|
||||
if (_object->_header_type == HEADER_1) {
|
||||
TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
|
||||
_object->_header << _object->_destination.hash();
|
||||
|
||||
if (_object->_packet_type == ANNOUNCE) {
|
||||
// Announce packets are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else if (_object->_packet_type == LINKREQUEST) {
|
||||
// Link request packets are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else if (_object->_packet_type == PROOF && _object->_context == RESOURCE_PRF) {
|
||||
// Resource proofs are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
// CBA LINK
|
||||
//p elif self.packet_type == Packet.PROOF and self.destination.type == RNS.Destination.LINK:
|
||||
else if (_object->_packet_type == PROOF && _object->_destination.type() == Type::Destination::LINK) {
|
||||
// Packet proofs over links are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else if (_object->_context == RESOURCE) {
|
||||
// A resource takes care of encryption
|
||||
// by itself
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else if (_object->_context == KEEPALIVE) {
|
||||
// Keepalive packets contain no actual
|
||||
// data
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else if (_object->_context == CACHE_REQUEST) {
|
||||
// Cache-requests are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
else {
|
||||
// In all other cases, we encrypt the packet
|
||||
// with the destination's encryption method
|
||||
// CBA LINK
|
||||
if (_object->_destination_link) {
|
||||
_object->_ciphertext = _object->_destination_link.encrypt(_object->_data);
|
||||
TRACEF("***** Link data: %s", _object->_ciphertext.toHex().c_str());
|
||||
}
|
||||
else {
|
||||
_object->_ciphertext = _object->_destination.encrypt(_object->_data);
|
||||
TRACEF("***** Destination Data: %s", _object->_ciphertext.toHex().c_str());
|
||||
}
|
||||
// CBA RATCHET
|
||||
/*p TODO
|
||||
if hasattr(self.destination, "latest_ratchet_id"):
|
||||
self.ratchet_id = self.destination.latest_ratchet_id
|
||||
*/
|
||||
_object->_encrypted = true;
|
||||
}
|
||||
}
|
||||
else if (_object->_header_type == HEADER_2) {
|
||||
if (!_object->_transport_id) {
|
||||
throw std::invalid_argument("Packet with header type 2 must have a transport ID");
|
||||
}
|
||||
TRACE("Packet::pack: transport id: " + _object->_transport_id.toHex() );
|
||||
TRACE("Packet::pack: destination hash: " + _object->_destination.hash().toHex() );
|
||||
_object->_header << _object->_transport_id;
|
||||
_object->_header << _object->_destination.hash();
|
||||
|
||||
if (_object->_packet_type == ANNOUNCE) {
|
||||
// Announce packets are not encrypted
|
||||
_object->_ciphertext = _object->_data;
|
||||
}
|
||||
// CBA No default encryption here like with header type HEADER_1 ???
|
||||
// CBA Is there any packet type besides ANNOUNCE with header type HEADER_2 ???
|
||||
// CBA Safe to assume that all HEADER_2 type packets are in transport and therefore can not and will not be decrypted locally ???
|
||||
}
|
||||
}
|
||||
|
||||
_object->_header << (uint8_t)_object->_context;
|
||||
_object->_raw = _object->_header + _object->_ciphertext;
|
||||
|
||||
if (_object->_raw.size() > _object->_MTU) {
|
||||
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " exceeds MTU of " + std::to_string(_object->_MTU) +" bytes");
|
||||
}
|
||||
|
||||
_object->_packed = true;
|
||||
update_hash();
|
||||
}
|
||||
|
||||
bool Packet::unpack() {
|
||||
assert(_object);
|
||||
TRACE("Packet::unpack: unpacking packet...");
|
||||
try {
|
||||
if (_object->_raw.size() < Type::Reticulum::HEADER_MINSIZE) {
|
||||
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MINSIZE) +" bytes");
|
||||
}
|
||||
|
||||
const uint8_t* raw = _object->_raw.data();
|
||||
|
||||
// read header
|
||||
_object->_flags = raw[0];
|
||||
_object->_hops = raw[1];
|
||||
|
||||
unpack_flags(_object->_flags);
|
||||
|
||||
// CBA TODO detect invalid flags and throw error
|
||||
if (false) {
|
||||
log("Received malformed packet, dropping it.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_object->_header_type == HEADER_2) {
|
||||
if (_object->_raw.size() < Type::Reticulum::HEADER_MAXSIZE) {
|
||||
throw std::length_error("Packet size of " + std::to_string(_object->_raw.size()) + " does not meet minimum header size of " + std::to_string(Type::Reticulum::HEADER_MAXSIZE) +" bytes");
|
||||
}
|
||||
_object->_transport_id.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
|
||||
_object->_destination_hash.assign(raw+Type::Reticulum::DESTINATION_LENGTH+2, Type::Reticulum::DESTINATION_LENGTH);
|
||||
_object->_context = static_cast<context_types>(raw[2*Type::Reticulum::DESTINATION_LENGTH+2]);
|
||||
_object->_data.assign(raw+2*Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(2*Type::Reticulum::DESTINATION_LENGTH+3));
|
||||
// uknown at this point whether data is encrypted or not
|
||||
_object->_encrypted = false;
|
||||
}
|
||||
else {
|
||||
_object->_transport_id.clear();
|
||||
_object->_destination_hash.assign(raw+2, Type::Reticulum::DESTINATION_LENGTH);
|
||||
_object->_context = static_cast<context_types>(raw[Type::Reticulum::DESTINATION_LENGTH+2]);
|
||||
_object->_data.assign(raw+Type::Reticulum::DESTINATION_LENGTH+3, _object->_raw.size()-(Type::Reticulum::DESTINATION_LENGTH+3));
|
||||
// uknown at this point whether data is encrypted or not
|
||||
_object->_encrypted = false;
|
||||
}
|
||||
|
||||
_object->_packed = false;
|
||||
update_hash();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR(std::string("Received malformed packet, dropping it. The contained exception was: ") + e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Sends the packet.
|
||||
|
||||
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
|
||||
*/
|
||||
PacketReceipt Packet::send() {
|
||||
assert(_object);
|
||||
TRACE("Packet::send: sending packet...");
|
||||
if (_object->_sent) {
|
||||
throw std::logic_error("Packet was already sent");
|
||||
}
|
||||
// CBA LINK
|
||||
//p if self.destination.type == RNS.Destination.LINK:
|
||||
if (_object->_destination.type() == Type::Destination::LINK) {
|
||||
if (!_object->_destination_link) throw std::invalid_argument("Packet is not associated with a Link");
|
||||
if (_object->_destination_link.status() == Type::Link::CLOSED) {
|
||||
throw std::runtime_error("Attempt to transmit over a closed link");
|
||||
}
|
||||
else {
|
||||
_object->_destination_link.last_outbound(OS::time());
|
||||
_object->_destination_link.increment_tx();
|
||||
_object->_destination_link.increment_txbytes(_object->_data.size());
|
||||
}
|
||||
}
|
||||
|
||||
if (!_object->_packed) {
|
||||
pack();
|
||||
}
|
||||
|
||||
if (Transport::outbound(*this)) {
|
||||
TRACE("Packet::send: successfully sent packet!!!");
|
||||
//p return self.receipt
|
||||
return _object->_receipt;
|
||||
}
|
||||
else {
|
||||
ERROR("No interfaces could process the outbound packet");
|
||||
_object->_sent = false;
|
||||
_object->_receipt = {Type::NONE};
|
||||
//p return False
|
||||
return {Type::NONE};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Re-sends the packet.
|
||||
|
||||
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
|
||||
*/
|
||||
bool Packet::resend() {
|
||||
assert(_object);
|
||||
TRACE("Packet::resend: re-sending packet...");
|
||||
if (!_object->_sent) {
|
||||
throw std::logic_error("Packet was not sent yet");
|
||||
}
|
||||
// Re-pack the packet to obtain new ciphertext for
|
||||
// encrypted destinations
|
||||
pack();
|
||||
|
||||
if (Transport::outbound(*this)) {
|
||||
TRACE("Packet::resend: successfully sent packet!!!");
|
||||
//z return self.receipt
|
||||
// MOCK
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ERROR("No interfaces could process the outbound packet");
|
||||
_object->_sent = false;
|
||||
//z self.receipt = None;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Packet::prove(const Destination& destination /*= {Type::NONE}*/) {
|
||||
assert(_object);
|
||||
TRACE("Packet::prove: proving packet...");
|
||||
// CBA LINK
|
||||
// CBA TODO: Determine under which circumstances to use _destination and which to use _link since it's unclear from this logic
|
||||
//p if self.fromPacked and hasattr(self, "destination") and self.destination:
|
||||
if (_object->_fromPacked && _object->_destination) {
|
||||
if (_object->_destination.identity() && _object->_destination.identity().prv()) {
|
||||
_object->_destination.identity().prove(*this, destination);
|
||||
}
|
||||
}
|
||||
//p elif self.fromPacked and hasattr(self, "link") and self.link:
|
||||
else if (_object->_fromPacked && _object->_link) {
|
||||
_object->_link.prove_packet(*this);
|
||||
}
|
||||
else {
|
||||
ERROR("Could not prove packet associated with neither a destination nor a link");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Generates a special destination that allows Reticulum
|
||||
// to direct the proof back to the proved packet's sender
|
||||
ProofDestination Packet::generate_proof_destination() const {
|
||||
return ProofDestination(*this);
|
||||
}
|
||||
|
||||
bool Packet::validate_proof_packet(const Packet& proof_packet) {
|
||||
assert(_object);
|
||||
if (!_object->_receipt) {
|
||||
return false;
|
||||
}
|
||||
return _object->_receipt.validate_proof_packet(proof_packet);
|
||||
}
|
||||
|
||||
bool Packet::validate_proof(const Bytes& proof) {
|
||||
assert(_object);
|
||||
if (!_object->_receipt) {
|
||||
return false;
|
||||
}
|
||||
return _object->_receipt.validate_proof(proof);
|
||||
}
|
||||
|
||||
void Packet::update_hash() {
|
||||
assert(_object);
|
||||
_object->_packet_hash = get_hash();
|
||||
}
|
||||
|
||||
const Bytes Packet::get_hash() const {
|
||||
assert(_object);
|
||||
Bytes hashable_part = get_hashable_part();
|
||||
// CBA MCU SHORTER HASH
|
||||
return Identity::full_hash(hashable_part);
|
||||
//return Identity::truncated_hash(hashable_part);
|
||||
}
|
||||
|
||||
const Bytes Packet::getTruncatedHash() const {
|
||||
assert(_object);
|
||||
Bytes hashable_part = get_hashable_part();
|
||||
return Identity::truncated_hash(hashable_part);
|
||||
}
|
||||
|
||||
const Bytes Packet::get_hashable_part() const {
|
||||
assert(_object);
|
||||
Bytes hashable_part;
|
||||
hashable_part << (uint8_t)(_object->_raw.data()[0] & 0b00001111);
|
||||
if (_object->_header_type == HEADER_2) {
|
||||
//p hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
||||
hashable_part << _object->_raw.mid((Type::Identity::TRUNCATED_HASHLENGTH/8)+2);
|
||||
}
|
||||
else {
|
||||
//p hashable_part += self.raw[2:];
|
||||
hashable_part << _object->_raw.mid(2);
|
||||
}
|
||||
return hashable_part;
|
||||
}
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string Packet::debugString() const {
|
||||
if (!_object) {
|
||||
return "";
|
||||
}
|
||||
if (_object->_packed) {
|
||||
//unpack();
|
||||
}
|
||||
std::string str = "ph=" + _object->_packet_hash.toHex();
|
||||
str += " ht=" + std::to_string(_object->_header_type);
|
||||
str += " tt=" + std::to_string(_object->_transport_type);
|
||||
str += " dt=" + std::to_string(_object->_destination_type);
|
||||
str += " pt=" + std::to_string(_object->_packet_type);
|
||||
str += " hp=" + std::to_string(_object->_hops);
|
||||
str += " ti=" + _object->_transport_id.toHex();
|
||||
str += " dh=" + _object->_destination_hash.toHex();
|
||||
return str;
|
||||
}
|
||||
std::string Packet::dumpString() const {
|
||||
if (!_object) {
|
||||
return "";
|
||||
}
|
||||
//if (_object->_packed) {
|
||||
// unpack();
|
||||
//}
|
||||
bool encrypted = true;
|
||||
std::string dump;
|
||||
dump = "\n------------------------------------------------------------------------------\n";
|
||||
dump += "hash: " + _object->_packet_hash.toHex() + "\n";
|
||||
dump += "flags: " + hexFromByte(_object->_flags) + "\n";
|
||||
//dump += " header_type: " + std::to_string(_object->_header_type) + "\n";
|
||||
dump += " header_type: ";
|
||||
switch (_object->_header_type) {
|
||||
case HEADER_1:
|
||||
dump += "HEADER_1\n";
|
||||
break;
|
||||
case HEADER_2:
|
||||
encrypted = false;
|
||||
dump += "HEADER_2\n";
|
||||
break;
|
||||
default:
|
||||
std::to_string(_object->_header_type) + "\n";
|
||||
}
|
||||
//dump += " transport_type: " + std::to_string(_object->_transport_type) + "\n";
|
||||
dump += " transport_type: ";
|
||||
switch (_object->_transport_type) {
|
||||
case Type::Transport::BROADCAST:
|
||||
dump += "BROADCAST\n";
|
||||
break;
|
||||
case Type::Transport::TRANSPORT:
|
||||
dump += "TRANSPORT\n";
|
||||
break;
|
||||
case Type::Transport::RELAY:
|
||||
dump += "RELAY\n";
|
||||
break;
|
||||
case Type::Transport::TUNNEL:
|
||||
dump += "TUNNEL\n";
|
||||
break;
|
||||
case Type::Transport::NONE:
|
||||
dump += "NONE\n";
|
||||
break;
|
||||
default:
|
||||
std::to_string(_object->_transport_type) + "\n";
|
||||
}
|
||||
//dump += " destination_type: " + std::to_string(_object->_destination_type) + "\n";
|
||||
dump += " destination_type: ";
|
||||
switch (_object->_destination_type) {
|
||||
case Type::Destination::SINGLE:
|
||||
dump += "SINGLE\n";
|
||||
break;
|
||||
case Type::Destination::GROUP:
|
||||
dump += "GROUP\n";
|
||||
break;
|
||||
case Type::Destination::PLAIN:
|
||||
dump += "PLAIN\n";
|
||||
break;
|
||||
case Type::Destination::LINK:
|
||||
dump += "LINK\n";
|
||||
break;
|
||||
default:
|
||||
std::to_string(_object->_destination_type) + "\n";
|
||||
}
|
||||
//dump += " packet_type: " + std::to_string(_object->_packet_type) + "\n";
|
||||
dump += " packet_type: ";
|
||||
switch (_object->_packet_type) {
|
||||
case DATA:
|
||||
dump += "DATA\n";
|
||||
break;
|
||||
case ANNOUNCE:
|
||||
encrypted = false;
|
||||
dump += "ANNOUNCE\n";
|
||||
break;
|
||||
case LINKREQUEST:
|
||||
encrypted = false;
|
||||
dump += "LINKREQUEST\n";
|
||||
break;
|
||||
case PROOF:
|
||||
dump += "PROOF\n";
|
||||
if (_object->_context == RESOURCE_PRF) {
|
||||
encrypted = false;
|
||||
}
|
||||
// CBA LINK
|
||||
if (_object->_destination && _object->_destination.type() == Type::Destination::LINK) {
|
||||
encrypted = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::to_string(_object->_packet_type) + "\n";
|
||||
}
|
||||
dump += "hops: " + std::to_string(_object->_hops) + "\n";
|
||||
dump += "transport: " + _object->_transport_id.toHex() + "\n";
|
||||
dump += "destination: " + _object->_destination_hash.toHex() + "\n";
|
||||
//dump += "context: " + std::to_string(_object->_context) + "\n";
|
||||
dump += "context: ";
|
||||
switch (_object->_context) {
|
||||
case CONTEXT_NONE:
|
||||
dump += "CONTEXT_NONE\n";
|
||||
break;
|
||||
case RESOURCE:
|
||||
encrypted = false;
|
||||
dump += "RESOURCE\n";
|
||||
break;
|
||||
case RESOURCE_ADV:
|
||||
dump += "RESOURCE_ADV\n";
|
||||
break;
|
||||
case RESOURCE_REQ:
|
||||
dump += "RESOURCE_REQ\n";
|
||||
break;
|
||||
case RESOURCE_HMU:
|
||||
dump += "RESOURCE_HMU\n";
|
||||
break;
|
||||
case RESOURCE_PRF:
|
||||
encrypted = false;
|
||||
dump += "RESOURCE_PRF\n";
|
||||
break;
|
||||
case RESOURCE_ICL:
|
||||
dump += "RESOURCE_ICL\n";
|
||||
break;
|
||||
case RESOURCE_RCL:
|
||||
dump += "RESOURCE_RCL\n";
|
||||
break;
|
||||
case CACHE_REQUEST:
|
||||
encrypted = false;
|
||||
dump += "CACHE_REQUEST\n";
|
||||
break;
|
||||
case REQUEST:
|
||||
dump += "REQUEST\n";
|
||||
break;
|
||||
case RESPONSE:
|
||||
dump += "RESPONSE\n";
|
||||
break;
|
||||
case PATH_RESPONSE:
|
||||
dump += "PATH_RESPONSE\n";
|
||||
break;
|
||||
case COMMAND:
|
||||
dump += "COMMAND\n";
|
||||
break;
|
||||
case COMMAND_STATUS:
|
||||
dump += "COMMAND_STATUS\n";
|
||||
break;
|
||||
case CHANNEL:
|
||||
dump += "CHANNEL\n";
|
||||
break;
|
||||
case KEEPALIVE:
|
||||
encrypted = false;
|
||||
dump += "KEEPALIVE\n";
|
||||
break;
|
||||
case LINKIDENTIFY:
|
||||
dump += "LINKIDENTIFY\n";
|
||||
break;
|
||||
case LINKCLOSE:
|
||||
dump += "LINKCLOSE\n";
|
||||
break;
|
||||
case LINKPROOF:
|
||||
dump += "LINKPROOF\n";
|
||||
break;
|
||||
case LRRTT:
|
||||
dump += "LRRTT\n";
|
||||
break;
|
||||
case LRPROOF:
|
||||
encrypted = false;
|
||||
dump += "LRPROOF\n";
|
||||
break;
|
||||
default:
|
||||
std::to_string(_object->_context) + "\n";
|
||||
}
|
||||
dump += "raw: " + _object->_raw.toHex() + "\n";
|
||||
dump += " length: " + std::to_string(_object->_raw.size()) + "\n";
|
||||
dump += "data: " + _object->_data.toHex() + "\n";
|
||||
dump += " length: " + std::to_string(_object->_data.size()) + "\n";
|
||||
//if ((encrypted || _object->_encrypted) && _object->_raw.size() > 0) {
|
||||
if (false) {
|
||||
size_t header_len = Type::Reticulum::HEADER_MINSIZE;
|
||||
if (_object->_header_type == HEADER_2) {
|
||||
header_len = Type::Reticulum::HEADER_MAXSIZE;
|
||||
}
|
||||
dump += "encrypted:\n";
|
||||
dump += " header: " + _object->_raw.left(header_len).toHex() + "\n";
|
||||
dump += " key: " + _object->_raw.mid(header_len, Type::Identity::KEYSIZE/8/2).toHex() + "\n";
|
||||
Bytes ciphertext(_object->_raw.mid(header_len+Type::Identity::KEYSIZE/8/2));
|
||||
dump += " ciphertext: " + ciphertext.toHex() + "\n";
|
||||
dump += " length: " + std::to_string(ciphertext.size()) + "\n";
|
||||
dump += " iv: " + ciphertext.left(16).toHex() + "\n";
|
||||
dump += " sig: " + ciphertext.right(32).toHex() + "\n";
|
||||
if (ciphertext.size() >= 48) {
|
||||
dump += " aes ciphertext: " + ciphertext.mid(16, ciphertext.size()-48).toHex() + "\n";
|
||||
dump += " length: " + std::to_string(ciphertext.size()-48) + "\n";
|
||||
}
|
||||
}
|
||||
dump += "------------------------------------------------------------------------------\n";
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
PacketReceipt::PacketReceipt(const Packet& packet) : _object(new Object()) {
|
||||
|
||||
if (!packet.destination()) {
|
||||
throw std::invalid_argument("Packet with destination is required");
|
||||
}
|
||||
_object->_hash = packet.get_hash();
|
||||
_object->_truncated_hash = packet.getTruncatedHash();
|
||||
_object->_destination = packet.destination();
|
||||
|
||||
// CBA LINK
|
||||
if (packet.destination().type() == Type::Destination::LINK) {
|
||||
if (!packet.destination_link()) throw std::invalid_argument("Packet is not associated with a Link");
|
||||
//p self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
|
||||
_object->_timeout = std::max(packet.destination_link().rtt() * packet.destination_link().traffic_timeout_factor(), (double)RNS::Type::Link::TRAFFIC_TIMEOUT_MIN_MS/1000);
|
||||
}
|
||||
else {
|
||||
//p self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
|
||||
//p self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||
_object->_timeout = RNS::Reticulum::get_instance().get_first_hop_timeout(_object->_destination.hash());
|
||||
_object->_timeout += TIMEOUT_PER_HOP * Transport::hops_to(_object->_destination.hash());
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a proof packet
|
||||
bool PacketReceipt::validate_proof_packet(const Packet& proof_packet) {
|
||||
if (proof_packet.link()) {
|
||||
return validate_link_proof(proof_packet.data(), proof_packet.link(), proof_packet);
|
||||
}
|
||||
else {
|
||||
return validate_proof(proof_packet.data(), proof_packet);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a raw proof for a link
|
||||
//bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet /*= {Type::NONE}*/) {
|
||||
bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link) {
|
||||
return validate_link_proof(proof, link, {Type::NONE});
|
||||
}
|
||||
bool PacketReceipt::validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet) {
|
||||
assert(_object);
|
||||
TRACE("PacketReceipt::validate_link_proof: validating link proof...");
|
||||
// TODO: Hardcoded as explicit proofs for now
|
||||
if (true || proof.size() == EXPL_LENGTH) {
|
||||
// This is an explicit proof
|
||||
Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
|
||||
Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
|
||||
if (proof_hash == _object->_hash) {
|
||||
//z if (link.validate(signature, _object->_hash)) {
|
||||
if (false) {
|
||||
_object->_status = DELIVERED;
|
||||
_object->_proved = true;
|
||||
_object->_concluded_at = OS::time();
|
||||
//z _object->_proof_packet = proof_packet;
|
||||
//z link.last_proof(_object->_concluded_at);
|
||||
|
||||
if (_object->_callbacks._delivery) {
|
||||
try {
|
||||
_object->_callbacks._delivery(*this);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("An error occurred while evaluating external delivery callback for " + link.toString());
|
||||
ERROR("The contained exception was: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (proof.size() == IMPL_LENGTH) {
|
||||
// TODO: Why is this disabled?
|
||||
// signature = proof[:RNS.Identity.SIGLENGTH//8]
|
||||
// proof_valid = self.link.validate(signature, self.hash)
|
||||
// if proof_valid:
|
||||
// self.status = PacketReceipt.DELIVERED
|
||||
// self.proved = True
|
||||
// self.concluded_at = time.time()
|
||||
// if self.callbacks.delivery != None:
|
||||
// self.callbacks.delivery(self)
|
||||
// RNS.log("valid")
|
||||
// return True
|
||||
// else:
|
||||
// RNS.log("invalid")
|
||||
// return False
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate a raw proof
|
||||
//bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet /*= {Type::NONE}*/) {
|
||||
bool PacketReceipt::validate_proof(const Bytes& proof) {
|
||||
return validate_proof(proof, {Type::NONE});
|
||||
}
|
||||
bool PacketReceipt::validate_proof(const Bytes& proof, const Packet& proof_packet) {
|
||||
assert(_object);
|
||||
TRACE("PacketReceipt::validate_proof: validating proof...");
|
||||
// CBA LINK
|
||||
// CBA TODO: Determine whether to use destination.identity or link.identity here!!!
|
||||
if (proof.size() == EXPL_LENGTH) {
|
||||
// This is an explicit proof
|
||||
Bytes proof_hash = proof.left(Type::Identity::HASHLENGTH/8);
|
||||
Bytes signature = proof.mid(Type::Identity::HASHLENGTH/8, Type::Identity::SIGLENGTH/8);
|
||||
if (proof_hash == _object->_hash) {
|
||||
if (_object->_destination.identity().validate(signature, _object->_hash)) {
|
||||
_object->_status = DELIVERED;
|
||||
_object->_proved = true;
|
||||
_object->_concluded_at = OS::time();
|
||||
//z _object->_proof_packet = proof_packet;
|
||||
|
||||
if (_object->_callbacks._delivery) {
|
||||
try {
|
||||
_object->_callbacks._delivery(*this);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (proof.size() == IMPL_LENGTH) {
|
||||
// This is an implicit proof
|
||||
if (!_object->_destination.identity()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Bytes signature = proof.left(Type::Identity::SIGLENGTH/8);
|
||||
if (_object->_destination.identity().validate(signature, _object->_hash)) {
|
||||
_object->_status = DELIVERED;
|
||||
_object->_proved = true;
|
||||
_object->_concluded_at = OS::time();
|
||||
//z _object->_proof_packet = proof_packet;
|
||||
|
||||
if (_object->_callbacks._delivery) {
|
||||
try {
|
||||
_object->_callbacks._delivery(*this);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error while executing proof validated callback. The contained exception was: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PacketReceipt::check_timeout() {
|
||||
assert(_object);
|
||||
if (_object->_status == SENT && is_timed_out()) {
|
||||
if (_object->_timeout == -1) {
|
||||
_object->_status = CULLED;
|
||||
}
|
||||
else {
|
||||
_object->_status = FAILED;
|
||||
}
|
||||
|
||||
_object->_concluded_at = Utilities::OS::time();
|
||||
|
||||
if (_object->_callbacks._timeout) {
|
||||
//z thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
|
||||
//z thread.daemon = True
|
||||
//z thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void ArduinoJson::convertFromJson(JsonVariantConst src, RNS::Packet& dst) {
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
// Query transport for matching interface
|
||||
dst = Transport::get_cached_packet(hash);
|
||||
}
|
||||
else {
|
||||
dst = {RNS::Type::NONE};
|
||||
}
|
||||
}
|
||||
*/
|
||||
382
lib/microReticulum/src/Packet.h
Executable file
382
lib/microReticulum/src/Packet.h
Executable file
@@ -0,0 +1,382 @@
|
||||
#pragma once
|
||||
|
||||
#include "Link.h"
|
||||
#include "Interface.h"
|
||||
#include "Destination.h"
|
||||
#include "Bytes.h"
|
||||
#include "Log.h"
|
||||
#include "Type.h"
|
||||
#include "Utilities/OS.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class ProofDestination;
|
||||
class PacketReceipt;
|
||||
class Packet;
|
||||
|
||||
class ProofDestination : public Destination {
|
||||
public:
|
||||
ProofDestination(const Packet& packet);
|
||||
// CBA Can't use virtual methods because they are lost in object copies
|
||||
//inline virtual const Bytes encrypt(const Bytes& data) {
|
||||
// return data;
|
||||
//}
|
||||
};
|
||||
|
||||
/*
|
||||
The PacketReceipt class is used to receive notifications about
|
||||
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
|
||||
of this class are never created manually, but always returned from
|
||||
the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
|
||||
*/
|
||||
class PacketReceipt {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
using delivery = void(*)(const PacketReceipt& packet_receipt);
|
||||
using timeout = void(*)(const PacketReceipt& packet_receipt);
|
||||
public:
|
||||
delivery _delivery = nullptr;
|
||||
timeout _timeout = nullptr;
|
||||
friend class PacketReceipt;
|
||||
};
|
||||
|
||||
public:
|
||||
PacketReceipt() : _object(new Object()) {}
|
||||
PacketReceipt(Type::NoneConstructor none) {}
|
||||
PacketReceipt(const PacketReceipt& packet_receipt) : _object(packet_receipt._object) {}
|
||||
PacketReceipt(const Packet& packet);
|
||||
|
||||
inline PacketReceipt& operator = (const PacketReceipt& packet_receipt) {
|
||||
_object = packet_receipt._object;
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const PacketReceipt& packet_receipt) const {
|
||||
return _object.get() < packet_receipt._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
bool validate_proof_packet(const Packet& proof_packet);
|
||||
//bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet = {Type::NONE});
|
||||
bool validate_link_proof(const Bytes& proof, const Link& link);
|
||||
bool validate_link_proof(const Bytes& proof, const Link& link, const Packet& proof_packet);
|
||||
//bool validate_proof(const Bytes& proof, const Packet& proof_packet = {Type::NONE});
|
||||
bool validate_proof(const Bytes& proof);
|
||||
bool validate_proof(const Bytes& proof, const Packet& proof_packet);
|
||||
inline double get_rtt() { assert(_object); return _object->_concluded_at - _object->_sent_at; }
|
||||
inline bool is_timed_out() { assert(_object); return ((_object->_sent_at + _object->_timeout) < Utilities::OS::time()); }
|
||||
void check_timeout();
|
||||
|
||||
// :param timeout: The timeout in seconds.
|
||||
inline void set_timeout(int16_t timeout) { assert(_object); _object->_timeout = timeout; }
|
||||
|
||||
/*
|
||||
Sets a function that gets called if a successfull delivery has been proven.
|
||||
|
||||
:param callback: A *callable* with the signature *callback(packet_receipt)*
|
||||
*/
|
||||
inline void set_delivery_callback(Callbacks::delivery callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._delivery = callback;
|
||||
}
|
||||
|
||||
/*
|
||||
Sets a function that gets called if the delivery times out.
|
||||
|
||||
:param callback: A *callable* with the signature *callback(packet_receipt)*
|
||||
*/
|
||||
inline void set_timeout_callback(Callbacks::timeout callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._timeout = callback;
|
||||
}
|
||||
|
||||
// getters
|
||||
inline const Bytes& hash() const { assert(_object); return _object->_hash; }
|
||||
inline Type::PacketReceipt::Status status() const { assert(_object); return _object->_status; }
|
||||
inline bool proved() const { assert(_object); return _object->_proved; }
|
||||
inline double concluded_at() const { assert(_object); return _object->_concluded_at; }
|
||||
inline const Bytes& truncated_hash() const { assert(_object); return _object->_truncated_hash; }
|
||||
inline const Callbacks& callbacks() const { assert(_object); return _object->_callbacks; }
|
||||
|
||||
// setters
|
||||
inline void status(Type::PacketReceipt::Status status) { assert(_object); _object->_status = status; }
|
||||
inline void proved(bool proved) { assert(_object); _object->_proved = proved; }
|
||||
inline void concluded_at(double concluded_at) { assert(_object); _object->_concluded_at = concluded_at; }
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object() {}
|
||||
virtual ~Object() {}
|
||||
private:
|
||||
Bytes _hash;
|
||||
Bytes _truncated_hash;
|
||||
bool _sent = true;
|
||||
double _sent_at = Utilities::OS::time();
|
||||
bool _proved = false;
|
||||
Type::PacketReceipt::Status _status = Type::PacketReceipt::SENT;
|
||||
Destination _destination = {Type::NONE};
|
||||
Callbacks _callbacks;
|
||||
double _concluded_at = 0;
|
||||
// CBA TODO This shoujld almost certainly not be a reference but we have an issue with circular dependency between Packet and PacketReceipt
|
||||
//Packet _proof_packet = {Type::NONE};
|
||||
int16_t _timeout = 0;
|
||||
friend class PacketReceipt;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class Packet {
|
||||
|
||||
public:
|
||||
//static constexpr const uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
|
||||
uint8_t EMPTY_DESTINATION[Type::Reticulum::DESTINATION_LENGTH] = {0};
|
||||
|
||||
public:
|
||||
Packet(Type::NoneConstructor none) {
|
||||
MEM("Packet NONE object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
Packet(const Packet& packet) : _object(packet._object) {
|
||||
MEM("Packet object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
Packet(
|
||||
const Destination& destination,
|
||||
const Interface& attached_interface,
|
||||
const Bytes& data,
|
||||
Type::Packet::types packet_type = Type::Packet::DATA,
|
||||
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
|
||||
Type::Transport::types transport_type = Type::Transport::BROADCAST,
|
||||
Type::Packet::header_types header_type = Type::Packet::HEADER_1,
|
||||
const Bytes& transport_id = {Bytes::NONE},
|
||||
bool create_receipt = true,
|
||||
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
|
||||
);
|
||||
Packet(
|
||||
const Destination& destination,
|
||||
const Bytes& data,
|
||||
Type::Packet::types packet_type = Type::Packet::DATA,
|
||||
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
|
||||
Type::Transport::types transport_type = Type::Transport::BROADCAST,
|
||||
Type::Packet::header_types header_type = Type::Packet::HEADER_1,
|
||||
const Bytes& transport_id = {Bytes::NONE},
|
||||
bool create_receipt = true,
|
||||
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
|
||||
) : Packet(destination, {Type::NONE}, data, packet_type, context, transport_type, header_type, transport_id, create_receipt, context_flag) {}
|
||||
// CBA LINK
|
||||
Packet(
|
||||
const Link& link,
|
||||
const Bytes& data,
|
||||
Type::Packet::types packet_type = Type::Packet::DATA,
|
||||
Type::Packet::context_types context = Type::Packet::CONTEXT_NONE,
|
||||
Type::Packet::context_flags context_flag = Type::Packet::FLAG_UNSET
|
||||
);
|
||||
virtual ~Packet() {
|
||||
MEM("Packet object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
inline Packet& operator = (const Packet& packet) {
|
||||
_object = packet._object;
|
||||
MEM("Packet object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const Packet& packet) const {
|
||||
return _object.get() < packet._object.get();
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
void setTransportId(const uint8_t* transport_id);
|
||||
void setHeader(const uint8_t* header);
|
||||
void setRaw(const uint8_t* raw, uint16_t len);
|
||||
void setData(const uint8_t* rata, uint16_t len);
|
||||
*/
|
||||
|
||||
public:
|
||||
uint8_t get_packed_flags();
|
||||
void unpack_flags(uint8_t flags);
|
||||
void pack();
|
||||
bool unpack();
|
||||
PacketReceipt send();
|
||||
bool resend();
|
||||
void prove(const Destination& destination = {Type::NONE});
|
||||
ProofDestination generate_proof_destination() const;
|
||||
bool validate_proof_packet(const Packet& proof_packet);
|
||||
bool validate_proof(const Bytes& proof);
|
||||
void update_hash();
|
||||
const Bytes get_hash() const;
|
||||
const Bytes getTruncatedHash() const;
|
||||
const Bytes get_hashable_part() const;
|
||||
|
||||
inline std::string toString() const { if (!_object) return ""; return "{Packet:" + _object->_packet_hash.toHex() + "}"; }
|
||||
|
||||
// getters
|
||||
inline const Destination& destination() const { assert(_object); return _object->_destination; }
|
||||
inline const Link& link() const { assert(_object); return _object->_link; }
|
||||
inline const Interface& attached_interface() const { assert(_object); return _object->_attached_interface; }
|
||||
inline const Interface& receiving_interface() const { assert(_object); return _object->_receiving_interface; }
|
||||
inline Type::Packet::header_types header_type() const { assert(_object); return _object->_header_type; }
|
||||
inline Type::Packet::context_flags context_flag() const { assert(_object); return _object->_context_flag; }
|
||||
inline Type::Transport::types transport_type() const { assert(_object); return _object->_transport_type; }
|
||||
inline Type::Destination::types destination_type() const { assert(_object); return _object->_destination_type; }
|
||||
inline Type::Packet::types packet_type() const { assert(_object); return _object->_packet_type; }
|
||||
inline Type::Packet::context_types context() const { assert(_object); return _object->_context; }
|
||||
inline bool sent() const { assert(_object); return _object->_sent; }
|
||||
inline double sent_at() const { assert(_object); return _object->_sent_at; }
|
||||
inline bool create_receipt() const { assert(_object); return _object->_create_receipt; }
|
||||
inline const PacketReceipt& receipt() const { assert(_object); return _object->_receipt; }
|
||||
inline uint8_t flags() const { assert(_object); return _object->_flags; }
|
||||
inline uint8_t hops() const { assert(_object); return _object->_hops; }
|
||||
inline bool cached() const { assert(_object); return _object->_cached; }
|
||||
inline const Bytes& packet_hash() const { assert(_object); return _object->_packet_hash; }
|
||||
inline const Bytes& destination_hash() const { assert(_object); return _object->_destination_hash; }
|
||||
inline const Bytes& transport_id() const { assert(_object); return _object->_transport_id; }
|
||||
inline const Bytes& raw() const { assert(_object); return _object->_raw; }
|
||||
inline const Bytes& data() const { assert(_object); return _object->_data; }
|
||||
// CBA LINK
|
||||
inline const Link& destination_link() const { assert(_object); return _object->_destination_link; }
|
||||
//CBA Following method is only used by Resource to access decrypted resource advertisement form Link. Consider a better way.
|
||||
inline const Bytes& plaintext() { assert(_object); return _object->_plaintext; }
|
||||
|
||||
// setters
|
||||
inline void destination(const Destination& destination) { assert(_object); _object->_destination = destination; }
|
||||
inline void link(const Link& link) { assert(_object); _object->_link = link; }
|
||||
inline void receiving_interface(const Interface& receiving_interface) { assert(_object); _object->_receiving_interface = receiving_interface; }
|
||||
inline void sent(bool sent) { assert(_object); _object->_sent = sent; }
|
||||
inline void sent_at(double sent_at) { assert(_object); _object->_sent_at = sent_at; }
|
||||
inline void receipt(const PacketReceipt& receipt) { assert(_object); _object->_receipt = receipt; }
|
||||
inline void hops(uint8_t hops) { assert(_object); _object->_hops = hops; }
|
||||
inline void cached(bool cached) { assert(_object); _object->_cached = cached; }
|
||||
inline void transport_id(const Bytes& transport_id) { assert(_object); _object->_transport_id = transport_id; }
|
||||
//CBA Following method is only used by Link to provide Resource access to decrypted resource advertisement. Consider a better way.
|
||||
inline void plaintext(const Bytes& plaintext) { assert(_object); _object->_plaintext = plaintext; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string debugString() const;
|
||||
std::string dumpString() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object(const Destination& destination, const Interface& attached_interface) : _destination(destination), _attached_interface(attached_interface) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
// CBA LINK
|
||||
//Object(const Destination& destination, const Link& destination_link) : _destination(destination), _destination_link(destination_link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
//Object(const Link& link) : _destination(link.destination()), _destination_link(link) { MEM("Packet::Data object created, this: " + std::to_string((uintptr_t)this)); }
|
||||
virtual ~Object() { MEM("Packet::Data object destroyed, this: " + std::to_string((uintptr_t)this)); }
|
||||
private:
|
||||
Destination _destination = {Type::NONE};
|
||||
|
||||
// CBA LINK
|
||||
// CBA TODO: Determine if _link (assigned late by Transport) and _destination_link (assigned in constructor) can be one and the same !!!
|
||||
Link _destination_link = {Type::NONE};
|
||||
|
||||
Link _link = {Type::NONE};
|
||||
|
||||
Interface _attached_interface = {Type::NONE};
|
||||
Interface _receiving_interface = {Type::NONE};
|
||||
|
||||
Type::Packet::header_types _header_type = Type::Packet::HEADER_1;
|
||||
Type::Transport::types _transport_type = Type::Transport::BROADCAST;
|
||||
Type::Destination::types _destination_type = Type::Destination::SINGLE;
|
||||
Type::Packet::types _packet_type = Type::Packet::DATA;
|
||||
Type::Packet::context_types _context = Type::Packet::CONTEXT_NONE;
|
||||
Type::Packet::context_flags _context_flag = Type::Packet::FLAG_UNSET;
|
||||
|
||||
uint8_t _flags = 0;
|
||||
uint8_t _hops = 0;
|
||||
|
||||
bool _packed = false;
|
||||
bool _sent = false;
|
||||
bool _create_receipt = false;
|
||||
bool _fromPacked = false;
|
||||
bool _truncated = false; // whether data was truncated
|
||||
bool _encrypted = false; // whether data is encrypted
|
||||
bool _cached = false; // whether packet has been cached
|
||||
PacketReceipt _receipt = {Type::NONE};
|
||||
|
||||
uint16_t _MTU = Type::Reticulum::MTU;
|
||||
double _sent_at = 0;
|
||||
|
||||
float _rssi = 0.0;
|
||||
float _snr = 0.0;
|
||||
float _q = 0.0;
|
||||
|
||||
Bytes _packet_hash;
|
||||
Bytes _ratchet_id;
|
||||
Bytes _destination_hash;
|
||||
Bytes _transport_id;
|
||||
|
||||
Bytes _raw; // header + ( plaintext | ciphertext-token )
|
||||
Bytes _data; // plaintext | ciphertext
|
||||
|
||||
Bytes _plaintext; // used exclusively to relay decrypted resource advertisement form Link to Resource
|
||||
|
||||
Bytes _header;
|
||||
Bytes _ciphertext;
|
||||
|
||||
friend class Packet;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
namespace ArduinoJson {
|
||||
inline bool convertToJson(const RNS::Packet& src, JsonVariant dst) {
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
void convertFromJson(JsonVariantConst src, RNS::Packet& dst);
|
||||
inline bool canConvertFromJson(JsonVariantConst src, const RNS::Packet&) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
}
|
||||
*/
|
||||
/*
|
||||
namespace ArduinoJson {
|
||||
template <>
|
||||
struct Converter<RNS::Packet> {
|
||||
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
static RNS::Packet fromJson(JsonVariantConst src) {
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
TRACE(">>> Deserialized packet hash " + hash.toHex());
|
||||
TRACE(">>> Querying transport for cached packet");
|
||||
// Query transport for matching interface
|
||||
return RNS::Packet::get_cached_packet(hash);
|
||||
}
|
||||
else {
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
122
lib/microReticulum/src/Resource.cpp
Executable file
122
lib/microReticulum/src/Resource.cpp
Executable file
@@ -0,0 +1,122 @@
|
||||
#include "Resource.h"
|
||||
|
||||
#include "ResourceData.h"
|
||||
#include "Reticulum.h"
|
||||
#include "Transport.h"
|
||||
#include "Packet.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Resource;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
//Resource::Resource(const Link& link /*= {Type::NONE}*/) :
|
||||
// _object(new ResourceData(link))
|
||||
//{
|
||||
// assert(_object);
|
||||
// MEM("Resource object created");
|
||||
//}
|
||||
|
||||
Resource::Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout) :
|
||||
_object(new ResourceData(link))
|
||||
{
|
||||
assert(_object);
|
||||
MEM("Resource object created");
|
||||
}
|
||||
|
||||
Resource::Resource(const Bytes& data, const Link& link, bool advertise /*= true*/, bool auto_compress /*= true*/, Callbacks::concluded callback /*= nullptr*/, Callbacks::progress progress_callback /*= nullptr*/, double timeout /*= 0.0*/, int segment_index /*= 1*/, const Bytes& original_hash /*= {Type::NONE}*/, const Bytes& request_id /*= {Type::NONE}*/, bool is_response /*= false*/) :
|
||||
_object(new ResourceData(link))
|
||||
{
|
||||
assert(_object);
|
||||
MEM("Resource object created");
|
||||
}
|
||||
|
||||
|
||||
void Resource::validate_proof(const Bytes& proof_data) {
|
||||
}
|
||||
|
||||
void Resource::cancel() {
|
||||
}
|
||||
|
||||
/*
|
||||
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
|
||||
*/
|
||||
float Resource::get_progress() const {
|
||||
/*
|
||||
assert(_object);
|
||||
if (_object->_initiator) {
|
||||
_object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
|
||||
_object->_processed_parts += _object->sent_parts;
|
||||
_object->_progress_total_parts = float(_object->grand_total_parts);
|
||||
}
|
||||
else {
|
||||
_object->_processed_parts = (_object->_segment_index-1)*math.ceil(Type::Resource::MAX_EFFICIENT_SIZE/Type::Resource::SDU);
|
||||
_object->_processed_parts += _object->_received_count;
|
||||
if (_object->split) {
|
||||
_object->progress_total_parts = float(math.ceil(_object->total_size/Type::Resource::SDU));
|
||||
}
|
||||
else {
|
||||
_object->progress_total_parts = float(_object->total_parts);
|
||||
}
|
||||
}
|
||||
|
||||
return (float)_object->processed_parts / (float)_object->progress_total_parts;
|
||||
*/
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void Resource::set_concluded_callback(Callbacks::concluded callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._concluded = callback;
|
||||
}
|
||||
|
||||
void Resource::set_progress_callback(Callbacks::progress callback) {
|
||||
assert(_object);
|
||||
_object->_callbacks._progress = callback;
|
||||
}
|
||||
|
||||
|
||||
std::string Resource::toString() const {
|
||||
if (!_object) {
|
||||
return "";
|
||||
}
|
||||
//return "<"+RNS.hexrep(self.hash,delimit=False)+"/"+RNS.hexrep(self.link.link_id,delimit=False)+">"
|
||||
//return "{Resource:" + _object->_hash.toHex() + "}";
|
||||
return "{Resource: unknown}";
|
||||
}
|
||||
|
||||
// getters
|
||||
const Bytes& Resource::hash() const {
|
||||
assert(_object);
|
||||
return _object->_hash;
|
||||
}
|
||||
|
||||
const Bytes& Resource::request_id() const {
|
||||
assert(_object);
|
||||
return _object->_request_id;
|
||||
}
|
||||
|
||||
const Bytes& Resource::data() const {
|
||||
assert(_object);
|
||||
return _object->_data;
|
||||
}
|
||||
|
||||
const Type::Resource::status Resource::status() const {
|
||||
assert(_object);
|
||||
return _object->_status;
|
||||
}
|
||||
|
||||
const size_t Resource::size() const {
|
||||
assert(_object);
|
||||
return _object->_size;
|
||||
}
|
||||
|
||||
const size_t Resource::total_size() const {
|
||||
assert(_object);
|
||||
return _object->_total_size;
|
||||
}
|
||||
|
||||
// setters
|
||||
|
||||
110
lib/microReticulum/src/Resource.h
Executable file
110
lib/microReticulum/src/Resource.h
Executable file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "Destination.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class ResourceData;
|
||||
class Packet;
|
||||
class Destination;
|
||||
class Link;
|
||||
class Resource;
|
||||
|
||||
class Resource {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
// CBA std::function apparently not implemented in NRF52 framework
|
||||
//typedef std::function<void(const Resource& resource)> concluded;
|
||||
using concluded = void(*)(const Resource& resource);
|
||||
using progress = void(*)(const Resource& resource);
|
||||
public:
|
||||
concluded _concluded = nullptr;
|
||||
progress _progress = nullptr;
|
||||
friend class Resource;
|
||||
};
|
||||
|
||||
public:
|
||||
Resource(Type::NoneConstructor none) {
|
||||
MEM("Resource NONE object created");
|
||||
}
|
||||
Resource(const Resource& resource) : _object(resource._object) {
|
||||
MEM("Resource object copy created");
|
||||
}
|
||||
//Resource(const Link& link = {Type::NONE});
|
||||
Resource(const Bytes& data, const Link& link, const Bytes& request_id, bool is_response, double timeout);
|
||||
Resource(const Bytes& data, const Link& link, bool advertise = true, bool auto_compress = true, Callbacks::concluded callback = nullptr, Callbacks::progress progress_callback = nullptr, double timeout = 0.0, int segment_index = 1, const Bytes& original_hash = {Type::NONE}, const Bytes& request_id = {Type::NONE}, bool is_response = false);
|
||||
virtual ~Resource(){
|
||||
MEM("Resource object destroyed");
|
||||
}
|
||||
|
||||
Resource& operator = (const Resource& resource) {
|
||||
_object = resource._object;
|
||||
return *this;
|
||||
}
|
||||
operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
bool operator < (const Resource& resource) const {
|
||||
return _object.get() < resource._object.get();
|
||||
//return _object->_hash < resource._object->_hash;
|
||||
}
|
||||
|
||||
public:
|
||||
//p static def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
|
||||
|
||||
public:
|
||||
//p def hashmap_update_packet(self, plaintext):
|
||||
//p def hashmap_update(self, segment, hashmap):
|
||||
//p def get_map_hash(self, data):
|
||||
//p def advertise(self):
|
||||
//p def __advertise_job(self):
|
||||
//p def watchdog_job(self):
|
||||
//p def __watchdog_job(self):
|
||||
//p def assemble(self):
|
||||
//p def prove(self):
|
||||
void validate_proof(const Bytes& proof_data);
|
||||
//p def receive_part(self, packet):
|
||||
//p def request_next(self):
|
||||
//p def request(self, request_data):
|
||||
void cancel();
|
||||
//p def set_callback(self, callback):
|
||||
//p def progress_callback(self, callback):
|
||||
float get_progress() const;
|
||||
//p def get_transfer_size(self):
|
||||
//p def get_data_size(self):
|
||||
//p def get_parts(self):
|
||||
//p def get_segments(self):
|
||||
//p def get_hash(self):
|
||||
//p def is_compressed(self):
|
||||
void set_concluded_callback(Callbacks::concluded callback);
|
||||
void set_progress_callback(Callbacks::progress callback);
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
// getters
|
||||
const Bytes& hash() const;
|
||||
const Bytes& request_id() const;
|
||||
const Bytes& data() const;
|
||||
const Type::Resource::status status() const;
|
||||
const size_t size() const;
|
||||
const size_t total_size() const;
|
||||
|
||||
// setters
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ResourceData> _object;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class ResourceAdvertisement {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
31
lib/microReticulum/src/ResourceData.h
Executable file
31
lib/microReticulum/src/ResourceData.h
Executable file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
#include "Interface.h"
|
||||
#include "Packet.h"
|
||||
#include "Destination.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
#include "Cryptography/Fernet.h"
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class ResourceData {
|
||||
public:
|
||||
ResourceData(const Link& link) : _link(link) {}
|
||||
virtual ~ResourceData() {}
|
||||
private:
|
||||
Link _link;
|
||||
Bytes _hash;
|
||||
Bytes _request_id;
|
||||
Bytes _data;
|
||||
Type::Resource::status _status = Type::Resource::NONE;
|
||||
size_t _size = 0;
|
||||
size_t _total_size = 0;
|
||||
Resource::Callbacks _callbacks;
|
||||
|
||||
friend class Resource;
|
||||
};
|
||||
|
||||
}
|
||||
492
lib/microReticulum/src/Reticulum.cpp
Executable file
492
lib/microReticulum/src/Reticulum.cpp
Executable file
@@ -0,0 +1,492 @@
|
||||
#include "Reticulum.h"
|
||||
|
||||
#include "Transport.h"
|
||||
#include "Log.h"
|
||||
|
||||
//#include <TransistorNoiseSource.h>
|
||||
#include <RNG.h>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
//#include <TransistorNoiseSource.h>
|
||||
#endif
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Type::Reticulum;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
/*static*/ //std::string Reticulum::_storagepath;
|
||||
/*static*/ char Reticulum::_storagepath[FILEPATH_MAXSIZE];
|
||||
/*static*/ //std::string Reticulum::_cachepath;
|
||||
/*static*/ char Reticulum::_cachepath[FILEPATH_MAXSIZE];
|
||||
|
||||
/*static*/ const Reticulum& Reticulum::_instance = {Type::NONE};
|
||||
|
||||
/*static*/ bool Reticulum::__transport_enabled = false;
|
||||
/*static*/ bool Reticulum::__link_mtu_discovery = RNS::Type::Reticulum::LINK_MTU_DISCOVERY;
|
||||
/*static*/ bool Reticulum::__remote_management_enabled = false;
|
||||
/*static*/ bool Reticulum::__use_implicit_proof = true;
|
||||
/*static*/ bool Reticulum::__allow_probes = false;
|
||||
/*static*/ bool Reticulum::panic_on_interface_error = false;
|
||||
|
||||
#ifdef ARDUINO
|
||||
// Noise source to seed the random number generator.
|
||||
//TransistorNoiseSource noise(A1);
|
||||
#endif
|
||||
|
||||
/*
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
pass any traffic before being instantiated.
|
||||
|
||||
:param configdir: Full path to a Reticulum configuration directory.
|
||||
*/
|
||||
|
||||
/*p TODO
|
||||
@staticmethod
|
||||
void Reticulum::exit_handler():
|
||||
# This exit handler is called whenever Reticulum is asked to
|
||||
# shut down, and will in turn call exit handlers in other
|
||||
# classes, saving necessary information to disk and carrying
|
||||
# out cleanup operations.
|
||||
|
||||
Transport::exit_handler()
|
||||
RNS.Identity.exit_handler()
|
||||
|
||||
@staticmethod
|
||||
void Reticulum::sigint_handler(signal, frame):
|
||||
Transport::detach_interfaces()
|
||||
RNS.exit()
|
||||
|
||||
|
||||
@staticmethod
|
||||
void Reticulum::sigterm_handler(signal, frame):
|
||||
Transport::detach_interfaces()
|
||||
RNS.exit()
|
||||
*/
|
||||
|
||||
//def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
|
||||
Reticulum::Reticulum() : _object(new Object()) {
|
||||
MEM("Reticulum default object creating..., this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
|
||||
// Initialize random number generator
|
||||
TRACE("Initializing RNG...");
|
||||
RNG.begin("Reticulum");
|
||||
TRACE("RNG initial random value: " + std::to_string(Cryptography::randomnum()));
|
||||
|
||||
#ifdef ARDUINO
|
||||
// Add a noise source to the list of sources known to RNG.
|
||||
//RNG.addNoiseSource(noise);
|
||||
#endif
|
||||
|
||||
//z RNS.vendor.platformutils.platform_checks()
|
||||
|
||||
/*p TODO
|
||||
if configdir != None:
|
||||
Reticulum.configdir = configdir
|
||||
else:
|
||||
if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"):
|
||||
Reticulum.configdir = "/etc/reticulum"
|
||||
elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"):
|
||||
Reticulum.configdir = Reticulum.userdir+"/.config/reticulum"
|
||||
else:
|
||||
Reticulum.configdir = Reticulum.userdir+"/.reticulum"
|
||||
|
||||
if logdest == RNS.LOG_FILE:
|
||||
RNS.logdest = RNS.LOG_FILE
|
||||
RNS.logfile = Reticulum.configdir+"/logfile"
|
||||
|
||||
Reticulum.configpath = Reticulum.configdir+"/config"
|
||||
Reticulum.storagepath = Reticulum.configdir+"/storage"
|
||||
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
|
||||
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
|
||||
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
|
||||
*/
|
||||
// CBA TEST
|
||||
#ifdef ARDUINO
|
||||
//_storagepath = "";
|
||||
strncpy(_storagepath, "", FILEPATH_MAXSIZE);
|
||||
//_cachepath = "/cache";
|
||||
strncpy(_cachepath, "/cache", FILEPATH_MAXSIZE);
|
||||
#else
|
||||
//_storagepath = ".";
|
||||
strncpy(_storagepath, ".", FILEPATH_MAXSIZE);
|
||||
//_cachepath = "./cache";
|
||||
strncpy(_cachepath, "./cache", FILEPATH_MAXSIZE);
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO
|
||||
#if defined(RNS_USE_FS)
|
||||
// load time offset from file if it exists
|
||||
try {
|
||||
char time_offset_path[FILEPATH_MAXSIZE];
|
||||
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
|
||||
if (OS::file_exists(time_offset_path)) {
|
||||
Bytes buf;
|
||||
if (OS::read_file(time_offset_path, buf) == 8) {
|
||||
uint64_t offset = *(uint64_t*)buf.data();
|
||||
DEBUG("Read time offset of " + std::to_string(offset) + " from file");
|
||||
OS::setTimeOffset(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Failed to load time offset, the contained exception was: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Initialize time-based variables *after* time offset update
|
||||
_object->_last_data_persist = OS::time();
|
||||
_object->_last_cache_clean = 0.0;
|
||||
_object->_jobs_last_run = OS::time();
|
||||
|
||||
/*p TODO
|
||||
if not os.path.isdir(Reticulum.storagepath):
|
||||
os.makedirs(Reticulum.storagepath)
|
||||
|
||||
if not os.path.isdir(Reticulum.cachepath):
|
||||
os.makedirs(Reticulum.cachepath)
|
||||
|
||||
if not os.path.isdir(Reticulum.resourcepath):
|
||||
os.makedirs(Reticulum.resourcepath)
|
||||
|
||||
if not os.path.isdir(Reticulum.identitypath):
|
||||
os.makedirs(Reticulum.identitypath)
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try:
|
||||
self.config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
||||
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
RNS.panic()
|
||||
else:
|
||||
RNS.log("Could not load config file, creating default configuration file...")
|
||||
self.__create_default_config()
|
||||
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
|
||||
time.sleep(1.5)
|
||||
|
||||
self.__apply_config()
|
||||
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
||||
|
||||
RNS.Identity.load_known_destinations()
|
||||
*/
|
||||
|
||||
// CBA Moved to start() so Transport is not started until after interfaces are setup
|
||||
//Transport::start(*this);
|
||||
|
||||
/*p TODO
|
||||
self.rpc_addr = ("127.0.0.1", self.local_control_port)
|
||||
self.rpc_key = RNS.Identity.full_hash(Transport::identity.get_private_key())
|
||||
|
||||
if self.is_shared_instance:
|
||||
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
|
||||
thread = threading.Thread(target=self.rpc_loop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
atexit.register(Reticulum.exit_handler)
|
||||
signal.signal(signal.SIGINT, Reticulum.sigint_handler)
|
||||
signal.signal(signal.SIGTERM, Reticulum.sigterm_handler)
|
||||
*/
|
||||
|
||||
MEM("Reticulum default object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
void Reticulum::start() {
|
||||
|
||||
INFO("Total memory: " + std::to_string(OS::heap_size()));
|
||||
INFO("Total flash: " + std::to_string(OS::storage_size()));
|
||||
|
||||
INFO("Starting Transport...");
|
||||
Transport::start(*this);
|
||||
}
|
||||
|
||||
void Reticulum::loop() {
|
||||
assert(_object);
|
||||
if (!_object->_is_connected_to_shared_instance) {
|
||||
|
||||
// Perform Reticulum housekeeping
|
||||
if (OS::time() > (_object->_jobs_last_run + JOB_INTERVAL)) {
|
||||
jobs();
|
||||
_object->_jobs_last_run = OS::time();
|
||||
}
|
||||
|
||||
// Perform interface processing
|
||||
for (auto& [hash, interface] : Transport::get_interfaces()) {
|
||||
interface.loop();
|
||||
}
|
||||
|
||||
// Perform Filesystem processing
|
||||
FileSystem& filesystem = OS::get_filesystem();
|
||||
if (filesystem) {
|
||||
filesystem.loop();
|
||||
}
|
||||
|
||||
|
||||
// Perform Transport processing
|
||||
RNS::Transport::loop();
|
||||
}
|
||||
// Perform random number gnerator housekeeping
|
||||
RNG.loop();
|
||||
}
|
||||
|
||||
void Reticulum::jobs() {
|
||||
|
||||
double now = OS::time();
|
||||
|
||||
#if 1
|
||||
// CBA Detect low-memory condition and reset
|
||||
if (OS::heap_size() > 0) {
|
||||
uint8_t remaining = (uint8_t)((double)OS::heap_available() / (double)OS::heap_size() * 100.0);
|
||||
if (remaining <= 2) {
|
||||
head("DETECTED LOW-MEMORY CONDITION (" + std::to_string(remaining) + "%), RESETTING!!!", LOG_CRITICAL);
|
||||
persist_data();
|
||||
#if defined(ESP32)
|
||||
ESP.restart();
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
//dbgDumpMemory();
|
||||
NVIC_SystemReset();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (now > _object->_last_cache_clean + CLEAN_INTERVAL) {
|
||||
clean_caches();
|
||||
_object->_last_cache_clean = OS::time();
|
||||
}
|
||||
|
||||
if (now > _object->_last_data_persist + PERSIST_INTERVAL) {
|
||||
persist_data();
|
||||
}
|
||||
}
|
||||
|
||||
// CBA TODO
|
||||
/*
|
||||
void Reticulum::start_local_interface() {
|
||||
}
|
||||
|
||||
void Reticulum::apply_config() {
|
||||
}
|
||||
|
||||
void Reticulum::_add_interface(self,interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
|
||||
*/
|
||||
|
||||
void Reticulum::should_persist_data() {
|
||||
if (OS::time() > _object->_last_data_persist + GRACIOUS_PERSIST_INTERVAL) {
|
||||
persist_data();
|
||||
}
|
||||
}
|
||||
|
||||
void Reticulum::persist_data() {
|
||||
TRACE("Persisting transport and identity data...");
|
||||
Transport::persist_data();
|
||||
Identity::persist_data();
|
||||
|
||||
#ifdef ARDUINO
|
||||
#if defined(RNS_USE_FS)
|
||||
// write time offset to file
|
||||
try {
|
||||
char time_offset_path[FILEPATH_MAXSIZE];
|
||||
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
|
||||
uint64_t offset = OS::ltime();
|
||||
DEBUGF("Writing time offset of %llu to file %s", offset, time_offset_path);
|
||||
Bytes buf((uint8_t*)&offset, sizeof(offset));
|
||||
OS::write_file(time_offset_path, buf);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Failed to write time offset, the contained exception was: %s", e.what());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
_object->_last_data_persist = OS::time();
|
||||
}
|
||||
|
||||
void Reticulum::clean_caches() {
|
||||
TRACE("Cleaning resource and packet caches...");
|
||||
double now = OS::time();
|
||||
|
||||
#if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS)
|
||||
/*
|
||||
// Clean resource caches
|
||||
for (auto& filename : OS::list_directory(resourcepath) {
|
||||
try {
|
||||
if (filename.length() == (Type::Identity::HASHLENGTH//8)*2) {
|
||||
char filepath[FILEPATH_MAXSIZE];
|
||||
snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _resourcepath, filename.c_str());
|
||||
//p mtime = os.path.getmtime(filepath)
|
||||
//p age = now - mtime
|
||||
//p if (age > Types::Reticulum.RESOURCE_CACHE) {
|
||||
//p OS::remove_file(filepath);
|
||||
//p }
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error while cleaning resources cache, the contained exception was: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Clean packet caches
|
||||
for (auto& filename : OS::list_directory(_cachepath.c_str())) {
|
||||
try {
|
||||
if (filename.length() == (Type::Identity::HASHLENGTH/8)*2) {
|
||||
char filepath[FILEPATH_MAXSIZE];
|
||||
snprintf(filepath, FILEPATH_MAXSIZE, "%s/%s", _cachepath, filename.c_str());
|
||||
//p mtime = os.path.getmtime(filepath)
|
||||
//p age = now - mtime
|
||||
//p if (age > Types::Transport::DESTINATION_TIMEOUT) {
|
||||
//p OS::remove_file(filepath);
|
||||
//p }
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERROR("Error while cleaning packet cache, the contained exception was: %s", e.what());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Transport::clean_caches();
|
||||
|
||||
// CBA
|
||||
Identity::cull_known_destinations();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void Reticulum::clear_caches() {
|
||||
TRACE("Clearing resource and packet caches...");
|
||||
|
||||
try {
|
||||
char destination_table_path[FILEPATH_MAXSIZE];
|
||||
snprintf(destination_table_path, FILEPATH_MAXSIZE, "%s/destination_table", _storagepath);
|
||||
OS::remove_file(destination_table_path);
|
||||
|
||||
OS::remove_directory(_cachepath);
|
||||
|
||||
#ifdef ARDUINO
|
||||
char time_offset_path[FILEPATH_MAXSIZE];
|
||||
snprintf(time_offset_path, FILEPATH_MAXSIZE, "%s/time_offset", _storagepath);
|
||||
OS::remove_file(time_offset_path);
|
||||
#endif
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
ERRORF("Failed to clear cache file(s), the contained exception was: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/*p TODO
|
||||
|
||||
void Reticulum::__create_default_config() {
|
||||
self.config = ConfigObj(__default_rns_config__)
|
||||
self.config.filename = Reticulum.configpath
|
||||
|
||||
if not os.path.isdir(Reticulum.configdir):
|
||||
os.makedirs(Reticulum.configdir)
|
||||
self.config.write()
|
||||
}
|
||||
|
||||
void Reticulum::rpc_loop() {
|
||||
}
|
||||
|
||||
void Reticulum::get_interface_stats() const {
|
||||
}
|
||||
*/
|
||||
|
||||
const std::map<Bytes, Transport::DestinationEntry>& Reticulum::get_path_table() const {
|
||||
/*
|
||||
path_table = []
|
||||
for dst_hash in Transport::destination_table:
|
||||
entry = {
|
||||
"hash": dst_hash,
|
||||
"timestamp": Transport::destination_table[dst_hash][0],
|
||||
"via": Transport::destination_table[dst_hash][1],
|
||||
"hops": Transport::destination_table[dst_hash][2],
|
||||
"expires": Transport::destination_table[dst_hash][3],
|
||||
"interface": str(Transport::destination_table[dst_hash][5]),
|
||||
}
|
||||
path_table.append(entry)
|
||||
|
||||
return path_table
|
||||
*/
|
||||
return Transport::get_destination_table();
|
||||
}
|
||||
|
||||
const std::map<Bytes, Transport::RateEntry>& Reticulum::get_rate_table() const {
|
||||
/*
|
||||
rate_table = []
|
||||
for dst_hash in Transport::announce_rate_table:
|
||||
entry = {
|
||||
"hash": dst_hash,
|
||||
"last": Transport::announce_rate_table[dst_hash]["last"],
|
||||
"rate_violations": Transport::announce_rate_table[dst_hash]["rate_violations"],
|
||||
"blocked_until": Transport::announce_rate_table[dst_hash]["blocked_until"],
|
||||
"timestamps": Transport::announce_rate_table[dst_hash]["timestamps"],
|
||||
}
|
||||
rate_table.append(entry)
|
||||
|
||||
return rate_table
|
||||
*/
|
||||
return Transport::get_announce_rate_table();
|
||||
}
|
||||
|
||||
bool Reticulum::drop_path(const Bytes& destination) {
|
||||
return Transport::expire_path(destination);
|
||||
}
|
||||
|
||||
uint16_t Reticulum::drop_all_via(const Bytes& transport_hash) {
|
||||
uint16_t dropped_count = 0;
|
||||
//for (auto& destination_hash : Transport::get_destination_table()) {
|
||||
for (const auto& [destination_hash, destination_entry] : Transport::get_destination_table()) {
|
||||
if (destination_entry._received_from == transport_hash) {
|
||||
Transport::expire_path(destination_hash);
|
||||
++dropped_count;
|
||||
}
|
||||
}
|
||||
return dropped_count;
|
||||
}
|
||||
|
||||
void Reticulum::drop_announce_queues() {
|
||||
Transport::drop_announce_queues();
|
||||
}
|
||||
|
||||
const std::string Reticulum::get_next_hop_if_name(const Bytes& destination) const {
|
||||
return Transport::next_hop_interface(destination).name();
|
||||
}
|
||||
|
||||
double Reticulum::get_first_hop_timeout(const Bytes& destination) const {
|
||||
return Transport::first_hop_timeout(destination);
|
||||
}
|
||||
|
||||
const Bytes Reticulum::get_next_hop(const Bytes& destination) const {
|
||||
return Transport::next_hop(destination);
|
||||
}
|
||||
|
||||
size_t Reticulum::get_link_count() const {
|
||||
return Transport::get_link_table().size();
|
||||
}
|
||||
|
||||
/*p
|
||||
void Reticulum::get_packet_rssi(const Bytes& packet_hash) const {
|
||||
for entry in Transport::local_client_rssi_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
|
||||
void Reticulum::get_packet_snr(const Bytes& packet_hash) const {
|
||||
for entry in Transport::local_client_snr_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
|
||||
void Reticulum::get_packet_q(const Bytes& packet_hash) const {
|
||||
for entry in Transport::local_client_q_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
*/
|
||||
182
lib/microReticulum/src/Reticulum.h
Executable file
182
lib/microReticulum/src/Reticulum.h
Executable file
@@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
|
||||
#include "Transport.h"
|
||||
#include "Log.h"
|
||||
#include "Type.h"
|
||||
#include "Utilities/OS.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Reticulum {
|
||||
|
||||
public:
|
||||
|
||||
//z router = None
|
||||
//z config = None
|
||||
|
||||
// The default configuration path will be expanded to a directory
|
||||
// named ".reticulum" inside the current users home directory
|
||||
//z userdir = os.path.expanduser("~")
|
||||
//z configdir = None
|
||||
//z configpath = ""
|
||||
//p storagepath = ""
|
||||
//static std::string _storagepath;
|
||||
static char _storagepath[Type::Reticulum::FILEPATH_MAXSIZE];
|
||||
//p cachepath = ""
|
||||
//static std::string _cachepath;
|
||||
static char _cachepath[Type::Reticulum::FILEPATH_MAXSIZE];
|
||||
|
||||
static const Reticulum& _instance;
|
||||
|
||||
static bool __transport_enabled;
|
||||
static bool __link_mtu_discovery;
|
||||
static bool __remote_management_enabled;
|
||||
static bool __use_implicit_proof;
|
||||
static bool __allow_probes;
|
||||
static bool panic_on_interface_error;
|
||||
|
||||
public:
|
||||
// Return the currently running Reticulum instance
|
||||
inline static const Reticulum& get_instance() { return _instance; }
|
||||
|
||||
public:
|
||||
Reticulum();
|
||||
Reticulum(Type::NoneConstructor none) {
|
||||
MEM("Reticulum empty object created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
Reticulum(const Reticulum& reticulum) : _object(reticulum._object) {
|
||||
MEM("Reticulum object copy created, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
virtual ~Reticulum() {
|
||||
MEM("Reticulum object destroyed, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
}
|
||||
|
||||
inline Reticulum& operator = (const Reticulum& reticulum) {
|
||||
_object = reticulum._object;
|
||||
MEM("Reticulum object copy created by assignment, this: " + std::to_string((uintptr_t)this) + ", data: " + std::to_string((uintptr_t)_object.get()));
|
||||
return *this;
|
||||
}
|
||||
inline operator bool() const {
|
||||
return _object.get() != nullptr;
|
||||
}
|
||||
inline bool operator < (const Reticulum& reticulum) const {
|
||||
return _object.get() < reticulum._object.get();
|
||||
}
|
||||
|
||||
public:
|
||||
void start();
|
||||
void loop();
|
||||
void jobs();
|
||||
void should_persist_data();
|
||||
void persist_data();
|
||||
void clean_caches();
|
||||
void clear_caches();
|
||||
//void __create_default_config();
|
||||
//void rpc_loop();
|
||||
//void get_interface_stats() const;
|
||||
const std::map<Bytes, Transport::DestinationEntry>& get_path_table() const;
|
||||
const std::map<Bytes, Transport::RateEntry>& get_rate_table() const;
|
||||
bool drop_path(const Bytes& destination);
|
||||
uint16_t drop_all_via(const Bytes& transport_hash);
|
||||
void drop_announce_queues();
|
||||
const std::string get_next_hop_if_name(const Bytes& destination) const;
|
||||
double get_first_hop_timeout(const Bytes& destination) const;
|
||||
const Bytes get_next_hop(const Bytes& destination) const;
|
||||
size_t get_link_count() const;
|
||||
//void get_packet_rssi(const Bytes& packet_hash) const;
|
||||
//void get_packet_snr(const Bytes& packet_hash) const;
|
||||
//void get_packet_q(const Bytes& packet_hash) const;
|
||||
|
||||
/*
|
||||
Returns whether proofs sent are explicit or implicit.
|
||||
|
||||
:returns: True if the current running configuration specifies to use implicit proofs. False if not.
|
||||
*/
|
||||
inline static bool should_use_implicit_proof() { return __use_implicit_proof; }
|
||||
|
||||
/*
|
||||
Returns whether Transport is enabled for the running
|
||||
instance.
|
||||
|
||||
When Transport is enabled, Reticulum will
|
||||
route traffic for other peers, respond to path requests
|
||||
and pass announces over the network.
|
||||
|
||||
:returns: True if Transport is enabled, False if not.
|
||||
*/
|
||||
inline static bool transport_enabled() { return __transport_enabled; }
|
||||
inline static void transport_enabled(bool transport_enabled) { __transport_enabled = transport_enabled; }
|
||||
|
||||
/*
|
||||
Returns whether link MTU discovery is enabled for the running
|
||||
instance.
|
||||
|
||||
When link MTU discovery is enabled, Reticulum will
|
||||
automatically upgrade link MTUs to the highest supported
|
||||
value, increasing transfer speed and efficiency.
|
||||
|
||||
:returns: True if link MTU discovery is enabled, False if not.
|
||||
*/
|
||||
inline static bool link_mtu_discovery() { return __link_mtu_discovery; }
|
||||
inline static void link_mtu_discovery(bool link_mtu_discovery) { __link_mtu_discovery = link_mtu_discovery; }
|
||||
|
||||
/*
|
||||
Returns whether remote management is enabled for the
|
||||
running instance.
|
||||
|
||||
When remote management is enabled, authenticated peers
|
||||
can remotely query and manage this instance.
|
||||
|
||||
:returns: True if remote management is enabled, False if not.
|
||||
*/
|
||||
inline static bool remote_management_enabled() { return __remote_management_enabled; }
|
||||
inline static void remote_management_enabled(bool remote_management_enabled) { __remote_management_enabled = remote_management_enabled; }
|
||||
|
||||
inline static bool probe_destination_enabled() { return __allow_probes; }
|
||||
inline static void probe_destination_enabled(bool allow_probes) { __allow_probes = allow_probes; }
|
||||
|
||||
// getters/setters
|
||||
inline bool is_connected_to_shared_instance() const { assert(_object); return _object->_is_connected_to_shared_instance; }
|
||||
|
||||
private:
|
||||
class Object {
|
||||
public:
|
||||
Object() {
|
||||
MEM("Reticulum data object created, this: " + std::to_string((uintptr_t)this));
|
||||
}
|
||||
virtual ~Object() {
|
||||
MEM("Reticulum data object destroyed, this: " + std::to_string((uintptr_t)this));
|
||||
}
|
||||
private:
|
||||
|
||||
uint16_t _local_interface_port = 37428;
|
||||
uint16_t _local_control_port = 37429;
|
||||
bool _share_instance = true;
|
||||
//p _rpc_listener = None
|
||||
|
||||
//p _ifac_salt = Reticulum.IFAC_SALT
|
||||
|
||||
bool _is_shared_instance = false;
|
||||
bool _is_connected_to_shared_instance = false;
|
||||
bool _is_standalone_instance = false;
|
||||
//p _jobs_thread = None
|
||||
double _last_data_persist = Utilities::OS::time();
|
||||
double _last_cache_clean = 0.0;
|
||||
|
||||
// CBA
|
||||
double _jobs_last_run = Utilities::OS::time();
|
||||
|
||||
friend class Reticulum;
|
||||
};
|
||||
std::shared_ptr<Object> _object;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
4305
lib/microReticulum/src/Transport.cpp
Executable file
4305
lib/microReticulum/src/Transport.cpp
Executable file
File diff suppressed because it is too large
Load Diff
504
lib/microReticulum/src/Transport.h
Executable file
504
lib/microReticulum/src/Transport.h
Executable file
@@ -0,0 +1,504 @@
|
||||
#pragma once
|
||||
|
||||
#include "Packet.h"
|
||||
#include "Bytes.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
|
||||
//#define INTERFACES_SET
|
||||
//#define INTERFACES_LIST
|
||||
#define INTERFACES_MAP
|
||||
|
||||
//#define DESTINATIONS_SET
|
||||
#define DESTINATIONS_MAP
|
||||
|
||||
namespace RNS {
|
||||
|
||||
class Reticulum;
|
||||
class Identity;
|
||||
class Destination;
|
||||
class Interface;
|
||||
class Link;
|
||||
class Packet;
|
||||
class PacketReceipt;
|
||||
|
||||
class AnnounceHandler {
|
||||
public:
|
||||
// The initialisation method takes the optional
|
||||
// aspect_filter argument. If aspect_filter is set to
|
||||
// None, all announces will be passed to the instance.
|
||||
// If only some announces are wanted, it can be set to
|
||||
// an aspect string.
|
||||
AnnounceHandler(const char* aspect_filter = nullptr) { if (aspect_filter != nullptr) _aspect_filter = aspect_filter; }
|
||||
// This method will be called by Reticulums Transport
|
||||
// system when an announce arrives that matches the
|
||||
// configured aspect filter. Filters must be specific,
|
||||
// and cannot use wildcards.
|
||||
virtual void received_announce(const Bytes& destination_hash, const Identity& announced_identity, const Bytes& app_data) = 0;
|
||||
std::string& aspect_filter() { return _aspect_filter; }
|
||||
private:
|
||||
std::string _aspect_filter;
|
||||
};
|
||||
using HAnnounceHandler = std::shared_ptr<AnnounceHandler>;
|
||||
|
||||
/*
|
||||
Through static methods of this class you can interact with the
|
||||
Transport system of Reticulum.
|
||||
*/
|
||||
class Transport {
|
||||
|
||||
public:
|
||||
class Callbacks {
|
||||
public:
|
||||
using receive_packet = void(*)(const Bytes& raw, const Interface& interface);
|
||||
using transmit_packet = void(*)(const Bytes& raw, const Interface& interface);
|
||||
using filter_packet = bool(*)(const Packet& packet);
|
||||
public:
|
||||
receive_packet _receive_packet = nullptr;
|
||||
transmit_packet _transmit_packet = nullptr;
|
||||
filter_packet _filter_packet = nullptr;
|
||||
friend class Transport;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
class PacketEntry {
|
||||
public:
|
||||
PacketEntry() {}
|
||||
PacketEntry(const Bytes& raw, double sent_at, const Bytes& destination_hash) :
|
||||
_raw(raw),
|
||||
_sent_at(sent_at),
|
||||
_destination_hash(destination_hash)
|
||||
{
|
||||
}
|
||||
PacketEntry(const Packet& packet) :
|
||||
_raw(packet.raw()),
|
||||
_sent_at(packet.sent_at()),
|
||||
_destination_hash(packet.destination_hash())
|
||||
{
|
||||
}
|
||||
public:
|
||||
Bytes _raw;
|
||||
double _sent_at = 0;
|
||||
Bytes _destination_hash;
|
||||
bool _cached = false;
|
||||
#ifndef NDEBUG
|
||||
inline std::string debugString() const {
|
||||
std::string dump;
|
||||
dump = "PacketEntry: destination_hash=" + _destination_hash.toHex() +
|
||||
" sent_at=" + std::to_string(_sent_at);
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
// CBA TODO Analyze safety of using Packet references here
|
||||
class DestinationEntry {
|
||||
public:
|
||||
DestinationEntry() {}
|
||||
DestinationEntry(double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, const Bytes& receiving_interface, const Bytes& packet) :
|
||||
_timestamp(timestamp),
|
||||
_received_from(received_from),
|
||||
_hops(announce_hops),
|
||||
_expires(expires),
|
||||
_random_blobs(random_blobs),
|
||||
_receiving_interface(receiving_interface),
|
||||
_announce_packet(packet)
|
||||
{
|
||||
}
|
||||
public:
|
||||
inline Interface receiving_interface() const { return find_interface_from_hash(_receiving_interface); }
|
||||
inline Packet announce_packet() const { return get_cached_packet(_announce_packet); }
|
||||
public:
|
||||
double _timestamp = 0;
|
||||
Bytes _received_from;
|
||||
uint8_t _hops = 0;
|
||||
double _expires = 0;
|
||||
std::set<Bytes> _random_blobs;
|
||||
//Interface _receiving_interface = {Type::NONE};
|
||||
Bytes _receiving_interface;
|
||||
//const Packet& _announce_packet;
|
||||
//Packet _announce_packet = {Type::NONE};
|
||||
Bytes _announce_packet;
|
||||
inline bool operator < (const DestinationEntry& entry) const {
|
||||
// sort by ascending timestamp (oldest entries at the top)
|
||||
return _timestamp < entry._timestamp;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
inline std::string debugString() const {
|
||||
std::string dump;
|
||||
dump = "DestinationEntry: timestamp=" + std::to_string(_timestamp) +
|
||||
" received_from=" + _received_from.toHex() +
|
||||
" hops=" + std::to_string(_hops) +
|
||||
" expires=" + std::to_string(_expires) +
|
||||
//" random_blobs=" + _random_blobs +
|
||||
" receiving_interface=" + _receiving_interface.toHex() +
|
||||
" announce_packet=" + _announce_packet.toHex();
|
||||
dump += " random_blobs=(";
|
||||
for (auto& blob : _random_blobs) {
|
||||
dump += blob.toHex() + ",";
|
||||
}
|
||||
dump += ")";
|
||||
return dump;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
// CBA TODO Analyze safety of using Packet references here
|
||||
class AnnounceEntry {
|
||||
public:
|
||||
AnnounceEntry(double timestamp, double retransmit_timeout, uint8_t retries, const Bytes& received_from, uint8_t hops, const Packet& packet, uint8_t local_rebroadcasts, bool block_rebroadcasts, const Interface& attached_interface) :
|
||||
_timestamp(timestamp),
|
||||
_retransmit_timeout(retransmit_timeout),
|
||||
_retries(retries),
|
||||
_received_from(received_from),
|
||||
_hops(hops),
|
||||
_packet(packet),
|
||||
_local_rebroadcasts(local_rebroadcasts),
|
||||
_block_rebroadcasts(block_rebroadcasts),
|
||||
_attached_interface(attached_interface)
|
||||
{
|
||||
}
|
||||
public:
|
||||
double _timestamp = 0;
|
||||
double _retransmit_timeout = 0;
|
||||
uint8_t _retries = 0;
|
||||
const Bytes _received_from;
|
||||
uint8_t _hops = 0;
|
||||
// CBA Storing packet reference causes memory issues, presumably because orignal packet is being destroyed
|
||||
// MUST use instance instad of reference!!!
|
||||
//const Packet& _packet;
|
||||
const Packet _packet = {Type::NONE};
|
||||
uint8_t _local_rebroadcasts = 0;
|
||||
bool _block_rebroadcasts = false;
|
||||
const Interface _attached_interface = {Type::NONE};
|
||||
};
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
class LinkEntry {
|
||||
public:
|
||||
LinkEntry(double timestamp, const Bytes& next_hop, const Interface& outbound_interface, uint8_t remaining_hops, const Interface& receiving_interface, uint8_t hops, const Bytes& destination_hash, bool validated, double proof_timeout) :
|
||||
_timestamp(timestamp),
|
||||
_next_hop(next_hop),
|
||||
_outbound_interface(outbound_interface),
|
||||
_remaining_hops(remaining_hops),
|
||||
_receiving_interface(receiving_interface),
|
||||
_hops(hops),
|
||||
_destination_hash(destination_hash),
|
||||
_validated(validated),
|
||||
_proof_timeout(proof_timeout)
|
||||
{
|
||||
}
|
||||
public:
|
||||
double _timestamp = 0;
|
||||
const Bytes _next_hop;
|
||||
const Interface _outbound_interface = {Type::NONE};
|
||||
uint8_t _remaining_hops = 0;
|
||||
Interface _receiving_interface = {Type::NONE};
|
||||
uint8_t _hops = 0;
|
||||
const Bytes _destination_hash;
|
||||
bool _validated = false;
|
||||
double _proof_timeout = 0;
|
||||
};
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
class ReverseEntry {
|
||||
public:
|
||||
ReverseEntry(const Interface& receiving_interface, const Interface& outbound_interface, double timestamp) :
|
||||
_receiving_interface(receiving_interface),
|
||||
_outbound_interface(outbound_interface),
|
||||
_timestamp(timestamp)
|
||||
{
|
||||
}
|
||||
public:
|
||||
Interface _receiving_interface = {Type::NONE};
|
||||
const Interface _outbound_interface = {Type::NONE};
|
||||
double _timestamp = 0;
|
||||
};
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
class PathRequestEntry {
|
||||
public:
|
||||
PathRequestEntry(const Bytes& destination_hash, double timeout, const Interface& requesting_interface) :
|
||||
_destination_hash(destination_hash),
|
||||
_timeout(timeout),
|
||||
_requesting_interface(requesting_interface)
|
||||
{
|
||||
}
|
||||
public:
|
||||
const Bytes _destination_hash;
|
||||
double _timeout = 0;
|
||||
const Interface _requesting_interface = {Type::NONE};
|
||||
};
|
||||
|
||||
/*
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
class SerialisedEntry {
|
||||
public:
|
||||
SerialisedEntry(const Bytes& destination_hash, double timestamp, const Bytes& received_from, uint8_t announce_hops, double expires, const std::set<Bytes>& random_blobs, Interface& receiving_interface, const Packet& packet) :
|
||||
_destination_hash(destination_hash),
|
||||
_timestamp(timestamp),
|
||||
_hops(announce_hops),
|
||||
_expires(expires),
|
||||
_random_blobs(random_blobs),
|
||||
_receiving_interface(receiving_interface),
|
||||
_announce_packet(packet)
|
||||
{
|
||||
}
|
||||
public:
|
||||
const Bytes _destination_hash;
|
||||
double _timestamp = 0;
|
||||
const Bytes _received_from;
|
||||
uint8_t _hops = 0;
|
||||
double _expires = 0;
|
||||
std::set<Bytes> _random_blobs;
|
||||
Interface _receiving_interface = {Type::NONE};
|
||||
Packet _announce_packet = {Type::NONE};
|
||||
};
|
||||
*/
|
||||
|
||||
// CBA TODO Analyze safety of using Inrerface references here
|
||||
class TunnelEntry {
|
||||
public:
|
||||
TunnelEntry(const Bytes& tunnel_id, const Bytes& interface_hash, double expires) :
|
||||
_tunnel_id(tunnel_id),
|
||||
_interface_hash(interface_hash),
|
||||
_expires(expires)
|
||||
{
|
||||
}
|
||||
public:
|
||||
const Bytes _tunnel_id;
|
||||
const Bytes _interface_hash;
|
||||
std::map<Bytes, DestinationEntry> _serialised_paths;
|
||||
double _expires = 0;
|
||||
};
|
||||
|
||||
class RateEntry {
|
||||
public:
|
||||
RateEntry(double now) :
|
||||
_last(now)
|
||||
{
|
||||
_timestamps.push_back(now);
|
||||
}
|
||||
public:
|
||||
double _last = 0.0;
|
||||
double _rate_violations = 0.0;
|
||||
double _blocked_until = 0.0;
|
||||
std::vector<double> _timestamps;
|
||||
};
|
||||
|
||||
public:
|
||||
static void start(const Reticulum& reticulum_instance);
|
||||
static void loop();
|
||||
static void jobs();
|
||||
static void transmit(Interface& interface, const Bytes& raw);
|
||||
static bool outbound(Packet& packet);
|
||||
static bool packet_filter(const Packet& packet);
|
||||
//static void inbound(const Bytes& raw, const Interface& interface = {Type::NONE});
|
||||
static void inbound(const Bytes& raw, const Interface& interface);
|
||||
static void inbound(const Bytes& raw);
|
||||
static void synthesize_tunnel(const Interface& interface);
|
||||
static void tunnel_synthesize_handler(const Bytes& data, const Packet& packet);
|
||||
static void handle_tunnel(const Bytes& tunnel_id, const Interface& interface);
|
||||
static void register_interface(Interface& interface);
|
||||
static void deregister_interface(const Interface& interface);
|
||||
inline static const std::map<Bytes, Interface&> get_interfaces() { return _interfaces; }
|
||||
static void register_destination(Destination& destination);
|
||||
static void deregister_destination(const Destination& destination);
|
||||
static void register_link(Link& link);
|
||||
static void activate_link(Link& link);
|
||||
static void register_announce_handler(HAnnounceHandler handler);
|
||||
static void deregister_announce_handler(HAnnounceHandler handler);
|
||||
static Interface find_interface_from_hash(const Bytes& interface_hash);
|
||||
static bool should_cache_packet(const Packet& packet);
|
||||
static bool cache_packet(const Packet& packet, bool force_cache = false);
|
||||
static Packet get_cached_packet(const Bytes& packet_hash);
|
||||
static bool clear_cached_packet(const Bytes& packet_hash);
|
||||
static bool cache_request_packet(const Packet& packet);
|
||||
static void cache_request(const Bytes& packet_hash, const Destination& destination);
|
||||
static bool remove_path(const Bytes& destination_hash);
|
||||
static bool has_path(const Bytes& destination_hash);
|
||||
static uint8_t hops_to(const Bytes& destination_hash);
|
||||
static Bytes next_hop(const Bytes& destination_hash);
|
||||
static Interface next_hop_interface(const Bytes& destination_hash);
|
||||
static uint32_t next_hop_interface_bitrate(const Bytes& destination_hash);
|
||||
static uint16_t next_hop_interface_hw_mtu(const Bytes& destination_hash);
|
||||
static double next_hop_per_bit_latency(const Bytes& destination_hash);
|
||||
static double next_hop_per_byte_latency(const Bytes& destination_hash);
|
||||
static double first_hop_timeout(const Bytes& destination_hash);
|
||||
static double extra_link_proof_timeout(const Interface& interface);
|
||||
static bool expire_path(const Bytes& destination_hash);
|
||||
//static void request_path(const Bytes& destination_hash, const Interface& on_interface = {Type::NONE}, const Bytes& tag = {}, bool recursive = false);
|
||||
static void request_path(const Bytes& destination_hash, const Interface& on_interface, const Bytes& tag = {}, bool recursive = false);
|
||||
static void request_path(const Bytes& destination_hash);
|
||||
static void path_request_handler(const Bytes& data, const Packet& packet);
|
||||
static void path_request(const Bytes& destination_hash, bool is_from_local_client, const Interface& attached_interface, const Bytes& requestor_transport_id = {}, const Bytes& tag = {});
|
||||
static bool from_local_client(const Packet& packet);
|
||||
static bool is_local_client_interface(const Interface& interface);
|
||||
static bool interface_to_shared_instance(const Interface& interface);
|
||||
static void detach_interfaces();
|
||||
static void shared_connection_disappeared();
|
||||
static void shared_connection_reappeared();
|
||||
static void drop_announce_queues();
|
||||
static uint64_t announce_emitted(const Packet& packet);
|
||||
static void write_packet_hashlist();
|
||||
static bool read_path_table();
|
||||
static bool write_path_table();
|
||||
static void read_tunnel_table();
|
||||
static void write_tunnel_table();
|
||||
static void persist_data();
|
||||
static void clean_caches();
|
||||
static void dump_stats();
|
||||
static void exit_handler();
|
||||
|
||||
static uint16_t remove_reverse_entries(const std::vector<Bytes>& hashes);
|
||||
static uint16_t remove_links(const std::vector<Bytes>& hashes);
|
||||
static uint16_t remove_paths(const std::vector<Bytes>& hashes);
|
||||
static uint16_t remove_discovery_path_requests(const std::vector<Bytes>& hashes);
|
||||
static uint16_t remove_tunnels(const std::vector<Bytes>& hashes);
|
||||
|
||||
static Destination find_destination_from_hash(const Bytes& destination_hash);
|
||||
|
||||
// CBA
|
||||
static void cull_path_table();
|
||||
|
||||
// getters/setters
|
||||
static inline void set_receive_packet_callback(Callbacks::receive_packet callback) { _callbacks._receive_packet = callback; }
|
||||
static inline void set_transmit_packet_callback(Callbacks::transmit_packet callback) { _callbacks._transmit_packet = callback; }
|
||||
static inline void set_filter_packet_callback(Callbacks::filter_packet callback) { _callbacks._filter_packet = callback; }
|
||||
static inline const Reticulum& reticulum() { return _owner; }
|
||||
static inline const Identity& identity() { return _identity; }
|
||||
inline static uint16_t path_table_maxsize() { return _path_table_maxsize; }
|
||||
inline static void path_table_maxsize(uint16_t path_table_maxsize) { _path_table_maxsize = path_table_maxsize; }
|
||||
inline static uint16_t probe_destination_enabled() { return _path_table_maxpersist; }
|
||||
inline static void path_table_maxpersist(uint16_t path_table_maxpersist) { _path_table_maxpersist = path_table_maxpersist; }
|
||||
// CBA TEST
|
||||
static inline void identity(Identity& identity) { _identity = identity; }
|
||||
|
||||
inline static const std::map<Bytes, DestinationEntry>& get_destination_table() { return _destination_table; }
|
||||
inline static const std::map<Bytes, RateEntry>& get_announce_rate_table() { return _announce_rate_table; }
|
||||
inline static const std::map<Bytes, LinkEntry>& get_link_table() { return _link_table; }
|
||||
|
||||
private:
|
||||
// CBA MUST use references to interfaces here in order for virtul overrides for send/receive to work
|
||||
#if defined(INTERFACES_SET)
|
||||
// set sorted, can use find
|
||||
//static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _interfaces; // All active interfaces
|
||||
static std::set<std::reference_wrapper<Interface>, std::less<Interface>> _interfaces; // All active interfaces
|
||||
#elif defined(INTERFACES_LIST)
|
||||
// list is unsorted, can't use find
|
||||
static std::list<std::reference_wrapper<Interface>> _interfaces; // All active interfaces
|
||||
#elif defined(INTERFACES_MAP)
|
||||
// map is sorted, can use find
|
||||
static std::map<Bytes, Interface&> _interfaces; // All active interfaces
|
||||
#endif
|
||||
#if defined(DESTINATIONS_SET)
|
||||
static std::set<Destination> _destinations; // All active destinations
|
||||
#elif defined(DESTINATIONS_MAP)
|
||||
static std::map<Bytes, Destination> _destinations; // All active destinations
|
||||
#endif
|
||||
// CBA TODO: Reconsider using std::set for enforcing uniqueness. Maybe consider std::map keyed on hash instead
|
||||
static std::set<Link> _pending_links; // Links that are being established
|
||||
static std::set<Link> _active_links; // Links that are active
|
||||
static std::set<Bytes> _packet_hashlist; // A list of packet hashes for duplicate detection
|
||||
static std::list<PacketReceipt> _receipts; // Receipts of all outgoing packets for proof processing
|
||||
|
||||
// TODO: "destination_table" should really be renamed to "path_table"
|
||||
// Notes on memory usage: 1 megabyte of memory can store approximately
|
||||
// 55.100 path table entries or approximately 22.300 link table entries.
|
||||
|
||||
static std::map<Bytes, AnnounceEntry> _announce_table; // A table for storing announces currently waiting to be retransmitted
|
||||
static std::map<Bytes, DestinationEntry> _destination_table; // A lookup table containing the next hop to a given destination
|
||||
static std::map<Bytes, ReverseEntry> _reverse_table; // A lookup table for storing packet hashes used to return proofs and replies
|
||||
static std::map<Bytes, LinkEntry> _link_table; // A lookup table containing hops for links
|
||||
static std::map<Bytes, AnnounceEntry> _held_announces; // A table containing temporarily held announce-table entries
|
||||
static std::set<HAnnounceHandler> _announce_handlers; // A table storing externally registered announce handlers
|
||||
static std::map<Bytes, TunnelEntry> _tunnels; // A table storing tunnels to other transport instances
|
||||
static std::map<Bytes, RateEntry> _announce_rate_table; // A table for keeping track of announce rates
|
||||
static std::map<Bytes, double> _path_requests; // A table for storing path request timestamps
|
||||
|
||||
static std::map<Bytes, PathRequestEntry> _discovery_path_requests; // A table for keeping track of path requests on behalf of other nodes
|
||||
static std::set<Bytes> _discovery_pr_tags; // A table for keeping track of tagged path requests
|
||||
|
||||
// Transport control destinations are used
|
||||
// for control purposes like path requests
|
||||
static std::set<Destination> _control_destinations;
|
||||
static std::set<Bytes> _control_hashes;
|
||||
|
||||
// Interfaces for communicating with
|
||||
// local clients connected to a shared
|
||||
// Reticulum instance
|
||||
//static std::set<Interface> _local_client_interfaces;
|
||||
static std::set<std::reference_wrapper<const Interface>, std::less<const Interface>> _local_client_interfaces;
|
||||
|
||||
static std::map<Bytes, const Interface&> _pending_local_path_requests;
|
||||
|
||||
// CBA
|
||||
static std::map<Bytes, PacketEntry> _packet_table; // A lookup table containing announce packets for known paths
|
||||
|
||||
//z _local_client_rssi_cache = []
|
||||
//z _local_client_snr_cache = []
|
||||
static uint16_t _LOCAL_CLIENT_CACHE_MAXSIZE;
|
||||
|
||||
static double _start_time;
|
||||
static bool _jobs_locked;
|
||||
static bool _jobs_running;
|
||||
static float _job_interval;
|
||||
static double _jobs_last_run;
|
||||
static double _links_last_checked;
|
||||
static float _links_check_interval;
|
||||
static double _receipts_last_checked;
|
||||
static float _receipts_check_interval;
|
||||
static double _announces_last_checked;
|
||||
static float _announces_check_interval;
|
||||
static double _tables_last_culled;
|
||||
static float _tables_cull_interval;
|
||||
static bool _saving_path_table;
|
||||
static uint16_t _hashlist_maxsize;
|
||||
static uint16_t _max_pr_tags;
|
||||
|
||||
// CBA
|
||||
static uint16_t _path_table_maxsize;
|
||||
static uint16_t _path_table_maxpersist;
|
||||
static double _last_saved;
|
||||
static float _save_interval;
|
||||
static uint32_t _destination_table_crc;
|
||||
|
||||
static Reticulum _owner;
|
||||
static Identity _identity;
|
||||
|
||||
// CBA
|
||||
static Callbacks _callbacks;
|
||||
|
||||
// CBA Stats
|
||||
static uint32_t _packets_sent;
|
||||
static uint32_t _packets_received;
|
||||
static uint32_t _destinations_added;
|
||||
static size_t _last_memory;
|
||||
static size_t _last_flash;
|
||||
};
|
||||
|
||||
template <typename M, typename S>
|
||||
void MapToValues(const M& m, S& s) {
|
||||
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
|
||||
s.insert(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename M, typename S>
|
||||
void MapToPairs(const M& m, S& s) {
|
||||
for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) {
|
||||
s.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
549
lib/microReticulum/src/Type.h
Executable file
549
lib/microReticulum/src/Type.h
Executable file
@@ -0,0 +1,549 @@
|
||||
#pragma once
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
namespace RNS { namespace Type {
|
||||
|
||||
// generic empty object constructor type
|
||||
enum NoneConstructor {
|
||||
NONE
|
||||
};
|
||||
|
||||
namespace Persistence {
|
||||
//static const uint16_t DOCUMENT_MAXSIZE = 1024;
|
||||
static const uint16_t DOCUMENT_MAXSIZE = 8192;
|
||||
//static const uint16_t DOCUMENT_MAXSIZE = 16384;
|
||||
static const uint16_t BUFFER_MAXSIZE = Persistence::DOCUMENT_MAXSIZE * 1.5; // Json write buffer of 1.5 times document seems to be sufficient
|
||||
}
|
||||
|
||||
namespace Cryptography {
|
||||
namespace Fernet {
|
||||
static const uint8_t FERNET_OVERHEAD = 48; // Bytes
|
||||
}
|
||||
namespace Token {
|
||||
static const uint8_t TOKEN_OVERHEAD = 48; // Bytes
|
||||
enum token_mode {
|
||||
MODE_AES = 0x01,
|
||||
MODE_AES_128_CBC = 0x02,
|
||||
MODE_AES_256_CBC = 0x03,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace Reticulum {
|
||||
|
||||
/*
|
||||
The MTU that Reticulum adheres to, and will expect other peers to
|
||||
adhere to. By default, the MTU is 507 bytes. In custom RNS network
|
||||
implementations, it is possible to change this value, but doing so will
|
||||
completely break compatibility with all other RNS networks. An identical
|
||||
MTU is a prerequisite for peers to communicate in the same network.
|
||||
|
||||
Unless you really know what you are doing, the MTU should be left at
|
||||
the default value.
|
||||
|
||||
Future minimum will probably be locked in at 251 bytes to support
|
||||
networks with segments of different MTUs. Absolute minimum is 219.
|
||||
*/
|
||||
static const uint16_t MTU = 500;
|
||||
|
||||
/*
|
||||
Whether automatic link MTU discovery is enabled by default in this
|
||||
release. Link MTU discovery significantly increases throughput over
|
||||
fast links, but requires all intermediary hops to also support it.
|
||||
Support for this feature was added in RNS version 0.9.0. This option
|
||||
will become enabled by default in the near future. Please update your
|
||||
RNS instances.
|
||||
*/
|
||||
static const bool LINK_MTU_DISCOVERY = true;
|
||||
|
||||
static const uint16_t MAX_QUEUED_ANNOUNCES = 16384;
|
||||
static const uint32_t QUEUED_ANNOUNCE_LIFE = 60*60*24;
|
||||
|
||||
static const uint8_t ANNOUNCE_CAP = 2;
|
||||
/*
|
||||
The maximum percentage of interface bandwidth that, at any given time,
|
||||
may be used to propagate announces. If an announce was scheduled for
|
||||
broadcasting on an interface, but doing so would exceed the allowed
|
||||
bandwidth allocation, the announce will be queued for transmission
|
||||
when there is bandwidth available.
|
||||
|
||||
Reticulum will always prioritise propagating announces with fewer
|
||||
hops, ensuring that distant, large networks with many peers on fast
|
||||
links don't overwhelm the capacity of smaller networks on slower
|
||||
mediums. If an announce remains queued for an extended amount of time,
|
||||
it will eventually be dropped.
|
||||
|
||||
This value will be applied by default to all created interfaces,
|
||||
but it can be configured individually on a per-interface basis.
|
||||
*/
|
||||
|
||||
static const uint16_t MINIMUM_BITRATE = 500;
|
||||
|
||||
// TODO: To reach the 300bps level without unreasonably impacting
|
||||
// performance on faster links, we need a mechanism for setting
|
||||
// this value more intelligently. One option could be inferring it
|
||||
// from interface speed, but a better general approach would most
|
||||
// probably be to let Reticulum somehow continously build a map of
|
||||
// per-hop latencies and use this map for the timeout calculation.
|
||||
static const uint8_t DEFAULT_PER_HOP_TIMEOUT = 6;
|
||||
|
||||
static const uint16_t HASHLENGTH = 256; // In bits
|
||||
// Length of truncated hashes in bits.
|
||||
static const uint16_t TRUNCATED_HASHLENGTH = 128; // In bits
|
||||
|
||||
static const uint16_t HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*1; // In bytes
|
||||
static const uint16_t HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH/8)*2; // In bytes
|
||||
static const uint16_t IFAC_MIN_SIZE = 1;
|
||||
//z IFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8")
|
||||
|
||||
static const uint16_t MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE;
|
||||
|
||||
static const uint32_t RESOURCE_CACHE = 60*60*24;
|
||||
// CBA TEST
|
||||
//static const uint16_t JOB_INTERVAL = 60*5;
|
||||
static const uint16_t JOB_INTERVAL = 60;
|
||||
// CBA TEST
|
||||
//static const uint16_t CLEAN_INTERVAL = 60*15;
|
||||
static const uint16_t CLEAN_INTERVAL = 60;
|
||||
// CBA MCU
|
||||
//static const uint16_t PERSIST_INTERVAL = 60*60*12;
|
||||
// CBA TEST
|
||||
//static const uint16_t PERSIST_INTERVAL = 60*60;
|
||||
static const uint16_t PERSIST_INTERVAL = 60;
|
||||
static const uint16_t GRACIOUS_PERSIST_INTERVAL = 60*5;
|
||||
|
||||
static const uint8_t DESTINATION_LENGTH = TRUNCATED_HASHLENGTH/8; // In bytes
|
||||
|
||||
// CBA MCU
|
||||
//static const uint8_t FILEPATH_MAXSIZE = 64;
|
||||
static const uint8_t FILEPATH_MAXSIZE = 96;
|
||||
|
||||
}
|
||||
|
||||
namespace Identity {
|
||||
|
||||
// The curve used for Elliptic Curve DH key exchanges
|
||||
//static const char CURVE[] = "Curve25519";
|
||||
static constexpr const char* CURVE = "Curve25519";
|
||||
|
||||
// X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
|
||||
static const uint16_t KEYSIZE = 256*2;
|
||||
|
||||
// X.25519 ratchet key size in bits.
|
||||
static const uint16_t RATCHETSIZE = 256;
|
||||
|
||||
/*
|
||||
The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
|
||||
announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
|
||||
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
|
||||
*/
|
||||
static const uint32_t RATCHET_EXPIRY = 60*60*24*30;
|
||||
|
||||
// Non-configurable constants
|
||||
static const uint8_t FERNET_OVERHEAD = Cryptography::Fernet::FERNET_OVERHEAD;
|
||||
static const uint8_t TOKEN_OVERHEAD = Cryptography::Token::TOKEN_OVERHEAD;
|
||||
static const uint8_t AES128_BLOCKSIZE = 16; // In bytes
|
||||
static const uint16_t HASHLENGTH = Reticulum::HASHLENGTH; // In bits
|
||||
static const uint16_t SIGLENGTH = KEYSIZE; // In bits
|
||||
|
||||
static const uint8_t NAME_HASH_LENGTH = 80;
|
||||
static const uint8_t RANDOM_HASH_LENGTH = 80;
|
||||
// Constant specifying the truncated hash length (in bits) used by Reticulum
|
||||
// for addressable hashes and other purposes. Non-configurable.
|
||||
static const uint16_t TRUNCATED_HASHLENGTH = Reticulum::TRUNCATED_HASHLENGTH; // In bits
|
||||
|
||||
static const uint16_t DERIVED_KEY_LENGTH = 512/8;
|
||||
static const uint16_t DERIVED_KEY_LENGTH_LEGACY = 256/8;
|
||||
|
||||
}
|
||||
|
||||
namespace Destination {
|
||||
|
||||
enum types {
|
||||
SINGLE = 0x00,
|
||||
GROUP = 0x01,
|
||||
PLAIN = 0x02,
|
||||
LINK = 0x03,
|
||||
};
|
||||
|
||||
enum proof_strategies {
|
||||
PROVE_NONE = 0x21,
|
||||
PROVE_APP = 0x22,
|
||||
PROVE_ALL = 0x23,
|
||||
};
|
||||
|
||||
enum request_policies {
|
||||
ALLOW_NONE = 0x00,
|
||||
ALLOW_ALL = 0x01,
|
||||
ALLOW_LIST = 0x02,
|
||||
};
|
||||
|
||||
enum directions {
|
||||
IN = 0x11,
|
||||
OUT = 0x12,
|
||||
};
|
||||
|
||||
const uint8_t PR_TAG_WINDOW = 30;
|
||||
|
||||
}
|
||||
|
||||
namespace Link {
|
||||
|
||||
// The curve used for Elliptic Curve DH key exchanges
|
||||
static constexpr const char* CURVE = Identity::CURVE;
|
||||
|
||||
static const uint16_t ECPUBSIZE = 32+32;
|
||||
static const uint8_t KEYSIZE = 32;
|
||||
|
||||
//static const uint16_t MDU = floor((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
|
||||
static const uint16_t MDU = ((Reticulum::MTU-Reticulum::IFAC_MIN_SIZE-Reticulum::HEADER_MINSIZE-Identity::FERNET_OVERHEAD)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
|
||||
|
||||
// Timeout for link establishment in seconds per hop to destination.
|
||||
static const uint8_t ESTABLISHMENT_TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
|
||||
|
||||
static const uint8_t LINK_MTU_SIZE = 3;
|
||||
static const uint8_t TRAFFIC_TIMEOUT_MIN_MS = 5;
|
||||
// RTT timeout factor used in link timeout calculation.
|
||||
static const uint8_t TRAFFIC_TIMEOUT_FACTOR = 6;
|
||||
static const float KEEPALIVE_MAX_RTT = 1.75;
|
||||
static const uint8_t KEEPALIVE_TIMEOUT_FACTOR = 4;
|
||||
// Grace period in seconds used in link timeout calculation.
|
||||
static const uint8_t STALE_GRACE = 2;
|
||||
// Interval for sending keep-alive packets on established links in seconds.
|
||||
static const uint16_t KEEPALIVE = 360;
|
||||
/*
|
||||
If no traffic or keep-alive packets are received within this period, the
|
||||
link will be marked as stale, and a final keep-alive packet will be sent.
|
||||
If after this no traffic or keep-alive packets are received within ``RTT`` *
|
||||
``KEEPALIVE_TIMEOUT_FACTOR`` + ``STALE_GRACE``, the link is considered timed out,
|
||||
and will be torn down.
|
||||
*/
|
||||
static const uint8_t STALE_FACTOR = 2;
|
||||
static const uint16_t STALE_TIME = 2*KEEPALIVE;
|
||||
|
||||
static const uint8_t WATCHDOG_MAX_SLEEP = 5;
|
||||
|
||||
static const uint64_t MTU_BYTEMASK = 0x1FFFFF;
|
||||
static const uint8_t MODE_BYTEMASK = 0xE0;
|
||||
|
||||
enum status {
|
||||
PENDING = 0x00,
|
||||
HANDSHAKE = 0x01,
|
||||
ACTIVE = 0x02,
|
||||
STALE = 0x03,
|
||||
CLOSED = 0x04
|
||||
};
|
||||
|
||||
enum teardown_reason {
|
||||
TEARDOWN_NONE = 0x00,
|
||||
TIMEOUT = 0x01,
|
||||
INITIATOR_CLOSED = 0x02,
|
||||
DESTINATION_CLOSED = 0x03,
|
||||
};
|
||||
|
||||
enum resource_strategy {
|
||||
ACCEPT_NONE = 0x00,
|
||||
ACCEPT_APP = 0x01,
|
||||
ACCEPT_ALL = 0x02,
|
||||
};
|
||||
|
||||
enum link_mode {
|
||||
MODE_AES128_CBC = 0x00,
|
||||
MODE_AES256_CBC = 0x01,
|
||||
MODE_AES256_GCM = 0x02,
|
||||
MODE_OTP_RESERVED = 0x03,
|
||||
MODE_PQ_RESERVED_1 = 0x04,
|
||||
MODE_PQ_RESERVED_2 = 0x05,
|
||||
MODE_PQ_RESERVED_3 = 0x06,
|
||||
MODE_PQ_RESERVED_4 = 0x07,
|
||||
};
|
||||
|
||||
/*
|
||||
MODE_DESCRIPTIONS = {MODE_AES128_CBC: "AES_128_CBC",
|
||||
MODE_AES256_CBC: "AES_256_CBC",
|
||||
MODE_AES256_GCM: "MODE_AES256_GCM",
|
||||
MODE_OTP_RESERVED: "MODE_OTP_RESERVED",
|
||||
MODE_PQ_RESERVED_1: "MODE_PQ_RESERVED_1",
|
||||
MODE_PQ_RESERVED_2: "MODE_PQ_RESERVED_2",
|
||||
MODE_PQ_RESERVED_3: "MODE_PQ_RESERVED_3",
|
||||
MODE_PQ_RESERVED_4: "MODE_PQ_RESERVED_4"}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
namespace RequestReceipt {
|
||||
|
||||
enum status {
|
||||
FAILED = 0x00,
|
||||
SENT = 0x01,
|
||||
DELIVERED = 0x02,
|
||||
RECEIVING = 0x03,
|
||||
READY = 0x04,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Interface {
|
||||
|
||||
// Interface mode definitions
|
||||
enum modes {
|
||||
MODE_NONE = 0x00,
|
||||
MODE_FULL = 0x01,
|
||||
MODE_POINT_TO_POINT = 0x04,
|
||||
MODE_ACCESS_POINT = 0x08,
|
||||
MODE_ROAMING = 0x10,
|
||||
MODE_BOUNDARY = 0x20,
|
||||
MODE_GATEWAY = 0x40,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Packet {
|
||||
|
||||
// Packet types
|
||||
enum types {
|
||||
DATA = 0x00, // Data packets
|
||||
ANNOUNCE = 0x01, // Announces
|
||||
LINKREQUEST = 0x02, // Link requests
|
||||
PROOF = 0x03, // Proofs
|
||||
};
|
||||
|
||||
// Header types
|
||||
enum header_types {
|
||||
HEADER_1 = 0x00, // Normal header format
|
||||
HEADER_2 = 0x01, // Header format used for packets in transport
|
||||
};
|
||||
|
||||
// Packet context types
|
||||
enum context_types {
|
||||
CONTEXT_NONE = 0x00, // Generic data packet
|
||||
RESOURCE = 0x01, // Packet is part of a resource
|
||||
RESOURCE_ADV = 0x02, // Packet is a resource advertisement
|
||||
RESOURCE_REQ = 0x03, // Packet is a resource part request
|
||||
RESOURCE_HMU = 0x04, // Packet is a resource hashmap update
|
||||
RESOURCE_PRF = 0x05, // Packet is a resource proof
|
||||
RESOURCE_ICL = 0x06, // Packet is a resource initiator cancel message
|
||||
RESOURCE_RCL = 0x07, // Packet is a resource receiver cancel message
|
||||
CACHE_REQUEST = 0x08, // Packet is a cache request
|
||||
REQUEST = 0x09, // Packet is a request
|
||||
RESPONSE = 0x0A, // Packet is a response to a request
|
||||
PATH_RESPONSE = 0x0B, // Packet is a response to a path request
|
||||
COMMAND = 0x0C, // Packet is a command
|
||||
COMMAND_STATUS = 0x0D, // Packet is a status of an executed command
|
||||
CHANNEL = 0x0E, // Packet contains link channel data
|
||||
KEEPALIVE = 0xFA, // Packet is a keepalive packet
|
||||
LINKIDENTIFY = 0xFB, // Packet is a link peer identification proof
|
||||
LINKCLOSE = 0xFC, // Packet is a link close message
|
||||
LINKPROOF = 0xFD, // Packet is a link packet proof
|
||||
LRRTT = 0xFE, // Packet is a link request round-trip time measurement
|
||||
LRPROOF = 0xFF, // Packet is a link request proof
|
||||
};
|
||||
|
||||
// Context flag values
|
||||
enum context_flags {
|
||||
FLAG_SET = 0x01,
|
||||
FLAG_UNSET = 0x00,
|
||||
};
|
||||
|
||||
// This is used to calculate allowable
|
||||
// payload sizes
|
||||
static const uint16_t HEADER_MAXSIZE = Reticulum::HEADER_MAXSIZE;
|
||||
static const uint16_t MDU = Reticulum::MDU;
|
||||
|
||||
// With an MTU of 500, the maximum of data we can
|
||||
// send in a single encrypted packet is given by
|
||||
// the below calculation; 383 bytes.
|
||||
//static const uint16_t ENCRYPTED_MDU = floor((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
|
||||
//static const uint16_t ENCRYPTED_MDU;
|
||||
static const uint16_t ENCRYPTED_MDU = ((Reticulum::MDU-Identity::FERNET_OVERHEAD-Identity::KEYSIZE/16)/Identity::AES128_BLOCKSIZE)*Identity::AES128_BLOCKSIZE - 1;
|
||||
// The maximum size of the payload data in a single encrypted packet
|
||||
static const uint16_t PLAIN_MDU = MDU;
|
||||
// The maximum size of the payload data in a single unencrypted packet
|
||||
|
||||
static const uint8_t TIMEOUT_PER_HOP = Reticulum::DEFAULT_PER_HOP_TIMEOUT;
|
||||
|
||||
}
|
||||
|
||||
namespace PacketReceipt {
|
||||
|
||||
// Receipt status constants
|
||||
enum Status {
|
||||
FAILED = 0x00,
|
||||
SENT = 0x01,
|
||||
DELIVERED = 0x02,
|
||||
CULLED = 0xFF
|
||||
};
|
||||
|
||||
static const uint16_t EXPL_LENGTH = Identity::HASHLENGTH / 8 + Identity::SIGLENGTH / 8;
|
||||
static const uint16_t IMPL_LENGTH = Identity::SIGLENGTH / 8;
|
||||
|
||||
}
|
||||
|
||||
namespace Transport {
|
||||
|
||||
enum types {
|
||||
BROADCAST = 0x00,
|
||||
TRANSPORT = 0x01,
|
||||
RELAY = 0x02,
|
||||
TUNNEL = 0x03,
|
||||
NONE = 0xFF,
|
||||
};
|
||||
|
||||
enum reachabilities {
|
||||
REACHABILITY_UNREACHABLE = 0x00,
|
||||
REACHABILITY_DIRECT = 0x01,
|
||||
REACHABILITY_TRANSPORT = 0x02,
|
||||
};
|
||||
|
||||
enum state {
|
||||
STATE_UNKNOWN = 0x00,
|
||||
STATE_UNRESPONSIVE = 0x01,
|
||||
STATE_RESPONSIVE = 0x02,
|
||||
};
|
||||
|
||||
static constexpr const char* APP_NAME = "rnstransport";
|
||||
|
||||
// Maximum amount of hops that Reticulum will transport a packet.
|
||||
static const uint8_t PATHFINDER_M = 128; // Max hops
|
||||
|
||||
static const uint8_t PATHFINDER_R = 1; // Retransmit retries
|
||||
static const uint8_t PATHFINDER_G = 5; // Retry grace period
|
||||
static constexpr const float PATHFINDER_RW = 0.5; // Random window for announce rebroadcast
|
||||
|
||||
// TODO: Calculate an optimal number for this in
|
||||
// various situations
|
||||
static const uint8_t LOCAL_REBROADCASTS_MAX = 2; // How many local rebroadcasts of an announce is allowed
|
||||
|
||||
static const uint8_t PATH_REQUEST_TIMEOUT = 15; // Default timuout for client path requests in seconds
|
||||
static constexpr const float PATH_REQUEST_GRACE = 0.35; // Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
static const uint8_t PATH_REQUEST_RW = 2; // Path request random window
|
||||
static const uint8_t PATH_REQUEST_MI = 5; // Minimum interval in seconds for automated path requests
|
||||
|
||||
static constexpr const float LINK_TIMEOUT = Link::STALE_TIME * 1.25;
|
||||
static const uint16_t REVERSE_TIMEOUT = 30*60; // Reverse table entries are removed after 30 minutes
|
||||
// CBA MCU
|
||||
//static const uint16_t MAX_RECEIPTS = 1024; // Maximum number of receipts to keep track of
|
||||
static const uint16_t MAX_RECEIPTS = 20; // Maximum number of receipts to keep track of
|
||||
static const uint8_t MAX_RATE_TIMESTAMPS = 16; // Maximum number of announce timestamps to keep per destination
|
||||
static const uint8_t PERSIST_RANDOM_BLOBS = 32; // Maximum number of random blobs per destination to persist to disk
|
||||
static const uint8_t MAX_RANDOM_BLOBS = 64; // Maximum number of random blobs per destination to keep in memory
|
||||
|
||||
// CBA MCU
|
||||
//static const uint32_t DESTINATION_TIMEOUT = 60*60*24*7; // Destination table entries are removed if unused for one week
|
||||
//static const uint32_t PATHFINDER_E = 60*60*24*7; // Path expiration of one week
|
||||
//static const uint32_t AP_PATH_TIME = 60*60*24; // Path expiration of one day for Access Point paths
|
||||
//static const uint32_t ROAMING_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Roaming paths
|
||||
static const uint32_t DESTINATION_TIMEOUT = 60*60*24*1; // Destination table entries are removed if unused for one day
|
||||
static const uint32_t PATHFINDER_E = 60*60*24*1; // Path expiration of one day
|
||||
static const uint32_t AP_PATH_TIME = 60*60*6; // Path expiration of 6 hours for Access Point paths
|
||||
static const uint32_t ROAMING_PATH_TIME = 60*60*1; // Path expiration of 1 hour for Roaming paths
|
||||
|
||||
static const uint16_t LOCAL_CLIENT_CACHE_MAXSIZE = 512;
|
||||
}
|
||||
|
||||
namespace Resource {
|
||||
|
||||
// The initial window size at beginning of transfer
|
||||
static const uint8_t WINDOW = 4;
|
||||
|
||||
// Absolute minimum window size during transfer
|
||||
static const uint8_t WINDOW_MIN = 1;
|
||||
|
||||
// The maximum window size for transfers on slow links
|
||||
static const uint8_t WINDOW_MAX_SLOW = 10;
|
||||
|
||||
// The maximum window size for transfers on fast links
|
||||
static const uint8_t WINDOW_MAX_FAST = 75;
|
||||
|
||||
// For calculating maps and guard segments, this
|
||||
// must be set to the global maximum window.
|
||||
static const uint8_t WINDOW_MAX = WINDOW_MAX_FAST;
|
||||
|
||||
// If the fast rate is sustained for this many request
|
||||
// rounds, the fast link window size will be allowed.
|
||||
static const uint8_t FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2;
|
||||
|
||||
// If the RTT rate is higher than this value,
|
||||
// the max window size for fast links will be used.
|
||||
// The default is 50 Kbps (the value is stored in
|
||||
// bytes per second, hence the "/ 8").
|
||||
static const uint16_t RATE_FAST = (50*1000) / 8;
|
||||
|
||||
// The minimum allowed flexibility of the window size.
|
||||
// The difference between window_max and window_min
|
||||
// will never be smaller than this value.
|
||||
static const uint8_t WINDOW_FLEXIBILITY = 4;
|
||||
|
||||
// Number of bytes in a map hash
|
||||
static const uint8_t MAPHASH_LEN = 4;
|
||||
static const uint16_t SDU = Packet::MDU;
|
||||
static const uint8_t RANDOM_HASH_SIZE = 4;
|
||||
|
||||
// This is an indication of what the
|
||||
// maximum size a resource should be, if
|
||||
// it is to be handled within reasonable
|
||||
// time constraint, even on small systems.
|
||||
#
|
||||
// A small system in this regard is
|
||||
// defined as a Raspberry Pi, which should
|
||||
// be able to compress, encrypt and hash-map
|
||||
// the resource in about 10 seconds.
|
||||
#
|
||||
// This constant will be used when determining
|
||||
// how to sequence the sending of large resources.
|
||||
#
|
||||
// Capped at 16777215 (0xFFFFFF) per segment to
|
||||
// fit in 3 bytes in resource advertisements.
|
||||
static const uint32_t MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1;
|
||||
static const uint8_t RESPONSE_MAX_GRACE_TIME = 10;
|
||||
|
||||
// The maximum size to auto-compress with
|
||||
// bz2 before sending.
|
||||
static const uint32_t AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE;
|
||||
|
||||
static const uint8_t PART_TIMEOUT_FACTOR = 4;
|
||||
static const uint8_t PART_TIMEOUT_FACTOR_AFTER_RTT = 2;
|
||||
static const uint8_t MAX_RETRIES = 8;
|
||||
static const uint8_t MAX_ADV_RETRIES = 4;
|
||||
static const uint8_t SENDER_GRACE_TIME = 10;
|
||||
static const float RETRY_GRACE_TIME = 0.25;
|
||||
static const float PER_RETRY_DELAY = 0.5;
|
||||
|
||||
static const uint8_t WATCHDOG_MAX_SLEEP = 1;
|
||||
|
||||
static const uint8_t HASHMAP_IS_NOT_EXHAUSTED = 0x00;
|
||||
static const uint8_t HASHMAP_IS_EXHAUSTED = 0xFF;
|
||||
|
||||
// Status constants
|
||||
enum status {
|
||||
NONE = 0x00,
|
||||
QUEUED = 0x01,
|
||||
ADVERTISED = 0x02,
|
||||
TRANSFERRING = 0x03,
|
||||
AWAITING_PROOF = 0x04,
|
||||
ASSEMBLING = 0x05,
|
||||
COMPLETE = 0x06,
|
||||
FAILED = 0x07,
|
||||
CORRUPT = 0x08
|
||||
};
|
||||
|
||||
namespace ResourceAdvertisement {
|
||||
static const uint8_t OVERHEAD = 134;
|
||||
static const uint16_t HASHMAP_MAX_LEN = floor((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
|
||||
//static const uint16_t HASHMAP_MAX_LEN = ((Link::MDU-OVERHEAD)/Resource::MAPHASH_LEN);
|
||||
static const uint16_t COLLISION_GUARD_SIZE = 2*Resource::WINDOW_MAX+HASHMAP_MAX_LEN;
|
||||
|
||||
//assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Channel {
|
||||
}
|
||||
|
||||
} }
|
||||
16
lib/microReticulum/src/Utilities/Crc.cpp
Executable file
16
lib/microReticulum/src/Utilities/Crc.cpp
Executable file
@@ -0,0 +1,16 @@
|
||||
#include "Crc.h"
|
||||
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
/*static*/ uint32_t Crc::crc32(uint32_t crc, const uint8_t* buf, size_t size) {
|
||||
const unsigned char *data = (const unsigned char *)buf;
|
||||
if (data == NULL)
|
||||
return 0;
|
||||
crc ^= 0xffffffff;
|
||||
while (size--) {
|
||||
crc ^= *data++;
|
||||
for (unsigned k = 0; k < 8; k++)
|
||||
crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1;
|
||||
}
|
||||
return crc ^ 0xffffffff;
|
||||
}
|
||||
17
lib/microReticulum/src/Utilities/Crc.h
Executable file
17
lib/microReticulum/src/Utilities/Crc.h
Executable file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace RNS { namespace Utilities {
|
||||
|
||||
class Crc {
|
||||
|
||||
public:
|
||||
static uint32_t crc32(uint32_t crc, const uint8_t* buffer, size_t size);
|
||||
static inline uint32_t crc32(uint32_t crc, uint8_t byte) { return crc32(crc, &byte, sizeof(byte)); }
|
||||
static inline uint32_t crc32(uint32_t crc, const char* str) { return crc32(crc, (const uint8_t*)str, strlen(str)); }
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
289
lib/microReticulum/src/Utilities/OS.cpp
Executable file
289
lib/microReticulum/src/Utilities/OS.cpp
Executable file
@@ -0,0 +1,289 @@
|
||||
#include "OS.h"
|
||||
|
||||
#include "../Type.h"
|
||||
#include "../Log.h"
|
||||
|
||||
using namespace RNS;
|
||||
using namespace RNS::Utilities;
|
||||
|
||||
|
||||
#if defined(RNS_USE_ALLOCATOR)
|
||||
|
||||
#if defined(RNS_USE_TLSF)
|
||||
#if defined(ESP32)
|
||||
//#define BUFFER_SIZE 1024 * 80
|
||||
#define BUFFER_SIZE 0
|
||||
#define BUFFER_FRACTION 0.8
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
//#define BUFFER_SIZE 1024 * 80
|
||||
#define BUFFER_SIZE 0
|
||||
#define BUFFER_FRACTION 0.8
|
||||
#else
|
||||
#define BUFFER_SIZE 1024 * 1000
|
||||
#define BUFFER_FRACTION 0
|
||||
#endif
|
||||
|
||||
bool _tlsf_init = false;
|
||||
//char _tlsf_msg[256] = "";
|
||||
size_t _buffer_size = BUFFER_SIZE;
|
||||
size_t _contiguous_size = 0;
|
||||
|
||||
/*static*/ //tlsf_t OS::_tlsf = tlsf_create_with_pool(malloc(1024 * 1024), 1024 * 1024);
|
||||
/*static*/ tlsf_t OS::_tlsf = nullptr;
|
||||
#endif
|
||||
|
||||
uint32_t _new_count = 0;
|
||||
uint32_t _new_fault = 0;
|
||||
uint64_t _new_size = 0;
|
||||
uint32_t _delete_count = 0;
|
||||
uint32_t _delete_fault = 0;
|
||||
size_t _min_size = 0;
|
||||
size_t _max_size = 0;
|
||||
|
||||
// CBA Added attribute weak to avoid collision with new override on nrf52
|
||||
void* operator new(size_t size) {
|
||||
//__attribute__((weak)) void* operator new(size_t size) {
|
||||
#if defined(RNS_USE_TLSF)
|
||||
//if (OS::_tlsf == nullptr) {
|
||||
if (!_tlsf_init) {
|
||||
_tlsf_init = true;
|
||||
#if defined(ESP32)
|
||||
// CBA Still unknown why the call to tlsf_create_with_pool() is so flaky on ESP32 with calculated buffer size. Reuires more research and unit tests.
|
||||
_contiguous_size = ESP.getMaxAllocHeap();
|
||||
TRACEF("contiguous_size: %u", _contiguous_size);
|
||||
if (_buffer_size == 0) {
|
||||
// CBA NOTE Using fp mathhere somehow causes tlsf_create_with_pool() to fail.
|
||||
//_buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
|
||||
// Compute 80% exactly using integers
|
||||
_buffer_size = (_contiguous_size * 4) / 5;
|
||||
}
|
||||
// Round DOWN to TLSF alignment
|
||||
size_t align = tlsf_align_size();
|
||||
_buffer_size &= ~(align - 1);
|
||||
void* raw_buffer = (void*)aligned_alloc(align, _buffer_size);
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
_contiguous_size = dbgHeapFree();
|
||||
TRACEF("contiguous_size: %u", _contiguous_size);
|
||||
if (_buffer_size == 0) {
|
||||
_buffer_size = (size_t)(_contiguous_size * BUFFER_FRACTION);
|
||||
}
|
||||
// For NRF52 round to kB
|
||||
_buffer_size = (size_t)(_buffer_size / 1024) * 1024;
|
||||
TRACEF("buffer_size: %u", _buffer_size);
|
||||
void* raw_buffer = malloc(_buffer_size);
|
||||
#else
|
||||
_buffer_size = (size_t)BUFFER_SIZE;
|
||||
TRACEF("buffer_size: %u", _buffer_size);
|
||||
void* raw_buffer = malloc(_buffer_size);
|
||||
#endif
|
||||
if (raw_buffer == nullptr) {
|
||||
ERROR("-- allocation for tlsf FAILED");
|
||||
//strcpy(_tlsf_msg, "-- allocation for tlsf FAILED!!!");
|
||||
}
|
||||
else {
|
||||
#if 1
|
||||
OS::_tlsf = tlsf_create_with_pool(raw_buffer, _buffer_size);
|
||||
//if (OS::_tlsf == nullptr) {
|
||||
// sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d FAILED!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
|
||||
//}
|
||||
//else {
|
||||
// sprintf(_tlsf_msg, "initialization of tlsf with align=%d, contiguous=%d, size=%d SUCCESSFUL!!!", tlsf_align_size(), _contiguous_size, _buffer_size);
|
||||
//}
|
||||
#else
|
||||
Serial.print("raw_buffer: ");
|
||||
Serial.println((long)raw_buffer);
|
||||
Serial.print("align_size: ");
|
||||
Serial.println((long)tlsf_align_size());
|
||||
void* aligned_buffer = (void*)(((size_t)raw_buffer + (tlsf_align_size() - 1)) & ~(tlsf_align_size() - 1));
|
||||
Serial.print("aligned_buffer: ");
|
||||
Serial.println((long)aligned_buffer);
|
||||
OS::_tlsf = tlsf_create_with_pool(aligned_buffer, BUFFER_SIZE-(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
|
||||
//tlfs = tlsf_create_with_pool(aligned_buffer, buffer_size--(size_t)((uint32_t)aligned_buffer - (uint32_t)raw_buffer));
|
||||
#endif
|
||||
if (OS::_tlsf == nullptr) {
|
||||
ERROR("-- initialization of tlsf FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
++_new_count;
|
||||
_new_size += size;
|
||||
if (size < _min_size || _min_size == 0) {
|
||||
_min_size = size;
|
||||
}
|
||||
//if (size > _max_size) {
|
||||
if (size < 4192 && size > _max_size) {
|
||||
_max_size = size;
|
||||
}
|
||||
void* p;
|
||||
#if defined(RNS_USE_TLSF)
|
||||
if (OS::_tlsf != nullptr) {
|
||||
//TRACEF("--- allocating memory from tlsf (%u bytes)", size);
|
||||
p = tlsf_malloc(OS::_tlsf, size);
|
||||
//TRACEF("--- allocated memory from tlsf (%u bytes) (addr=%lx)", size, p);
|
||||
}
|
||||
else {
|
||||
//TRACEF("--- allocating memory (%u bytes)", size);
|
||||
p = malloc(size);
|
||||
//TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
|
||||
++_new_fault;
|
||||
}
|
||||
#else
|
||||
//TRACEF("--- allocating memory (%u bytes)", size);
|
||||
p = malloc(size);
|
||||
//TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p);
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
// CBA Added attribute weak to avoid collision with new override on nrf52
|
||||
void operator delete(void* p) {
|
||||
//__attribute__((weak)) void operator delete(void* p) {
|
||||
#if defined(RNS_USE_TLSF)
|
||||
if (OS::_tlsf != nullptr) {
|
||||
//TRACEF("--- freeing memory from tlsf (addr=%lx)", p);
|
||||
tlsf_free(OS::_tlsf, p);
|
||||
}
|
||||
else {
|
||||
//TRACEF("--- freeing memory (addr=%lx)", p);
|
||||
free(p);
|
||||
++_delete_fault;
|
||||
}
|
||||
#else
|
||||
//TRACEF("--- freeing memory (addr=%lx)", p);
|
||||
//TRACE("--- freeing memory");
|
||||
free(p);
|
||||
#endif
|
||||
++_delete_count;
|
||||
#if defined(RNS_USE_TLSF)
|
||||
//if (_delete_count == _new_count) {
|
||||
// TRACE("TLFS deinitializing");
|
||||
// OS::dump_memory_stats();
|
||||
// tlsf_destroy(OS::_tlsf);
|
||||
// OS::_tlsf = nullptr;
|
||||
//}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(RNS_USE_TLSF)
|
||||
uint32_t _tlsf_used_count = 0;
|
||||
uint32_t _tlsf_used_size = 0;
|
||||
uint32_t _tlsf_free_count = 0;
|
||||
uint32_t _tlsf_free_size = 0;
|
||||
uint32_t _tlsf_free_max_size = 0;
|
||||
void tlsf_mem_walker(void* ptr, size_t size, int used, void* user)
|
||||
{
|
||||
if (used) {
|
||||
_tlsf_used_count++;
|
||||
_tlsf_used_size += size;
|
||||
}
|
||||
else {
|
||||
_tlsf_free_count++;
|
||||
_tlsf_free_size += size;
|
||||
if (size > _tlsf_free_max_size) {
|
||||
_tlsf_free_max_size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
void dump_tlsf_stats() {
|
||||
_tlsf_used_count = 0;
|
||||
_tlsf_used_size = 0;
|
||||
_tlsf_free_count = 0;
|
||||
_tlsf_free_size = 0;
|
||||
_tlsf_free_max_size = 0;
|
||||
//TRACEF("TLSF Message: %s", _tlsf_msg);
|
||||
if (OS::_tlsf == nullptr) {
|
||||
return;
|
||||
}
|
||||
tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, nullptr);
|
||||
HEAD("TLSF Stats", LOG_TRACE);
|
||||
TRACEF("Buffer Size: %u", _buffer_size);
|
||||
TRACEF("Contiguous Size: %u", _contiguous_size);
|
||||
TRACEF("Used Count: %u", _tlsf_used_count);
|
||||
TRACEF("Used Size: %u (%u%% used)", _tlsf_used_size, (unsigned)((double)_tlsf_used_size / (double)_buffer_size * 100.0));
|
||||
TRACEF("Free Count: %u", _tlsf_free_count);
|
||||
TRACEF("Free Size: %u (%u%% free)", _tlsf_free_size, (unsigned)((double)_tlsf_free_size / (double)_buffer_size * 100.0));
|
||||
TRACEF("Max Free Size: %u (%u%% fragmented)\n", _tlsf_free_max_size, (unsigned)(100.0 - (double)_tlsf_free_max_size / (double)_tlsf_free_size * 100.0));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*static*/ void OS::dump_allocator_stats() {
|
||||
HEAD("Allocator Stats", LOG_TRACE);
|
||||
TRACEF("New Count: %u", _new_count);
|
||||
TRACEF("New Fault: %u", _new_fault);
|
||||
TRACEF("Delete Count: %u", _delete_count);
|
||||
TRACEF("Delete Fault: %u", _delete_fault);
|
||||
TRACEF("Active Count: %u", (_new_count - _delete_count));
|
||||
TRACEF("Min Size: %u", _min_size);
|
||||
TRACEF("Max Size: %u", _max_size);
|
||||
TRACEF("Avg Size: %u\n", (size_t)(_new_size / _new_count));
|
||||
#if defined(RNS_USE_TLSF)
|
||||
dump_tlsf_stats();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // RNS_USE_ALLOCATOR
|
||||
|
||||
|
||||
size_t maxContiguousAllocation() {
|
||||
// Brute-force determine maximum allocation size
|
||||
//const size_t block_size = 256;
|
||||
const size_t block_size = 32;
|
||||
size_t block_count;
|
||||
for (block_count = 1; ; block_count++) {
|
||||
void* ptr = malloc(block_count * block_size);
|
||||
if (ptr == nullptr) {
|
||||
break;
|
||||
}
|
||||
free(ptr);
|
||||
}
|
||||
return (block_count - 1) * block_size;
|
||||
}
|
||||
|
||||
/*static*/ FileSystem OS::_filesystem = {Type::NONE};
|
||||
/*static*/ uint64_t OS::_time_offset = 0;
|
||||
|
||||
/*static*/ size_t OS::heap_size() {
|
||||
#if defined(ESP32)
|
||||
return ESP.getHeapSize();
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
return dbgHeapTotal();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*static*/ size_t OS::heap_available() {
|
||||
#if defined(ESP32)
|
||||
return ESP.getFreeHeap();
|
||||
//return ESP.getMaxAllocHeap();
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
return dbgHeapFree();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*static*/ void OS::dump_heap_stats() {
|
||||
HEAD("Heap Stats", LOG_TRACE);
|
||||
#if defined(ESP32)
|
||||
TRACEF("Heap size: %u", ESP.getHeapSize());
|
||||
TRACEF("Heap free: %u (%u%% free)", ESP.getFreeHeap(), (unsigned)((double)ESP.getFreeHeap() / (double)ESP.getHeapSize() * 100.0));
|
||||
//TRACEF("Heap free: %u (%u%% free)", xPortGetFreeHeapSize(), (unsigned)((double)xPortGetFreeHeapSize() / (double)xPort * 100.0));
|
||||
TRACEF("Heap min free: %u", ESP.getMinFreeHeap());
|
||||
//TRACEF("Heap min free: %u", xPortGetMinimumEverFreeHeapSize());
|
||||
TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)ESP.getFreeHeap() * 100.0));
|
||||
//TRACEF("Heap max alloc: %u (%u%% fragmented)", ESP.getMaxAllocHeap(), (unsigned)(100.0 - (double)ESP.getMaxAllocHeap() / (double)xPortGetFreeHeapSize() * 100.0));
|
||||
TRACEF("PSRAM size: %u", ESP.getPsramSize());
|
||||
TRACEF("PSRAM free: %u (%u%% free)", ESP.getFreePsram(), (ESP.getPsramSize() > 0) ? (unsigned)((double)ESP.getFreePsram() / (double)ESP.getPsramSize() * 100.0) : 0);
|
||||
TRACEF("PSRAM min free: %u", ESP.getMinFreePsram());
|
||||
TRACEF("PSRAM max alloc: %u (%u%% fragmented)", ESP.getMaxAllocPsram(), (ESP.getFreePsram() > 0) ? (unsigned)(100.0 - (double)ESP.getMaxAllocPsram() / (double)ESP.getFreePsram() * 100.0) : 0);
|
||||
#elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT)
|
||||
if (loglevel() == LOG_TRACE) {
|
||||
dbgMemInfo();
|
||||
}
|
||||
#endif
|
||||
#if defined(RNS_USE_ALLOCATOR)
|
||||
OS::dump_allocator_stats();
|
||||
#endif
|
||||
}
|
||||
234
lib/microReticulum/src/Utilities/OS.h
Executable file
234
lib/microReticulum/src/Utilities/OS.h
Executable file
@@ -0,0 +1,234 @@
|
||||
#pragma once
|
||||
|
||||
#include "../FileSystem.h"
|
||||
#include "../FileStream.h"
|
||||
#include "../Bytes.h"
|
||||
|
||||
#include "tlsf.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#undef round
|
||||
|
||||
namespace RNS { namespace Utilities {
|
||||
|
||||
class OS {
|
||||
|
||||
private:
|
||||
static FileSystem _filesystem;
|
||||
static uint64_t _time_offset;
|
||||
|
||||
public:
|
||||
static tlsf_t _tlsf;
|
||||
|
||||
public:
|
||||
static inline uint64_t getTimeOffset() { return _time_offset; }
|
||||
static inline void setTimeOffset(uint64_t offset) { _time_offset = offset; }
|
||||
|
||||
#ifdef ARDUINO
|
||||
// return current time in milliseconds since startup
|
||||
static inline uint64_t ltime() {
|
||||
// handle roll-over of 32-bit millis (approx. 49 days)
|
||||
static uint32_t low32, high32;
|
||||
uint32_t new_low32 = millis();
|
||||
if (new_low32 < low32) high32++;
|
||||
low32 = new_low32;
|
||||
return ((uint64_t)high32 << 32 | low32) + _time_offset;
|
||||
}
|
||||
#else
|
||||
// return current time in milliseconds since 00:00:00, January 1, 1970 (Unix Epoch)
|
||||
static inline uint64_t ltime() { timeval time; ::gettimeofday(&time, NULL); return (uint64_t)(time.tv_sec * 1000) + (uint64_t)(time.tv_usec / 1000); }
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO
|
||||
// return current time in float seconds since startup
|
||||
static inline double time() { return (double)(ltime() / 1000.0); }
|
||||
#else
|
||||
// return current time in float seconds since 00:00:00, January 1, 1970 (Unix Epoch)
|
||||
static inline double time() { timeval time; ::gettimeofday(&time, NULL); return (double)time.tv_sec + ((double)time.tv_usec / 1000000); }
|
||||
#endif
|
||||
|
||||
// sleep for specified milliseconds
|
||||
//static inline void sleep(float seconds) { ::sleep(seconds); }
|
||||
#ifdef ARDUINO
|
||||
static inline void sleep(float seconds) { delay((uint32_t)(seconds * 1000)); }
|
||||
#else
|
||||
static inline void sleep(float seconds) { timespec time; time.tv_sec = (time_t)(seconds); time.tv_nsec = (seconds - (float)time.tv_sec) * 1000000000; ::nanosleep(&time, nullptr); }
|
||||
#endif
|
||||
//static inline void sleep(uint32_t milliseconds) { ::sleep((float)milliseconds / 1000.0); }
|
||||
|
||||
// round decimal number to specified precision
|
||||
//static inline float round(float value, uint8_t precision) { return std::round(value / precision) * precision; }
|
||||
//static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
|
||||
static inline double round(double value, uint8_t precision) { return std::round(value / precision) * precision; }
|
||||
|
||||
static inline uint64_t from_bytes_big_endian(const uint8_t* data, size_t len) {
|
||||
uint64_t result = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
result = (result << 8) | data[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Detect endianness at runtime
|
||||
static int is_big_endian(void) {
|
||||
uint16_t test = 0x0102;
|
||||
return ((uint8_t*)&test)[0] == 0x01;
|
||||
}
|
||||
|
||||
// Byte swap functions
|
||||
static uint16_t swap16(uint16_t val) {
|
||||
return (val << 8) | (val >> 8);
|
||||
}
|
||||
|
||||
static uint32_t swap32(uint32_t val) {
|
||||
return ((val << 24) & 0xFF000000) |
|
||||
((val << 8) & 0x00FF0000) |
|
||||
((val >> 8) & 0x0000FF00) |
|
||||
((val >> 24) & 0x000000FF);
|
||||
}
|
||||
|
||||
// Platform-independent replacements
|
||||
|
||||
static uint16_t portable_htons(uint16_t val) {
|
||||
return is_big_endian() ? val : swap16(val);
|
||||
}
|
||||
|
||||
static uint32_t portable_htonl(uint32_t val) {
|
||||
return is_big_endian() ? val : swap32(val);
|
||||
}
|
||||
|
||||
static uint16_t portable_ntohs(uint16_t val) {
|
||||
return is_big_endian() ? val : swap16(val);
|
||||
}
|
||||
|
||||
static uint32_t portable_ntohl(uint32_t val) {
|
||||
return is_big_endian() ? val : swap32(val);
|
||||
}
|
||||
|
||||
#if defined(RNS_USE_ALLOCATOR)
|
||||
static void dump_allocator_stats();
|
||||
#endif
|
||||
|
||||
inline static void register_filesystem(FileSystem& filesystem) {
|
||||
TRACE("Registering filesystem...");
|
||||
_filesystem = filesystem;
|
||||
}
|
||||
|
||||
/*
|
||||
inline static void register_filesystem(FileSystemImpl* filesystemimpl) {
|
||||
TRACE("Registering filesystem...");
|
||||
_filesystem = filesystemimpl;
|
||||
}
|
||||
*/
|
||||
|
||||
inline static void deregister_filesystem() {
|
||||
TRACE("Deregistering filesystem...");
|
||||
_filesystem = {Type::NONE};
|
||||
}
|
||||
|
||||
inline static FileSystem& get_filesystem() {
|
||||
return _filesystem;
|
||||
}
|
||||
|
||||
|
||||
inline static bool file_exists(const char* file_path) {
|
||||
if (!_filesystem) {
|
||||
WARNING("file_exists: filesystem not registered");
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.file_exists(file_path);
|
||||
}
|
||||
|
||||
inline static size_t read_file(const char* file_path, Bytes& data) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.read_file(file_path, data);
|
||||
}
|
||||
|
||||
inline static size_t write_file(const char* file_path, const Bytes& data) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.write_file(file_path, data);
|
||||
}
|
||||
|
||||
inline static FileStream open_file(const char* file_path, RNS::FileStream::MODE file_mode) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.open_file(file_path, file_mode);
|
||||
}
|
||||
|
||||
inline static bool remove_file(const char* file_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.remove_file(file_path);
|
||||
}
|
||||
|
||||
inline static bool rename_file(const char* from_file_path, const char* to_file_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.rename_file(from_file_path, to_file_path);
|
||||
}
|
||||
|
||||
inline static bool directory_exists(const char* directory_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.directory_exists(directory_path);
|
||||
}
|
||||
|
||||
inline static bool create_directory(const char* directory_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.create_directory(directory_path);
|
||||
}
|
||||
|
||||
inline static bool remove_directory(const char* directory_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.remove_directory(directory_path);
|
||||
}
|
||||
|
||||
inline static std::list<std::string> list_directory(const char* directory_path) {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.list_directory(directory_path);
|
||||
}
|
||||
|
||||
inline static size_t storage_size() {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.storage_size();
|
||||
}
|
||||
|
||||
inline static size_t storage_available() {
|
||||
if (!_filesystem) {
|
||||
throw std::runtime_error("FileSystem has not been registered");
|
||||
}
|
||||
return _filesystem.storage_available();
|
||||
}
|
||||
|
||||
static size_t heap_size();
|
||||
static size_t heap_available();
|
||||
static void dump_heap_stats();
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
9
lib/microReticulum/src/Utilities/Persistence.cpp
Executable file
9
lib/microReticulum/src/Utilities/Persistence.cpp
Executable file
@@ -0,0 +1,9 @@
|
||||
#include "Persistence.h"
|
||||
|
||||
#include "Bytes.h"
|
||||
|
||||
using namespace RNS;
|
||||
|
||||
/*static*/ //DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
|
||||
/*static*/ JsonDocument _document;
|
||||
/*static*/ Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);
|
||||
699
lib/microReticulum/src/Utilities/Persistence.h
Executable file
699
lib/microReticulum/src/Utilities/Persistence.h
Executable file
@@ -0,0 +1,699 @@
|
||||
#pragma once
|
||||
|
||||
// CBA NOTE If headers for classes referenced in this file are not included here,
|
||||
// then they MUST be included BEFORE this header is included.
|
||||
#include "Transport.h"
|
||||
#include "Type.h"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace ArduinoJson {
|
||||
|
||||
// ArduinoJSON serialization support for std::vector<T>
|
||||
template <typename T>
|
||||
struct Converter<std::vector<T>> {
|
||||
static void toJson(const std::vector<T>& src, JsonVariant dst) {
|
||||
TRACE("<<< Serializing vector");
|
||||
JsonArray array = dst.to<JsonArray>();
|
||||
for (T item : src)
|
||||
array.add(item);
|
||||
TRACE("<<< Finished serializing vector");
|
||||
}
|
||||
static std::vector<T> fromJson(JsonVariantConst src) {
|
||||
TRACE("<<< Deserializing vector");
|
||||
std::vector<T> dst;
|
||||
for (T item : src.as<JsonArrayConst>())
|
||||
dst.push_back(item);
|
||||
TRACE("<<< Finished deserializing vector");
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
JsonArrayConst array = src;
|
||||
bool result = array;
|
||||
for (JsonVariantConst item : array)
|
||||
result &= item.is<T>();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// ArduinoJSON serialization support for std::set<T>
|
||||
template <typename T>
|
||||
struct Converter<std::set<T>> {
|
||||
static void toJson(const std::set<T>& src, JsonVariant dst) {
|
||||
JsonArray array = dst.to<JsonArray>();
|
||||
for (T item : src)
|
||||
array.add(item);
|
||||
}
|
||||
static std::set<T> fromJson(JsonVariantConst src) {
|
||||
std::set<T> dst;
|
||||
for (T item : src.as<JsonArrayConst>())
|
||||
dst.insert(item);
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
JsonArrayConst array = src;
|
||||
bool result = array;
|
||||
for (JsonVariantConst item : array)
|
||||
result &= item.is<T>();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// ArduinoJSON serialization support for std::map<std::string, T>
|
||||
template <typename T>
|
||||
struct Converter<std::map<std::string, T>> {
|
||||
static void toJson(const std::map<std::string, T>& src, JsonVariant dst) {
|
||||
JsonObject obj = dst.to<JsonObject>();
|
||||
for (const auto& item : src)
|
||||
obj[item.first] = item.second;
|
||||
}
|
||||
static std::map<std::string, T> fromJson(JsonVariantConst src) {
|
||||
std::map<std::string, T> dst;
|
||||
for (JsonPairConst item : src.as<JsonObjectConst>())
|
||||
dst[item.key().c_str()] = item.value().as<T>();
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
JsonObjectConst obj = src;
|
||||
bool result = obj;
|
||||
for (JsonPairConst item : obj)
|
||||
result &= item.value().is<T>();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// ArduinoJSON serialization support for std::map<Bytes, T>
|
||||
template <typename T>
|
||||
struct Converter<std::map<RNS::Bytes, T>> {
|
||||
static void toJson(const std::map<RNS::Bytes, T>& src, JsonVariant dst) {
|
||||
JsonObject obj = dst.to<JsonObject>();
|
||||
for (const auto& item : src) {
|
||||
//obj[item.first] = item.second;
|
||||
obj[item.first.toHex()] = item.second;
|
||||
}
|
||||
}
|
||||
static std::map<RNS::Bytes, T> fromJson(JsonVariantConst src) {
|
||||
std::map<RNS::Bytes, T> dst;
|
||||
for (JsonPairConst item : src.as<JsonObjectConst>()) {
|
||||
//dst[item.key().c_str()] = item.value().as<T>();
|
||||
RNS::Bytes key;
|
||||
key.assignHex(item.key().c_str());
|
||||
//dst[key] = item.value().as<T>();
|
||||
dst.insert({key, item.value().as<T>()});
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
JsonObjectConst obj = src;
|
||||
bool result = obj;
|
||||
for (JsonPairConst item : obj) {
|
||||
result &= item.value().is<T>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
// ArduinoJSON serialization support for RNS::Bytes
|
||||
template <>
|
||||
struct Converter<RNS::Bytes> {
|
||||
static bool toJson(const RNS::Bytes& src, JsonVariant dst) {
|
||||
//TRACE("<<< Serializing Bytes");
|
||||
//TRACE("<<< Finished serializing Bytes");
|
||||
return true;
|
||||
}
|
||||
static RNS::Bytes fromJson(JsonVariantConst src) {
|
||||
Bytes bytes;
|
||||
//TRACE(">>> Deserializing Bytes");
|
||||
//TRACE(">>> Finished deserializing Bytes");
|
||||
return bytes;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return
|
||||
src.is<RNS::Bytes>();
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
// ArduinoJSON serialization support for RNS::Interface
|
||||
template <>
|
||||
struct Converter<RNS::Interface> {
|
||||
static bool toJson(const RNS::Interface& src, JsonVariant dst) {
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
//TRACE("<<< Serializing interface hash " + src.get_hash().toHex());
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
static RNS::Interface fromJson(JsonVariantConst src) {
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
//TRACE(">>> Deserialized interface hash " + hash.toHex());
|
||||
//TRACE(">>> Querying transport for interface");
|
||||
// Query transport for matching interface
|
||||
return RNS::Transport::find_interface_from_hash(hash);
|
||||
}
|
||||
else {
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
// ArduinoJSON serialization support for RNS::Packet
|
||||
template <>
|
||||
struct Converter<RNS::Packet> {
|
||||
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
|
||||
if (!src) {
|
||||
return dst.set(nullptr);
|
||||
}
|
||||
//TRACE("<<< Serializing packet hash " + src.get_hash().toHex());
|
||||
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
|
||||
RNS::Transport::cache_packet(src, true);
|
||||
return dst.set(src.get_hash().toHex());
|
||||
}
|
||||
static RNS::Packet fromJson(JsonVariantConst src) {
|
||||
if (!src.isNull()) {
|
||||
RNS::Bytes hash;
|
||||
hash.assignHex(src.as<const char*>());
|
||||
//TRACE(">>> Deserialized packet hash " + hash.toHex());
|
||||
//TRACE(">>> Querying transport for cached packet");
|
||||
// Query transport for matching packet
|
||||
return RNS::Transport::get_cached_packet(hash);
|
||||
}
|
||||
else {
|
||||
return {RNS::Type::NONE};
|
||||
}
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return src.is<const char*>() && strlen(src.as<const char*>()) == 64;
|
||||
}
|
||||
};
|
||||
*/
|
||||
// ArduinoJSON serialization support for RNS::Packet
|
||||
template <>
|
||||
struct Converter<RNS::Packet> {
|
||||
static bool toJson(const RNS::Packet& src, JsonVariant dst) {
|
||||
//TRACE("<<< Serializing Packet");
|
||||
//dst["hash"] = src.get_hash();
|
||||
dst["raw"] = src.raw();
|
||||
dst["sent_at"] = src.sent_at();
|
||||
dst["destination_hash"] = src.get_hash();
|
||||
//TRACE("<<< Finished serializing Packet");
|
||||
return true;
|
||||
}
|
||||
static RNS::Packet fromJson(JsonVariantConst src) {
|
||||
//TRACE(">>> Deserializing Packet");
|
||||
RNS::Bytes raw = src["raw"];
|
||||
//RNS::Bytes raw = src["raw"].as<const RNS::Bytes&>();
|
||||
RNS::Packet packet(RNS::Destination(RNS::Type::NONE), raw);
|
||||
//packet.set_hash(src["hash"]);
|
||||
packet.sent_at(src["sent_at"]);
|
||||
RNS::Bytes destination_hash = src["destination_hash"];
|
||||
// set cached flag since pcket was read from cache
|
||||
packet.cached(true);
|
||||
//TRACE(">>> Finished deserializing Packet");
|
||||
return packet;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return
|
||||
//src["hash"].is<RNS::Bytes>() &&
|
||||
src["raw"].is<RNS::Bytes>() &&
|
||||
src["sent_at"].is<double>() &&
|
||||
src["destination_hash"].is<RNS::Bytes>();
|
||||
}
|
||||
};
|
||||
|
||||
// ArduinoJSON serialization support for RNS::Transport::PacketEntry
|
||||
template <>
|
||||
struct Converter<RNS::Transport::PacketEntry> {
|
||||
static bool toJson(const RNS::Transport::PacketEntry& src, JsonVariant dst) {
|
||||
//TRACE("<<< Serializing Transport::PacketEntry");
|
||||
//dst["hash"] = src._hash;
|
||||
dst["raw"] = src._raw;
|
||||
dst["sent_at"] = src._sent_at;
|
||||
dst["destination_hash"] = src._destination_hash;
|
||||
//TRACE("<<< Finished serializing Transport::PacketEntry");
|
||||
return true;
|
||||
}
|
||||
static RNS::Transport::PacketEntry fromJson(JsonVariantConst src) {
|
||||
//TRACE(">>> Deserializing Transport::PacketEntry");
|
||||
RNS::Transport::PacketEntry dst;
|
||||
//dst._hash = src["hash"];
|
||||
dst._raw = src["raw"];
|
||||
dst._sent_at = src["sent_at"];
|
||||
dst._destination_hash = src["destination_hash"];
|
||||
// set cached flag since pcket was read from cache
|
||||
dst._cached = true;
|
||||
//TRACE(">>> Finished deserializing Transport::PacketEntry");
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return
|
||||
//src["hash"].is<RNS::Bytes>() &&
|
||||
src["raw"].is<RNS::Bytes>() &&
|
||||
src["sent_at"].is<double>() &&
|
||||
src["destination_hash"].is<RNS::Bytes>();
|
||||
}
|
||||
};
|
||||
|
||||
#if 1
|
||||
// ArduinoJSON serialization support for RNS::Transport::DestinationEntry
|
||||
template <>
|
||||
struct Converter<RNS::Transport::DestinationEntry> {
|
||||
static bool toJson(const RNS::Transport::DestinationEntry& src, JsonVariant dst) {
|
||||
//TRACE("<<< Serializing Transport::DestinationEntry");
|
||||
dst["timestamp"] = src._timestamp;
|
||||
dst["received_from"] = src._received_from;
|
||||
dst["announce_hops"] = src._hops;
|
||||
dst["expires"] = src._expires;
|
||||
dst["random_blobs"] = src._random_blobs;
|
||||
/*
|
||||
//dst["interface_hash"] = src._receiving_interface;
|
||||
if (src._receiving_interface) {
|
||||
dst["interface_hash"] = src._receiving_interface.get_hash();
|
||||
}
|
||||
else {
|
||||
dst["interface_hash"] = nullptr;
|
||||
}
|
||||
// CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
|
||||
// anyway if there's no space to left to write destination table.
|
||||
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
|
||||
//dst["packet"] = src._announce_packet;
|
||||
if (src._announce_packet) {
|
||||
dst["packet_hash"] = src._announce_packet.get_hash();
|
||||
// Only cache packet if not already cached
|
||||
if (!src._announce_packet.cached()) {
|
||||
if (RNS::Transport::cache_packet(src._announce_packet, true)) {
|
||||
const_cast<RNS::Packet&>(src._announce_packet).cached(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dst["packet_hash"] = nullptr;
|
||||
}
|
||||
*/
|
||||
dst["interface_hash"] = src._receiving_interface;
|
||||
dst["packet_hash"] = src._announce_packet;
|
||||
//TRACE("<<< Finished Serializing Transport::DestinationEntry");
|
||||
return true;
|
||||
}
|
||||
static RNS::Transport::DestinationEntry fromJson(JsonVariantConst src) {
|
||||
//TRACE(">>> Deserializing Transport::DestinationEntry");
|
||||
RNS::Transport::DestinationEntry dst;
|
||||
dst._timestamp = src["timestamp"];
|
||||
dst._received_from = src["received_from"];
|
||||
dst._hops = src["announce_hops"];
|
||||
dst._expires = src["expires"];
|
||||
dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
|
||||
/*
|
||||
//dst._receiving_interface = src["interface_hash"];
|
||||
RNS::Bytes interface_hash = src["interface_hash"];
|
||||
if (interface_hash) {
|
||||
// Query transport for matching interface
|
||||
dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
|
||||
}
|
||||
//dst._announce_packet = src["packet"];
|
||||
RNS::Bytes packet_hash = src["packet_hash"];
|
||||
if (packet_hash) {
|
||||
// Query transport for matching packet
|
||||
dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
|
||||
}
|
||||
*/
|
||||
dst._receiving_interface = src["interface_hash"];
|
||||
dst._announce_packet = src["packet_hash"];
|
||||
/*
|
||||
//RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
|
||||
RNS::Transport::DestinationEntry dst(
|
||||
src["timestamp"].as<double>(),
|
||||
src["received_from"].as<RNS::Bytes>(),
|
||||
src["announce_hops"].as<int>(),
|
||||
src["expires"].as<double>(),
|
||||
src["random_blobs"].as<std::set<RNS::Bytes>>(),
|
||||
src["receiving_interface"].as<RNS::Interface>(),
|
||||
src["packet"].as<RNS::Packet>()
|
||||
);
|
||||
*/
|
||||
//TRACE(">>> Finished Deserializing Transport::DestinationEntry");
|
||||
return dst;
|
||||
}
|
||||
static bool checkJson(JsonVariantConst src) {
|
||||
return
|
||||
src["timestamp"].is<double>() &&
|
||||
src["received_from"].is<RNS::Bytes>() &&
|
||||
src["announce_hops"].is<int>() &&
|
||||
src["expires"].is<double>() &&
|
||||
src["random_blobs"].is<std::set<RNS::Bytes>>() &&
|
||||
src["interface_hash"].is<RNS::Bytes>() &&
|
||||
src["packet_hash"].is<RNS::Bytes>();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#if 0
|
||||
namespace RNS {
|
||||
|
||||
inline bool convertToJson(const Transport::DestinationEntry& src, JsonVariant dst) {
|
||||
//TRACE("<<< NEW Serializing Transport::DestinationEntry");
|
||||
dst["timestamp"] = src._timestamp;
|
||||
dst["received_from"] = src._received_from;
|
||||
dst["announce_hops"] = src._hops;
|
||||
dst["expires"] = src._expires;
|
||||
dst["random_blobs"] = src._random_blobs;
|
||||
/*
|
||||
//dst["interface_hash"] = src._receiving_interface;
|
||||
if (src._receiving_interface) {
|
||||
dst["interface_hash"] = src._receiving_interface.get_hash();
|
||||
}
|
||||
else {
|
||||
dst["interface_hash"] = nullptr;
|
||||
}
|
||||
// CBA TODO Move packet serialization to *after* destination table serialization since packets are useless
|
||||
// anyway if there's no space to left to write destination table.
|
||||
// Whenever a reference to a packet is serialized we must ensure that packet itself also gets serialized separately
|
||||
//dst["packet"] = src._announce_packet;
|
||||
if (src._announce_packet) {
|
||||
dst["packet_hash"] = src._announce_packet.get_hash();
|
||||
// Only cache packet if not already cached
|
||||
if (!src._announce_packet.cached()) {
|
||||
if (RNS::Transport::cache_packet(src._announce_packet, true)) {
|
||||
const_cast<RNS::Packet&>(src._announce_packet).cached(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRACE("Destination announce packet " + src._announce_packet.get_hash().toHex() + " is already cached");
|
||||
}
|
||||
}
|
||||
else {
|
||||
dst["packet_hash"] = nullptr;
|
||||
}
|
||||
*/
|
||||
dst["interface_hash"] = src._receiving_interface;
|
||||
dst["packet_hash"] = src._announce_packet;
|
||||
//TRACE("<<< Finished Serializing Transport::DestinationEntry");
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void convertFromJson(JsonVariantConst src, Transport::DestinationEntry& dst) {
|
||||
//TRACE(">>> NEW Deserializing Transport::DestinationEntry");
|
||||
dst._timestamp = src["timestamp"];
|
||||
dst._received_from = src["received_from"];
|
||||
dst._hops = src["announce_hops"];
|
||||
dst._expires = src["expires"];
|
||||
dst._random_blobs = src["random_blobs"].as<std::set<RNS::Bytes>>();
|
||||
/*
|
||||
//dst._receiving_interface = src["interface_hash"];
|
||||
RNS::Bytes interface_hash = src["interface_hash"];
|
||||
if (interface_hash) {
|
||||
// Query transport for matching interface
|
||||
dst._receiving_interface = RNS::Transport::find_interface_from_hash(interface_hash);
|
||||
}
|
||||
//dst._announce_packet = src["packet"];
|
||||
RNS::Bytes packet_hash = src["packet_hash"];
|
||||
if (packet_hash) {
|
||||
// Query transport for matching packet
|
||||
dst._announce_packet = RNS::Transport::get_cached_packet(packet_hash);
|
||||
}
|
||||
*/
|
||||
dst._receiving_interface = src["interface_hash"];
|
||||
dst._announce_packet = src["packet_hash"];
|
||||
/*
|
||||
//RNS::Transport::DestinationEntry dst(src["timestamp"], src["received_from"], src["announce_hops"], src["expires"], src["random_blobs"], src["receiving_interface"], src["packet"]);
|
||||
RNS::Transport::DestinationEntry dst(
|
||||
src["timestamp"].as<double>(),
|
||||
src["received_from"].as<RNS::Bytes>(),
|
||||
src["announce_hops"].as<int>(),
|
||||
src["expires"].as<double>(),
|
||||
src["random_blobs"].as<std::set<RNS::Bytes>>(),
|
||||
src["receiving_interface"].as<RNS::Interface>(),
|
||||
src["packet"].as<RNS::Packet>()
|
||||
);
|
||||
*/
|
||||
//TRACE(">>> Finished Deserializing Transport::DestinationEntry");
|
||||
}
|
||||
|
||||
inline bool canConvertFromJson(JsonVariantConst src, const Transport::DestinationEntry&) {
|
||||
TRACE("=== NEW Checking Transport::DestinationEntry");
|
||||
return
|
||||
src["timestamp"].is<double>() &&
|
||||
src["received_from"].is<RNS::Bytes>() &&
|
||||
src["announce_hops"].is<int>() &&
|
||||
src["expires"].is<double>() &&
|
||||
src["random_blobs"].is<std::set<RNS::Bytes>>() &&
|
||||
src["interface_hash"].is<RNS::Bytes>() &&
|
||||
src["packet_hash"].is<RNS::Bytes>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace RNS { namespace Persistence {
|
||||
|
||||
//static DynamicJsonDocument _document(Type::Persistence::DOCUMENT_MAXSIZE);
|
||||
static JsonDocument _document;
|
||||
static Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE);
|
||||
|
||||
template <typename T> size_t crc(const T& obj) {
|
||||
//TRACE("Persistence::crc<T>");
|
||||
_document.set(obj);
|
||||
size_t size = _buffer.capacity();
|
||||
#ifdef USE_MSGPACK
|
||||
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
|
||||
#else
|
||||
size_t length = serializeJson(_document, _buffer.writable(size), size);
|
||||
#endif
|
||||
if (length < size) {
|
||||
_buffer.resize(length);
|
||||
}
|
||||
TRACEF("Persistence::crc: serialized %d bytes", length);
|
||||
return Utilities::Crc::crc32(0, _buffer.data(), _buffer.size());
|
||||
}
|
||||
|
||||
template <typename T> size_t serialize(const T& obj, const char* file_path) {
|
||||
//TRACE("Persistence::serialize<T>");
|
||||
_document.set(obj);
|
||||
size_t size = _buffer.capacity();
|
||||
#ifdef USE_MSGPACK
|
||||
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
|
||||
#else
|
||||
size_t length = serializeJson(_document, _buffer.writable(size), size);
|
||||
#endif
|
||||
if (length < size) {
|
||||
_buffer.resize(length);
|
||||
}
|
||||
TRACEF("Persistence::serialize: serialized %d bytes", length);
|
||||
size_t wrote = 0;
|
||||
if (length > 0) {
|
||||
wrote = RNS::Utilities::OS::write_file(file_path, _buffer);
|
||||
if (wrote == _buffer.size()) {
|
||||
TRACEF("Persistence::serialize: wrote %d bytes", _buffer.size());
|
||||
}
|
||||
else {
|
||||
TRACE("Persistence::serialize: write failed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRACE("Persistence::serialize: failed to serialize");
|
||||
return 0;
|
||||
}
|
||||
return wrote;
|
||||
}
|
||||
|
||||
template <typename T> size_t deserialize(T& obj, const char* file_path) {
|
||||
//TRACE("Persistence::deserialize<T>");
|
||||
size_t read = RNS::Utilities::OS::read_file(file_path, _buffer);
|
||||
if (read > 0) {
|
||||
TRACEF("Persistence::deserialize: read: %d bytes", _buffer.size());
|
||||
//TRACE("testDeserializeVector: data: " + _buffer.toString());
|
||||
#ifdef USE_MSGPACK
|
||||
DeserializationError error = deserializeMsgPack(_document, _buffer.data());
|
||||
#else
|
||||
DeserializationError error = deserializeJson(_document, _buffer.data());
|
||||
#endif
|
||||
if (!error) {
|
||||
TRACE("Persistence::deserialize: successfully deserialized document");
|
||||
obj = _document.as<T>();
|
||||
// CBA Following obj check doesn't work when T is a collection
|
||||
//if (obj) {
|
||||
return read;
|
||||
//}
|
||||
TRACE("Persistence::deserialize: failed to compose object");
|
||||
}
|
||||
else {
|
||||
TRACE("Persistence::deserialize: failed to deserialize");
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRACE("Persistence::deserialize: read failed");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 1
|
||||
template <typename T> size_t crc(std::map<Bytes, T>& map) {
|
||||
//TRACE("Persistence::crc<map<Bytes, T>>");
|
||||
|
||||
uint32_t crc = 0;
|
||||
crc = Utilities::Crc::crc32(crc, '{');
|
||||
for (const auto& [key, value] : map) {
|
||||
crc = Utilities::Crc::crc32(crc, '"');
|
||||
std::string hex = key.toHex();
|
||||
crc = Utilities::Crc::crc32(crc, hex.c_str());
|
||||
crc = Utilities::Crc::crc32(crc, '"');
|
||||
crc = Utilities::Crc::crc32(crc, ':');
|
||||
|
||||
_document.set(value);
|
||||
size_t size = _buffer.capacity();
|
||||
#ifdef USE_MSGPACK
|
||||
size_t length = serializeMsgPack(_document, _buffer.writable(size), size);
|
||||
#else
|
||||
size_t length = serializeJson(_document, _buffer.writable(size), size);
|
||||
#endif
|
||||
if (length < size) {
|
||||
_buffer.resize(length);
|
||||
}
|
||||
TRACEF("Persistence::crc: serialized entry %d bytes", length);
|
||||
|
||||
if (length > 0) {
|
||||
crc = Utilities::Crc::crc32(crc, _buffer.data(), _buffer.size());
|
||||
}
|
||||
else {
|
||||
// if failed to serialize entry then write empty entry
|
||||
crc = Utilities::Crc::crc32(crc, "{}");
|
||||
}
|
||||
crc = Utilities::Crc::crc32(crc, ',');
|
||||
}
|
||||
return Utilities::Crc::crc32(crc, '}');
|
||||
}
|
||||
|
||||
template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
|
||||
//TRACE("Persistence::serialize<map<Bytes,T>>");
|
||||
|
||||
// CBA TODO: Use stream here instead to avoid having to buffer entire structure
|
||||
RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_WRITE);
|
||||
if (!stream) {
|
||||
TRACE("Persistence::serialize: failed to open write stream");
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream.write('{');
|
||||
for (const auto& [key, value] : map) {
|
||||
stream.write('"');
|
||||
std::string hex = key.toHex();
|
||||
stream.write(hex.c_str());
|
||||
stream.write('"');
|
||||
stream.write(':');
|
||||
|
||||
_document.set(value);
|
||||
#ifdef USE_MSGPACK
|
||||
size_t length = serializeMsgPack(_document, stream);
|
||||
#else
|
||||
size_t length = serializeJson(_document, stream);
|
||||
#endif
|
||||
TRACEF("Persistence::serialize: serialized entry %d bytes", length);
|
||||
|
||||
if (length == 0) {
|
||||
// if failed to serialize entry then write empty entry
|
||||
stream.write("{}");
|
||||
}
|
||||
stream.write(',');
|
||||
}
|
||||
stream.write('}');
|
||||
TRACEF("Persistence::serialize: stream size: %d bytes", stream.size());
|
||||
crc = stream.crc();
|
||||
return stream.size();
|
||||
}
|
||||
|
||||
template <typename T> size_t serialize(std::map<Bytes, T>& map, const char* file_path) {
|
||||
uint32_t crc;
|
||||
return serialize(map, file_path, crc);
|
||||
}
|
||||
|
||||
template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path, uint32_t& crc) {
|
||||
//TRACE("Persistence::deserialize<map<Bytes,T>>");
|
||||
|
||||
// CBA TODO: Use stream here instead to avoid having to buffer entire structure
|
||||
RNS::FileStream stream = RNS::Utilities::OS::open_file(file_path, RNS::FileStream::MODE_READ);
|
||||
if (!stream) {
|
||||
TRACE("Persistence::deserialize: failed to open read stream");
|
||||
return 0;
|
||||
}
|
||||
TRACEF("Persistence::deserialize: size: %d bytes", stream.size());
|
||||
|
||||
map.clear();
|
||||
|
||||
if (stream.size() == 0) {
|
||||
TRACE("Persistence::deserialize: read stream is empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// find opening brace
|
||||
if (stream.find('{')) {
|
||||
char key_str[RNS::Type::Reticulum::DESTINATION_LENGTH*2+1] = "";
|
||||
do {
|
||||
key_str[0] = 0;
|
||||
// find map key opening quote
|
||||
if (stream.find('"')) {
|
||||
if (stream.readBytesUntil('"', key_str, sizeof(key_str)) > 0) {
|
||||
Bytes key;
|
||||
key.assignHex(key_str);
|
||||
TRACEF("Persistence::deserialize: key: %s", key.toHex().c_str());
|
||||
if (stream.find(':')) {
|
||||
#ifdef USE_MSGPACK
|
||||
//DeserializationError error = deserializeMsgPack(_document, _buffer.data());
|
||||
DeserializationError error = deserializeMsgPack(_document, stream);
|
||||
#else
|
||||
//DeserializationError error = deserializeJson(_document, _buffer.data());
|
||||
DeserializationError error = deserializeJson(_document, stream);
|
||||
#endif
|
||||
if (!error) {
|
||||
TRACE("Persistence::deserialize: successfully deserialized entry");
|
||||
T obj = _document.as<T>();
|
||||
// CBA Following obj check doesn't work when T is a collection
|
||||
//if (obj) {
|
||||
// TRACE("Persistence::deserialize: failed to compose object");
|
||||
// break;
|
||||
//}
|
||||
map.insert({key, obj});
|
||||
}
|
||||
else {
|
||||
TRACE("Persistence::deserialize: failed to deserialize entry");
|
||||
//break;
|
||||
}
|
||||
|
||||
if (!stream.find(',')) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (key_str[0] != 0);
|
||||
}
|
||||
crc = stream.crc();
|
||||
return stream.size();
|
||||
}
|
||||
|
||||
template <typename T> size_t deserialize(std::map<Bytes, T>& map, const char* file_path) {
|
||||
uint32_t crc;
|
||||
return deserialize(map, file_path, crc);
|
||||
}
|
||||
#endif
|
||||
|
||||
} }
|
||||
354
lib/microReticulum/src/Utilities/Print.cpp
Executable file
354
lib/microReticulum/src/Utilities/Print.cpp
Executable file
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
Print.cpp - Base class that provides print() and println()
|
||||
Copyright (c) 2008 David A. Mellis. All right reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Modified 23 November 2006 by David A. Mellis
|
||||
Modified December 2014 by Ivan Grokhotkov
|
||||
Modified May 2015 by Michael C. Miller - ESP31B progmem support
|
||||
*/
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "Print.h"
|
||||
extern "C" {
|
||||
#include "time.h"
|
||||
}
|
||||
|
||||
// Public Methods //////////////////////////////////////////////////////////////
|
||||
|
||||
/* default implementation: may be overridden */
|
||||
size_t Print::write(const uint8_t *buffer, size_t size)
|
||||
{
|
||||
size_t n = 0;
|
||||
while(size--) {
|
||||
n += write(*buffer++);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::printf(const char *format, ...)
|
||||
{
|
||||
char loc_buf[64];
|
||||
char * temp = loc_buf;
|
||||
va_list arg;
|
||||
va_list copy;
|
||||
va_start(arg, format);
|
||||
va_copy(copy, arg);
|
||||
int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
|
||||
va_end(copy);
|
||||
if(len < 0) {
|
||||
va_end(arg);
|
||||
return 0;
|
||||
}
|
||||
if(len >= (int)sizeof(loc_buf)){ // comparation of same sign type for the compiler
|
||||
temp = (char*) malloc(len+1);
|
||||
if(temp == NULL) {
|
||||
va_end(arg);
|
||||
return 0;
|
||||
}
|
||||
len = vsnprintf(temp, len+1, format, arg);
|
||||
}
|
||||
va_end(arg);
|
||||
len = write((uint8_t*)temp, len);
|
||||
if(temp != loc_buf){
|
||||
free(temp);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t Print::print(const std::string &s)
|
||||
{
|
||||
return write(s.c_str(), s.length());
|
||||
}
|
||||
|
||||
size_t Print::print(const char str[])
|
||||
{
|
||||
return write(str);
|
||||
}
|
||||
|
||||
size_t Print::print(char c)
|
||||
{
|
||||
return write(c);
|
||||
}
|
||||
|
||||
size_t Print::print(unsigned char b, int base)
|
||||
{
|
||||
return print((unsigned long) b, base);
|
||||
}
|
||||
|
||||
size_t Print::print(int n, int base)
|
||||
{
|
||||
return print((long) n, base);
|
||||
}
|
||||
|
||||
size_t Print::print(unsigned int n, int base)
|
||||
{
|
||||
return print((unsigned long) n, base);
|
||||
}
|
||||
|
||||
size_t Print::print(long n, int base)
|
||||
{
|
||||
int t = 0;
|
||||
if (base == 10 && n < 0) {
|
||||
t = print('-');
|
||||
n = -n;
|
||||
}
|
||||
return printNumber(static_cast<unsigned long>(n), base) + t;
|
||||
}
|
||||
|
||||
size_t Print::print(unsigned long n, int base)
|
||||
{
|
||||
if(base == 0) {
|
||||
return write(n);
|
||||
} else {
|
||||
return printNumber(n, base);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Print::print(long long n, int base)
|
||||
{
|
||||
int t = 0;
|
||||
if (base == 10 && n < 0) {
|
||||
t = print('-');
|
||||
n = -n;
|
||||
}
|
||||
return printNumber(static_cast<unsigned long long>(n), base) + t;
|
||||
}
|
||||
|
||||
size_t Print::print(unsigned long long n, int base)
|
||||
{
|
||||
if (base == 0) {
|
||||
return write(n);
|
||||
} else {
|
||||
return printNumber(n, base);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Print::print(double n, int digits)
|
||||
{
|
||||
return printFloat(n, digits);
|
||||
}
|
||||
|
||||
size_t Print::print(struct tm * timeinfo, const char * format)
|
||||
{
|
||||
const char * f = format;
|
||||
if(!f){
|
||||
f = "%c";
|
||||
}
|
||||
char buf[64];
|
||||
size_t written = strftime(buf, 64, f, timeinfo);
|
||||
if(written == 0){
|
||||
return written;
|
||||
}
|
||||
return print(buf);
|
||||
}
|
||||
|
||||
size_t Print::println(void)
|
||||
{
|
||||
return print("\r\n");
|
||||
}
|
||||
|
||||
size_t Print::println(const std::string &s)
|
||||
{
|
||||
size_t n = print(s);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(const char c[])
|
||||
{
|
||||
size_t n = print(c);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(char c)
|
||||
{
|
||||
size_t n = print(c);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(unsigned char b, int base)
|
||||
{
|
||||
size_t n = print(b, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(int num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(unsigned int num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(long num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(unsigned long num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(long long num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(unsigned long long num, int base)
|
||||
{
|
||||
size_t n = print(num, base);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(double num, int digits)
|
||||
{
|
||||
size_t n = print(num, digits);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
size_t Print::println(struct tm * timeinfo, const char * format)
|
||||
{
|
||||
size_t n = print(timeinfo, format);
|
||||
n += println();
|
||||
return n;
|
||||
}
|
||||
|
||||
// Private Methods /////////////////////////////////////////////////////////////
|
||||
|
||||
size_t Print::printNumber(unsigned long n, uint8_t base)
|
||||
{
|
||||
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||
char *str = &buf[sizeof(buf) - 1];
|
||||
|
||||
*str = '\0';
|
||||
|
||||
// prevent crash if called with base == 1
|
||||
if(base < 2) {
|
||||
base = 10;
|
||||
}
|
||||
|
||||
do {
|
||||
char c = n % base;
|
||||
n /= base;
|
||||
|
||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||
} while (n);
|
||||
|
||||
return write(str);
|
||||
}
|
||||
|
||||
size_t Print::printNumber(unsigned long long n, uint8_t base)
|
||||
{
|
||||
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||
char* str = &buf[sizeof(buf) - 1];
|
||||
|
||||
*str = '\0';
|
||||
|
||||
// prevent crash if called with base == 1
|
||||
if (base < 2) {
|
||||
base = 10;
|
||||
}
|
||||
|
||||
do {
|
||||
auto m = n;
|
||||
n /= base;
|
||||
char c = m - base * n;
|
||||
|
||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||
} while (n);
|
||||
|
||||
return write(str);
|
||||
}
|
||||
|
||||
size_t Print::printFloat(double number, uint8_t digits)
|
||||
{
|
||||
size_t n = 0;
|
||||
|
||||
if(isnan(number)) {
|
||||
return print("nan");
|
||||
}
|
||||
if(isinf(number)) {
|
||||
return print("inf");
|
||||
}
|
||||
if(number > 4294967040.0) {
|
||||
return print("ovf"); // constant determined empirically
|
||||
}
|
||||
if(number < -4294967040.0) {
|
||||
return print("ovf"); // constant determined empirically
|
||||
}
|
||||
|
||||
// Handle negative numbers
|
||||
if(number < 0.0) {
|
||||
n += print('-');
|
||||
number = -number;
|
||||
}
|
||||
|
||||
// Round correctly so that print(1.999, 2) prints as "2.00"
|
||||
double rounding = 0.5;
|
||||
for(uint8_t i = 0; i < digits; ++i) {
|
||||
rounding /= 10.0;
|
||||
}
|
||||
|
||||
number += rounding;
|
||||
|
||||
// Extract the integer part of the number and print it
|
||||
unsigned long int_part = (unsigned long) number;
|
||||
double remainder = number - (double) int_part;
|
||||
n += print(int_part);
|
||||
|
||||
// Print the decimal point, but only if there are digits beyond
|
||||
if(digits > 0) {
|
||||
n += print(".");
|
||||
}
|
||||
|
||||
// Extract digits from the remainder one at a time
|
||||
while(digits-- > 0) {
|
||||
remainder *= 10.0;
|
||||
int toPrint = int(remainder);
|
||||
n += print(toPrint);
|
||||
remainder -= toPrint;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif
|
||||
121
lib/microReticulum/src/Utilities/Print.h
Executable file
121
lib/microReticulum/src/Utilities/Print.h
Executable file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Print.h - Base class that provides print() and println()
|
||||
Copyright (c) 2008 David A. Mellis. All right reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#ifndef Print_h
|
||||
#define Print_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#include <string>
|
||||
|
||||
//#define DEC 10
|
||||
//#define HEX 16
|
||||
//#define OCT 8
|
||||
//#define BIN 2
|
||||
static constexpr uint8_t DEC {10};
|
||||
static constexpr uint8_t HEX {16};
|
||||
static constexpr uint8_t OCT {8};
|
||||
static constexpr uint8_t BIN {2};
|
||||
|
||||
class Print
|
||||
{
|
||||
private:
|
||||
int write_error;
|
||||
size_t printNumber(unsigned long, uint8_t);
|
||||
size_t printNumber(unsigned long long, uint8_t);
|
||||
size_t printFloat(double, uint8_t);
|
||||
protected:
|
||||
void setWriteError(int err = 1)
|
||||
{
|
||||
write_error = err;
|
||||
}
|
||||
public:
|
||||
Print() :
|
||||
write_error(0)
|
||||
{
|
||||
}
|
||||
virtual ~Print() {}
|
||||
int getWriteError()
|
||||
{
|
||||
return write_error;
|
||||
}
|
||||
void clearWriteError()
|
||||
{
|
||||
setWriteError(0);
|
||||
}
|
||||
|
||||
virtual size_t write(uint8_t) = 0;
|
||||
size_t write(const char *str)
|
||||
{
|
||||
if(str == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return write((const uint8_t *) str, strlen(str));
|
||||
}
|
||||
virtual size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t write(const char *buffer, size_t size)
|
||||
{
|
||||
return write((const uint8_t *) buffer, size);
|
||||
}
|
||||
|
||||
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
// add availableForWrite to make compatible with Arduino Print.h
|
||||
// default to zero, meaning "a single write may block"
|
||||
// should be overriden by subclasses with buffering
|
||||
virtual int availableForWrite() { return 0; }
|
||||
size_t print(const std::string &);
|
||||
size_t print(const char[]);
|
||||
size_t print(char);
|
||||
size_t print(unsigned char, int = DEC);
|
||||
size_t print(int, int = DEC);
|
||||
size_t print(unsigned int, int = DEC);
|
||||
size_t print(long, int = DEC);
|
||||
size_t print(unsigned long, int = DEC);
|
||||
size_t print(long long, int = DEC);
|
||||
size_t print(unsigned long long, int = DEC);
|
||||
size_t print(double, int = 2);
|
||||
size_t print(struct tm * timeinfo, const char * format = NULL);
|
||||
|
||||
size_t println(const std::string &s);
|
||||
size_t println(const char[]);
|
||||
size_t println(char);
|
||||
size_t println(unsigned char, int = DEC);
|
||||
size_t println(int, int = DEC);
|
||||
size_t println(unsigned int, int = DEC);
|
||||
size_t println(long, int = DEC);
|
||||
size_t println(unsigned long, int = DEC);
|
||||
size_t println(long long, int = DEC);
|
||||
size_t println(unsigned long long, int = DEC);
|
||||
size_t println(double, int = 2);
|
||||
size_t println(struct tm * timeinfo, const char * format = NULL);
|
||||
size_t println(void);
|
||||
|
||||
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
339
lib/microReticulum/src/Utilities/Stream.cpp
Executable file
339
lib/microReticulum/src/Utilities/Stream.cpp
Executable file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
Stream.cpp - adds parsing methods to Stream class
|
||||
Copyright (c) 2008 David A. Mellis. All right reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Created July 2011
|
||||
parsing functions based on TextFinder library by Michael Margolis
|
||||
*/
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#include "Stream.h"
|
||||
#include "OS.h"
|
||||
|
||||
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
|
||||
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
|
||||
|
||||
// private method to read stream with timeout
|
||||
int Stream::timedRead()
|
||||
{
|
||||
int c;
|
||||
_startMillis = RNS::Utilities::OS::ltime();
|
||||
do {
|
||||
c = read();
|
||||
if(c >= 0) {
|
||||
return c;
|
||||
}
|
||||
} while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
|
||||
return -1; // -1 indicates timeout
|
||||
}
|
||||
|
||||
// private method to peek stream with timeout
|
||||
int Stream::timedPeek()
|
||||
{
|
||||
int c;
|
||||
_startMillis = RNS::Utilities::OS::ltime();
|
||||
do {
|
||||
c = peek();
|
||||
if(c >= 0) {
|
||||
return c;
|
||||
}
|
||||
} while(RNS::Utilities::OS::ltime() - _startMillis < _timeout);
|
||||
return -1; // -1 indicates timeout
|
||||
}
|
||||
|
||||
// returns peek of the next digit in the stream or -1 if timeout
|
||||
// discards non-numeric characters
|
||||
int Stream::peekNextDigit()
|
||||
{
|
||||
int c;
|
||||
while(1) {
|
||||
c = timedPeek();
|
||||
if(c < 0) {
|
||||
return c; // timeout
|
||||
}
|
||||
if(c == '-') {
|
||||
return c;
|
||||
}
|
||||
if(c >= '0' && c <= '9') {
|
||||
return c;
|
||||
}
|
||||
read(); // discard non-numeric
|
||||
}
|
||||
}
|
||||
|
||||
// Public Methods
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait
|
||||
{
|
||||
_timeout = timeout;
|
||||
}
|
||||
unsigned long Stream::getTimeout(void) {
|
||||
return _timeout;
|
||||
}
|
||||
|
||||
// find returns true if the target string is found
|
||||
bool Stream::find(const char *target)
|
||||
{
|
||||
return findUntil(target, strlen(target), NULL, 0);
|
||||
}
|
||||
|
||||
// reads data from the stream until the target string of given length is found
|
||||
// returns true if target string is found, false if timed out
|
||||
bool Stream::find(const char *target, size_t length)
|
||||
{
|
||||
return findUntil(target, length, NULL, 0);
|
||||
}
|
||||
|
||||
// as find but search ends if the terminator string is found
|
||||
bool Stream::findUntil(const char *target, const char *terminator)
|
||||
{
|
||||
return findUntil(target, strlen(target), terminator, strlen(terminator));
|
||||
}
|
||||
|
||||
// reads data from the stream until the target string of the given length is found
|
||||
// search terminated if the terminator string is found
|
||||
// returns true if target string is found, false if terminated or timed out
|
||||
bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen)
|
||||
{
|
||||
if (terminator == NULL) {
|
||||
MultiTarget t[1] = {{target, targetLen, 0}};
|
||||
return findMulti(t, 1) == 0 ? true : false;
|
||||
} else {
|
||||
MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
|
||||
return findMulti(t, 2) == 0 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) {
|
||||
// any zero length target string automatically matches and would make
|
||||
// a mess of the rest of the algorithm.
|
||||
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
|
||||
if (t->len <= 0)
|
||||
return t - targets;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int c = timedRead();
|
||||
if (c < 0)
|
||||
return -1;
|
||||
|
||||
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
|
||||
// the simple case is if we match, deal with that first.
|
||||
if (c == t->str[t->index]) {
|
||||
if (++t->index == t->len)
|
||||
return t - targets;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
// if not we need to walk back and see if we could have matched further
|
||||
// down the stream (ie '1112' doesn't match the first position in '11112'
|
||||
// but it will match the second position so we can't just reset the current
|
||||
// index to 0 when we find a mismatch.
|
||||
if (t->index == 0)
|
||||
continue;
|
||||
|
||||
int origIndex = t->index;
|
||||
do {
|
||||
--t->index;
|
||||
// first check if current char works against the new current index
|
||||
if (c != t->str[t->index])
|
||||
continue;
|
||||
|
||||
// if it's the only char then we're good, nothing more to check
|
||||
if (t->index == 0) {
|
||||
t->index++;
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise we need to check the rest of the found string
|
||||
int diff = origIndex - t->index;
|
||||
size_t i;
|
||||
for (i = 0; i < t->index; ++i) {
|
||||
if (t->str[i] != t->str[i + diff])
|
||||
break;
|
||||
}
|
||||
|
||||
// if we successfully got through the previous loop then our current
|
||||
// index is good.
|
||||
if (i == t->index) {
|
||||
t->index++;
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise we just try the next index
|
||||
} while (t->index);
|
||||
}
|
||||
}
|
||||
// unreachable
|
||||
return -1;
|
||||
}
|
||||
|
||||
// returns the first valid (long) integer value from the current position.
|
||||
// initial characters that are not digits (or the minus sign) are skipped
|
||||
// function is terminated by the first character that is not a digit.
|
||||
long Stream::parseInt()
|
||||
{
|
||||
return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout)
|
||||
}
|
||||
|
||||
// as above but a given skipChar is ignored
|
||||
// this allows format characters (typically commas) in values to be ignored
|
||||
long Stream::parseInt(char skipChar)
|
||||
{
|
||||
bool isNegative = false;
|
||||
long value = 0;
|
||||
int c;
|
||||
|
||||
c = peekNextDigit();
|
||||
// ignore non numeric leading characters
|
||||
if(c < 0) {
|
||||
return 0; // zero returned if timeout
|
||||
}
|
||||
|
||||
do {
|
||||
if(c == skipChar) {
|
||||
} // ignore this charactor
|
||||
else if(c == '-') {
|
||||
isNegative = true;
|
||||
} else if(c >= '0' && c <= '9') { // is c a digit?
|
||||
value = value * 10 + c - '0';
|
||||
}
|
||||
read(); // consume the character we got with peek
|
||||
c = timedPeek();
|
||||
} while((c >= '0' && c <= '9') || c == skipChar);
|
||||
|
||||
if(isNegative) {
|
||||
value = -value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// as parseInt but returns a floating point value
|
||||
float Stream::parseFloat()
|
||||
{
|
||||
return parseFloat(NO_SKIP_CHAR);
|
||||
}
|
||||
|
||||
// as above but the given skipChar is ignored
|
||||
// this allows format characters (typically commas) in values to be ignored
|
||||
float Stream::parseFloat(char skipChar)
|
||||
{
|
||||
bool isNegative = false;
|
||||
bool isFraction = false;
|
||||
long value = 0;
|
||||
int c;
|
||||
float fraction = 1.0;
|
||||
|
||||
c = peekNextDigit();
|
||||
// ignore non numeric leading characters
|
||||
if(c < 0) {
|
||||
return 0; // zero returned if timeout
|
||||
}
|
||||
|
||||
do {
|
||||
if(c == skipChar) {
|
||||
} // ignore
|
||||
else if(c == '-') {
|
||||
isNegative = true;
|
||||
} else if(c == '.') {
|
||||
isFraction = true;
|
||||
} else if(c >= '0' && c <= '9') { // is c a digit?
|
||||
value = value * 10 + c - '0';
|
||||
if(isFraction) {
|
||||
fraction *= 0.1f;
|
||||
}
|
||||
}
|
||||
read(); // consume the character we got with peek
|
||||
c = timedPeek();
|
||||
} while((c >= '0' && c <= '9') || c == '.' || c == skipChar);
|
||||
|
||||
if(isNegative) {
|
||||
value = -value;
|
||||
}
|
||||
if(isFraction) {
|
||||
return value * fraction;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// read characters from stream into buffer
|
||||
// terminates if length characters have been read, or timeout (see setTimeout)
|
||||
// returns the number of characters placed in the buffer
|
||||
// the buffer is NOT null terminated.
|
||||
//
|
||||
size_t Stream::readBytes(char *buffer, size_t length)
|
||||
{
|
||||
size_t count = 0;
|
||||
while(count < length) {
|
||||
int c = timedRead();
|
||||
if(c < 0) {
|
||||
break;
|
||||
}
|
||||
*buffer++ = (char) c;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// as readBytes with terminator character
|
||||
// terminates if length characters have been read, timeout, or if the terminator character detected
|
||||
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||
|
||||
size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
|
||||
{
|
||||
if(length < 1) {
|
||||
return 0;
|
||||
}
|
||||
size_t index = 0;
|
||||
while(index < length) {
|
||||
int c = timedRead();
|
||||
if(c < 0 || c == terminator) {
|
||||
break;
|
||||
}
|
||||
*buffer++ = (char) c;
|
||||
index++;
|
||||
}
|
||||
return index; // return number of characters, not including null terminator
|
||||
}
|
||||
|
||||
std::string Stream::readString()
|
||||
{
|
||||
std::string ret;
|
||||
int c = timedRead();
|
||||
while(c >= 0) {
|
||||
ret += (char) c;
|
||||
c = timedRead();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Stream::readStringUntil(char terminator)
|
||||
{
|
||||
std::string ret;
|
||||
int c = timedRead();
|
||||
while(c >= 0 && c != terminator) {
|
||||
ret += (char) c;
|
||||
c = timedRead();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
143
lib/microReticulum/src/Utilities/Stream.h
Executable file
143
lib/microReticulum/src/Utilities/Stream.h
Executable file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Stream.h - base class for character-based streams.
|
||||
Copyright (c) 2010 David A. Mellis. All right reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
parsing functions based on TextFinder library by Michael Margolis
|
||||
*/
|
||||
|
||||
#ifndef ARDUINO
|
||||
|
||||
#ifndef Stream_h
|
||||
#define Stream_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "Print.h"
|
||||
|
||||
// compatability macros for testing
|
||||
/*
|
||||
#define getInt() parseInt()
|
||||
#define getInt(skipChar) parseInt(skipchar)
|
||||
#define getFloat() parseFloat()
|
||||
#define getFloat(skipChar) parseFloat(skipChar)
|
||||
#define getString( pre_string, post_string, buffer, length)
|
||||
readBytesBetween( pre_string, terminator, buffer, length)
|
||||
*/
|
||||
|
||||
class Stream: public Print
|
||||
{
|
||||
protected:
|
||||
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
|
||||
unsigned long _startMillis; // used for timeout measurement
|
||||
int timedRead(); // private method to read stream with timeout
|
||||
int timedPeek(); // private method to peek stream with timeout
|
||||
int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout
|
||||
|
||||
public:
|
||||
virtual int available() = 0;
|
||||
virtual int read() = 0;
|
||||
virtual int peek() = 0;
|
||||
|
||||
Stream():_startMillis(0)
|
||||
{
|
||||
_timeout = 1000;
|
||||
}
|
||||
virtual ~Stream() {}
|
||||
|
||||
// parsing methods
|
||||
|
||||
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
|
||||
unsigned long getTimeout(void);
|
||||
|
||||
bool find(const char *target); // reads data from the stream until the target string is found
|
||||
bool find(uint8_t *target)
|
||||
{
|
||||
return find((char *) target);
|
||||
}
|
||||
// returns true if target string is found, false if timed out (see setTimeout)
|
||||
|
||||
bool find(const char *target, size_t length); // reads data from the stream until the target string of given length is found
|
||||
bool find(const uint8_t *target, size_t length)
|
||||
{
|
||||
return find((char *) target, length);
|
||||
}
|
||||
// returns true if target string is found, false if timed out
|
||||
|
||||
bool find(char target)
|
||||
{
|
||||
return find (&target, 1);
|
||||
}
|
||||
|
||||
bool findUntil(const char *target, const char *terminator); // as find but search ends if the terminator string is found
|
||||
bool findUntil(const uint8_t *target, const char *terminator)
|
||||
{
|
||||
return findUntil((char *) target, terminator);
|
||||
}
|
||||
|
||||
bool findUntil(const char *target, size_t targetLen, const char *terminate, size_t termLen); // as above but search ends if the terminate string is found
|
||||
bool findUntil(const uint8_t *target, size_t targetLen, const char *terminate, size_t termLen)
|
||||
{
|
||||
return findUntil((char *) target, targetLen, terminate, termLen);
|
||||
}
|
||||
|
||||
long parseInt(); // returns the first valid (long) integer value from the current position.
|
||||
// initial characters that are not digits (or the minus sign) are skipped
|
||||
// integer is terminated by the first character that is not a digit.
|
||||
|
||||
float parseFloat(); // float version of parseInt
|
||||
|
||||
virtual size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer
|
||||
virtual size_t readBytes(uint8_t *buffer, size_t length)
|
||||
{
|
||||
return readBytes((char *) buffer, length);
|
||||
}
|
||||
// terminates if length characters have been read or timeout (see setTimeout)
|
||||
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||
|
||||
size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character
|
||||
size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length)
|
||||
{
|
||||
return readBytesUntil(terminator, (char *) buffer, length);
|
||||
}
|
||||
// terminates if length characters have been read, timeout, or if the terminator character detected
|
||||
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||
|
||||
// Arduino String functions to be added here
|
||||
virtual std::string readString();
|
||||
std::string readStringUntil(char terminator);
|
||||
|
||||
protected:
|
||||
long parseInt(char skipChar); // as above but the given skipChar is ignored
|
||||
// as above but the given skipChar is ignored
|
||||
// this allows format characters (typically commas) in values to be ignored
|
||||
|
||||
float parseFloat(char skipChar); // as above but the given skipChar is ignored
|
||||
|
||||
struct MultiTarget {
|
||||
const char *str; // string you're searching for
|
||||
size_t len; // length of string you're searching for
|
||||
size_t index; // index used by the search routine.
|
||||
};
|
||||
|
||||
// This allows you to search for an arbitrary number of strings.
|
||||
// Returns index of the target that is found first or -1 if timeout occurs.
|
||||
int findMulti(struct MultiTarget *targets, int tCount);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
1268
lib/microReticulum/src/Utilities/tlsf.c
Executable file
1268
lib/microReticulum/src/Utilities/tlsf.c
Executable file
File diff suppressed because it is too large
Load Diff
90
lib/microReticulum/src/Utilities/tlsf.h
Executable file
90
lib/microReticulum/src/Utilities/tlsf.h
Executable file
@@ -0,0 +1,90 @@
|
||||
#ifndef INCLUDED_tlsf
|
||||
#define INCLUDED_tlsf
|
||||
|
||||
/*
|
||||
** Two Level Segregated Fit memory allocator, version 3.1.
|
||||
** Written by Matthew Conte
|
||||
** http://tlsf.baisoku.org
|
||||
**
|
||||
** Based on the original documentation by Miguel Masmano:
|
||||
** http://www.gii.upv.es/tlsf/main/docs
|
||||
**
|
||||
** This implementation was written to the specification
|
||||
** of the document, therefore no GPL restrictions apply.
|
||||
**
|
||||
** Copyright (c) 2006-2016, Matthew Conte
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
** * Neither the name of the copyright holder nor the
|
||||
** names of its contributors may be used to endorse or promote products
|
||||
** derived from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
|
||||
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
|
||||
/* pool_t: a block of memory that TLSF can manage. */
|
||||
typedef void* tlsf_t;
|
||||
typedef void* pool_t;
|
||||
|
||||
/* Create/destroy a memory pool. */
|
||||
tlsf_t tlsf_create(void* mem);
|
||||
tlsf_t tlsf_create_with_pool(void* mem, size_t bytes);
|
||||
void tlsf_destroy(tlsf_t tlsf);
|
||||
pool_t tlsf_get_pool(tlsf_t tlsf);
|
||||
|
||||
/* Add/remove memory pools. */
|
||||
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
|
||||
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
|
||||
|
||||
/* malloc/memalign/realloc/free replacements. */
|
||||
void* tlsf_malloc(tlsf_t tlsf, size_t bytes);
|
||||
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes);
|
||||
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
|
||||
void tlsf_free(tlsf_t tlsf, void* ptr);
|
||||
|
||||
/* Returns internal block size, not original request size */
|
||||
size_t tlsf_block_size(void* ptr);
|
||||
|
||||
/* Overheads/limits of internal structures. */
|
||||
size_t tlsf_size(void);
|
||||
size_t tlsf_align_size(void);
|
||||
size_t tlsf_block_size_min(void);
|
||||
size_t tlsf_block_size_max(void);
|
||||
size_t tlsf_pool_overhead(void);
|
||||
size_t tlsf_alloc_overhead(void);
|
||||
|
||||
/* Debugging. */
|
||||
typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
|
||||
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
|
||||
/* Returns nonzero if any internal consistency check fails. */
|
||||
int tlsf_check(tlsf_t tlsf);
|
||||
int tlsf_check_pool(pool_t pool);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
23
lib/microReticulum/src/main.cpp
Executable file
23
lib/microReticulum/src/main.cpp
Executable file
@@ -0,0 +1,23 @@
|
||||
// Only include if building locally and NOT testing
|
||||
#if defined(LIBRARY_TEST) && !defined(PIO_UNIT_TESTING)
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.print("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
|
||||
}
|
||||
void loop() {
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main(void) {
|
||||
printf("\nSilly rabbit, microReticulum is a library!\n\nSee the examples directory for example programs that make use of this library.\n\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user