*
Microsoft.com Home|Site Map
MSDN*
|Developer Centers|Library|Downloads|How to Buy|Subscribers|Worldwide
Search for

Advanced Search

Coding 4 Fun
someassembly
Is That You? Writing Better Software for Cool USB Hardware

Scott Hanselman
Corillian Corporation

Note: The source code for this project is only the beginning. It continues to evolve at SourceForge. Go there to help guide its evolution or download the latest version for the .NET Framework 2.0 or 1.1.

Summary: In this fourth installment of the "Some Assembly Required" column, Scott Hanselman and Bryan Batchelder find a piece of hardware so compelling—and with included software so bad—that they write their own version using the .NET Framework 2.0. You can buy a little wireless key fob with USB Receiver manufactured by a no-name company and billed as a "Wireless USB Security Device" from a number of online retailers that is meant to lock your computer when you leave and unlock when you return. However, the software it comes with is terrible. So, we figure—Some Assembly Required. (Hopefully the company will ready this article and start using our software!) We'll also extend the application with all-new functionality using plugins written in Visual Basic!

The Hardware

What a fantastic idea this was. A little green button (US$15 at NewEgg) that you attach to your key ring. It acts as a "presence" indicator for your computer. It knows when you arrive and it knows when you leave and it performs actions like locking your computer, turning down the volume or running certain tasks. Brilliant, right? Wrong. The hardware is great, but the included software is some odd little app circa 1995 whose idea of "locking" your computer is covering all your applications with its own big non-standard window and forcing you to enter a password to override. No, not your Windows Login password, another totally different and application-specific password. Yikes.

Also, this little application wasn't extensible in any way, and there didn't appear to be any COM or .NET libraries included to easily receive the device's events. But, the idea and the hardware are just so darned compelling. Greg Hughes and I had talked about writing a better application for this little button a number of times but never took that first step. Thankfully Bryan Batchelder also couldn't sleep and took the first step for us all.

Interfacing with USB and a Little Abstraction

The hardware behind the little green USB Wireless Security Key Fob is two pieces. The button that you clip to your keys that "heartbeats" a short-range (10m) signal, and a small USB receiver that plugs into your computer. Interestingly, when it's plugged in for the first time, it doesn't require drivers. Windows knows about this little thing automatically! How is that possible? When I run msinfo32.exe (You have this little-known application on your system also; give it a run now!) I notice that it's registered itself as a "USB Human Interface Device" (mouse) and a "Game Controller" which are two devices that Windows knows about already. It makes sense that the company creating these little devices would use commonly available USB chipsets like those used in inexpensive mice. Also, no need to write a custom device driver. In the figure below, notice the USB Receiver device's PNP ID is "VID_04b4&PID_7417." That's important: we'll need to use it later when we go looking for it programmatically.

Click here for larger image

(click image to zoom)

Talking to a USB device is a lot different from talking to a Serial Port. Serial Ports have names like "COM3" and no matter what device is plugged into the COM3, it's still COM3. If you want to find a serial port device and you're not sure which port it's on, you have to basically "yell" out programmatically to each of the serial ports on the system saying, "Is that you?" In the USB world, you know the kind of device you're looking for, and you're really not concerned about which port it's on. You just know you want to talk to it.

Unfortunately, there's no support in the Base Class Library (BCL) included with .NET for talking to USB Devices. Most often, if you want to access a USB device from .NET, you'll use a high-level library that the device's manufacturer includes. However, in this case, as we said, the manufacturer includes no libraries that we can take advantage of, so we're at square one. Instead we'll be using a number of Win32 APIs from kernel32.dll, setupapi.dll, and hid.dll ("hid" means Human Interface Device).

We'll start by building up several layers of abstraction, because even though we could just call all this Win32 goodness from our UI, we'd really rather have a nice class called "KeyFob," wouldn't we? Below is a Class Diagram created by Visual Studio 2005 Professional Edition. You can read it from left to right. You might think that KeyFob is the class our application will use, since it's a good logical representation of the little device that we think of as the key fob. However, our application really cares about the concept of presence, and we have to take into consideration that there are multiple key fobs out there, any one of which could walk by our single receiver. So, we need a KeyFobCollection that is a generic Dictionary<string, KeyFob> that our PresenceManager will use. The PresenceManager will manage a list of authorized fobs; that is, KeyFobs that are allowed to affect our system by their presence.

