/*
 * Copyright (C) 2009 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 AdbWinUsbInterfaceObject
  that encapsulates an interface on our USB device that is accessible
  via WinUsb API.
*/

#include "stdafx.h"
#include "adb_winusb_interface.h"
#include "adb_winusb_endpoint_object.h"

AdbWinUsbInterfaceObject::AdbWinUsbInterfaceObject(const wchar_t* interf_name)
    : AdbInterfaceObject(interf_name),
      usb_device_handle_(INVALID_HANDLE_VALUE),
      winusb_handle_(NULL),
      interface_number_(0xFF),
      def_read_endpoint_(0xFF),
      read_endpoint_id_(0xFF),
      def_write_endpoint_(0xFF),
      write_endpoint_id_(0xFF) {
}

AdbWinUsbInterfaceObject::~AdbWinUsbInterfaceObject() {
  ATLASSERT(NULL == winusb_handle_);
  ATLASSERT(INVALID_HANDLE_VALUE == usb_device_handle_);
}

LONG AdbWinUsbInterfaceObject::Release() {
  ATLASSERT(ref_count_ > 0);
  LONG ret = InterlockedDecrement(&ref_count_);
  ATLASSERT(ret >= 0);
  if (0 == ret) {
    LastReferenceReleased();
    delete this;
  }
  return ret;
}

ADBAPIHANDLE AdbWinUsbInterfaceObject::CreateHandle() {
  // Open USB device for this inteface Note that WinUsb API
  // requires the handle to be opened for overlapped I/O.
  usb_device_handle_ = CreateFile(interface_name().c_str(),
                                  GENERIC_READ | GENERIC_WRITE,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL, OPEN_EXISTING,
                                  FILE_FLAG_OVERLAPPED, NULL);
  if (INVALID_HANDLE_VALUE == usb_device_handle_)
    return NULL;

  // Initialize WinUSB API for this interface
  if (!WinUsb_Initialize(usb_device_handle_, &winusb_handle_))
    return NULL;

  // Cache current interface number that will be used in
  // WinUsb_Xxx calls performed on this interface.
  if (!WinUsb_GetCurrentAlternateSetting(winusb_handle(), &interface_number_))
    return false;

  // Cache interface properties
  unsigned long bytes_written;

  // Cache USB device descriptor
  if (!WinUsb_GetDescriptor(winusb_handle(), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0,
                            reinterpret_cast<PUCHAR>(&usb_device_descriptor_),
                            sizeof(usb_device_descriptor_), &bytes_written)) {
    return false;
  }

  // Cache USB configuration descriptor
  if (!WinUsb_GetDescriptor(winusb_handle(), USB_CONFIGURATION_DESCRIPTOR_TYPE,
                            0, 0,
                            reinterpret_cast<PUCHAR>(&usb_config_descriptor_),
                            sizeof(usb_config_descriptor_), &bytes_written)) {
    return false;
  }

  // Cache USB interface descriptor
  if (!WinUsb_QueryInterfaceSettings(winusb_handle(), interface_number(),
                                     &usb_interface_descriptor_)) {
    return false;
  }

  // Save indexes and IDs for bulk read / write endpoints. We will use them to
  // convert ADB_QUERY_BULK_WRITE_ENDPOINT_INDEX and
  // ADB_QUERY_BULK_READ_ENDPOINT_INDEX into actual endpoint indexes and IDs.
  for (UCHAR endpoint = 0; endpoint < usb_interface_descriptor_.bNumEndpoints;
       endpoint++) {
    // Get endpoint information
    WINUSB_PIPE_INFORMATION pipe_info;
    if (!WinUsb_QueryPipe(winusb_handle(), interface_number(), endpoint,
                          &pipe_info)) {
      return false;
    }

    if (UsbdPipeTypeBulk == pipe_info.PipeType) {
      // This is a bulk endpoint. Cache its index and ID.
      if (0 != (pipe_info.PipeId & USB_ENDPOINT_DIRECTION_MASK)) {
        // Use this endpoint as default bulk read endpoint
        ATLASSERT(0xFF == def_read_endpoint_);
        def_read_endpoint_ = endpoint;
        read_endpoint_id_ = pipe_info.PipeId;
      } else {
        // Use this endpoint as default bulk write endpoint
        ATLASSERT(0xFF == def_write_endpoint_);
        def_write_endpoint_ = endpoint;
        write_endpoint_id_ = pipe_info.PipeId;
      }
    }
  }

  return AdbInterfaceObject::CreateHandle();
}

