// Copyright (c) 2013 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 "ppapi/proxy/url_loader_resource.h"

#include <algorithm>

#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppb_url_loader.h"
#include "ppapi/proxy/dispatch_reply_message.h"
#include "ppapi/proxy/file_ref_resource.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/url_request_info_resource.h"
#include "ppapi/proxy/url_response_info_resource.h"
#include "ppapi/shared_impl/ppapi_globals.h"
#include "ppapi/shared_impl/url_response_info_data.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/resource_creation_api.h"

using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_URLLoader_API;
using ppapi::thunk::PPB_URLRequestInfo_API;

namespace ppapi {
namespace proxy {

URLLoaderResource::URLLoaderResource(Connection connection,
                                     PP_Instance instance)
    : PluginResource(connection, instance),
      mode_(MODE_WAITING_TO_OPEN),
      status_callback_(NULL),
      bytes_sent_(0),
      total_bytes_to_be_sent_(-1),
      bytes_received_(0),
      total_bytes_to_be_received_(-1),
      user_buffer_(NULL),
      user_buffer_size_(0),
      done_status_(PP_OK_COMPLETIONPENDING),
      is_streaming_to_file_(false),
      is_asynchronous_load_suspended_(false) {
  SendCreate(RENDERER, PpapiHostMsg_URLLoader_Create());
}

URLLoaderResource::URLLoaderResource(Connection connection,
                                     PP_Instance instance,
                                     int pending_main_document_loader_id,
                                     const ppapi::URLResponseInfoData& data)
    : PluginResource(connection, instance),
      mode_(MODE_OPENING),
      status_callback_(NULL),
      bytes_sent_(0),
      total_bytes_to_be_sent_(-1),
      bytes_received_(0),
      total_bytes_to_be_received_(-1),
      user_buffer_(NULL),
      user_buffer_size_(0),
      done_status_(PP_OK_COMPLETIONPENDING),
      is_streaming_to_file_(false),
      is_asynchronous_load_suspended_(false) {
  AttachToPendingHost(RENDERER, pending_main_document_loader_id);
  SaveResponseInfo(data);
}

URLLoaderResource::~URLLoaderResource() {
}

PPB_URLLoader_API* URLLoaderResource::AsPPB_URLLoader_API() {
  return this;
}

int32_t URLLoaderResource::Open(PP_Resource request_id,
                                scoped_refptr<TrackedCallback> callback) {
  EnterResourceNoLock<PPB_URLRequestInfo_API> enter_request(request_id, true);
  if (enter_request.failed()) {
    Log(PP_LOGLEVEL_ERROR,
        "PPB_URLLoader.Open: invalid request resource ID. (Hint to C++ wrapper"
        " users: use the ResourceRequest constructor that takes an instance or"
        " else the request will be null.)");
    return PP_ERROR_BADARGUMENT;
  }
  return Open(enter_request.object()->GetData(), 0, callback);
}

int32_t URLLoaderResource::Open(
    const ::ppapi::URLRequestInfoData& request_data,
    int requestor_pid,
    scoped_refptr<TrackedCallback> callback) {
  int32_t rv = ValidateCallback(callback);
  if (rv != PP_OK)
    return rv;
  if (mode_ != MODE_WAITING_TO_OPEN)
    return PP_ERROR_INPROGRESS;

  request_data_ = request_data;

  mode_ = MODE_OPENING;
  is_asynchronous_load_suspended_ = false;

  RegisterCallback(callback);
  Post(RENDERER, PpapiHostMsg_URLLoader_Open(request_data));
  return PP_OK_COMPLETIONPENDING;
}

int32_t URLLoaderResource::FollowRedirect(
    scoped_refptr<TrackedCallback> callback) {
  int32_t rv = ValidateCallback(callback);
  if (rv != PP_OK)
    return rv;
  if (mode_ != MODE_OPENING)
    return PP_ERROR_INPROGRESS;

  SetDefersLoading(false);  // Allow the redirect to continue.
  RegisterCallback(callback);
  return PP_OK_COMPLETIONPENDING;
}

PP_Bool URLLoaderResource::GetUploadProgress(int64_t* bytes_sent,
                                              int64_t* total_bytes_to_be_sent) {
  if (!request_data_.record_upload_progress) {
    *bytes_sent = 0;
    *total_bytes_to_be_sent = 0;
    return PP_FALSE;
  }
  *bytes_sent = bytes_sent_;
  *total_bytes_to_be_sent = total_bytes_to_be_sent_;
  return PP_TRUE;
}

PP_Bool URLLoaderResource::GetDownloadProgress(
    int64_t* bytes_received,
    int64_t* total_bytes_to_be_received) {
  if (!request_data_.record_download_progress) {
    *bytes_received = 0;
    *total_bytes_to_be_received = 0;
    return PP_FALSE;
  }
  *bytes_received = bytes_received_;
  *total_bytes_to_be_received = total_bytes_to_be_received_;
  return PP_TRUE;
}

PP_Resource URLLoaderResource::GetResponseInfo() {
  if (response_info_.get())
    return response_info_->GetReference();
  return 0;
}

int32_t URLLoaderResource::ReadResponseBody(
    void* buffer,
    int32_t bytes_to_read,
    scoped_refptr<TrackedCallback> callback) {
  int32_t rv = ValidateCallback(callback);
  if (rv != PP_OK)
    return rv;
  if (!response_info_.get())
    return PP_ERROR_FAILED;

  if (bytes_to_read <= 0 || !buffer)
    return PP_ERROR_BADARGUMENT;

  user_buffer_ = static_cast<char*>(buffer);
  user_buffer_size_ = bytes_to_read;

  if (!buffer_.empty())
    return FillUserBuffer();

  // We may have already reached EOF.
  if (done_status_ != PP_OK_COMPLETIONPENDING) {
    user_buffer_ = NULL;
    user_buffer_size_ = 0;
    return done_status_;
  }

  RegisterCallback(callback);
  return PP_OK_COMPLETIONPENDING;
}

int32_t URLLoaderResource::FinishStreamingToFile(
    scoped_refptr<TrackedCallback> callback) {
  return PP_ERROR_NOTSUPPORTED;
}

void URLLoaderResource::Close() {
  mode_ = MODE_LOAD_COMPLETE;
  done_status_ = PP_ERROR_ABORTED;

  Post(RENDERER, PpapiHostMsg_URLLoader_Close());

  // Abort the callbacks, the plugin doesn't want to be called back after this.
  if (TrackedCallback::IsPending(pending_callback_))
    pending_callback_->PostAbort();
}

void URLLoaderResource::GrantUniversalAccess() {
  Post(RENDERER, PpapiHostMsg_URLLoader_GrantUniversalAccess());
}

void URLLoaderResource::RegisterStatusCallback(
    PP_URLLoaderTrusted_StatusCallback callback) {
  status_callback_ = callback;
}

void URLLoaderResource::OnReplyReceived(
    const ResourceMessageReplyParams& params,
    const IPC::Message& msg) {
  PPAPI_BEGIN_MESSAGE_MAP(URLLoaderResource, msg)
    case PpapiPluginMsg_URLLoader_SendData::ID:
      // Special message, manually dispatch since we don't want the automatic
      // unpickling.
      OnPluginMsgSendData(params, msg);
      break;

    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
        PpapiPluginMsg_URLLoader_ReceivedResponse,
        OnPluginMsgReceivedResponse)
    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
        PpapiPluginMsg_URLLoader_FinishedLoading,
        OnPluginMsgFinishedLoading)
    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
        PpapiPluginMsg_URLLoader_UpdateProgress,
        OnPluginMsgUpdateProgress)
  PPAPI_END_MESSAGE_MAP()
}

