/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** \file
  This file consists of implementation of class AndroidUsbDeviceObject that
  encapsulates an extension for KMDF device (FDO) object.
*/
#pragma data_seg()
#pragma code_seg()

#include "precomp.h"
#include "android_usb_device_object.h"
#include "android_usb_file_object.h"
#include "android_usb_device_file_object.h"
#include "android_usb_pipe_file_object.h"
#include "android_usb_bulk_file_object.h"
#include "android_usb_interrupt_file_object.h"

#pragma data_seg()

/// Buffer for bulk read pipe name
const WCHAR bulk_read_pipe_str[] = L"\\" DEVICE_BULK_READ_PIPE_NAME;

/// Unicode string for bulk read pipe name
UNICODE_STRING bulk_read_pipe_name = {
  sizeof(bulk_read_pipe_str) - sizeof(WCHAR),
  sizeof(bulk_read_pipe_str) - sizeof(WCHAR),
  const_cast<PWSTR>(bulk_read_pipe_str)
};

/// Buffer for bulk write pipe name
const WCHAR bulk_write_pipe_str[] = L"\\" DEVICE_BULK_WRITE_PIPE_NAME;

/// Unicode string for bulk write pipe name
UNICODE_STRING bulk_write_pipe_name = {
  sizeof(bulk_write_pipe_str) - sizeof(WCHAR),
  sizeof(bulk_write_pipe_str) - sizeof(WCHAR),
  const_cast<PWSTR>(bulk_write_pipe_str)
};

/// Buffer for an index-based pipe name prefix
const WCHAR index_pipe_prefix_str[] = L"\\" DEVICE_PIPE_NAME_PREFIX;

/// Unicode string for index-based pipe name prefix
UNICODE_STRING index_pipe_prefix = {
  sizeof(index_pipe_prefix_str) - sizeof(WCHAR),
  sizeof(index_pipe_prefix_str) - sizeof(WCHAR),
  const_cast<PWSTR>(index_pipe_prefix_str)
};

/// GUID that sets class ID for our device
const GUID android_guid = ANDROID_USB_CLASS_ID;

#pragma code_seg("PAGE")

AndroidUsbDeviceObject::AndroidUsbDeviceObject()
    : AndroidUsbWdfObject(AndroidUsbWdfObjectTypeDevice),
      wdf_target_device_(NULL),
      wdf_usb_interface_(NULL),
      serial_number_handle_(NULL),
      serial_number_char_len_(0),
      configured_pipes_num_(0),
      bulk_read_pipe_index_(INVALID_UCHAR),
      bulk_write_pipe_index_(INVALID_UCHAR),
      configuration_descriptor_(NULL) {
  ASSERT_IRQL_PASSIVE();
}

#pragma code_seg()

AndroidUsbDeviceObject::~AndroidUsbDeviceObject() {
  ASSERT_IRQL_LOW_OR_DISPATCH();
  if (NULL != serial_number_handle_)
    WdfObjectDelete(serial_number_handle_);
}

#pragma code_seg("PAGE")

NTSTATUS AndroidUsbDeviceObject::CreateFDODevice(PWDFDEVICE_INIT device_init) {
  ASSERT_IRQL_PASSIVE();

  ASSERT(!IsTaretDeviceCreated());
  if (IsTaretDeviceCreated())
    return STATUS_INTERNAL_ERROR;

  // Initialize our object attributes first
  WDF_OBJECT_ATTRIBUTES device_attr;
  NTSTATUS status = InitObjectAttributes(&device_attr, NULL);
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  // Initialize the pnp_power_callbacks structure.  Callback events for PnP
  // and Power are specified here. If we don't supply any callbacks, the
  // KMDF will take appropriate default actions for an FDO device object.
  // EvtDevicePrepareHardware and EvtDeviceReleaseHardware are major entry
  // points for initializing / cleaning up our device. Probably, we can leave
  // the rest to the framework.
  WDF_PNPPOWER_EVENT_CALLBACKS pnp_power_callbacks;
  WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnp_power_callbacks);
  pnp_power_callbacks.EvtDevicePrepareHardware =
    EvtDevicePrepareHardwareEntry;
  pnp_power_callbacks.EvtDeviceReleaseHardware =
    EvtDeviceReleaseHardwareEntry;
  WdfDeviceInitSetPnpPowerEventCallbacks(device_init, &pnp_power_callbacks);

  // Initialize the request attributes to specify the context size and type
  // for every request created by framework for this device.
  WDF_OBJECT_ATTRIBUTES request_attr;
  WDF_OBJECT_ATTRIBUTES_INIT(&request_attr);
  WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&request_attr, AndroidUsbWdfRequestContext);
  WdfDeviceInitSetRequestAttributes(device_init, &request_attr);

  // Initialize WDF_FILEOBJECT_CONFIG_INIT struct to tell the KMDF that we are
  // interested in handling Create requests that get genereated when an
  // application or another kernel component opens a handle through the device.
  // We are not interested in receiving cleanup / close IRPs at this point.
  WDF_FILEOBJECT_CONFIG file_config;
  WDF_OBJECT_ATTRIBUTES file_attr;
  WDF_FILEOBJECT_CONFIG_INIT(&file_config,
                             EvtDeviceFileCreateEntry,
                             WDF_NO_EVENT_CALLBACK,
                             WDF_NO_EVENT_CALLBACK);
  WDF_OBJECT_ATTRIBUTES_INIT(&file_attr);
  WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&file_attr,
                                         AndroidUsbWdfObjectContext);
  file_attr.EvtCleanupCallback = AndroidUsbWdfObject::EvtCleanupCallbackEntry;
  file_attr.EvtDestroyCallback = AndroidUsbWdfObject::EvtDestroyCallbackEntry;
  // We will provide our own synchronization for file access
  file_attr.SynchronizationScope = WdfSynchronizationScopeNone;
  WdfDeviceInitSetFileObjectConfig(device_init, &file_config, &file_attr);

  // I/O type is buffered by default. It could be very inefficient if we have
  // large reads / writes through our device.
  WdfDeviceInitSetIoType(device_init, WdfDeviceIoDirect);

  // DeviceInit is completely initialized. So call the framework
  // to create the device and attach it to the lower stack.
  WDFDEVICE wdf_dev = NULL;
  status = WdfDeviceCreate(&device_init, &device_attr, &wdf_dev);
  ASSERT(NT_SUCCESS(status) && (NULL != wdf_dev));
  if (!NT_SUCCESS(status))
    return status;

  // Save handle to the created device
  set_wdf_object(wdf_dev);

  // Tell the framework to set the SurpriseRemovalOK in the DeviceCaps so
  // that we don't get the popup in usermode (on Win2K) when we surprise
  // remove the device.
  WDF_DEVICE_PNP_CAPABILITIES pnp_caps;
  WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnp_caps);
  pnp_caps.SurpriseRemovalOK = WdfTrue;
  WdfDeviceSetPnpCapabilities(wdf_device(), &pnp_caps);

  // Create our default queue object for this device to start receiving I/O
  status = CreateDefaultQueue();
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  // Register a device interface so that app can find our device and talk to it.
  status = WdfDeviceCreateDeviceInterface(wdf_device(), &android_guid, NULL);
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  // Initialize our extension to that device. We will do this at the very end
  // so we know that we successfully passed entire device create chain when
  // we are called with other callbacks to that device.
  status = InitializeContext();
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  return STATUS_SUCCESS;
}

