BLE iOS 9.0.1pairing/bonding failed

Hi,


our app works fine with iOS 7.1 through 8.4. Its called "Toröffner" in AppStore (Schellenberg AG, its a garage door opener).

The peripheral itsels is ok and 100% BLE 1.0 compliant. We don't use encrypted characteristics.


After the characteristics are received we are trying to write to the link loss alert (level 0) to complete the bonding process

(as descibed in Apples guidelines).


The error with iOS 9.0.1 (actually) is, that the write request reurns with an error in didWriteValueForCharacteristic:


I logged the charateristics and the error


<CBCharacteristic: 0x15c532a00, UUID = 2A06, properties = 0xA, value = (null), notifying = NO>

Error Domain=CBATTErrorDomain Code=15 "Encryption is insufficient." UserInfo={NSLocalizedDescription=Encryption is insufficient.}


We're using no deprecated APIs.


I would say after intensious debugging this is a hard bug in iOS 9 Core Bluetooth so far.


I can't find anything about Apple has acknowledged this insufficient iOS 9 update regarding Core Bluetooth.


@Apple: K.I.S.S. would have led to less errors and bugs... AND never touch a running system......


Disappointed


Markus Kollmann

Locher & Christ GmbH

i.O.o. Alfred Schellenberg AG

Accepted Reply

You are probably suffering from a firmware bug on your peripheral which is exposed by some changes in iOS 9.


The reason for having this issue would be if your peripheral is calculating the key based on the assumption that some reserved bits (according to BLE Spec 4.0) in the pairing request would always be zero.


iOS 9 is now using the new LE Secure Connections pairing model, and the SC bit is set to 1 in the pairing request.


The peripheral can ignore this request and choose to use the legacy pairing model, but it cannot ignore the bits when creating the keys, and must use the data as received to calculate them.


Peripherals are supposed to use the pairing request exactly as received to calculate the keys otherwise the keys will be wrong when iOS sends a pairing request with these bits (in this case the SC bit) set to 1.

Replies

Still not working with iOS9.1 Final

I'll crosspost this on all related threads!


Our setup for this error is:


- the device BLE controller is a nordic N51822

- tested with iPhone A1429 (but fails also on all other iPhones with BLE4.x capabilities)

- iOS Build is 9.1 (13B143) (but also fails with all iOS version from 9.x up, even all the betas)


So what I've done is:


In code i use some constants for convenience (The code tags in this forum are stripping my c++ style comments...):


#define findMeServiceUUID             [CBUUID UUIDWithString:@"1802"]
#define genericAccessServiceUUID      [CBUUID UUIDWithString:@"1800"]
#define linkLossServiceUUID           [CBUUID UUIDWithString:@"1803"]
#define deviceNameCharacteristicUUID  [CBUUID UUIDWithString:@"2A00"]
#define alertLevelCharacteristicUUID  [CBUUID UUIDWithString:@"2A06"]
#define BT_ALERT_LEVEL_NONE 0
#define BT_LEVEL_MILD 1
#define BT_ALERT_LEVEL_HIGH 2
#define BT_ALERT_LEVEL_RESERVED(LVL) LVL


1. Scan for peripherals with findMe and linkLoss Service (1802 and 1803):


  NSDictionary* scanOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
                                                          forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
  NSArray* serviceArray = [NSArray arrayWithObjects:findMeServiceUUID,linkLossServiceUUID, nil];
  [centralManager scanForPeripheralsWithServices:serviceArray options:scanOptions];


2. When found an peripheral i add the CBPeripheral* to an mutable array used as datasource for a table view displaying the scan results:


- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
  NSLog(@"INFO: Did discover peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
  [peripherals addObject:peripheral];
  // Update UITableView
  [self reloadTable];
}


3. Selected the peripheral in table view (set as CBPeripheral* selectedPeripheral) and connected to it with:


  NSDictionary* connectOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
                                                             forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
  [centralManager connectPeripheral:selectedPeripheral options:connectOptions];


4. When connected I start the service discovery


- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
  NSLog(@"INFO: Did connect to peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
  isConnected = YES;
  // Update the info UILabel
  [self updateLabel];
  // Start service discovery
  [peripheral discoverServices:nil];
}


5. When a service is discoverd i start to discover the service characteristics:


