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

#if __ANDROID__

#include <ditto/binder.h>
#include <ditto/binder_request.h>
#include <ditto/logger.h>
#include <cutils/ashmem.h>
#include <sys/mman.h>


namespace dittosuite {

BinderRequest::BinderRequest(const std::string& kName, const Params& params,
                             const std::string& service_name)
    : Instruction(kName, params), service_name_(service_name) {}

BinderRequestDitto::BinderRequestDitto(const Params& params, const std::string& service_name)
    : BinderRequest(kName, params, service_name) {}

void BinderRequestDitto::RunSingle() {
  const int8_t c = 1;

  int8_t ret = service_->sync(c);
  if (ret != (~c)) {
    LOGF("Wrong result, expected: " + std::to_string(~c) + ", but got: " + std::to_string(ret));
  }
  LOGD("Returned from Binder request: " + std::to_string(ret));
}

void BinderRequestDitto::SetUp() {
  LOGD("Starting binder requester for service: " + service_name_);
  service_ = getBinderService<IDittoBinder>(service_name_);
  service_->start();
  Instruction::SetUp();
}

void BinderRequestDitto::TearDownSingle(bool is_last) {
  Instruction::TearDownSingle(is_last);
  if (is_last) {
    LOGD("This is the last, sending termination request");
    service_->end();
  }
}

BinderRequestMountService::BinderRequestMountService(const Params& params)
    : BinderRequest(kName, params, "mount") {}

void BinderRequestMountService::RunSingle() {
  bool ret = service_->isUsbMassStorageConnected();
  LOGD("Returned from Binder request: " + std::to_string(ret));
}

void BinderRequestMountService::SetUp() {
  LOGD("Starting binder requester for service: " + service_name_);
  service_ = getBinderService<android::IMountService>(service_name_);
  Instruction::SetUp();
}

void BinderRequestMountService::TearDownSingle(bool last) {
  Instruction::TearDownSingle(last);
}

GenericBinderRequest::GenericBinderRequest(const Params& params,
    std::string service_name, int32_t code,
    const google::protobuf::RepeatedPtrField
      <dittosuiteproto::BinderRequest_GenericService_ParcelInput> parcel_input)
    : BinderRequest(kName, params, service_name), parcel_input_(parcel_input),
     service_name_(service_name), code_(code) {}

void GenericBinderRequest::SetUp() {
  android::sp<android::IServiceManager> sm = android::defaultServiceManager();
  service_ = sm->checkService(String16(service_name_.c_str(), service_name_.length()));
}

void GenericBinderRequest::TearDownSingle(bool last) {
  Instruction::TearDownSingle(last);
}

int ParseAshmemWithPath(std::string path, android::Parcel& parcel) {
  int fd = open(path.c_str(), O_RDONLY);
  struct stat statbuf;
  int afd = -1;
  void* ptr = MAP_FAILED;
  if (fd < 0) {
    LOGF("Could not open " + path);
    return -1;
  }
  if (fstat(fd, &statbuf) != 0) {
    LOGF("Could not stat " + path);
    goto error_close_fd;
  }
  afd = ashmem_create_region("ditto", statbuf.st_size);
  if (afd < 0) {
    LOGF("ashmem_create_region failed " + path);
    goto error_close_fd;
  }
  ptr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, afd, 0);
  if (ptr == MAP_FAILED) {
    LOGF("mmap failed " + path);
    goto error_close_afd;
  }
  if (read(fd, ptr, statbuf.st_size) < 0) {
    LOGF("read failed " + path);
    goto error_unmap;
  }
  if (parcel.writeFileDescriptor(afd, true /* take ownership */) == android::OK) {
    // successfully parsed. unmap and afd close is done by the binder server.
    close(fd);
    return 0;
  }
  LOGF("writeFileDescriptor failed " + path);

error_unmap:
  munmap(ptr, statbuf.st_size);
error_close_afd:
  close(afd);
error_close_fd:
  close(fd);
  return -1;
}

int ParseParcelString(const google::protobuf::RepeatedPtrField
      <dittosuiteproto::BinderRequest_GenericService_ParcelInput>& input,
      android::Parcel& parcel) {
  for (const auto &it : input ) {
    std::string data_str = it.data();
    switch (it.type()) {
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_I32: {
        parcel.writeInt32(atoi(data_str.c_str()));
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_I64: {
        parcel.writeInt64(atoll(data_str.c_str()));
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_STRING_16: {
        parcel.writeString16(String16(data_str.c_str(), data_str.length()));
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_F: {
        parcel.writeFloat(atof(data_str.c_str()));
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_D: {
        parcel.writeDouble(atof(data_str.c_str()));
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_NULL_: {
        parcel.writeStrongBinder(nullptr);
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_FD: {
        parcel.writeFileDescriptor(atoi(data_str.c_str()), true /* take ownership */);
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_FD_PATH: {
        int fd = open(data_str.c_str(), O_RDONLY);
        if (fd < 0) {
          LOGF("Could not open " + data_str);
          return -1;
        }
        parcel.writeFileDescriptor(fd, true /* take ownership */);
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_ASHMEM_FD_PATH: {
        if (ParseAshmemWithPath(data_str.c_str(), parcel) < 0) {
          return -1;
        }
        break;
      }
      case dittosuiteproto::BinderRequest_GenericService_ParcelInput_Type_PARCEL: {
        int res = 0;
        auto inputs = it.nested_parcel().parcel_inputs();
        if (inputs.size() == 0) {
          //  Null parcelable flag.
          res = parcel.writeInt32(0);
        } else {
          //  Non-Null parcelable flag.
          res = parcel.writeInt32(1);
          if (res < 0) return res;
          res = ParseParcelString(it.nested_parcel().parcel_inputs(), parcel);
        }
        if (res < 0) {
          return res;
        }
        break;
      }
      default:
        break;
    }
  }
  return  0;
}

void GenericBinderRequest::RunSingle() {
  android::Parcel data, reply;
  data.markForBinder(service_);
  data.writeInterfaceToken(service_ ? service_->getInterfaceDescriptor() : String16());
  if (ParseParcelString(parcel_input_, data)) {
    LOGF("Error parsing parcel string\n");
    return;
  }

  service_->transact(code_, data, &reply);

  std::stringstream ss;
  ss << reply;
  LOGD("Returned from Binder transact:\n" + ss.str());
}
}  // namespace dittosuite

#endif
