#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>

/* library includes */
#include <uv/errno.h>
#include <uv.h>
#include <systemd/sd-daemon.h>

#define INTERESTING_OCTETS 14
#define DEFAULT_IDLE_TIMEOUT (10*60*1000)

enum protocol {
  DEMUX_HTTP = 0,
  DEMUX_DNS = 1,
  DEMUX_UNKNOWN = 2,
};

const char *protocol_names[] = { "HTTP", "DNS", "UNKNNOWN" };


/* see README.md for justification of the heuristics below: */
static inline enum protocol surmise_protocol(unsigned char *bytestream, size_t len) {
  if (len < 5)
    return DEMUX_UNKNOWN;
  int ix;
  bool all_interesting = len >= INTERESTING_OCTETS;

  for (ix = 0; ix < 5; ix++)
    if ((bytestream[ix] < 0x0a) || (bytestream[ix] > 0x7f))
      return DEMUX_DNS;

  if (memcmp(bytestream, "GET ", 4) != 0) { /* not HTTP/0.9 */
    for (ix = 5; ix < (all_interesting ? INTERESTING_OCTETS : len); ix++)
      if ((bytestream[ix] < 0x0a) || (bytestream[ix] > 0x7f))
        return DEMUX_DNS;
  } else { /* might be HTTP/0.9 */
    bool seen_sp = false;
    bool seen_high = false;
    for (ix = 5; ix < (all_interesting ? INTERESTING_OCTETS : len); ix++) {
      if (seen_sp && seen_high)
        return DEMUX_DNS;
      if (bytestream[ix] > 0x7f)
        seen_high = true;
      else if (bytestream[ix] < 0x0a)
        return DEMUX_DNS;
      else if (bytestream[ix] == 0x20)
        seen_sp = true;
      else if (bytestream[ix] == 0x0a)
        return DEMUX_HTTP;
    }
  }
  if (all_interesting)
    return DEMUX_HTTP;
  else
    return DEMUX_UNKNOWN;
}

struct demuxer;

/* managed streams, which will be sorted by last_touched */
struct streamstate {
  int id;
  struct {
    uv_tcp_t stream;
    size_t incoming;
    unsigned char *staging;
    size_t bytes_read;
    size_t bytes_written;
    uv_write_t writer;
    uv_shutdown_t shutdown;
    bool initialized;
    bool eof_seen;
    bool closed;
  } x[2];

  size_t first_read_size;
  enum protocol surmised_protocol;
  uv_connect_t connect;
  bool released;
  uint64_t last_touched;
  uv_timer_t timer;
  bool timer_closed;
};


int stream_splice(struct streamstate *s, uv_stream_t *from);
int stream_exchange(struct streamstate *s);
struct streamstate *new_streamstate(struct demuxer* demux);
void free_streamstate(struct streamstate *str);
void touch_streamstate(struct streamstate *st);


/* program state: */

struct demuxer {
  struct addrinfo *http_targets, *dns_targets, *next_http, *next_dns;
  uv_loop_t *loop;
  uv_signal_t usr1_signal;
  uv_signal_t term_signal;
  int next_stream_id;
  int active_streams;
  bool debug;
  uint64_t stream_timeout; /* in milliseconds */
  uv_tcp_t *listeners;
  int max_listeners;
  int listener_count;
};


struct demuxer* new_demuxer(uv_loop_t *loop, int max_listeners);
void free_demuxer(struct demuxer* demux);
static void demuxer_rotate(struct demuxer *demuxer, enum protocol proto);
static const struct addrinfo* demuxer_getaddrinfo(const struct demuxer *demuxer, enum protocol proto);
static void demuxer_dump(struct demuxer *demuxer, FILE *f);
void demuxer_signal_usr1(uv_signal_t *sig, int signum);
void demuxer_signal_term(uv_signal_t *sig, int signum);

/* workflow steps */

void new_inbound(uv_stream_t* server, int status);
void first_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf);
void inbound_first_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
void stream_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf);
void outbound_connection(uv_connect_t *req, int status);
void write_complete(uv_write_t* write, int status);
void stream_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
void interesting_sent(uv_write_t* req, int status);
void after_shutdown(uv_shutdown_t* req, int status);
void close_streamstate_stream(uv_handle_t *stream);
void close_streamstate_timer(uv_handle_t *timer);
void streamstate_timeout(uv_timer_t *timer);

