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
|
||||
Reference in New Issue
Block a user