- (void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
  NSLog(@"INFO: Did discover %d services on %@", (int)[peripheral services].count, [peripheral name]);
  for(CBService *s in [peripheral services]) {
    if([[s UUID] isEqual:findMeServiceUUID])
      immediateAlertService = s;
    if([[s UUID] isEqual:linkLossServiceUUID])
      linkLossService = s;
    NSLog(@"INFO: Did discover service %@ on %@", [s.UUID representativeString], [peripheral name]);
    [peripheral discoverCharacteristics:[NSArray arrayWithObject:alertLevelCharacteristicUUID] forService:s];
  }
}


6. When discoverd a characterisics for a service i store thr references to a variable.


- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
  NSLog(@"INFO: Did discover %d characteristics for service %@ on peripheral %@", (int)service.characteristics.count, service, peripheral);
  for(CBCharacteristic *c in [service characteristics])
  {
    NSLog(@"INFO: Did discover characteristic %@ for service %@ on peripheral %@", [c.UUID representativeString], service, peripheral);
    if([service isEqual:immediateAlertService] &&
       [[c UUID] isEqual:alertLevelCharacteristicUUID])
      immediateAlertAlertLevelCharacteristic = c;
    else if([service isEqual:linkLossService] &&
            [[c UUID] isEqual:alertLevelCharacteristicUUID]) {
      if(isBonded)
        return;
      linkLossAlertLevelCharacteristic = c;
      if(linkLossAlertLevelCharacteristic)
        [self writeLinkLossAlert:BT_ALERT_LEVEL_NONE];
    }
  }
}


7. As you can see if linkLossAlertLevelCharacteristic is discovered i immediately write BT_ALERT_LEVEL_NONE ( = 0 ) to it, to initialize bonding:


- (void) writeLinkLossAlertLevelToTag:(UInt8) linkLossAlertLevel
{
  NSData* data = [NSData dataWithBytes:&linkLossAlertLevel length:1];
  [selectedPeripheral writeValue:data forCharacteristic:linkLossAlertLevelCharacteristic type:CBCharacteristicWriteWithResponse];
}
- (bool) writeLinkLossAlert:(UInt8)level
{
  if(linkLossAlertLevelCharacteristic != nil) {
    NSLog(@"INFO: Writes link loss alert %d to peripheral...", level);
    [self writeLinkLossAlertLevelToTag:level];
    return YES;
  }
  NSLog(@"ERROR: Couldn´t write to alert level on link loss service.");
  return NO;
}


8. The "Pairing" dialog appears and is confirmed to pair with. But instead of pairing the write fails and there after i also get a disconnect with the timeout error.


It works all fine until iOS 9 and i don't use any deprecated API's


I logged the delegate callbacks from CBCentralManager and CBPeripheral:

2015-10-30 11:38:07.881 BLEControl[307:32547] INFO: Bluetooth is enabled

2015-10-30 11:38:14.423 BLEControl[307:32547] INFO: Start to scan for peripherals

2015-10-30 11:38:14.456 BLEControl[307:32547] INFO: Did discover peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>

2015-10-30 11:38:18.129 BLEControl[307:32547] INFO: Stopped to scan for peripherals

2015-10-30 11:38:18.164 BLEControl[307:32547] INFO: Did connect to peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>

2015-10-30 11:38:18.362 BLEControl[307:32547] INFO: Did discover 3 services on Torantrieb

2015-10-30 11:38:18.363 BLEControl[307:32547] INFO: Did discover service 1804 on Torantrieb

2015-10-30 11:38:18.364 BLEControl[307:32547] INFO: Did discover service 1802 on Torantrieb

2015-10-30 11:38:18.364 BLEControl[307:32547] INFO: Did discover service 1803 on Torantrieb

2015-10-30 11:38:18.366 BLEControl[307:32547] INFO: Did discover 0 characteristics for service <CBService: 0x15d85a00, isPrimary = YES, UUID = 1804> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>

2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover 1 characteristics for service <CBService: 0x15d8d5e0, isPrimary = YES, UUID = 1802> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>

2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover characteristic 2a06 for service <CBService: 0x15d8d5e0, isPrimary = YES, UUID = 1802> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>

2015-10-30 11:38:18.367 BLEControl[307:32547] INFO: Did discover 1 characteristics for service <CBService: 0x15d8d620, isPrimary = YES, UUID = 1803> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>