struct min_max_streamstate {
  const struct streamstate *min, *max;
};

void get_min_max_streamstate(uv_handle_t *h, void *x);

/* *************** utilities *************** */


static void dump_addrinfo(FILE *f, const char *label, const struct addrinfo *a, bool is_current) {
  int port = 0;
  const char *fam;
  char paddr[INET6_ADDRSTRLEN];
  const void *ia = NULL;
  if (a->ai_family == AF_INET6) {
    struct sockaddr_in6* ip = (struct sockaddr_in6*)a->ai_addr;
    port = ntohs(ip->sin6_port);
    fam = "AF_INET6";
    ia = &(ip->sin6_addr);
  } else if (a->ai_family == AF_INET) {
    struct sockaddr_in* ip = (struct sockaddr_in*)a->ai_addr;
    port = ntohs(ip->sin_port);
    fam = "AF_INET";
    ia = &(ip->sin_addr);
  } else {
    fam = "UNKNOWN";
  }
  if (ia) {
    if (inet_ntop(a->ai_family, ia, paddr, sizeof(paddr)) == NULL) {
      int e = errno;
      /* these shenanigans are all for the GNU variant of
         strerror_r(3), not the XSI variant: */
      const char * e2 = strerror_r(e, paddr+6, sizeof(paddr)-(6+1));
      if (e2 != paddr+6)
        strncpy(paddr+6, e2, sizeof(paddr)-(6+1));
      memcpy(paddr, "<err> ", 6);
      paddr[sizeof(paddr)-1] = '\0';
    }
  } else {
    strcpy(paddr, "<unknown>");
  }
    
  fprintf(f, "%s%s: fam: %s(%d) socktype: %d, proto: %d, addr: %s, port: %d\n",
          (is_current ? "[*]" : "   "), label, fam,
          a->ai_family, a->ai_socktype, a->ai_protocol,
          paddr, port);
}


static int get_target_addrinfo(const char* envname, const char* defaultnode, const char *defaultservice, struct addrinfo**res, bool debug) {
  char *n = getenv(envname);
  const char *node = NULL, *service = NULL;
  const struct addrinfo hints = {
    .ai_flags = AF_UNSPEC,
    .ai_socktype = SOCK_STREAM
  };
  
  if (n != NULL) {
    char *colon = strrchr(n, ':');
    char *bracket = strrchr(n, ']');
    if (debug)
      fprintf(stderr, "target for %s is %s\n", envname, n);
    if (colon != NULL) {
      if (bracket != NULL && (bracket > colon)) {
        /* this was just a colon in a bracketed IPv6 address, not a
           port delimiter */
      } else {
        *colon = '\0';
        service = colon + 1;
      }
    }
    node = n;
  } else {
    if (debug)
      fprintf(stderr, "target for %s is default (%s)\n", envname, defaultnode);
    node = defaultnode;
  }
  if (service == NULL) {
    service = defaultservice;
  }
  return getaddrinfo(node, service, &hints, res);
}




/* *************** streamstate *************** */


void stream_dump(FILE *f, struct streamstate *s) {
  fprintf(f, "[%d](%p) in: %p out %p\n", s->id, (void*)s, (void*)&(s->x[0].stream), (void*)&(s->x[1].stream));
}


struct streamstate *new_streamstate(struct demuxer *demux) {
  int err;
  struct streamstate *ret = calloc(1, sizeof(struct streamstate));

  if (ret) {
    if ((err = uv_timer_init(demux->loop, &(ret->timer)))) {
      fprintf(stderr, "[%d] Failed to uv_timer_init: (%d) %s\n", ret->id,
              err, uv_strerror(err));
      free(ret);
      return NULL;
    }
    ret->timer.data = ret;
    ret->surmised_protocol = DEMUX_UNKNOWN;
    ret->id = demux->next_stream_id++;
    demux->active_streams++;
    for (int ix = 0; ix < 2; ix++) {
      if ((err = uv_tcp_init(demux->loop, &(ret->x[ix].stream)))) {
        fprintf(stderr, "[%d] Failed to uv_tcp_init %s: (%d) %s\n", ret->id,
                (ix? "outbound":"inbound"), err, uv_strerror(err));
        free(ret);
        return NULL;
      }
      ret->x[ix].stream.data = ret;
    }
    touch_streamstate(ret);
  }
  return ret;
}