NTSTATUS AndroidUsbDeviceObject::ResetDevice() {
  ASSERT_IRQL_PASSIVE();

  if (!IsTaretDeviceCreated())
    return STATUS_SUCCESS;

  // Reset the device
  NTSTATUS status =
    status = WdfUsbTargetDeviceResetPortSynchronously(wdf_target_device());

  // !!!!! Note that after the call to WdfUsbTargetDeviceResetPortSynchronously
  // this object may be no longer valid !!!!!

  if (!NT_SUCCESS(status))
    GoogleDbgPrint("\n!!!!! AndroidUsbDeviceObject::ResetDevice failed %X", status);

  return status;
}

NTSTATUS AndroidUsbDeviceObject::OnEvtDevicePrepareHardware(
    WDFCMRESLIST resources_raw,
    WDFCMRESLIST resources_translated) {
  ASSERT_IRQL_PASSIVE();

  // Create a USB device handle so that we can communicate with the underlying
  // USB stack. The wdf_target_device_ handle is used to query, configure, and
  // manage all aspects of the USB device. These aspects include device
  // properties, bus properties, and I/O creation and synchronization. This
  // call gets the device and configuration descriptors and stores them in
  // wdf_target_device_ object.
  NTSTATUS status = WdfUsbTargetDeviceCreate(wdf_device(),
                                             WDF_NO_OBJECT_ATTRIBUTES,
                                             &wdf_target_device_);
  ASSERT(NT_SUCCESS(status) && (NULL != wdf_target_device_));
  if (!NT_SUCCESS(status))
    return status;

  // Retrieve USBD version information, port driver capabilites and device
  // capabilites such as speed, power, etc.
  WDF_USB_DEVICE_INFORMATION_INIT(&usb_device_info_);
  status = WdfUsbTargetDeviceRetrieveInformation(wdf_target_device(),
                                                 &usb_device_info_);
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  WdfUsbTargetDeviceGetDeviceDescriptor(wdf_target_device(),
                                        &usb_device_descriptor_);
#if DBG
  PrintUsbTargedDeviceInformation(usb_device_info());
  PrintUsbDeviceDescriptor(&usb_device_descriptor_);
#endif  // DBG

  // Save device serial number
  status =

    WdfUsbTargetDeviceAllocAndQueryString(wdf_target_device(),

                                          WDF_NO_OBJECT_ATTRIBUTES,

                                          &serial_number_handle_,

                                          &serial_number_char_len_,

                                          usb_device_descriptor_.iSerialNumber,

                                          0x0409);  // English (US)

  if (!NT_SUCCESS(status))

    return status;



#if DBG

  UNICODE_STRING ser_num;

  ser_num.Length = serial_number_byte_len();

  ser_num.MaximumLength = ser_num.Length;

  ser_num.Buffer = const_cast<WCHAR*>

    (serial_number());

  GoogleDbgPrint("\n*** Device serial number %wZ", &ser_num);

#endif  // DBG



  // Configure our device now
  status = ConfigureDevice();
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  // Select device interfaces
  status = SelectInterfaces();
  if (!NT_SUCCESS(status))
    return status;

  return status;
}

NTSTATUS AndroidUsbDeviceObject::OnEvtDeviceReleaseHardware(
    WDFCMRESLIST resources_translated) {
  ASSERT_IRQL_PASSIVE();

  // It's possible that Preparehardware failed half way thru. So make
  // sure the target device exists.
  if (!IsTaretDeviceCreated())
    return STATUS_SUCCESS;

  // Cancel all the currently queued I/O. This is better than sending an
  // explicit USB abort request down because release hardware gets
  // called even when the device surprise-removed.
  WdfIoTargetStop(WdfUsbTargetDeviceGetIoTarget(wdf_target_device()),
                  WdfIoTargetCancelSentIo);

  // Unselect all selected configurations
  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS config_params;
  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_DECONFIG(&config_params);

  NTSTATUS status = WdfUsbTargetDeviceSelectConfig(wdf_target_device(),
                                                   WDF_NO_OBJECT_ATTRIBUTES,
                                                   &config_params);
  ASSERT(NT_SUCCESS(status) || (STATUS_DEVICE_NOT_CONNECTED == status));
  return status;
}

