// nanonext - C level - Async Functions ----------------------------------------

#define NANONEXT_SIGNALS
#include "nanonext.h"

// internals -------------------------------------------------------------------

static SEXP mk_error_aio(const int xc, SEXP env) {

  SEXP err;
  PROTECT(err = Rf_ScalarInteger(xc));
  Rf_classgets(err, nano_error);
  Rf_defineVar(nano_ValueSymbol, err, env);
  Rf_defineVar(nano_AioSymbol, nano_success, env);
  UNPROTECT(1);
  return err;

}

// aio completion callbacks ----------------------------------------------------

void nano_list_do(nano_list_op listop, nano_aio *saio) {

  static nano_aio *free_list = NULL;
  static nng_mtx *free_mtx = NULL;

  switch (listop) {
  case INIT:
    if (free_mtx == NULL && nng_mtx_alloc(&free_mtx))
      Rf_error("NNG library init failure");
    break;
  case FINALIZE:
    nng_mtx_lock(free_mtx);
    nano_list_do(FREE, NULL);
    if (saio->mode == 0x1) {
      nng_mtx_unlock(free_mtx);
      nng_aio_free(saio->aio);
      if (saio->data != NULL)
        free(saio->data);
      free(saio);
    } else {
      saio->mode = 0x1;
      nng_mtx_unlock(free_mtx);
    }
    break;
  case COMPLETE:
    nng_mtx_lock(free_mtx);
    if (saio->mode == 0x1) {
      saio->next = free_list;
      free_list = saio;
    } else {
      saio->mode = 0x1;
    }
    nng_mtx_unlock(free_mtx);
    break;
  case SHUTDOWN:
    if (free_mtx == NULL) break;
    nng_mtx_lock(free_mtx);
    nano_list_do(FREE, NULL);
    nng_mtx_unlock(free_mtx);
    nng_mtx_free(free_mtx);
    free_mtx = NULL;
    break;
  case FREE: // must be entered under lock
    while (free_list != NULL) {
      nano_aio *current = free_list;
      free_list = (nano_aio *) current->next;
      nng_aio_free(current->aio);
      if (current->data != NULL)
        free(current->data);
      free(current);
    }
    break;
  }

}

static void saio_complete(void *arg) {

  nano_aio *saio = (nano_aio *) arg;
  const int res = nng_aio_result(saio->aio);
  if (res)
    nng_msg_free(nng_aio_get_msg(saio->aio));
  saio->result = res - !res;

  nano_list_do(COMPLETE, saio);

}

static void isaio_complete(void *arg) {

  nano_aio *iaio = (nano_aio *) arg;
  const int res = nng_aio_result(iaio->aio);
  iaio->result = res - !res;

  nano_list_do(COMPLETE, iaio);

}

static void raio_complete(void *arg) {

  nano_aio *raio = (nano_aio *) arg;
  int res = nng_aio_result(raio->aio);
  if (res == 0) {
    nng_msg *msg = nng_aio_get_msg(raio->aio);
    raio->data = msg;
    nng_pipe p = nng_msg_get_pipe(msg);
    res = - (int) p.id;
  }

  if (raio->next != NULL) {
    nano_cv *ncv = (nano_cv *) raio->next;
    nng_cv *cv = ncv->cv;
    nng_mtx *mtx = ncv->mtx;

    nng_mtx_lock(mtx);
    raio->result = res;
    ncv->condition++;
    nng_cv_wake(cv);
    nng_mtx_unlock(mtx);
  } else {
    raio->result = res;
  }

  if (raio->cb != NULL)
    later2(raio_invoke_cb, raio->cb);

}

static void raio_complete_interrupt(void *arg) {

  nano_aio *raio = (nano_aio *) arg;
  int res = nng_aio_result(raio->aio);
  if (res == 0) {
    nng_msg *msg = nng_aio_get_msg(raio->aio);
    raio->data = msg;
    nng_pipe p = nng_msg_get_pipe(msg);
    res = - (int) p.id;
  }

  raio->result = res;

  if (raio->cb != NULL)
    later2(raio_invoke_cb, raio->cb);

  if (res <= 0) {
#ifdef _WIN32
    UserBreak = 1;
#else
    R_interrupts_pending = 1;
    kill(getpid(), SIGINT);
#endif
  }

}