void free_streamstate(struct streamstate *st) {
  if (!st->released) {
    st->released = true;
    for (int ix = 0; ix < 2; ix++)
      uv_close((uv_handle_t*) (&(st->x[ix].stream)), close_streamstate_stream);
    uv_close((uv_handle_t*)(&(st->timer)), close_streamstate_timer);
  }
}

void touch_streamstate(struct streamstate *st) {
  int err;
  struct demuxer *demux = st->x[0].stream.loop->data;
  st->last_touched = uv_now(demux->loop);

  if ((err = uv_timer_stop(&(st->timer))))
    fprintf(stderr, "[%d] failed to uv_timer_stop: (%d) %s\n", st->id, err, uv_strerror(err));

  if (demux->stream_timeout > 0)
    if ((err = uv_timer_start(&(st->timer), streamstate_timeout, demux->stream_timeout, 0)))
      fprintf(stderr, "[%d] failed to uv_timer_start(%llu): (%d) %s\n",
              st->id, (long long unsigned int)(demux->stream_timeout),
              err, uv_strerror(err));
}

/* *************** demuxer *************** */

struct demuxer* new_demuxer(uv_loop_t *loop, int max_listeners) {
  int err;
  struct demuxer *ret = calloc(1, sizeof(struct demuxer));
  if (ret == NULL)
    return NULL;
  ret->listeners = calloc(max_listeners, sizeof(uv_tcp_t));
  if (ret->listeners == NULL) {
    free(ret);
    return NULL;
  }
  ret->max_listeners = max_listeners;
  
  ret->loop = loop;
  struct { uv_signal_t* sig; int signum; uv_signal_cb handler; } sigs[] = {
    { &(ret->usr1_signal), SIGUSR1, demuxer_signal_usr1 },
    { &(ret->term_signal), SIGTERM, demuxer_signal_term }
  };

  for (int ix = 0; ix < sizeof(sigs)/sizeof(sigs[0]); ix++) {
    if ((err = uv_signal_init(loop, sigs[ix].sig))) {
      fprintf(stderr, "Failed to init signal handler: (%d) %s\n", err, uv_strerror(err));
      free(ret);
      return NULL;
    }
    if ((err = uv_signal_start(sigs[ix].sig, sigs[ix].handler, sigs[ix].signum))) {
      fprintf(stderr, "Failed to connect signal handler (%d): (%d) %s\n", sigs[ix].signum, err, uv_strerror(err));
      free(ret);
      return NULL;
    }
  }
  loop->data = ret;
  return ret;
}

void demuxer_going_down(uv_handle_t *h, void *x) {
  if (h == NULL || x == NULL || h->data == NULL)
    return;
  struct demuxer *demux = x;
  if (h->loop != demux->loop)
    return;
  if (h->type != UV_TCP)
    return;
  struct streamstate *st = h->data;
  free_streamstate(st);
}

void free_demuxer(struct demuxer* demux) {
  uv_walk(demux->loop, demuxer_going_down, demux);
  for (int i = 0; i < demux->listener_count; i++) {
    uv_close((uv_handle_t*)(demux->listeners + i), NULL);
  }
  uv_close((uv_handle_t*)(&demux->usr1_signal), NULL);
  uv_close((uv_handle_t*)(&demux->term_signal), NULL);
  int err = uv_run(demux->loop, UV_RUN_NOWAIT);
  if (demux->debug)
    fprintf(stderr, "final loop instance completed: %d (%s)\n", err,
            err ? (err < 0 ? uv_strerror(err) : "unknown") : "none");
  
  free(demux->listeners);
  free(demux);
}