2015-10-30 11:38:18.369 BLEControl[307:32547] INFO: Did discover characteristic 2a06 for service <CBService: 0x15d8d620, isPrimary = YES, UUID = 1803> on peripheral <CBPeripheral: 0x15d85de0, identifier = 0545B850-B6BE-1BB9-10E7-80E3559AC9FB, name = Torantrieb, state = connected>

2015-10-30 11:38:18.370 BLEControl[307:32547] INFO: Writes link loss alert 0 to peripheral...

2015-10-30 11:38:22.354 BLEControl[307:32547] ERROR: Failed to write value for characteristic <CBCharacteristic: 0x15d8d890, UUID = 2A06, properties = 0xA, value = (null), notifying = NO>, reason: Error Domain=CBATTErrorDomain Code=15 "Encryption is insufficient." UserInfo={NSLocalizedDescription=Encryption is insufficient.}

2015-10-30 11:38:23.145 BLEControl[307:32547] ERROR: Did disconnect peripheral <Torantrieb> with identifier <<__NSConcreteUUID 0x15d70340> 0545B850-B6BE-1BB9-10E7-80E3559AC9FB>: Error Domain=CBErrorDomain Code=6 "The connection has timed out unexpectedly." UserInfo={NSLocalizedDescription=The connection has timed out unexpectedly.} {

NSLocalizedDescription = "The connection has timed out unexpectedly.";

}


Here is the full code of my Test-View Controller


ViewController.h

#import <UIKit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>
@interface CBUUID (StringExtraction)
- (NSString *)representativeString;
@end
@interface ViewController : UIViewController <UITableViewDelegate,UITableViewDataSource,CBCentralManagerDelegate,CBPeripheralDelegate>
@end

ViewController.m

