// Bend Bank: the host's FFI
// =========================
//
// The few OS calls host.cml needs beyond CakeML's basis_ffi.c, in CakeML's
// FFI convention: c/clen is a string from CakeML, a/alen a byte array both
// ways. Numbers cross as 4 (fd, count) or 8 (time) bytes, big endian.
//
// bank_listen  a = port: listens on 127.0.0.1:port, backlog 4096. a[0] = 0 ok.
// bank_wait    blocks until the next event on any connection, accepting new
//              ones on the way: a = fd, n, bytes. n > 0: the n bytes read;
//              n = 0: the fd closed (by the peer, an error, or idle 30 s).
// bank_write   c = bytes, a = fd: writes all of c. a[0] = 0 ok.
// bank_close   a = fd: closes it.
// bank_open    c = path: opens the log for append. a[0] = 0 ok.
// bank_append  c = line: appends it to the log and fsyncs. a[0] = 0 ok.
// bank_random  a = 16 random bytes.
// bank_time    a = the unix time in seconds.

#include <arpa/inet.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#define MAXFD 65536
#define IDLE 30

static struct pollfd fds[MAXFD]; // indexed by fd; fd -1 is a free slot
static long last[MAXFD];         // the time of each connection's last activity
static int lfd = -1, logfd = -1, hi = 0, cur = 0;

static long get(unsigned char *a, int k) { long n = 0; for (int i = 0; i < k; i++) n = n << 8 | a[i]; return n; }
static void put(unsigned char *a, int k, long n) { for (int i = k - 1; i >= 0; i--, n >>= 8) a[i] = n & 255; }

static int all(int fd, unsigned char *c, long n) {
  for (long w; n > 0; c += w, n -= w) if ((w = write(fd, c, n)) <= 0) return 1;
  return 0;
}

static void watch(int fd) {
  fds[fd].fd = fd; fds[fd].events = POLLIN; last[fd] = time(0);
  if (fd >= hi) hi = fd + 1;
}

void ffibank_listen(unsigned char *c, long clen, unsigned char *a, long alen) {
  struct sockaddr_in in = {.sin_family = AF_INET, .sin_port = htons(get(a, 4))};
  int one = 1;
  signal(SIGPIPE, SIG_IGN);
  for (int i = 0; i < MAXFD; i++) fds[i].fd = -1;
  in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  lfd = socket(AF_INET, SOCK_STREAM, 0);
  setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
  a[0] = bind(lfd, (struct sockaddr *)&in, sizeof in) || listen(lfd, 4096);
  fcntl(lfd, F_SETFL, O_NONBLOCK);
  watch(lfd);
}

void ffibank_wait(unsigned char *c, long clen, unsigned char *a, long alen) {
  for (;;) {
    poll(fds, hi, 1000);
    for (int k = 0; k < hi; k++) {
      int fd = cur = (cur + 1) % hi;
      if (fds[fd].fd < 0) continue;
      if (fd == lfd && fds[fd].revents) {
        int s = accept(lfd, 0, 0);
        struct timeval t = {IDLE, 0};
        if (s >= MAXFD) close(s);
        else if (s >= 0) { setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof t); watch(s); }
      }
      if (fd == lfd || !fds[fd].revents && time(0) - last[fd] < IDLE) continue;
      long n = fds[fd].revents ? read(fd, a + 8, alen - 8) : 0;
      if (n <= 0) { close(fd); fds[fd].fd = -1; n = 0; }
      last[fd] = time(0); put(a, 4, fd); put(a + 4, 4, n);
      return;
    }
  }
}

void ffibank_write(unsigned char *c, long clen, unsigned char *a, long alen) {
  int fd = get(a, 4);
  last[fd] = time(0);
  a[0] = all(fd, c, clen);
}

void ffibank_close(unsigned char *c, long clen, unsigned char *a, long alen) {
  int fd = get(a, 4);
  close(fd); fds[fd].fd = -1;
}

void ffibank_open(unsigned char *c, long clen, unsigned char *a, long alen) {
  char p[4096] = {0};
  memcpy(p, c, clen < 4095 ? clen : 4095);
  logfd = open(p, O_WRONLY | O_APPEND | O_CREAT, 0644);
  a[0] = logfd < 0;
}

void ffibank_append(unsigned char *c, long clen, unsigned char *a, long alen) {
  a[0] = all(logfd, c, clen) || fsync(logfd);
}

void ffibank_random(unsigned char *c, long clen, unsigned char *a, long alen) {
  if (getrandom(a, 16, 0) != 16) abort();
}

void ffibank_time(unsigned char *c, long clen, unsigned char *a, long alen) {
  put(a, 8, time(0));
}
