mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-13 09:35:34 +00:00
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
9.5 KiB
Python
199 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Static contracts for Companion Bluetooth identity configuration."""
|
|
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def source(relative: str) -> str:
|
|
return (ROOT / relative).read_text(encoding="utf-8")
|
|
|
|
|
|
class BluetoothMacContractTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.main = source("examples/companion_radio/main.cpp")
|
|
cls.mesh = source("examples/companion_radio/MyMesh.cpp")
|
|
cls.nrf_header = source("src/helpers/nrf52/SerialBLEInterface.h")
|
|
cls.nrf_source = source("src/helpers/nrf52/SerialBLEInterface.cpp")
|
|
cls.esp_header = source("src/helpers/esp32/SerialBLEInterface.h")
|
|
cls.esp_source = source("src/helpers/esp32/SerialBLEInterface.cpp")
|
|
|
|
def test_both_ble_backends_accept_the_same_configuration(self):
|
|
for header in (self.nrf_header, self.esp_header):
|
|
self.assertIn("const uint8_t* custom_address = nullptr", header)
|
|
self.assertIn("bool clear_bonds = false", header)
|
|
self.assertIn("bool stealth_pair_once = false", header)
|
|
self.assertIn("bonded_only_peer", header)
|
|
|
|
self.assertIn("the_mesh.getBLEPin(), bluetooth_address,", self.main)
|
|
self.assertIn("clear_bonds, stealth_pair_once,", self.main)
|
|
self.assertIn("bonded_only_peer_ptr))", self.main)
|
|
|
|
def test_nrf52_uses_random_static_softdevice_address(self):
|
|
self.assertIn("BLE_GAP_ADDR_TYPE_RANDOM_STATIC", self.nrf_source)
|
|
self.assertIn("sd_ble_gap_addr_set(&address)", self.nrf_source)
|
|
self.assertIn(
|
|
"mesh::companion::BLUETOOTH_MAC_BYTES - 1 - i",
|
|
self.nrf_source,
|
|
)
|
|
|
|
def test_esp32_supports_nimble_and_bluedroid(self):
|
|
self.assertIn("BLEDevice::setOwnAddr(native_address)", self.esp_source)
|
|
self.assertIn("BLEDevice::setOwnAddrType(BLE_OWN_ADDR_RANDOM)", self.esp_source)
|
|
self.assertIn("setDeviceAddress(", self.esp_source)
|
|
self.assertIn("BLE_ADDR_TYPE_RANDOM", self.esp_source)
|
|
|
|
def test_every_boot_mode_is_stable_across_start_retries(self):
|
|
self.assertIn(
|
|
"companion_bluetooth_session_address_ready", self.main
|
|
)
|
|
self.assertIn(
|
|
"BLUETOOTH_MAC_RANDOM_EVERY_BOOT", self.main
|
|
)
|
|
self.assertIn("clear_bonds = true;", self.main)
|
|
|
|
def test_cli_exposes_all_address_modes(self):
|
|
self.assertIn('"get bluetooth.mac"', self.mesh)
|
|
self.assertIn('"set bluetooth.mac"', self.mesh)
|
|
self.assertIn('strcmp(value, "random")', self.mesh)
|
|
self.assertIn('strcmp(value, "random-every-boot")', self.mesh)
|
|
self.assertIn('strcmp(value, "random everyboot")', self.mesh)
|
|
self.assertIn('strcmp(value, "random-after-connect")', self.mesh)
|
|
self.assertNotIn('strcmp(value, "stealth")', self.mesh)
|
|
self.assertIn('strcmp(value, "default")', self.mesh)
|
|
self.assertIn("parseBluetoothMac(custom_value, address)", self.mesh)
|
|
|
|
def test_stealth_is_a_separate_idempotent_flag(self):
|
|
self.assertIn('"get bluetooth.stealth"', self.mesh)
|
|
self.assertIn('"set bluetooth.stealth"', self.mesh)
|
|
self.assertIn('"get ble.stealth"', self.mesh)
|
|
self.assertIn('"set ble.stealth"', self.mesh)
|
|
setter = self.mesh[
|
|
self.mesh.index("bool MyMesh::applyAndSaveBluetoothStealth("):
|
|
self.mesh.index("void MyMesh::formatBluetoothStealthStatus(")
|
|
]
|
|
self.assertIn('strcmp(value, "on")', setter)
|
|
self.assertIn('strcmp(value, "off")', setter)
|
|
self.assertIn("setCompanionBluetoothStealth(_prefs,", setter)
|
|
self.assertIn("&& !savePrefs()", setter)
|
|
self.assertIn("_prefs.bluetooth_stealth_mode = previous_mode", setter)
|
|
self.assertNotIn("saveBluetoothMac(", setter)
|
|
self.assertNotIn("_prefs.bluetooth_mac_mode =", setter)
|
|
self.assertNotIn("getRNG()", setter)
|
|
self.assertNotIn("|stealth|", self.mesh)
|
|
|
|
def test_rotation_and_stealth_share_the_authenticated_event(self):
|
|
self.assertIn("companion_bluetooth_session_stealth_mode", self.main)
|
|
self.assertIn("prefs->bluetooth_stealth_mode", self.main)
|
|
self.assertIn("bluetoothMacPoliciesMatch(", self.main)
|
|
start = self.mesh.index("bool MyMesh::prepareBluetoothMacForBoot(")
|
|
end = self.mesh.index("bool MyMesh::armBluetoothMacRotationAfterConnection(")
|
|
self.assertIn("BLUETOOTH_MAC_RANDOM_EVERY_BOOT", self.mesh[start:end])
|
|
self.assertIn("resetBluetoothStealthPairing()", self.mesh[start:end])
|
|
start = self.mesh.index("bool MyMesh::saveBluetoothStealthPeer(")
|
|
end = self.mesh.index("bool MyMesh::resetBluetoothStealthPairing()")
|
|
save_peer = self.mesh[start:end]
|
|
self.assertLess(save_peer.index("BLUETOOTH_MAC_RANDOM_AFTER_CONNECT_ARMED"),
|
|
save_peer.index("if (savePrefs())"))
|
|
self.assertIn("_prefs.bluetooth_mac_mode = previous_mode", save_peer)
|
|
start = self.mesh.index("bool MyMesh::saveBluetoothMac(")
|
|
end = self.mesh.index("bool MyMesh::applyAndSaveBluetoothMac(")
|
|
self.assertIn("clearCompanionBluetoothStealthPeer(_prefs)", self.mesh[start:end])
|
|
self.assertIn("_prefs.bluetooth_stealth_mode = previous_stealth_mode",
|
|
self.mesh[start:end])
|
|
|
|
def test_web_snapshot_and_dispatch_keep_flag_separate(self):
|
|
self.assertIn('strcmp(key, "bluetooth.stealth")', self.mesh)
|
|
self.assertIn("s.bluetooth_stealth =", self.mesh)
|
|
self.assertNotIn('sizeof(s.bluetooth_mac), "stealth"', self.mesh)
|
|
self.assertIn("CAP_BLUETOOTH_STEALTH", self.mesh)
|
|
|
|
def test_random_after_connect_arms_after_authentication(self):
|
|
self.assertIn("takeSuccessfulConnection(", self.nrf_header)
|
|
self.assertIn("takeSuccessfulConnection(", self.esp_header)
|
|
self.assertIn(
|
|
"_successfulConnectionPending.store(", self.nrf_source
|
|
)
|
|
self.assertIn(
|
|
"_successfulConnectionPending.store(", self.esp_source
|
|
)
|
|
self.assertIn("prepareBluetoothMacForBoot", self.main)
|
|
self.assertIn("armBluetoothMacRotationAfterConnection", self.main)
|
|
self.assertIn("bluetoothMacModeIsRandomAfterConnect", self.main)
|
|
self.assertIn(
|
|
"companion_bluetooth_clear_bonds_this_boot", self.main
|
|
)
|
|
|
|
def test_stealth_switches_to_bonded_peer_only_advertising(self):
|
|
for header in (self.nrf_header, self.esp_header):
|
|
self.assertIn("enableBondedOnlyAdvertising", header)
|
|
self.assertIn("takeBondedOnlyRecovery", header)
|
|
self.assertIn("cancelStealthPairingTransition", header)
|
|
|
|
self.assertIn(
|
|
"BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED",
|
|
self.nrf_source,
|
|
)
|
|
self.assertIn("Bluefruit.Advertising.clearData()", self.nrf_source)
|
|
self.assertIn("Bluefruit.ScanResponse.clearData()", self.nrf_source)
|
|
self.assertIn("setPeerAddress(native_peer)", self.nrf_source)
|
|
self.assertIn("sd_ble_gap_device_identities_set(", self.nrf_source)
|
|
self.assertIn("serviceBondedOnlyTransition()", self.nrf_source)
|
|
self.assertIn("_bonded_only_configure_pending", self.nrf_source)
|
|
self.assertIn("ble_gap_wl_set(&native_peer, 1)", self.esp_source)
|
|
self.assertIn("esp_ble_gap_clear_whitelist()", self.esp_source)
|
|
self.assertIn("esp_ble_gap_update_whitelist", self.esp_source)
|
|
self.assertIn("advertising->setScanFilter(true, true)", self.esp_source)
|
|
self.assertIn("advertising->setScanResponse(false)", self.esp_source)
|
|
self.assertIn("saveBluetoothStealthPeer", self.main)
|
|
|
|
def test_stealth_holds_general_advertising_during_transition(self):
|
|
for source_text in (self.nrf_source, self.esp_source):
|
|
self.assertIn("_advertisingSuppressed.store(", source_text)
|
|
self.assertIn("advertisingAllowed()", source_text)
|
|
callback_at = source_text.index(
|
|
"void SerialBLEInterface::noteSuccessfulConnection"
|
|
)
|
|
callback_end = source_text.index("\n}", callback_at)
|
|
callback = source_text[callback_at:callback_end]
|
|
self.assertLess(
|
|
callback.index("_advertisingSuppressed.store("),
|
|
callback.index("_successfulConnectionPending.store("),
|
|
)
|
|
self.assertIn("restartOnDisconnect(false)", self.nrf_source)
|
|
self.assertIn(
|
|
"_conn_handle != BLE_CONN_HANDLE_INVALID",
|
|
self.nrf_source[
|
|
self.nrf_source.index(
|
|
"bool SerialBLEInterface::enableBondedOnlyAdvertising"
|
|
) : self.nrf_source.index(
|
|
"void SerialBLEInterface::serviceBondedOnlyTransition"
|
|
)
|
|
],
|
|
)
|
|
pairing_callback = self.nrf_source.index(
|
|
"void SerialBLEInterface::onPairingComplete"
|
|
)
|
|
pairing_success = self.nrf_source.index(
|
|
"pairing successful", pairing_callback
|
|
)
|
|
next_callback = self.nrf_source.index(
|
|
"void SerialBLEInterface::onBLEEvent", pairing_success
|
|
)
|
|
self.assertIn(
|
|
"noteSuccessfulConnection(conn->getPeerAddr())",
|
|
self.nrf_source[pairing_success:next_callback],
|
|
)
|
|
self.assertIn("resetBluetoothStealthPairing", self.main)
|
|
for source_text in (self.nrf_source, self.esp_source):
|
|
self.assertIn("BLE_BOND_PERSIST_TIMEOUT_MS", source_text)
|
|
self.assertIn("new peer bond did not persist", source_text)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|