/*
 * 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 AdbLegacyEndpointObject that
  encapsulates a handle opened to an endpoint on our device controlled by
  a custom (legacy) USB driver.
*/

#include "stdafx.h"
#include "adb_api_legacy.h"
#include "adb_legacy_endpoint_object.h"
#include "adb_legacy_io_completion.h"
#include "adb_helper_routines.h"

AdbLegacyEndpointObject::AdbLegacyEndpointObject(
    AdbLegacyInterfaceObject* parent_interf,
    UCHAR endpoint_id,
    UCHAR endpoint_index)
    : AdbEndpointObject(parent_interf, endpoint_id, endpoint_index),
      usb_handle_(INVALID_HANDLE_VALUE) {
}

AdbLegacyEndpointObject::~AdbLegacyEndpointObject() {
  if (INVALID_HANDLE_VALUE != usb_handle_) {
    ::CloseHandle(usb_handle_);
  }
}

ADBAPIHANDLE AdbLegacyEndpointObject::CommonAsyncReadWrite(
    bool is_read,
    void* buffer,
    ULONG bytes_to_transfer,
    ULONG* bytes_transferred,
    HANDLE event_handle,
    ULONG time_out) {
  if (NULL != bytes_transferred) {
    *bytes_transferred = 0;
  }

  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  bool is_ioctl_write = is_read ? false : (0 != time_out);

  // Create completion i/o object
  AdbLegacyIOCompletion* adb_io_completion = NULL;

  try {
    adb_io_completion = new AdbLegacyIOCompletion(this,
                                                  bytes_to_transfer,
                                                  event_handle,
                                                  is_ioctl_write);
  } catch (... ) {
    // We don't expect exceptions other than OOM thrown here.
    SetLastError(ERROR_OUTOFMEMORY);
    return NULL;
  }

  // Create a handle for it
  ADBAPIHANDLE ret = adb_io_completion->CreateHandle();
  ULONG transferred = 0;
  if (NULL != ret) {
    BOOL res = TRUE;
    if (0 == time_out) {
      // Go the read / write file way
      res = is_read ? ReadFile(usb_handle(),
                               buffer,
                               bytes_to_transfer,
                               &transferred,
                               adb_io_completion->overlapped()) :
                      WriteFile(usb_handle(),
                                buffer,
                                bytes_to_transfer,
                                &transferred,
                                adb_io_completion->overlapped());
    } else {
      // Go IOCTL way
      AdbBulkTransfer transfer_param;
      transfer_param.time_out = time_out;
      transfer_param.transfer_size = is_read ? 0 : bytes_to_transfer;
      transfer_param.SetWriteBuffer(is_read ? NULL : buffer);

      res = DeviceIoControl(usb_handle(),
        is_read ? ADB_IOCTL_BULK_READ : ADB_IOCTL_BULK_WRITE,
        &transfer_param, sizeof(transfer_param),
        is_read ? buffer : adb_io_completion->transferred_bytes_ptr(),
        is_read ? bytes_to_transfer : sizeof(ULONG),
        &transferred,
        adb_io_completion->overlapped());
    }

    if (NULL != bytes_transferred) {
      *bytes_transferred = transferred;
    }

    ULONG error = GetLastError();
    if (!res && (ERROR_IO_PENDING != error)) {
      // I/O failed immediatelly. We need to close i/o completion object
      // before we return NULL to the caller.
      adb_io_completion->CloseHandle();
      ret = NULL;
      SetLastError(error);
    }
  }

  // Offseting 'new'
  adb_io_completion->Release();

  return ret;
}

bool AdbLegacyEndpointObject::CommonSyncReadWrite(bool is_read,
                                                  void* buffer,
                                                  ULONG bytes_to_transfer,
                                                  ULONG* bytes_transferred,
                                                  ULONG time_out) {
  if (NULL != bytes_transferred) {
    *bytes_transferred = 0;
  }

  if (!IsOpened()) {
    SetLastError(ERROR_INVALID_HANDLE);
    return false;
  }

  bool is_ioctl_write = is_read ? false : (0 != time_out);

  // This is synchronous I/O. Since we always open I/O items for
  // overlapped I/O we're obligated to always provide OVERLAPPED
  // structure to read / write routines. Prepare it now.
  OVERLAPPED overlapped;
  ZeroMemory(&overlapped, sizeof(overlapped));

  BOOL ret = TRUE;
  ULONG ioctl_write_transferred = 0;
  if (0 == time_out) {
    // Go the read / write file way
    ret = is_read ?
      ReadFile(usb_handle(), buffer, bytes_to_transfer, bytes_transferred, &overlapped) :
      WriteFile(usb_handle(), buffer, bytes_to_transfer, bytes_transferred, &overlapped);
  } else {
    // Go IOCTL way
    AdbBulkTransfer transfer_param;
    transfer_param.time_out = time_out;
    transfer_param.transfer_size = is_read ? 0 : bytes_to_transfer;
    transfer_param.SetWriteBuffer(is_read ? NULL : buffer);

    ULONG tmp;
    ret = DeviceIoControl(usb_handle(),
      is_read ? ADB_IOCTL_BULK_READ : ADB_IOCTL_BULK_WRITE,
      &transfer_param, sizeof(transfer_param),
      is_read ? buffer : &ioctl_write_transferred,
      is_read ? bytes_to_transfer : sizeof(ULONG),
      &tmp,
      &overlapped);
  }

  // Lets see the result
  if (!ret && (ERROR_IO_PENDING != GetLastError())) {
    // I/O failed.
    return false;
  }

  // Lets wait till I/O completes
  ULONG transferred = 0;
  ret = GetOverlappedResult(usb_handle(), &overlapped, &transferred, TRUE);
  if (ret && (NULL != bytes_transferred)) {
    *bytes_transferred = is_ioctl_write ? ioctl_write_transferred :
                                          transferred;
  }

  return ret ? true : false;
}

ADBAPIHANDLE AdbLegacyEndpointObject::CreateHandle(
    const wchar_t* item_path,
    AdbOpenAccessType access_type,
    AdbOpenSharingMode share_mode) {
  // Convert access / share parameters into CreateFile - compatible
  ULONG desired_access;
  ULONG desired_sharing;

  if (!GetSDKComplientParam(access_type, share_mode,
                            &desired_access, &desired_sharing)) {
    return NULL;
  }

  // Open USB handle
  usb_handle_ = CreateFile(item_path,
                           desired_access,
                           share_mode,
                           NULL,
                           OPEN_EXISTING,
                           FILE_FLAG_OVERLAPPED,  // Always overlapped!
                           NULL);
  if (INVALID_HANDLE_VALUE == usb_handle_) {
    return NULL;
  }

  // Create ADB handle
  ADBAPIHANDLE ret = AdbObjectHandle::CreateHandle();

  if (NULL == ret) {
    // If creation of ADB handle failed we have to close USB handle too.
    ULONG error = GetLastError();
    ::CloseHandle(usb_handle());
    usb_handle_ = INVALID_HANDLE_VALUE;
    SetLastError(error);
  }

  return ret;
}

bool AdbLegacyEndpointObject::CloseHandle() {
  if (INVALID_HANDLE_VALUE != usb_handle_) {
    ::CloseHandle(usb_handle_);
    usb_handle_ = INVALID_HANDLE_VALUE;
  }

  return AdbEndpointObject::CloseHandle();
}