void AndroidUsbDeviceObject::OnEvtDeviceFileCreate(WDFREQUEST request,
                                                   WDFFILEOBJECT wdf_fo) {
  ASSERT_IRQL_PASSIVE();
  ASSERT(IsInterfaceSelected());
  if (!IsInterfaceSelected()) {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_STATE);
    return;
  }

  PUNICODE_STRING file_name = WdfFileObjectGetFileName(wdf_fo);
  ASSERT(NULL != file_name);
  if (NULL == file_name) {
    WdfRequestComplete(request, STATUS_OBJECT_NAME_INVALID);
    return;
  }

  WDFUSBPIPE wdf_pipe_obj = NULL;
  WDF_USB_PIPE_INFORMATION pipe_info;

  // TODO: Share / access check here?

  // Lets see if this is a device open
  if (0 != file_name->Length) {
    // This is a pipe open. Lets retrieve pipe index from the name
    UCHAR pipe_index = GetPipeIndexFromFileName(file_name);
    if (INVALID_UCHAR == pipe_index) {
      GoogleDbgPrint("\n!!!!! There is no pipe index for file %wZ", file_name);
      WdfRequestComplete(request, STATUS_OBJECT_NAME_INVALID);
      return;
    }

    // Make sure that pipe index doesn't exceed number of pipes
    if (pipe_index >= configured_pipes_num()) {
      WdfRequestComplete(request, STATUS_OBJECT_NAME_NOT_FOUND);
      return;
    }

    // Retrieve the pipe along with the pipe info
    WDF_USB_PIPE_INFORMATION_INIT(&pipe_info);
    wdf_pipe_obj = WdfUsbInterfaceGetConfiguredPipe(wdf_usb_interface(),
                                                    pipe_index,
                                                    &pipe_info);
    if (NULL == wdf_pipe_obj) {
      GoogleDbgPrint("\n!!!!! There is no pipe for index %u for file %wZ",
                     pipe_index, file_name);
      WdfRequestComplete(request, STATUS_OBJECT_NAME_NOT_FOUND);
      return;
    }
  }

  // If we're here this must be either device open or pipe open
  ASSERT((NULL != wdf_pipe_obj) || (0 == file_name->Length));

  // Create our file object extension for this file
  AndroidUsbFileObject* wdf_file_ext = NULL;
  NTSTATUS status;

  if (0 == file_name->Length) {
    // This is a device FO. Create wrapper for device FO
    ASSERT(NULL == wdf_pipe_obj);
    wdf_file_ext = new(NonPagedPool, GANDR_POOL_TAG_DEVICE_FO)
      AndroidUsbDeviceFileObject(this, wdf_fo);
    ASSERT(NULL != wdf_file_ext);
    if (NULL == wdf_file_ext) {
      WdfRequestComplete(request, STATUS_INSUFFICIENT_RESOURCES);
      return;
    }

    // Initialize extension
    status = wdf_file_ext->Initialize();
    if (!NT_SUCCESS(status)) {
      delete wdf_file_ext;
      WdfRequestComplete(request, status);
      return;
    }
  } else {
    // This is a pipe file. Create and initialize appropriate extension for it.
    status =
      CreatePipeFileObjectExt(wdf_fo, wdf_pipe_obj, &pipe_info, &wdf_file_ext);
    ASSERT((NULL != wdf_file_ext) || !NT_SUCCESS(status));
    if (!NT_SUCCESS(status)) {
      WdfRequestComplete(request, status);
      return;
    }
  }
  ASSERT(GetAndroidUsbFileObjectFromHandle(wdf_fo) == wdf_file_ext);
  WdfRequestComplete(request, STATUS_SUCCESS);
}

NTSTATUS AndroidUsbDeviceObject::EvtDevicePrepareHardwareEntry(
    WDFDEVICE wdf_dev,
    WDFCMRESLIST resources_raw,
    WDFCMRESLIST resources_translated) {
  ASSERT_IRQL_PASSIVE();

  // Get our wrapper for the device and redirect event to its handler
  AndroidUsbDeviceObject* wdf_device_ext =
    GetAndroidUsbDeviceObjectFromHandle(wdf_dev);
  ASSERT(NULL != wdf_device_ext);
  return (NULL != wdf_device_ext) ?
    wdf_device_ext->OnEvtDevicePrepareHardware(resources_raw,
                                               resources_translated) :
    STATUS_INVALID_DEVICE_REQUEST;
}

NTSTATUS AndroidUsbDeviceObject::EvtDeviceReleaseHardwareEntry(
    WDFDEVICE wdf_dev,
    WDFCMRESLIST resources_translated) {
  ASSERT_IRQL_PASSIVE();

  // Get our wrapper for the device and redirect event to its handler
  AndroidUsbDeviceObject* wdf_device_ext =
    GetAndroidUsbDeviceObjectFromHandle(wdf_dev);
  ASSERT(NULL != wdf_device_ext);
  return (NULL != wdf_device_ext) ?
    wdf_device_ext->OnEvtDeviceReleaseHardware(resources_translated) :
    STATUS_INVALID_DEVICE_REQUEST;
}

