/*
 * 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 AdbLegacyInterfaceObject
  that encapsulates an interface on our USB device that is accessible
*/

#include "stdafx.h"
#include "adb_api_legacy.h"
#include "adb_legacy_interface.h"
#include "adb_legacy_endpoint_object.h"

AdbLegacyInterfaceObject::AdbLegacyInterfaceObject(const wchar_t* interf_name)
    : AdbInterfaceObject(interf_name),
      def_read_endpoint_(0xFF),
      read_endpoint_id_(0xFF),
      def_write_endpoint_(0xFF),
      write_endpoint_id_(0xFF) {
}

AdbLegacyInterfaceObject::~AdbLegacyInterfaceObject() {
}

ADBAPIHANDLE AdbLegacyInterfaceObject::CreateHandle() {
  // Open USB device for this intefface
  HANDLE usb_device_handle = CreateFile(interface_name().c_str(),
                                        GENERIC_READ | GENERIC_WRITE,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        NULL,
                                        OPEN_EXISTING,
                                        0,
                                        NULL);
  if (INVALID_HANDLE_VALUE == usb_device_handle) {
    return NULL;
  }

  // Now, we ensured that our usb device / interface is up and running.
  // Lets collect device, interface and pipe information
  bool ok = true;
  if (!CacheUsbDeviceDescriptor(usb_device_handle) ||
      !CacheUsbConfigurationDescriptor(usb_device_handle) ||
      !CacheUsbInterfaceDescriptor(usb_device_handle)) {
    ok = false;
  }

  // Preserve error accross handle close
  ULONG error = ok ? NO_ERROR : GetLastError();

  ::CloseHandle(usb_device_handle);

  if (NO_ERROR != error) {
    SetLastError(error);
  }

  if (!ok) {
    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
    AdbEndpointInformation pipe_info;
    if (!GetEndpointInformation(endpoint, &pipe_info)) {
      return false;
    }

    if (AdbEndpointTypeBulk == pipe_info.endpoint_type) {
      // This is a bulk endpoint. Cache its index and ID.
      if (0 != (pipe_info.endpoint_address & 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.endpoint_address;
      } else {
        // Use this endpoint as default bulk write endpoint
        ATLASSERT(0xFF == def_write_endpoint_);
        def_write_endpoint_ = endpoint;
        write_endpoint_id_ = pipe_info.endpoint_address;
      }
    }
  }

  return AdbObjectHandle::CreateHandle();
}

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

  // Open USB device for this intefface
  HANDLE usb_device_handle = CreateFile(interface_name().c_str(),
                                        GENERIC_READ,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        NULL,
                                        OPEN_EXISTING,
                                        0,
                                        NULL);
  if (INVALID_HANDLE_VALUE == usb_device_handle) {
    return NULL;
  }

  WCHAR serial_number[512];

  // Send IOCTL
  DWORD ret_bytes = 0;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_SERIAL_NUMBER,
                             NULL, 0,
                             serial_number, sizeof(serial_number),
                             &ret_bytes,
                             NULL);

  // Preserve error accross CloseHandle
  ULONG error = ret ? NO_ERROR : GetLastError();

  ::CloseHandle(usb_device_handle);

  if (NO_ERROR != error) {
    SetLastError(error);
    return false;
  }

  unsigned long str_len =
    static_cast<unsigned long>(wcslen(serial_number) + 1);

  if ((NULL == buffer) || (*buffer_char_size < str_len)) {
    *buffer_char_size = str_len;
    SetLastError(ERROR_INSUFFICIENT_BUFFER);
    return false;
  }

  if (!ansi) {
    // If user asked for wide char name just return it
    wcscpy(reinterpret_cast<wchar_t*>(buffer), serial_number);
    return true;
  }

  // We need to convert name from wide char to ansi string
  int res = WideCharToMultiByte(CP_ACP,
                                0,
                                serial_number,
                                static_cast<int>(str_len),
                                reinterpret_cast<PSTR>(buffer),
                                static_cast<int>(*buffer_char_size),
                                NULL,
                                NULL);
  return (res != 0);
}