static void iraio_complete(void *arg) {

  nano_aio *iaio = (nano_aio *) arg;
  const int res = nng_aio_result(iaio->aio);

  if (iaio->next != NULL) {
    nano_cv *ncv = (nano_cv *) iaio->next;
    nng_cv *cv = ncv->cv;
    nng_mtx *mtx = ncv->mtx;

    nng_mtx_lock(mtx);
    iaio->result = res - !res;
    ncv->condition++;
    nng_cv_wake(cv);
    nng_mtx_unlock(mtx);
  } else {
    iaio->result = res - !res;
  }

  if (iaio->cb != NULL)
    later2(raio_invoke_cb, iaio->cb);

}

// finalisers ------------------------------------------------------------------

static void saio_finalizer(SEXP xptr) {

  if (NANO_PTR(xptr) == NULL) return;
  nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
  nano_list_do(FINALIZE, xp);

}

static void iaio_finalizer(SEXP xptr) {

  if (NANO_PTR(xptr) == NULL) return;
  nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
  nng_aio_free(xp->aio);
  if (xp->data != NULL)
    free(xp->data);
  free(xp);

}

static void raio_finalizer(SEXP xptr) {

  if (NANO_PTR(xptr) == NULL) return;
  nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
  nng_aio_free(xp->aio);
  if (xp->data != NULL)
    nng_msg_free((nng_msg *) xp->data);
  free(xp);

}

// core aio - internal ---------------------------------------------------------

static inline SEXP create_aio_result(SEXP env, nano_aio *saio) {

  if (saio->result > 0)
    return mk_error_aio(saio->result, env);

  Rf_defineVar(nano_ValueSymbol, nano_success, env);
  Rf_defineVar(nano_AioSymbol, R_NilValue, env);
  return nano_success;

}

static inline SEXP create_aio_msg(SEXP env, SEXP aio, nano_aio *raio, int res) {

  SEXP out, pipe;
  unsigned char *buf;
  size_t sz;

  if (raio->type == IOV_RECVAIO || raio->type == IOV_RECVAIOS) {
    buf = raio->data;
    sz = nng_aio_count(raio->aio);
    PROTECT(out = nano_decode(buf, sz, raio->mode, NANO_PROT(aio)));
    free(raio->data);
  } else {
    nng_msg *msg = (nng_msg *) raio->data;
    buf = nng_msg_body(msg);
    sz = nng_msg_len(msg);
    PROTECT(out = nano_decode(buf, sz, raio->mode, NANO_PROT(aio)));
    nng_msg_free(msg);
  }
  raio->data = NULL;

  PROTECT(pipe = Rf_ScalarInteger(-res));
  Rf_defineVar(nano_ValueSymbol, out, env);
  Rf_defineVar(nano_AioSymbol, pipe, env);

  UNPROTECT(2);
  return out;

}


SEXP nano_aio_result(SEXP env) {

  int found;
  SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
  if (found)
    return exist;

  const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
  nano_aio *saio = (nano_aio *) NANO_PTR(aio);

  return create_aio_result(env, saio);

}

SEXP nano_aio_get_msg(SEXP env) {

  int found;
  SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
  if (found)
    return exist;

  const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
  nano_aio *raio = (nano_aio *) NANO_PTR(aio);

  int res;
  switch (raio->type) {
  case RECVAIO:
  case REQAIO:
  case IOV_RECVAIO:
  case RECVAIOS:
  case REQAIOS:
  case IOV_RECVAIOS:
    res = raio->result;
    if (res > 0)
      return mk_error_aio(res, env);
    break;
  default:
    /* not reached */
    res = 0;
  }

  return create_aio_msg(env, aio, raio, res);

}

// core aio --------------------------------------------------------------------

SEXP rnng_aio_result(SEXP env) {

  int found;
  SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
  if (found)
    return exist;

  const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
  nano_aio *saio = (nano_aio *) NANO_PTR(aio);

  if (nng_aio_busy(saio->aio))
    return nano_unresolved;

  return create_aio_result(env, saio);

}

SEXP rnng_aio_get_msg(SEXP env) {

  int found;
  SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
  if (found)
    return exist;

  const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
  nano_aio *raio = (nano_aio *) NANO_PTR(aio);

  int res;
  switch (raio->type) {
  case RECVAIO:
  case REQAIO:
  case IOV_RECVAIO:
    if (nng_aio_busy(raio->aio))
      return nano_unresolved;

    res = raio->result;
    if (res > 0)
      return mk_error_aio(res, env);

    break;
  case RECVAIOS:
  case REQAIOS:
  case IOV_RECVAIOS: {
    nng_mtx *mtx = ((nano_cv *) raio->next)->mtx;
    nng_mtx_lock(mtx);
    res = raio->result;
    nng_mtx_unlock(mtx);

    if (res == 0)
      return nano_unresolved;

    if (res > 0)
      return mk_error_aio(res, env);

    break;
  }
  default:
    /* not reached */
    res = 0;
  }

  return create_aio_msg(env, aio, raio, res);

}

