Contents
Introduction
BlueCove[^] is a popular development kit that enables rapid development of Bluetooth applications in Java. It has a comprehensive feature set and allows a developer to do almost anything related to Bluetooth communications on Windows, Mac, and Linux systems. I say almost because there is one desirable feature that is apparently supported in Linux Bluez versions of BlueCove but is lacking in Windows: that of pass code authentication during the pairing process.
BlueCove Bluez provides a passkey agent that responds to remote authentication requests with a passkey in order to facilitate device pairing. This functionality is handled natively in Windows by the use of the Windows API method BluetoothRegisterForAuthenticationEx()
but I was unable to find a BlueCove passkey agent for Windows and so I set out to create something that would fulfill this role for my application.
Designing a Windows authenticator
The first thing I considered before setting out to write the authenticator class was how BlueCove handled device discovery in general. For instance, if I have a room full of devices that are discoverable and want to search for them, I can use the DiscoveryAgent.startInquiry()
method which takes, as an argument, a callback of type DiscoveryListener
. In this case, I initiate an active search for remote devices instead of passively responding to remote device pairing requests. Here is an example of how this works (Source: bluecove.org)[^].
import java.io.IOException;
import java.util.Vector;
import javax.bluetooth.*;
public class RemoteDeviceDiscovery {
public static final Vector devicesDiscovered = new Vector();
public static void main(String[] args) throws IOException, InterruptedException {
final Object inquiryCompletedEvent = new Object();
devicesDiscovered.clear();
DiscoveryListener listener = new DiscoveryListener() {
public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
System.out.println("Device " + btDevice.getBluetoothAddress() + " found");
devicesDiscovered.addElement(btDevice);
try {
System.out.println(" name " + btDevice.getFriendlyName(false));
} catch (IOException cantGetDeviceName) {
}
}
public void inquiryCompleted(int discType) {
System.out.println("Device Inquiry completed!");
synchronized(inquiryCompletedEvent){
inquiryCompletedEvent.notifyAll();
}
}
public void serviceSearchCompleted(int transID, int respCode) {
}
public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
}
};
synchronized(inquiryCompletedEvent) {
boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().
startInquiry(DiscoveryAgent.GIAC, listener);
if (started) {
System.out.println("wait for device inquiry to complete...");
inquiryCompletedEvent.wait();
System.out.println(devicesDiscovered.size() + " device(s) found");
}
}
}
}
If I desire to authenticate a discovered device, I could perform this action in the DiscoveryListener
method deviceDiscovered()
using the following:
public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
try {
RemoteDeviceHelper.authenticate(btDevice, "1234");
} catch (IOException CantAuthenticate) {
}
}
Unlike the DiscoveryAgent.startInquiry()
method's DiscoveryListener
callback, I only needed one method since services were not involved and I would only respond to each remote device request in turn. However I patterned my Win32AuthenticationListener
classes single method authenticationRequest()
after DiscoveryListener
's deviceDiscovered()
method. Here's an example of how the Win32Authenticator
is used:
import java.io.IOException;
import javax.bluetooth.*;
import com.intel.bluetooth.RemoteDeviceHelper;
public class Win32AuthenticationTest {
static Win32Authenticator pairing;
public static void main(String[] args) throws BluetoothStateException {
java.util.Scanner scanner = new java.util.Scanner(System.in);
try {
pairing = new Win32Authenticator(
new Win32Authenticator.Win32AuthenticationListener() {
@Override
public void authenticationRequest(
RemoteDevice btDevice, DeviceClass cod) {
try {
System.out.println("Auth Request Recieved");
RemoteDeviceHelper.authenticate(btDevice, "1234");
} catch (IOException cantAuthenticate) {
}
}
});
System.out.println("Waiting for authentications...");
System.out.println("Press Enter to quit.");
scanner.nextLine();
pairing.dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Authenticator nuts and bolts
The actual Win32Authenticator
class implements JNA or Java Native Access[^] to call into the native Win32 library methods. In this case, the only native methods I needed to use were BluetoothRegisterForAuthenticationEx()
and BluetoothUnregisterAuthentication()
. The majority of the code in the module consists of native compatible struct/class definitions as well as some operating code.
In order to get the BlueCove side of things working, I did have to employ a few tricks. The code pattern for the callback required two arguments: one of type RemoteDevice
and the other of type DeviceClass
. Of these two BlueCove classes, I had no trouble creating an instance of DeviceClass
from the callback thread; RemoteDevice
however, was another matter entirely. For whatever reason, instantiating this class on any thread other than the main thread would cause a deadlock. I spent quite a bit of time researching a workaround for this without much success. Then I hit upon a solution that I figured might work, that is, to create an instance of the class in the main thread and reuse it each time the callback fired off.
Before I attempted this feat however, I decided to take a look at the BlueCove RemoteDevice
class to see what I would be dealing with. I fired up Java decompiler[^] and opened the BlueCove jar to take a look, and this is what I saw:
public class RemoteDevice
{
private String addressStr;
private long addressLong;
protected RemoteDevice(String address)
{...
Sweet! Only two fields and all of the class methods that follow return values based upon these two fields! Now notice the protected constructor; this class can only be instantiated from within its parent package. Because of this, the Win32Authenticator
class is a part of the javax.bluetooth
package. Thus I created a re-usable instance of RemoteDevice
in the class constructor (called on the main thread) which is updated via Reflection each time a device requests authentication.
this.m_callback = new _Bthprops.BluetoothAuthenticationCallback() {
@Override
public boolean callback(
Pointer param,
_Bthprops.BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS pAuthCallbackParams) {
DeviceClass dc = new DeviceClass(
pAuthCallbackParams.deviceInfo.ulClassofDevice);
setRemoteDeviceViaReflection(rd, pAuthCallbackParams.deviceInfo
.getBluetoothAddress());
if (m_userCallback != null) {
m_userCallback.authenticationRequest(rd, dc);
return true;
}
return false;
}
};
Now here's how I use Reflection to update the RemoteDevice
object:
private void setRemoteDeviceViaReflection(RemoteDevice rd, String address) {
String formattedAddress = RemoteDeviceHelper
.formatBluetoothAddress(address);
Class<? extends RemoteDevice> c = rd.getClass();
if (address == null) {
throw new NullPointerException("address is null");
}
if (address.length() != 12) {
throw new IllegalArgumentException("Malformed address: " + address
+ "; should be 12 characters");
}
if (address.startsWith("-")) {
throw new IllegalArgumentException("Malformed address: " + address
+ "; can't be negative");
}
try {
Field f = c.getDeclaredField("addressStr");
f.setAccessible(true);
f.set(rd, formattedAddress);
f.setAccessible(false);
} catch (Exception e) {
throw ((RuntimeException) UtilsJavaSE.initCause(
new RuntimeException("Can't set RemoteDevice field addressStr"),
e));
}
try {
if (formattedAddress.equals(LocalDevice.getLocalDevice()
.getBluetoothAddress()))
throw new IllegalArgumentException(
"can't use the LocalDevice address.");
} catch (BluetoothStateException e) {
throw ((RuntimeException) UtilsJavaSE.initCause(
new RuntimeException("Can't initialize bluetooth support"),
e));
}
try {
Field f = c.getDeclaredField("addressLong");
f.setAccessible(true);
f.setLong(rd, RemoteDeviceHelper.getAddress(address));
f.setAccessible(false);
} catch (Exception e) {
throw ((RuntimeException) UtilsJavaSE.initCause(
new RuntimeException("Can't set RemoteDevice field addressLong"),
e));
}
}
Bonus: Win32LocalDeviceHelper
The BlueCove LocalDevice
allows you to get the friendly name of your Bluetooth antenna. This is probably not all that helpful if you really care about the friendly name since there is no method to programmatically set the friendly name of your antenna. In my case, I had a remote device that would go active periodically and seek to link to a host with a specific friendly name. The application running on the host needed to ensure that the friendly name was correct and if not, reset it. I did quite a bit of digging on this one and found two solutions, one buried many pages deep in the comments of a blog post that became an informal go-to site for swapping Bluetooth solutions. The other on a help forum. Neither of these solutions worked correctly out of the box, however combining elements of both of them did the trick.
In a nutshell, on a Windows machine, the process of changing your local device friendly name involves finding a certain Registry key. Once found, write the desired name to that key. Finally, it is necessary to trigger the driver to read the Registry key with the new name. Smarter developers than me figured that one out and I am grateful to them. I created the Win32LocalDeviceHelper
class to easily set the LocalDevice friendly name. It also employs JNA to call the native Win32 library methods used.
Final comments
The added functionality of the two classes presented here fill in some gaps in the the BlueCove development kit. Until such time that equivalent features should be added to BlueCove, it is my hope that these two classes prove to be as useful to some of you as they have been to me.
History
- Mar 15, 2012: Version 1.0.0.0.
- June 11, 2012: Version 1.1.0.0 - Modified code to use BluetoothRegisterForAuthenticationEx() instead of BluetoothAuthenticationCallback() to support Windows 7
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.