void AndroidUsbDeviceObject::EvtDeviceFileCreateEntry(
    WDFDEVICE wdf_dev,
    WDFREQUEST request,
    WDFFILEOBJECT wdf_fo) {
  ASSERT_IRQL_PASSIVE();

  ASSERT(NULL != wdf_fo);
  if (NULL == wdf_fo) {
    WdfRequestComplete(request, STATUS_INVALID_PARAMETER);
    return;
  }

  // Get our wrapper for the device and redirect event to its handler
  AndroidUsbDeviceObject* wdf_device_ext =
    GetAndroidUsbDeviceObjectFromHandle(wdf_dev);
  ASSERT(NULL != wdf_device_ext);
  if (NULL != wdf_device_ext) {
    wdf_device_ext->OnEvtDeviceFileCreate(request, wdf_fo);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

#pragma code_seg()

void AndroidUsbDeviceObject::OnEvtIoRead(WDFREQUEST request,
                                         size_t length) {
  ASSERT_IRQL_LOW_OR_DISPATCH();
  ASSERT(IsInterfaceSelected());
  if (!IsInterfaceSelected()) {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_STATE);
    return;
  }

  // Get our file extension and dispatch this event to its handler
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->OnEvtIoRead(request, length);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::OnEvtIoWrite(WDFREQUEST request,
                                          size_t length) {
  ASSERT_IRQL_LOW_OR_DISPATCH();
  ASSERT(IsInterfaceSelected());
  if (!IsInterfaceSelected()) {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_STATE);
    return;
  }

  // Get our file extension and dispatch this event to its handler
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->OnEvtIoWrite(request, length);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::OnEvtIoDeviceControl(WDFREQUEST request,
                                                  size_t output_buf_len,
                                                  size_t input_buf_len,
                                                  ULONG ioctl_code) {
  ASSERT_IRQL_LOW_OR_DISPATCH();
  ASSERT(IsInterfaceSelected());
  if (!IsInterfaceSelected()) {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_STATE);
    return;
  }

  // Get our file extension and dispatch this event to its handler
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->OnEvtIoDeviceControl(request,
                                       output_buf_len,
                                       input_buf_len,
                                       ioctl_code);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::EvtIoReadEntry(WDFQUEUE queue,
                                            WDFREQUEST request,
                                            size_t length) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Get our file extension and dispatch this event to the appropriate handler
  // inside our device extension.
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->device_object()->OnEvtIoRead(request, length);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::EvtIoWriteEntry(WDFQUEUE queue,
                                            WDFREQUEST request,
                                            size_t length) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Get our file extension and dispatch this event to the appropriate handler
  // inside our device extension.
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->device_object()->OnEvtIoWrite(request, length);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::EvtIoDeviceControlEntry(WDFQUEUE queue,
                                                    WDFREQUEST request,
                                                    size_t output_buf_len,
                                                    size_t input_buf_len,
                                                    ULONG ioctl_code) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Get our file extension and dispatch this event to the appropriate handler
  // inside our device extension.
  AndroidUsbFileObject* wdf_file_ext =
    GetAndroidUsbFileObjectForRequest(request);
  ASSERT(NULL != wdf_file_ext);
  if (NULL != wdf_file_ext) {
    wdf_file_ext->device_object()->OnEvtIoDeviceControl(request,
                                                        output_buf_len,
                                                        input_buf_len,
                                                        ioctl_code);
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::OnGetUsbDeviceDescriptorCtl(WDFREQUEST request,
                                                         size_t output_buf_len) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Check the buffer first
  if (output_buf_len >= sizeof(USB_DEVICE_DESCRIPTOR)) {
    // Get the output buffer
    NTSTATUS status;
    void* ret_info = OutAddress(request, &status);
    ASSERT(NT_SUCCESS(status) && (NULL != ret_info));
    if (NT_SUCCESS(status)) {
      // Copy requested info into output buffer and complete request
      RtlCopyMemory(ret_info,
                    usb_device_descriptor(),
                    sizeof(USB_DEVICE_DESCRIPTOR));

      WdfRequestCompleteWithInformation(request,
                                        STATUS_SUCCESS,
                                        sizeof(USB_DEVICE_DESCRIPTOR));
    } else {
      WdfRequestComplete(request, status);
    }
  } else {
    WdfRequestCompleteWithInformation(request,
                                      STATUS_BUFFER_TOO_SMALL,
                                      sizeof(USB_DEVICE_DESCRIPTOR));
  }
}

void AndroidUsbDeviceObject::OnGetUsbConfigDescriptorCtl(WDFREQUEST request,
                                                         size_t output_buf_len) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  if (NULL != configuration_descriptor()) {
    // Check the buffer first
    if (output_buf_len >= sizeof(USB_CONFIGURATION_DESCRIPTOR)) {
      // Get the output buffer
      NTSTATUS status;
      void* ret_info = OutAddress(request, &status);
      ASSERT(NT_SUCCESS(status) && (NULL != ret_info));
      if (NT_SUCCESS(status)) {
        // Copy requested info into output buffer and complete request
        RtlCopyMemory(ret_info,
                      configuration_descriptor(),
                      sizeof(USB_CONFIGURATION_DESCRIPTOR));

        WdfRequestCompleteWithInformation(request,
                                          STATUS_SUCCESS,
                                          sizeof(USB_CONFIGURATION_DESCRIPTOR));
      } else {
        WdfRequestComplete(request, status);
      }
    } else {
      WdfRequestCompleteWithInformation(request,
                                        STATUS_BUFFER_TOO_SMALL,
                                        sizeof(USB_CONFIGURATION_DESCRIPTOR));
    }
  } else {
    WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
  }
}

void AndroidUsbDeviceObject::OnGetUsbInterfaceDescriptorCtl(WDFREQUEST request,
                                                            size_t output_buf_len) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Check the buffer first
  if (output_buf_len >= sizeof(USB_INTERFACE_DESCRIPTOR)) {
    // Get the output buffer
    NTSTATUS status;
    void* ret_info = OutAddress(request, &status);
    ASSERT(NT_SUCCESS(status) && (NULL != ret_info));
    if (NT_SUCCESS(status)) {
      // Copy requested info into output buffer and complete request
      RtlCopyMemory(ret_info,
                    interface_descriptor(),
                    sizeof(USB_INTERFACE_DESCRIPTOR));

      WdfRequestCompleteWithInformation(request,
                                        STATUS_SUCCESS,
                                        sizeof(USB_INTERFACE_DESCRIPTOR));
    } else {
      WdfRequestComplete(request, status);
    }
  } else {
    WdfRequestCompleteWithInformation(request,
                                      STATUS_BUFFER_TOO_SMALL,
                                      sizeof(USB_INTERFACE_DESCRIPTOR));
  }
}

