mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 14:54:44 -05:00
Pair QR: address review feedback
- nRF52: zero-pad the passkey to six digits for setPIN and the pairing alert. A fixed PIN below 100000 was rejected by setPIN, so the device ran without it while the QR advertised the padded value. - Rebuild frames when the input-broker toggle turns Bluetooth off without a reboot (non-nRF52), so the Pair frame goes away. - Draw the spec's 4-module quiet zone, falling back to 2 modules only when 4 won't fit at one pixel per module. - test_qr_encode: add a reference symbol for the 62-character no-PIN link (version 4, at capacity) and scope the header comment to the symbols the file checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d74892ddf
commit
08f47e6f98
4 files changed
+59
-21
No files matched your search
@@ -701,11 +701,16 @@ void drawFrameBluetoothPair(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
||||
return;
|
||||
}
|
||||
|
||||
// 2-module quiet zone; largest whole-pixel scale that fits below the header
|
||||
const int span = qr.size + 4;
|
||||
// Largest whole-pixel scale that fits below the header, with the spec's 4-module quiet zone,
|
||||
// or 2 modules (which phones still read) when 4 won't fit at one pixel per module
|
||||
const int availH = display->getHeight() - (top - y);
|
||||
const int availW = display->getWidth();
|
||||
int scale = (availH < availW ? availH : availW) / span;
|
||||
const int avail = availH < availW ? availH : availW;
|
||||
int quiet = 4;
|
||||
if (avail < qr.size + 2 * quiet)
|
||||
quiet = 2;
|
||||
const int span = qr.size + 2 * quiet;
|
||||
const int scale = avail / span;
|
||||
if (scale < 1) {
|
||||
display->drawString(x + BASEUI_BODY_LR_MARGIN, top, "Screen too small");
|
||||
return;
|
||||
@@ -720,7 +725,7 @@ void drawFrameBluetoothPair(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
||||
for (uint8_t my = 0; my < qr.size; my++)
|
||||
for (uint8_t mx = 0; mx < qr.size; mx++)
|
||||
if (qrGetModule(&qr, mx, my))
|
||||
display->fillRect(qrX + (mx + 2) * scale, qrY + (my + 2) * scale, scale, scale);
|
||||
display->fillRect(qrX + (mx + quiet) * scale, qrY + (my + quiet) * scale, scale, scale);
|
||||
display->setColor(WHITE);
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
// Keep dark-on-light whatever the theme, so phone scanners read it
|
||||
|
||||
@@ -69,6 +69,7 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
|
||||
if (!config.bluetooth.enabled) {
|
||||
disableBluetooth();
|
||||
IF_SCREEN(screen->showSimpleBanner("Bluetooth OFF", 3000));
|
||||
IF_SCREEN(screen->setFrames(graphics::Screen::FOCUS_PRESERVE)); // Drop the Pair frame
|
||||
} else {
|
||||
IF_SCREEN(screen->showSimpleBanner("Bluetooth ON\nRebooting", 3000));
|
||||
rebootAtMsec = Time::timerEndsAtMillis(DEFAULT_REBOOT_SECONDS * 1000);
|
||||
|
||||
@@ -415,9 +415,11 @@ void NRF52Bluetooth::setup()
|
||||
configuredPasskey = hwrand % 900000u + 100000u;
|
||||
}
|
||||
passkeyConfigured = true;
|
||||
auto pinString = std::to_string(configuredPasskey);
|
||||
LOG_INFO("Bluetooth pin set to '%i'", configuredPasskey);
|
||||
Bluefruit.Security.setPIN(pinString.c_str());
|
||||
// setPIN wants exactly six digits, so keep a fixed PIN's leading zeros
|
||||
char pinString[12];
|
||||
snprintf(pinString, sizeof(pinString), "%06lu", (unsigned long)configuredPasskey);
|
||||
LOG_INFO("Bluetooth pin set to '%s'", pinString);
|
||||
Bluefruit.Security.setPIN(pinString);
|
||||
Bluefruit.Security.setIOCaps(true, false, false);
|
||||
Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey);
|
||||
Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted);
|
||||
@@ -542,7 +544,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
|
||||
|
||||
#if HAS_SCREEN && !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
if (screen) {
|
||||
std::string configuredPasskeyText = std::to_string(configuredPasskey);
|
||||
char pinText[12];
|
||||
snprintf(pinText, sizeof(pinText), "%06lu", (unsigned long)configuredPasskey);
|
||||
std::string configuredPasskeyText = pinText;
|
||||
std::string ble_message =
|
||||
"Bluetooth\nPIN\n[M]" + configuredPasskeyText.substr(0, 3) + " " + configuredPasskeyText.substr(3, 6);
|
||||
// Use the pairing_pin notification type so the lockdown UI short-
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
// only correct if every module matches what a full encoder produces for the same version and mask.
|
||||
//
|
||||
// Regressions guarded:
|
||||
// - any change to placement, interleaving, Reed-Solomon, masking or format bits that moves a module.
|
||||
// The reference matrix below is Project Nayuki's encoder output for the 73-character link (version 5,
|
||||
// mask 2, which both encoders choose), and it decodes with zxing;
|
||||
// - any change to placement, interleaving, Reed-Solomon, masking or format bits that moves a module in
|
||||
// the two symbols the frame shows. The reference matrices below are Project Nayuki's encoder output for
|
||||
// the 73-character link with a PIN (version 5, two blocks) and the 62-character link without one
|
||||
// (version 4, two blocks, filled to capacity), both mask 2 in both encoders, and both decode with zxing.
|
||||
// Versions 1-3 and 6 and other masks are not checked module by module;
|
||||
// - picking a larger version than needed (a coarser code on 128x64 panels) or a smaller one that the
|
||||
// data overflows, at each version's byte-mode capacity boundary;
|
||||
// - writing past the module buffer instead of rejecting data longer than version 6 holds.
|
||||
@@ -23,9 +25,9 @@
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
static const char *const kLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99&pin=317338";
|
||||
static const char *const kPinLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99&pin=317338";
|
||||
|
||||
static const char *const kLinkModules[] = {
|
||||
static const char *const kPinLinkModules[] = {
|
||||
"1111111000101001111011111001101111111", "1000001000101101010100000101101000001", "1011101010100110101111100011101011101",
|
||||
"1011101011000100110100111111101011101", "1011101011001001000111010000101011101", "1000001011100010001010000001101000001",
|
||||
"1111111010101010101010101010101111111", "0000000010001000100111110011100000000", "1011111001000011001110010101101111100",
|
||||
@@ -40,24 +42,49 @@ static const char *const kLinkModules[] = {
|
||||
"1011101011000001001101110000101111101", "1011101011001011100011101100100101111", "1000001000000111001101110011000101001",
|
||||
"1111111011011101010000100110000000111"};
|
||||
|
||||
static const char *const kNoPinLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99";
|
||||
|
||||
static const char *const kNoPinLinkModules[] = {
|
||||
"111111100110110010111001001111111", "100000100001100110000100001000001", "101110101100001110001101001011101",
|
||||
"101110101110010111100111001011101", "101110101000001001001001001011101", "100000101111110100100110001000001",
|
||||
"111111101010101010101010101111111", "000000001000101000011110000000000", "101111100010111001001011101111100",
|
||||
"010111011001011011111101111001101", "001000101010100101101100010011110", "001110001000001010110111000101111",
|
||||
"111010100111000010001111010111011", "111111001010110000110101111001001", "110001111000100111100110010100010",
|
||||
"101000000001001100010111100001110", "100100100110110101111010110011011", "001000001000110010111111101000101",
|
||||
"000001100100001101101000100001010", "000011000001110010000111101101110", "110010110101111000001001010110001",
|
||||
"101100011100110111110111001000101", "100110101110100111001000001001010", "101111010010011110011111110100101",
|
||||
"101110111001111101000010111111011", "000000001011110010111001100010001", "111111100101101110000001101010110",
|
||||
"100000101111110010101100100011100", "101110101011110011101000111111010", "101110101001011000010011010111011",
|
||||
"101110101110010101100100101001000", "100000100111100000010101010101100", "111111101000100001001011110000010"};
|
||||
|
||||
static uint8_t modules[QR_ENCODE_BUFFER_SIZE(QR_ENCODE_MAX_VERSION)];
|
||||
|
||||
static void test_connect_link_matches_reference_symbol()
|
||||
static void assertMatchesReference(const char *link, uint8_t version, uint8_t mask, const char *const *reference)
|
||||
{
|
||||
QrEncode qr;
|
||||
TEST_ASSERT_TRUE(qrEncodeText(&qr, modules, kLink));
|
||||
TEST_ASSERT_EQUAL_UINT8(5, qr.version);
|
||||
TEST_ASSERT_EQUAL_UINT8(37, qr.size);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, qr.mask);
|
||||
TEST_ASSERT_TRUE(qrEncodeText(&qr, modules, link));
|
||||
TEST_ASSERT_EQUAL_UINT8(version, qr.version);
|
||||
TEST_ASSERT_EQUAL_UINT8(QR_ENCODE_SIZE(version), qr.size);
|
||||
TEST_ASSERT_EQUAL_UINT8(mask, qr.mask);
|
||||
for (uint8_t y = 0; y < qr.size; y++) {
|
||||
char row[38];
|
||||
char row[QR_ENCODE_SIZE(QR_ENCODE_MAX_VERSION) + 1];
|
||||
for (uint8_t x = 0; x < qr.size; x++)
|
||||
row[x] = qrGetModule(&qr, x, y) ? '1' : '0';
|
||||
row[qr.size] = '\0';
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(kLinkModules[y], row, "module row differs from reference");
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(reference[y], row, "module row differs from reference");
|
||||
}
|
||||
}
|
||||
|
||||
static void test_pin_link_matches_reference_symbol()
|
||||
{
|
||||
assertMatchesReference(kPinLink, 5, 2, kPinLinkModules);
|
||||
}
|
||||
|
||||
static void test_no_pin_link_matches_reference_symbol()
|
||||
{
|
||||
assertMatchesReference(kNoPinLink, 4, 2, kNoPinLinkModules);
|
||||
}
|
||||
|
||||
// Byte-mode capacity at ECC M: 14, 26, 42, 62, 84, 106 characters for versions 1-6.
|
||||
static void test_smallest_version_is_chosen_at_each_capacity_boundary()
|
||||
{
|
||||
@@ -96,7 +123,8 @@ void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_connect_link_matches_reference_symbol);
|
||||
RUN_TEST(test_pin_link_matches_reference_symbol);
|
||||
RUN_TEST(test_no_pin_link_matches_reference_symbol);
|
||||
RUN_TEST(test_smallest_version_is_chosen_at_each_capacity_boundary);
|
||||
RUN_TEST(test_data_beyond_version_6_is_rejected);
|
||||
RUN_TEST(test_getModule_outside_symbol_is_light);
|
||||
|
||||
Reference in new issue
Block a user