bool AdbLegacyInterfaceObject::GetEndpointInformation(
    UCHAR endpoint_index,
    AdbEndpointInformation* info) {
  // Open USB device for this intefface
  HANDLE usb_device_handle = CreateFile(interface_name().c_str(),
                                        GENERIC_READ,
                                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        NULL,
                                        OPEN_EXISTING,
                                        0,
                                        NULL);
  if (INVALID_HANDLE_VALUE == usb_device_handle) {
    return NULL;
  }

  // Init ICTL param
  AdbQueryEndpointInformation param;
  param.endpoint_index = endpoint_index;

  // Send IOCTL
  DWORD ret_bytes = 0;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_ENDPOINT_INFORMATION,
                             &param, sizeof(param),
                             info, sizeof(AdbEndpointInformation),
                             &ret_bytes,
                             NULL);
  ATLASSERT(!ret || (sizeof(AdbEndpointInformation) == ret_bytes));

  // Preserve error accross CloseHandle
  ULONG error = ret ? NO_ERROR : GetLastError();

  ::CloseHandle(usb_device_handle);

  if (NO_ERROR != error) {
    SetLastError(error);
  }

  return ret ? true : false;
}

ADBAPIHANDLE AdbLegacyInterfaceObject::OpenEndpoint(
    UCHAR endpoint_index,
    AdbOpenAccessType access_type,
    AdbOpenSharingMode sharing_mode) {
  // Convert index into name and ID.
  std::wstring endpoint_name;
  UCHAR endpoint_id;

  try {
    if ((ADB_QUERY_BULK_READ_ENDPOINT_INDEX == endpoint_index) ||
        (def_read_endpoint_ == endpoint_index)) {
      endpoint_name = DEVICE_BULK_READ_PIPE_NAME;
      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_name = DEVICE_BULK_WRITE_PIPE_NAME;
      endpoint_id = write_endpoint_id_;
      endpoint_index = def_write_endpoint_;
    } else {
      SetLastError(ERROR_INVALID_PARAMETER);
      return false;
    }
  } catch (...) {
    // We don't expect exceptions other than OOM thrown here.
    SetLastError(ERROR_OUTOFMEMORY);
    return NULL;
  }

  return OpenEndpoint(endpoint_name.c_str(), endpoint_id, endpoint_index,
                      access_type, sharing_mode);
}

ADBAPIHANDLE AdbLegacyInterfaceObject::OpenEndpoint(
    const wchar_t* endpoint_name,
    UCHAR endpoint_id,
    UCHAR endpoint_index,
    AdbOpenAccessType access_type,
    AdbOpenSharingMode sharing_mode) {
  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  AdbLegacyEndpointObject* adb_endpoint = NULL;

  try {
    adb_endpoint =
        new AdbLegacyEndpointObject(this, endpoint_id, endpoint_index);
  } catch (...) {
    // We don't expect exceptions other than OOM thrown here.
    SetLastError(ERROR_OUTOFMEMORY);
    return NULL;
  }

  // Build full path to the object
  std::wstring endpoint_path = interface_name();
  endpoint_path += L"\\";
  endpoint_path += endpoint_name;

  ADBAPIHANDLE ret = adb_endpoint->CreateHandle(endpoint_path.c_str(),
                                                access_type,
                                                sharing_mode);

  adb_endpoint->Release();

  return ret;
}

bool AdbLegacyInterfaceObject::CacheUsbDeviceDescriptor(
    HANDLE usb_device_handle) {
  DWORD ret_bytes = 0;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_USB_DEVICE_DESCRIPTOR,
                             NULL, 0,
                             &usb_device_descriptor_,
                             sizeof(usb_device_descriptor_),
                             &ret_bytes,
                             NULL);
  ATLASSERT(!ret || (sizeof(USB_DEVICE_DESCRIPTOR) == ret_bytes));

  return ret ? true : false;
}

bool AdbLegacyInterfaceObject::CacheUsbConfigurationDescriptor(
    HANDLE usb_device_handle) {
  DWORD ret_bytes = 0;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_USB_CONFIGURATION_DESCRIPTOR,
                             NULL, 0,
                             &usb_config_descriptor_,
                             sizeof(usb_config_descriptor_),
                             &ret_bytes,
                             NULL);
  ATLASSERT(!ret || (sizeof(USB_CONFIGURATION_DESCRIPTOR) == ret_bytes));

  return ret ? true : false;
}

bool AdbLegacyInterfaceObject::CacheUsbInterfaceDescriptor(
    HANDLE usb_device_handle) {
  DWORD ret_bytes = 0;
  BOOL ret = DeviceIoControl(usb_device_handle,
                             ADB_IOCTL_GET_USB_INTERFACE_DESCRIPTOR,
                             NULL, 0,
                             &usb_interface_descriptor_,
                             sizeof(usb_interface_descriptor_),
                             &ret_bytes,
                             NULL);
  ATLASSERT(!ret || (sizeof(USB_INTERFACE_DESCRIPTOR) == ret_bytes));

  return ret ? true : false;
}
