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.
169 lines
6.3 KiB
C++
169 lines
6.3 KiB
C++
#pragma once
|
|
|
|
#include "../BaseSerialInterface.h"
|
|
#include "../BluetoothMac.h"
|
|
#include "../BleTxStallWatchdog.h"
|
|
#include "../UsbLogging.h"
|
|
#include "SecuritySessionTimer.h"
|
|
#include <bluefruit.h>
|
|
#include <atomic>
|
|
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
#include "../BleMotaProtocol.h"
|
|
#include "../BleMotaStream.h"
|
|
#endif
|
|
|
|
#ifndef BLE_TX_POWER
|
|
#define BLE_TX_POWER 4
|
|
#endif
|
|
|
|
class SerialBLEInterface : public BaseSerialInterface {
|
|
BLEDfu bledfu;
|
|
BLEUart bleuart;
|
|
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
BLEService _mota_service = BLEService(mesh::ota::BLE_MOTA_SERVICE_UUID);
|
|
BLECharacteristic _mota_request =
|
|
BLECharacteristic(mesh::ota::BLE_MOTA_REQUEST_UUID);
|
|
BLECharacteristic _mota_response =
|
|
BLECharacteristic(mesh::ota::BLE_MOTA_RESPONSE_UUID);
|
|
mesh::ota::BleMotaStream _mota_stream;
|
|
#endif
|
|
bool _isEnabled;
|
|
bool _isDeviceConnected;
|
|
uint16_t _conn_handle;
|
|
unsigned long _last_health_check;
|
|
unsigned long _last_retry_attempt;
|
|
ble_gap_addr_t _peer_address = {};
|
|
bool _peer_address_valid;
|
|
bool _bond_removed_for_connection;
|
|
ble_gap_addr_t _successful_peer_address = {};
|
|
bool _stealth_pair_once;
|
|
bool _bonded_only;
|
|
bool _bonded_only_configure_pending;
|
|
mesh::companion::BluetoothPeerIdentity _pending_bonded_peer;
|
|
std::atomic<bool> _advertisingSuppressed{false};
|
|
std::atomic<bool> _bondedOnlyRecoveryPending{false};
|
|
std::atomic<bool> _pairingRequestPending{false};
|
|
std::atomic<bool> _successfulConnectionPending{false};
|
|
std::atomic<uint32_t> _successfulConnectionStarted{0};
|
|
SecuritySessionTimer _security_timer;
|
|
mesh::BleTxStallWatchdog _tx_stall_watchdog;
|
|
mesh::BleDisconnectRecovery _tx_disconnect_recovery;
|
|
|
|
struct Frame {
|
|
uint8_t len;
|
|
uint8_t buf[MAX_FRAME_SIZE];
|
|
};
|
|
|
|
#define FRAME_QUEUE_SIZE 12
|
|
|
|
uint8_t send_queue_len;
|
|
Frame send_queue[FRAME_QUEUE_SIZE];
|
|
|
|
uint8_t recv_queue_len;
|
|
Frame recv_queue[FRAME_QUEUE_SIZE];
|
|
|
|
void clearBuffers();
|
|
void shiftSendQueueLeft();
|
|
void shiftRecvQueueLeft();
|
|
size_t writeBleUartFrame(const Frame& frame);
|
|
void recoverStalledTx(const char* cause);
|
|
void serviceTxRecovery(uint32_t now);
|
|
bool removeStoredBondForPeer(const char* cause);
|
|
void noteSuccessfulConnection(const ble_gap_addr_t& peer_address);
|
|
bool resolveSuccessfulPeer(
|
|
mesh::companion::BluetoothPeerIdentity& peer) const;
|
|
bool configureBondedOnlyAdvertising(
|
|
const mesh::companion::BluetoothPeerIdentity& peer,
|
|
bool require_stored_bond);
|
|
void serviceBondedOnlyTransition();
|
|
void requestBondedOnlyRecovery(const char* cause);
|
|
bool advertisingAllowed() const;
|
|
bool startAdvertising(const char* failure_cause);
|
|
bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const;
|
|
bool isAdvertising() const;
|
|
static void onConnect(uint16_t connection_handle);
|
|
static void onDisconnect(uint16_t connection_handle, uint8_t reason);
|
|
static void onSecured(uint16_t connection_handle);
|
|
static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request);
|
|
static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status);
|
|
static void onBLEEvent(ble_evt_t* evt);
|
|
static void onBleUartRX(uint16_t conn_handle);
|
|
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
static void onMotaResponse(uint16_t conn_handle,
|
|
BLECharacteristic* characteristic,
|
|
uint8_t* data, uint16_t length);
|
|
static size_t sendMotaRequest(void* context, const uint8_t* data,
|
|
size_t length);
|
|
#endif
|
|
|
|
public:
|
|
SerialBLEInterface() {
|
|
_isEnabled = false;
|
|
_isDeviceConnected = false;
|
|
_conn_handle = BLE_CONN_HANDLE_INVALID;
|
|
_last_health_check = 0;
|
|
_last_retry_attempt = 0;
|
|
_peer_address_valid = false;
|
|
_bond_removed_for_connection = false;
|
|
_stealth_pair_once = false;
|
|
_bonded_only = false;
|
|
_bonded_only_configure_pending = false;
|
|
send_queue_len = 0;
|
|
recv_queue_len = 0;
|
|
}
|
|
|
|
/**
|
|
* init the BLE interface.
|
|
* @param prefix a prefix for the device name
|
|
* @param name a name for the device (combined with prefix); "@@MAC" uses the hardware address
|
|
* @param pin_code the BLE security pin
|
|
* @param custom_address optional human-order BLE random-static address
|
|
* @param clear_bonds clear saved peer bonds before accepting connections
|
|
* @param stealth_pair_once suppress general advertising after first pairing
|
|
* @param bonded_only_peer optional peer allowed to reconnect in stealth mode
|
|
*/
|
|
bool begin(const char* prefix, const char* name, uint32_t pin_code,
|
|
const uint8_t* custom_address = nullptr,
|
|
bool clear_bonds = false, bool stealth_pair_once = false,
|
|
const mesh::companion::BluetoothPeerIdentity*
|
|
bonded_only_peer = nullptr);
|
|
|
|
void disconnect();
|
|
void enable() override;
|
|
void disable() override;
|
|
bool isEnabled() const override { return _isEnabled; }
|
|
bool isConnected() const override;
|
|
bool isReadBusy() const override;
|
|
bool isWriteBusy() const override;
|
|
bool hasPendingIO() const override;
|
|
bool takePairingRequest() override {
|
|
return _pairingRequestPending.exchange(false, std::memory_order_acq_rel);
|
|
}
|
|
bool takeSuccessfulConnection(
|
|
mesh::companion::BluetoothPeerIdentity* peer = nullptr);
|
|
bool enableBondedOnlyAdvertising(
|
|
const mesh::companion::BluetoothPeerIdentity& peer);
|
|
void cancelStealthPairingTransition();
|
|
bool takeBondedOnlyRecovery() {
|
|
return _bondedOnlyRecoveryPending.exchange(
|
|
false, std::memory_order_acq_rel);
|
|
}
|
|
size_t writeFrame(const uint8_t src[], size_t len) override;
|
|
size_t checkRecvFrame(uint8_t dest[]) override;
|
|
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
Stream& motaStream() { return _mota_stream; }
|
|
bool isMotaChannelReady();
|
|
bool isMotaStreamActive() const { return _mota_stream.isActive(); }
|
|
void setMotaStreamActive(bool active) { _mota_stream.setActive(active); }
|
|
#endif
|
|
};
|
|
|
|
#if BLE_DEBUG_LOGGING && ARDUINO
|
|
#include <Arduino.h>
|
|
#define BLE_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { mesh::usbLoggingPort().printf("BLE: " F, ##__VA_ARGS__); } } while(0)
|
|
#define BLE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { mesh::usbLoggingPort().printf("BLE: " F "\n", ##__VA_ARGS__); } } while(0)
|
|
#else
|
|
#define BLE_DEBUG_PRINT(...) {}
|
|
#define BLE_DEBUG_PRINTLN(...) {}
|
|
#endif
|