/*
 * 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 AdbWinUsbEndpointObject that
  encapsulates a handle opened to a WinUsb endpoint on our device.
*/

#include "stdafx.h"
#include "adb_winusb_endpoint_object.h"
#include "adb_winusb_io_completion.h"

AdbWinUsbEndpointObject::AdbWinUsbEndpointObject(
    AdbWinUsbInterfaceObject* parent_interf,
    UCHAR endpoint_id,
    UCHAR endpoint_index)
    : AdbEndpointObject(parent_interf, endpoint_id, endpoint_index),
    lock_(), is_closing_(false), pending_io_count_(0) {
}

AdbWinUsbEndpointObject::~AdbWinUsbEndpointObject() {
}

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

bool AdbWinUsbEndpointObject::CloseHandle() {
  // This method only returns once all pending IOs are aborted and after
  // preventing future pending IOs. This means that once CloseHandle()
  // returns, threads using this object won't be using
  // parent_winusb_interface()->winusb_handle(), so it can then be safely
  // released.
  lock_.Lock();
  if (!is_closing_) {
    // Set flag to prevent new I/Os from starting up.
    is_closing_ = true;
  }

  // While there are pending IOs, keep aborting the pipe. We have to do this
  // repeatedly because pending_ios_ is incremented before the IO has actually
  // started, and abort (probably) only works if the IO has been started.
  while (pending_io_count_ > 0) {
    lock_.Unlock();

    // It has been noticed that on Windows 7, if you only call
    // WinUsb_AbortPipe(), without first calling WinUsb_ResetPipe(), the call
    // to WinUsb_AbortPipe() hangs.
    if (!WinUsb_ResetPipe(parent_winusb_interface()->winusb_handle(),
                          endpoint_id()) ||
        !WinUsb_AbortPipe(parent_winusb_interface()->winusb_handle(),
                          endpoint_id())) {
      // Reset or Abort failed for unexpected reason. We might not be able to
      // abort pending IOs, so we shouldn't keep polling pending_io_count_ or
      // else we might hang forever waiting for the IOs to abort. In this
      // situation it is preferable to risk a race condition (which may or may
      // not crash) and just break now.
      lock_.Lock();
      break;
    }

    // Give the IO threads time to break out of I/O calls and decrement
    // pending_io_count_. They should finish up pretty quick. The amount of time
    // "wasted" here (as opposed to if we did synchronization with an event)
    // doesn't really matter since this is an uncommon corner-case.
    Sleep(16);  // 16 ms, old default OS scheduler granularity

    lock_.Lock();
  }

  lock_.Unlock();

  return AdbEndpointObject::CloseHandle();
}

ADBAPIHANDLE AdbWinUsbEndpointObject::CommonAsyncReadWrite(
    bool is_read,
    void* buffer,
    ULONG bytes_to_transfer,
    ULONG* bytes_transferred,
    HANDLE event_handle,
    ULONG time_out) {
  // TODO: Do synchronization with is_closing_ and pending_io_count_ like
  // CommonSyncReadWrite(). This is not yet implemented because there are no
  // callers to Adb{Read,Write}EndpointAsync() in AOSP, and hence no testing.
  if (!SetTimeout(time_out))
    return false;

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

  try {
    adb_io_completion = new AdbWinUsbIOCompletion(this,
                                                  bytes_to_transfer,
                                                  event_handle);
  } catch (... ) {
    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;
    // Go the read / write file way
    res = is_read ?
        WinUsb_ReadPipe(parent_winusb_interface()->winusb_handle(),
                        endpoint_id(),
                        reinterpret_cast<PUCHAR>(buffer),
                        bytes_to_transfer,
                        &transferred,
                        adb_io_completion->overlapped()) :
        WinUsb_WritePipe(parent_winusb_interface()->winusb_handle(),
                         endpoint_id(),
                         reinterpret_cast<PUCHAR>(buffer),
                         bytes_to_transfer,
                         &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 AdbWinUsbEndpointObject::CommonSyncReadWrite(bool is_read,
                                                  void* buffer,
                                                  ULONG bytes_to_transfer,
                                                  ULONG* bytes_transferred,
                                                  ULONG time_out) {
  lock_.Lock();
  if (is_closing_) {
    lock_.Unlock();
    // AdbCloseHandle() is in progress, so don't start up any new IOs.
    SetLastError(ERROR_HANDLES_CLOSED);
    return false;
  } else {
    // Not closing down, so record the fact that we're doing IO. This will
    // prevent CloseHandle() from returning until our IO completes or it aborts
    // our IO.
    ++pending_io_count_;
    lock_.Unlock();
  }

  // Because we've incremented pending_ios_, do the matching decrement when this
  // object goes out of scope.
  DecrementPendingIO dec(this);

  if (!SetTimeout(time_out))
    return false;

  // 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));
  overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

  BOOL ret = TRUE;
  ULONG transferred = 0;
  // Go the read / write file way
  ret = is_read ?
        WinUsb_ReadPipe(parent_winusb_interface()->winusb_handle(),
                        endpoint_id(),
                        reinterpret_cast<PUCHAR>(buffer),
                        bytes_to_transfer,
                        &transferred,
                        &overlapped) :
        WinUsb_WritePipe(parent_winusb_interface()->winusb_handle(),
                         endpoint_id(),
                         reinterpret_cast<PUCHAR>(buffer),
                         bytes_to_transfer,
                         &transferred,
                         &overlapped);

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

  // Lets wait till I/O completes
  ret = WinUsb_GetOverlappedResult(parent_winusb_interface()->winusb_handle(), &overlapped,
                                   &transferred, TRUE);
  if (ret && (NULL != bytes_transferred)) {
    *bytes_transferred = transferred;
  }

  if (NULL != overlapped.hEvent)
    ::CloseHandle(overlapped.hEvent);

  return ret ? true : false;
}

bool AdbWinUsbEndpointObject::SetTimeout(ULONG timeout) {
  if (!WinUsb_SetPipePolicy(parent_winusb_interface()->winusb_handle(),
                            endpoint_id(), PIPE_TRANSFER_TIMEOUT,
                            sizeof(ULONG), &timeout)) {
    return false;
  }

  return true;
}
