Public Access
One firmware: the Debug Console in every build, off until switched on, with the device's own token
There is no Debug Build any more (ADR 0010, issue #68, Q188 to Q195). The console and the test commands are compiled into every firmware. It listens only while Settings > Debug Console is on, which isn't the default; off, neither its task nor its 4 KB ring exists. The token is made by the device and shown on that page; a client proves it knows it by answering a challenge with an HMAC, so it never crosses the network, and five wrong answers close the console for a minute. DBG in the Status Bar while it listens. Over USB serial only: debug on, debug token <value>, debug token new. scripts/flash.sh --debug uses them to set a device up with the developer's token. scripts/rdbg.py takes the token from -t, $RORO_DEBUG_TOKEN or the file, answers the challenge, and fetches a release's ELF to decode a crash. Gone: the cardputer-adv-debug environment, RORO_DEBUG, the +debug version, scripts/debug_flags.py, update install ... force, and the rule that a Debug Build doesn't install releases. Old clients and old firmwares don't talk to each other. Against the builds it replaces: 30 KB more flash and 88 bytes more static RAM than the release, 4 KB less RAM than the Debug Build. 468 host tests. Checked on the device: off by default, login, the pause after wrong tokens, Safe Mode with the console, the setting surviving an update, debug off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
#include "apps/debug_console_page.h"
|
||||
|
||||
#include "debug_auth.h"
|
||||
#include "services/debug_console.h"
|
||||
#include "ui/fonts.h"
|
||||
#include "ui/widgets.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
void DebugConsolePage::enter() {
|
||||
list_.setCount(kRows);
|
||||
confirm_.reset();
|
||||
typing_ = false;
|
||||
refusal_.clear();
|
||||
}
|
||||
|
||||
bool DebugConsolePage::onKey(const KeyEvent& e) {
|
||||
if (confirm_) {
|
||||
confirm_->onKey(e);
|
||||
if (confirm_->result() == 1) {
|
||||
if (ask_ == Ask::SwitchOn) DebugConsole::switchOn(settings_);
|
||||
else settings_.setString(Setting::DebugToken, DebugConsole::freshToken());
|
||||
}
|
||||
if (confirm_->result() != DialogModel::kPending) confirm_.reset();
|
||||
return true;
|
||||
}
|
||||
if (typing_) {
|
||||
switch (e.key) {
|
||||
case Key::Char: editor_.insert(e.ch); break;
|
||||
case Key::Delete: editor_.backspace(); break;
|
||||
case Key::Left: editor_.left(); break;
|
||||
case Key::Right: editor_.right(); break;
|
||||
case Key::Back: typing_ = false; break;
|
||||
case Key::Select: {
|
||||
std::string token = debug::tidyToken(editor_.text());
|
||||
if (debug::validToken(token) && settings_.setString(Setting::DebugToken, token)) typing_ = false;
|
||||
else refusal_ = "16 to 64 characters, please";
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
switch (e.key) {
|
||||
case Key::Up: list_.up(); break;
|
||||
case Key::Down: list_.down(); break;
|
||||
case Key::Back: return false;
|
||||
case Key::Left:
|
||||
case Key::Right:
|
||||
case Key::Select:
|
||||
if (e.key != Key::Select && list_.selected() != kSwitch) break;
|
||||
switch (list_.selected()) {
|
||||
case kSwitch:
|
||||
if (settings_.getBool(Setting::DebugConsole)) settings_.setBool(Setting::DebugConsole, false);
|
||||
else { // on is the one to think about
|
||||
ask_ = Ask::SwitchOn;
|
||||
confirm_.reset(new DialogModel({"Cancel", "Switch on"}));
|
||||
}
|
||||
break;
|
||||
case kNewToken:
|
||||
ask_ = Ask::NewToken;
|
||||
confirm_.reset(new DialogModel({"Cancel", "New token"}));
|
||||
break;
|
||||
case kTypeToken:
|
||||
editor_ = LineEditor(64);
|
||||
refusal_.clear();
|
||||
typing_ = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugConsolePage::draw(Canvas& c) {
|
||||
const auto& area = theme::kContent;
|
||||
c.setTextDatum(top_left);
|
||||
if (typing_) {
|
||||
c.setFont(&fonts::body);
|
||||
c.setTextColor(theme::kMuted);
|
||||
c.drawString("A token of your own", 4, area.y + 4);
|
||||
widgets::lineEditor(c, editor_, {4, area.y + 22, area.w - 8, 0});
|
||||
c.setTextColor(refusal_.empty() ? theme::kMuted : theme::kWarning);
|
||||
c.drawString(refusal_.empty() ? "16 to 64 characters. Capitals or not, it's the same." : refusal_.c_str(), 4, area.y + 44);
|
||||
c.setTextColor(theme::kMuted);
|
||||
c.drawString("Enter: save `: cancel", 4, area.y + 44 + theme::kLineHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
bool on = settings_.getBool(Setting::DebugConsole);
|
||||
std::string ip = wifi_.ip();
|
||||
widgets::list(
|
||||
c, list_, {area.x, area.y, area.w, kRows * theme::kLineHeight},
|
||||
[](int i) -> std::string {
|
||||
switch (i) {
|
||||
case kSwitch: return "Debug Console";
|
||||
case kAddress: return "Connect to";
|
||||
case kNewToken: return "New token";
|
||||
default: return "Type a token";
|
||||
}
|
||||
},
|
||||
[&](int i) -> std::string {
|
||||
switch (i) {
|
||||
case kSwitch: return on ? "On" : "Off";
|
||||
case kAddress: return !on ? "-" : ip.empty() ? "Wi-Fi not connected" : ip + ":" + std::to_string(DebugConsole::kPort);
|
||||
default: return ">";
|
||||
}
|
||||
});
|
||||
|
||||
// The token: here, in full, and nowhere else (it's never printed on a console). One the device
|
||||
// made is drawn at twice the size, three groups then two: it has to be read and typed.
|
||||
int y = area.y + kRows * theme::kLineHeight + 4;
|
||||
c.drawFastHLine(4, y - 2, area.w - 8, theme::kMuted);
|
||||
const std::string& token = settings_.getString(Setting::DebugToken);
|
||||
std::string shown = debug::groupToken(token);
|
||||
c.setFont(&fonts::body);
|
||||
if (token.empty()) {
|
||||
c.setTextColor(theme::kMuted);
|
||||
c.drawString("No token yet: one is made when", 4, y + 4);
|
||||
c.drawString("you switch the console on.", 4, y + 4 + theme::kLineHeight);
|
||||
} else if (token.size() <= debug::kTokenChars) {
|
||||
c.setTextColor(theme::kText);
|
||||
c.setTextSize(2);
|
||||
c.drawString(shown.substr(0, 14).c_str(), 4, y + 2);
|
||||
if (shown.size() > 15) c.drawString(shown.substr(15).c_str(), 4, y + 2 + 2 * theme::kLineHeight - 3);
|
||||
c.setTextSize(1);
|
||||
} else {
|
||||
c.setTextColor(theme::kText);
|
||||
for (size_t at = 0; at < shown.size(); at += 35, y += theme::kLineHeight) c.drawString(shown.substr(at, 35).c_str(), 4, y + 2);
|
||||
}
|
||||
c.setFont(&fonts::small);
|
||||
c.setTextColor(on ? theme::kWarning : theme::kMuted);
|
||||
c.drawString(on ? "On this Wi-Fi, the token is full control." : "Off: nothing listens.", 4, area.y + area.h - 9);
|
||||
|
||||
if (confirm_) {
|
||||
if (ask_ == Ask::SwitchOn)
|
||||
widgets::dialog(c, "Switch it on?", "With the token, anyone on this Wi-Fi can read the console, press keys and copy files.", *confirm_);
|
||||
else widgets::dialog(c, "A new token?", "The one in use stops working, and whoever is connected is cut off.", *confirm_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "dialog_model.h"
|
||||
#include "key_event.h"
|
||||
#include "line_editor.h"
|
||||
#include "list_model.h"
|
||||
#include "services/wifi_service.h"
|
||||
#include "settings.h"
|
||||
#include "ui/canvas.h"
|
||||
#include "ui/theme.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
// Settings → Debug Console (ADR 0010): the switch, where to connect, and the token, which is shown
|
||||
// here and nowhere else. Switching on makes a token if there's none; a new one, or one typed by
|
||||
// hand, ends the connection of whoever holds the old one.
|
||||
class DebugConsolePage {
|
||||
public:
|
||||
DebugConsolePage(Settings& settings, WifiService& wifi) : settings_(settings), wifi_(wifi) {}
|
||||
|
||||
void enter();
|
||||
bool onKey(const KeyEvent& e); // false: leave the page
|
||||
bool textEntryActive() const { return typing_; }
|
||||
void draw(Canvas& c);
|
||||
|
||||
private:
|
||||
enum Row { kSwitch, kAddress, kNewToken, kTypeToken, kRows };
|
||||
enum class Ask { None, SwitchOn, NewToken };
|
||||
|
||||
Settings& settings_;
|
||||
WifiService& wifi_;
|
||||
ListModel list_{kRows};
|
||||
std::unique_ptr<DialogModel> confirm_;
|
||||
Ask ask_ = Ask::None;
|
||||
bool typing_ = false;
|
||||
LineEditor editor_{64};
|
||||
std::string refusal_;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
@@ -80,7 +80,6 @@ bool FirmwarePage::installable(std::string& why) const {
|
||||
std::string running = update_.runningVersion();
|
||||
if (!shown_.usable()) why = "Nothing to install in it";
|
||||
else if (!release::versionNewer(shown_.tag, running) && !versionOlder(shown_.tag, running)) why = "That's the version running";
|
||||
else if (release::isDebugBuild(running)) why = "Debug Build: update from the PC"; // it keeps its console (Q171)
|
||||
else if (wifi_.state() != WifiController::State::Connected) why = "Not connected to Wi-Fi";
|
||||
else return true;
|
||||
return false;
|
||||
|
||||
@@ -34,6 +34,9 @@ bool SettingsApp::onKey(const KeyEvent& e) {
|
||||
case Page::Firmware:
|
||||
if (!firmwarePage_.onKey(e)) page_ = Page::Menu;
|
||||
return true;
|
||||
case Page::Debug:
|
||||
if (!debugPage_.onKey(e)) page_ = Page::Menu;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -75,6 +78,10 @@ bool SettingsApp::onMenuKey(const KeyEvent& e) {
|
||||
page_ = Page::Firmware;
|
||||
firmwarePage_.enter();
|
||||
break;
|
||||
case Row::DebugConsole:
|
||||
page_ = Page::Debug;
|
||||
debugPage_.enter();
|
||||
break;
|
||||
default: page_ = Page::About; break;
|
||||
}
|
||||
break;
|
||||
@@ -124,7 +131,7 @@ bool SettingsApp::onAboutKey(const KeyEvent& e) {
|
||||
|
||||
void SettingsApp::update(uint32_t nowMs) {
|
||||
// Live values on About and Firmware.
|
||||
bool live = page_ == Page::About || page_ == Page::Firmware ||
|
||||
bool live = page_ == Page::About || page_ == Page::Firmware || page_ == Page::Debug ||
|
||||
(page_ == Page::Wifi && wifiPage_.live());
|
||||
if (live && nowMs - lastRefreshMs_ >= 500) {
|
||||
lastRefreshMs_ = nowMs;
|
||||
@@ -184,6 +191,7 @@ void SettingsApp::draw(Canvas& c) {
|
||||
case Page::About: widgets::textLines(c, aboutLines(), 0, area); break;
|
||||
case Page::Wifi: wifiPage_.draw(c); break;
|
||||
case Page::Firmware: firmwarePage_.draw(c); break;
|
||||
case Page::Debug: debugPage_.draw(c); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "services/battery_service.h"
|
||||
#include "services/clock_service.h"
|
||||
#include "services/storage_service.h"
|
||||
#include "apps/debug_console_page.h"
|
||||
#include "apps/firmware_page.h"
|
||||
#include "apps/wifi_settings_page.h"
|
||||
#include "settings_menu.h"
|
||||
@@ -32,24 +33,26 @@ struct SettingsAppDeps {
|
||||
UpdateService& update;
|
||||
};
|
||||
|
||||
// Settings: every user-facing setting, plus the Wi-Fi, Firmware and About pages.
|
||||
// Settings: every user-facing setting, plus the Wi-Fi, Firmware, Debug Console and About pages.
|
||||
class SettingsApp : public App {
|
||||
public:
|
||||
explicit SettingsApp(const SettingsAppDeps& deps)
|
||||
: d_(deps),
|
||||
menu_(deps.settings),
|
||||
wifiPage_(deps.settings, deps.savedNetworks, deps.wifi, deps.bus),
|
||||
firmwarePage_(deps.update, deps.wifi, deps.storage) {}
|
||||
firmwarePage_(deps.update, deps.wifi, deps.storage),
|
||||
debugPage_(deps.settings, deps.wifi) {}
|
||||
void onEnter() override;
|
||||
bool onKey(const KeyEvent& e) override;
|
||||
void update(uint32_t nowMs) override;
|
||||
bool textEntryActive() const override {
|
||||
return page_ == Page::Text || (page_ == Page::Wifi && wifiPage_.textEntryActive());
|
||||
return page_ == Page::Text || (page_ == Page::Wifi && wifiPage_.textEntryActive()) ||
|
||||
(page_ == Page::Debug && debugPage_.textEntryActive());
|
||||
}
|
||||
void draw(Canvas& c) override;
|
||||
|
||||
private:
|
||||
enum class Page { Menu, Text, Choice, About, Wifi, Firmware };
|
||||
enum class Page { Menu, Text, Choice, About, Wifi, Firmware, Debug };
|
||||
|
||||
bool onMenuKey(const KeyEvent& e);
|
||||
bool onTextKey(const KeyEvent& e);
|
||||
@@ -62,6 +65,7 @@ class SettingsApp : public App {
|
||||
SettingsMenu menu_;
|
||||
WifiSettingsPage wifiPage_;
|
||||
FirmwarePage firmwarePage_;
|
||||
DebugConsolePage debugPage_;
|
||||
Page page_ = Page::Menu;
|
||||
ListModel list_{theme::kContent.h / theme::kLineHeight};
|
||||
ListModel choices_{theme::kContent.h / theme::kLineHeight};
|
||||
|
||||
Reference in New Issue
Block a user