// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/devtools/device/usb/android_usb_socket.h"

#include <stddef.h>

#include "base/callback_helpers.h"
#include "base/logging.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"

namespace {

const int kMaxPayload = 4096;

}  // namespace

AndroidUsbSocket::AndroidUsbSocket(scoped_refptr<AndroidUsbDevice> device,
                                   uint32_t socket_id,
                                   const std::string& command,
                                   base::Closure delete_callback)
    : device_(device),
      command_(command),
      local_id_(socket_id),
      remote_id_(0),
      is_connected_(false),
      delete_callback_(delete_callback),
      weak_factory_(this) {}

AndroidUsbSocket::~AndroidUsbSocket() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (is_connected_)
    Disconnect();
  if (!delete_callback_.is_null())
    delete_callback_.Run();
}

void AndroidUsbSocket::HandleIncoming(std::unique_ptr<AdbMessage> message) {
  if (!device_.get())
    return;

  CHECK_EQ(message->arg1, local_id_);
  switch (message->command) {
    case AdbMessage::kCommandOKAY:
      if (!is_connected_) {
        remote_id_ = message->arg0;
        is_connected_ = true;
        if (!connect_callback_.is_null())
          std::move(connect_callback_).Run(net::OK);
        // "this" can be deleted.
      } else {
        RespondToWriter(write_length_);
        // "this" can be deleted.
      }
      break;
    case AdbMessage::kCommandWRTE:
      device_->Send(AdbMessage::kCommandOKAY, local_id_, message->arg0, "");
      read_buffer_ += message->body;
      // Allow WRTE over new connection even though OKAY ack was not received.
      if (!is_connected_) {
        remote_id_ = message->arg0;
        is_connected_ = true;
        if (!connect_callback_.is_null())
          std::move(connect_callback_).Run(net::OK);
        // "this" can be deleted.
      } else {
        RespondToReader(false);
        // "this" can be deleted.
      }
      break;
    case AdbMessage::kCommandCLSE:
      if (is_connected_)
        device_->Send(AdbMessage::kCommandCLSE, local_id_, 0, "");
      Terminated(true);
      // "this" can be deleted.
      break;
    default:
      break;
  }
}

void AndroidUsbSocket::Terminated(bool closed_by_device) {
  is_connected_ = false;

  // Break the socket -> device connection, release the device.
  device_ = nullptr;
  std::move(delete_callback_).Run();

  if (!closed_by_device)
    return;

  // Respond to pending callbacks.
  if (!connect_callback_.is_null()) {
    std::move(connect_callback_).Run(net::ERR_FAILED);
    // "this" can be deleted.
    return;
  }
  base::WeakPtr<AndroidUsbSocket> weak_this = weak_factory_.GetWeakPtr();
  RespondToReader(true);
  // "this" can be deleted.
  if (weak_this) {
    RespondToWriter(net::ERR_FAILED);
    // "this" can be deleted.
  }
}

int AndroidUsbSocket::Read(net::IOBuffer* buffer,
                           int length,
                           net::CompletionOnceCallback callback) {
  DCHECK(!callback.is_null());
  if (!is_connected_)
    return device_.get() ? net::ERR_SOCKET_NOT_CONNECTED : 0;

  DCHECK(read_callback_.is_null());
  if (read_buffer_.empty()) {
    read_callback_ = std::move(callback);
    read_io_buffer_ = buffer;
    read_length_ = length;
    return net::ERR_IO_PENDING;
  }

  size_t bytes_to_copy = static_cast<size_t>(length) > read_buffer_.length() ?
      read_buffer_.length() : static_cast<size_t>(length);
  memcpy(buffer->data(), read_buffer_.data(), bytes_to_copy);
  if (read_buffer_.length() > bytes_to_copy)
    read_buffer_ = read_buffer_.substr(bytes_to_copy);
  else
    read_buffer_ = std::string();
  return bytes_to_copy;
}