KeyFob has Status, SerialNumber and HandleMessage methods as well as helpful information like IsAuthorized and LastHeartbeat. It has protected items that aren't directly available to us, the most interesting one being a KeyFobReceiver. This class is wholly encapsulated within the KeyFob class and provides the class with information that we on the outside really don't need to know, like the array of bytes of the lastPacket. Moving lower, the KeyFobReceiver has an instance of the UsbStickReceiver class. This is the first class that formally recognizes that this device is a USB device and there is some very low-level I/O going on here. It has an instance of the USBSharp class which is a managed wrapper around all the Win32 DLLs APIs mentioned before.

Click here for larger image

(click image to zoom)

The really crazy and interesting stuff happens in UsbStickReceiver, just above the low-level APIs. The USBSharp class handles marshaling of the various Windows SDK datatypes we'll need to be passing in and receiving. Let's take a look at the FindReceiver method of UsbStickReceiver. Not much can happen in our application until the receiver is plugged in, right?

I've added comments to the code below to explain what's happening and what we're trying to accomplish.

public static UsbStickReceiver FindReceiver()
{
    //We haven't found any devices
    int my_device_count = 0;
    //We have no idea where our device is (yet)
    string my_device_path = string.Empty;
    //But we know we'll need all these methods!
    USBSharp.USBSharp myUsb = new USBSharp.USBSharp();
    //And we need to get the Human Interface Device GUID
    myUsb.CT_HidGuid();
    //Let the system know we're looking for active devices
    myUsb.CT_SetupDiGetClassDevs();
 
    //Get ready...
    int result = -1;
    int device_count = 0;
    int size = 0;
    int requiredSize = 0;
 
    //While nothing goes wrong
    while(result != 0)
    {
        //Starting with device 0...
        result = myUsb.CT_SetupDiEnumDeviceInterfaces(device_count);
        //Let me know how much room I'm going to need to get info about this device
        int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);
        //Cool, store that                
        size = requiredSize;
        //Gimme the info you've got
        resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetailx(ref requiredSize, size);
        //Did we find the USB Receiver? Remember it's name from earlier?
        if(myUsb.DevicePathName.IndexOf("vid_04b4&pid_7417") > 0)
        {
            //Sweet, it's device # "device_count," let's store this!
            my_device_count = device_count;
            my_device_path = myUsb.DevicePathName;
            //Bail, we're done!
            break;
        }
        device_count++;
    }
 
    if(my_device_path == string.Empty)
    {
        Exception devNotFound = new Exception(@"Device could not be found.");
        throw(devNotFound);
    }
 
    return new UsbStickReceiver(my_device_count, my_device_path);
}
This is a pretty good example of abstraction. The name of the method is "FindReceiver" and it takes no parameters. It returns a UsbStickReceiver to the caller, KeyFobReceiver, and it hinds a LOT of stuff. In this example also, the methods we call are all managed methods on the UsbSharp class which, in turn, hides all the unmanaged Win32 goop. Each class has its responsibility and does just that. 'Twas Bryan who did all this good work and for that we thank him.

Another interesting thing to note is that Human Interface Devices (HIDs) can use File Handles to provide us access to their data, so later UsbStickReceiver will take the devicePath retrieved in the code above and do

resultb = myUsb.CT_CreateFile(devicePath); 

and then take the resulting file handle HidHandle and

fs = new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle((IntPtr)myUsb.HidHandle, false), FileAccess.Read, 5, true);
to get the data. Be sure to read the code and have the Class Diagram close by for reference. It's really interesting.

Squashing Bugs and Reading the Manual

One small but interesting aside: as Bryan and I (and the folks who used the initial version of this application) were testing this application, we noticed that some users' systems just couldn't find the USB receiver when it was plugged into a USB hub. They'd have to move it around until it got found. It wasn't until we did a line-by-line review of the code in UsbStickReceiver.cs and compared each method call to the MSDN documentation that we discovered that we'd been passing an incorrect parameter to one of the wrapped Win32 methods.

//Wrong. We were passing in the previous device's resultb value, which caused random and unpredictable weirdness.
int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, resultb);

//Right. Odd as it may seem, the MSDN documentation explicitly says to pass in "0" for the second parameter. Whatever, dude. It makes the whole thing work!
int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);
Bugs like this are really hard to find and fix because it DID work. Most of the time. It worked so most-of-the-time that we figured it was a hardware problem. Hard to debug, but rewarding now that we can reliably find the USB Receiver now.