SEXP rnng_aio_call(SEXP x) {

  switch (TYPEOF(x)) {
  case ENVSXP: {
    const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
    if (NANO_PTR_CHECK(coreaio, nano_AioSymbol))
      return x;

    nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
    nng_aio_wait(aiop->aio);
    switch (aiop->type) {
    case RECVAIO:
    case REQAIO:
    case IOV_RECVAIO:
    case RECVAIOS:
    case REQAIOS:
    case IOV_RECVAIOS:
      nano_aio_get_msg(x);
      break;
    case SENDAIO:
    case IOV_SENDAIO:
      nano_aio_result(x);
      break;
    case HTTP_AIO:
      nano_aio_http_status(x);
      break;
    }
    break;
  }
  case VECSXP: {
    const R_xlen_t xlen = Rf_xlength(x);
    for (R_xlen_t i = 0; i < xlen; i++) {
      rnng_aio_call(VECTOR_PTR_RO(x)[i]);
    }
    break;
  }
  }

  return x;

}

static SEXP rnng_aio_collect_impl(SEXP x, SEXP (*const func)(SEXP)) {

  SEXP out;
  int found;

  switch (TYPEOF(x)) {
  case ENVSXP: {
    out = nano_findVarInFrame(func(x), nano_ValueSymbol, &found);
    if (!found) goto fail;
    break;
  }
  case VECSXP: {
    SEXP env, val, names;
    const R_xlen_t xlen = Rf_xlength(x);
    PROTECT(out = Rf_allocVector(VECSXP, xlen));
    for (R_xlen_t i = 0; i < xlen; i++) {
      env = func(VECTOR_PTR_RO(x)[i]);
      if (TYPEOF(env) != ENVSXP) goto fail;
      val = nano_findVarInFrame(env, nano_ValueSymbol, &found);
      if (!found) goto fail;
      SET_VECTOR_ELT(out, i, val);
    }
    names = Rf_getAttrib(x, R_NamesSymbol);
    if (names != R_NilValue)
      out = Rf_namesgets(out, names);
    UNPROTECT(1);
    break;
  }
  default:
    goto fail;
  }

  return out;

  fail:
  Rf_error("object is not an Aio or list of Aios");

}

SEXP rnng_aio_collect(SEXP x) {

  return rnng_aio_collect_impl(x, rnng_aio_call);

}

SEXP rnng_aio_collect_safe(SEXP x) {

  return rnng_aio_collect_impl(x, rnng_wait_thread_create);

}

SEXP rnng_aio_stop(SEXP x) {

  switch (TYPEOF(x)) {
  case ENVSXP: {
    const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
    if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) break;
    nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
    nng_aio_stop(aiop->aio);
    // See #194, this is to reset the R interrupts state after an interrupt
    // requested by `stop_request()` has already triggered
#ifdef _WIN32
    UserBreak = 0;
#else
    R_interrupts_pending = 0;
#endif
    break;
  }
  case VECSXP: {
    const R_xlen_t xlen = Rf_xlength(x);
    for (R_xlen_t i = 0; i < xlen; i++) {
      rnng_aio_stop(VECTOR_PTR_RO(x)[i]);
    }
    break;
  }
  }

  return R_NilValue;

}

