Hi, I’m Kristina Hotz, a Program Manager on the USB team.  In this post, I’ll explain how you can create a container ID for a USB device by using the same mechanism as Windows 7.

 

You will find the information useful if you are developing a USB driver stack that replaces the Microsoft-provided USB driver stack or if you are a USB device manufacturer and would like to know how your device is recognized by the Windows 7 version of the operating system.

 

A container ID is an identification string that is generated by the USB driver stack. The string is unique to a physical device. To view all physical devices connected to your computer, from Start, select Devices and Printers. A physical device can expose one or more functional devices. For a single function device, the icon in Devices and Printers represents the physical device and its functional device. If you have a multi-function device (for example, a printer/scanner/fax machine), you will notice an icon that represents the physical device (for example, the printer/scanner/fax machine appears as a printer). That is because Windows uses container IDs to group all functional devices associated with the physical device. 

 

After a USB device is connected to the computer, the USB driver stack (specifically, the bus driver) starts enumerating device nodes (devnode) for each functional device associated with the physical device. The bus driver then assigns a container ID for each devnode. The container ID is a property of a devnode, and is specified through a globally unique identifier (GUID). That GUID is set as a string property on a devnode.  All devnodes originating from a physical device must have the same container ID.  

 

For an external device’s devnode, the bus driver obtains the container ID by one of the following ways:

·         Reading the Microsoft OS ContainerID descriptor supplied by the device.  For more information, see Using Microsoft OS ContainerID Descriptors.

·         Generating a container ID by hashing certain device information. (See How to Generate a Container ID String)

·         Generating a random GUID for the container ID.

·         Inheriting the container ID of the parent devnode.

Note: Windows uses ACPI to determine whether the physical device is an external device or internal device.  An internal device’s devnode always inherits the container ID of the computer, i.e. its parent devnode.

For a flowchart which outlines the details of the assignment process, see How USB Devices are Assigned Container IDs.

How to Generate a Container ID String

The Microsoft USB bus driver generates a unique container ID string for a device when all of the following conditions are true:

·         The USB bus driver reports the device’s port as not-removable,

·         The device has a valid serial number, and

·         The device does not provide the Microsoft OS ContainerID descriptor.

To generate a container ID, the bus driver creates a hash by using the BCrypt Cryptographic APIs. The BCrypt functions are declared in the header bcrypt.h; exported by the library ksecdd.lib. 

If you are developing a driver stack for a USB 3.0 device, in your response to the IRP_MN_QUERY_ID request, provide a hash for the container ID. Make sure that you follow the steps in this section to create the hash. Failure to do so might result in multiple entries in Devices and Printers when you move your device from a USB 2.0 port to a USB 3.0 port.

To create the container ID hash, you need the following set of information that can be obtained from the Device Descriptor.

Note: You retrieve the Device Descriptor by using a GET_DESCRIPTOR control request.

·         Vendor ID indicated by the idVendor field.

·         Product ID indicated by the idProduct field.

·         Device release number indicated by the bcdDevice field.

·         USB serial number indicated by the iSerialNumber field.

The following procedure describes the steps required to create the hash.

1.       Create an empty string and populate it with the device’s Vendor ID, Product ID, and device release number.  Each of those identifiers is represented in the string as four-character hexadecimal values. For example, “045E07730110” indicates that 045E is the Vendor ID, 0773 is the Product ID, and 0110 signifies a USB 1.1 device.

2.       Append the USB serial number to the string.

3.       Generate a GUID for your USB container ID.

4.       Call BCryptOpenAlgorithmProvider to open the SHA1 algorithm provider.

5.       Call BCryptCreateHash to create a handle for the hash operation.

6.       Call BCryptHashData  to hash the USB container ID GUID (created in step 2 )

7.       Call BCryptHashData  to hash the string (created in step 1). Make sure you use the same handle (created in step 5) for both the hash operations.

8.       Call BCryptFinishHash to retrieve the hash result.

9.       Copy the resulting hash into a GUID variable and make the following modifications:

·         Clear the version number.

·         Set the version to name-based SHA 1 indicated by 5.

·         Clear the variant bits to make sure that the container ID GUID conforms to the UUID specification.        

10.   Call RtlStringFromGUID to convert the GUID to a string.

After your bus driver receives the IRP_MN_QUERY_ID request, check the Parameters.QueryId.IdType member of the IO_STACK_LOCATION. If that member is set to BusQueryContainerID, return the string containing the GUID in response to the IRP.

The following code example shows how to create the hash for the container ID. The example assumes that you have already retrieved the Device Descriptor and its serial number from the device.

Input Parameters:

DeviceDescriptor - USB Device Descriptor

SerialNumberId - UNICODE_STRING containing device serial number

Output Parameters:

containerIdString - UNICODE_STRING containing generated container ID string

 

// {4B06FD46-C84E-4664-9C65-0C86D9047A0C}  

static const GUID GUID_USB_CONTAINER_ID_NAME_SPACE =   

{ 0x4b06fd46, 0xc84e, 0x4664, { 0x9c, 0x65, 0xc, 0x86, 0xd9, 0x4, 0x7a, 0xc } };

 

void CreateContainerIDString(USB_DEVICE_DESCRIPTOR DeviceDescriptor

                             UNICODE_STRING SerialNumberId)

