keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / jni.c
diff --git a/jni.c b/jni.c
index 9f9e44d..3841f35 100644 (file)
--- a/jni.c
+++ b/jni.c
+/* -*-c-*-
+ *
+ * Native-code portions of the project
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#define _FILE_OFFSET_BITS 64
+
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <inttypes.h>
-#include <stdint.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include <jni.h>
-
-#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
 #include <sys/un.h>
-#include <unistd.h>
+
+#include <jni.h>
+
+//#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#include <mLib/align.h>
+#include <mLib/bits.h>
+#include <mLib/dstr.h>
+#include <mLib/macros.h>
+
+#include <catacomb/ghash.h>
+
+#define TUN_INTERNALS
+#include <tripe.h>
 
 #undef sun
 
-union align {
-  int i;
-  long l;
-  double d;
-  void *p;
-  void (*f)(void *);
-  struct notexist *s;
-};
+/*----- Magic class names and similar -------------------------------------*/
+
+/* The name decoration is horrific.  Hide it. */
+#define JNIFUNC(f) Java_uk_org_distorted_tripe_sys_package_00024_##f
+
+/* The little class for bundling up error codes. */
+#define ERRENTCLS "uk/org/distorted/tripe/sys/package$ErrorEntry"
+
+/* The `sys' package class. */
+#define SYSCLS "uk/org/distorted/tripe/sys/package"
+
+/* The server lock class. */
+#define LOCKCLS "uk/org/distorted/tripe/sys/package$ServerLock"
+
+/* The `stat' class. */
+#define STATCLS "uk/org/distorted/tripe/sys/package$FileInfo"
+
+/* Standard Java classes. */
+#define FDCLS "java/io/FileDescriptor"
+#define STRCLS "java/lang/String"
+#define RANDCLS "java/security/SecureRandom"
+
+/* Exception class names. */
+#define NULLERR "java/lang/NullPointerException"
+#define TYPEERR "uk/org/distorted/tripe/sys/package$NativeObjectTypeException"
+#define SYSERR "uk/org/distorted/tripe/sys/package$SystemError"
+#define NAMEERR "uk/org/distorted/tripe/sys/package$NameResolutionException"
+#define INITERR "uk/org/distorted/tripe/sys/package$InitializationException"
+#define ARGERR "java/lang/IllegalArgumentException"
+#define STERR "java/lang/IllegalStateException"
+#define BOUNDSERR "java/lang/IndexOutOfBoundsException"
+
+/*----- Essential state ---------------------------------------------------*/
+
+static JNIEnv *jni_tripe = 0;
+
+/*----- Miscellaneous utilities -------------------------------------------*/
+
+static void vexcept(JNIEnv *jni, const char *clsname,
+                   const char *msg, va_list *ap)
+{
+  jclass cls;
+  int rc;
+  dstr d = DSTR_INIT;
+
+  cls = (*jni)->FindClass(jni, clsname); assert(cls);
+  if (!msg)
+    rc = (*jni)->ThrowNew(jni, cls, 0);
+  else {
+    dstr_vputf(&d, msg, ap);
+    rc = (*jni)->ThrowNew(jni, cls, d.buf);
+    assert(!rc);
+    dstr_destroy(&d);
+  }
+  assert(!rc);
+}
+
+static void except(JNIEnv *jni, const char *clsname, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vexcept(jni, clsname, msg, &ap);
+  va_end(ap);
+}
+
+#ifdef DEBUG
+static void dump_bytes(const void *p, size_t n, size_t o)
+{
+  const unsigned char *q = p;
+  size_t i;
+
+  if (!n) return;
+  for (;;) {
+    fprintf(stderr, ";;   %08zx\n", o);
+    for (i = 0; i < 8; i++)
+      if (i < n) fprintf(stderr, "%02x ", q[i]);
+      else fprintf(stderr, "** ");
+    fprintf(stderr, ": ");
+    for (i = 0; i < 8; i++)
+      fputc(i >= n ? '*' : isprint(q[i]) ? q[i] : '.', stderr);
+    fputc('\n', stderr);
+    if (n <= 8) break;
+    q += 8; n -= 8;
+  }
+}
+
+static void dump_byte_array(JNIEnv *jni, const char *what, jbyteArray v)
+{
+  jsize n;
+  jbyte *p;
+
+  fprintf(stderr, ";; %s\n", what);
+  if (!v) { fprintf(stderr, ";;   <null>\n"); return; }
+  n = (*jni)->GetArrayLength(jni, v);
+  p = (*jni)->GetByteArrayElements(jni, v, 0);
+  dump_bytes(p, n, 0);
+  (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT);
+}
+#endif
+
+static jbyteArray wrap_cstring(JNIEnv *jni, const char *p)
+{
+  size_t n;
+  jbyteArray v;
+  jbyte *q;
+
+  if (!p) return (0);
+  n = strlen(p) + 1;
+  v = (*jni)->NewByteArray(jni, n); if (!v) return (0);
+  q = (*jni)->GetByteArrayElements(jni, v, 0); if (!q) return (0);
+  memcpy(q, p, n);
+  (*jni)->ReleaseByteArrayElements(jni, v, q, 0);
+  return (v);
+}
+
+static const char *get_cstring(JNIEnv *jni, jbyteArray v)
+{
+  if (!v) { except(jni, NULLERR, 0); return (0); }
+  return ((const char *)(*jni)->GetByteArrayElements(jni, v, 0));
+}
+
+static void put_cstring(JNIEnv *jni, jbyteArray v, const char *p)
+  { if (p) (*jni)->ReleaseByteArrayElements(jni, v, (jbyte *)p, JNI_ABORT); }
+
+static void vexcept_syserror(JNIEnv *jni, const char *clsname,
+                            int err, const char *msg, va_list *ap)
+{
+  jclass cls;
+  int rc;
+  dstr d = DSTR_INIT;
+  jbyteArray msgstr;
+  jthrowable e;
+  jmethodID init;
+
+  cls = (*jni)->FindClass(jni, clsname); assert(cls);
+  init = (*jni)->GetMethodID(jni, cls, "<init>", "(I[B)V"); assert(init);
+  dstr_vputf(&d, msg, ap);
+  msgstr = wrap_cstring(jni, d.buf); assert(msgstr);
+  dstr_destroy(&d);
+  e = (*jni)->NewObject(jni, cls, init, err, msgstr); assert(e);
+  rc = (*jni)->Throw(jni, e); assert(!rc);
+}
+
+static void except_syserror(JNIEnv *jni, const char *clsname,
+                           int err, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vexcept_syserror(jni, clsname, err, msg, &ap);
+  va_end(ap);
+}
+
+static int set_nonblocking(JNIEnv *jni, int fd, int nb)
+{
+  int f0 = fcntl(fd, F_GETFL), f1;
+  if (f0 < 0) goto err;
+  if (nb) f1 = f0 | O_NONBLOCK;
+  else f1 = f0&~O_NONBLOCK;
+  if (fcntl(fd, F_SETFL, f1)) goto err;
+  return (f0 & O_NONBLOCK);
+err:
+    except_syserror(jni, SYSERR, errno,
+                   "failed to set descriptor nonblocking");
+    return (-1);
+}
+
+static int set_closeonexec(JNIEnv *jni, int fd)
+{
+  int f = fcntl(fd, F_GETFD);
+  if (f < 0 || fcntl(fd, F_SETFD, f | FD_CLOEXEC)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to set descriptor close-on-exec");
+    return (-1);
+  }
+  return (0);
+}
+
+/*----- Wrapping native types ---------------------------------------------*/
+
+/* There's no way defined in the JNI to stash a C pointer in a Java object.
+ * It seems that the usual approach is to cast to `jlong', but this is
+ * clearly unsatisfactory.  Instead, we store structures as Java byte arrays,
+ * with a 32-bit tag on the front.
+ */
 
 struct native_type {
   const char *name;
   size_t sz;
-  uint32_t tag;
+  uint32 tag;
 };
 