static void demuxer_rotate(struct demuxer *demuxer, enum protocol proto) {
  if (proto == DEMUX_HTTP) {
    demuxer->next_http = demuxer->next_http->ai_next;
    if (demuxer->next_http == NULL)
      demuxer->next_http = demuxer->http_targets;
  } else if (proto == DEMUX_DNS) {
    demuxer->next_dns = demuxer->next_dns->ai_next;
    if (demuxer->next_dns == NULL)
      demuxer->next_dns = demuxer->dns_targets;
  } else {
    assert(false);
  }
}
static const struct addrinfo* demuxer_getaddrinfo(const struct demuxer *demuxer, enum protocol proto) {
  if (proto == DEMUX_HTTP)
    return demuxer->next_http;
  else if (proto == DEMUX_DNS)
    return demuxer->next_dns;
  else
    assert(false);
}

void demuxer_signal_usr1(uv_signal_t *sig, int signum) {
  assert(sig != NULL);
  assert(signum = SIGUSR1);
  struct demuxer *demux = sig->loop->data;

  demuxer_dump(demux, stderr);
}
void demuxer_signal_term(uv_signal_t *sig, int signum) {
  assert(sig != NULL);
  assert(signum = SIGTERM);
  uv_stop(sig->loop);
}

static inline void showtime(FILE *f, const char *label, int id, uint64_t t, uint64_t now) {
  assert(now >= t);
  fprintf(f, "%s: [%d] last_touched: %g seconds ago\n",
          label, id, (double)(now-t)/1000);
}

static void demuxer_dump(struct demuxer *demuxer, FILE *f) {
  const struct addrinfo *x;
  uint64_t now;
  now = uv_now(demuxer->loop);
  struct min_max_streamstate s = { .min = NULL, .max = NULL };
  uv_walk(demuxer->loop, get_min_max_streamstate, &s);

  fprintf(f, "Total created: %d\nOutstanding: %d\nDefault timeout: %llums\n",
          demuxer->next_stream_id, demuxer->active_streams, (long long unsigned int)demuxer->stream_timeout);
  if (s.min)
    showtime(f, "oldest", s.min->id, s.min->last_touched, now);
  if (s.max)
    showtime(f, "youngest", s.max->id, s.max->last_touched, now);
  
  for (x = demuxer->dns_targets; x; x = x->ai_next)
    dump_addrinfo(f, "DNS", x, x == demuxer->next_dns);
  for (x = demuxer->http_targets; x; x = x->ai_next)
    dump_addrinfo(f, "HTTP", x, x == demuxer->next_http);
}

int demuxer_add_listener_fd(struct demuxer *demuxer, int fd, int listen_backlog) {
  int err;
  if (sd_is_socket(fd, AF_UNSPEC, SOCK_STREAM, 1) <= 0) {
    fprintf(stderr, "File descriptor %d is not a stream socket, ignoring.\n", fd);
    return 0;
  }
  if (demuxer->listener_count >= demuxer->max_listeners) {
    fprintf(stderr, "too many listeners! (have: %d, max: %d) ignoring file descriptor %d\n",
            demuxer->listener_count, demuxer->max_listeners, fd);
    return 0;
  }
  if ((err = uv_tcp_init(demuxer->loop, demuxer->listeners + demuxer->listener_count))) {
    fprintf(stderr, "failed to uv_tcp_init: %s\n", uv_strerror(err));
    return 1;
  }
  /* listeners have NULL data, compared with normal streams, which
     point to their enclosing struct streamstate */
  demuxer->listeners[demuxer->listener_count].data = NULL;

  if ((err = uv_tcp_open(demuxer->listeners + demuxer->listener_count, fd))) {
    fprintf(stderr, "Failed to uv_tcp_open (fd: %d): %s\n", fd, uv_strerror(err));
    return 1;
  }
  if ((err = uv_listen((uv_stream_t*)(demuxer->listeners + demuxer->listener_count),
                       listen_backlog, new_inbound))) {
    fprintf(stderr, "Failed to listen on stream (fd: %d): %s\n", fd, uv_strerror(err));
    return 1;
  }
  demuxer->listener_count++;
  return 0;
}



/* *************** workflow stages  *************** */