SEXP rnng_request_stop(SEXP x) {

  SEXP out;
  switch (TYPEOF(x)) {
  case ENVSXP: {
    int res = 0;
    SEXP coreaio;
    PROTECT(coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL));
    if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) goto fail;
    nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
    if (aiop->type != REQAIOS && aiop->type != REQAIO) goto fail;
    nng_aio_stop(aiop->aio);
    nano_saio *saio = (nano_saio *) aiop->cb;
    if (saio->disp == NULL) goto fail;
    if (saio->type) {
      nng_msg *msgp = NULL;
      nng_ctx *ctx = (nng_ctx *) saio->disp;
      const nng_duration dur = NANONEXT_WAIT_DUR;
      if (nng_ctx_set_ms(*ctx, "send-timeout", dur) ||
          nng_ctx_set_ms(*ctx, "recv-timeout", dur) ||
          nng_msg_alloc(&msgp, 0) ||
          nng_msg_append_u32(msgp, 0) ||
          nng_msg_append(msgp, &saio->id, sizeof(int)) ||
          nng_ctx_sendmsg(*ctx, msgp, 0)) {
        nng_msg_free(msgp);
        goto fail;
      }
      msgp = NULL;
      if (nng_ctx_recvmsg(*ctx, &msgp, 0)) {
        nng_msg_free(msgp);
        goto fail;
      }
      memcpy(&res, nng_msg_body(msgp), sizeof(int));
      nng_msg_free(msgp);
    } else {
      res = dispatch_cancel_direct(saio->disp, saio->id);
    }

    fail:
    UNPROTECT(1);
    out = Rf_ScalarLogical(res != 0);
    break;
  }
  case VECSXP: {
    const R_xlen_t xlen = Rf_xlength(x);
    PROTECT(out = Rf_allocVector(LGLSXP, xlen));
    for (R_xlen_t i = xlen - 1; i >= 0; i--) {
      SEXP item = rnng_request_stop(VECTOR_PTR_RO(x)[i]);
      SET_LOGICAL_ELT(out, i, NANO_INTEGER(item));
    }
    UNPROTECT(1);
    break;
  }
  default:
    out = Rf_ScalarLogical(0);
  }

  return out;

}

static int rnng_unresolved_impl(SEXP x) {

  int xc;
  switch (TYPEOF(x)) {
  case ENVSXP: {
    const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
    if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) {
      xc = 0; break;
    }
    SEXP value;
    nano_aio *aio = (nano_aio *) NANO_PTR(coreaio);
    switch (aio->type) {
    case SENDAIO:
    case IOV_SENDAIO:
      value = rnng_aio_result(x);
      break;
    case HTTP_AIO:
      value = rnng_aio_http_status(x);
      break;
    default:
      value = rnng_aio_get_msg(x);
      break;
    }
    xc = value == nano_unresolved;
    break;
  }
  case LGLSXP:
    xc = x == nano_unresolved;
    break;
  default:
    xc = 0;
  }

  return xc;

}

SEXP rnng_unresolved(SEXP x) {

  switch (TYPEOF(x)) {
  case ENVSXP:
  case LGLSXP:
    return Rf_ScalarLogical(rnng_unresolved_impl(x));
  case VECSXP: {
    const R_xlen_t xlen = Rf_xlength(x);
    for (R_xlen_t i = 0; i < xlen; i++) {
      if (rnng_unresolved_impl(VECTOR_PTR_RO(x)[i]))
        return Rf_ScalarLogical(1);
    }
  }
  }

  return Rf_ScalarLogical(0);

}

static int rnng_unresolved2_impl(SEXP x) {

  if (TYPEOF(x) == ENVSXP) {
    const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
    if (NANO_PTR_CHECK(coreaio, nano_AioSymbol))
      return 0;
    nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
    return nng_aio_busy(aiop->aio);
  }

  return 0;

}

SEXP rnng_unresolved2(SEXP x) {

  switch (TYPEOF(x)) {
  case ENVSXP:
    return Rf_ScalarLogical(rnng_unresolved2_impl(x));
  case VECSXP: {
    int xc = 0;
    const R_xlen_t xlen = Rf_xlength(x);
    for (R_xlen_t i = 0; i < xlen; i++) {
      xc += rnng_unresolved2_impl(VECTOR_PTR_RO(x)[i]);
    }
    return Rf_ScalarInteger(xc);
  }
  }

  return Rf_ScalarLogical(0);

}

static inline R_xlen_t race_find_resolved(SEXP x, R_xlen_t xlen) {
  for (R_xlen_t i = 0; i < xlen; i++) {
    SEXP elem = VECTOR_PTR_RO(x)[i];
    if (TYPEOF(elem) == ENVSXP && !rnng_unresolved2_impl(elem))
      return i + 1;
  }
  return 0;
}

static inline int race_count_unresolved(SEXP x, R_xlen_t xlen) {
  int n = 0;
  for (R_xlen_t i = 0; i < xlen; i++)
    n += rnng_unresolved2_impl(VECTOR_PTR_RO(x)[i]);
  return n;
}

