diff --git a/README.md b/README.md index 53fab5ac..0e381873 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This API is dependent on the following external libraries: - `-DRNS_USE_FS` Used to enable use of file system by RNS for persistence - `-DRNS_PERSIST_PATHS` Used to enable persistence of RNS paths in file system (also requires `-DRNS_USE_FS`) - `-DRNS_USE_TLSF=1` Enables the use of the TLSF (Two-Level Segregate Fit) dynamic memory manager for efficient management of constrained MCU memory with minimal fragmentation. Currently only required on NRF52 boards (ESP32 already uses TLSF internally). +- `-RNS_TLSF_BUFFER_SIZ=N` Defines the TLSF memory pool buffer size (N bytes, default 0 which means dynamic) - `-DRNS_USE_ALLOCATOR=1` Enables the replacement of default new/delete operators with custom implementations that take advantage of optimized memory managers (eg, TLSF). Currently only required on NRF52 boards (ESP32 already uses TLSF internally). ## Building diff --git a/boards/rak4630.json b/boards/rak4630.json index c7ef814c..53b4f35b 100644 --- a/boards/rak4630.json +++ b/boards/rak4630.json @@ -46,6 +46,7 @@ ], "debug": { "jlink_device": "nRF52840_xxAA", + "openocd_target": "nrf52840", "svd_path": "nrf52840.svd" }, "frameworks": [ diff --git a/platformio.ini b/platformio.ini index 8367ba0e..b23a830e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,6 +12,7 @@ default_envs = native17 [env] +monitor_speed = 115200 build_unflags = -fno-exceptions build_flags = @@ -70,7 +71,37 @@ lib_deps = ${env.lib_deps} lib_compat_mode = off -[env:mcu] +[env:native_debug] +platform = native +build_flags = + ${env.build_flags} + -std=gnu++11 + -DNATIVE + ; CBA TEST + ;-fsanitize=address + -fno-omit-frame-pointer + -g + ;-O1 + -O0 + ;-DRNS_MEM_LOG + -DRNS_USE_ALLOCATOR=1 + -DRNS_USE_TLSF=1 + -DTLSF_DEBUG=1 + ;-DRNS_TLSF_BUFFER_SIZE=81920 + ;-DRNS_TLSF_BUFFER_SIZE=102400 + -DRNS_TLSF_BUFFER_SIZE=204800 + ;-DRNS_TLSF_BUFFER_SIZE=307200 + ;-DRNS_TLSF_BUFFER_SIZE=409600 + ;-DRNS_TLSF_BUFFER_SIZE=512000 + ;-DRNS_TLSF_BUFFER_SIZE=614400 + ;-DRNS_TLSF_BUFFER_SIZE=1024000 +lib_deps = + ${env.lib_deps} +lib_compat_mode = off + +[env:embedded] +framework = arduino +upload_speed = 460800 build_unflags = ${env.build_unflags} ;-std=gnu++11 @@ -81,59 +112,75 @@ lib_deps = ${env.lib_deps} [env:ttgo-lora32-v21] +extends = env:embedded platform = espressif32 board = ttgo-lora32-v21 board_build.partitions = no_ota.csv -framework = arduino -monitor_speed = 115200 build_unflags = - ${env:mcu.build_unflags} + ${env:embedded.build_unflags} -std=gnu++11 build_flags = - ${env:mcu.build_flags} + ${env:embedded.build_flags} -std=gnu++17 -DBOARD_ESP32 -DMSGPACK_USE_BOOST=OFF lib_deps = - ${env:mcu.lib_deps} + ${env:embedded.lib_deps} +monitor_filters = esp32_exception_decoder [env:ttgo-t-beam] +extends = env:embedded platform = espressif32 board = ttgo-t-beam board_build.partitions = no_ota.csv ;board_build.partitions = huge_app.csv -framework = arduino -monitor_speed = 115200 build_flags = - ${env:mcu.build_flags} + ${env:embedded.build_flags} -DBOARD_ESP32 -DMSGPACK_USE_BOOST=OFF lib_deps = - ${env:mcu.lib_deps} + ${env:embedded.lib_deps} +monitor_filters = esp32_exception_decoder [env:lilygo_tbeam_supreme] +extends = env:embedded platform = espressif32 board = t-beams3-supreme board_build.partitions = no_ota.csv ;board_build.partitions = huge_app.csv -framework = arduino -monitor_speed = 115200 build_flags = - ${env:mcu.build_flags} + ${env:embedded.build_flags} -DBOARD_ESP32 -DMSGPACK_USE_BOOST=OFF lib_deps = - ${env:mcu.lib_deps} + ${env:embedded.lib_deps} +monitor_filters = esp32_exception_decoder + +; also Heltec Wireless Tracker +[env:heltec_wifi_lora_32_V4] +extends = env:embedded +platform = espressif32 +board = esp32-s3-devkitc-1 +board_build.partitions = no_ota.csv +;board_build.partitions = huge_app.csv +build_flags = + ${env.build_flags} + -DBOARD_ESP32 + -DMSGPACK_USE_BOOST=OFF + -DARDUINO_USB_CDC_ON_BOOT=1 +lib_deps = + ${env:embedded.lib_deps} +monitor_filters = esp32_exception_decoder +upload_speed = 921600 [env:wiscore_rak4631] +extends = env:embedded platform = nordicnrf52 board = rak4630 board_build.partitions = no_ota.csv -framework = arduino -monitor_speed = 115200 -build_src_filter = ${env:mcu.build_src_filter}+<../variants/rak4630> +build_src_filter = ${env:embedded.build_src_filter}+<../variants/rak4630> build_flags = - ${env:mcu.build_flags} + ${env:embedded.build_flags} -I variants/rak4630 -fexceptions -DBOARD_NRF52 @@ -141,12 +188,12 @@ build_flags = -DRNS_USE_TLSF=1 -DMSGPACK_USE_BOOST=OFF lib_deps = - ${env:mcu.lib_deps} + ${env:embedded.lib_deps} adafruit/Adafruit TinyUSB Library ; Overrides for specific tests (not working!) [test_env:test_objects] build_flags = - ${env:mcu.build_flags} + ${env:embedded.build_flags} ;-DTEST_OBJECT_DATA=1 -DTEST_OBJECT_IMPL=1 diff --git a/src/Destination.cpp b/src/Destination.cpp index 778c79d2..957d05d8 100644 --- a/src/Destination.cpp +++ b/src/Destination.cpp @@ -195,129 +195,129 @@ Packet Destination::announce(const Bytes& app_data, bool path_response, const In } try { - 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; + 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; + 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()); + // 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"); } - else { - TRACE("Destination::announce: tag not found in _path_responses"); + 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"); + 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; + 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 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()); + 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; + announce_data << _object->_identity.get_public_key() << _object->_name_hash << random_hash << signature; - if (new_app_data) { - announce_data << new_app_data; - } + if (new_app_data) { + announce_data << new_app_data; + } - // CBA ACCUMULATES - try { - _object->_path_responses.insert({tag, {OS::time(), announce_data}}); - } - catch (const std::bad_alloc&) { - ERROR("announce: out of memory, path response not stored for " + _object->_hash.toHex()); - } - catch (const std::exception& e) { - ERROR(std::string("announce: exception storing path response: ") + e.what()); + // CBA ACCUMULATES + try { + _object->_path_responses.insert({tag, {OS::time(), announce_data}}); + } + catch (const std::bad_alloc&) { + ERROR("announce: bad_alloc - out of memory, path response not stored for " + _object->_hash.toHex()); + } + catch (const std::exception& e) { + ERROR(std::string("announce: exception storing path response: ") + e.what()); + } } - } - //TRACE("Destination::announce: announce_data:" + announce_data.toHex()); + //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; - } + 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); + //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; - } + if (send) { + TRACE("Destination::announce: sending announce packet..."); + announce_packet.send(); + return {Type::NONE}; + } + else { + return announce_packet; + } } catch (const std::bad_alloc&) { - ERROR("announce: out of memory, announce not sent for " + _object->_hash.toHex()); + ERROR("announce: bad_alloc - out of memory, announce not sent for " + _object->_hash.toHex()); return {Type::NONE}; } catch (const std::exception& e) { @@ -407,7 +407,7 @@ TRACE("***** Accepting link request"); _object->_links.insert(link); } catch (const std::bad_alloc&) { - ERROR("incoming_link_request: out of memory, link not tracked"); + ERROR("incoming_link_request: bad_alloc - out of memory, link not tracked"); } catch (const std::exception& e) { ERROR(std::string("incoming_link_request: exception tracking link: ") + e.what()); diff --git a/src/Identity.cpp b/src/Identity.cpp index aa404dc2..d2ac586e 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -201,9 +201,11 @@ Can be used to load previously created and saved identities into Reticulum. // CBA ACCUMULATES try { _known_destinations.insert({destination_hash, {OS::time(), packet_hash, public_key, app_data}}); + // CBA IMMEDIATE CULL + cull_known_destinations(); } catch (const std::bad_alloc&) { - ERROR("remember: out of memory, identity not stored for " + destination_hash.toHex()); + ERROR("remember: bad_alloc - out of memory, identity not stored for " + destination_hash.toHex()); } catch (const std::exception& e) { ERROR(std::string("remember: exception storing identity: ") + e.what()); @@ -306,6 +308,8 @@ Recall last heard app_data for a destination hash. //_known_destinations[destination_hash] = identity_entry; // CBA ACCUMULATES _known_destinations.insert({destination_hash, identity_entry}); + // CBA IMMEDIATE CULL + cull_known_destinations(); } } @@ -390,7 +394,7 @@ Recall last heard app_data for a destination hash. DEBUG("Removed " + std::to_string(count) + " path(s) from known destinations"); } catch (const std::bad_alloc& e) { - ERROR("cull_known_destinations: out of memory building sort index, falling back to single erase"); + ERROR("cull_known_destinations: bad_alloc - out of memory building sort index, falling back to single erase"); // Fallback: std::min_element does no heap allocation — erase one oldest entry auto oldest = std::min_element( _known_destinations.begin(), _known_destinations.end(), diff --git a/src/Log.cpp b/src/Log.cpp index 2da9a511..decc74ec 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -98,10 +98,15 @@ void RNS::doLog(LogLevel level, const char* msg) { #endif } -void HEAD(const char* msg, LogLevel level) { +void RNS::doHeadLog(LogLevel level, const char* msg) { if (level > _level) { return; } + if (_on_log != nullptr) { + _on_log("", level); + _on_log(msg, level); + return; + } #ifdef ARDUINO Serial.println(""); #else diff --git a/src/Log.h b/src/Log.h index 5caacbbb..34f44c0d 100644 --- a/src/Log.h +++ b/src/Log.h @@ -8,30 +8,35 @@ #include +#ifndef ARDUINO + #define msg (msg) +#endif + #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__)) + +#define CRITICAL(msg) (RNS::log(msg, RNS::LOG_CRITICAL)) +#define CRITICALF(msg, ...) (RNS::logf(RNS::LOG_CRITICAL, msg, __VA_ARGS__)) +#define ERROR(msg) (RNS::log(msg, RNS::LOG_ERROR)) +#define ERRORF(msg, ...) (RNS::logf(RNS::LOG_ERROR, msg, __VA_ARGS__)) +#define WARNING(msg) (RNS::log(msg, RNS::LOG_WARNING)) +#define WARNINGF(msg, ...) (RNS::logf(RNS::LOG_WARNING, msg, __VA_ARGS__)) +#define NOTICE(msg) (RNS::log(msg, RNS::LOG_NOTICE)) +#define NOTICEF(msg, ...) (RNS::logf(RNS::LOG_NOTICE, msg, __VA_ARGS__)) +#define INFO(msg) (RNS::log(msg, RNS::LOG_INFO)) +#define INFOF(msg, ...) (RNS::logf(RNS::LOG_INFO, msg, __VA_ARGS__)) +#define VERBOSE(msg) (RNS::log(msg, RNS::LOG_VERBOSE)) +#define VERBOSEF(msg, ...) (RNS::logf(RNS::LOG_VERBOSE, 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__)) + #define DEBUG(msg) (RNS::log(msg, RNS::LOG_DEBUG)) + #define DEBUGF(msg, ...) (RNS::logf(RNS::LOG_DEBUG, msg, __VA_ARGS__)) + #define TRACE(msg) (RNS::log(msg, RNS::LOG_TRACE)) + #define TRACEF(msg, ...) (RNS::logf(RNS::LOG_TRACE, msg, __VA_ARGS__)) #if defined(RNS_MEM_LOG) - #define MEM(msg) (RNS::mem(msg)) - #define MEMF(msg, ...) (RNS::memf(msg, __VA_ARGS__)) + #define MEM(msg) (RNS::log(msg, RNS::LOG_MEM)) + #define MEMF(msg, ...) (RNS::logf(RNS::LOG_MEM, msg, __VA_ARGS__)) #else #define MEM(ignore) ((void)0) #define MEMF(...) ((void)0) @@ -39,12 +44,14 @@ #else #define DEBUG(ignore) ((void)0) #define DEBUGF(...) ((void)0) - #define TRACE(...) ((void)0) + #define TRACE(ignore) ((void)0) #define TRACEF(...) ((void)0) #define MEM(ignore) ((void)0) #define MEMF(...) ((void)0) #endif +#define RNS_LOG_BUFFER_SIZE 1024 + namespace RNS { enum LogLevel { @@ -71,19 +78,38 @@ namespace RNS { 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 doLog(LogLevel level, const char* msg, va_list vlist) { char buf[RNS_LOG_BUFFER_SIZE]; vsnprintf(buf, sizeof(buf), msg, vlist); doLog(level, buf); } +#ifdef ARDUINO + //inline void doLog(LogLevel level, const __FlashStringHelper* msg) { char buf[RNS_LOG_BUFFER_SIZE]; strncpy_P(buf, (const char*)msg, sizeof(buf)); doLog(level, buf); } + //inline void doLog(LogLevel level, const __FlashStringHelper* msg, va_list vlist) { char buf[RNS_LOG_BUFFER_SIZE]; vsnprintf_P(buf, sizeof(buf), (const char*)msg, vlist); doLog(level, buf); } +#endif + + void doHeadLog(LogLevel level, const char* msg); + inline void doHeadLog(LogLevel level, const char* msg, va_list vlist) { char buf[RNS_LOG_BUFFER_SIZE]; vsnprintf(buf, sizeof(buf), msg, vlist); doHeadLog(level, buf); } +#ifdef ARDUINO + //inline void doHeadLog(LogLevel level, const __FlashStringHelper* msg) { char buf[RNS_LOG_BUFFER_SIZE]; strncpy_P(buf, (const char*)msg, sizeof(buf)); doHeadLog(level, buf); } + //inline void doHeadLog(LogLevel level, const __FlashStringHelper* msg, va_list vlist) { char buf[RNS_LOG_BUFFER_SIZE]; vsnprintf_P(buf, sizeof(buf), (const char*)msg, vlist); doHeadLog(level, buf); } +#endif inline void log(const char* msg, LogLevel level = LOG_NOTICE) { doLog(level, msg); } + 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); } #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()); } + //inline void log(const __FlashStringHelper* msg, LogLevel level = LOG_NOTICE) { doLog(level, msg); } + //inline void logf(LogLevel level, const __FlashStringHelper* msg, ...) { va_list vlist; va_start(vlist, msg); doLog(level, msg, vlist); va_end(vlist); } #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 head(const char* msg, LogLevel level = LOG_NOTICE) { doHeadLog(level, msg); } + inline void head(const std::string& msg, LogLevel level = LOG_NOTICE) { doHeadLog(level, msg.c_str()); } + inline void headf(LogLevel level, const char* msg, ...) { va_list vlist; va_start(vlist, msg); doHeadLog(level, msg, vlist); va_end(vlist); } +#ifdef ARDUINO + inline void head(const String& msg, LogLevel level = LOG_NOTICE) { doHeadLog(level, msg.c_str()); } + inline void head(const StringSumHelper& msg, LogLevel level = LOG_NOTICE) { doHeadLog(level, msg.c_str()); } + //inline void head(const __FlashStringHelper* msg, LogLevel level = LOG_NOTICE) { doHeadLog(level, msg); } + //inline void headf(LogLevel level, const __FlashStringHelper* msg, ...) { va_list vlist; va_start(vlist, msg); doHeadLog(level, msg, vlist); va_end(vlist); } +#endif inline void critical(const char* msg) { doLog(LOG_CRITICAL, msg); } inline void critical(const std::string& msg) { doLog(LOG_CRITICAL, msg.c_str()); } diff --git a/src/Packet.cpp b/src/Packet.cpp index f544a223..a3111404 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -420,7 +420,7 @@ bool Packet::unpack() { update_hash(); } catch (const std::bad_alloc&) { - ERROR("Packet::unpack: out of memory unpacking packet"); + ERROR("Packet::unpack: bad_alloc - out of memory unpacking packet"); return false; } catch (std::exception& e) { diff --git a/src/Transport.cpp b/src/Transport.cpp index 0f726a1e..372ff833 100644 --- a/src/Transport.cpp +++ b/src/Transport.cpp @@ -22,6 +22,10 @@ using namespace RNS::Utilities; #define RNS_PATH_TABLE_MAX 100 #endif +#ifndef RNS_ANNOUNCE_TABLE_MAX +#define RNS_ANNOUNCE_TABLE_MAX 100 +#endif + #ifndef RNS_HASHLIST_MAX #define RNS_HASHLIST_MAX 100 #endif @@ -105,6 +109,7 @@ using namespace RNS::Utilities; /*static*/ double Transport::_last_saved = 0.0; /*static*/ float Transport::_save_interval = 3600.0; /*static*/ uint32_t Transport::_destination_table_crc = 0; +/*static*/ uint16_t Transport::_announce_table_maxsize = RNS_ANNOUNCE_TABLE_MAX; /*static*/ Reticulum Transport::_owner({Type::NONE}); /*static*/ Identity Transport::_identity({Type::NONE}); @@ -423,6 +428,8 @@ using namespace RNS::Utilities; // CBA ACCUMULATES _announce_table.insert({destination_hash, held_entry}); DEBUG("Reinserting held announce into table"); + // CBA IMMEDIATE CULL + cull_announce_table(); } } } @@ -463,7 +470,7 @@ using namespace RNS::Utilities; remove_reverse_entries(stale_reverse_entries); } catch (const std::bad_alloc&) { - ERROR("jobs: out of memory culling reverse table"); + ERROR("jobs: bad_alloc - out of memory culling reverse table"); } catch (const std::exception& e) { ERROR("jobs: failed to cull reverse table: " + std::string(e.what())); @@ -546,7 +553,7 @@ using namespace RNS::Utilities; remove_links(stale_links); } catch (const std::bad_alloc&) { - ERROR("jobs: out of memory culling link table"); + ERROR("jobs: bad_alloc - out of memory culling link table"); } catch (const std::exception& e) { ERROR("jobs: failed to cull link table: " + std::string(e.what())); @@ -581,7 +588,7 @@ using namespace RNS::Utilities; remove_paths(stale_paths); } catch (const std::bad_alloc&) { - ERROR("jobs: out of memory culling path table"); + ERROR("jobs: bad_alloc - out of memory culling path table"); } catch (const std::exception& e) { ERROR("jobs: failed to cull path table: " + std::string(e.what())); @@ -600,7 +607,7 @@ using namespace RNS::Utilities; remove_discovery_path_requests(stale_discovery_path_requests); } catch (const std::bad_alloc&) { - ERROR("jobs: out of memory culling discovery path requests"); + ERROR("jobs: bad_alloc - out of memory culling discovery path requests"); } catch (const std::exception& e) { ERROR("jobs: failed to cull discovery path requests: " + std::string(e.what())); @@ -638,7 +645,7 @@ using namespace RNS::Utilities; remove_tunnels(stale_tunnels); } catch (const std::bad_alloc&) { - ERROR("jobs: out of memory culling tunnel table"); + ERROR("jobs: bad_alloc - out of memory culling tunnel table"); } catch (const std::exception& e) { ERROR("jobs: failed to cull tunnel table: " + std::string(e.what())); @@ -1686,21 +1693,25 @@ using namespace RNS::Utilities; if (Reticulum::transport_enabled() && _announce_table.count(packet.destination_hash()) > 0) { //AnnounceEntry& announce_entry = _announce_table[packet.destination_hash()]; AnnounceEntry& announce_entry = (*_announce_table.find(packet.destination_hash())).second; - + + bool announce_erased = false; if ((packet.hops() - 1) == announce_entry._hops) { DEBUG("Heard a local rebroadcast of announce for " + packet.destination_hash().toHex()); announce_entry._local_rebroadcasts += 1; if (announce_entry._local_rebroadcasts >= LOCAL_REBROADCASTS_MAX) { DEBUG("Max local rebroadcasts of announce for " + packet.destination_hash().toHex() + " reached, dropping announce from our table"); _announce_table.erase(packet.destination_hash()); + announce_erased = true; } } - if ((packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) { + // CBA Checking announce_erased so we don't access announce_entry if it's already been freed! + if (!announce_erased && (packet.hops() - 1) == (announce_entry._hops + 1) && announce_entry._retries > 0) { double now = OS::time(); if (now < announce_entry._timestamp) { DEBUG("Rebroadcasted announce for " + packet.destination_hash().toHex() + " has been passed on to another node, no further tries needed"); _announce_table.erase(packet.destination_hash()); + announce_erased = true; } } } @@ -1898,6 +1909,8 @@ using namespace RNS::Utilities; ); // CBA ACCUMULATES _announce_table.insert({packet.destination_hash(), announce_entry}); + // CBA IMMEDIATE CULL + cull_announce_table(); } } // TODO: Check from_local_client once and store result @@ -1926,6 +1939,8 @@ using namespace RNS::Utilities; ); // CBA ACCUMULATES _announce_table.insert({packet.destination_hash(), announce_entry}); + // CBA IMMEDIATE CULL + cull_announce_table(); } } @@ -2042,20 +2057,21 @@ using namespace RNS::Utilities; packet.get_hash() ); // CBA ACCUMULATES - // Pre-emptively cull before insert to avoid hitting OOM at max capacity - if (_destination_table.size() >= _path_table_maxsize) { - cull_path_table(); - } try { + // CBA First remove any existing path before inserting new one + remove_path(packet.destination_hash()); if (_destination_table.insert({packet.destination_hash(), destination_table_entry}).second) { + TRACE("Added destination " + packet.destination_hash().toHex() + " to path table!"); ++_destinations_added; - if (_destination_table.size() > _path_table_maxsize) { - cull_path_table(); - } + // CBA IMMEDIATE CULL + cull_path_table(); + } + else { + ERROR("Failed to add destination " + packet.destination_hash().toHex() + " to path table!"); } } catch (const std::bad_alloc&) { - ERROR("inbound: out of memory storing destination entry"); + ERROR("inbound: bad_alloc - out of memory storing destination entry"); } catch (const std::exception& e) { ERROR(std::string("inbound: exception storing destination entry: ") + e.what()); @@ -2488,29 +2504,29 @@ using namespace RNS::Utilities; #if defined(INTERFACES_SET) //for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) { // if ((*iter).get() == interface) { + // TRACE("Transport::deregister_interface: Found and removing interface " + (*iter).get().toString()); // _interfaces.erase(iter); - // TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); // break; // } //} //auto iter = _interfaces.find(interface); auto iter = _interfaces.find(const_cast(interface)); if (iter != _interfaces.end()) { + TRACE("Transport::deregister_interface: Found and removing interface " + (*iter).get().toString()); _interfaces.erase(iter); - TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); } #elif defined(INTERFACES_LIST) for (auto iter = _interfaces.begin(); iter != _interfaces.end(); ++iter) { if ((*iter).get() == interface) { + TRACE("Transport::deregister_interface: Found and removing interface " + (*iter).get().toString()); _interfaces.erase(iter); - TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).get().toString()); break; } } #elif defined(INTERFACES_MAP) auto iter = _interfaces.find(interface.get_hash()); if (iter != _interfaces.end()) { - TRACE("Transport::deregister_interface: Found and removed interface " + (*iter).second.toString()); + TRACE("Transport::deregister_interface: Found and removing interface " + (*iter).second.toString()); _interfaces.erase(iter); } #endif @@ -2569,14 +2585,14 @@ using namespace RNS::Utilities; TRACE("Transport: Deregistering destination " + destination.toString()); #if defined(DESTINATIONS_SET) if (_destinations.find(destination) != _destinations.end()) { + TRACE("Transport::deregister_destination: Found and removing destination " + destination.toString()); _destinations.erase(destination); - TRACE("Transport::deregister_destination: Found and removed destination " + destination.toString()); } #elif defined(DESTINATIONS_MAP) auto iter = _destinations.find(destination.hash()); if (iter != _destinations.end()) { + TRACE("Transport::deregister_destination: Found and removing destination " + (*iter).second.toString()); _destinations.erase(iter); - TRACE("Transport::deregister_destination: Found and removed destination " + (*iter).second.toString()); } #endif } @@ -2627,8 +2643,8 @@ Deregisters an announce handler. /*static*/ void Transport::deregister_announce_handler(HAnnounceHandler handler) { TRACE("Transport: Deregistering announce handler " + handler->aspect_filter()); if (_announce_handlers.find(handler) != _announce_handlers.end()) { + TRACE("Transport::deregister_announce_handler: Found and removing handler" + handler->aspect_filter()); _announce_handlers.erase(handler); - TRACE("Transport::deregister_announce_handler: Found and removed handler" + handler->aspect_filter()); } } @@ -2636,21 +2652,21 @@ Deregisters an announce handler. #if defined(INTERFACES_SET) for (const Interface& interface : _interfaces) { if (interface.get_hash() == interface_hash) { - TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); + //TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); return interface; } } #elif defined(INTERFACES_LIST) for (Interface& interface : _interfaces) { if (interface.get_hash() == interface_hash) { - TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); + //TRACE("Transport::find_interface_from_hash: Found interface " + interface.toString()); return interface; } } #elif defined(INTERFACES_MAP) auto iter = _interfaces.find(interface_hash); if (iter != _interfaces.end()) { - TRACE("Transport::find_interface_from_hash: Found interface " + (*iter).second.toString()); + //TRACE("Transport::find_interface_from_hash: Found interface " + (*iter).second.toString()); return (*iter).second; } #endif @@ -2792,8 +2808,18 @@ Deregisters an announce handler. } /*static*/ bool Transport::remove_path(const Bytes& destination_hash) { - if (_destination_table.erase(destination_hash) > 0) { - // CBA also remove cached announce packet if exists + auto iter = _destination_table.find(destination_hash); + if (iter != _destination_table.end()) { + // Remove cached packet file associated with this destination + char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; + snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, iter->second._announce_packet.toHex().c_str()); + if (OS::file_exists(packet_cache_path)) { + OS::remove_file(packet_cache_path); + } + if (_destination_table.erase(destination_hash) < 1) { + WARNING("Failed to remove destination " + destination_hash.toHex() + " from path table"); + } + return true; } return false; } @@ -3237,6 +3263,8 @@ will announce it. ); // CBA ACCUMULATES _announce_table.insert({announce_packet.destination_hash(), announce_entry}); + // CBA IMMEDIATE CULL + cull_announce_table(); } } } @@ -4018,7 +4046,7 @@ TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence:: #if defined(RNS_USE_FS) && defined(RNS_PERSIST_PATHS) auto it = _destination_table.find(destination_hash); if (it != _destination_table.end()) { - // Remove cached packet file + // Remove cached packet file associated with this destination char packet_cache_path[Type::Reticulum::FILEPATH_MAXSIZE]; snprintf(packet_cache_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/%s", Reticulum::_cachepath, it->second._announce_packet.toHex().c_str()); if (OS::file_exists(packet_cache_path)) { @@ -4037,7 +4065,7 @@ TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence:: DEBUG("Removed " + std::to_string(count) + " path(s) from path table"); } catch (const std::bad_alloc& e) { - ERROR("cull_path_table: out of memory building sort index, falling back to single erase"); + ERROR("cull_path_table: bad_alloc - out of memory building sort index, falling back to single erase"); // Fallback: std::min_element does no heap allocation — erase one oldest entry auto oldest = std::min_element( _destination_table.begin(), _destination_table.end(), @@ -4055,7 +4083,55 @@ TRACE("Transport::write_path_table: buffer size " + std::to_string(Persistence:: } } } - + +/*static*/ void Transport::cull_announce_table() { + TRACE("Transport::cull_announce_table()"); + if (_announce_table.size() > _announce_table_maxsize) { + try { + // Build lightweight (timestamp, key) index to avoid copying full AnnounceEntry + // objects (which contain nested std::set) — prevents OOM on heap-constrained + // devices when the table hits max capacity. + std::vector> sorted_keys; + sorted_keys.reserve(_announce_table.size()); + for (const auto& [key, entry] : _announce_table) { + sorted_keys.emplace_back(entry._timestamp, key); + } + // Sort ascending by timestamp so oldest entries are removed first + std::sort(sorted_keys.begin(), sorted_keys.end()); + + uint16_t count = 0; + for (const auto& [timestamp, destination_hash] : sorted_keys) { + TRACE("Transport::cull_announce_table: Removing destination " + destination_hash.toHex() + " from path table"); + if (_announce_table.erase(destination_hash) < 1) { + WARNING("Failed to remove destination " + destination_hash.toHex() + " from path table"); + } + ++count; + if (_announce_table.size() <= _path_table_maxsize) { + break; + } + } + DEBUG("Removed " + std::to_string(count) + " path(s) from path table"); + } + catch (const std::bad_alloc& e) { + ERROR("cull_announce_table: bad_alloc - out of memory building sort index, falling back to single erase"); + // Fallback: std::min_element does no heap allocation — erase one oldest entry + auto oldest = std::min_element( + _announce_table.begin(), _announce_table.end(), + [](const std::pair& a, + const std::pair& b) { + return a.second._timestamp < b.second._timestamp; + } + ); + if (oldest != _announce_table.end()) { + _announce_table.erase(oldest); + } + } + catch (const std::exception& e) { + ERROR(std::string("cull_announce_table: exception: ") + e.what()); + } + } +} + /*static*/ uint16_t Transport::remove_reverse_entries(const std::vector& hashes) { uint16_t count = 0; for (const auto& truncated_packet_hash : hashes) { diff --git a/src/Transport.h b/src/Transport.h index 118e2502..352c17dd 100644 --- a/src/Transport.h +++ b/src/Transport.h @@ -371,6 +371,7 @@ namespace RNS { // CBA static void cull_path_table(); + static void cull_announce_table(); // getters/setters static inline void set_receive_packet_callback(Callbacks::receive_packet callback) { _callbacks._receive_packet = callback; } @@ -380,6 +381,8 @@ namespace RNS { 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 announce_table_maxsize() { return _announce_table_maxsize; } + inline static void announce_table_maxsize(uint16_t announce_table_maxsize) { _announce_table_maxsize = announce_table_maxsize; } inline static uint16_t hashlist_maxsize() { return _hashlist_maxsize; } inline static void hashlist_maxsize(uint16_t hashlist_maxsize) { _hashlist_maxsize = hashlist_maxsize; } inline static uint16_t max_pr_tags() { return _max_pr_tags; } @@ -477,6 +480,7 @@ namespace RNS { static double _last_saved; static float _save_interval; static uint32_t _destination_table_crc; + static uint16_t _announce_table_maxsize; static Reticulum _owner; static Identity _identity; diff --git a/src/Utilities/OS.cpp b/src/Utilities/OS.cpp index 3b2ece60..ebbcad2d 100644 --- a/src/Utilities/OS.cpp +++ b/src/Utilities/OS.cpp @@ -3,6 +3,8 @@ #include "../Type.h" #include "../Log.h" +#include + using namespace RNS; using namespace RNS::Utilities; @@ -11,26 +13,46 @@ using namespace RNS::Utilities; #if defined(RNS_USE_TLSF) #if defined(ESP32) - //#define BUFFER_SIZE 1024 * 80 - #define BUFFER_SIZE 0 - #define BUFFER_FRACTION 0.8 + #ifndef RNS_TLSF_BUFFER_SIZE + //#define RNS_TLSF_BUFFER_SIZE 1024 * 80 + #define RNS_TLSF_BUFFER_SIZE 0 + #endif + #ifndef BUFFER_FRACTION + #define BUFFER_FRACTION 0.8 + #endif #elif defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_NRF52_ADAFRUIT) - //#define BUFFER_SIZE 1024 * 80 - #define BUFFER_SIZE 0 - #define BUFFER_FRACTION 0.8 + #ifndef RNS_TLSF_BUFFER_SIZE + //#define RNS_TLSF_BUFFER_SIZE 1024 * 80 + #define RNS_TLSF_BUFFER_SIZE 0 + #endif + #ifndef BUFFER_FRACTION + #define BUFFER_FRACTION 0.8 + #endif #else - #define BUFFER_SIZE 1024 * 1000 - #define BUFFER_FRACTION 0 + #ifndef RNS_TLSF_BUFFER_SIZE + #define RNS_TLSF_BUFFER_SIZE 1024 * 1000 + #endif + #ifndef BUFFER_FRACTION + #define BUFFER_FRACTION 0 + #endif #endif -bool _tlsf_init = false; +struct tlsf_stats { + uint32_t used_count = 0; + uint32_t used_size = 0; + uint32_t free_count = 0; + uint32_t free_size = 0; + uint32_t free_max_size = 0; +}; + +bool _pool_init = false; //char _tlsf_msg[256] = ""; -size_t _buffer_size = BUFFER_SIZE; +size_t _buffer_size = RNS_TLSF_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 +#endif // RNS_USE_TLSF uint32_t _new_count = 0; uint32_t _new_fault = 0; @@ -40,72 +62,84 @@ 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) { +void tlsf_mem_walker(void* ptr, size_t size, int used, void* user); +void dump_tlsf_stats(); + #if defined(RNS_USE_TLSF) - //if (OS::_tlsf == nullptr) { - if (!_tlsf_init) { - _tlsf_init = true; +inline void pool_init() { + #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); + // 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("TLSF: 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); + _contiguous_size = dbgHeapFree(); + TRACEF("TLSF: 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("TLSF: 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); + _buffer_size = (size_t)RNS_TLSF_BUFFER_SIZE; + TRACEF("TLSF: 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!!!"); + if (raw_buffer == nullptr) { + ERROR("-- TLSF: buffer allocation FAILED"); + //strcpy(_tlsf_msg, "-- TLSF: buffer allocation FAILED!!!"); + } + else { +#if 1 + OS::_tlsf = tlsf_create_with_pool(raw_buffer, _buffer_size); + if (OS::_tlsf == nullptr) { + //sprintf(_tlsf_msg, "TLSF: initialization with align=%d, contiguous=%d, size=%d FAILED!!!", tlsf_align_size(), _contiguous_size, _buffer_size); + printf("TLSF: initialization with align=%d, contiguous=%d, size=%d FAILED!!!\n", tlsf_align_size(), _contiguous_size, _buffer_size); } 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); - //} + //sprintf(_tlsf_msg, "TLSF: initialization with align=%d, contiguous=%d, size=%d SUCCESSFUL!!!", tlsf_align_size(), _contiguous_size, _buffer_size); + printf("TLSF: initialization with align=%d, contiguous=%d, size=%d SUCCESSFUL!!!\n", 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)); + Serial.print("TLSF: raw_buffer: "); + Serial.println((long)raw_buffer); + Serial.print("TLSF: 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("TLSF: 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"); - } + if (OS::_tlsf == nullptr) { + ERROR("-- TLSF: initialization of FAILED"); } } -#endif + +} +#endif // RNS_USE_TLSF + +void* pool_malloc(std::size_t size) { +#if defined(RNS_USE_TLSF) + //if (OS::_tlsf == nullptr) { + if (!_pool_init) { + _pool_init = true; + printf("Initializing TLSF pool...\n"); + pool_init(); + } +#endif // RNS_USE_TLSF ++_new_count; _new_size += size; if (size < _min_size || _min_size == 0) { @@ -118,30 +152,37 @@ void* operator new(size_t size) { void* p; #if defined(RNS_USE_TLSF) if (OS::_tlsf != nullptr) { + //struct tlsf_stats stats; + //memset(&stats, 0, sizeof(stats)); + //tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, &stats); + //if (tlsf_check(OS::_tlsf) != 0) { + // printf("--- HEAP CORRUPTION DETECTED!!!\n"); + //} //TRACEF("--- allocating memory from tlsf (%u bytes)", size); + //printf("--- allocating memory from tlsf (%u bytes) (%u free)\n", size, stats.free_size); p = tlsf_malloc(OS::_tlsf, size); - //TRACEF("--- allocated memory from tlsf (%u bytes) (addr=%lx)", size, p); + //TRACEF("--- allocated memory from tlsf (addr=%lx) (%u bytes)", p, size); + //printf("--- allocated memory from tlsf (addr=%lx) (%u bytes)\n", p, size); } else { //TRACEF("--- allocating memory (%u bytes)", size); p = malloc(size); - //TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p); + //TRACEF("--- allocated memory (addr=%lx) (%u bytes)", p, size); ++_new_fault; } #else //TRACEF("--- allocating memory (%u bytes)", size); p = malloc(size); - //TRACEF("--- allocated memory (%u bytes) (addr=%lx)", size, p); -#endif + //TRACEF("--- allocated memory (addr=%lx) (%u bytes)", p, size); +#endif // RNS_USE_TLSF 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) { + +void pool_free(void* p) noexcept { #if defined(RNS_USE_TLSF) if (OS::_tlsf != nullptr) { //TRACEF("--- freeing memory from tlsf (addr=%lx)", p); + //printf("--- freeing memory from tlsf (addr=%lx)\n", p); tlsf_free(OS::_tlsf, p); } else { @@ -153,7 +194,7 @@ void operator delete(void* p) { //TRACEF("--- freeing memory (addr=%lx)", p); //TRACE("--- freeing memory"); free(p); -#endif +#endif // RNS_USE_TLSF ++_delete_count; #if defined(RNS_USE_TLSF) //if (_delete_count == _new_count) { @@ -165,47 +206,91 @@ void operator delete(void* p) { #endif } + +// CBA Added attribute weak to avoid collision with new override on nrf52 +void* operator new(std::size_t size) { +//__attribute__((weak)) void* operator new(std::size_t size) { + //printf("--- new allocating memory from tlsf (%u bytes)\n", size); + void* p = pool_malloc(size); + if (p == nullptr) { + throw std::bad_alloc(); + } + return p; +} + +// CBA Added attribute weak to avoid collision with new override on nrf52 +void operator delete(void* p) noexcept { +//__attribute__((weak)) void operator delete(void* p) noexcept { + //printf("--- delete freeing memory from tlsf (addr=%lx)\n", p); + pool_free(p); +} + + +/**/ +void* operator new[](std::size_t size) { + //printf("--- new[] allocating memory from tlsf (%u bytes)\n", size); + void* p = pool_malloc(size); + if (p == nullptr) { + throw std::bad_alloc(); + } + return p; +} + +void operator delete[](void* p) noexcept { + //printf("--- delete[] freeing memory from tlsf (addr=%lx)\n", p); + pool_free(p); +} + + +void operator delete(void* p, std::size_t size) noexcept { // sized delete + //printf("--- delete(size) freeing memory from tlsf (addr=%lx)\n", p); + pool_free(p); +} + +void operator delete[](void* p, std::size_t size) noexcept { + //printf("--- delete[](size) freeing memory from tlsf (addr=%lx)\n", p); + pool_free(p); +} +/**/ + + #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 (!user) { + return; + } + struct tlsf_stats* stats = (struct tlsf_stats*)user; if (used) { - _tlsf_used_count++; - _tlsf_used_size += size; + stats->used_count++; + stats->used_size += size; } else { - _tlsf_free_count++; - _tlsf_free_size += size; - if (size > _tlsf_free_max_size) { - _tlsf_free_max_size = size; + stats->free_count++; + stats->free_size += size; + if (size > stats->free_max_size) { + stats->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; + struct tlsf_stats stats; + memset(&stats, 0, sizeof(stats)); //TRACEF("TLSF Message: %s", _tlsf_msg); if (OS::_tlsf == nullptr) { return; } - tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, nullptr); + tlsf_walk_pool(tlsf_get_pool(OS::_tlsf), tlsf_mem_walker, &stats); 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)); + TRACEF("Used Count: %u", stats.used_count); + TRACEF("Used Size: %u (%u%% used)", stats.used_size, (unsigned)((double)stats.used_size / (double)_buffer_size * 100.0)); + TRACEF("Free Count: %u", stats.free_count); + TRACEF("Free Size: %u (%u%% free)", stats.free_size, (unsigned)((double)stats.free_size / (double)_buffer_size * 100.0)); + TRACEF("Max Free Size: %u (%u%% fragmented)\n", stats.free_max_size, (unsigned)(100.0 - (double)stats.free_max_size / (double)stats.free_size * 100.0)); } -#endif +#endif // RNS_USE_TLSF /*static*/ void OS::dump_allocator_stats() { HEAD("Allocator Stats", LOG_TRACE); diff --git a/src/Utilities/Persistence.h b/src/Utilities/Persistence.h index cdf0c895..3c006e07 100644 --- a/src/Utilities/Persistence.h +++ b/src/Utilities/Persistence.h @@ -468,7 +468,7 @@ namespace RNS { namespace Persistence { static JsonDocument _document; static Bytes _buffer(Type::Persistence::BUFFER_MAXSIZE); - template size_t crc(const T& obj) { + template uint32_t crc(const T& obj) { //TRACE("Persistence::crc"); _document.set(obj); size_t size = _buffer.capacity(); @@ -480,7 +480,7 @@ namespace RNS { namespace Persistence { if (length < size) { _buffer.resize(length); } - TRACEF("Persistence::crc: serialized %d bytes", length); + //TRACEF("Persistence::crc: serialized %d bytes", length); return Utilities::Crc::crc32(0, _buffer.data(), _buffer.size()); } @@ -546,7 +546,7 @@ namespace RNS { namespace Persistence { } #if 1 - template size_t crc(std::map& map) { + template uint32_t crc(std::map& map) { //TRACE("Persistence::crc>"); uint32_t crc = 0; @@ -568,7 +568,7 @@ namespace RNS { namespace Persistence { if (length < size) { _buffer.resize(length); } - TRACEF("Persistence::crc: serialized entry %d bytes", length); + //TRACEF("Persistence::crc: serialized entry %d bytes", length); if (length > 0) { crc = Utilities::Crc::crc32(crc, _buffer.data(), _buffer.size()); diff --git a/test/common/filesystem/FileSystem.h b/test/common/filesystem/FileSystem.h index 4f6fd153..6df3cdaf 100644 --- a/test/common/filesystem/FileSystem.h +++ b/test/common/filesystem/FileSystem.h @@ -22,6 +22,7 @@ using namespace Adafruit_LittleFS_Namespace; #include #include #include +#include #endif class FileSystem : public RNS::FileSystemImpl { @@ -399,7 +400,8 @@ class FileSystem : public RNS::FileSystemImpl { } #else // Native - return false; + struct stat st = {0}; + return (stat(directory_path, &st) == 0); #endif } @@ -451,7 +453,11 @@ class FileSystem : public RNS::FileSystemImpl { #endif #else // Native - return false; + if (rmdir(directory_path) == 0) { + ERROR("remove_directory: failed to remove directory " + std::string(directory_path)); + return false; + } + return true; #endif } @@ -483,6 +489,23 @@ class FileSystem : public RNS::FileSystemImpl { return files; #else // Native + DIR *dir = opendir(directory_path); + if (dir == NULL) { + ERROR("list_directory: failed to open directory " + std::string(directory_path)); + return files; + } + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + char* name = entry->d_name; + files.push_back(name); + } + if (closedir(dir) == -1) { + ERROR("list_directory: failed to close directory " + std::string(directory_path)); + } + return files; #endif } diff --git a/test/test_bytes/test_main.cpp b/test/test_bytes/test_main.cpp index 2fe82605..6bb25805 100644 --- a/test/test_bytes/test_main.cpp +++ b/test/test_bytes/test_main.cpp @@ -569,6 +569,83 @@ void testIndex() { TEST_ASSERT_EQUAL_UINT8('o', bytes[4]); } +// ============================================================================ +// Bytes edge case tests +// ============================================================================ + +void test_assignHex_even_length() { + // Normal case: even-length hex string + RNS::Bytes bytes; + bytes.assignHex("48656C6C6F"); // "Hello" + TEST_ASSERT_EQUAL_size_t(5, bytes.size()); + TEST_ASSERT_EQUAL_MEMORY("Hello", bytes.data(), 5); +} + +void test_assignHex_odd_length() { + // Odd-length hex should be truncated to even (drop trailing nibble) + RNS::Bytes bytes; + bytes.assignHex("ABC"); // 3 chars - only "AB" should be decoded + TEST_ASSERT_EQUAL_size_t(1, bytes.size()); + TEST_ASSERT_EQUAL_UINT8(0xAB, bytes.data()[0]); +} + +void test_assignHex_single_char() { + // Single char hex can't form a complete byte - should produce empty + RNS::Bytes bytes; + bytes.assignHex("A"); + TEST_ASSERT_EQUAL_size_t(0, bytes.size()); +} + +void test_assignHex_empty() { + RNS::Bytes bytes; + bytes.assignHex(""); + TEST_ASSERT_EQUAL_size_t(0, bytes.size()); +} + +void test_appendHex_odd_length() { + // appendHex with odd length should also truncate to even + RNS::Bytes bytes; + bytes.assignHex("4142"); // "AB" + TEST_ASSERT_EQUAL_size_t(2, bytes.size()); + + bytes.appendHex("434"); // 3 chars - only "43" ('C') should be appended + TEST_ASSERT_EQUAL_size_t(3, bytes.size()); + TEST_ASSERT_EQUAL_UINT8(0x43, bytes.data()[2]); +} + +void test_hex_roundtrip_stability() { + // toHex always produces even length, so roundtrip should be safe + RNS::Bytes original("Hello World"); + std::string hex = original.toHex(); + TEST_ASSERT_TRUE(hex.length() % 2 == 0); // Must be even + + RNS::Bytes restored; + restored.assignHex(hex.c_str()); + TEST_ASSERT_EQUAL_size_t(original.size(), restored.size()); + TEST_ASSERT_EQUAL_MEMORY(original.data(), restored.data(), original.size()); +} + +void test_mid_large_len() { + // mid() with len that extends past end should clamp + RNS::Bytes bytes("Hello World"); + + RNS::Bytes mid = bytes.mid(3, 100); + TEST_ASSERT_EQUAL_size_t(8, mid.size()); + TEST_ASSERT_EQUAL_MEMORY("lo World", mid.data(), 8); + + // SIZE_MAX len should also clamp, not integer-overflow and crash + RNS::Bytes mid2 = bytes.mid(3, SIZE_MAX); + TEST_ASSERT_EQUAL_size_t(8, mid2.size()); + TEST_ASSERT_EQUAL_MEMORY("lo World", mid2.data(), 8); +} + +void test_mid_zero_size_bytes() { + RNS::Bytes empty; + RNS::Bytes mid = empty.mid(0, 5); + TEST_ASSERT_FALSE(mid); + TEST_ASSERT_EQUAL_size_t(0, mid.size()); +} + void setUp(void) { // set stuff up here before each test @@ -598,6 +675,16 @@ int runUnityTests(void) { RUN_TEST(testConcat); RUN_TEST(testIndex); + // Bytes edge cases + RUN_TEST(test_assignHex_even_length); + RUN_TEST(test_assignHex_odd_length); + RUN_TEST(test_assignHex_single_char); + RUN_TEST(test_assignHex_empty); + RUN_TEST(test_appendHex_odd_length); + RUN_TEST(test_hex_roundtrip_stability); + RUN_TEST(test_mid_large_len); + RUN_TEST(test_mid_zero_size_bytes); + // Suite-level teardown size_t post_memory = RNS::Utilities::OS::heap_available(); size_t diff_memory = (int)pre_memory - (int)post_memory; diff --git a/test/test_transport/test_main.cpp b/test/test_transport/test_main.cpp index 625a42c8..357fc5a5 100644 --- a/test/test_transport/test_main.cpp +++ b/test/test_transport/test_main.cpp @@ -25,6 +25,17 @@ class InInterface : public RNS::InterfaceImpl { } virtual ~InInterface() { _name = "(deleted)"; } virtual void send_outgoing(const RNS::Bytes &data) {} + virtual void handle_incoming(const RNS::Bytes &data) { + try { + InterfaceImpl::handle_incoming(data); + } + catch (const std::bad_alloc&) { + ERROR("handle_incoming: bad_alloc - out of memory"); + } + catch (const std::exception& e) { + ERROR(std::string("handle_incoming: exception: ") + e.what()); + } + } }; class OutInterface : public RNS::InterfaceImpl { @@ -37,7 +48,15 @@ class OutInterface : public RNS::InterfaceImpl { virtual ~OutInterface() { _name = "(deleted)"; } virtual void send_outgoing(const RNS::Bytes &data) { _in_interface.handle_incoming(data); - InterfaceImpl::handle_outgoing(data); + try { + InterfaceImpl::handle_outgoing(data); + } + catch (const std::bad_alloc&) { + ERROR("handle_outgoing: bad_alloc - out of memory"); + } + catch (const std::exception& e) { + ERROR(std::string("handle_outgoing: exception: ") + e.what()); + } } private: RNS::Interface& _in_interface; @@ -63,113 +82,59 @@ bool rns_initialized = false; void initRNS() { if (rns_initialized) return; - RNS::FileSystem reticulum_filesystem = new FileSystem(); - ((FileSystem*)reticulum_filesystem.get())->init(); - RNS::Utilities::OS::register_filesystem(reticulum_filesystem); - - RNS::Transport::register_interface(in_interface); - RNS::Transport::register_interface(out_interface); - - // Transport identity - RNS::Bytes transport_prv; - transport_prv.assignHex("BABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABE"); - RNS::Identity transport_identity(false); - transport_identity.load_private_key(transport_prv); - RNS::Transport::identity(transport_identity); - - test_reticulum = RNS::Reticulum(); - test_reticulum.transport_enabled(true); - test_reticulum.start(); - - // Single identity and destination (same pattern as test_rns_loopback) - test_identity = RNS::Identity(false); - RNS::Bytes prv; - prv.assignHex("E0D43398EDC974EBA9F4A83463691A08F4D306D4E56BA6B275B8690A2FBD9852E9EBE7C03BC45CAEC9EF8E78C830037210BFB9986F6CA2DEE2B5C28D7B4DE6B0"); - test_identity.load_private_key(prv); - - test_dest = RNS::Destination(test_identity, RNS::Type::Destination::IN, - RNS::Type::Destination::SINGLE, "test", "transport"); - test_dest.set_packet_callback(onTestPacket); - test_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); - - rns_initialized = true; -} - -// ============================================================================ -// Bytes edge case tests -// ============================================================================ - -void test_assignHex_even_length() { - // Normal case: even-length hex string - RNS::Bytes bytes; - bytes.assignHex("48656C6C6F"); // "Hello" - TEST_ASSERT_EQUAL_size_t(5, bytes.size()); - TEST_ASSERT_EQUAL_MEMORY("Hello", bytes.data(), 5); -} - -void test_assignHex_odd_length() { - // Odd-length hex should be truncated to even (drop trailing nibble) - RNS::Bytes bytes; - bytes.assignHex("ABC"); // 3 chars - only "AB" should be decoded - TEST_ASSERT_EQUAL_size_t(1, bytes.size()); - TEST_ASSERT_EQUAL_UINT8(0xAB, bytes.data()[0]); -} - -void test_assignHex_single_char() { - // Single char hex can't form a complete byte - should produce empty - RNS::Bytes bytes; - bytes.assignHex("A"); - TEST_ASSERT_EQUAL_size_t(0, bytes.size()); -} - -void test_assignHex_empty() { - RNS::Bytes bytes; - bytes.assignHex(""); - TEST_ASSERT_EQUAL_size_t(0, bytes.size()); -} - -void test_appendHex_odd_length() { - // appendHex with odd length should also truncate to even - RNS::Bytes bytes; - bytes.assignHex("4142"); // "AB" - TEST_ASSERT_EQUAL_size_t(2, bytes.size()); - - bytes.appendHex("434"); // 3 chars - only "43" ('C') should be appended - TEST_ASSERT_EQUAL_size_t(3, bytes.size()); - TEST_ASSERT_EQUAL_UINT8(0x43, bytes.data()[2]); -} - -void test_hex_roundtrip_stability() { - // toHex always produces even length, so roundtrip should be safe - RNS::Bytes original("Hello World"); - std::string hex = original.toHex(); - TEST_ASSERT_TRUE(hex.length() % 2 == 0); // Must be even - - RNS::Bytes restored; - restored.assignHex(hex.c_str()); - TEST_ASSERT_EQUAL_size_t(original.size(), restored.size()); - TEST_ASSERT_EQUAL_MEMORY(original.data(), restored.data(), original.size()); -} - -void test_mid_large_len() { - // mid() with len that extends past end should clamp - RNS::Bytes bytes("Hello World"); - - RNS::Bytes mid = bytes.mid(3, 100); - TEST_ASSERT_EQUAL_size_t(8, mid.size()); - TEST_ASSERT_EQUAL_MEMORY("lo World", mid.data(), 8); - - // SIZE_MAX len should also clamp, not integer-overflow and crash - RNS::Bytes mid2 = bytes.mid(3, SIZE_MAX); - TEST_ASSERT_EQUAL_size_t(8, mid2.size()); - TEST_ASSERT_EQUAL_MEMORY("lo World", mid2.data(), 8); -} +#if defined(RNS_MEM_LOG) + RNS::loglevel(RNS::LOG_MEM); +#else + RNS::loglevel(RNS::LOG_TRACE); +#endif -void test_mid_zero_size_bytes() { - RNS::Bytes empty; - RNS::Bytes mid = empty.mid(0, 5); - TEST_ASSERT_FALSE(mid); - TEST_ASSERT_EQUAL_size_t(0, mid.size()); + try { + // Set sane memory limits based on hardware-specific availability + RNS::Transport::path_table_maxsize(50); + RNS::Transport::announce_table_maxsize(50); + RNS::Transport::hashlist_maxsize(50); + RNS::Transport::max_pr_tags(32); + RNS::Identity::known_destinations_maxsize(50); + + RNS::FileSystem reticulum_filesystem = new FileSystem(); + ((FileSystem*)reticulum_filesystem.get())->init(); + RNS::Utilities::OS::register_filesystem(reticulum_filesystem); + + RNS::Transport::register_interface(in_interface); + RNS::Transport::register_interface(out_interface); + + RNS::Transport::dump_stats(); + + // Transport identity + RNS::Bytes transport_prv; + transport_prv.assignHex("BABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABEBABE"); + RNS::Identity transport_identity(false); + transport_identity.load_private_key(transport_prv); + RNS::Transport::identity(transport_identity); + + test_reticulum = RNS::Reticulum(); + test_reticulum.transport_enabled(true); + test_reticulum.start(); + + // Single identity and destination (same pattern as test_rns_loopback) + test_identity = RNS::Identity(false); + RNS::Bytes prv; + prv.assignHex("E0D43398EDC974EBA9F4A83463691A08F4D306D4E56BA6B275B8690A2FBD9852E9EBE7C03BC45CAEC9EF8E78C830037210BFB9986F6CA2DEE2B5C28D7B4DE6B0"); + test_identity.load_private_key(prv); + + test_dest = RNS::Destination(test_identity, RNS::Type::Destination::IN, + RNS::Type::Destination::SINGLE, "test", "transport"); + test_dest.set_packet_callback(onTestPacket); + test_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); + + rns_initialized = true; + } + catch (const std::bad_alloc&) { + ERROR("initRNS: bad_alloc - out of memory"); + } + catch (const std::exception& e) { + ERROR(std::string("initRNS: exception: ") + e.what()); + } } // ============================================================================ @@ -405,8 +370,8 @@ void test_destination_packet_cycle() { temp_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); // Announce - std::string announce_data = "test_cycle_" + std::to_string(cycle); - temp_dest.announce(RNS::bytesFromString(announce_data.c_str())); + std::string app_data = "test_cycle_" + std::to_string(cycle); + temp_dest.announce(RNS::bytesFromString(app_data.c_str())); test_reticulum.loop(); // temp_dest goes out of scope here @@ -415,6 +380,176 @@ void test_destination_packet_cycle() { TEST_ASSERT_TRUE_MESSAGE(true, "Destination lifecycle did not crash"); } + +void test_incoming_announce_limit() { + + printf("test_incoming_announce_max: BEGIN\n"); + + initRNS(); + + const int num_announces = 50; + const int announces_period = 100; + + int announce_count = 0; + uint64_t start_time = RNS::Utilities::OS::ltime(); + uint64_t announce_time = 0; + uint64_t log_time = 0; + while (true) { + // Loop for 10 seconds after last announce + if ((RNS::Utilities::OS::ltime() - start_time) >= (num_announces * announces_period + 10000)) { + break; + } + if (announce_count < num_announces && (RNS::Utilities::OS::ltime() - announce_time) >= announces_period) { + printf("***** test_incoming_announce_limit: Sending announce #%d\n", announce_count + 1); + RNS::Identity temp_id(true); + RNS::Destination temp_dest(temp_id, RNS::Type::Destination::IN, + RNS::Type::Destination::SINGLE, "test", "announce"); + temp_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); + + // Announce + std::string app_data = "test_announce_" + std::to_string(announce_count + 1); + RNS::Packet announce_packet = temp_dest.announce(app_data, false, {RNS::Type::NONE}, {RNS::Type::NONE}, false); + announce_packet.pack(); + + // Must deregister destination so that the ANNOUNCE appears to be from a remote node + RNS::Transport::deregister_destination(temp_dest); + + in_interface.handle_incoming(announce_packet.raw()); + + announce_time = RNS::Utilities::OS::ltime(); + ++announce_count; + } + test_reticulum.loop(); + if ((RNS::Utilities::OS::ltime() - log_time) >= 5000) { + RNS::Transport::dump_stats(); + log_time = RNS::Utilities::OS::ltime(); + } + RNS::Utilities::OS::sleep(0.1); + } + RNS::Transport::persist_data(); + RNS::Transport::dump_stats(); + + printf("test_incoming_announce_limit: END\n"); +} + +void test_incoming_announce_over_limit() { + + printf("test_incoming_announce_over_limit: BEGIN\n"); + + initRNS(); + + const int num_announces = 100; + const int announces_period = 100; + + int announce_count = 0; + uint64_t start_time = RNS::Utilities::OS::ltime(); + uint64_t announce_time = 0; + uint64_t log_time = 0; + while (true) { + // Loop for 10 seconds after last announce + if ((RNS::Utilities::OS::ltime() - start_time) >= (num_announces * announces_period + 10000)) { + break; + } + if (announce_count < num_announces && (RNS::Utilities::OS::ltime() - announce_time) >= announces_period) { + printf("***** test_incoming_announce_over_limit: Sending announce #%d\n", announce_count + 1); + RNS::Identity temp_id(true); + RNS::Destination temp_dest(temp_id, RNS::Type::Destination::IN, + RNS::Type::Destination::SINGLE, "test", "announce"); + temp_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); + + // Announce + std::string app_data = "test_announce_" + std::to_string(announce_count + 1); + RNS::Packet announce_packet = temp_dest.announce(app_data, false, {RNS::Type::NONE}, {RNS::Type::NONE}, false); + announce_packet.pack(); + + // Must deregister destination so that the ANNOUNCE appears to be from a remote node + RNS::Transport::deregister_destination(temp_dest); + + in_interface.handle_incoming(announce_packet.raw()); + + announce_time = RNS::Utilities::OS::ltime(); + ++announce_count; + } + test_reticulum.loop(); + if ((RNS::Utilities::OS::ltime() - log_time) >= 5000) { + RNS::Transport::dump_stats(); + log_time = RNS::Utilities::OS::ltime(); + } + RNS::Utilities::OS::sleep(0.1); + } + RNS::Transport::persist_data(); + RNS::Transport::dump_stats(); + + printf("test_incoming_announce_over_limit: END\n"); +} + +void test_incoming_announce_stress() { + + printf("test_incoming_announce_stress: BEGIN\n"); + + initRNS(); + + //const int num_announces = 0; + //const int num_announces = 100; + //const int num_announces = 1000; + const int num_announces = 10000; + //const int announces_period = 1000; + const int announces_period = 100; + + int announce_count = 0; + uint64_t start_time = RNS::Utilities::OS::ltime(); + uint64_t announce_time = 0; + uint64_t log_time = 0; + while (true) { + // Loop for 60 seconds after last announce + if ((RNS::Utilities::OS::ltime() - start_time) >= (num_announces * announces_period + 60000)) { + break; + } + try { + if (announce_count < num_announces && (RNS::Utilities::OS::ltime() - announce_time) >= announces_period) { + printf("***** test_incoming_announce_stress: Sending announce #%d\n", announce_count + 1); + RNS::Identity temp_id(true); + RNS::Destination temp_dest(temp_id, RNS::Type::Destination::IN, + RNS::Type::Destination::SINGLE, "test", "announce"); + temp_dest.set_proof_strategy(RNS::Type::Destination::PROVE_ALL); + + // Announce + std::string app_data = "test_announce_" + std::to_string(announce_count + 1); + RNS::Packet announce_packet = temp_dest.announce(app_data, false, {RNS::Type::NONE}, {RNS::Type::NONE}, false); + announce_packet.pack(); + //printf("test_incoming_anounce: Sending packet with data: %s\n", announce_packet.raw().toHex().c_str()); + + // Must deregister destination so that the ANNOUNCE appears to be from a remote node + RNS::Transport::deregister_destination(temp_dest); + + in_interface.handle_incoming(announce_packet.raw()); + + announce_time = RNS::Utilities::OS::ltime(); + ++announce_count; + } + test_reticulum.loop(); + if ((RNS::Utilities::OS::ltime() - log_time) >= 5000) { + RNS::Transport::dump_stats(); + log_time = RNS::Utilities::OS::ltime(); + } + } + catch (const std::bad_alloc&) { + ERROR("test_incoming_announce_stress: bad_alloc - out of memory"); + break; + } + catch (const std::exception& e) { + ERROR(std::string("test_incoming_announce_stress: exception: ") + e.what()); + break; + } + RNS::Utilities::OS::sleep(0.1); + } + RNS::Transport::persist_data(); + RNS::Transport::dump_stats(); + + printf("test_incoming_announce_stress: END\n"); +} + + // ============================================================================ // Test runner // ============================================================================ @@ -425,16 +560,6 @@ void tearDown(void) {} int runUnityTests(void) { UNITY_BEGIN(); - // Bytes edge cases - RUN_TEST(test_assignHex_even_length); - RUN_TEST(test_assignHex_odd_length); - RUN_TEST(test_assignHex_single_char); - RUN_TEST(test_assignHex_empty); - RUN_TEST(test_appendHex_odd_length); - RUN_TEST(test_hex_roundtrip_stability); - RUN_TEST(test_mid_large_len); - RUN_TEST(test_mid_zero_size_bytes); - // Transport receipt lifecycle RUN_TEST(test_receipt_creation_and_culling); RUN_TEST(test_receipt_rapid_fire); @@ -454,6 +579,10 @@ int runUnityTests(void) { RUN_TEST(test_identity_create_destroy_cycle); RUN_TEST(test_destination_packet_cycle); + //RUN_TEST(test_incoming_announce_limit); + RUN_TEST(test_incoming_announce_over_limit); + //RUN_TEST(test_incoming_announce_stress); + return UNITY_END(); }