int AndroidUsbSocket::Write(
    net::IOBuffer* buffer,
    int length,
    net::CompletionOnceCallback callback,
    const net::NetworkTrafficAnnotationTag& /*traffic_annotation*/) {
  DCHECK(!callback.is_null());
  if (!is_connected_)
    return net::ERR_SOCKET_NOT_CONNECTED;

  if (length > kMaxPayload)
    length = kMaxPayload;

  DCHECK(write_callback_.is_null());
  write_callback_ = std::move(callback);
  write_length_ = length;
  device_->Send(AdbMessage::kCommandWRTE, local_id_, remote_id_,
                std::string(buffer->data(), length));
  return net::ERR_IO_PENDING;
}

int AndroidUsbSocket::SetReceiveBufferSize(int32_t size) {
  NOTIMPLEMENTED();
  return net::ERR_NOT_IMPLEMENTED;
}

int AndroidUsbSocket::SetSendBufferSize(int32_t size) {
  NOTIMPLEMENTED();
  return net::ERR_NOT_IMPLEMENTED;
}

int AndroidUsbSocket::Connect(net::CompletionOnceCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!callback.is_null());
  if (!device_.get())
    return net::ERR_FAILED;

  DCHECK(!is_connected_);
  DCHECK(connect_callback_.is_null());
  connect_callback_ = std::move(callback);
  device_->Send(AdbMessage::kCommandOPEN, local_id_, 0, command_);
  return net::ERR_IO_PENDING;
}

void AndroidUsbSocket::Disconnect() {
  if (!device_.get())
    return;
  device_->Send(AdbMessage::kCommandCLSE, local_id_, remote_id_, "");
  Terminated(false);
}

bool AndroidUsbSocket::IsConnected() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return is_connected_;
}

bool AndroidUsbSocket::IsConnectedAndIdle() const {
  NOTIMPLEMENTED();
  return false;
}

int AndroidUsbSocket::GetPeerAddress(net::IPEndPoint* address) const {
  *address = net::IPEndPoint(net::IPAddress(0, 0, 0, 0), 0);
  return net::OK;
}

int AndroidUsbSocket::GetLocalAddress(net::IPEndPoint* address) const {
  NOTIMPLEMENTED();
  return net::ERR_NOT_IMPLEMENTED;
}

const net::NetLogWithSource& AndroidUsbSocket::NetLog() const {
  return net_log_;
}

bool AndroidUsbSocket::WasEverUsed() const {
  NOTIMPLEMENTED();
  return true;
}

bool AndroidUsbSocket::WasAlpnNegotiated() const {
  NOTIMPLEMENTED();
  return true;
}

net::NextProto AndroidUsbSocket::GetNegotiatedProtocol() const {
  NOTIMPLEMENTED();
  return net::kProtoUnknown;
}

bool AndroidUsbSocket::GetSSLInfo(net::SSLInfo* ssl_info) {
  return false;
}

void AndroidUsbSocket::GetConnectionAttempts(
    net::ConnectionAttempts* out) const {
  out->clear();
}

int64_t AndroidUsbSocket::GetTotalReceivedBytes() const {
  NOTIMPLEMENTED();
  return 0;
}

void AndroidUsbSocket::ApplySocketTag(const net::SocketTag& tag) {
  NOTIMPLEMENTED();
}

void AndroidUsbSocket::RespondToReader(bool disconnect) {
  if (read_callback_.is_null() || (read_buffer_.empty() && !disconnect))
    return;
  size_t bytes_to_copy =
      static_cast<size_t>(read_length_) > read_buffer_.length() ?
          read_buffer_.length() : static_cast<size_t>(read_length_);
  memcpy(read_io_buffer_->data(), read_buffer_.data(), bytes_to_copy);
  if (read_buffer_.length() > bytes_to_copy)
    read_buffer_ = read_buffer_.substr(bytes_to_copy);
  else
    read_buffer_ = std::string();
  std::move(read_callback_).Run(bytes_to_copy);
}

void AndroidUsbSocket::RespondToWriter(int result) {
  if (!write_callback_.is_null())
    std::move(write_callback_).Run(result);
}