SEXP rnng_race_aio(SEXP x, SEXP cvar) {

  if (TYPEOF(x) != VECSXP)
    return Rf_ScalarInteger(0);

  const R_xlen_t xlen = Rf_xlength(x);
  if (xlen == 0)
    return Rf_ScalarInteger(0);

  R_xlen_t idx = race_find_resolved(x, xlen);
  if (idx)
    return Rf_ScalarInteger((int) idx);

  if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
    return Rf_ScalarInteger(0);

  nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
  nng_cv *cv = ncv->cv;
  nng_mtx *mtx = ncv->mtx;

  nng_mtx_lock(mtx);
  ncv->flag = ncv->flag < 0;
  ncv->condition = 0;
  int n = race_count_unresolved(x, xlen);
  nng_mtx_unlock(mtx);
  if (n == 0)
    return Rf_ScalarInteger((int) race_find_resolved(x, xlen));

  nng_time time = nng_clock();
  int current_n, flag;

  do {
    time = time + 400;
    int signalled = 1;
    nng_mtx_lock(mtx);
    while (ncv->condition == 0) {
      if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
        signalled = 0;
        break;
      }
    }
    if (signalled)
      ncv->condition--;
    flag = ncv->flag;
    nng_mtx_unlock(mtx);

    if (flag < 0)
      return Rf_ScalarInteger(0);

    if (!signalled)
      R_CheckUserInterrupt();

    current_n = race_count_unresolved(x, xlen);

  } while (current_n == n);

  return Rf_ScalarInteger((int) race_find_resolved(x, xlen));

}

// send recv aio functions -----------------------------------------------------

SEXP rnng_send_aio(SEXP con, SEXP data, SEXP mode, SEXP timeout, SEXP pipe, SEXP clo) {

  const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
  const int raw = nano_encode_mode(mode);
  SEXP aio, env, fun;
  nano_aio *saio = NULL;
  nano_buf buf;
  int sock, xc;

  if ((sock = !NANO_PTR_CHECK(con, nano_SocketSymbol)) || !NANO_PTR_CHECK(con, nano_ContextSymbol)) {

    const int pipeid = sock ? nano_integer(pipe) : 0;
    if (raw) {
      nano_encode(&buf, data);
    } else {
      nano_serialize(&buf, data, NANO_PROT(con), 0);
    }
    nng_msg *msg = NULL;

    saio = calloc(1, sizeof(nano_aio));
    NANO_ENSURE_ALLOC(saio);
    saio->type = SENDAIO;

    if ((xc = nng_msg_alloc(&msg, 0)) ||
        (xc = nng_aio_alloc(&saio->aio, saio_complete, saio))) {
      nng_msg_free(msg);
      goto fail;
    }

    nano_msg_set_body(msg, &buf);

    if (pipeid) {
      nng_pipe p;
      p.id = (uint32_t) pipeid;
      nng_msg_set_pipe(msg, p);
    }

    nng_aio_set_msg(saio->aio, msg);
    nng_aio_set_timeout(saio->aio, dur);
    sock ? nng_send_aio(*(nng_socket *) NANO_PTR(con), saio->aio) :
           nng_ctx_send(*(nng_ctx *) NANO_PTR(con), saio->aio);
    NANO_FREE(buf);

    PROTECT(aio = R_MakeExternalPtr(saio, nano_AioSymbol, R_NilValue));
    R_RegisterCFinalizerEx(aio, saio_finalizer, TRUE);

  } else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {

    nano_encode(&buf, data);

    nano_stream *nst = (nano_stream *) NANO_PTR(con);
    nng_stream *sp = nst->stream;

    saio = calloc(1, sizeof(nano_aio));
    NANO_ENSURE_ALLOC(saio);

    if (nst->msgmode) {
      nng_msg *msg;
      const size_t xlen = buf.cur - nst->textframes;
      saio->type = SENDAIO;
      if ((xc = nng_msg_alloc(&msg, xlen)))
        goto fail;
      memcpy(nng_msg_body(msg), buf.buf, xlen);

      if ((xc = nng_aio_alloc(&saio->aio, saio_complete, saio))) {
        nng_msg_free(msg);
        goto fail;
      }
      nng_aio_set_msg(saio->aio, msg);
    } else {
      saio->type = IOV_SENDAIO;
      saio->data = malloc(buf.cur);
      NANO_ENSURE_ALLOC(saio->data);
      memcpy(saio->data, buf.buf, buf.cur);
      nng_iov iov = {
        .iov_buf = saio->data,
        .iov_len = buf.cur - nst->textframes
      };

      if ((xc = nng_aio_alloc(&saio->aio, isaio_complete, saio)) ||
          (xc = nng_aio_set_iov(saio->aio, 1u, &iov)))
        goto fail;
    }

    nng_aio_set_timeout(saio->aio, dur);
    nng_stream_send(sp, saio->aio);
    NANO_FREE(buf);

    PROTECT(aio = R_MakeExternalPtr(saio, nano_AioSymbol, R_NilValue));
    R_RegisterCFinalizerEx(aio, saio_finalizer, TRUE);

  } else {
    Rf_error("`con` is not a valid Socket, Context, or Stream");
  }

  PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
  Rf_classgets(env, nano_sendAio);
  Rf_defineVar(nano_AioSymbol, aio, env);

  PROTECT(fun = R_mkClosure(R_NilValue, nano_aioFuncRes, clo));
  R_MakeActiveBinding(nano_ResultSymbol, fun, env);

  UNPROTECT(3);
  return env;

  fail:
  nng_aio_free(saio->aio);
  free(saio->data);
  failmem:
  NANO_FREE(buf);
  free(saio);
  return mk_error_data(-xc);

}