bool AdbWinUsbInterfaceObject::CloseHandle() {
  if (NULL != winusb_handle_) {
    WinUsb_Free(winusb_handle_);
    winusb_handle_ = NULL;
  }
  if (INVALID_HANDLE_VALUE != usb_device_handle_) {
    ::CloseHandle(usb_device_handle_);
    usb_device_handle_ = INVALID_HANDLE_VALUE;
  }

  return AdbInterfaceObject::CloseHandle();
}

bool AdbWinUsbInterfaceObject::GetSerialNumber(void* buffer,
                                               unsigned long* buffer_char_size,
                                               bool ansi) {
  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  if (NULL == buffer_char_size) {
    SetLastError(ERROR_INVALID_PARAMETER);
    return false;
  }

  // Calculate serial number string size. Note that WinUsb_GetDescriptor
  // API will not return number of bytes needed to store serial number
  // string. So we will have to start with a reasonably large preallocated
  // buffer and then loop through WinUsb_GetDescriptor calls, doubling up
  // string buffer size every time ERROR_INSUFFICIENT_BUFFER is returned.
  union {
    // Preallocate reasonably sized buffer on the stack.
    char small_buffer[64];
    USB_STRING_DESCRIPTOR initial_ser_num;
  };
  USB_STRING_DESCRIPTOR* ser_num = &initial_ser_num;
  // Buffer byte size
  unsigned long ser_num_size = sizeof(small_buffer);
  // After successful call to WinUsb_GetDescriptor will contain serial
  // number descriptor size.
  unsigned long bytes_written;
  while (!WinUsb_GetDescriptor(winusb_handle(), USB_STRING_DESCRIPTOR_TYPE,
                               usb_device_descriptor_.iSerialNumber,
                               0x0409, // English (US)
                               reinterpret_cast<PUCHAR>(ser_num),
                               ser_num_size, &bytes_written)) {
    // Any error other than ERROR_INSUFFICIENT_BUFFER is terminal here.
    if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
      if (ser_num != &initial_ser_num)
        delete[] reinterpret_cast<char*>(ser_num);
      return false;
    }

    // Double up buffer size and reallocate string buffer
    ser_num_size *= 2;
    if (ser_num != &initial_ser_num)
      delete[] reinterpret_cast<char*>(ser_num);
    try {
      ser_num =
          reinterpret_cast<USB_STRING_DESCRIPTOR*>(new char[ser_num_size]);
    } catch (...) {
      SetLastError(ERROR_OUTOFMEMORY);
      return false;
    }
  }

  // Serial number string length
  unsigned long str_len = (ser_num->bLength -
                           FIELD_OFFSET(USB_STRING_DESCRIPTOR, bString)) /
                          sizeof(wchar_t);

  // Lets see if requested buffer is big enough to fit the string
  if ((NULL == buffer) || (*buffer_char_size < (str_len + 1))) {
    // Requested buffer is too small.
    if (ser_num != &initial_ser_num)
      delete[] reinterpret_cast<char*>(ser_num);
    *buffer_char_size = str_len + 1;
    SetLastError(ERROR_INSUFFICIENT_BUFFER);
    return false;
  }

  bool ret = true;
  if (ansi) {
    // We need to convert name from wide char to ansi string
    if (0 != WideCharToMultiByte(CP_ACP, 0, ser_num->bString,
                                 static_cast<int>(str_len),
                                 reinterpret_cast<PSTR>(buffer),
                                 static_cast<int>(*buffer_char_size),
                                 NULL, NULL)) {
      // Zero-terminate output string.
      reinterpret_cast<char*>(buffer)[str_len] = '\0';
    } else {
      ret = false;
    }
  } else {
    // For wide char output just copy string buffer,
    // and zero-terminate output string.
    CopyMemory(buffer, ser_num->bString, bytes_written);
    reinterpret_cast<wchar_t*>(buffer)[str_len] = L'\0';
  }

  if (ser_num != &initial_ser_num)
    delete[] reinterpret_cast<char*>(ser_num);

  return ret;
}