void new_inbound(uv_stream_t* server, int status) {
  assert(server != NULL);
  if (status) {
    fprintf(stderr, "ignoring weird non-zero new_inbound() status: (%d) %s\n", status, uv_strerror(status));
  } else {
    struct streamstate *st = new_streamstate(server->loop->data);
    int err;
    if (st == NULL) {
      fprintf(stderr, "No RAM for new connection\n");
      return;
    }
    
    if ((err = uv_accept(server, (uv_stream_t*)(&(st->x[0].stream))))) {
      fprintf(stderr, "failed to uv_accept: %s\n", uv_strerror(err));
      free_streamstate(st);
      return;
    }
    st->x[0].initialized = true;
    if ((err = uv_read_start((uv_stream_t*)(&(st->x[0].stream)), first_alloc, inbound_first_read))) {
      fprintf(stderr, "failed to uv_read_start: %s\n", uv_strerror(err));
      free_streamstate(st);
      return;
    }
  }
}

void first_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
  struct streamstate *st = handle->data;
  assert(st != NULL);
  assert((uv_handle_t*)(&st->x[0].stream) == handle);

  if (st->x[0].staging == NULL) {
    if (suggested_size < INTERESTING_OCTETS)
      suggested_size = INTERESTING_OCTETS;
    st->x[0].staging = malloc(suggested_size);
    if (st->x[0].staging == NULL)
      fprintf(stderr, "first_alloc malloc() failed: (%d) %s\n", errno, strerror(errno));
    st->first_read_size = suggested_size;
  }
  
  buf->base = (char*)(st->x[0].staging) + st->x[0].bytes_read;
  if (buf->base)
    buf->len = st->first_read_size - st->x[0].bytes_read;
  else
    buf->len = 0;
}


void inbound_first_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
  int err;
  assert(stream != NULL);
  assert(stream->data != NULL);
  struct streamstate *st = stream->data;
  assert((uv_stream_t*)(&(st->x[0].stream)) == stream); /* this must only be called on input! */
  assert(st->x[0].initialized);
  assert(st->surmised_protocol == DEMUX_UNKNOWN);
  assert((unsigned char*)(buf->base) == st->x[0].staging + st->x[0].bytes_read);
  struct demuxer *demuxer = stream->loop->data;

  if (nread == 0 || nread == UV_EAGAIN || nread == UV_EBUSY) {
    fprintf(stderr, "[%d] ignoring weird inbound_first_read() with status: (%zd), %s\n",
            st->id, nread, uv_strerror(nread));
    return;
  }
    
  if (nread < 0) {
    fprintf(stderr, "[%d] error on inbound stream before we could interpret it (%zd): %s\n", st->id, nread, uv_strerror(nread));
    free_streamstate(st);
    return;
  }

  if (demuxer->debug)
    fprintf(stderr, "[%d] inbound initial read %zd octets\n", st->id, nread);

  st->x[0].bytes_read += nread;
  st->surmised_protocol = surmise_protocol(st->x[0].staging, st->x[0].bytes_read);

  if (st->surmised_protocol == DEMUX_UNKNOWN) {
    fprintf(stderr, "[%d] inbound stream is still unknown: only sent %zd octets so far (%zd in this flight)\n",
            st->id, st->x[0].bytes_read, nread);
    touch_streamstate(st);
    return;
  }
  /* do not read more from the inbound stream until we've established
     the outbound stream */
  if ((err = uv_read_stop(stream))) {
    fprintf(stderr, "[%d] inbound initial uv_read_stop error %d: %s\n", st->id, err, uv_strerror(err));
    free_streamstate(st);
    return;
  }
  const struct addrinfo *addr = demuxer_getaddrinfo(demuxer, st->surmised_protocol);
  if (demuxer->debug) {
    fprintf(stderr, "[%d] new stream is %s\n", st->id, protocol_names[st->surmised_protocol]);
    dump_addrinfo(stderr, protocol_names[st->surmised_protocol], addr, false);
  }
  
  demuxer_rotate(demuxer, st->surmised_protocol);

  if ((err = uv_tcp_connect(&st->connect, &(st->x[1].stream),
                            addr->ai_addr, outbound_connection))) {
    fprintf(stderr, "[%d] Failed to uv_tcp_connect during new outbound stream: (%d) %s\n",
            st->id, err, uv_strerror(err));
    free_streamstate(st);
  }
  touch_streamstate(st);
}

