//
// Copyright (C) 2016 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.
//

#include "trunks/trunks_binder_proxy.h"

#include <base/bind.h>
#include <base/callback.h>
#include <base/logging.h>
#include <binderwrapper/binder_wrapper.h>
#include <utils/Errors.h>

#include "android/trunks/BnTrunksClient.h"
#include "android/trunks/BpTrunks.h"
#include "trunks/binder_interface.h"
#include "trunks/error_codes.h"
#include "trunks/interface.pb.h"

namespace {

// Implements ITrunksClient and forwards response data to a ResponseCallback.
class ResponseObserver : public android::trunks::BnTrunksClient {
 public:
  ResponseObserver(const trunks::CommandTransceiver::ResponseCallback& callback)
      : callback_(callback) {}

  // ITrunksClient interface.
  android::binder::Status OnCommandResponse(
      const std::vector<uint8_t>& response_proto_data) override {
    trunks::SendCommandResponse response_proto;
    if (!response_proto.ParseFromArray(response_proto_data.data(),
                                       response_proto_data.size())) {
      LOG(ERROR) << "TrunksBinderProxy: Bad response data.";
      callback_.Run(
          trunks::CreateErrorResponse(trunks::SAPI_RC_MALFORMED_RESPONSE));
    }
    callback_.Run(response_proto.response());
    return android::binder::Status::ok();
  }

 private:
  trunks::CommandTransceiver::ResponseCallback callback_;
};

}  // namespace

namespace trunks {

bool TrunksBinderProxy::Init() {
  android::sp<android::IBinder> service_binder =
      android::BinderWrapper::GetOrCreateInstance()->GetService(
          kTrunksServiceName);
  if (!service_binder.get()) {
    LOG(ERROR) << "TrunksBinderProxy: Trunks service does not exist.";
    return false;
  }
  trunks_service_ = new android::trunks::BpTrunks(service_binder);
  return true;
}

void TrunksBinderProxy::SendCommand(const std::string& command,
                                    const ResponseCallback& callback) {
  SendCommandRequest command_proto;
  command_proto.set_command(command);
  std::vector<uint8_t> command_proto_data;
  command_proto_data.resize(command_proto.ByteSize());
  if (!command_proto.SerializeToArray(command_proto_data.data(),
                                      command_proto_data.size())) {
    LOG(ERROR) << "TrunksBinderProxy: Failed to serialize protobuf.";
    callback.Run(CreateErrorResponse(TRUNKS_RC_IPC_ERROR));
    return;
  }
  android::sp<ResponseObserver> observer(new ResponseObserver(callback));
  android::binder::Status status =
      trunks_service_->SendCommand(command_proto_data, observer);
  if (!status.isOk()) {
    LOG(ERROR) << "TrunksBinderProxy: Binder error: " << status.toString8();
    callback.Run(CreateErrorResponse(TRUNKS_RC_IPC_ERROR));
    return;
  }
}

std::string TrunksBinderProxy::SendCommandAndWait(const std::string& command) {
  SendCommandRequest command_proto;
  command_proto.set_command(command);
  std::vector<uint8_t> command_proto_data;
  command_proto_data.resize(command_proto.ByteSize());
  if (!command_proto.SerializeToArray(command_proto_data.data(),
                                      command_proto_data.size())) {
    LOG(ERROR) << "TrunksBinderProxy: Failed to serialize protobuf.";
    return CreateErrorResponse(TRUNKS_RC_IPC_ERROR);
  }
  std::vector<uint8_t> response_proto_data;
  android::binder::Status status = trunks_service_->SendCommandAndWait(
      command_proto_data, &response_proto_data);
  if (!status.isOk()) {
    LOG(ERROR) << "TrunksBinderProxy: Binder error: " << status.toString8();
    return CreateErrorResponse(TRUNKS_RC_IPC_ERROR);
  }
  trunks::SendCommandResponse response_proto;
  if (!response_proto.ParseFromArray(response_proto_data.data(),
                                     response_proto_data.size())) {
    LOG(ERROR) << "TrunksBinderProxy: Bad response data.";
    return trunks::CreateErrorResponse(trunks::SAPI_RC_MALFORMED_RESPONSE);
  }
  return response_proto.response();
}

}  // namespace trunks