User-specific Settings

By now we've got our PresenceManager. Next stop SettingsManager. Since more than one person may be using a single machine and will each likely have their own key fob, we'll want them to each have their own user-specific settings. We wanted to get a path that is user-specific and application-specific. We also wanted to create a directory and reasonable default settings file if needed.

//This constructor is private because SettingsManager is accessed via a Factory 
private SettingsManager()
{
    doc = new XmlDocument();
    string appDirPath = Path.Combine(
        System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), 
        "Usb Wireless Security");
    configFilePath = Path.Combine(appDirPath, "SettingsV2.xml");
    CreateIfNeeded(appDirPath, configFilePath);
    doc.Load(configFilePath);
}
 
protected void CreateIfNeeded(string directory, string file)
{
    const string DEFAULT_SETTING_FILE = 
        @"<?xml version=""1.0"" encoding=""utf-8"" ?>
        <Settings><KeyFobs />
        <PresenceWindow>5</PresenceWindow>
        <OverridePassword /><DisabledPlugins/></Settings>";
    DirectoryInfo di = new DirectoryInfo(directory);
    if(!di.Exists) { di.Create(); }
 
    FileInfo fi = new FileInfo(file);
    if(!fi.Exists)
    {
        FileStream fs = fi.Create();
        StreamWriter sr = new StreamWriter(fs);
        sr.Write(DEFAULT_SETTING_FILE);
        sr.Close();
    }
}
There's a lot of new Settings functionality build into .NET 2.0, but for our needs a simple XML file loaded into an XmlDocument was easy and very few lines of code. Each to his or her own. The number one thing to get out of this snippet is that your application should operate on the "Principle of Least Surprise." That means it shouldn't do, or need to do, anything that surprises the user. It should just work, and if it doesn't have something available, it should make it. In our example, it's reasonable for the user to assume they have their own settings, so we put our settings in the C:\documents and settings\<username>\Application Data folder that we retrieved by using

System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData)

and we create the folder if it's not there, as well as a new settings file with reasonable defaults if one is missing. It's little things like this that will keep your users happy and you off the support phone.

Extending our Application with Plugins (of all languges!)

Creating applications is fun, but extending applications is the most fun of all. This application is just begging to be extended. Our application is listening for key fob activity via the PresenceManager, which will send an event when it detects activity from our USB receiver. Now we want the application to announce these "presence events" to any one who cares to listen—that means all of us.

public void HandlePresenceNotification(PresenceNotificationEventArgs e)
{
    foreach(Plugin plugin in Plugins)
    {
        if(plugin.Enabled)
        {
            plugin.Worker.HandlePresenceNotification(e);
        }
    }
}
To create a plugin, we create a new project from within Visual Studio. This time we'll use Visual Basic. We'll create a plugin that will put a message in the Windows Event Log everytime a KeyFob message is received. This will be useful not only for auditing, but debugging. It will also provide a record of anyone else wearing a KeyFob who might walk by my desk.

Every plugin will include a reference to our UsbSecurity.Core assembly. Note that even though the Core assembly is written in C#, it can be utilized by any .NET language like VB. Our UsbSecurity.Core assembly includes a base class that plugins must derive from called PresencePluginBase. If we look at PresencePluginBase in the Object Browser we can see that it provides us with a number of virtual methods like HandlePresenceNotification and WorkstationLocked that we can take advantage of.

Click here for larger image

(click image to zoom)

We'll start by deriving our class from PresensePluginBase after adding a reference to UsbWireless.Core. We also import the System.Diagnostics namespace so we can use the EventLog class from the BCL.

Imports System
Imports UsbWirelessSecurity
Imports System.Text
Imports System.Diagnostics
 
Namespace DefaultPlugins
 
    'When the host exe finds us, point them to our Configurator!
    <PresencePluginConfigurator("Event Logging Plugin")> _
    Class EventLoggerPlugin
        Inherits PresencePluginBase
 
    End Class