void AndroidUsbDeviceObject::OnGetEndpointInformationCtl(
    WDFREQUEST request,
    size_t input_buf_len,
    size_t output_buf_len) {
  ASSERT_IRQL_LOW_OR_DISPATCH();

  // Check the buffers first
  if (input_buf_len < sizeof(AdbQueryEndpointInformation)) {
    WdfRequestComplete(request, STATUS_INVALID_BUFFER_SIZE);
    return;
  }

  if (output_buf_len < sizeof(AdbEndpointInformation)) {
    WdfRequestCompleteWithInformation(request,
                                      STATUS_BUFFER_TOO_SMALL,
                                      sizeof(AdbEndpointInformation));
    return;
  }

  // Get the output buffer
  NTSTATUS status;
  AdbEndpointInformation* ret_info = reinterpret_cast<AdbEndpointInformation*>
    (OutAddress(request, &status));
  ASSERT(NT_SUCCESS(status) && (NULL != ret_info));
  if (!NT_SUCCESS(status)) {
    WdfRequestComplete(request, status);
    return;
  }

  // Get the input buffer
  AdbQueryEndpointInformation* in = reinterpret_cast<AdbQueryEndpointInformation*>
    (InAddress(request, &status));
  ASSERT(NT_SUCCESS(status) && (NULL != in));
  if (!NT_SUCCESS(status)) {
    WdfRequestComplete(request, status);
    return;
  }

  // Lets see what exactly is queried
  UCHAR endpoint_index = in->endpoint_index;
  if (ADB_QUERY_BULK_WRITE_ENDPOINT_INDEX == endpoint_index)
    endpoint_index = bulk_write_pipe_index();
  else if (ADB_QUERY_BULK_READ_ENDPOINT_INDEX == endpoint_index)
    endpoint_index = bulk_read_pipe_index();

  // Make sure index is valid and within interface range
  if ((INVALID_UCHAR == endpoint_index) ||
      (endpoint_index >= configured_pipes_num())) {
    WdfRequestComplete(request, STATUS_NOT_FOUND);
    return;
  }

  // Get endpoint information
  WDF_USB_PIPE_INFORMATION pipe_info;
  WDF_USB_PIPE_INFORMATION_INIT(&pipe_info);
  WDFUSBPIPE wdf_pipe_obj =
      WdfUsbInterfaceGetConfiguredPipe(wdf_usb_interface(), endpoint_index, &pipe_info);
  if (NULL == wdf_pipe_obj) {
    WdfRequestComplete(request, STATUS_NOT_FOUND);
    return;
  }

  // Copy endpoint info to the output
  ret_info->max_packet_size = pipe_info.MaximumPacketSize;
  ret_info->endpoint_address = pipe_info.EndpointAddress;
  ret_info->polling_interval = pipe_info.Interval;
  ret_info->setting_index = pipe_info.SettingIndex;
  ret_info->endpoint_type = static_cast<AdbEndpointType>(pipe_info.PipeType);
  ret_info->max_transfer_size = pipe_info.MaximumTransferSize;

  WdfRequestCompleteWithInformation(request,
                                    STATUS_SUCCESS,
                                    sizeof(AdbEndpointInformation));
}

void AndroidUsbDeviceObject::OnGetSerialNumberCtl(WDFREQUEST request,
                                                  size_t output_buf_len) {
  ASSERT_IRQL_LOW();

  if (NULL == serial_number()) {
    // There is no serial number saved for this device!
    WdfRequestComplete(request, STATUS_INTERNAL_ERROR);
    return;
  }

  size_t expected_len = serial_number_byte_len() + sizeof(WCHAR);

  // Check the buffer first
  if (output_buf_len >= expected_len) {
    // Get the output buffer
    NTSTATUS status;
    WCHAR* ret_info = reinterpret_cast<WCHAR*>(OutAddress(request, &status));
    ASSERT(NT_SUCCESS(status) && (NULL != ret_info));
    if (NT_SUCCESS(status)) {
      // Copy serial number
      RtlCopyMemory(ret_info, serial_number(), serial_number_byte_len());
      ret_info[serial_number_char_len()] = L'\0';
      WdfRequestCompleteWithInformation(request, STATUS_SUCCESS, expected_len);
    } else {
      WdfRequestComplete(request, status);
    }
  } else {
    WdfRequestCompleteWithInformation(request,
                                      STATUS_BUFFER_TOO_SMALL,
                                      sizeof(expected_len));
  }
}

#pragma code_seg("PAGE")

NTSTATUS AndroidUsbDeviceObject::CreateDefaultQueue() {
  ASSERT_IRQL_PASSIVE();

  // Register I/O callbacks to tell the framework that we are interested
  // in handling WdfRequestTypeRead, WdfRequestTypeWrite, and
  // WdfRequestTypeDeviceControl requests. WdfIoQueueDispatchParallel means
  // that we are capable of handling all the I/O request simultaneously and we
  // are responsible for protecting data that could be accessed by these
  // callbacks simultaneously. This queue will be, by default, automanaged by
  // the framework with respect to PnP and Power events. That is, framework
  // will take care of queuing, failing, dispatching incoming requests based
  // on the current PnP / Power state of the device. We also need to register
  // a EvtIoStop handler so that we can acknowledge requests that are pending
  // at the target driver.
  WDF_IO_QUEUE_CONFIG io_queue_config;
  WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&io_queue_config,
                                         WdfIoQueueDispatchParallel);

  io_queue_config.EvtIoDeviceControl = EvtIoDeviceControlEntry;
  io_queue_config.EvtIoRead = EvtIoReadEntry;
  io_queue_config.EvtIoWrite = EvtIoWriteEntry;
  io_queue_config.AllowZeroLengthRequests = TRUE;
  // By default KMDF will take care of the power management of this queue
  io_queue_config.PowerManaged = WdfUseDefault;

  // Create queue object
  WDFQUEUE wdf_queue_obj = NULL;
  NTSTATUS status = WdfIoQueueCreate(wdf_device(),
                                     &io_queue_config,
                                     WDF_NO_OBJECT_ATTRIBUTES,
                                     &wdf_queue_obj);
  ASSERT(NT_SUCCESS(status) && (NULL != wdf_queue_obj));
  if (!NT_SUCCESS(status))
    return status;
  return STATUS_SUCCESS;
}

NTSTATUS AndroidUsbDeviceObject::ConfigureDevice() {
  ASSERT_IRQL_PASSIVE();

  ASSERT(IsTaretDeviceCreated());
  if (!IsTaretDeviceCreated())
    return STATUS_INTERNAL_ERROR;

  // In order to get the configuration descriptor we must first query for its
  // size (by supplying NULL for the descriptor's address), allocate enough
  // memory and then retrieve the descriptor.
  USHORT size = 0;

  // Query descriptor size first
  NTSTATUS status =
    WdfUsbTargetDeviceRetrieveConfigDescriptor(wdf_target_device(),
                                               WDF_NO_HANDLE,
                                               &size);
  ASSERT((status == STATUS_BUFFER_TOO_SMALL) || !NT_SUCCESS(status));
  if (status != STATUS_BUFFER_TOO_SMALL)
    return status;

  // Create a memory object and specify our device as the parent so that
  // it will be freed automatically along with our device.
  WDFMEMORY memory = NULL;
  WDF_OBJECT_ATTRIBUTES attributes;
  WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
  attributes.ParentObject = wdf_device();
  status = WdfMemoryCreate(&attributes,
                           NonPagedPool,
                           GANDR_POOL_TAG_DEV_CFG_DESC,
                           size,
                           &memory,
                           reinterpret_cast<PVOID*>(&configuration_descriptor_));
  ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

  // Now retrieve configuration descriptor
  status =
    WdfUsbTargetDeviceRetrieveConfigDescriptor(wdf_target_device(),
                                               configuration_descriptor_,
                                               &size);
  ASSERT(NT_SUCCESS(status) && (NULL != configuration_descriptor_));
  if (!NT_SUCCESS(status))
    return status;

#if DBG
  PrintConfigDescriptor(configuration_descriptor(), size);
#endif  // DBG

  return status;
}