#import "ViewController.h"
#define findMeServiceUUID             [CBUUID UUIDWithString:@"1802"]
#define genericAccessServiceUUID      [CBUUID UUIDWithString:@"1800"]
#define linkLossServiceUUID           [CBUUID UUIDWithString:@"1803"]
#define deviceNameCharacteristicUUID  [CBUUID UUIDWithString:@"2A00"]
#define alertLevelCharacteristicUUID  [CBUUID UUIDWithString:@"2A06"]
#define BT_ALERT_LEVEL_NONE 0
#define BT_LEVEL_MILD 1
#define BT_ALERT_LEVEL_HIGH 2
#define BT_ALERT_LEVEL_RESERVED(LVL) LVL
@implementation CBUUID (StringExtraction)
- (NSString *)representativeString;
{
  NSData *data = [self data];

  NSUInteger bytesToConvert = [data length];
  const unsigned char *uuidBytes = [data bytes];
  NSMutableString *outputString = [NSMutableString stringWithCapacity:16];

  for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++)
  {
    switch (currentByteIndex)
    {
      case 3:
      case 5:
      case 7:
      case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
      default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
    }
  
  }
  return outputString;
}
@end
@interface ViewController ()
@end
@implementation ViewController {
  NSMutableArray* peripherals;
  BOOL isScanning;
  BOOL isPairing;
  BOOL isBonded;
  BOOL isConnected;
  BOOL hasFailed;

  CBCentralManager* centralManager;

  CBPeripheral* selectedPeripheral;
  CBService *immediateAlertService;
  CBCharacteristic *immediateAlertAlertLevelCharacteristic;

  CBService *linkLossService;
  CBCharacteristic *linkLossAlertLevelCharacteristic;

  IBOutlet UILabel* labelPeripheral;
  IBOutlet UIButton* buttonScan;
  IBOutlet UITableView* tablePeripherals;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if(!self)
    return self;

  peripherals = [[NSMutableArray alloc] init];
  centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
  return self;
}
- (void)viewDidLoad {
  [super viewDidLoad];
  [self updateLabel];
}
- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
}
#pragma mark - BLE
-(void)startScan {
  if(isScanning)
    return;

  [centralManager stopScan];
  [peripherals removeAllObjects];
  [self reloadTable];

  NSDictionary* scanOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
                                                          forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
  NSArray* serviceArray = [NSArray arrayWithObjects:findMeServiceUUID,linkLossServiceUUID, nil];

  [centralManager scanForPeripheralsWithServices:serviceArray options:scanOptions];

  NSLog(@"INFO: Start to scan for peripherals");
  isScanning = YES;
}
-(void)stopScan {

  /

  if(!isScanning)
    return;

  [centralManager stopScan];
  NSLog(@"INFO: Stopped to scan for peripherals");

  isScanning = NO;
}
-(void)pairPeripheral {

  if(!selectedPeripheral || isPairing)
    return;

  isPairing = YES;
  selectedPeripheral.delegate = self;

  [self updateLabel];

  [self connectPeripheral];
}
-(void)connectPeripheral {
  if(!selectedPeripheral || isConnected)
    return;

  NSDictionary* connectOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
                                                             forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
  [centralManager connectPeripheral:selectedPeripheral options:connectOptions];
}
-(void)disconnectPeripheral {
  if(!selectedPeripheral || !isConnected)
    return;

  [centralManager cancelPeripheralConnection:selectedPeripheral];
}
-(void)alertPeripheral {
  if(!selectedPeripheral || !isBonded)
    return;

  [self writeImmediateAlert:BT_ALERT_LEVEL_RESERVED(3)];
}
- (void) writeLinkLossAlertLevelToTag:(UInt8) linkLossAlertLevel
{
  NSData* data = [NSData dataWithBytes:&linkLossAlertLevel length:1];
  [selectedPeripheral writeValue:data forCharacteristic:linkLossAlertLevelCharacteristic type:CBCharacteristicWriteWithResponse];
}
- (bool) writeLinkLossAlert:(UInt8)level
{
  if(linkLossAlertLevelCharacteristic != nil) {
    NSLog(@"INFO: Writes link loss alert %d to peripheral...", level);
    [self writeLinkLossAlertLevelToTag:level];
    return YES;
  }
  NSLog(@"ERROR: Couldn´t write to alert level on link loss service.");
  return NO;
}
- (void) writeImmediateAlertLevelToTag:(UInt8) immediateAlertLevel
{
  NSData* data = [NSData dataWithBytes:&immediateAlertLevel length:1];
  [selectedPeripheral writeValue:data forCharacteristic:immediateAlertAlertLevelCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
- (bool) writeImmediateAlert:(UInt8)level
{
  if(immediateAlertAlertLevelCharacteristic != nil) {
    NSLog(@"INFO: Writes immediate alert %d to tag...", level);
    [self writeImmediateAlertLevelToTag:level];
    return YES;
  }
  NSLog(@"INFO: Couldn´t write to alert level on immediate alert service.");
  return NO;
}
#pragma mark - CentralManager delegate
- (void) centralManagerDidUpdateState:(CBCentralManager *)central
{
  if ([central state] == CBCentralManagerStatePoweredOn)
    NSLog(@"INFO: Bluetooth is enabled");
  else
  {
    NSLog(@"WARNING: Bluetooth is not enabled");
    [labelPeripheral setText:@"Bluetooth is not enabled!"];
  }
}
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
  NSLog(@"INFO: Did discover peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
  [peripherals addObject:peripheral];
  [self reloadTable];
}
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
  NSLog(@"INFO: Did connect to peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);

  isConnected = YES;
  [self updateLabel];

  [peripheral discoverServices:nil];
}
- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
  isConnected = NO;
  if(error) {
    NSLog(@"ERROR: Did disconnect peripheral <%@>  with identifier <%@>: %@ %@", peripheral.name, peripheral.identifier, error, [error userInfo]);
  
    if(isPairing) {
      hasFailed = YES;
      isPairing = NO;
      [self updateLabel];
    }
    return;
  }
  [self updateLabel];
  NSLog(@"INFO: Did disconnect peripheral <%@> with identifier <%@>", peripheral.name, peripheral.identifier);
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
  isPairing = NO;
  [self updateLabel];

  if(error) {
    NSLog(@"ERROR: Did failed to connect to peripheral <%@>  with identifier <%@>: %@ %@", peripheral.name, peripheral.identifier, error, [error userInfo]);
    return;
  }
  NSLog(@"WARNING: Did failed to connect to peripheral <%@>  with identifier <%@>", peripheral.name, peripheral.identifier);
}
#pragma mark - Peripheral delegate
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
  NSLog(@"INFO: Did discover %d services on %@", (int)[peripheral services].count, [peripheral name]);
  for(CBService *s in [peripheral services]) {
    if([[s UUID] isEqual:findMeServiceUUID])
      immediateAlertService = s;
  
    if([[s UUID] isEqual:linkLossServiceUUID])
      linkLossService = s;
  
    NSLog(@"INFO: Did discover service %@ on %@", [s.UUID representativeString], [peripheral name]);
    [peripheral discoverCharacteristics:[NSArray arrayWithObject:alertLevelCharacteristicUUID] forService:s];
  }
}
- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
  NSLog(@"INFO: Did discover %d characteristics for service %@ on peripheral %@", (int)service.characteristics.count, service, peripheral);
  for(CBCharacteristic *c in [service characteristics])
  {
    NSLog(@"INFO: Did discover characteristic %@ for service %@ on peripheral %@", [c.UUID representativeString], service, peripheral);
    if([service isEqual:immediateAlertService] &&
       [[c UUID] isEqual:alertLevelCharacteristicUUID])
      immediateAlertAlertLevelCharacteristic = c;
  
    else if([service isEqual:linkLossService] &&
            [[c UUID] isEqual:alertLevelCharacteristicUUID]) {
    
      if(isBonded)
        return;
    
      linkLossAlertLevelCharacteristic = c;
      if(linkLossAlertLevelCharacteristic)
        [self writeLinkLossAlert:BT_ALERT_LEVEL_NONE];
    
    }
  }
}
- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
  NSLog(@"INFO: Did update value for characteristic %@, new value: %@, error: %@", characteristic, [characteristic value], error);
}
- (void) peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
  if (error)
  {
    NSLog(@"ERROR: Failed to write value for characteristic %@, reason: %@", characteristic, error);
  }
  else
  {
    NSLog(@"INFO: Did write value for characterstic %@, new value: %@", characteristic, [characteristic value]);
  
    isPairing = NO;
    isBonded = YES;
  
    [self updateLabel];
  }
}
#pragma mark - UI
-(IBAction)onScanButton:(id)sender {

  if(!isScanning) {
    [self startScan];
    [buttonScan setTitle:@"Stop" forState:UIControlStateNormal];
  }
  else {
    [self stopScan];
    [buttonScan setTitle:@"Start" forState:UIControlStateNormal];
  }
  [self updateLabel];
}
-(IBAction)onConnectButton:(id)sender {
  [self connectPeripheral];
}
-(IBAction)onDisconnectButton:(id)sender {
  [self disconnectPeripheral];
}
-(IBAction)onAlertButton:(id)sender {
  [self alertPeripheral];
}
-(void) updateLabel {
  if(!selectedPeripheral) {
    if(!isScanning)
      [labelPeripheral setText:@"Press start to scan"];
    else
      [labelPeripheral setText:@"Scanning..."];
    return;
  }

  if(hasFailed) {
    [labelPeripheral setText:[NSString stringWithFormat:@"%@ pairing failed", selectedPeripheral.name]];
    return;
  }

  if(isBonded) {
    [labelPeripheral setText:[NSString stringWithFormat:@"%@ bonded", selectedPeripheral.name]];
    return;
  }

  if(isConnected) {
    [labelPeripheral setText:[NSString stringWithFormat:@"%@ connected", selectedPeripheral.name]];
    return;
  }
  if(isPairing) {
    [labelPeripheral setText:[NSString stringWithFormat:@"%@ pairing...", selectedPeripheral.name]];
    return;
  }
}
#pragma Mark - Validating
-(CBPeripheral*)getPeripheral:(NSInteger)arrayIndex {
  CBPeripheral* peripheral = NULL;

  if(!peripherals)
    NSLog(@"FATAL: Peripherals array is NULL");
  else {
    if(peripherals.count < arrayIndex)
      NSLog(@"FATAL: Peripherals array length is too short.");
  
  
    peripheral = [peripherals objectAtIndex:arrayIndex];
    if(!peripheral)
      NSLog(@"FATAL: Peripheral is NULL.");
  }

  return peripheral;
}
# pragma mark - TableView thread synchronization
-(void)reloadTable {

  [tablePeripherals performSelectorOnMainThread:@selector(reloadData)
                                     withObject:nil
                                  waitUntilDone:YES];
}
#pragma mark - TableView dataSource and delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  return 64;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  if(!peripherals)
    return 0;

  return [peripherals count];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {


  selectedPeripheral = [self getPeripheral:indexPath.item];
  if(!selectedPeripheral)
    return;

  if(isScanning)
    [self onScanButton:buttonScan];
  [self pairPeripheral];
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"SettingsCell"];
  if(!cell) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SettingsCell"];
    cell.selectionStyle = UITableViewCellSelectionStyleBlue;
  }

  CBPeripheral* peripheral = [self getPeripheral:indexPath.item];
  if(!peripheral) {
    [cell.textLabel setText:@"<Unexpected error>"];
    return cell;
  }

  [cell.textLabel setText:peripheral.name];
  return cell;
}
@end

