Fix Companion pairing PIN display and truthful identity startup

This commit is contained in:
mikecarper committed 2026-10-02 12:42:26 -07:00
1 parent a2854f8f43
commit 202930de8f
31 files changed
+1915 -148

No files matched your search

+34
View File
@@ -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;
+5 -1
View File
@@ -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
+2
View File
@@ -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);
+4
View File
@@ -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;
+130
View File
@@ -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();
}
};
} }