NTSTATUS AndroidUsbDeviceObject::SelectInterfaces() {
  ASSERT_IRQL_PASSIVE();

  ASSERT(IsDeviceConfigured());
  if (!IsDeviceConfigured())
    return STATUS_INTERNAL_ERROR;

  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS config_params;
  PWDF_USB_INTERFACE_SETTING_PAIR pairs = NULL;
  // TODO: We need to find a way (possibly by looking at each
  // interface descriptor) to get index of the ADB interface in multiinterface
  // configuration.
  UCHAR adb_interface_index = 0;

  if (IsSingleInterfaceDevice()) {
    // Our device has only one interface, so we don't have to bother with
    // multiple interfaces at all.
    GoogleDbgPrint("\n********** Device reports single interface");
    // Select single interface configuration
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&config_params);
  } else {
    // Configure multiple interfaces
    ULONG num_interf = GetInterfaceCount();
    GoogleDbgPrint("\n********** Device reports %u interfaces",
             num_interf);

    // Allocate pairs for each interface
    pairs = new(PagedPool, GANDR_POOL_TAG_INTERF_PAIRS)
              WDF_USB_INTERFACE_SETTING_PAIR[num_interf];
    ASSERT(NULL != pairs);
    if (NULL == pairs)
      return STATUS_INSUFFICIENT_RESOURCES;

    adb_interface_index = 1;
    // Initialize each interface pair
    for (UCHAR pair = 0; pair < num_interf; pair++) {
      pairs[pair].SettingIndex = 0;
      pairs[pair].UsbInterface =
        WdfUsbTargetDeviceGetInterface(wdf_target_device(), pair);
      ASSERT(NULL != pairs[pair].UsbInterface);
      if (NULL == pairs[pair].UsbInterface) {
        delete[] pairs;
        return STATUS_INTERNAL_ERROR;
      }
    }

    // Select multiinterface configuration
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&config_params,
                                                                 (UCHAR)num_interf,
                                                                 pairs);
  }

  NTSTATUS status =
    WdfUsbTargetDeviceSelectConfig(wdf_target_device(),
                                   WDF_NO_OBJECT_ATTRIBUTES,
                                   &config_params);
  if (NULL != pairs)
    delete[] pairs;

  // ASSERT(NT_SUCCESS(status));
  if (!NT_SUCCESS(status))
    return status;

#if DBG
  PrintSelectedConfig(&config_params);
#endif  // DBG

  wdf_usb_interface_ =
    WdfUsbTargetDeviceGetInterface(wdf_target_device(), adb_interface_index);
  ASSERT(NULL != wdf_usb_interface_);
  if (NULL == wdf_usb_interface_)
    return STATUS_INTERNAL_ERROR;

  configured_pipes_num_ = WdfUsbInterfaceGetNumEndpoints(wdf_usb_interface(), 0);
  ASSERT(0 != configured_pipes_num_);

  // Cache selected interface descriptor
  BYTE setting_index =
    WdfUsbInterfaceGetConfiguredSettingIndex(wdf_usb_interface());

  WdfUsbInterfaceGetDescriptor(wdf_usb_interface(),
                               setting_index,
                               &interface_descriptor_);

#if DBG
  PrintInterfaceDescriptor(interface_descriptor());
#endif  // DBG

  // Iterate over pipes, decoding and saving info about bulk r/w pipes for
  // easier and faster addressing later on when they get opened
  for (UCHAR pipe = 0; pipe < configured_pipes_num(); pipe++) {
    WDF_USB_PIPE_INFORMATION pipe_info;
    WDF_USB_PIPE_INFORMATION_INIT(&pipe_info);
    WDFUSBPIPE wdf_pipe_obj =
      WdfUsbInterfaceGetConfiguredPipe(wdf_usb_interface(), pipe, &pipe_info);
    ASSERT(NULL != wdf_pipe_obj);
    if (NULL != wdf_pipe_obj) {
      if ((WdfUsbPipeTypeBulk  == pipe_info.PipeType) &&
          WDF_USB_PIPE_DIRECTION_IN(pipe_info.EndpointAddress)) {
        // This is a bulk read pipe
        ASSERT(!IsBulkReadPipeKnown());
        bulk_read_pipe_index_ = pipe;
      } else {
        ASSERT(!IsBulkWritePipeKnown());
        bulk_write_pipe_index_ = pipe;
      }
    }
#if DBG
    PrintPipeInformation(&pipe_info, pipe);
#endif  // DBG
  }

  // At the end we must have calculated indexes for both,
  // bulk read and write pipes
  ASSERT(!NT_SUCCESS(status) || (IsBulkReadPipeKnown() &&
                                 IsBulkWritePipeKnown()));

  return status;
}