void outbound_connection(uv_connect_t *req, int status) {
  int err;
  assert(req != NULL);
  assert(req->handle != NULL);

  struct streamstate *st = req->handle->data;
  assert(st != NULL);
  assert(((uv_stream_t*)(&(st->x[1]))) == req->handle); /* this should be the outbound leg */
  assert(!st->x[1].initialized);
  
  if (status) {
    fprintf(stderr, "[%d] failed to complete the connection: status %d (%s)\n",
            st->id, status, uv_strerror(status));
    free_streamstate(st);
    return;
  }
  st->x[1].initialized = true;
  /* send the interesting bits along */
  const struct uv_buf_t buf = { .base = (char*)(st->x[0].staging), .len = st->x[0].bytes_read  };
  if ((err = uv_write(&(st->x[1].writer), (uv_stream_t*)(&(st->x[1].stream)),
                      &buf, 1, write_complete))) {
    fprintf(stderr, "[%d] Failed to uv_write first frame to outbound: (%d) %s\n",
            st->id, err, uv_strerror(err));
    free_streamstate(st);
    return;
  }
  /* start reading from outbound: */
  if ((err = uv_read_start((uv_stream_t*)(&(st->x[1].stream)), stream_alloc, stream_read))) {
    fprintf(stderr, "[%d] Failed to uv_read_start outbound: (%d) %s\n",
            st->id, err, uv_strerror(err));
    free_streamstate(st);
    return;
  }
  touch_streamstate(st);
}

void stream_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
  struct streamstate *st = handle->data;
  assert(st != NULL);
  
  for (int ix = 0; ix < 2; ix++)
    if ((uv_tcp_t*)handle == &(st->x[ix].stream)) {
      assert(st->x[ix].staging == NULL);
      st->x[ix].staging = malloc(suggested_size);
      if (st->x[ix].staging == NULL)
        fprintf(stderr, "stream_alloc malloc() failed: (%d) %s\n", errno, strerror(errno));

      buf->base = (char*)(st->x[ix].staging);
      buf->len = buf->base ? suggested_size : 0;
    }
}

void write_complete(uv_write_t* write, int status) {
  int err;
  assert(write != NULL);
  assert(write->handle != NULL);
  struct streamstate *st = write->handle->data;
  assert(st != NULL);
  assert(st->x[0].initialized);
  assert(st->x[1].initialized);
  
  for (int ix = 0; ix < 2; ix++)
    if ((uv_tcp_t*)(write->handle) == &(st->x[ix].stream)) {
      /* free the buffer from the other since it has been written: */
      assert(st->x[!ix].staging != NULL);
      free(st->x[!ix].staging);
      st->x[!ix].staging = NULL;

      if (status) {
        fprintf(stderr, "[%d] write to %s failed: (%d) %s\n", st->id, (ix?"outbound":"inbound"),
                status, uv_strerror(status));
        free_streamstate(st);
        return;
      }
      /* re-enable reading from the other */
      if ((err = uv_read_start((uv_stream_t*)&(st->x[!ix].stream), stream_alloc, stream_read))) {
        fprintf(stderr, "[%d] re-enabling reading from %s failed: (%d) %s\n", st->id, (!ix?"outbound":"inbound"),
                status, uv_strerror(status));
        free_streamstate(st);
        return;
      }
      touch_streamstate(st);
      return;
    }
  assert(false); /* should never reach here */
}