You are probably suffering from a firmware bug on your peripheral which is exposed by some changes in iOS 9.


The reason for having this issue would be if your peripheral is calculating the key based on the assumption that some reserved bits (according to BLE Spec 4.0) in the pairing request would always be zero.


iOS 9 is now using the new LE Secure Connections pairing model, and the SC bit is set to 1 in the pairing request.


The peripheral can ignore this request and choose to use the legacy pairing model, but it cannot ignore the bits when creating the keys, and must use the data as received to calculate them.


Peripherals are supposed to use the pairing request exactly as received to calculate the keys otherwise the keys will be wrong when iOS sends a pairing request with these bits (in this case the SC bit) set to 1.

Is there any chance to get a workaround built in in iOS, like when LE Secure connections is ignored by the device that iOS calculates 2 keys (with and without SC bit set) so it can match?

If you want to see if iOS can handle this for you, please open a Radar through https://bugreport.apple.com

Make sure to include *all* the information about your BLE device, including BLE chipset manufacturer info, model, version, firmware, etc.

If will also be helpful if you can include a sniffer log with your report.


Of course there is no guarantee that this could or would be fixed in iOS in a future version, so it would be best to fix your firmware to handle this correctly.


Unfortunately there is nothing you can do at app level on iOS to solve this.

Thank you for the concrete answer.