End Namespace
In addition to a base class for plugins, we require a custom attribute to be placed on each plugin class. Since each plugin will appear in a ListBox in our Application's UI, we'd like to know what the plugin developer wants displayed. A custom attribute is an easy way for the plugin developer to add a "post-it note" in their code to let us know additional information. Note the <PresencePluginConfigurator("Event Logging Plugin"> attribute in the VB code above.

Next we'll override each of the virtual methods from PresencePluginBase and log the details of each message to the EventLog.

Imports System
Imports UsbWirelessSecurity
Imports System.Text
Imports System.Runtime.InteropServices
 
Namespace DefaultPlugins
 
    'When the host exe finds us, point them to our Configurator!
    <PresencePluginConfigurator("Event Logging Plugin")> _
    Class EventLoggerPlugin
        Inherits PresencePluginBase
 
        Dim logName As String = "USB Wireless Security"
 
        Public Overrides Sub HandleMessage(ByVal m As UsbWirelessSecurity.KeyFobMessage)
            MyBase.HandleMessage(m)
 
            If (Not m.MessageType = KeyFobMessageType.Heartbeat) Then
                Using aLog As New EventLog(logName)
                aLog.Source = logName
                aLog.WriteEntry( _ 
                     String.Format("Message Received: Device {0} reports {1}.", _ 
                     m.SerialNumber, m.MessageType.ToString()))
                End Using
            End If
        End Sub
 
        Public Overrides Sub WorkstationLocked()
            MyBase.WorkstationLocked()
            Using aLog As New EventLog(logName)
            aLog.Source = logName
            aLog.WriteEntry("Workstation Locked")
            End Using
        End Sub
 
        Public Overrides Sub WorkstationUnlocked()
            MyBase.WorkstationUnlocked()
            Using aLog As New EventLog(logName)
            aLog.Source = logName
            aLog.WriteEntry("Workstation Unlocked")
            End Using
        End Sub
 
        Public Overrides Sub HandlePresenceNotification(ByVal e As UsbWirelessSecurity.PresenceNotificationEventArgs)
            MyBase.HandlePresenceNotification(e)
 
            If (Not e.NotificationType = PresenceNotificationType.Heartbeat) Then
                Using aLog As New EventLog(logName)
                aLog.Source = logName
                aLog.WriteEntry( _
                     String.Format("Presence Received: Device {0} reports {1}.", _
                     e.KeyFob.SerialNumber, e.NotificationType.ToString()))
                End Using
            End If
        End Sub
    End Class
End Namespace
In order to prevent the EventLog filling with a heartbeat message every 250ms, we ignore those messages.

Click here for larger image

(click image to zoom)

Conclusion

With a clean hardware abstraction layer and a plugin architecture, you can enable your code-savvy users to extend your application with new functionality. Here's some ideas that we've been kicking around to extend the USB Wireless Security Application. Bryan and I hope that folks will take up the challenge and begin to exploit this great little device using Visual Studio.

  • Send an email or SMS when the device leaves or returns

  • Start the default screensaver

  • Save all your files

  • Start Defragmenting or start Drive Cleanup

  • Set Skype to "Away" when you leave

  • Stop playing all music applications

  • Create a centralized Web Service that each system calls when it sees a key fob to create a companywide "presence notification service."

Enjoy expanding on the existing project and remember not to fear the words "Some Assembly Required!"


Big thanks to Bryan Batchelder for the original idea and for the amazing work on the hardware abstraction layer to get the USB key fob to be heard from .NET!


Scott Hanselman is the Chief Architect at the Corillian Corporation, an eFinance enabler. He has twelve years' experience developing software in C, C++, VB, COM, and most recently in VB.NET and C#. Scott is proud to be a Microsoft RD and MVP. He is co-author of a new book on ASP.NET 2.0 with Bill Evjen et al., which will be released later in 2005. He spoke this year on Code Generation and Software Factories at TechEd 2005 in Orlando. His thoughts on the Zen of .NET, Programming and Web Services can be found on his blog at http://www.computerzen.com.


Bryan Batchelder is the Head of Research & Development for PatchAdvisor, Inc., a vulnerability intelligence provider and security assessment company. He is also an architect/lead developer for Greenline Systems, a supply chain risk management solution provider, working with DHS to help secure all cargo entering the United States. He has 8 years' experience developing software in C++, Java, ColdFusion, ASP, and these days exclusively in .NET and C#. His thoughts on computer security, risk management, and software development, can be found on his blog at http://labs.patchadvisor.com/blogs/bryan.

Top of Page Top of Page


© 2006 Microsoft Corporation. All rights reserved. Terms of Use |Trademarks |Privacy Statement
Microsoft