Fix Companion pairing PIN display and truthful identity startup
This commit is contained in:
1 parent
a2854f8f43
commit
202930de8f
31 files changed
+1915
-148
No files matched your search
@@ -10,6 +10,38 @@ namespace mesh {
|
||||
// unlikely while still allowing callers to fail closed.
|
||||
constexpr size_t MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS = 11;
|
||||
|
||||
// Startup-only cooperative progress hook. Radio entropy collection calls this
|
||||
// between bytes, so displays can animate without threads or changing entropy.
|
||||
struct IdentityGenerationProgress {
|
||||
void (*callback)(void*) = nullptr;
|
||||
void* context = nullptr;
|
||||
};
|
||||
|
||||
inline IdentityGenerationProgress& identityGenerationProgress() {
|
||||
static IdentityGenerationProgress progress;
|
||||
return progress;
|
||||
}
|
||||
|
||||
inline void serviceIdentityGenerationProgress() {
|
||||
const auto progress = identityGenerationProgress();
|
||||
if (progress.callback != nullptr) progress.callback(progress.context);
|
||||
}
|
||||
|
||||
class ScopedIdentityGenerationProgress {
|
||||
IdentityGenerationProgress _previous;
|
||||
public:
|
||||
ScopedIdentityGenerationProgress(void (*callback)(void*), void* context)
|
||||
: _previous(identityGenerationProgress()) {
|
||||
identityGenerationProgress().callback = callback;
|
||||
identityGenerationProgress().context = context;
|
||||
}
|
||||
~ScopedIdentityGenerationProgress() {
|
||||
identityGenerationProgress() = _previous;
|
||||
}
|
||||
ScopedIdentityGenerationProgress(const ScopedIdentityGenerationProgress&) = delete;
|
||||
ScopedIdentityGenerationProgress& operator=(const ScopedIdentityGenerationProgress&) = delete;
|
||||
};
|
||||
|
||||
inline bool hasReservedIdentityPrefix(const Identity& identity) {
|
||||
return identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF;
|
||||
}
|
||||
@@ -19,7 +51,9 @@ bool generateUsableLocalIdentity(LocalIdentity& identity, Generator generator) {
|
||||
for (size_t attempt = 0;
|
||||
attempt < MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS;
|
||||
++attempt) {
|
||||
serviceIdentityGenerationProgress();
|
||||
identity = generator();
|
||||
serviceIdentityGenerationProgress();
|
||||
if (!hasReservedIdentityPrefix(identity)) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -396,7 +396,11 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
|
||||
BLEDevice::setMTU(MAX_FRAME_SIZE + 3);
|
||||
|
||||
#if defined(MESH_USE_NIMBLE_ARDUINO)
|
||||
BLEDevice::setSecurityPasskey(pin_code);
|
||||
// NimBLE-Arduino 2.5.1 bypasses onPassKeyDisplay() for a non-default
|
||||
// static passkey. Keep its callback sentinel here: our callback returns
|
||||
// the actual random/saved _pin_code and also wakes the pairing display.
|
||||
// This does not change the PIN used on air or weaken authentication.
|
||||
BLEDevice::setSecurityPasskey(123456);
|
||||
BLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
|
||||
BLEDevice::setSecurityAuth(true, true, true);
|
||||
#else
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <Mesh.h>
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
#include "CadTiming.h"
|
||||
#include "RadioAirtime.h"
|
||||
#include "RXPowerSaving.h"
|
||||
@@ -389,6 +390,7 @@ public:
|
||||
// hardware entropy is mixed in without becoming the sole source.
|
||||
for (size_t i = 0; i < sz; i++) {
|
||||
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
|
||||
mesh::serviceIdentityGenerationProgress();
|
||||
}
|
||||
#ifdef USE_CC310_HW_CRYPTO
|
||||
mesh::mixCC310Random(dest, sz);
|
||||
|
||||
@@ -41,6 +41,10 @@ public:
|
||||
bool begin();
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
#if defined(COMPANION_PAIRING_UI_HIL)
|
||||
// Test-only access to the same pixels sent by endFrame(), never a mock UI.
|
||||
const uint8_t* hilFramebuffer() { return display.getBuffer(); }
|
||||
#endif
|
||||
bool supportsRotation() const override { return true; }
|
||||
bool setRotationDegrees(uint16_t degrees) override;
|
||||
void setFlipped(bool flipped) override;
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(COMPANION_PAIRING_UI_HIL)
|
||||
void companionStartupUiHilFrame(const char* status);
|
||||
#endif
|
||||
|
||||
namespace mesh { namespace ui {
|
||||
|
||||
// Used before UITask exists. Do not initialize the normal power policy here:
|
||||
// its boot timeout must start when the real UI is ready, not during keygen.
|
||||
class StartupScreen {
|
||||
DisplayDriver* _display = nullptr;
|
||||
bool _generating = false;
|
||||
const char* _status = "Starting...";
|
||||
uint8_t _phase = 0;
|
||||
uint32_t _last_frame = 0;
|
||||
|
||||
void draw() {
|
||||
if (_display == nullptr) return;
|
||||
DisplayDriver& display = *_display;
|
||||
display.startFrame();
|
||||
display.setTextSize(1);
|
||||
display.setCompactText(false);
|
||||
display.setColor(UIColor::primary_txt);
|
||||
const int line = display.textLineHeight();
|
||||
const char* first = _status;
|
||||
const char* second = nullptr;
|
||||
if (display.getTextWidth(first) > display.width()) {
|
||||
first = _generating ? "Generating" :
|
||||
strcmp(_status, "Loading identity") == 0 ? "Loading" :
|
||||
strcmp(_status, "Saving identity") == 0 ? "Saving" : "Starting";
|
||||
if (_generating) second = "key";
|
||||
else if (strcmp(_status, "Starting...") != 0) second = "identity";
|
||||
if (display.getTextWidth(first) > display.width()) {
|
||||
first = _generating ? "Key" :
|
||||
strcmp(_status, "Loading identity") == 0 ? "Load" :
|
||||
strcmp(_status, "Saving identity") == 0 ? "Save" : "Boot";
|
||||
if (_generating) second = "gen";
|
||||
else if (second != nullptr && display.getTextWidth(second) > display.width())
|
||||
second = "ID";
|
||||
}
|
||||
}
|
||||
const int spinner_size = 12;
|
||||
const int text_height = line * (second == nullptr ? 1 : 2);
|
||||
const int total_height = text_height + (_generating ? spinner_size + 2 : 0);
|
||||
int y = (display.height() - total_height) / 2;
|
||||
if (y < 0) y = 0;
|
||||
display.drawTextCentered(display.width() / 2, y, first);
|
||||
if (second != nullptr)
|
||||
display.drawTextCentered(display.width() / 2, y + line, second);
|
||||
if (_generating) {
|
||||
// A rotating bright pair on an eight-dot ring works on monochrome and
|
||||
// color panels, without depending on a font's spinner characters.
|
||||
static const uint8_t dots[8][2] = {
|
||||
{5, 0}, {9, 1}, {10, 5}, {9, 9},
|
||||
{5, 10}, {1, 9}, {0, 5}, {1, 1}};
|
||||
const int x = (display.width() - spinner_size) / 2;
|
||||
const int top = y + text_height + 2;
|
||||
if (x >= 0 && top + spinner_size <= display.height()) {
|
||||
for (uint8_t i = 0; i < 2; ++i) {
|
||||
const uint8_t index = (_phase + i) & 7;
|
||||
display.fillRect(x + dots[index][0], top + dots[index][1], 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
display.endFrame();
|
||||
#if defined(COMPANION_PAIRING_UI_HIL)
|
||||
companionStartupUiHilFrame(_status);
|
||||
#endif
|
||||
_last_frame = millis();
|
||||
}
|
||||
|
||||
public:
|
||||
void begin(DisplayDriver* display, bool usb_power) {
|
||||
const auto& prefs = displayPowerPrefs();
|
||||
const auto mode = (usb_power ? prefs.usb : prefs.battery).mode;
|
||||
_display = display;
|
||||
if (mode == DisplayMode::Off || mode == DisplayMode::Pairing) {
|
||||
if (_display != nullptr && _display->isOn()) _display->turnOff();
|
||||
_display = nullptr;
|
||||
return;
|
||||
}
|
||||
if (_display == nullptr) return;
|
||||
if (!_display->isOn()) _display->turnOn();
|
||||
starting();
|
||||
}
|
||||
|
||||
void starting() {
|
||||
_generating = false;
|
||||
_status = "Starting...";
|
||||
draw();
|
||||
}
|
||||
|
||||
void loadingIdentity() {
|
||||
_generating = false;
|
||||
_status = "Loading identity";
|
||||
draw();
|
||||
}
|
||||
|
||||
void savingIdentity() {
|
||||
_generating = false;
|
||||
_status = "Saving identity";
|
||||
draw();
|
||||
}
|
||||
|
||||
void generatingKey() {
|
||||
_generating = true;
|
||||
_status = "Generating key";
|
||||
_phase = 0;
|
||||
draw();
|
||||
}
|
||||
|
||||
void service() {
|
||||
if (_display == nullptr || !_generating) return;
|
||||
// E-paper's partial refresh is slower and needs a gentler refresh cadence.
|
||||
const uint32_t interval = _display->isEink() ? 1000UL : 150UL;
|
||||
if (uint32_t(millis() - _last_frame) < interval) return;
|
||||
_phase = (_phase + 1) & 7;
|
||||
draw();
|
||||
}
|
||||
|
||||
static void progress(void* context) {
|
||||
if (context != nullptr) static_cast<StartupScreen*>(context)->service();
|
||||
}
|
||||
};
|
||||
|
||||
} }
|
||||
Reference in new issue
Block a user