void URLLoaderResource::OnPluginMsgReceivedResponse(
    const ResourceMessageReplyParams& params,
    const URLResponseInfoData& data) {
  SaveResponseInfo(data);
  RunCallback(PP_OK);
}

void URLLoaderResource::OnPluginMsgSendData(
    const ResourceMessageReplyParams& params,
    const IPC::Message& message) {
  base::PickleIterator iter(message);
  const char* data;
  int data_length;
  if (!iter.ReadData(&data, &data_length)) {
    NOTREACHED() << "Expecting data";
    return;
  }

  mode_ = MODE_STREAMING_DATA;
  buffer_.insert(buffer_.end(), data, data + data_length);

  // To avoid letting the network stack download an entire stream all at once,
  // defer loading when we have enough buffer.
  // Check for this before we run the callback, even though that could move
  // data out of the buffer. Doing anything after the callback is unsafe.
  DCHECK(request_data_.prefetch_buffer_lower_threshold <
         request_data_.prefetch_buffer_upper_threshold);
  if (!is_streaming_to_file_ &&
      !is_asynchronous_load_suspended_ &&
      (buffer_.size() >= static_cast<size_t>(
          request_data_.prefetch_buffer_upper_threshold))) {
    DVLOG(1) << "Suspending async load - buffer size: " << buffer_.size();
    SetDefersLoading(true);
  }

  if (user_buffer_)
    RunCallback(FillUserBuffer());
  else
    DCHECK(!TrackedCallback::IsPending(pending_callback_));
}