{ 

    GUID                    containerId;

    NTSTATUS                status;

    BCRYPT_HASH_HANDLE      hHash;

    DWORD                   dwcb;

    DWORD                   dwcbResult;

    PUCHAR                  pbHashObject;

    PUCHAR                  pbHash;

    BCRYPT_ALG_HANDLE       hContainerIDHashAlg;

    ULONG                   length;

    UNICODE_STRING          uniqueString;

    UNICODE_STRING          containerIDString;

 

 

    status = STATUS_SUCCESS;

    hHash = NULL;

    dwcb = 0;

    dwcbResult = 0;

    pbHashObject = NULL;

    pbHash = NULL;

    hContainerIDHashAlg = NULL;   

 

    RtlZeroMemory(&uniqueString,

        sizeof(uniqueString));

 

    RtlZeroMemory(&containerIDString,

        sizeof(containerIDString));

 

 

    length = sizeof(L"vvvvpppprrrr\0") + SerialNumberId.LengthInBytes;

 

    uniqueString.Buffer = ExAllocatePoolWithTag(NonPagedPool,

        length,

        HUB_TAG);

 

    if (uniqueString.Buffer == NULL)

    {

        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;

    }

 

    uniqueString.MaximumLength = (USHORT)length;

    uniqueString.Length = 0;

 

    status = RtlUnicodeStringPrintf(&uniqueString,

        L"%.4X%.4X%.4X",

        DeviceDescriptor.idVendor,

        DeviceDescriptor.idProduct,

        DeviceDescriptor.bcdDevice);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    // Append the serial number to the string if the buffer is valid

    if (SerialNumberId.Buffer)

    {

        status = RtlUnicodeStringCbCatStringN(&uniqueString,

            SerialNumberId.Buffer,

            SerialNumberId.LengthInBytes);

        if (!NT_SUCCESS(status))

        {

            goto Exit;

        }

    }

 

    // Open the the SHA1 algorithm provider

    status = BCryptOpenAlgorithmProvider(&hContainerIDHashAlg,

        BCRYPT_SHA1_ALGORITHM,

        MS_PRIMITIVE_PROVIDER,

        BCRYPT_PROV_DISPATCH);

 

    if (!NT_SUCCESS(status))

    {

        hContainerIDHashAlg = NULL;

        goto Exit;

    }

 

    // Create a handle for the hash operation

    status = BCryptGetProperty(hContainerIDHashAlg,

        BCRYPT_OBJECT_LENGTH,

        (PUCHAR)&dwcb,

        sizeof(dwcb),

        &dwcbResult,

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    if (sizeof(DWORD) != dwcbResult)

    {

        status = STATUS_INVALID_BUFFER_SIZE;

        goto Exit;

    }

 

    pbHashObject = ExAllocatePoolWithTag(NonPagedPool,

        dwcb,

        HUB_TAG);

 

    if (NULL == pbHashObject)

    {

        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;

    }

 

    status = BCryptCreateHash(hContainerIDHashAlg,

        &hHash,

        pbHashObject,

        dwcb,

        NULL,

        0,

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    // Hash in the USB Container ID name space

    status = BCryptHashData(hHash,

        (PUCHAR)&GUID_USB_CONTAINER_ID_NAME_SPACE,

        sizeof(GUID_USB_CONTAINER_ID_NAME_SPACE),

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    // Hash in the string that contains device information

    status = BCryptHashData(hHash,

        (PUCHAR)uniqueString.Buffer,

        (ULONG) uniqueString.Length,

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    // Get the resulting hash

    status = BCryptGetProperty(hContainerIDHashAlg,

        BCRYPT_HASH_LENGTH,

        (PUCHAR)&dwcb,

        sizeof(dwcb),

        &dwcbResult,

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    if (sizeof(DWORD) != dwcbResult)

    {

        status = STATUS_INVALID_PARAMETER;

        goto Exit;

    }

 

    if (sizeof(GUID) > dwcb)

    {

        status = STATUS_INVALID_PARAMETER;

        goto Exit;

    }

 

    pbHash = ExAllocatePoolWithTag(NonPagedPool,

        dwcb,

        HUB_TAG);

 

    if (NULL == pbHash)

    {

        status = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;

    }

 

    status = BCryptFinishHash(hHash,

        (PUCHAR)pbHash,

        dwcb,

        0);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

    // Copy the resulting hash into the container ID string

    // and set the version number and the variant fields.

 

    RtlCopyMemory(containerID,

        pbHash,

        sizeof(GUID));

 

    containerID.Data3 &= 0x0FFF;     // Clear the version number

    containerID.Data3 |= (5 << 12);  // Set version = (name-based SHA1) = 5

    containerID.Data4[0] &= 0x3F;    // clear the variant bits

    containerID.Data4[0] |= 0x80;           

 

    status = RtlStringFromGUID(containerID,

        &containerIDString);

 

    if (!NT_SUCCESS(status))

    {

        goto Exit;

    }

 

Exit:

 

    if (hHash)

    {

        BCryptDestroyHash(hHash);

    }

 

    if (pbHash)

    {

        ExFreePool(pbHash);

    }

 

    if(pbHashObject)

    {

        ExFreePool(pbHashObject);

    }

 

    if (uniqueString.Buffer)

    {

        ExFreePool(uniqueString.Buffer);

    }

 

    if (hContainerIDHashAlg)

    {

        BCryptCloseAlgorithmProvider(hContainerIDHashAlg,0);

    }

}

Resources

Multifunction Device Support and Device Container Groupings in Windows 7

Container IDs for USB Devices

Microsoft OS Descriptors