UCHAR AndroidUsbDeviceObject::GetPipeIndexFromFileName(
    PUNICODE_STRING file_path) {
  ASSERT_IRQL_PASSIVE();
  ASSERT((NULL != file_path) && (0 != file_path->Length) && (NULL != file_path->Buffer));
  if ((NULL == file_path) ||
      (0 == file_path->Length) ||
      (NULL == file_path->Buffer)) {
    return INVALID_UCHAR;
  }

  // Lets check for explicit r/w pipe names
  if (0 == RtlCompareUnicodeString(file_path, &bulk_read_pipe_name, TRUE))
    return bulk_read_pipe_index();
  if (0 == RtlCompareUnicodeString(file_path, &bulk_write_pipe_name, TRUE))
    return bulk_write_pipe_index();

  // Lets check path format
  if (file_path->Length <= index_pipe_prefix.Length) {
    GoogleDbgPrint("\n!!!!! Bad format for pipe name: %wZ", file_path);
    return INVALID_UCHAR;
  }

  // Now when whe know that file_path->Length is sufficient lets match this
  // path with the prefix
  UNICODE_STRING prefix_match = *file_path;
  prefix_match.Length = index_pipe_prefix.Length;
  prefix_match.MaximumLength = prefix_match.Length;

  if (0 != RtlCompareUnicodeString(&prefix_match, &index_pipe_prefix, TRUE)) {
    GoogleDbgPrint("\n!!!!! Bad format for pipe name: %wZ", file_path);
    return INVALID_UCHAR;
  }

  // Prefix matches. Make sure that remaining chars are all decimal digits.
  // Pipe index begins right after the prefix ends.
  const ULONG index_begins_at = WcharLen(index_pipe_prefix.Length);
  const ULONG name_len = WcharLen(file_path->Length);
  for (ULONG index = index_begins_at; index < name_len; index++) {
    if ((file_path->Buffer[index] > L'9') ||
        (file_path->Buffer[index] < L'0')) {
      GoogleDbgPrint("\n!!!!! Bad format for pipe name: %wZ", file_path);
      return INVALID_UCHAR;
    }
  }

  // Parse the pipe#
  ULONG uval = 0;
  ULONG umultiplier = 1;

  // traversing least to most significant digits.
  for (ULONG index = name_len - 1; index >= index_begins_at; index--) {
    uval += (umultiplier * static_cast<ULONG>(file_path->Buffer[index] - L'0'));
    umultiplier *= 10;
  }

  return static_cast<UCHAR>(uval);
}

NTSTATUS AndroidUsbDeviceObject::CreatePipeFileObjectExt(
    WDFFILEOBJECT wdf_fo,
    WDFUSBPIPE wdf_pipe_obj,
    const WDF_USB_PIPE_INFORMATION* pipe_info,
    AndroidUsbFileObject** wdf_file_ext) {
  ASSERT_IRQL_PASSIVE();
  ASSERT((NULL != wdf_fo) && (NULL != wdf_pipe_obj) && (NULL != pipe_info) && (NULL != wdf_file_ext));
  if ((NULL == wdf_fo) || (NULL == wdf_pipe_obj) || (NULL == pipe_info) || (NULL == wdf_file_ext)) {
    return STATUS_INTERNAL_ERROR;
  }
  *wdf_file_ext = NULL;

  AndroidUsbPipeFileObject* wdf_pipe_file_ext = NULL;

  // We support only WdfUsbPipeTypeBulk and WdfUsbPipeTypeInterrupt files
  // at this point.
  switch (pipe_info->PipeType) {
    case WdfUsbPipeTypeBulk:
      wdf_pipe_file_ext = new(NonPagedPool, GANDR_POOL_TAG_BULK_FILE)
            AndroidUsbBulkPipeFileObject(this, wdf_fo, wdf_pipe_obj);
      break;

    case WdfUsbPipeTypeInterrupt:
      wdf_pipe_file_ext = new(NonPagedPool, GANDR_POOL_TAG_INTERRUPT_FILE)
          AndroidUsbInterruptPipeFileObject(this, wdf_fo, wdf_pipe_obj);
      break;;

    case WdfUsbPipeTypeIsochronous:
    case WdfUsbPipeTypeControl:
    case WdfUsbPipeTypeInvalid:
    default:
      return STATUS_OBJECT_TYPE_MISMATCH;
  }

  // If we reached here instance of a file wrapper must be created.
  ASSERT(NULL != wdf_pipe_file_ext);
  if (NULL == wdf_pipe_file_ext)
    return STATUS_INSUFFICIENT_RESOURCES;
    
  // Initialize the wrapper.
  NTSTATUS status = wdf_pipe_file_ext->InitializePipe(pipe_info);
  ASSERT(NT_SUCCESS(status));
  if (NT_SUCCESS(status)) {
    *wdf_file_ext = wdf_pipe_file_ext;
  } else {
    delete wdf_pipe_file_ext;
  }

  return STATUS_SUCCESS;
}

#if DBG
#pragma code_seg()

void AndroidUsbDeviceObject::PrintUsbDeviceDescriptor(
    const USB_DEVICE_DESCRIPTOR* desc) {
  GoogleDbgPrint("\n***** USB_DEVICE_DESCRIPTOR %p for device %p", desc, this);
  GoogleDbgPrint("\n      bDescriptorType    = %u", desc->bDescriptorType);
  GoogleDbgPrint("\n      bcdUSB             = x%02X", desc->bcdUSB);
  GoogleDbgPrint("\n      bDeviceClass       = x%02X", desc->bDeviceClass);
  GoogleDbgPrint("\n      bDeviceSubClass    = x%02X", desc->bDeviceSubClass);
  GoogleDbgPrint("\n      bDeviceProtocol    = x%02X", desc->bDeviceProtocol);
  GoogleDbgPrint("\n      bMaxPacketSize     = %u", desc->bMaxPacketSize0);
  GoogleDbgPrint("\n      idVendor           = x%04X", desc->idVendor);
  GoogleDbgPrint("\n      idProduct          = x%04X", desc->idProduct);
  GoogleDbgPrint("\n      bcdDevice          = x%02X", desc->bcdDevice);
  GoogleDbgPrint("\n      iManufacturer      = %u", desc->iManufacturer);
  GoogleDbgPrint("\n      iProduct           = %u", desc->iProduct);
  GoogleDbgPrint("\n      iSerialNumber      = %u", desc->iSerialNumber);
  GoogleDbgPrint("\n      bNumConfigurations = %u", desc->bNumConfigurations);
}

