KISS

Keep It Simple Stupid

Weird Bluetooth SDP discovery on OS X

| comments

I happened to do some work with Bluetooth on OS X some time ago. It involved inquiring Android devices, SDP service discovery, and connecting to it. The original corresponding part works on Linux with BlueZ. Here, I stumbled upon some odd behavior.

First, I run device scan and inquiry, and when it’s done I’m looking for a certain service UUID, performing an SDP query if necessary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted {
    for (IOBluetoothDevice *device in sender.foundDevices) {
        NSLog(@"device with address %@ [ %@ ]", device.addressString, device.name);

        NSArray *serviceArray = @[ self.serviceUUID ];

        NSArray *channels = [self rfcommChannelsOfUUID:self.serviceUUID onDevice:device];
        if (channels.count > 0) {
            NSLog(@"service found here, channels: %@", [channels componentsJoinedByString:@", "]);
        } else {
            NSDate *lastUpdate = [device getLastServicesUpdate];
            if (!lastUpdate) {
                IOReturn ret = [device performSDPQuery:self uuids:serviceArray];
                if (kIOReturnSuccess == ret) {
                    NSLog(@"started searching for services");
                } else {
                    NSLog(@"failed to start searching for services");
                }
            }
        }
    }
}

Connecting is easy once you get a channel ID to connect to:

1
2
3
4
5
6
7
IOBluetoothRFCOMMChannel *channel = nil;
IOReturn ret = [device openRFCOMMChannelSync:&channel withChannelID:channelID delegate:self];
if (kIOReturnSuccess == ret) {
    // …
} else {
    NSLog(@"Failed to open RFCOMM connection, channel %@ open? %d, error = %x", channel, channel.isOpen, ret);
}

In the vast majority of cases, it fails with return code e00002cd, which is kIOReturnNotOpen. Based on few links (http://lists.apple.com/archives/bluetooth-dev/2005/Oct/msg00020.html) available, I switched to the async version. After long inquiries, I noticed the OSX app finds the RFCOMM channels different from those Ubuntu finds.

Later on I was very surprised to discover that for some reason my target service has the same RFCOMM channel ID as something called “SIM Access Server”. But how is it possible? The only related information I found is here: http://developer.samsung.com/forum/board/thread/view.do?boardName=GeneralB&messageId=219648, no answers. The Android phone shows this message “Bluetooth permission request. Name is requesting permission to connect to access SIM.” when I’m trying to connect.

…Ha, I knew this moment would come eventually! The Solution: perform SDP query every time and don’t rely on the cached results! Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry *)sender error:(IOReturn)error aborted:(BOOL)aborted {
    for (IOBluetoothDevice *device in sender.foundDevices) {
        NSLog(@"device with address %@ [ %@ ]", device.addressString, device.name);

        NSArray *serviceArray = @[ self.serviceUUID ];

        IOReturn ret = [device performSDPQuery:self uuids:serviceArray];
        if (kIOReturnSuccess == ret) {
            NSLog(@"started searching for services");
        } else {
            NSLog(@"failed to start searching for services");
        }
    }
}

- (void)sdpQueryComplete:(IOBluetoothDevice *)device status:(IOReturn)status {
    NSLog(@"sdp query completed on device %@ [ %@ ]", device.addressString, device.name);
    if (kIOReturnSuccess == status) {
        NSArray *channels = [self rfcommChannelsOfUUID:self.serviceUUID onDevice:device];
        if (channels.count > 0) {
            NSLog(@"service found here, channels: %@", [channels componentsJoinedByString:@", "]);
        }
    } else {
        NSLog(@"  error = %x", status);
    }
}

And now the openRFCOMMChannelSync:withChannelID:delegate: method works fine!

Comments