X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe-android/blobdiff_plain/3a2f1a4ba4cc824e192c9d144b317a215e79c8d6..HEAD:/jni.c
diff --git a/jni.c b/jni.c
index 9f9e44d..3841f35 100644
--- a/jni.c
+++ b/jni.c
@@ -1,286 +1,1771 @@
+/* -*-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 .
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#define _FILE_OFFSET_BITS 64
+
#include
+#include
#include
#include
-#include
+#include
#include
#include
#include
-#include
-
-#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
#include
-#include
+
+#include
+
+//#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#define TUN_INTERNALS
+#include
#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, ";; \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, "", "(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, "",
+ "(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, "", "()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, ©p);
- 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, "", "(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 ("");
+ 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, ©p);
+ 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, ©p);
+ 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 -------------------------------------------------*/