void stream_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
  assert(stream != NULL);
  struct streamstate *st = stream->data;
  assert(st != NULL);
  assert(st->x[0].initialized);
  assert(st->x[1].initialized);
  struct demuxer *demuxer = stream->loop->data;
  assert(demuxer != NULL);
  int err;

  for (int ix = 0; ix < 2; ix++)
    if ((uv_tcp_t*)stream == &(st->x[ix].stream)) {
      const char *indir = (ix? "outbound" : "inbound");
      const char *outdir = (ix? "inbound" : "outbound");
      assert(!st->x[ix].eof_seen);
      touch_streamstate(st);
      
      if (nread == 0 || nread == UV_EAGAIN || nread == UV_EBUSY ||
          nread ==UV_ENOBUFS || nread == UV_EINTR) {
        fprintf(stderr, "[%d] ignoring weird stream_read() with status: (%zd), %s\n",
                st->id, nread, uv_strerror(nread));
        return;
      }
  
      if (nread == UV_EOF) {
        if (demuxer->debug)
          fprintf(stderr, "[%d] EOF received from %s leg\n", st->id, indir);
        /* we've just seen the EOF from one channel -- shut down the
           write side of the other. */
        st->x[ix].eof_seen = true;
        if ((err = uv_shutdown(&(st->x[!ix].shutdown), (uv_stream_t*)(&(st->x[!ix].stream)), after_shutdown))) {
          fprintf(stderr, "[%d] error calling uv_shutdown() on %s leg: (%d) %s\n", st->id, outdir, err, uv_strerror(err));
          free_streamstate(st);
        }
        return;
      }
      if (nread < 0) {
        fprintf(stderr, "[%d] closing due to %s read error: (%zd): %s\n",
                st->id, indir, nread, uv_strerror(nread));
        free_streamstate(st);
        return;
      }
      if (demuxer->debug)
        fprintf(stderr, "[%d] %s read %zd octets\n", st->id, indir, nread);
      assert((unsigned char*)(buf->base) == st->x[ix].staging);
      if ((err = uv_read_stop(stream))) {
        fprintf(stderr, "[%d] %s uv_read_stop error %d: %s\n", st->id, indir, err, uv_strerror(err));
        free_streamstate(st);
        return;
      }
      st->x[ix].bytes_read += nread;

      const struct uv_buf_t buf = { .base = (char*)(st->x[ix].staging), .len = nread };
      /* schedule write to other channel */
      if ((err = uv_write(&(st->x[!ix].writer), (uv_stream_t*)&(st->x[!ix].stream),
                          &buf, 1, write_complete))) {
        fprintf(stderr, "[%d] %s uv_write failed: (%d) %s\n", st->id, outdir, err, uv_strerror(err));
        free_streamstate(st);
        return;
      }
      return;
    }
  assert(false); /* should never reach here */
}


void after_shutdown(uv_shutdown_t* req, int status) {
  assert(req != NULL);
  assert(req->handle != NULL);
  assert(req->handle->data != NULL);
  struct streamstate *st = req->handle->data;
  assert(st->x[0].initialized);
  assert(st->x[1].initialized);

  for (int ix = 0; ix < 2; ix++)
    if ((uv_tcp_t*)(req->handle) == &(st->x[ix].stream)) {
      if (status) {
        fprintf(stderr, "[%d] error in shutdown of %s stream: (%d) %s\n", st->id,
                (ix?"outbound":"inbound"), status, uv_strerror(status));
        free_streamstate(st);
        return;
      }
      /* if we've seen eof on both sides, we can shut this down */
      if (st->x[!ix].eof_seen)
        free_streamstate(st);
      else
        touch_streamstate(st);
      return;
    }
  assert(false); /* should never have gotten here. */
}

inline void streamstate_dispose(struct streamstate *st, struct demuxer *demux) {
  if (st->timer_closed && 
      st->x[0].closed &&
      st->x[1].closed) {
    demux->active_streams--;
    for (int ix = 0; ix < 2; ix++)
      if (st->x[ix].staging) {
        free(st->x[ix].staging);
        st->x[ix].staging = NULL;
      }
    free(st);
  }
}


void close_streamstate_timer(uv_handle_t *timer) {
  assert(timer != NULL);
  assert(timer->data != NULL);
  struct streamstate *st = timer->data;
  assert(timer->loop != NULL);
  struct demuxer *demux = timer->loop->data;
  if (demux->debug)
    fprintf(stderr, "[%d] close_streamstate_timer\n", st->id);
  st->timer_closed = true;
  streamstate_dispose(st, demux);
}

void close_streamstate_stream(uv_handle_t *stream) {
  assert(stream != NULL);
  assert(stream->data != NULL);
  struct streamstate *st = stream->data;
  int found = -1;
  assert(stream->loop != NULL);
  struct demuxer *demux = stream->loop->data;
  assert(demux != NULL);

  for (int ix = 0; ix < 2; ix++)
    if ((uv_tcp_t*)stream == &(st->x[ix].stream)) {
      found = ix;
    }
  if (demux->debug)
    fprintf(stderr, "[%d] close_streamstate_stream %d\n", st->id, found);
  assert(found != -1);
  st->x[found].closed = true;
  streamstate_dispose(st, demux);
}


