Keep stealth separate from custom, saved-random and rotating MAC policies without migrating the development MAC mode. Persist authenticated peer state, guard advertising transitions and recovery, and expose the setting through the CLI and WebConfig. Add regression and CI checks and record exact XIAO BLE pairing, reconnect, reboot and address-rotation hardware results.
199 lines
6.2 KiB
C++
199 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
namespace mesh {
|
|
namespace companion {
|
|
|
|
static const size_t BLUETOOTH_MAC_BYTES = 6;
|
|
static const size_t BLUETOOTH_MAC_TEXT_SIZE = 18;
|
|
|
|
enum BluetoothMacMode : uint8_t {
|
|
BLUETOOTH_MAC_DEFAULT = 0,
|
|
BLUETOOTH_MAC_CUSTOM = 1,
|
|
BLUETOOTH_MAC_RANDOM_SAVED = 2,
|
|
BLUETOOTH_MAC_RANDOM_EVERY_BOOT = 3,
|
|
BLUETOOTH_MAC_RANDOM_AFTER_CONNECT = 4,
|
|
// Internal persisted state: the current identity completed an authenticated
|
|
// connection and must rotate before it advertises next boot.
|
|
BLUETOOTH_MAC_RANDOM_AFTER_CONNECT_ARMED = 5,
|
|
};
|
|
|
|
inline bool isValidBluetoothMacMode(uint8_t mode) {
|
|
return mode <= BLUETOOTH_MAC_RANDOM_AFTER_CONNECT_ARMED;
|
|
}
|
|
|
|
inline bool bluetoothMacModeIsRandomAfterConnect(uint8_t mode) {
|
|
return mode == BLUETOOTH_MAC_RANDOM_AFTER_CONNECT
|
|
|| mode == BLUETOOTH_MAC_RANDOM_AFTER_CONNECT_ARMED;
|
|
}
|
|
|
|
enum BluetoothStealthMode : uint8_t {
|
|
BLUETOOTH_STEALTH_OFF = 0,
|
|
BLUETOOTH_STEALTH_PAIRING = 1,
|
|
BLUETOOTH_STEALTH_PAIRED = 2,
|
|
};
|
|
|
|
inline bool isValidBluetoothStealthMode(uint8_t mode) {
|
|
return mode <= BLUETOOTH_STEALTH_PAIRED;
|
|
}
|
|
|
|
inline bool bluetoothStealthEnabled(uint8_t mode) {
|
|
return mode == BLUETOOTH_STEALTH_PAIRING
|
|
|| mode == BLUETOOTH_STEALTH_PAIRED;
|
|
}
|
|
|
|
inline bool bluetoothMacPoliciesMatch(uint8_t first, uint8_t second) {
|
|
return first == second
|
|
|| (bluetoothMacModeIsRandomAfterConnect(first)
|
|
&& bluetoothMacModeIsRandomAfterConnect(second));
|
|
}
|
|
|
|
inline bool bluetoothMacModeUsesSavedAddress(uint8_t mode) {
|
|
return mode == BLUETOOTH_MAC_CUSTOM
|
|
|| mode == BLUETOOTH_MAC_RANDOM_SAVED
|
|
|| bluetoothMacModeIsRandomAfterConnect(mode);
|
|
}
|
|
|
|
inline bool hasCustomBluetoothMac(
|
|
const uint8_t address[BLUETOOTH_MAC_BYTES]) {
|
|
if (address == NULL) return false;
|
|
for (size_t i = 0; i < BLUETOOTH_MAC_BYTES; i++) {
|
|
if (address[i] != 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// User-selected BLE identities are random-static addresses, not IEEE-assigned
|
|
// public addresses. The two most-significant bits must be one, while the
|
|
// remaining 46 bits may not be all zero or all one.
|
|
inline bool isValidBluetoothMac(
|
|
const uint8_t address[BLUETOOTH_MAC_BYTES]) {
|
|
if (address == NULL || (address[0] & 0xC0u) != 0xC0u) return false;
|
|
|
|
bool payload_all_zero = (address[0] & 0x3Fu) == 0;
|
|
bool payload_all_one = (address[0] & 0x3Fu) == 0x3Fu;
|
|
for (size_t i = 1; i < BLUETOOTH_MAC_BYTES; i++) {
|
|
payload_all_zero = payload_all_zero && address[i] == 0;
|
|
payload_all_one = payload_all_one && address[i] == 0xFFu;
|
|
}
|
|
return !payload_all_zero && !payload_all_one;
|
|
}
|
|
|
|
inline bool bluetoothMacShouldRotateAtBoot(
|
|
uint8_t mode, const uint8_t address[BLUETOOTH_MAC_BYTES]) {
|
|
return bluetoothMacModeIsRandomAfterConnect(mode)
|
|
&& (mode == BLUETOOTH_MAC_RANDOM_AFTER_CONNECT_ARMED
|
|
|| !isValidBluetoothMac(address));
|
|
}
|
|
|
|
enum BluetoothPeerAddressType : uint8_t {
|
|
BLUETOOTH_PEER_ADDRESS_NONE = 0,
|
|
BLUETOOTH_PEER_ADDRESS_PUBLIC = 1,
|
|
BLUETOOTH_PEER_ADDRESS_RANDOM = 2,
|
|
};
|
|
|
|
struct BluetoothPeerIdentity {
|
|
uint8_t type = BLUETOOTH_PEER_ADDRESS_NONE;
|
|
uint8_t address[BLUETOOTH_MAC_BYTES] = {};
|
|
};
|
|
|
|
inline bool isValidBluetoothPeerIdentity(
|
|
const BluetoothPeerIdentity& peer) {
|
|
if (peer.type != BLUETOOTH_PEER_ADDRESS_PUBLIC
|
|
&& peer.type != BLUETOOTH_PEER_ADDRESS_RANDOM) {
|
|
return false;
|
|
}
|
|
|
|
bool all_zero = true;
|
|
bool all_one = true;
|
|
for (size_t i = 0; i < BLUETOOTH_MAC_BYTES; i++) {
|
|
all_zero = all_zero && peer.address[i] == 0;
|
|
all_one = all_one && peer.address[i] == 0xFFu;
|
|
}
|
|
return !all_zero && !all_one;
|
|
}
|
|
|
|
// Convert six random bytes into a valid BLE random-static identity. This is
|
|
// deterministic for a given input so both saved-random and per-boot modes can
|
|
// use the platform's existing entropy source without platform-specific rules.
|
|
inline void makeRandomStaticBluetoothMac(
|
|
uint8_t address[BLUETOOTH_MAC_BYTES]) {
|
|
if (address == NULL) return;
|
|
address[0] |= 0xC0u;
|
|
if (!isValidBluetoothMac(address)) address[5] ^= 0x01u;
|
|
}
|
|
|
|
inline void makeRandomStaticBluetoothMacDifferentFrom(
|
|
uint8_t address[BLUETOOTH_MAC_BYTES],
|
|
const uint8_t previous[BLUETOOTH_MAC_BYTES]) {
|
|
makeRandomStaticBluetoothMac(address);
|
|
if (address == NULL || previous == NULL
|
|
|| memcmp(address, previous, BLUETOOTH_MAC_BYTES) != 0) {
|
|
return;
|
|
}
|
|
|
|
address[5] ^= 0x01u;
|
|
if (!isValidBluetoothMac(address)) {
|
|
// The old payload was one bit away from all-zero or all-one. Restore that
|
|
// bit and flip a second payload bit so the result is valid and different.
|
|
address[5] ^= 0x01u;
|
|
address[4] ^= 0x01u;
|
|
}
|
|
}
|
|
|
|
inline int bluetoothMacHexDigit(char value) {
|
|
if (value >= '0' && value <= '9') return value - '0';
|
|
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
|
|
if (value >= 'A' && value <= 'F') return value - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
inline bool parseBluetoothMac(
|
|
const char* text, uint8_t output[BLUETOOTH_MAC_BYTES]) {
|
|
if (text == NULL || output == NULL || strlen(text) != 17) return false;
|
|
const char separator = text[2];
|
|
if (separator != ':' && separator != '-') return false;
|
|
|
|
uint8_t parsed[BLUETOOTH_MAC_BYTES];
|
|
for (size_t i = 0; i < BLUETOOTH_MAC_BYTES; i++) {
|
|
const size_t offset = i * 3;
|
|
const int high = bluetoothMacHexDigit(text[offset]);
|
|
const int low = bluetoothMacHexDigit(text[offset + 1]);
|
|
if (high < 0 || low < 0
|
|
|| (i + 1 < BLUETOOTH_MAC_BYTES
|
|
&& text[offset + 2] != separator)) {
|
|
return false;
|
|
}
|
|
parsed[i] = static_cast<uint8_t>((high << 4) | low);
|
|
}
|
|
if (!isValidBluetoothMac(parsed)) return false;
|
|
memcpy(output, parsed, sizeof(parsed));
|
|
return true;
|
|
}
|
|
|
|
inline bool formatBluetoothMac(
|
|
const uint8_t address[BLUETOOTH_MAC_BYTES], char* output,
|
|
size_t output_size) {
|
|
if (address == NULL || output == NULL
|
|
|| output_size < BLUETOOTH_MAC_TEXT_SIZE) {
|
|
if (output != NULL && output_size != 0) output[0] = 0;
|
|
return false;
|
|
}
|
|
|
|
static const char HEX_DIGITS[] = "0123456789ABCDEF";
|
|
size_t cursor = 0;
|
|
for (size_t i = 0; i < BLUETOOTH_MAC_BYTES; i++) {
|
|
output[cursor++] = HEX_DIGITS[address[i] >> 4];
|
|
output[cursor++] = HEX_DIGITS[address[i] & 0x0Fu];
|
|
if (i + 1 < BLUETOOTH_MAC_BYTES) output[cursor++] = ':';
|
|
}
|
|
output[cursor] = 0;
|
|
return true;
|
|
}
|
|
|
|
} // namespace companion
|
|
} // namespace mesh
|