void AndroidUsbDeviceObject::PrintUsbTargedDeviceInformation(
    const WDF_USB_DEVICE_INFORMATION* info) {
  GoogleDbgPrint("\n***** WDF_USB_DEVICE_INFORMATION %p for device %p", info, this);
  GoogleDbgPrint("\n      HcdPortCapabilities               = x%08X", info->HcdPortCapabilities);
  GoogleDbgPrint("\n      Traits                            = x%08X", info->Traits);
  GoogleDbgPrint("\n      VersionInfo.USBDI_Version         = x%08X",
           info->UsbdVersionInformation.USBDI_Version);
  GoogleDbgPrint("\n      VersionInfo.Supported_USB_Version = x%08X",
           info->UsbdVersionInformation.Supported_USB_Version);
}

void AndroidUsbDeviceObject::PrintConfigDescriptor(
    const USB_CONFIGURATION_DESCRIPTOR* desc,
    ULONG size) {
  GoogleDbgPrint("\n***** USB_CONFIGURATION_DESCRIPTOR %p for device %p size %u",
           desc, this, size);
  GoogleDbgPrint("\n      bDescriptorType     = %u", desc->bDescriptorType);
  GoogleDbgPrint("\n      wTotalLength        = %u", desc->wTotalLength);
  GoogleDbgPrint("\n      bNumInterfaces      = %u", desc->bNumInterfaces);
  GoogleDbgPrint("\n      bConfigurationValue = %u", desc->bConfigurationValue);
  GoogleDbgPrint("\n      iConfiguration      = %u", desc->iConfiguration);
  GoogleDbgPrint("\n      bmAttributes        = %u", desc->bmAttributes);
  GoogleDbgPrint("\n      MaxPower            = %u", desc->MaxPower);
}

void AndroidUsbDeviceObject::PrintSelectedConfig(
    const WDF_USB_DEVICE_SELECT_CONFIG_PARAMS* config) {
  GoogleDbgPrint("\n***** WDF_USB_DEVICE_SELECT_CONFIG_PARAMS %p for device %p", config, this);
  GoogleDbgPrint("\n      Type = %u", config->Type);
  switch (config->Type) {
    case WdfUsbTargetDeviceSelectConfigTypeSingleInterface:
      GoogleDbgPrint("\n      SingleInterface:");
      GoogleDbgPrint("\n         NumberConfiguredPipes  = %u",
               config->Types.SingleInterface.NumberConfiguredPipes);
      GoogleDbgPrint("\n         ConfiguredUsbInterface = %p",
               config->Types.SingleInterface.ConfiguredUsbInterface);
      break;

    case WdfUsbTargetDeviceSelectConfigTypeMultiInterface:
      GoogleDbgPrint("\n      MultiInterface:");
      GoogleDbgPrint("\n         NumberInterfaces              = %u",
               config->Types.MultiInterface.NumberInterfaces);
      GoogleDbgPrint("\n         NumberOfConfiguredInterfaces  = %u",
               config->Types.MultiInterface.NumberOfConfiguredInterfaces);
      GoogleDbgPrint("\n         Pairs                         = %p",
               config->Types.MultiInterface.Pairs);
      break;

    case WdfUsbTargetDeviceSelectConfigTypeInterfacesDescriptor:
      GoogleDbgPrint("\n      Descriptor:");
      GoogleDbgPrint("\n         NumInterfaceDescriptors = %u",
               config->Types.Descriptor.NumInterfaceDescriptors);
      GoogleDbgPrint("\n         ConfigurationDescriptor = %p",
               config->Types.Descriptor.ConfigurationDescriptor);
      GoogleDbgPrint("\n         InterfaceDescriptors    = %p",
               config->Types.Descriptor.InterfaceDescriptors);
      break;

    case WdfUsbTargetDeviceSelectConfigTypeUrb:
      GoogleDbgPrint("\n      Urb:");
      GoogleDbgPrint("\n         Urb = %p",
               config->Types.Urb.Urb);
      break;

    case WdfUsbTargetDeviceSelectConfigTypeInterfacesPairs:
    case WdfUsbTargetDeviceSelectConfigTypeInvalid:
    case WdfUsbTargetDeviceSelectConfigTypeDeconfig:
    default:
      GoogleDbgPrint("\n      Config type is unknown or invalid or not printable.");
      break;
  }
}

void AndroidUsbDeviceObject::PrintInterfaceDescriptor(
    const USB_INTERFACE_DESCRIPTOR* desc) {
  GoogleDbgPrint("\n***** USB_INTERFACE_DESCRIPTOR %p for device %p",
           desc, this);
  GoogleDbgPrint("\n      bLength            = %u", desc->bLength);
  GoogleDbgPrint("\n      bDescriptorType    = %u", desc->bDescriptorType);
  GoogleDbgPrint("\n      bInterfaceNumber   = %u", desc->bInterfaceNumber);
  GoogleDbgPrint("\n      bAlternateSetting  = %u", desc->bAlternateSetting);
  GoogleDbgPrint("\n      bNumEndpoints      = %u", desc->bNumEndpoints);
  GoogleDbgPrint("\n      bInterfaceClass    = x%02X", desc->bInterfaceClass);
  GoogleDbgPrint("\n      bInterfaceSubClass = x%02X", desc->bInterfaceSubClass);
  GoogleDbgPrint("\n      bInterfaceProtocol = x%02X", desc->bInterfaceProtocol);
  GoogleDbgPrint("\n      iInterface         = %u", desc->iInterface);
}

void AndroidUsbDeviceObject::PrintPipeInformation(
    const WDF_USB_PIPE_INFORMATION* info,
    UCHAR pipe_index) {
  GoogleDbgPrint("\n***** WDF_USB_PIPE_INFORMATION[%u] %p for device %p",
           pipe_index, info, this);
  GoogleDbgPrint("\n      Size                = %u", info->Size);
  GoogleDbgPrint("\n      MaximumPacketSize   = %u", info->MaximumPacketSize);
  GoogleDbgPrint("\n      EndpointAddress     = x%02X", info->EndpointAddress);
  GoogleDbgPrint("\n      Interval            = %u", info->Interval);
  GoogleDbgPrint("\n      SettingIndex        = %u", info->SettingIndex);
  GoogleDbgPrint("\n      PipeType            = %u", info->PipeType);
  GoogleDbgPrint("\n      MaximumTransferSize = %u", info->MaximumTransferSize);
}

#endif  // DBG

#pragma data_seg()
#pragma code_seg()