void streamstate_timeout(uv_timer_t *timer) {
  assert(timer != NULL);
  assert(timer->data != NULL);
  struct streamstate *st = timer->data;
  assert(timer->loop != NULL);
  struct demuxer *demux = timer->loop->data;
  assert(demux != NULL);
  if (demux->debug)
    fprintf(stderr, "[%d] closing stream due to IDLE_TIMEOUT\n", st->id);
  free_streamstate(st);
}


void get_min_max_streamstate(uv_handle_t *h, void *x) {
  if (h->type != UV_TCP)
    return;
  if (h->data == NULL)
    return;
  if (h->data == h->loop)
    return;
  if (x == NULL)
    return;

  struct min_max_streamstate *minmax = x;
  struct streamstate *s = h->data;

  if (minmax->min == NULL || minmax->min->last_touched > s->last_touched)
    minmax->min = s;
  if (minmax->max == NULL || minmax->max->last_touched < s->last_touched)
    minmax->max = s;
}


int main(int argc, const char **argv) {
  int lmax = 0;
  int err;
  uv_loop_t loop;
  int listen_backlog = 10;
  struct demuxer *demuxer = NULL;

  if ((err = uv_loop_init(&loop)) < 0) {
    fprintf(stderr, "Failed to initialize uv_loop: %s\n", uv_strerror(err));
    return 1;
  }
  lmax = sd_listen_fds(0);
  demuxer = new_demuxer(&loop, lmax);
  if ((err = setvbuf(stderr, NULL, _IONBF, 0))) {
    fprintf(stderr, "Failed to unbuffer stderr: %s\n", strerror(errno));
    return 1;
  }

  for (int i = 0; i < lmax; i++) {
    int fd = SD_LISTEN_FDS_START + i;
    if ((err = demuxer_add_listener_fd(demuxer, fd, listen_backlog))) {
      fprintf(stderr, "Failed to listen on fd %d: (%d) %s\n", fd, err, uv_strerror(err));
      return 1;
    }
  }

  if (demuxer->listener_count < 1) {
    fprintf(stderr, "No listening sockets available!  nothing to do.\n");
    return 1;
  }

  char *debug = getenv("HDDEMUX_DEBUG");
  demuxer->debug = (debug != NULL && debug[0] != '\0');
  
  char *timeout = getenv("IDLE_TIMEOUT");
  if (timeout)
    demuxer->stream_timeout = strtoull(timeout, NULL, 0);
  else
    demuxer->stream_timeout = DEFAULT_IDLE_TIMEOUT;
  
  if ((err = get_target_addrinfo("HTTP_TARGET", "localhost", "80", &demuxer->http_targets, demuxer->debug))) {
    fprintf(stderr, "Failed looking up HTTP_TARGET: %s\n", gai_strerror(err));
    return 1;
  }
  if ((err = get_target_addrinfo("DNS_TARGET", "localhost", "53", &demuxer->dns_targets, demuxer->debug))) {
    fprintf(stderr, "Failed looking up DNS_TARGET: %s\n", gai_strerror(err));
    return 1;
  }
  demuxer->next_http = demuxer->http_targets;
  demuxer->next_dns = demuxer->dns_targets;

  if (demuxer->debug)
    demuxer_dump(demuxer, stderr);
  
  sd_notify(0, "READY=1");
  
  err = uv_run(&loop, UV_RUN_DEFAULT);
  if (demuxer->debug)
    fprintf(stderr, "loop completed: %d (%s)\n", err, err ? (err < 0 ? uv_strerror(err) : "unknown") : "none");
         
  freeaddrinfo(demuxer->http_targets);
  freeaddrinfo(demuxer->dns_targets);
  free_demuxer(demuxer);
  if ((err = uv_loop_close(&loop)))
    fprintf(stderr, "uv_loop_close() failed: (%d) %s\n", err, uv_strerror(err));
  
  return 0;
}