bool AdbWinUsbInterfaceObject::GetEndpointInformation(
    UCHAR endpoint_index,
    AdbEndpointInformation* info) {
  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  if (NULL == info) {
    SetLastError(ERROR_INVALID_PARAMETER);
    return false;
  }

  // Get actual endpoint index for predefined read / write endpoints.
  if (ADB_QUERY_BULK_READ_ENDPOINT_INDEX == endpoint_index) {
    endpoint_index = def_read_endpoint_;
  } else if (ADB_QUERY_BULK_WRITE_ENDPOINT_INDEX == endpoint_index) {
    endpoint_index = def_write_endpoint_;
  }

  // Query endpoint information
  WINUSB_PIPE_INFORMATION pipe_info;
  if (!WinUsb_QueryPipe(winusb_handle(), interface_number(), endpoint_index,
                        &pipe_info)) {
    return false;
  }

  // Save endpoint information into output.
  info->max_packet_size = pipe_info.MaximumPacketSize;
  info->max_transfer_size = 0xFFFFFFFF;
  info->endpoint_address = pipe_info.PipeId;
  info->polling_interval = pipe_info.Interval;
  info->setting_index = interface_number();
  switch (pipe_info.PipeType) {
    case UsbdPipeTypeControl:
      info->endpoint_type = AdbEndpointTypeControl;
      break;

    case UsbdPipeTypeIsochronous:
      info->endpoint_type = AdbEndpointTypeIsochronous;
      break;

    case UsbdPipeTypeBulk:
      info->endpoint_type = AdbEndpointTypeBulk;
      break;

    case UsbdPipeTypeInterrupt:
      info->endpoint_type = AdbEndpointTypeInterrupt;
      break;

    default:
      info->endpoint_type = AdbEndpointTypeInvalid;
      break;
  }

  return true;
}

ADBAPIHANDLE AdbWinUsbInterfaceObject::OpenEndpoint(
    UCHAR endpoint_index,
    AdbOpenAccessType access_type,
    AdbOpenSharingMode sharing_mode) {
  // Convert index into id
  UCHAR endpoint_id;

  if ((ADB_QUERY_BULK_READ_ENDPOINT_INDEX == endpoint_index) ||
      (def_read_endpoint_ == endpoint_index)) {
    endpoint_id = read_endpoint_id_;
    endpoint_index = def_read_endpoint_;
  } else if ((ADB_QUERY_BULK_WRITE_ENDPOINT_INDEX == endpoint_index) ||
             (def_write_endpoint_ == endpoint_index)) {
    endpoint_id = write_endpoint_id_;
    endpoint_index = def_write_endpoint_;
  } else {
    SetLastError(ERROR_INVALID_PARAMETER);
    return false;
  }

  return OpenEndpoint(endpoint_id, endpoint_index);
}

ADBAPIHANDLE AdbWinUsbInterfaceObject::OpenEndpoint(UCHAR endpoint_id,
                                                    UCHAR endpoint_index) {
  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  AdbEndpointObject* adb_endpoint = NULL;
  
  try {
    adb_endpoint =
        new AdbWinUsbEndpointObject(this, endpoint_id, endpoint_index);
  } catch (...) {
    SetLastError(ERROR_OUTOFMEMORY);
    return NULL;
  }

  ADBAPIHANDLE ret = adb_endpoint->CreateHandle();

  adb_endpoint->Release();

  return ret;
}
