Skip to content

Commit

Permalink
Add method to control and query pairing
Browse files Browse the repository at this point in the history
  • Loading branch information
eltos committed Jan 4, 2021
1 parent 915e914 commit f54e7a2
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
#include <ArduinoBLE.h>


#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");

Expand All @@ -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");
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -208,4 +237,4 @@ void updateBatteryLevel() {
batteryLevelChar.writeValue(batteryLevel); // and update the battery level characteristic
oldBatteryLevel = batteryLevel; // save the level for next comparison
}
}
}
3 changes: 3 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions src/local/BLELocalDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/local/BLELocalDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#include "BLEService.h"
#include "BLEAdvertisingData.h"

enum Pairable {
NO = 0,
YES = 1,
ONCE = 2,
};

class BLELocalDevice {
public:
BLELocalDevice();
Expand Down Expand Up @@ -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*));
Expand Down
26 changes: 26 additions & 0 deletions src/utility/ATT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ATT_MAX_PEERS; i++){
if((_peers[i].encryption & PEER_ENCRYPTION::ENCRYPTED_AES) > 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<ATT_MAX_PEERS; i++){
if(_peers[i].connectionHandle != handle){continue;}
return (_peers[i].encryption & PEER_ENCRYPTION::ENCRYPTED_AES) > 0;
}
return false; // unknown handle
}

uint16_t ATTClass::mtu(uint16_t handle) const
{
for (int i = 0; i < ATT_MAX_PEERS; i++) {
Expand Down
2 changes: 2 additions & 0 deletions src/utility/ATT.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
105 changes: 63 additions & 42 deletions src/utility/L2CAPSignaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ L2CAPSignalingClass::L2CAPSignalingClass() :
_minInterval(0),
_maxInterval(0),
_supervisionTimeout(0)
,_pairing_enabled(1)
{
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 12 additions & 1 deletion src/utility/L2CAPSignaling.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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();



Expand All @@ -78,6 +88,7 @@ class L2CAPSignalingClass {
uint16_t _minInterval;
uint16_t _maxInterval;
uint16_t _supervisionTimeout;
uint8_t _pairing_enabled;
};

extern L2CAPSignalingClass& L2CAPSignaling;
Expand Down

0 comments on commit f54e7a2

Please sign in to comment.