diff --git a/Devices/lilygo-tdeck/source/module.cpp b/Devices/lilygo-tdeck/source/module.cpp index 5916a8447..9087099f1 100644 --- a/Devices/lilygo-tdeck/source/module.cpp +++ b/Devices/lilygo-tdeck/source/module.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -42,7 +43,7 @@ static error_t start() { } // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. - tt::kernel::delayMillis(100); + delay_millis(100); subscribe_events(); diff --git a/Devices/unphone/source/drivers/unphone_nav_buttons.cpp b/Devices/unphone/source/drivers/unphone_nav_buttons.cpp index 7e3eefeab..26162dfa8 100644 --- a/Devices/unphone/source/drivers/unphone_nav_buttons.cpp +++ b/Devices/unphone/source/drivers/unphone_nav_buttons.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "unphone_nav_buttons.h" +#include #include #include #include @@ -76,9 +77,9 @@ static int32_t nav_buttons_thread_main(UnphoneNavButtonsInternal* internal) { // Debounce all events for a short period of time // This is easier than keeping track when each button was last pressed - tt::kernel::delayMillis(50); + delay_millis(50); xQueueReset(internal->event_queue); - tt::kernel::delayMillis(50); + delay_millis(50); xQueueReset(internal->event_queue); } } diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 9aafe7368..f92dbe67f 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -2,7 +2,6 @@ ## Before release -- WebServer service shouldn't save webserver.properties at start, it slows the boot process - Remove incubating flag from various devices - Add `// SPDX-License-Identifier: GPL-3.0-only` and `// SPDX-License-Identifier: Apache-2.0` to individual files in the project - Elecrow Basic & Advance 3.5" memory issue: not enough memory for App Hub @@ -13,7 +12,6 @@ ## Higher Priority -- Bluetooth app: when toggling BT on, it doesn't update the UI with discovered devices. It only works after re-opening the app. - display.h API: get_backlight does not change ref counting, but it should - bluetooth: various getters for child devices do not change ref counting, but they should - Improve kernel_init.cpp (and other modules): create driver_ensure_added() and driver_ensure_destructed() diff --git a/Modules/lvgl-module/include/lvgl/icons/launcher.h b/Modules/lvgl-module/include/lvgl/icons/launcher.h index 000f4abd4..fb90ba1d2 100644 --- a/Modules/lvgl-module/include/lvgl/icons/launcher.h +++ b/Modules/lvgl-module/include/lvgl/icons/launcher.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 #pragma once #define LVGL_ICON_LAUNCHER_APPS "\xEE\x97\x83" diff --git a/Modules/lvgl-module/include/lvgl/icons/shared.h b/Modules/lvgl-module/include/lvgl/icons/shared.h index 142367c3c..e32aa0ff8 100644 --- a/Modules/lvgl-module/include/lvgl/icons/shared.h +++ b/Modules/lvgl-module/include/lvgl/icons/shared.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 #pragma once #define LVGL_ICON_SHARED_ADD "\xEE\x85\x85" diff --git a/Modules/lvgl-module/include/lvgl/icons/statusbar.h b/Modules/lvgl-module/include/lvgl/icons/statusbar.h index d6f38b159..51f47bad7 100644 --- a/Modules/lvgl-module/include/lvgl/icons/statusbar.h +++ b/Modules/lvgl-module/include/lvgl/icons/statusbar.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 #pragma once #define LVGL_ICON_STATUSBAR_LOCATION_ON "\xEF\x87\x9B" diff --git a/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp b/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp index 554845e15..53449a8b1 100644 --- a/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp +++ b/Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp @@ -194,10 +194,22 @@ void ble_resolve_next_unnamed_peer(struct Device* device, size_t start_idx) { size_t i = start_idx; while (true) { - ble_addr_t addr = {}; - bool found = false; - { - xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY); + ble_addr_t addr = {}; + bool found = false; + bool radio_on = false; + int rc = -1; + + // Don't start (or chain into) a new GAP connection once the radio is going down + // (or is already off) — ble_gap_connect() racing nimble_port_stop() can block the + // NimBLE host task and hang the stop, same class of bug as the OFF_PENDING guard + // on advertising restart in gap_event_handler's BLE_GAP_EVENT_DISCONNECT case. + // The check must be re-done on every iteration (not just once on entry) since a + // failed ble_gap_connect() loops back for the next peer, and it must happen under + // scan_mutex together with the connect call itself so a concurrent dispatch_disable() + // can't flip radio_state between the check and the call. + xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY); + radio_on = ctx->radio_state.load() == BT_RADIO_STATE_ON; + if (radio_on) { while (i < ctx->scan_count) { if (ctx->scan_results[i].name[0] == '\0') { addr = ctx->scan_addrs[i]; @@ -206,7 +218,23 @@ void ble_resolve_next_unnamed_peer(struct Device* device, size_t start_idx) { } ++i; } - xSemaphoreGive(ctx->scan_mutex); + if (found) { + uint8_t own_addr_type; + ble_hs_id_infer_auto(0, &own_addr_type); + void* idx_arg = (void*)(uintptr_t)i; + rc = ble_gap_connect(own_addr_type, &addr, 1500, nullptr, + name_res_gap_callback, idx_arg); + } + } + xSemaphoreGive(ctx->scan_mutex); + + if (!radio_on) { + LOG_I(TAG, "Name resolution: aborting (radio not on)"); + ble_set_scan_active(device, false); + struct BtEvent e = {}; + e.type = BT_EVENT_SCAN_FINISHED; + ble_publish_event(device, e); + return; } if (!found) { @@ -218,12 +246,6 @@ void ble_resolve_next_unnamed_peer(struct Device* device, size_t start_idx) { return; } - uint8_t own_addr_type; - ble_hs_id_infer_auto(0, &own_addr_type); - - void* idx_arg = (void*)(uintptr_t)i; - int rc = ble_gap_connect(own_addr_type, &addr, 1500, nullptr, - name_res_gap_callback, idx_arg); if (rc == 0) { return; // name_res_gap_callback continues the chain } diff --git a/Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h b/Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h index c22fe7d0e..6b9d70a92 100644 --- a/Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h +++ b/Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h @@ -8,6 +8,9 @@ #include #include +#include +#include + namespace tt::app::btmanage { class BtManage final : public App { @@ -18,6 +21,15 @@ class BtManage final : public App { View view = View(&bindings, &state); bool isViewEnabled = false; Device* btDevice = nullptr; + bool callbackRegistered = false; + + // Bumped by onHide() to invalidate any BT event already dispatched to the main + // task for this show/hide session (BtManage is reused across hide/show cycles - + // e.g. launching BtPeerSettings pushes it on top and hides this instance without + // destroying it). Kept in its own heap allocation, independent of BtManage's + // lifetime, so a dispatched callback can check it without touching a possibly + // already-destroyed `this`. + std::shared_ptr> generation = std::make_shared>(0); public: @@ -35,6 +47,20 @@ class BtManage final : public App { State& getState() { return state; } void requestViewUpdate(); + + std::shared_ptr> getGeneration() const { return generation; } + + // Re-attempts registering the device event callback. Needed because the BLE driver + // only allocates its callback list while the device is started/on: a registration + // attempted while the radio is off silently no-ops, so this must be called again + // right after a successful bluetooth::start(). Idempotent: no-ops if already + // registered for this device, so it's safe to call from both onShow() and here. + void registerDeviceCallback(Device* dev); + + // Call after bluetooth::stop(): the driver frees its callback list on stop, so the + // registration state must be cleared here too, without touching the (now-dangling) + // driver-side list. + void forgetCallbackRegistration(); }; } // namespace tt::app::btmanage diff --git a/Tactility/Source/SystemEvents.cpp b/Tactility/Source/SystemEvents.cpp index 6a761bdd2..2f2834072 100644 --- a/Tactility/Source/SystemEvents.cpp +++ b/Tactility/Source/SystemEvents.cpp @@ -1,10 +1,11 @@ +#include +#include #include -#include #include #include +#include -#include #include namespace tt::kernel { diff --git a/Tactility/Source/app/alertdialog/AlertDialog.cpp b/Tactility/Source/app/alertdialog/AlertDialog.cpp index 7f1ac2609..656a73ea2 100644 --- a/Tactility/Source/app/alertdialog/AlertDialog.cpp +++ b/Tactility/Source/app/alertdialog/AlertDialog.cpp @@ -1,13 +1,13 @@ #include "Tactility/app/alertdialog/AlertDialog.h" -#include -#include "Tactility/service/loader/Loader.h" - +#include #include -#include #include +#include +#include + namespace tt::app::alertdialog { #define PARAMETER_BUNDLE_KEY_TITLE "title" diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index a713d6861..706266f9a 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -1,6 +1,8 @@ -#include "Tactility/lvgl/Lvgl.h" -#include "tactility/drivers/backlight.h" -#include "tactility/drivers/display.h" +#include +#include +#include +#include +#include #include #include @@ -10,13 +12,13 @@ #include #include #include +#include #include #include #include #include #include -#include #include @@ -108,23 +110,23 @@ class BootApp : public App { } static void waitForMinimalSplashDuration(TickType_t startTime) { - const auto end_time = kernel::getTicks(); + const auto end_time = get_ticks(); const auto ticks_passed = end_time - startTime; constexpr auto minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS); if (minimum_ticks > ticks_passed) { - kernel::delayTicks(minimum_ticks - ticks_passed); + delay_ticks(minimum_ticks - ticks_passed); } } static int32_t bootThreadCallback() { LOG_I(TAG, "Starting boot thread"); - const auto start_time = kernel::getTicks(); + const auto start_time = get_ticks(); // Give the UI some time to redraw // If we don't do this, various init calls will read files and block SPI IO for the display // This would result in a blank/black screen being shown during this phase of the boot process // This works with 5 ms on a T-Lora Pager, so we give it 10 ms to be safe - kernel::delayMillis(10); + delay_millis(10); // TODO: Support for multiple displays LOG_I(TAG, "Setup display"); diff --git a/Tactility/Source/app/btmanage/BtManage.cpp b/Tactility/Source/app/btmanage/BtManage.cpp index 33fc08452..77f4524c1 100644 --- a/Tactility/Source/app/btmanage/BtManage.cpp +++ b/Tactility/Source/app/btmanage/BtManage.cpp @@ -23,10 +23,20 @@ static void onBtToggled(bool requestOn) { bool radio_on = bluetooth::isRadioOnOrPending(dev); if (requestOn && !radio_on) { LOG_I(TAG, "Turning on"); - bluetooth::start(dev); + if (bluetooth::start(dev)) { + // The driver only allocates its callback list once the device is started, + // so the registration attempted in onShow() (while radio was off) was a + // no-op. Register again now that the device is actually up. + auto bt = std::static_pointer_cast(getCurrentApp()); + bt->registerDeviceCallback(dev); + } } else if (!requestOn && radio_on) { LOG_I(TAG, "Turning off"); - bluetooth::stop(dev); + if (bluetooth::stop(dev)) { + // A completed stop frees the driver's callback list. + auto bt = std::static_pointer_cast(getCurrentApp()); + bt->forgetCallbackRegistration(); + } } device_put(dev); } else { @@ -90,13 +100,19 @@ void BtManage::unlock() { } void BtManage::requestViewUpdate() { + // Lock order must match onShow()/onHide(): both run under GuiService's lvgl_lock() + // and then take `mutex` internally. Taking `mutex` before lvgl_lock() here would + // invert that order and deadlock against a concurrent onHide()/onShow() (GUI task + // holding LVGL lock, waiting on `mutex`; this task holding `mutex`, waiting on LVGL + // lock) - exactly what happens when BT events fire rapidly (e.g. during scanning) + // while the app is being hidden. + lvgl_lock(); lock(); if (isViewEnabled) { - lvgl_lock(); view.update(); - lvgl_unlock(); } unlock(); + lvgl_unlock(); } void BtManage::onBtEvent(const BtEvent& event) { @@ -148,17 +164,45 @@ static void onKernelBtEvent(Device* /*device*/, void* context, BtEvent event) { // nimble_port_stop), creating a permanent deadlock. Dispatch to the main task so // the NimBLE host task is never blocked by BtManage's state updates or LVGL lock. auto* self = static_cast(context); - getMainDispatcher().dispatch([self, event] { + // Captured while `self` is still guaranteed valid (the callback is only invoked + // while registered, i.e. before onHide() removes it). Comparing this later - without + // dereferencing `self` - lets the dispatched lambda detect a stale event from a + // session that has since been hidden (and possibly destroyed) without a UAF. + auto generation = self->getGeneration(); + int expectedGeneration = generation->load(); + getMainDispatcher().dispatch([self, generation, expectedGeneration, event] { + if (generation->load() != expectedGeneration) { + return; + } self->onBtEvent(event); }); } +void BtManage::registerDeviceCallback(Device* dev) { + lock(); + if (btDevice == dev && !callbackRegistered) { + // Only latch the flag on success: while the radio is off the driver has no + // callback list yet, so this add is a silent no-op and must be retried once + // bluetooth::start() actually brings the device up. + if (bluetooth_add_event_callback(dev, this, onKernelBtEvent) == ERROR_NONE) { + callbackRegistered = true; + } + } + unlock(); +} + +void BtManage::forgetCallbackRegistration() { + lock(); + callbackRegistered = false; + unlock(); +} + void BtManage::onShow(AppContext& app, lv_obj_t* parent) { // Initialise state and view before subscribing to avoid incoming events // racing with state initialisation. state.setRadioState(bluetooth::getRadioState()); Device* dev = nullptr; - device_get_first_active_by_type(&BLUETOOTH_TYPE, &dev); + device_get_first_by_type(&BLUETOOTH_TYPE, &dev); state.setScanning(dev ? bluetooth_is_scanning(dev) : false); state.updateScanResults(); @@ -177,7 +221,7 @@ void BtManage::onShow(AppContext& app, lv_obj_t* parent) { btDevice = dev; if (btDevice) { - bluetooth_add_event_callback(btDevice, this, onKernelBtEvent); + registerDeviceCallback(btDevice); } auto radio_state = bluetooth::getRadioState(); @@ -192,9 +236,17 @@ void BtManage::onShow(AppContext& app, lv_obj_t* parent) { } void BtManage::onHide(AppContext& app) { + // Invalidate any BT event dispatched-but-not-yet-run for this session before doing + // anything else, so it can't race a subsequent destruction of this instance (see + // onKernelBtEvent()/getGeneration()). + generation->fetch_add(1); + lock(); if (btDevice) { - bluetooth_remove_event_callback(btDevice, onKernelBtEvent); + if (callbackRegistered) { + bluetooth_remove_event_callback(btDevice, onKernelBtEvent); + callbackRegistered = false; + } device_put(btDevice); btDevice = nullptr; } diff --git a/Tactility/Source/app/gpssettings/GpsSettings.cpp b/Tactility/Source/app/gpssettings/GpsSettings.cpp index e2af6a3af..98e725c38 100644 --- a/Tactility/Source/app/gpssettings/GpsSettings.cpp +++ b/Tactility/Source/app/gpssettings/GpsSettings.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -211,7 +212,7 @@ class GpsSettingsApp final : public App { GpsSettingsApp() { // Runs while the screen is shown - there's no push notification for GPS device state // changes, so this is the only way this screen finds out about them. - timer = std::make_unique(Timer::Type::Periodic, kernel::secondsToTicks(1), [this] { + timer = std::make_unique(Timer::Type::Periodic, seconds_to_ticks(1), [this] { updateDeviceStates(); }); } diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 09a863353..03708a6e3 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -53,7 +54,7 @@ struct DeviceEntry { class PowerApp : public App { - Timer update_timer = Timer(Timer::Type::Periodic, kernel::millisToTicks(1000),[]() { onTimer(); }); + Timer update_timer = Timer(Timer::Type::Periodic, millis_to_ticks(1000),[]() { onTimer(); }); std::vector entries; diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index fabade818..564a7f057 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -1,8 +1,11 @@ -#include -#include +#include "tactility/time.h" + + +#include #include +#include #include -#include +#include #include #include @@ -242,7 +245,7 @@ static std::shared_ptr optApp() { } class SystemInfoApp final : public App { - Timer memoryTimer = Timer(Timer::Type::Periodic, kernel::millisToTicks(10000), [] { + Timer memoryTimer = Timer(Timer::Type::Periodic, millis_to_ticks(10000), [] { auto app = optApp(); if (app) { lvgl_lock(); @@ -251,7 +254,7 @@ class SystemInfoApp final : public App { } }); - Timer tasksTimer = Timer(Timer::Type::Periodic, kernel::millisToTicks(15000), [] { + Timer tasksTimer = Timer(Timer::Type::Periodic, millis_to_ticks(15000), [] { auto app = optApp(); if (app) { lvgl_lock(); diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 0d964eecd..7d254129b 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -182,7 +183,7 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) { obj_set_style_bg_invisible(left_spacer); lv_obj_set_flex_grow(left_spacer, 1); - statusbar_data.mutex.lock(kernel::MAX_TICKS); + statusbar_data.mutex.lock(MAX_TICKS); for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) { auto* image = lv_image_create(obj); lv_obj_set_size(image, icon_size, icon_size); // regular padding doesn't work diff --git a/Tactility/Source/service/displayidle/DisplayIdle.cpp b/Tactility/Source/service/displayidle/DisplayIdle.cpp index 3ef86869c..f25c0c4ac 100644 --- a/Tactility/Source/service/displayidle/DisplayIdle.cpp +++ b/Tactility/Source/service/displayidle/DisplayIdle.cpp @@ -1,6 +1,8 @@ #ifdef ESP_PLATFORM #include +#include +#include #include "BouncingBallsScreensaver.h" #include "MatrixRainScreensaver.h" @@ -8,14 +10,14 @@ #include "Screensaver.h" #include "StackChanScreensaver.h" -#include -#include #include #include +#include #include #include #include + #include namespace tt::service::displayidle { @@ -221,7 +223,7 @@ bool DisplayIdleService::onStart(ServiceContext& service) { cachedDisplaySettings = settings::display::loadOrGetDefault(); - timer = std::make_unique(Timer::Type::Periodic, kernel::millisToTicks(TICK_INTERVAL_MS), [this]{ this->tick(); }); + timer = std::make_unique(Timer::Type::Periodic, millis_to_ticks(TICK_INTERVAL_MS), [this]{ this->tick(); }); timer->setCallbackPriority(Thread::Priority::Lower); timer->start(); return true; @@ -238,7 +240,7 @@ void DisplayIdleService::onStop(ServiceContext& service) { for (int i = 0; i < maxRetries && screensaverOverlay; ++i) { stopScreensaver(); if (screensaverOverlay && i < maxRetries - 1) { - kernel::delayMillis(50); // Brief delay before retry + delay_millis(50); // Brief delay before retry } } if (screensaverOverlay) { diff --git a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp index 33942156d..24a96c29e 100644 --- a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp +++ b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -9,10 +10,10 @@ #include #include -#include #include #include #include +#include namespace tt::service::keyboardidle { @@ -92,7 +93,7 @@ class KeyboardIdleService final : public Service { // Note: Settings changes require service restart to take effect // TODO: Add KeyboardSettingsChanged events for dynamic updates - timer = std::make_unique(Timer::Type::Periodic, kernel::millisToTicks(250), [this]{ this->tick(); }); + timer = std::make_unique(Timer::Type::Periodic, millis_to_ticks(250), [this]{ this->tick(); }); timer->setCallbackPriority(Thread::Priority::Lower); timer->start(); return true; diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.cpp b/Tactility/Source/service/screenshot/ScreenshotTask.cpp index b91b088da..84e70e6bc 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.cpp +++ b/Tactility/Source/service/screenshot/ScreenshotTask.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -71,7 +72,7 @@ void ScreenshotTask::taskMain() { if (work.type == TASK_WORK_TYPE_DELAY) { // Splitting up the delays makes it easier to stop the service for (int i = 0; i < (work.delay_in_seconds * 10) && !isInterrupted(); ++i){ - kernel::delayMillis(100); + delay_millis(100); } if (!isInterrupted()) { @@ -88,14 +89,14 @@ void ScreenshotTask::taskMain() { if (appContext != nullptr) { const app::AppManifest& manifest = appContext->getManifest(); if (manifest.appId != last_app_id) { - kernel::delayMillis(100); + delay_millis(100); last_app_id = manifest.appId; auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.appId); makeScreenshot(filename); } } // Ensure the LVGL widgets are rendered as the app just started - kernel::delayMillis(250); + delay_millis(250); } } diff --git a/Tactility/Source/service/wifi/Wifi.cpp b/Tactility/Source/service/wifi/Wifi.cpp index d3b074077..64781f3ec 100644 --- a/Tactility/Source/service/wifi/Wifi.cpp +++ b/Tactility/Source/service/wifi/Wifi.cpp @@ -11,12 +11,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -76,7 +76,7 @@ struct WifiServiceState { bool connectionTargetRemember = false; settings::WifiApSettings connectionTarget; uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT; - TickType_t lastScanTime = kernel::MAX_TICKS; + TickType_t lastScanTime = MAX_TICKS; std::unique_ptr autoConnectTimer; kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription; }; @@ -165,7 +165,7 @@ void dispatchScan() { LOG_I(TAG, "dispatchScan()"); if (!started || state.device == nullptr || !device_is_ready(state.device)) return; - state.lastScanTime = kernel::getTicks(); + state.lastScanTime = get_ticks(); error_t result = wifi_scan(state.device); if (result != ERROR_NONE) { @@ -263,7 +263,7 @@ bool shouldScanForAutoConnect() { !state.pauseAutoConnect && !state.externalScanPause.load(); if (!radio_scannable) return false; - TickType_t current_time = kernel::getTicks(); + TickType_t current_time = get_ticks(); bool scan_time_has_looped = current_time < state.lastScanTime; bool no_recent_scan = (current_time - state.lastScanTime) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS); return scan_time_has_looped || no_recent_scan; diff --git a/Tactility/Source/settings/WebServerSettings.cpp b/Tactility/Source/settings/WebServerSettings.cpp index 1a5860191..99cc17d15 100644 --- a/Tactility/Source/settings/WebServerSettings.cpp +++ b/Tactility/Source/settings/WebServerSettings.cpp @@ -144,23 +144,17 @@ bool load(WebServerSettings& settings) { ? static_cast(parseInt(ap_channel->second, 1, 13, 1)) : 1; - // Security: If AP password is empty, generate a strong random password. + // Security: If AP password is empty, generate a strong random password in memory. // Skip this if user explicitly wants an open network. // Note: We only auto-generate for EMPTY passwords, not user-set ones. + // This is a read-only function: the generated password is NOT persisted here — + // callers that want it saved must call save() explicitly. if (!settings.apOpenNetwork && isEmptyCredential(settings.apPassword)) { - LOG_I(TAG, "AP password is empty - generating secure random password"); + LOG_I(TAG, "AP password is empty - generating secure random password (not persisted)"); // Generate 12-character random password (alphanumeric, ~71 bits of entropy) // WPA2 requires 8-63 characters, so 12 is well within range settings.apPassword = generateRandomCredential(12); - - // Persist the generated password immediately - map[KEY_AP_PASSWORD] = settings.apPassword; - if (file::savePropertiesFile(getSettingsFilePath(), map)) { - LOG_I(TAG, "Generated and saved new secure AP password"); - } else { - LOG_E(TAG, "Failed to save generated AP password"); - } } // Web server settings @@ -181,27 +175,20 @@ bool load(WebServerSettings& settings) { settings.webServerUsername = (webserver_username != map.end()) ? webserver_username->second : ""; settings.webServerPassword = (webserver_password != map.end()) ? webserver_password->second : ""; - // Security: If auth is enabled but credentials are empty, - // generate strong random credentials and persist them immediately. - // Note: We only auto-generate for EMPTY credentials, allowing users to set their own. + // Security: If auth is enabled but credentials are empty, generate strong random + // credentials in memory. Note: We only auto-generate for EMPTY credentials, allowing + // users to set their own. + // This is a read-only function: the generated credentials are NOT persisted here — + // callers that want them saved (so they're consistent across reboots) must call + // save() explicitly. if (settings.webServerAuthEnabled && (isEmptyCredential(settings.webServerUsername) || isEmptyCredential(settings.webServerPassword))) { - LOG_I(TAG, "Auth enabled with empty credentials - generating secure random credentials"); + LOG_I(TAG, "Auth enabled with empty credentials - generating secure random credentials (not persisted)"); // Generate 12-character random credentials (alphanumeric, ~71 bits of entropy each) settings.webServerUsername = generateRandomCredential(12); settings.webServerPassword = generateRandomCredential(12); - - // Persist the generated credentials immediately - // We need to save these to the file so they're consistent across reboots - map[KEY_WEBSERVER_USERNAME] = settings.webServerUsername; - map[KEY_WEBSERVER_PASSWORD] = settings.webServerPassword; - if (file::savePropertiesFile(getSettingsFilePath(), map)) { - LOG_I(TAG, "Generated and saved new secure credentials"); - } else { - LOG_E(TAG, "Failed to save generated credentials - auth may be inconsistent across reboots"); - } } return true; @@ -228,14 +215,10 @@ WebServerSettings loadOrGetDefault() { bool loadedFromFlash = load(settings); if (!loadedFromFlash) { - // First boot - use defaults (WiFi OFF, WebServer OFF) + // No properties file yet (e.g. first boot) - use defaults in memory (WiFi OFF, + // WebServer OFF). Read-only function: does NOT persist these defaults — callers + // that want them saved must call save() explicitly. settings = getDefault(); - // Save defaults to flash so toggle states persist - if (save(settings)) { - LOG_I(TAG, "First boot - saved default settings (WiFi OFF WebServer OFF)"); - } else { - LOG_W(TAG, "First boot - failed to save default settings to flash"); - } } return settings; diff --git a/TactilityFreeRtos/Include/Tactility/Dispatcher.h b/TactilityFreeRtos/Include/Tactility/Dispatcher.h index 1fe6cbb27..b03510aad 100644 --- a/TactilityFreeRtos/Include/Tactility/Dispatcher.h +++ b/TactilityFreeRtos/Include/Tactility/Dispatcher.h @@ -57,7 +57,7 @@ class Dispatcher final { * @param[in] timeout lock acquisition timeout * @return true if dispatching was successful (timeout not reached) */ - bool dispatch(Function function, TickType_t timeout = kernel::MAX_TICKS) { + bool dispatch(Function function, TickType_t timeout = kernel::FREERTOS_MAX_TICKS) { // Mutate if (!mutex.lock(timeout)) { #ifdef ESP_PLATFORM @@ -92,7 +92,7 @@ class Dispatcher final { * @param[in] timeout the ticks to wait for a message * @return the amount of messages that were consumed */ - uint32_t consume(TickType_t timeout = kernel::MAX_TICKS) { + uint32_t consume(TickType_t timeout = kernel::FREERTOS_MAX_TICKS) { // Wait for signal if (!eventFlag.wait(WAIT_FLAG, false, true, nullptr, timeout)) { return 0; diff --git a/TactilityFreeRtos/Include/Tactility/DispatcherThread.h b/TactilityFreeRtos/Include/Tactility/DispatcherThread.h index a67809151..d126ca5ae 100644 --- a/TactilityFreeRtos/Include/Tactility/DispatcherThread.h +++ b/TactilityFreeRtos/Include/Tactility/DispatcherThread.h @@ -46,7 +46,7 @@ class DispatcherThread final { /** * Dispatch a message. */ - bool dispatch(const Dispatcher::Function& function, TickType_t timeout = kernel::MAX_TICKS) { + bool dispatch(const Dispatcher::Function& function, TickType_t timeout = kernel::FREERTOS_MAX_TICKS) { return dispatcher.dispatch(function, timeout); } diff --git a/TactilityFreeRtos/Include/Tactility/EventGroup.h b/TactilityFreeRtos/Include/Tactility/EventGroup.h index 4b919a0a6..7570eba1f 100644 --- a/TactilityFreeRtos/Include/Tactility/EventGroup.h +++ b/TactilityFreeRtos/Include/Tactility/EventGroup.h @@ -100,7 +100,7 @@ class EventGroup final { bool awaitAll = false, bool clearOnExit = true, uint32_t* outFlags = nullptr, - TickType_t timeout = kernel::MAX_TICKS + TickType_t timeout = kernel::FREERTOS_MAX_TICKS ) const { assert(xPortInIsrContext() == pdFALSE); diff --git a/TactilityFreeRtos/Include/Tactility/Lock.h b/TactilityFreeRtos/Include/Tactility/Lock.h index 49c61d31c..0dc0a4588 100644 --- a/TactilityFreeRtos/Include/Tactility/Lock.h +++ b/TactilityFreeRtos/Include/Tactility/Lock.h @@ -20,7 +20,7 @@ class Lock { virtual bool lock(TickType_t timeout) const = 0; - bool lock() const { return lock(kernel::MAX_TICKS); } + bool lock() const { return lock(kernel::FREERTOS_MAX_TICKS); } virtual void unlock() const = 0; @@ -40,9 +40,9 @@ class Lock { } } - void withLock(const std::function& onLockAcquired) const { withLock(kernel::MAX_TICKS, onLockAcquired); } + void withLock(const std::function& onLockAcquired) const { withLock(kernel::FREERTOS_MAX_TICKS, onLockAcquired); } - void withLock(const std::function& onLockAcquired, const std::function& onLockFailed) const { withLock(kernel::MAX_TICKS, onLockAcquired, onLockFailed); } + void withLock(const std::function& onLockAcquired, const std::function& onLockFailed) const { withLock(kernel::FREERTOS_MAX_TICKS, onLockAcquired, onLockFailed); } ScopedLock asScopedLock() const; }; diff --git a/TactilityFreeRtos/Include/Tactility/PubSub.h b/TactilityFreeRtos/Include/Tactility/PubSub.h index b7102cc52..c56a42789 100644 --- a/TactilityFreeRtos/Include/Tactility/PubSub.h +++ b/TactilityFreeRtos/Include/Tactility/PubSub.h @@ -43,7 +43,7 @@ class PubSub final { } // Wait for Mutex usage - if (mutex.lock(kernel::MAX_TICKS)) { + if (mutex.lock(kernel::FREERTOS_MAX_TICKS)) { // TODO: Fix the case where the mutex might be immediately locked after this point and then crashes when deleted mutex.unlock(); } diff --git a/TactilityFreeRtos/Include/Tactility/Thread.h b/TactilityFreeRtos/Include/Tactility/Thread.h index 2caefaa65..20d4c1c27 100644 --- a/TactilityFreeRtos/Include/Tactility/Thread.h +++ b/TactilityFreeRtos/Include/Tactility/Thread.h @@ -224,7 +224,7 @@ class Thread final { /** * @warning If this blocks forever, it might be because of the Thread, but it could also be because another task is blocking the CPU. */ - bool join(TickType_t timeout = kernel::MAX_TICKS, TickType_t pollInterval = 10) { + bool join(TickType_t timeout = kernel::FREERTOS_MAX_TICKS, TickType_t pollInterval = 10) { assert(getCurrent() != this); TickType_t start_ticks = kernel::getTicks(); diff --git a/TactilityFreeRtos/Include/Tactility/Timer.h b/TactilityFreeRtos/Include/Tactility/Timer.h index f77b25199..4a748a639 100644 --- a/TactilityFreeRtos/Include/Tactility/Timer.h +++ b/TactilityFreeRtos/Include/Tactility/Timer.h @@ -29,7 +29,7 @@ class Timer final { struct TimerHandleDeleter { void operator()(TimerHandle_t handleToDelete) const { - xTimerDelete(handleToDelete, kernel::MAX_TICKS); + xTimerDelete(handleToDelete, kernel::FREERTOS_MAX_TICKS); } }; @@ -75,7 +75,7 @@ class Timer final { */ bool start() const { assert(xPortInIsrContext() == pdFALSE); - return xTimerStart(handle.get(), kernel::MAX_TICKS) == pdPASS; + return xTimerStart(handle.get(), kernel::FREERTOS_MAX_TICKS) == pdPASS; } /** Stop the timer @@ -84,7 +84,7 @@ class Timer final { */ bool stop() const { assert(xPortInIsrContext() == pdFALSE); - return xTimerStop(handle.get(), kernel::MAX_TICKS) == pdPASS; + return xTimerStop(handle.get(), kernel::FREERTOS_MAX_TICKS) == pdPASS; } /** @@ -94,8 +94,8 @@ class Timer final { */ bool reset(TickType_t interval) const { assert(xPortInIsrContext() == pdFALSE); - return xTimerChangePeriod(handle.get(), interval, kernel::MAX_TICKS) == pdPASS && - xTimerReset(handle.get(), kernel::MAX_TICKS) == pdPASS; + return xTimerChangePeriod(handle.get(), interval, kernel::FREERTOS_MAX_TICKS) == pdPASS && + xTimerReset(handle.get(), kernel::FREERTOS_MAX_TICKS) == pdPASS; } /** @@ -104,7 +104,7 @@ class Timer final { */ bool reset() const { assert(xPortInIsrContext() == pdFALSE); - return xTimerReset(handle.get(), kernel::MAX_TICKS) == pdPASS; + return xTimerReset(handle.get(), kernel::FREERTOS_MAX_TICKS) == pdPASS; } /** @return true when the timer is running */ diff --git a/TactilityFreeRtos/Include/Tactility/kernel/Kernel.h b/TactilityFreeRtos/Include/Tactility/kernel/Kernel.h index 59830a6dc..0576878c9 100644 --- a/TactilityFreeRtos/Include/Tactility/kernel/Kernel.h +++ b/TactilityFreeRtos/Include/Tactility/kernel/Kernel.h @@ -17,7 +17,7 @@ namespace tt::kernel { -constexpr TickType_t MAX_TICKS = ~static_cast(0); +constexpr TickType_t FREERTOS_MAX_TICKS = ~static_cast(0); /** @return the frequency at which the kernel task schedulers operate */ constexpr uint32_t getTickFrequency() { diff --git a/TactilityKernel/include/tactility/delay.h b/TactilityKernel/include/tactility/delay.h index 2d969e797..00e09e406 100644 --- a/TactilityKernel/include/tactility/delay.h +++ b/TactilityKernel/include/tactility/delay.h @@ -10,6 +10,7 @@ #endif #include +#include #include #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/time.h b/TactilityKernel/include/tactility/time.h index af5ff11cc..94f24c0fa 100644 --- a/TactilityKernel/include/tactility/time.h +++ b/TactilityKernel/include/tactility/time.h @@ -11,7 +11,8 @@ #include #include "defines.h" -#include "tactility/freertos/task.h" +#include +#include #ifdef ESP_PLATFORM #include @@ -31,6 +32,8 @@ static_assert(configTICK_RATE_HZ == 1000); static_assert(configTICK_RATE_HZ == 1000, "configTICK_RATE_HZ must be 1000"); #endif +#define MAX_TICKS (~(TickType_t)0) + static inline uint32_t get_tick_frequency() { return configTICK_RATE_HZ; } diff --git a/Tests/TactilityFreeRtos/Source/MutexTest.cpp b/Tests/TactilityFreeRtos/Source/MutexTest.cpp index b30ee2bd3..a4ad267f9 100644 --- a/Tests/TactilityFreeRtos/Source/MutexTest.cpp +++ b/Tests/TactilityFreeRtos/Source/MutexTest.cpp @@ -7,13 +7,13 @@ using namespace tt; TEST_CASE("a Mutex can block a thread") { auto mutex = Mutex(); - CHECK_EQ(mutex.lock(kernel::MAX_TICKS), true); + CHECK_EQ(mutex.lock(kernel::FREERTOS_MAX_TICKS), true); Thread thread = Thread( "thread", 1024, [&mutex] { - mutex.lock(kernel::MAX_TICKS); + mutex.lock(kernel::FREERTOS_MAX_TICKS); return 0; } ); diff --git a/Tests/TactilityFreeRtos/Source/RecursiveMutexTest.cpp b/Tests/TactilityFreeRtos/Source/RecursiveMutexTest.cpp index 907edd88f..f30eb2e8c 100644 --- a/Tests/TactilityFreeRtos/Source/RecursiveMutexTest.cpp +++ b/Tests/TactilityFreeRtos/Source/RecursiveMutexTest.cpp @@ -7,13 +7,13 @@ using namespace tt; TEST_CASE("a RecursiveMutex can block a thread") { auto mutex = RecursiveMutex(); - CHECK_EQ(mutex.lock(kernel::MAX_TICKS), true); + CHECK_EQ(mutex.lock(kernel::FREERTOS_MAX_TICKS), true); Thread thread = Thread( "thread", 1024, [&mutex] { - mutex.lock(kernel::MAX_TICKS); + mutex.lock(kernel::FREERTOS_MAX_TICKS); return 0; } );