From f54e7a246b117af37f1abd16673971a60f6d7bd6 Mon Sep 17 00:00:00 2001 From: eltos Date: Mon, 4 Jan 2021 12:20:06 +0100 Subject: [PATCH] Add method to control and query pairing --- .../EncryptedBatteryMonitor.ino | 67 +++++++---- keywords.txt | 3 + src/local/BLELocalDevice.cpp | 26 +++++ src/local/BLELocalDevice.h | 10 ++ src/utility/ATT.cpp | 26 +++++ src/utility/ATT.h | 2 + src/utility/L2CAPSignaling.cpp | 105 +++++++++++------- src/utility/L2CAPSignaling.h | 13 ++- 8 files changed, 190 insertions(+), 62 deletions(-) diff --git a/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino b/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino index d4953b03..fd40f5ee 100644 --- a/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino +++ b/examples/Peripheral/EncryptedBatteryMonitor/EncryptedBatteryMonitor.ino @@ -18,6 +18,14 @@ #include +#define PAIR_BUTTON D3 // button for pairing +#define PAIR_LED 24 // LED used to signal pairing +#define PAIR_LED_ON LOW // Blue LED on Nano BLE has inverted logic +#define PAIR_INTERVAL 30000 // interval for pairing after button press in ms + +#define CTRL_LED LED_BUILTIN + + // BLE Battery Service BLEService batteryService("180F"); @@ -31,13 +39,17 @@ BLEStringCharacteristic stringcharacteristic("183E", BLERead | BLEWrite, 31); BLEUnsignedCharCharacteristic secretValue("2a3F", BLERead | BLEWrite | BLEEncryption); int oldBatteryLevel = 0; // last battery level reading from analog input -long previousMillis = 0; // last time the battery level was checked, in ms +unsigned long previousMillis = 0; // last time the battery level was checked, in ms +unsigned long pairingStarted = 0; // pairing start time when button is pressed +bool wasConnected = 0; void setup() { Serial.begin(9600); // initialize serial communication while (!Serial); - pinMode(LED_BUILTIN, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected + pinMode(CTRL_LED, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected + pinMode(PAIR_LED, OUTPUT); + pinMode(PAIR_BUTTON, INPUT_PULLUP); Serial.println("Serial connected"); @@ -145,6 +157,9 @@ void setup() { secretValue.writeValue(0); delay(1000); + + // prevent pairing until button is pressed (will show a pairing rejected message) + BLE.setPairable(false); /* Start advertising BLE. It will start continuously transmitting BLE advertising packets and will be visible to remote BLE central devices @@ -169,30 +184,44 @@ void loop() { // wait for a BLE central BLEDevice central = BLE.central(); + + // If button is pressed, allow pairing for 30 sec + if (!BLE.pairable() && digitalRead(PAIR_BUTTON) == LOW){ + pairingStarted = millis(); + BLE.setPairable(Pairable::ONCE); + Serial.println("Accepting pairing for 30s"); + } else if (BLE.pairable() && millis() > pairingStarted + PAIR_INTERVAL){ + BLE.setPairable(false); + Serial.println("No longer accepting pairing"); + } + // Make LED blink while pairing is allowed + digitalWrite(PAIR_LED, (BLE.pairable() ? (millis()%400)<200 : BLE.paired()) ? PAIR_LED_ON : !PAIR_LED_ON); + + // if a central is connected to the peripheral: - if (central) { - Serial.print("Connected to central: "); - // print the central's BT address: - Serial.println(central.address()); + if (central && central.connected()) { + if (!wasConnected){ + wasConnected = true; + Serial.print("Connected to central: "); + // print the central's BT address: + Serial.println(central.address()); + } // check the battery level every 200ms // while the central is connected: - while (central.connected()) { - long currentMillis = millis(); - // if 200ms have passed, check the battery level: - if (currentMillis - previousMillis >= 1000) { - previousMillis = currentMillis; - updateBatteryLevel(); - if(secretValue.value()>0){ - digitalWrite(13,HIGH); - }else{ - digitalWrite(13,LOW); - } - } + long currentMillis = millis(); + // if 200ms have passed, check the battery level: + if (currentMillis - previousMillis >= 1000) { + previousMillis = currentMillis; + updateBatteryLevel(); + digitalWrite(CTRL_LED, secretValue.value()>0 ? HIGH : LOW); } + } else if (wasConnected){ + wasConnected = false; Serial.print("Disconnected from central: "); Serial.println(central.address()); } + } void updateBatteryLevel() { @@ -208,4 +237,4 @@ void updateBatteryLevel() { batteryLevelChar.writeValue(batteryLevel); // and update the battery level characteristic oldBatteryLevel = batteryLevel; // save the level for next comparison } -} +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index 464cac72..effdfbca 100644 --- a/keywords.txt +++ b/keywords.txt @@ -77,9 +77,12 @@ setEventHandler KEYWORD2 setAdvertisingInterval KEYWORD2 setConnectionInterval KEYWORD2 setConnectable KEYWORD2 +setPairable KEYWORD2 setTimeout KEYWORD2 debug KEYWORD2 noDebug KEYWORD2 +pairable KEYWORD2 +paired KEYWORD2 properties KEYWORD2 valueSize KEYWORD2 diff --git a/src/local/BLELocalDevice.cpp b/src/local/BLELocalDevice.cpp index 49cc6603..029fb488 100644 --- a/src/local/BLELocalDevice.cpp +++ b/src/local/BLELocalDevice.cpp @@ -214,6 +214,16 @@ bool BLELocalDevice::connected() const return ATT.connected(); } +/* + * Whether there is at least one paired device + */ +bool BLELocalDevice::paired() +{ + HCI.poll(); + + return ATT.paired(); +} + bool BLELocalDevice::disconnect() { return ATT.disconnect(); @@ -395,6 +405,22 @@ void BLELocalDevice::setTimeout(unsigned long timeout) ATT.setTimeout(timeout); } +/* + * Control whether pairing is allowed or rejected + * Use true/false or the Pairable enum + */ +void BLELocalDevice::setPairable(uint8_t pairable) +{ + L2CAPSignaling.setPairingEnabled(pairable); +} + +/* + * Whether pairing is currently allowed + */ +bool BLELocalDevice::pairable() +{ + return L2CAPSignaling.isPairingEnabled(); +} void BLELocalDevice::setGetIRKs(int (*getIRKs)(uint8_t* nIRKs, uint8_t** BADDR_type, uint8_t*** BADDRs, uint8_t*** IRKs)){ HCI._getIRKs = getIRKs; diff --git a/src/local/BLELocalDevice.h b/src/local/BLELocalDevice.h index 22a5f589..0c471938 100644 --- a/src/local/BLELocalDevice.h +++ b/src/local/BLELocalDevice.h @@ -24,6 +24,12 @@ #include "BLEService.h" #include "BLEAdvertisingData.h" +enum Pairable { + NO = 0, + YES = 1, + ONCE = 2, +}; + class BLELocalDevice { public: BLELocalDevice(); @@ -80,6 +86,10 @@ class BLELocalDevice { virtual void debug(Stream& stream); virtual void noDebug(); + + virtual void setPairable(uint8_t pairable); + virtual bool pairable(); + virtual bool paired(); /// TODO: Put in actual variable names virtual void setStoreIRK(int (*storeIRK)(uint8_t*, uint8_t*)); diff --git a/src/utility/ATT.cpp b/src/utility/ATT.cpp index 18556413..a5166217 100644 --- a/src/utility/ATT.cpp +++ b/src/utility/ATT.cpp @@ -497,6 +497,32 @@ bool ATTClass::connected(uint16_t handle) const return false; } +/* + * Return true if any of the known devices is paired (peer encrypted) + * Does not check if the paired device is also connected + */ +bool ATTClass::paired() const +{ + for(int i=0; i 0){ + return true; + } + } + return false; +} + +/* + * Return true if the specified device is paired (peer encrypted) + */ +bool ATTClass::paired(uint16_t handle) const +{ + for(int i=0; i 0; + } + return false; // unknown handle +} + uint16_t ATTClass::mtu(uint16_t handle) const { for (int i = 0; i < ATT_MAX_PEERS; i++) { diff --git a/src/utility/ATT.h b/src/utility/ATT.h index 1c8910c3..c9f007f9 100644 --- a/src/utility/ATT.h +++ b/src/utility/ATT.h @@ -75,6 +75,8 @@ class ATTClass { virtual bool connected() const; virtual bool connected(uint8_t addressType, const uint8_t address[6]) const; virtual bool connected(uint16_t handle) const; + virtual bool paired() const; + virtual bool paired(uint16_t handle) const; virtual uint16_t mtu(uint16_t handle) const; virtual bool disconnect(); diff --git a/src/utility/L2CAPSignaling.cpp b/src/utility/L2CAPSignaling.cpp index 4adeaa0d..622f2178 100644 --- a/src/utility/L2CAPSignaling.cpp +++ b/src/utility/L2CAPSignaling.cpp @@ -32,6 +32,7 @@ L2CAPSignalingClass::L2CAPSignalingClass() : _minInterval(0), _maxInterval(0), _supervisionTimeout(0) + ,_pairing_enabled(1) { } @@ -131,53 +132,64 @@ void L2CAPSignalingClass::handleSecurityData(uint16_t connectionHandle, uint8_t btct.printBytes(data,dlen); #endif if (code == CONNECTION_PAIRING_REQUEST) { - // 0x1 - struct __attribute__ ((packed)) PairingRequest { - uint8_t ioCapability; - uint8_t oobDataFlag; - uint8_t authReq; - uint8_t maxEncSize; - uint8_t initiatorKeyDistribution; - uint8_t responderKeyDistribution; - } *pairingRequest = (PairingRequest*)l2capSignalingHdr->data; - - - ATT.remoteKeyDistribution = KeyDistribution(pairingRequest->initiatorKeyDistribution); - ATT.localKeyDistribution = KeyDistribution(pairingRequest->responderKeyDistribution); - KeyDistribution rkd(pairingRequest->responderKeyDistribution); - AuthReq req(pairingRequest->authReq); - KeyDistribution responseKD = KeyDistribution(); - responseKD.setIdKey(true); + + if (isPairingEnabled()){ + if (_pairing_enabled >= 2) _pairing_enabled = 0; // 2 = pair once only + + // 0x1 + struct __attribute__ ((packed)) PairingRequest { + uint8_t ioCapability; + uint8_t oobDataFlag; + uint8_t authReq; + uint8_t maxEncSize; + uint8_t initiatorKeyDistribution; + uint8_t responderKeyDistribution; + } *pairingRequest = (PairingRequest*)l2capSignalingHdr->data; + + + ATT.remoteKeyDistribution = KeyDistribution(pairingRequest->initiatorKeyDistribution); + ATT.localKeyDistribution = KeyDistribution(pairingRequest->responderKeyDistribution); + KeyDistribution rkd(pairingRequest->responderKeyDistribution); + AuthReq req(pairingRequest->authReq); + KeyDistribution responseKD = KeyDistribution(); + responseKD.setIdKey(true); #ifdef _BLE_TRACE_ - Serial.print("Req has properties: "); - Serial.print(req.Bonding()?"bonding, ":"no bonding, "); - Serial.print(req.CT2()?"CT2, ":"no CT2, "); - Serial.print(req.KeyPress()?"KeyPress, ":"no KeyPress, "); - Serial.print(req.MITM()?"MITM, ":"no MITM, "); - Serial.print(req.SC()?"SC, ":"no SC, "); + Serial.print("Req has properties: "); + Serial.print(req.Bonding()?"bonding, ":"no bonding, "); + Serial.print(req.CT2()?"CT2, ":"no CT2, "); + Serial.print(req.KeyPress()?"KeyPress, ":"no KeyPress, "); + Serial.print(req.MITM()?"MITM, ":"no MITM, "); + Serial.print(req.SC()?"SC, ":"no SC, "); #endif - uint8_t peerIOCap[3]; - peerIOCap[0] = pairingRequest->authReq; - peerIOCap[1] = pairingRequest->oobDataFlag; - peerIOCap[2] = pairingRequest->ioCapability; - ATT.setPeerIOCap(connectionHandle, peerIOCap); - ATT.setPeerEncryption(connectionHandle, ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::PAIRING_REQUEST); + uint8_t peerIOCap[3]; + peerIOCap[0] = pairingRequest->authReq; + peerIOCap[1] = pairingRequest->oobDataFlag; + peerIOCap[2] = pairingRequest->ioCapability; + ATT.setPeerIOCap(connectionHandle, peerIOCap); + ATT.setPeerEncryption(connectionHandle, ATT.getPeerEncryption(connectionHandle) | PEER_ENCRYPTION::PAIRING_REQUEST); #ifdef _BLE_TRACE_ - Serial.print("Peer encryption : 0b"); - Serial.println(ATT.getPeerEncryption(connectionHandle), BIN); + Serial.print("Peer encryption : 0b"); + Serial.println(ATT.getPeerEncryption(connectionHandle), BIN); #endif - struct __attribute__ ((packed)) PairingResponse { - uint8_t code; - uint8_t ioCapability; - uint8_t oobDataFlag; - uint8_t authReq; - uint8_t maxEncSize; - uint8_t initiatorKeyDistribution; - uint8_t responderKeyDistribution; - } response = { CONNECTION_PAIRING_RESPONSE, LOCAL_IOCAP, 0, LOCAL_AUTHREQ, 0x10, responseKD.getOctet(), responseKD.getOctet()}; - - HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(response), &response); + struct __attribute__ ((packed)) PairingResponse { + uint8_t code; + uint8_t ioCapability; + uint8_t oobDataFlag; + uint8_t authReq; + uint8_t maxEncSize; + uint8_t initiatorKeyDistribution; + uint8_t responderKeyDistribution; + } response = { CONNECTION_PAIRING_RESPONSE, LOCAL_IOCAP, 0, LOCAL_AUTHREQ, 0x10, responseKD.getOctet(), responseKD.getOctet()}; + + HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(response), &response); + + } else { + // Pairing not enabled + uint8_t ret[2] = {CONNECTION_PAIRING_FAILED, 0x05}; // reqect pairing + HCI.sendAclPkt(connectionHandle, SECURITY_CID, sizeof(ret), ret); + ATT.setPeerEncryption(connectionHandle, NO_ENCRYPTION); + } } else if (code == CONNECTION_PAIRING_RANDOM) { @@ -392,6 +404,15 @@ void L2CAPSignalingClass::setSupervisionTimeout(uint16_t supervisionTimeout) _supervisionTimeout = supervisionTimeout; } +void L2CAPSignalingClass::setPairingEnabled(uint8_t enabled) +{ + _pairing_enabled = enabled; +} +bool L2CAPSignalingClass::isPairingEnabled() +{ + return _pairing_enabled > 0; +} + void L2CAPSignalingClass::connectionParameterUpdateRequest(uint16_t handle, uint8_t identifier, uint8_t dlen, uint8_t data[]) { struct __attribute__ ((packed)) L2CAPConnectionParameterUpdateRequest { diff --git a/src/utility/L2CAPSignaling.h b/src/utility/L2CAPSignaling.h index 7fc64d4f..f8a361ee 100644 --- a/src/utility/L2CAPSignaling.h +++ b/src/utility/L2CAPSignaling.h @@ -41,8 +41,15 @@ #define CONNECTION_PAIRING_DHKEY_CHECK 0x0D #define CONNECTION_PAIRING_KEYPRESS 0x0E +#define IOCAP_DISPLAY_ONLY 0x00 +#define IOCAP_DISPLAY_YES_NO 0x01 +#define IOCAP_KEYBOARD_ONLY 0x02 +#define IOCAP_NO_INPUT_NO_OUTPUT 0x03 +#define IOCAP_KEYBOARD_DISPLAY 0x04 + + #define LOCAL_AUTHREQ 0b00101101 -#define LOCAL_IOCAP 0x3 +#define LOCAL_IOCAP IOCAP_NO_INPUT_NO_OUTPUT // will use JustWorks pairing class L2CAPSignalingClass { public: @@ -63,6 +70,9 @@ class L2CAPSignalingClass { virtual void setConnectionInterval(uint16_t minInterval, uint16_t maxInterval); virtual void setSupervisionTimeout(uint16_t supervisionTimeout); + + virtual void setPairingEnabled(uint8_t enabled); + virtual bool isPairingEnabled(); @@ -78,6 +88,7 @@ class L2CAPSignalingClass { uint16_t _minInterval; uint16_t _maxInterval; uint16_t _supervisionTimeout; + uint8_t _pairing_enabled; }; extern L2CAPSignalingClass& L2CAPSignaling;