SEXP rnng_recv_aio(SEXP con, SEXP mode, SEXP timeout, SEXP cvar, SEXP clo) {

  const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
  const int signal = cvar != R_NilValue && !NANO_PTR_CHECK(cvar, nano_CvSymbol);
  const int interrupt = TYPEOF(cvar) == SYMSXP;
  nano_cv *ncv = signal ? (nano_cv *) NANO_PTR(cvar) : NULL;
  nano_aio *raio = NULL;
  SEXP aio, env, fun;
  int sock, xc;

  if ((sock = !NANO_PTR_CHECK(con, nano_SocketSymbol)) || !NANO_PTR_CHECK(con, nano_ContextSymbol)) {

    const uint8_t mod = (uint8_t) nano_matcharg(mode);
    raio = calloc(1, sizeof(nano_aio));
    NANO_ENSURE_ALLOC(raio);
    raio->next = ncv;
    raio->type = signal ? RECVAIOS : RECVAIO;
    raio->mode = mod;

    if ((xc = nng_aio_alloc(&raio->aio, interrupt ? raio_complete_interrupt : raio_complete, raio)))
      goto fail;

    nng_aio_set_timeout(raio->aio, dur);
    sock ? nng_recv_aio(*(nng_socket *) NANO_PTR(con), raio->aio) :
      nng_ctx_recv(*(nng_ctx *) NANO_PTR(con), raio->aio);

    PROTECT(aio = R_MakeExternalPtr(raio, nano_AioSymbol, NANO_PROT(con)));
    R_RegisterCFinalizerEx(aio, raio_finalizer, TRUE);

  } else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {

    const uint8_t mod = (uint8_t) (nano_matcharg(mode) == 1 ? 2 : nano_matcharg(mode));
    nano_stream *nst = (nano_stream *) NANO_PTR(con);
    nng_stream *sp = nst->stream;

    raio = calloc(1, sizeof(nano_aio));
    NANO_ENSURE_ALLOC(raio);
    raio->next = ncv;
    raio->mode = mod;

    if (nst->msgmode) {
      raio->type = signal ? RECVAIOS : RECVAIO;
      if ((xc = nng_aio_alloc(&raio->aio, raio_complete, raio)))
        goto fail;
    } else {
      size_t xlen = nst->bufsize;
      raio->type = signal ? IOV_RECVAIOS : IOV_RECVAIO;
      raio->data = malloc(xlen);
      NANO_ENSURE_ALLOC(raio->data);
      nng_iov iov = {
        .iov_buf = raio->data,
        .iov_len = xlen
      };
      if ((xc = nng_aio_alloc(&raio->aio, iraio_complete, raio)) ||
          (xc = nng_aio_set_iov(raio->aio, 1u, &iov)))
        goto fail;
    }

    nng_aio_set_timeout(raio->aio, dur);
    nng_stream_recv(sp, raio->aio);

    PROTECT(aio = R_MakeExternalPtr(raio, nano_AioSymbol, R_NilValue));
    R_RegisterCFinalizerEx(aio, nst->msgmode ? raio_finalizer : iaio_finalizer, TRUE);

  } else {
    Rf_error("`con` is not a valid Socket, Context or Stream");
  }

  PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
  Rf_classgets(env, nano_recvAio);
  Rf_defineVar(nano_AioSymbol, aio, env);

  PROTECT(fun = R_mkClosure(R_NilValue, nano_aioFuncMsg, clo));
  R_MakeActiveBinding(nano_DataSymbol, fun, env);

  UNPROTECT(3);
  return env;

  fail:
  nng_aio_free(raio->aio);
  free(raio->data);
  failmem:
  free(raio);
  return mk_error_data(xc);

}