-typedef jbyteArray wrapped;
+typedef jbyteArray wrapper;
 
-struct open {
-  wrapped obj;
-  jbyte *arr;
+struct native_base {
+  uint32 tag;
 };
 
-struct base {
-  uint32_t tag;
+static int unwrap(JNIEnv *jni, void *p,
+                 const struct native_type *ty, wrapper w)
+{
+  jbyte *q;
+  jclass cls;
+  struct native_base *b = p;
+  jsize n;
+
+  if (!w) { except(jni, NULLERR, 0); return (-1); }
+  cls = (*jni)->FindClass(jni, "[B"); assert(cls);
+  if (!(*jni)->IsInstanceOf(jni, w, cls)) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: expected a byte array");
+    return (-1);
+  }
+  n = (*jni)->GetArrayLength(jni, w);
+  if (n != ty->sz) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: wrong size for `%s'",
+          ty->name);
+    return (-1);
+  }
+  q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
+  memcpy(b, q, ty->sz);
+  (*jni)->ReleaseByteArrayElements(jni, w, q, JNI_ABORT);
+  if (b->tag != ty->tag) {
+    except(jni, TYPEERR,
+          "corrupted native object wrapper: expected tag for `%s'",
+          ty->name);
+    return (-1);
+  }
+  return (0);
+}
+
+static int update_wrapper(JNIEnv *jni, const struct native_type *ty,
+                         wrapper w, const void *p)
+{
+  jbyte *q;
+
+  q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1);
+  memcpy(q, p, ty->sz);
+  (*jni)->ReleaseByteArrayElements(jni, w, q, 0);
+  return (0);
+}
+
+static wrapper wrap(JNIEnv *jni, const struct native_type *ty, const void *p)
+{
+  wrapper w;
+
+  w = (*jni)->NewByteArray(jni, ty->sz); if (!w) return (0);
+  if (update_wrapper(jni, ty, w, p)) return (0);
+  return (w);
+}
+
+#define INIT_NATIVE(type, p) do (p)->_base.tag = type##_type.tag; while (0)
+
+/*----- Crypto information ------------------------------------------------*/
+
+JNIEXPORT jint JNICALL JNIFUNC(hashsz)(JNIEnv *jni, jobject cls,
+                                      jstring hnamestr)
+{
+  jint rc = -1;
+  const char *hname;
+  const gchash *hc;
+
+  hname = (*jni)->GetStringUTFChars(jni, hnamestr, 0);
+  if (!hname) goto end;
+  hc = ghash_byname(hname); if (!hc) goto end;
+  rc = hc->hashsz;
+
+end:
+  if (hname) (*jni)->ReleaseStringUTFChars(jni, hnamestr, hname);
+  return (rc);
+}
+
+/*----- System errors -----------------------------------------------------*/
+
+static const struct errtab { const char *tag; int err; } errtab[] = {
+  /*
+     ;;; The errno name table is very boring to type.  To make life less
+     ;;; awful, put the errno names in this list and evaluate the code to
+     ;;; get Emacs to regenerate it.
+
+     (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
+                    ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
+                    EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
+                    ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
+                    ERANGE
+
+                    EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
+                    EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
+                    ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
+                    EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
+                    ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
+                    EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
+                    EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
+                    EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
+                    EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
+                    ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
+                    EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
+                    ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
+                    ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
+                    EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
+                    ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
+                    ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
+                    EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
+       (save-excursion
+        (goto-char (point-min))
+        (search-forward (concat "***" "BEGIN errtab" "***"))
+        (beginning-of-line 2)
+        (delete-region (point)
+                       (progn
+                         (search-forward "***END***")
+                         (beginning-of-line)
+                         (point)))
+        (dolist (err errors)
+          (insert (format "#ifdef %s\n  { \"%s\", %s },\n#endif\n"
+                          err err err)))))
+  */
+  /***BEGIN errtab***/
+#ifdef EPERM
+  { "EPERM", EPERM },
+#endif
+#ifdef ENOENT
+  { "ENOENT", ENOENT },
+#endif
+#ifdef ESRCH
+  { "ESRCH", ESRCH },
+#endif
+#ifdef EINTR
+  { "EINTR", EINTR },
+#endif
+#ifdef EIO
+  { "EIO", EIO },
+#endif
+#ifdef ENXIO
+  { "ENXIO", ENXIO },
+#endif
+#ifdef E2BIG
+  { "E2BIG", E2BIG },
+#endif
+#ifdef ENOEXEC
+  { "ENOEXEC", ENOEXEC },
+#endif
+#ifdef EBADF
+  { "EBADF", EBADF },
+#endif
+#ifdef ECHILD
+  { "ECHILD", ECHILD },
+#endif
+#ifdef EAGAIN
+  { "EAGAIN", EAGAIN },
+#endif
+#ifdef ENOMEM
+  { "ENOMEM", ENOMEM },
+#endif
+#ifdef EACCES
+  { "EACCES", EACCES },
+#endif
+#ifdef EFAULT
+  { "EFAULT", EFAULT },
+#endif
+#ifdef ENOTBLK
+  { "ENOTBLK", ENOTBLK },
+#endif
+#ifdef EBUSY
+  { "EBUSY", EBUSY },
+#endif
+#ifdef EEXIST
+  { "EEXIST", EEXIST },
+#endif
+#ifdef EXDEV
+  { "EXDEV", EXDEV },
+#endif
+#ifdef ENODEV
+  { "ENODEV", ENODEV },
+#endif
+#ifdef ENOTDIR
+  { "ENOTDIR", ENOTDIR },
+#endif
+#ifdef EISDIR
+  { "EISDIR", EISDIR },
+#endif
+#ifdef EINVAL
+  { "EINVAL", EINVAL },
+#endif
+#ifdef ENFILE
+  { "ENFILE", ENFILE },
+#endif
+#ifdef EMFILE
+  { "EMFILE", EMFILE },
+#endif
+#ifdef ENOTTY
+  { "ENOTTY", ENOTTY },
+#endif
+#ifdef ETXTBSY
+  { "ETXTBSY", ETXTBSY },
+#endif
+#ifdef EFBIG
+  { "EFBIG", EFBIG },
+#endif
+#ifdef ENOSPC
+  { "ENOSPC", ENOSPC },
+#endif
+#ifdef ESPIPE
+  { "ESPIPE", ESPIPE },
+#endif
+#ifdef EROFS
+  { "EROFS", EROFS },
+#endif
+#ifdef EMLINK
+  { "EMLINK", EMLINK },
+#endif
+#ifdef EPIPE
+  { "EPIPE", EPIPE },
+#endif
+#ifdef EDOM
+  { "EDOM", EDOM },
+#endif
+#ifdef ERANGE
+  { "ERANGE", ERANGE },
+#endif
+#ifdef EDEADLK
+  { "EDEADLK", EDEADLK },
+#endif
+#ifdef ENAMETOOLONG
+  { "ENAMETOOLONG", ENAMETOOLONG },
+#endif
+#ifdef ENOLCK
+  { "ENOLCK", ENOLCK },
+#endif
+#ifdef ENOSYS
+  { "ENOSYS", ENOSYS },
+#endif
+#ifdef ENOTEMPTY
+  { "ENOTEMPTY", ENOTEMPTY },
+#endif
+#ifdef ELOOP
+  { "ELOOP", ELOOP },
+#endif
+#ifdef EWOULDBLOCK
+  { "EWOULDBLOCK", EWOULDBLOCK },
+#endif
+#ifdef ENOMSG
+  { "ENOMSG", ENOMSG },
+#endif
+#ifdef EIDRM
+  { "EIDRM", EIDRM },
+#endif
+#ifdef ECHRNG
+  { "ECHRNG", ECHRNG },
+#endif
+#ifdef EL2NSYNC
+  { "EL2NSYNC", EL2NSYNC },
+#endif
+#ifdef EL3HLT
+  { "EL3HLT", EL3HLT },
+#endif
+#ifdef EL3RST
+  { "EL3RST", EL3RST },
+#endif
+#ifdef ELNRNG
+  { "ELNRNG", ELNRNG },
+#endif
+#ifdef EUNATCH
+  { "EUNATCH", EUNATCH },
+#endif
+#ifdef ENOCSI
+  { "ENOCSI", ENOCSI },
+#endif
+#ifdef EL2HLT
+  { "EL2HLT", EL2HLT },
+#endif
+#ifdef EBADE
+  { "EBADE", EBADE },
+#endif
+#ifdef EBADR
+  { "EBADR", EBADR },
+#endif
+#ifdef EXFULL
+  { "EXFULL", EXFULL },
+#endif
+#ifdef ENOANO
+  { "ENOANO", ENOANO },
+#endif
+#ifdef EBADRQC
+  { "EBADRQC", EBADRQC },
+#endif
+#ifdef EBADSLT
+  { "EBADSLT", EBADSLT },
+#endif
+#ifdef EDEADLOCK
+  { "EDEADLOCK", EDEADLOCK },
+#endif
+#ifdef EBFONT
+  { "EBFONT", EBFONT },
+#endif
+#ifdef ENOSTR
+  { "ENOSTR", ENOSTR },
+#endif
+#ifdef ENODATA
+  { "ENODATA", ENODATA },
+#endif
+#ifdef ETIME
+  { "ETIME", ETIME },
+#endif
+#ifdef ENOSR
+  { "ENOSR", ENOSR },
+#endif
+#ifdef ENONET
+  { "ENONET", ENONET },
+#endif
+#ifdef ENOPKG
+  { "ENOPKG", ENOPKG },
+#endif
+#ifdef EREMOTE
+  { "EREMOTE", EREMOTE },
+#endif
+#ifdef ENOLINK
+  { "ENOLINK", ENOLINK },
+#endif
+#ifdef EADV
+  { "EADV", EADV },
+#endif
+#ifdef ESRMNT
+  { "ESRMNT", ESRMNT },
+#endif
+#ifdef ECOMM
+  { "ECOMM", ECOMM },
+#endif
+#ifdef EPROTO
+  { "EPROTO", EPROTO },
+#endif
+#ifdef EMULTIHOP
+  { "EMULTIHOP", EMULTIHOP },
+#endif
+#ifdef EDOTDOT
+  { "EDOTDOT", EDOTDOT },
+#endif
+#ifdef EBADMSG
+  { "EBADMSG", EBADMSG },
+#endif
+#ifdef EOVERFLOW
+  { "EOVERFLOW", EOVERFLOW },
+#endif
+#ifdef ENOTUNIQ
+  { "ENOTUNIQ", ENOTUNIQ },
+#endif
+#ifdef EBADFD
+  { "EBADFD", EBADFD },
+#endif
+#ifdef EREMCHG
+  { "EREMCHG", EREMCHG },
+#endif
+#ifdef ELIBACC
+  { "ELIBACC", ELIBACC },
+#endif
+#ifdef ELIBBAD
+  { "ELIBBAD", ELIBBAD },
+#endif
+#ifdef ELIBSCN
+  { "ELIBSCN", ELIBSCN },
+#endif
+#ifdef ELIBMAX
+  { "ELIBMAX", ELIBMAX },
+#endif
+#ifdef ELIBEXEC
+  { "ELIBEXEC", ELIBEXEC },
+#endif
+#ifdef EILSEQ
+  { "EILSEQ", EILSEQ },
+#endif
+#ifdef ERESTART
+  { "ERESTART", ERESTART },
+#endif
+#ifdef ESTRPIPE
+  { "ESTRPIPE", ESTRPIPE },
+#endif
+#ifdef EUSERS
+  { "EUSERS", EUSERS },
+#endif
+#ifdef ENOTSOCK
+  { "ENOTSOCK", ENOTSOCK },
+#endif
+#ifdef EDESTADDRREQ
+  { "EDESTADDRREQ", EDESTADDRREQ },
+#endif
+#ifdef EMSGSIZE
+  { "EMSGSIZE", EMSGSIZE },
+#endif
+#ifdef EPROTOTYPE
+  { "EPROTOTYPE", EPROTOTYPE },
+#endif
+#ifdef ENOPROTOOPT
+  { "ENOPROTOOPT", ENOPROTOOPT },
+#endif
+#ifdef EPROTONOSUPPORT
+  { "EPROTONOSUPPORT", EPROTONOSUPPORT },
+#endif
+#ifdef ESOCKTNOSUPPORT
+  { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT },
+#endif
+#ifdef EOPNOTSUPP
+  { "EOPNOTSUPP", EOPNOTSUPP },
+#endif
+#ifdef EPFNOSUPPORT
+  { "EPFNOSUPPORT", EPFNOSUPPORT },
+#endif
+#ifdef EAFNOSUPPORT
+  { "EAFNOSUPPORT", EAFNOSUPPORT },
+#endif
+#ifdef EADDRINUSE
+  { "EADDRINUSE", EADDRINUSE },
+#endif
+#ifdef EADDRNOTAVAIL
+  { "EADDRNOTAVAIL", EADDRNOTAVAIL },
+#endif
+#ifdef ENETDOWN
+  { "ENETDOWN", ENETDOWN },
+#endif
+#ifdef ENETUNREACH
+  { "ENETUNREACH", ENETUNREACH },
+#endif
+#ifdef ENETRESET
+  { "ENETRESET", ENETRESET },
+#endif
+#ifdef ECONNABORTED
+  { "ECONNABORTED", ECONNABORTED },
+#endif
+#ifdef ECONNRESET
+  { "ECONNRESET", ECONNRESET },
+#endif
+#ifdef ENOBUFS
+  { "ENOBUFS", ENOBUFS },
+#endif
+#ifdef EISCONN
+  { "EISCONN", EISCONN },
+#endif
+#ifdef ENOTCONN
+  { "ENOTCONN", ENOTCONN },
+#endif
+#ifdef ESHUTDOWN
+  { "ESHUTDOWN", ESHUTDOWN },
+#endif
+#ifdef ETOOMANYREFS
+  { "ETOOMANYREFS", ETOOMANYREFS },
+#endif
+#ifdef ETIMEDOUT
+  { "ETIMEDOUT", ETIMEDOUT },
+#endif
+#ifdef ECONNREFUSED
+  { "ECONNREFUSED", ECONNREFUSED },
+#endif
+#ifdef EHOSTDOWN
+  { "EHOSTDOWN", EHOSTDOWN },
+#endif
+#ifdef EHOSTUNREACH
+  { "EHOSTUNREACH", EHOSTUNREACH },
+#endif
+#ifdef EALREADY
+  { "EALREADY", EALREADY },
+#endif
+#ifdef EINPROGRESS
+  { "EINPROGRESS", EINPROGRESS },
+#endif
+#ifdef ESTALE
+  { "ESTALE", ESTALE },
+#endif
+#ifdef EUCLEAN
+  { "EUCLEAN", EUCLEAN },
+#endif
+#ifdef ENOTNAM
+  { "ENOTNAM", ENOTNAM },
+#endif
+#ifdef ENAVAIL
+  { "ENAVAIL", ENAVAIL },
+#endif
+#ifdef EISNAM
+  { "EISNAM", EISNAM },
+#endif
+#ifdef EREMOTEIO
+  { "EREMOTEIO", EREMOTEIO },
+#endif
+#ifdef EDQUOT
+  { "EDQUOT", EDQUOT },
+#endif
+#ifdef ENOMEDIUM
+  { "ENOMEDIUM", ENOMEDIUM },
+#endif
+#ifdef EMEDIUMTYPE
+  { "EMEDIUMTYPE", EMEDIUMTYPE },
+#endif
+#ifdef ECANCELED
+  { "ECANCELED", ECANCELED },
+#endif
+#ifdef ENOKEY
+  { "ENOKEY", ENOKEY },
+#endif
+#ifdef EKEYEXPIRED
+  { "EKEYEXPIRED", EKEYEXPIRED },
+#endif
+#ifdef EKEYREVOKED
+  { "EKEYREVOKED", EKEYREVOKED },
+#endif
+#ifdef EKEYREJECTED
+  { "EKEYREJECTED", EKEYREJECTED },
+#endif
+#ifdef EOWNERDEAD
+  { "EOWNERDEAD", EOWNERDEAD },
+#endif
+#ifdef ENOTRECOVERABLE
+  { "ENOTRECOVERABLE", ENOTRECOVERABLE },
+#endif
+#ifdef ERFKILL
+  { "ERFKILL", ERFKILL },
+#endif
+#ifdef EHWPOISON
+  { "EHWPOISON", EHWPOISON },
+#endif
+  /***END***/
 };
 