void URLLoaderResource::OnPluginMsgFinishedLoading(
    const ResourceMessageReplyParams& params,
    int32_t result) {
  mode_ = MODE_LOAD_COMPLETE;
  done_status_ = result;
  user_buffer_ = NULL;
  user_buffer_size_ = 0;

  // If the client hasn't called any function that takes a callback since
  // the initial call to Open, or called ReadResponseBody and got a
  // synchronous return, then the callback will be NULL.
  if (TrackedCallback::IsPending(pending_callback_))
    RunCallback(done_status_);
}

void URLLoaderResource::OnPluginMsgUpdateProgress(
    const ResourceMessageReplyParams& params,
    int64_t bytes_sent,
    int64_t total_bytes_to_be_sent,
    int64_t bytes_received,
    int64_t total_bytes_to_be_received) {
  bytes_sent_ = bytes_sent;
  total_bytes_to_be_sent_ = total_bytes_to_be_sent;
  bytes_received_ = bytes_received;
  total_bytes_to_be_received_ = total_bytes_to_be_received;

  if (status_callback_) {
    status_callback_(pp_instance(), pp_resource(),
                     bytes_sent_, total_bytes_to_be_sent_,
                     bytes_received_, total_bytes_to_be_received_);
  }
}

void URLLoaderResource::SetDefersLoading(bool defers_loading) {
  is_asynchronous_load_suspended_ = defers_loading;
  Post(RENDERER, PpapiHostMsg_URLLoader_SetDeferLoading(defers_loading));
}

int32_t URLLoaderResource::ValidateCallback(
    scoped_refptr<TrackedCallback> callback) {
  DCHECK(callback.get());
  if (TrackedCallback::IsPending(pending_callback_))
    return PP_ERROR_INPROGRESS;
  return PP_OK;
}

void URLLoaderResource::RegisterCallback(
    scoped_refptr<TrackedCallback> callback) {
  DCHECK(!TrackedCallback::IsPending(pending_callback_));
  pending_callback_ = callback;
}

void URLLoaderResource::RunCallback(int32_t result) {
  // This may be null when this is a main document loader.
  if (!pending_callback_.get())
    return;

  // If |user_buffer_| was set as part of registering a callback, the paths
  // which trigger that callack must have cleared it since the callback is now
  // free to delete it.
  DCHECK(!user_buffer_);

  // As a second line of defense, clear the |user_buffer_| in case the
  // callbacks get called in an unexpected order.
  user_buffer_ = NULL;
  user_buffer_size_ = 0;
  pending_callback_->Run(result);
}

void URLLoaderResource::SaveResponseInfo(const URLResponseInfoData& data) {
  response_info_ =
      new URLResponseInfoResource(connection(), pp_instance(), data);
}

int32_t URLLoaderResource::FillUserBuffer() {
  DCHECK(user_buffer_);
  DCHECK(user_buffer_size_);

  size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_);
  std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_);
  buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy);

  // If the buffer is getting too empty, resume asynchronous loading.
  if (is_asynchronous_load_suspended_ &&
      buffer_.size() <= static_cast<size_t>(
          request_data_.prefetch_buffer_lower_threshold)) {
    DVLOG(1) << "Resuming async load - buffer size: " << buffer_.size();
    SetDefersLoading(false);
  }

  // Reset for next time.
  user_buffer_ = NULL;
  user_buffer_size_ = 0;
  return base::checked_cast<int32_t>(bytes_to_copy);
}

}  // namespace proxy
}  // namespace ppapi