We are going to update the firmware, which is already in test and seems to be working.


This does not resove the problem with the already to the end-custumer sold devices. Ok, the can send them back to us for update. More convienient would be an fix by Apple as suggested above.


Hopefully,


Markus Kollmann

We have the same problem, product that worked with all preiOS9.X revisions. Now inoperable with the iOS9.x releases. Apple has confirmed the failures are because they now require BONDing when doing secure connections/pairing. They didn't in prior iOS releases. The whole BONDing issue is a problem for us, mainly due to the fact that our products use a BLE SOC with limited RAM/FLASH and can only support a BOND table of 8 clients. However, our use case is such that our products need to be able to 'talk' to dozens of clients. Can't do that with an 8-deep BOND table. Our solution was to manually free an entry in our products BOND list on a FIFO basis to allow the 9th, 10th, etc. device to connect.

That just led to another iOS problem. When the iOS9.x still has a BOND table entry for the device that just deleted the iOS client from it's BOND table the iOS can no longer connect (ever) to the server unless the user manually goes to the Settings/bt/pair screen in the iOS product and removes it from it's managed connections list (the 'Forget This Device' option).

We have still not found a work around.

Hardly a GOOD User experience for our products on the newer iOS9.x devices.

I should also have mentioned one of our developers opened a ticket with Apple support to adress this issue. It was via a BT Sniffer log they verified the BOND (or lack of bonding) was the failure for rejected connection attempts. After that they posed a question to us from their developers which basically said .. why wouldn't you want to BOND. The answers are clear from the above description (that's the primary reason, there are others). We've asked a half dozen follow-up questions on how to now work with the new BONDing requirements in products/use cases like ours and haven't gotten a response back from them for days (even though we keep pinging for an update). Their ticket/support system is basically worthless.

What changes did you make in the firmware to fix this? Did you have to change to a different softdevice version on the nrf51822 or did you have to make a change in the bonding code, eg to set bond == 1 to allow bonding? I'm trying to figure out whether the original suggestion that the softdevice didn't take account of the SC bit and generated an incorrect key was correct, which must surely have required a new softdevice, or whether it was just a pairing offered vs bonding required issue.