-static void except(JNIEnv *jni, const char *clsname, const char *msg)
+JNIEXPORT jobject JNIFUNC(errtab)(JNIEnv *jni, jobject cls)
+{
+  size_t i;
+  jclass eltcls;
+  jarray v;
+  jmethodID init;
+  jobject e;
+
+  eltcls =
+    (*jni)->FindClass(jni, ERRENTCLS);
+  assert(eltcls);
+  v = (*jni)->NewObjectArray(jni, N(errtab), eltcls, 0); if (!v) return (0);
+  init = (*jni)->GetMethodID(jni, eltcls, "<init>",
+                            "(L"STRCLS";I)V");
+  assert(init);
+
+  for (i = 0; i < N(errtab); i++) {
+    e = (*jni)->NewObject(jni, eltcls, init,
+                         (*jni)->NewStringUTF(jni, errtab[i].tag),
+                         errtab[i].err);
+    (*jni)->SetObjectArrayElement(jni, v, i, e);
+  }
+  return (v);
+}
+
+JNIEXPORT jobject JNIFUNC(strerror)(JNIEnv *jni, jobject cls, jint err)
+  { return (wrap_cstring(jni, strerror(err))); }
+
+/*----- Messing with file descriptors -------------------------------------*/
+
+static void fdguts(JNIEnv *jni, jclass *cls, jfieldID *fid)
+{
+  *cls = (*jni)->FindClass(jni, FDCLS); assert(cls);
+  *fid = (*jni)->GetFieldID(jni, *cls, "fd", "I"); // OpenJDK
+  if (!*fid) *fid = (*jni)->GetFieldID(jni, *cls, "descriptor", "I"); // Android
+  assert(*fid);
+}
+
+static int fdint(JNIEnv *jni, jobject jfd)
+{
+  jclass cls;
+  jfieldID fid;
+
+  fdguts(jni, &cls, &fid);
+  return ((*jni)->GetIntField(jni, jfd, fid));
+}
+
+static jobject newfd(JNIEnv *jni, int fd)
 {
+  jobject jfd;
   jclass cls;
+  jmethodID init;
+  jfieldID fid;
+
+  fdguts(jni, &cls, &fid);
+  init = (*jni)->GetMethodID(jni, cls, "<init>", "()V"); assert(init);
+  jfd = (*jni)->NewObject(jni, cls, init);
+  (*jni)->SetIntField(jni, jfd, fid, fd);
+  return (jfd);
+}
+
+JNIEXPORT jint JNIFUNC(fdint)(JNIEnv *jni, jobject cls, jobject jfd)
+  { return (fdint(jni, jfd)); }
+
+JNIEXPORT jobject JNIFUNC(newfd)(JNIEnv *jni, jobject cls, jint fd)
+  { return (newfd(jni, fd)); }
+
+JNIEXPORT jboolean JNIFUNC(isatty)(JNIEnv *jni, jobject cls, jobject jfd)
+  { return (isatty(fdint(jni, jfd))); }
+
+/*----- Low-level file operations -----------------------------------------*/
+
+/* Java has these already, as methods on `java.io.File' objects.  Alas, these
+ * methods are useless at reporting errors: they tend to return a `boolean'
+ * success/fail indicator, and throw away any more detailed information.
+ * There's better functionality in `java.nio.file.Files', but that only turns
+ * up in Android API 26 (in 7.0 Nougat).  There's `android.system.Os', which
+ * has a bunch of POSIX-shaped functions -- but they're only in Android API
+ * 21 (in 5.0 Lollipop), and there's nothing in the support library to help.
+ *
+ * So the other option is to implement them ourselves.
+ */
+
+JNIEXPORT void JNIFUNC(unlink)(JNIEnv *jni, jobject cls, jobject path)
+{
+  const char *pathstr = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (unlink(pathstr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to delete file `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(rmdir)(JNIEnv *jni, jobject cls, jobject path)
+{
+  const char *pathstr = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (rmdir(pathstr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to delete directory `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(mkdir)(JNIEnv *jni, jobject cls,
+                             jobject path, jint mode)
+{
+  const char *pathstr = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (mkdir(pathstr, mode)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to create directory `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(chmod)(JNIEnv *jni, jobject cls,
+                             jobject path, jint mode)
+{
+  const char *pathstr = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (chmod(pathstr, mode)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed st permissions on `%s'", pathstr);
+    goto end;
+  }
+end:
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(mkfile)(JNIEnv *jni, jobject cls,
+                           jobject path, jint mode)
+{
+  const char *pathstr = 0;
+  int fd = -1;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  fd = open(pathstr, O_WRONLY | O_CREAT | O_EXCL, mode);
+  if (fd < 0) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to create fresh file `%s'", pathstr);
+    goto end;
+  }
+end:
+  if (fd != -1) close(fd);
+  put_cstring(jni, path, pathstr);
+}
+
+JNIEXPORT void JNIFUNC(rename)(JNIEnv *jni, jobject cls,
+                              jobject from, jobject to)
+{
+  const char *fromstr = 0, *tostr = 0;
+
+  fromstr = get_cstring(jni, from); if (!fromstr) goto end;
+  tostr = get_cstring(jni, to); if (!tostr) goto end;
+  if (rename(fromstr, tostr)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to rename `%s' as `%s'", fromstr, tostr);
+    goto end;
+  }
+end:
+  put_cstring(jni, from, fromstr);
+  put_cstring(jni, to, tostr);
+}
+
+#define LKF_EXCL 0x1000u
+#define LKF_WAIT 0x2000u
+struct lockf {
+  struct native_base _base;
+  int fd;
+};
+static struct native_type lockf_type =
+       { "lock", sizeof(struct lockf), 0xb2648926};
+JNIEXPORT wrapper JNIFUNC(lock)(JNIEnv *jni, jobject cls,
+                               jobject path, jint flags)
+{
+  const char *pathstr = 0;
+  int fd = -1;
+  struct flock l;
+  struct lockf lk;
+  struct stat st0, st1;
+  int f;
+  wrapper r = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+
+again:
+  fd = open(pathstr, O_RDWR | O_CREAT, flags&07777); if (fd < 0) goto err;
+  if (fstat(fd, &st0)) goto err;
+  f = fcntl(fd, F_GETFD); if (f < 0) goto err;
+  if (fcntl(fd, F_SETFD, f | FD_CLOEXEC)) goto err;
+  l.l_type = (flags&LKF_EXCL) ? F_WRLCK : F_RDLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  if (fcntl(fd, (flags&LKF_WAIT) ? F_SETLKW : F_SETLK, &l)) goto err;
+  if (stat(pathstr, &st1))
+    { if (errno == ENOENT) goto again; else goto err; }
+  if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino)
+    { close(fd); fd = -1; goto again; }
+
+  INIT_NATIVE(lockf, &lk); lk.fd = fd; fd = -1;
+  r = wrap(jni, &lockf_type, &lk);
+  goto end;
+
+err:
+  except_syserror(jni, SYSERR, errno, "failed to lock file `%s'", pathstr);
+end:
+  if (fd != -1) close(fd);
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
+
+JNIEXPORT void JNIFUNC(unlock)(JNIEnv *jni, jobject cls, wrapper wlk)
+{
+  struct lockf lk;
+  struct flock l;
   int rc;
 
-  cls = (*jni)->FindClass(jni, clsname); assert(cls);
-  rc = (*jni)->ThrowNew(jni, cls, msg); assert(!rc);
+  if (unwrap(jni, &lk, &lockf_type, wlk)) goto end;
+  if (lk.fd == -1) goto end;
+  l.l_type = F_UNLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  if (fcntl(lk.fd, F_SETLK, &l)) goto end;
+  close(lk.fd); lk.fd = -1;
+  rc = update_wrapper(jni, &lockf_type, wlk, &lk); assert(!rc);
+end:;
 }
 
-static void except_errno(JNIEnv *jni, const char *clsname, int err)
-  { except(jni, clsname, strerror(err)); }
+static jlong xlttimespec(const struct timespec *ts)
+  { return (1000*(jlong)ts->tv_sec + ts->tv_nsec/1000000); }
 
-static void *open_struct_unchecked(JNIEnv *jni, wrapped obj, struct open *op)
+static jobject xltstat(JNIEnv *jni, const struct stat *st)
 {
-  jboolean copyp;
-  uintptr_t p, q;
+  jclass cls;
+  jmethodID init;
+  jint modehack;
+
+  modehack = st->st_mode&07777;
+  if (S_ISFIFO(st->st_mode)) modehack |= 0010000;
+  else if (S_ISCHR(st->st_mode)) modehack |= 0020000;
+  else if (S_ISDIR(st->st_mode)) modehack |= 0040000;
+  else if (S_ISBLK(st->st_mode)) modehack |= 0060000;
+  else if (S_ISREG(st->st_mode)) modehack |= 0100000;
+  else if (S_ISLNK(st->st_mode)) modehack |= 0120000;
+  else if (S_ISSOCK(st->st_mode)) modehack |= 0140000;
 
-  op->obj = obj;
-  op->arr = (*jni)->GetByteArrayElements(jni, obj, &copyp);
-  if (!op->arr) return (0);
-  p = (uintptr_t)op->arr;
-  q = p + sizeof(union align) - 1;
-  q -= q%sizeof(union align);
-  fprintf(stderr, ";; offset = %"PRIuPTR"\n", q - p);
-  return (op->arr + (q - p));
+  cls = (*jni)->FindClass(jni, STATCLS); assert(cls);
+  init = (*jni)->GetMethodID(jni, cls, "<init>", "(IIJIIIIIIJIJJJJ)V");
+  assert(init);
+  return ((*jni)->NewObject(jni, cls, init,
+                           (jint)major(st->st_dev), (jint)minor(st->st_dev),
+                           (jlong)st->st_ino,
+                           modehack,
+                           (jint)st->st_nlink,
+                           (jint)st->st_uid, (jint)st->st_gid,
+                           (jint)major(st->st_rdev), (jint)minor(st->st_rdev),
+                           (jlong)st->st_size,
+                           (jint)st->st_blksize, (jlong)st->st_blocks,
+                           xlttimespec(&st->st_atim),
+                           xlttimespec(&st->st_mtim),
+                           xlttimespec(&st->st_ctim)));
 }
 
-static void *open_struct(JNIEnv *jni, wrapped obj,
-                        const struct native_type *ty, struct open *op)
+JNIEXPORT jobject JNIFUNC(stat)(JNIEnv *jni, jobject cls, jobject path)
 {
-  struct base *p;
-  jsize n;
+  jobject r = 0;
+  const char *pathstr = 0;
+  struct stat st;
 
-  if (!obj) { except(jni, "java/lang/NullPointerException", 0); return (0); }
-  n = (*jni)->GetArrayLength(jni, obj);
-  if ((*jni)->ExceptionOccurred(jni)) return (0);
-  p = open_struct_unchecked(jni, obj, op);
-  if (!p) return (0);
-  if (n < ty->sz + sizeof(union align) - 1 || p->tag != ty->tag)
-  {
-    (*jni)->ReleaseByteArrayElements(jni, obj, op->arr, JNI_ABORT);
-    except(jni, "uk/org/distorted/tripe/JNI$NativeObjectTypeException", 0);
-    return (0);
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (stat(pathstr, &st)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read information about `%s'", pathstr);
+    goto end;
+  }
+  r = xltstat(jni, &st);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
+
+JNIEXPORT jobject JNIFUNC(lstat)(JNIEnv *jni, jobject cls, jobject path)
+{
+  jobject r = 0;
+  const char *pathstr = 0;
+  struct stat st;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  if (lstat(pathstr, &st)) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read information about `%s'", pathstr);
+    goto end;
   }
-  return (p);
+  r = xltstat(jni, &st);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
 }
 
-static wrapped close_struct(JNIEnv *jni, struct open *op)
+struct dir {
+  struct native_base _base;
+  DIR *d;
+};
+static const struct native_type dir_type =
+       { "dir", sizeof(struct dir), 0x0f5ca477 };
+
+JNIEXPORT jobject JNIFUNC(opendir)(JNIEnv *jni, jobject cls, jobject path)
 {
-  (*jni)->ReleaseByteArrayElements(jni, op->obj, op->arr, 0);
-  return (op->obj);
+  const char *pathstr = 0;
+  struct dir dir;
+  wrapper r = 0;
+
+  pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+  INIT_NATIVE(dir, &dir);
+  dir.d = opendir(pathstr);
+  if (!dir.d) {
+    except_syserror(jni, SYSERR, errno,
+                   "failed to open directory `%s'", pathstr);
+    goto end;
+  }
+  r = wrap(jni, &dir_type, &dir);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
+}
+
+JNIEXPORT jbyteArray JNIFUNC(readdir)(JNIEnv *jni, jobject cls,
+                                     jobject path, jobject wdir)
+{
+  const char *pathstr = 0;
+  struct dir dir;
+  struct dirent *d;
+  jbyteArray r = 0;
+
+  if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
+  if (!dir.d) { except(jni, ARGERR, "directory has been closed"); goto end; }
+  errno = 0; d = readdir(dir.d);
+  if (errno) {
+    pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+    except_syserror(jni, SYSERR, errno,
+                   "failed to read directory `%s'", pathstr);
+    goto end;
+  }
+  if (d) r = wrap_cstring(jni, d->d_name);
+end:
+  put_cstring(jni, path, pathstr);
+  return (r);
 }
 
-static void *alloc_struct(JNIEnv *jni, const struct native_type *ty,
-                         struct open *op)
+JNIEXPORT void JNIFUNC(closedir)(JNIEnv *jni, jobject cls,
+                                jobject path, jobject wdir)
 {
-  wrapped obj;
-  struct base *p;
+  const char *pathstr = 0;
+  struct dir dir;
 
-  obj = (*jni)->NewByteArray(jni, ty->sz + sizeof(union align) - 1);
-  if (!obj) return (0);
-  p = open_struct_unchecked(jni, obj, op);
-  if (!p) { (*jni)->DeleteLocalRef(jni, obj); return (0); }
-  p->tag = ty->tag;
-  return (p);
+  if (unwrap(jni, &dir, &dir_type, wdir)) goto end;
+  if (!dir.d) goto end;
+  if (closedir(dir.d)) {
+    pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+    except_syserror(jni, SYSERR, errno,
+                   "failed to close directory `%s'", pathstr);
+    goto end;
+  }
+  dir.d = 0;
+  if (update_wrapper(jni, &dir_type, wdir, &dir)) goto end;
+end:
+  put_cstring(jni, path, pathstr);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_test
-       (JNIEnv *jni, jobject cls)
-  { printf("Hello from C!\n"); }
+/*----- Triggers ----------------------------------------------------------*/
+
+/* A trigger is a gadget for waking up a thread which is blocking on I/O,
+ * and it's used to implement interruptability.
+ *
+ * Really, a trigger is a pipe.  A `blocking' I/O operation secretly uses
+ * select(2) to block on the descriptor of interest /and/ the read side of
+ * the trigger pipe.  To wake up a thread that's blocked, we just write a
+ * byte (nobody cares /which/ byte) to the write end.
+ */
 
-struct toy {
-  struct base _base;
-  const char *p;
+struct trigger {
+  struct native_base _base;
+  int rfd, wfd;
 };
-static const struct native_type toy_type =
-       { "toy", sizeof(struct toy), 0x58008918 };
+static const struct native_type trigger_type =
+       { "trigger", sizeof(struct trigger), 0x65ffd8b4 };
+
+JNIEXPORT wrapper JNICALL JNIFUNC(make_1trigger)(JNIEnv *jni, jobject cls)
+{
+  struct trigger trig;
+  int fd[2];
+  int i;
+  wrapper ret = 0;
+
+  fd[0] = fd[1] = -1;
+  if (pipe(fd)) {
+    except_syserror(jni, SYSERR, errno, "failed to create pipe");
+    goto end;
+  }
+  for (i = 0; i < 2; i++) {
+    if (set_nonblocking(jni, fd[i], 1) < 0 || set_closeonexec(jni, fd[i]))
+      goto end;
+  }
+
+  INIT_NATIVE(trigger, &trig);
+  trig.rfd = fd[0]; fd[0] = -1;
+  trig.wfd = fd[1]; fd[1] = -1;
+  ret = wrap(jni, &trigger_type, &trig);
 
-JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_make
-       (JNIEnv *jni, jobject cls)
+end:
+  for (i = 0; i < 2; i++)
+    if (fd[i] != -1) close(fd[i]);
+  return (ret);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(destroy_1trigger)(JNIEnv *jni, jobject cls,
+                                               wrapper wtrig)
 {
-  struct open op_toy;
-  struct toy *toy;
+  struct trigger trig;
 
-  toy = alloc_struct(jni, &toy_type, &op_toy);
-  if (!toy) return (0);
-  toy->p = "A working thing";
-  return (close_struct(jni, &op_toy));
+  if (unwrap(jni, &trig, &trigger_type, wtrig)) return;
+  if (trig.rfd != -1) { close(trig.rfd); trig.rfd = -1; }
+  if (trig.wfd != -1) { close(trig.wfd); trig.wfd = -1; }
+  update_wrapper(jni, &trigger_type, wtrig, &trig);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_check
-       (JNIEnv *jni, jobject cls, wrapped wtoy)
+JNIEXPORT void JNICALL JNIFUNC(reset_1trigger)(JNIEnv *jni, jobject cls,
+                                             wrapper wtrig)
 {
-  struct toy *toy;
-  struct open op_toy;
+  struct trigger trig;
+  char buf[64];
+  ssize_t n;
 
-  toy = open_struct(jni, wtoy, &toy_type, &op_toy);
-  if (!toy) return;
-  printf("Toy says: %s\n", toy->p);
-  close_struct(jni, &op_toy);
+  if (unwrap(jni, &trig, &trigger_type, wtrig)) return;
+  for (;;) {
+    n = read(trig.rfd, buf, sizeof(buf));
+    if (n > 0) continue;
+    assert(n < 0);
+    if (errno == EAGAIN || errno == EWOULDBLOCK) break;
+    else {
+      except_syserror(jni, SYSERR, errno, "failed to reset trigger");
+      break;
+    }
+  }
 }
 
-struct conn {
-  struct base _base;
-  int fd;
-  unsigned f;
-#define CF_CLOSERD 1u
-#define CF_CLOSEWR 2u
-#define CF_CLOSEMASK (CF_CLOSERD | CF_CLOSEWR)
+JNIEXPORT void JNICALL JNIFUNC(trigger)(JNIEnv *jni, jobject cls,
+                                       wrapper wtrig)
+{
+  struct trigger trig;
+  ssize_t n;
+  char c = 0;
+
+  if (unwrap(jni, &trig, &trigger_type, wtrig)) return;
+  n = write(trig.wfd, &c, 1);
+  if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
+    except_syserror(jni, SYSERR, errno, "failed to pull trigger");
+}
+
+/*----- A tunnel supplied by Java -----------------------------------------*/
+
+struct tunnel {
+  const tunnel_ops *ops;
+  sel_file f;
+  struct peer *p;
 };
-static const struct native_type conn_type
-       = { "conn", sizeof(struct conn), 0xed030167 };
 
-JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_connect
-       (JNIEnv *jni, jobject cls)
+static const struct tunnel_ops tun_java;
+
+static int t_init(void) { return (0); }
+
+static void t_read(int fd, unsigned mode, void *v)
 {
-  struct conn *conn;
-  struct open op;
-  struct sockaddr_un sun;
+  tunnel *t = v;
+  ssize_t n;
+  buf b;
+
+  n = read(fd, buf_i, sizeof(buf_i));
+  if (n < 0) {
+    a_warn("TUN", "%s", p_ifname(t->p), "java",
+          "read-error", "?ERRNO", A_END);
+    return;
+  }
+  IF_TRACING(T_TUNNEL, {
+    trace(T_TUNNEL, "tun-java: packet arrived");
+    trace_block(T_PACKET, "tunnel: packet contents", buf_i, n);
+  })
+  buf_init(&b, buf_i, n);
+  p_tun(t->p, &b);
+}
+
+static tunnel *t_create(peer *p, int fd, char **ifn)
+{
+  JNIEnv *jni = jni_tripe;
+  tunnel *t = 0;
+  const char *name = p_name(p);
+  jbyteArray jname;
+  size_t n = strlen(p_name(p));
+  jclass cls, metacls;
+  jstring jclsname, jexcmsg;
+  const char *clsname, *excmsg;
+  jmethodID mid;
+  jthrowable exc;
+
+  assert(jni);
+
+  jname = wrap_cstring(jni, name);
+  cls = (*jni)->FindClass(jni, SYSCLS); assert(cls);
+  mid = (*jni)->GetStaticMethodID(jni, cls, "getTunnelFd", "([B)I");
+  assert(mid);
+  fd = (*jni)->CallStaticIntMethod(jni, cls, mid, jname);
+
+  exc = (*jni)->ExceptionOccurred(jni);
+  if (exc) {
+    cls = (*jni)->GetObjectClass(jni, exc);
+    metacls = (*jni)->GetObjectClass(jni, cls);
+    mid = (*jni)->GetMethodID(jni, metacls,
+                             "getName", "()L"STRCLS";");
+    assert(mid);
+    jclsname = (*jni)->CallObjectMethod(jni, cls, mid);
+    clsname = (*jni)->GetStringUTFChars(jni, jclsname, 0);
+    mid = (*jni)->GetMethodID(jni, cls,
+                             "getMessage", "()L"STRCLS";");
+    jexcmsg = (*jni)->CallObjectMethod(jni, exc, mid);
+    excmsg = (*jni)->GetStringUTFChars(jni, jexcmsg, 0);
+    a_warn("TUN", "-", "java", "get-tunnel-fd-failed",
+          "%s", clsname, "%s", excmsg, A_END);
+    (*jni)->ReleaseStringUTFChars(jni, jclsname, clsname);
+    (*jni)->ReleaseStringUTFChars(jni, jexcmsg, excmsg);
+    (*jni)->ExceptionClear(jni);
+    goto end;
+  }
+
+  t = CREATE(tunnel);
+  t->ops = &tun_java;
+  t->p = p;
+  sel_initfile(&sel, &t->f, fd, SEL_READ, t_read, t);
+
+  if (!*ifn) {
+    *ifn = xmalloc(n + 5);
+    sprintf(*ifn, "vpn-%s", name);
+  }
+
+end:
+  return (t);
+}
+
+static void t_inject(tunnel *t, buf *b)
+{
+  IF_TRACING(T_TUNNEL, {
+    trace(T_TUNNEL, "tun-java: inject decrypted packet");
+    trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b));
+  })
+  DISCARD(write(t->f.fd, BBASE(b), BLEN(b)));
+}
+
+static void t_destroy(tunnel *t)
+  { sel_rmfile(&t->f); close(t->f.fd); DESTROY(t); }
+
+static const struct tunnel_ops tun_java = {
+  "java", 0,
+  /*      init */ t_init,
+  /*    create */ t_create,
+  /* setifname */ 0,
+  /*    inject */ t_inject,
+  /*   destroy */ t_destroy
+};
+
+
+JNIEXPORT jint JNICALL JNIFUNC(open_1tun)(JNIEnv *jni, jobject cls)
+{
+  int ret = -1;
   int fd = -1;
+  struct ifreq iff;
 
-  conn = alloc_struct(jni, &conn_type, &op);
-  if (!conn) goto err;
+  if ((fd = open("/dev/net/tun", O_RDWR)) < 0) {
+    except_syserror(jni, SYSERR, errno, "failed to open tunnel device");
+    goto end;
+  }
 
-  fd = socket(SOCK_STREAM, PF_UNIX, 0);
-  if (!fd) goto err_except;
+  if (set_nonblocking(jni, fd, 1) || set_closeonexec(jni, fd)) goto end;
 
-  sun.sun_family = AF_UNIX;
-  strcpy(sun.sun_path, "/tmp/mdw/sk");
-  if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err_except;
+  memset(&iff, 0, sizeof(iff));
+  iff.ifr_name[0] = 0;
+  iff.ifr_flags = IFF_TUN | IFF_NO_PI;
+  if (ioctl(fd, TUNSETIFF, &iff) < 0) {
+    except_syserror(jni, SYSERR, errno, "failed to configure tunnel device");
+    goto end;
+  }
 
-  conn->fd = fd;
-  return (close_struct(jni, &op));
+  ret = fd; fd = -1;
 
-err_except:
-  except_errno(jni, "java/io/IOException", errno);
-err:
-  if (fd) close(fd);
+end:
+  if (fd != -1) close(fd);
+  return (ret);
+}
+
+/*----- A custom noise source ---------------------------------------------*/
+
+static void javanoise(rand_pool *r)
+{
+  JNIEnv *jni = jni_tripe;
+  jclass cls;
+  jmethodID mid;
+  jbyteArray v;
+  jbyte *p;
+  jsize n;
+
+  noise_devrandom(r);
+
+  assert(jni);
+  cls = (*jni)->FindClass(jni, RANDCLS); assert(cls);
+  mid = (*jni)->GetStaticMethodID(jni, cls, "getSeed", "(I)[B"); assert(mid);
+  v = (*jni)->CallStaticObjectMethod(jni, cls, mid, 32);
+  if (v) {
+    n = (*jni)->GetArrayLength(jni, v);
+    p = (*jni)->GetByteArrayElements(jni, v, 0);
+    rand_add(r, p, n, n);
+    (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT);
+  }
+  if ((*jni)->ExceptionOccurred(jni)) {
+    (*jni)->ExceptionDescribe(jni);
+    (*jni)->ExceptionClear(jni);
+  }
+}
+
+static const rand_source javasource = { javanoise, noise_timer };
+
+/*----- Embedding the TrIPE server ----------------------------------------*/
+
+static void lock_tripe(JNIEnv *jni)
+{
+  jclass cls = (*jni)->FindClass(jni, LOCKCLS); assert(cls);
+  (*jni)->MonitorEnter(jni, cls);
+}
+
+static void unlock_tripe(JNIEnv *jni)
+{
+  jclass cls = (*jni)->FindClass(jni, LOCKCLS); assert(cls);
+  (*jni)->MonitorExit(jni, cls);
+}
+
+#define STATES(_)                                                      \
+       _(INIT)                                                         \
+       _(RESOLVE)                                                      \
+       _(KEYS)                                                         \
+       _(BIND)                                                         \
+       _(READY)                                                        \
+       _(RUNNING)
+
+enum {
+#define DEFTAG(st) st,
+  STATES(DEFTAG)
+#undef DEFTAG
+  MAXSTATE
+};
+
+static const char *statetab[] = {
+#define DEFNAME(st) #st,
+  STATES(DEFNAME)
+#undef DEFNAME
+};
+
+static unsigned state = INIT;
+static int clientsk = -1;
+
+static const char *statename(unsigned st)
+{
+  if (st >= MAXSTATE) return ("<invalid>");
+  else return (statetab[st]);
+}
+
+static int ensure_state(JNIEnv *jni, unsigned want)
+{
+  unsigned cur;
+
+  lock_tripe(jni);
+  cur = state;
+  unlock_tripe(jni);
+
+  if (cur != want) {
+    except(jni, STERR, "server is in state %s (%u), not %s (%u)",
+          statename(cur), cur, statename(want), want);
+    return (-1);
+  }
   return (0);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_send
-       (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf,
-        jint start, jint len)
+JNIEXPORT void JNICALL JNIFUNC(base_1init)(JNIEnv *jni, jobject cls)
+{
+  int fd[2];
+  int i;
+
+  for (i = 0; i < N(fd); i++) fd[i] = -1;
+
+  lock_tripe(jni);
+  jni_tripe = jni;
+  if (ensure_state(jni, INIT)) goto end;
+
+  if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)) {
+    except_syserror(jni, SYSERR, errno, "failed to create socket pair");
+    goto end;
+  }
+
+  clientsk = fd[0]; fd[0] = -1;
+
+  rand_noisesrc(RAND_GLOBAL, &javasource);
+  rand_seed(RAND_GLOBAL, MAXHASHSZ);
+  lp_init();
+  a_create(fd[1], fd[1], AF_NOTE | AF_WARN | AF_TRACE); fd[1] = -1;
+  a_switcherr();
+  p_addtun(&tun_java); p_setdflttun(&tun_java);
+  p_init();
+  kx_init();
+
+  state++;
+
+end:
+  for (i = 0; i < N(fd); i++) if (fd[i] != -1) close(fd[i]);
+  jni_tripe = 0;
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(setup_1resolver)(JNIEnv *jni, jobject cls)
+{
+  lock_tripe(jni);
+  if (ensure_state(jni, RESOLVE)) goto end;
+
+  if (a_init())
+    { except(jni, INITERR, "failed to initialize resolver"); return; }
+
+  state++;
+
+end:
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(load_1keys)(JNIEnv *jni, jobject cls,
+                                         jobject privstr, jobject pubstr,
+                                         jobject tagstr)
+{
+  const char *priv = 0, *pub = 0, *tag = 0;
+
+  lock_tripe(jni);
+  if (ensure_state(jni, KEYS)) return;
+
+  priv = get_cstring(jni, privstr); if (!priv) goto end;
+  pub = get_cstring(jni, pubstr); if (!pub) goto end;
+  tag = get_cstring(jni, tagstr); if (!tag) goto end;
+
+  if (km_init(priv, pub, tag))
+    { except(jni, INITERR, "failed to load initial keys"); goto end; }
+
+  state++;
+
+end:
+  put_cstring(jni, privstr, priv);
+  put_cstring(jni, pubstr, pub);
+  put_cstring(jni, tagstr, tag);
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(unload_1keys)(JNIEnv *jni, jobject cls)
+{
+  lock_tripe(jni);
+  if (ensure_state(jni, KEYS + 1)) goto end;
+
+  km_clear();
+
+  state--;
+
+end:
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(bind)(JNIEnv *jni, jobject cls,
+                                    jbyteArray hoststr, jbyteArray svcstr)
+{
+  const char *host = 0, *svc = 0;
+  struct addrinfo hint, *ai = 0;
+  int err;
+
+  lock_tripe(jni);
+  if (ensure_state(jni, BIND)) goto end;
+
+  if (hoststr) { host = get_cstring(jni, hoststr); if (!host) goto end; }
+  svc = get_cstring(jni, svcstr); if (!svc) goto end;
+
+  hint.ai_socktype = SOCK_DGRAM;
+  hint.ai_family = AF_UNSPEC;
+  hint.ai_protocol = IPPROTO_UDP;
+  hint.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+  err = getaddrinfo(host, svc, &hint, &ai);
+  if (err) {
+    except(jni, NAMEERR, "failed to resolve %c%s%c, port `%s': %s",
+          host ? '`' : '<', host ? host : "nil", host ? '\'' : '>',
+          svc, gai_strerror(err));
+    goto end;
+  }
+
+  if (p_bind(ai))
+    { except(jni, INITERR, "failed to bind master socket"); goto end; }
+
+  state++;
+
+end:
+  if (ai) freeaddrinfo(ai);
+  put_cstring(jni, hoststr, host);
+  put_cstring(jni, svcstr, svc);
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(unbind)(JNIEnv *jni, jobject cls)
+{
+  lock_tripe(jni);
+  if (ensure_state(jni, BIND + 1)) goto end;
+
+  p_unbind();
+
+  state--;
+
+end:
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(mark)(JNIEnv *jni, jobject cls, jint seq)
+{
+  lock_tripe(jni);
+  a_notify("MARK", "%d", seq, A_END);
+  unlock_tripe(jni);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(run)(JNIEnv *jni, jobject cls)
+{
+  lock_tripe(jni);
+  if (ensure_state(jni, READY)) goto end;
+  assert(!jni_tripe);
+  jni_tripe = jni;
+  state = RUNNING;
+  unlock_tripe(jni);
+
+  lp_run();
+
+  lock_tripe(jni);
+  jni_tripe = 0;
+  state = READY;
+
+end:
+  unlock_tripe(jni);
+}
+
+static int check_buffer_bounds(JNIEnv *jni, const char *what,
+                              jbyteArray buf, jint start, jint len)
 {
-  struct conn *conn = 0;
-  struct open op;
-  jboolean copyp;
   jsize bufsz;
+  jclass cls;
+
+  cls = (*jni)->FindClass(jni, "[B"); assert(cls);
+  if (!(*jni)->IsInstanceOf(jni, buf, cls)) {
+    except(jni, ARGERR,
+          "expected a byte array");
+    return (-1);
+  }
+  bufsz = (*jni)->GetArrayLength(jni, buf);
+  if (start > bufsz) {
+    except(jni, BOUNDSERR,
+          "bad %s buffer bounds: start %d > buffer size %d", start, bufsz);
+    return (-1);
+  }
+  if (len > bufsz - start) {
+    except(jni, BOUNDSERR,
+          "bad %s buffer bounds: length %d > remaining buffer size %d",
+          len, bufsz - start);
+    return (-1);
+  }
+  return (0);
+}
+
+JNIEXPORT void JNICALL JNIFUNC(send)(JNIEnv *jni, jobject cls,
+                                    jbyteArray buf,
+                                    jint start, jint len,
+                                    wrapper wtrig)
+{
+  struct trigger trig;
+  int rc, maxfd;
   ssize_t n;
+  fd_set rfds, wfds;
   jbyte *p = 0;
 
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn) goto end;
+  if (ensure_state(jni, RUNNING)) goto end;
 
-  bufsz = (*jni)->GetArrayLength(jni, buf);
-  if ((*jni)->ExceptionOccurred(jni)) goto end;
-  if (bufsz < start || bufsz - start < len) {
-    except(jni, "java/lang/IndexOutOfBoundsException",
-          "bad send-buffer bounds");
-    goto end;
-  }
+  if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end;
+  if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
 
-  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+  p = (*jni)->GetByteArrayElements(jni, buf, 0);
   if (!p) goto end;
 
+  maxfd = trig.rfd;
+  if (maxfd < clientsk) maxfd = clientsk;
   while (len) {
-    n = send(conn->fd, p + start, len, 0);
-    if (n < 0) {
-      except_errno(jni, "java/io/IOException", errno);
-      goto end;
+    FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds);
+    FD_ZERO(&wfds); FD_SET(clientsk, &wfds);
+    rc = select(maxfd + 1, &rfds, &wfds, 0, 0); if (rc < 0) goto err;
+    if (FD_ISSET(trig.rfd, &rfds)) break;
+    if (FD_ISSET(clientsk, &wfds)) {
+      n = send(clientsk, p + start, len, 0);
+      if (n >= 0) { start += n; len -= n; }
+      else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err;
     }
-    start += n; len -= n;
   }
+  goto end;
 
+err:
+  except_syserror(jni, SYSERR, errno, "failed to send on connection");
 end:
   if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, JNI_ABORT);
-  if (conn) close_struct(jni, &op);
   return;
 }
 
-JNIEXPORT jint JNICALL Java_uk_org_distorted_tripe_JNI_recv
-       (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf,
-        jint start, jint len)
+JNIEXPORT jint JNICALL JNIFUNC(recv)(JNIEnv *jni, jobject cls,
+                                    jbyteArray buf,
+                                    jint start, jint len,
+                                    wrapper wtrig)
 {
-  struct conn *conn = 0;
-  struct open op;
-  jboolean copyp;
-  jsize bufsz;
+  struct trigger trig;
+  int maxfd;
+  fd_set rfds;
   jbyte *p = 0;
   jint rc = -1;
 
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn) goto end;
-
-  bufsz = (*jni)->GetArrayLength(jni, buf);
-  if ((*jni)->ExceptionOccurred(jni)) goto end;
-  if (bufsz < start || bufsz - start < len) {
-    except(jni, "java/lang/IndexOutOfBoundsException",
-          "bad receive-buffer bounds");
+  lock_tripe(jni);
+  if (clientsk == -1) {
+    except(jni, STERR, "client connection not established");
+    unlock_tripe(jni);
     goto end;
   }
+  unlock_tripe(jni);
+
+  if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end;
+  if (check_buffer_bounds(jni, "send", buf, start, len)) goto end;
 
-  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+  p = (*jni)->GetByteArrayElements(jni, buf, 0);
   if (!p) goto end;
 
-  rc = recv(conn->fd, p + start, len, 0);
-  if (rc < 0) {
-    except_errno(jni, "java/io/IOException", errno);
-    goto end;
+  maxfd = trig.rfd;
+  if (maxfd < clientsk) maxfd = clientsk;
+  for (;;) {
+    FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); FD_SET(clientsk, &rfds);
+    rc = select(maxfd + 1, &rfds, 0, 0, 0); if (rc < 0) goto err;
+    if (FD_ISSET(trig.rfd, &rfds)) {
+      break;
+    }
+    if (FD_ISSET(clientsk, &rfds)) {
+      rc = recv(clientsk, p + start, len, 0);
+      if (rc >= 0) break;
+      else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err;
+    }
   }
   if (!rc) rc = -1;
+  goto end;
 
+err:
+  except_syserror(jni, SYSERR, errno, "failed to read from connection");
 end:
   if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, 0);
-  if (conn) close_struct(jni, &op);
   return (rc);
 }
 
-JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_close
-       (JNIEnv *jni, jobject cls, wrapped wconn, jint how)
-{
-  struct conn *conn = 0;
-  struct open op;
-
-  conn = open_struct(jni, wconn, &conn_type, &op);
-  if (!conn || conn->fd == -1) goto end;
-
-  how &= CF_CLOSEMASK&~conn->f;
-  conn->f |= how;
-fprintf(stderr, ";; closing %u\n", how);
-  if ((conn->f&CF_CLOSEMASK) == CF_CLOSEMASK) {
-    close(conn->fd);
-    conn->fd = -1;
-  } else {
-    if (how&CF_CLOSERD) shutdown(conn->fd, SHUT_RD);
-    if (how&CF_CLOSEWR) shutdown(conn->fd, SHUT_WR);
-  }
-
-end:
-  if (conn) close_struct(jni, &op);
-  return;
-}
+/*----- That's all, folks -------------------------------------------------*/