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:
James L
2026-02-23 18:08:29 -05:00
parent e741d1cd64
commit 5ed70dcca9
66 changed files with 18264 additions and 35 deletions

View 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;
}

View 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)); }
};
} }

View 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
}

View 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();
};
} }

View 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);

View 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
} }

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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