// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// 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 "util/linux/ptrace_broker.h"

#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#include <utility>

#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/linux/get_tls.h"
#include "test/multiprocess.h"
#include "util/file/file_io.h"
#include "util/linux/ptrace_client.h"
#include "util/posix/scoped_mmap.h"
#include "util/synchronization/semaphore.h"
#include "util/thread/thread.h"

namespace crashpad {
namespace test {
namespace {

class ScopedTimeoutThread : public Thread {
 public:
  ScopedTimeoutThread() : join_sem_(0) {}
  ~ScopedTimeoutThread() { EXPECT_TRUE(JoinWithTimeout(5.0)); }

 protected:
  void ThreadMain() override { join_sem_.Signal(); }

 private:
  bool JoinWithTimeout(double timeout) {
    if (!join_sem_.TimedWait(timeout)) {
      return false;
    }
    Join();
    return true;
  }

  Semaphore join_sem_;

  DISALLOW_COPY_AND_ASSIGN(ScopedTimeoutThread);
};

class RunBrokerThread : public ScopedTimeoutThread {
 public:
  RunBrokerThread(PtraceBroker* broker)
      : ScopedTimeoutThread(), broker_(broker) {}

  ~RunBrokerThread() {}

 private:
  void ThreadMain() override {
    EXPECT_EQ(broker_->Run(), 0);
    ScopedTimeoutThread::ThreadMain();
  }

  PtraceBroker* broker_;

  DISALLOW_COPY_AND_ASSIGN(RunBrokerThread);
};

class BlockOnReadThread : public ScopedTimeoutThread {
 public:
  BlockOnReadThread(int readfd, int writefd)
      : ScopedTimeoutThread(), readfd_(readfd), writefd_(writefd) {}

  ~BlockOnReadThread() {}

 private:
  void ThreadMain() override {
    pid_t pid = syscall(SYS_gettid);
    LoggingWriteFile(writefd_, &pid, sizeof(pid));

    LinuxVMAddress tls = GetTLS();
    LoggingWriteFile(writefd_, &tls, sizeof(tls));

    CheckedReadFileAtEOF(readfd_);
    ScopedTimeoutThread::ThreadMain();
  }

  int readfd_;
  int writefd_;

  DISALLOW_COPY_AND_ASSIGN(BlockOnReadThread);
};

class SameBitnessTest : public Multiprocess {
 public:
  SameBitnessTest() : Multiprocess(), mapping_() {}
  ~SameBitnessTest() {}

 protected:
  void PreFork() override {
    ASSERT_NO_FATAL_FAILURE(Multiprocess::PreFork());

    size_t page_size = getpagesize();
    ASSERT_TRUE(mapping_.ResetMmap(nullptr,
                                   page_size * 3,
                                   PROT_READ | PROT_WRITE,
                                   MAP_PRIVATE | MAP_ANON,
                                   -1,
                                   0));
    ASSERT_TRUE(mapping_.ResetAddrLen(mapping_.addr(), page_size * 2));

    auto buffer = mapping_.addr_as<char*>();
    for (size_t index = 0; index < mapping_.len(); ++index) {
      buffer[index] = index % 256;
    }
  }

 private:
  void MultiprocessParent() override {
    LinuxVMAddress child1_tls;
    ASSERT_TRUE(LoggingReadFileExactly(
        ReadPipeHandle(), &child1_tls, sizeof(child1_tls)));

    pid_t child2_tid;
    ASSERT_TRUE(LoggingReadFileExactly(
        ReadPipeHandle(), &child2_tid, sizeof(child2_tid)));

    LinuxVMAddress child2_tls;
    ASSERT_TRUE(LoggingReadFileExactly(
        ReadPipeHandle(), &child2_tls, sizeof(child2_tls)));

    int socks[2];
    ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0);
    ScopedFileHandle broker_sock(socks[0]);
    ScopedFileHandle client_sock(socks[1]);

#if defined(ARCH_CPU_64_BITS)
    constexpr bool am_64_bit = true;
#else
    constexpr bool am_64_bit = false;
#endif  // ARCH_CPU_64_BITS
    PtraceBroker broker(broker_sock.get(), am_64_bit);
    RunBrokerThread broker_thread(&broker);
    broker_thread.Start();

    {
      PtraceClient client;
      ASSERT_TRUE(client.Initialize(client_sock.get(), ChildPID()));

      EXPECT_EQ(client.GetProcessID(), ChildPID());
      EXPECT_TRUE(client.Attach(child2_tid));
      EXPECT_EQ(client.Is64Bit(), am_64_bit);

      ThreadInfo info1;
      ASSERT_TRUE(client.GetThreadInfo(ChildPID(), &info1));
      EXPECT_EQ(info1.thread_specific_data_address, child1_tls);

      ThreadInfo info2;
      ASSERT_TRUE(client.GetThreadInfo(child2_tid, &info2));
      EXPECT_EQ(info2.thread_specific_data_address, child2_tls);

      auto buffer = std::make_unique<char[]>(mapping_.len());
      ASSERT_TRUE(client.Read(
          mapping_.addr_as<VMAddress>(), mapping_.len(), buffer.get()));
      auto expected_buffer = mapping_.addr_as<char*>();
      for (size_t index = 0; index < mapping_.len(); ++index) {
        EXPECT_EQ(buffer[index], expected_buffer[index]);
      }

      char first;
      ASSERT_TRUE(
          client.Read(mapping_.addr_as<VMAddress>(), sizeof(first), &first));
      EXPECT_EQ(first, expected_buffer[0]);

      char last;
      ASSERT_TRUE(
          client.Read(mapping_.addr_as<VMAddress>() + mapping_.len() - 1,
                      sizeof(last),
                      &last));
      EXPECT_EQ(last, expected_buffer[mapping_.len() - 1]);

      char unmapped;
      EXPECT_FALSE(client.Read(mapping_.addr_as<VMAddress>() + mapping_.len(),
                               sizeof(unmapped),
                               &unmapped));
    }
  }

  void MultiprocessChild() override {
    LinuxVMAddress tls = GetTLS();
    ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &tls, sizeof(tls)));

    BlockOnReadThread thread(ReadPipeHandle(), WritePipeHandle());
    thread.Start();

    CheckedReadFileAtEOF(ReadPipeHandle());
  }

  ScopedMmap mapping_;

  DISALLOW_COPY_AND_ASSIGN(SameBitnessTest);
};

TEST(PtraceBroker, SameBitness) {
  SameBitnessTest test;
  test.Run();
}

// TODO(jperaza): Test against a process with different bitness.

}  // namespace
}  // namespace test
}  // namespace crashpad