Initial commit.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 16 May 2018 09:20:55 +0000 (10:20 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 16 May 2018 09:20:55 +0000 (10:20 +0100)
Makefile [new file with mode: 0644]
jni.c [new file with mode: 0644]
jni.java [new file with mode: 0644]
main.scala [new file with mode: 0644]
sock.scala [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..2cf811a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,61 @@
+### -*-makefile-*-
+
+V                       = 0
+v_tag                   = $(call v_tag_$V,$1)
+v_tag_0                         = @printf "  %-8s %s\n" "$1" "$@";
+V_AT                    = $(V_AT_$V)
+V_AT_0                  = @
+
+JDK                     = /usr/lib/jvm/java-8-openjdk-amd64
+JDK_PLAT                = linux
+INCLUDES                = $(JDK)/include $(JDK)/include/$(JDK_PLAT)
+
+CC                      = gcc
+CFLAGS                  = -O0 -g -Wall -fPIC $(addprefix -I,$(INCLUDES))
+
+LD                      = gcc
+LDFLAGS.so              = -shared
+
+JAVAC                   = javac
+JAVAFLAGS               = -d .
+
+SCALAC                  = scalac
+SCALAFLAGS              = -d . -optimise
+
+all::
+.PHONY: all
+
+%.o: %.c
+       $(call v_tag,CC)$(CC) -c $(CFLAGS) -MMD -o$@ $<
+CLEANFILES             += *.o *.d
+
+%.stamp: %.java
+       $(call v_tag,JAVAC)$(JAVAC) $(JAVAFLAGS) $< && echo built >$@
+%.stamp: %.scala
+       $(call v_tag,SCALAC)$(SCALAC) $(SCALAFLAGS) $< && echo built >$@
+CLEANFILES             += *.stamp
+
+objects                         = $(patsubst %.c,%$2,$1)
+
+TARGETS += libtoy.so
+libtoy.so_SOURCES       = jni.c
+libtoy.so: $(call objects,$(libtoy.so_SOURCES),.o)
+       $(call v_tag,LD)$(LD) $(LDFLAGS.so) -o$@ $^
+
+clean::; rm -rf uk/
+
+TARGETS                        += jni.stamp
+
+TARGETS                        += sock.stamp
+sock.stamp: jni.stamp
+
+TARGETS                        += main.stamp
+main.stamp: jni.stamp sock.stamp
+
+all:: $(TARGETS)
+ALLSOURCES             += $(foreach t,$(TARGETS),$($t_SOURCES))
+
+clean::; rm -f $(CLEANFILES) $(TARGETS)
+.PHONY: clean
+
+-include $(call objects,$(ALLSOURCES),.d)
diff --git a/jni.c b/jni.c
new file mode 100644 (file)
index 0000000..9f9e44d
--- /dev/null
+++ b/jni.c
@@ -0,0 +1,286 @@
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <jni.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#undef sun
+
+union align {
+  int i;
+  long l;
+  double d;
+  void *p;
+  void (*f)(void *);
+  struct notexist *s;
+};
+
+struct native_type {
+  const char *name;
+  size_t sz;
+  uint32_t tag;
+};
+
+typedef jbyteArray wrapped;
+
+struct open {
+  wrapped obj;
+  jbyte *arr;
+};
+
+struct base {
+  uint32_t tag;
+};
+
+static void except(JNIEnv *jni, const char *clsname, const char *msg)
+{
+  jclass cls;
+  int rc;
+
+  cls = (*jni)->FindClass(jni, clsname); assert(cls);
+  rc = (*jni)->ThrowNew(jni, cls, msg); assert(!rc);
+}
+
+static void except_errno(JNIEnv *jni, const char *clsname, int err)
+  { except(jni, clsname, strerror(err)); }
+
+static void *open_struct_unchecked(JNIEnv *jni, wrapped obj, struct open *op)
+{
+  jboolean copyp;
+  uintptr_t p, q;
+
+  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));
+}
+
+static void *open_struct(JNIEnv *jni, wrapped obj,
+                        const struct native_type *ty, struct open *op)
+{
+  struct base *p;
+  jsize n;
+
+  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);
+  }
+  return (p);
+}
+
+static wrapped close_struct(JNIEnv *jni, struct open *op)
+{
+  (*jni)->ReleaseByteArrayElements(jni, op->obj, op->arr, 0);
+  return (op->obj);
+}
+
+static void *alloc_struct(JNIEnv *jni, const struct native_type *ty,
+                         struct open *op)
+{
+  wrapped obj;
+  struct base *p;
+
+  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);
+}
+
+JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_test
+       (JNIEnv *jni, jobject cls)
+  { printf("Hello from C!\n"); }
+
+struct toy {
+  struct base _base;
+  const char *p;
+};
+static const struct native_type toy_type =
+       { "toy", sizeof(struct toy), 0x58008918 };
+
+JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_make
+       (JNIEnv *jni, jobject cls)
+{
+  struct open op_toy;
+  struct toy *toy;
+
+  toy = alloc_struct(jni, &toy_type, &op_toy);
+  if (!toy) return (0);
+  toy->p = "A working thing";
+  return (close_struct(jni, &op_toy));
+}
+
+JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_check
+       (JNIEnv *jni, jobject cls, wrapped wtoy)
+{
+  struct toy *toy;
+  struct open op_toy;
+
+  toy = open_struct(jni, wtoy, &toy_type, &op_toy);
+  if (!toy) return;
+  printf("Toy says: %s\n", toy->p);
+  close_struct(jni, &op_toy);
+}
+
+struct conn {
+  struct base _base;
+  int fd;
+  unsigned f;
+#define CF_CLOSERD 1u
+#define CF_CLOSEWR 2u
+#define CF_CLOSEMASK (CF_CLOSERD | CF_CLOSEWR)
+};
+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)
+{
+  struct conn *conn;
+  struct open op;
+  struct sockaddr_un sun;
+  int fd = -1;
+
+  conn = alloc_struct(jni, &conn_type, &op);
+  if (!conn) goto err;
+
+  fd = socket(SOCK_STREAM, PF_UNIX, 0);
+  if (!fd) goto err_except;
+
+  sun.sun_family = AF_UNIX;
+  strcpy(sun.sun_path, "/tmp/mdw/sk");
+  if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err_except;
+
+  conn->fd = fd;
+  return (close_struct(jni, &op));
+
+err_except:
+  except_errno(jni, "java/io/IOException", errno);
+err:
+  if (fd) close(fd);
+  return (0);
+}
+
+JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_send
+       (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf,
+        jint start, jint len)
+{
+  struct conn *conn = 0;
+  struct open op;
+  jboolean copyp;
+  jsize bufsz;
+  ssize_t n;
+  jbyte *p = 0;
+
+  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 send-buffer bounds");
+    goto end;
+  }
+
+  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+  if (!p) goto end;
+
+  while (len) {
+    n = send(conn->fd, p + start, len, 0);
+    if (n < 0) {
+      except_errno(jni, "java/io/IOException", errno);
+      goto end;
+    }
+    start += n; len -= n;
+  }
+
+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)
+{
+  struct conn *conn = 0;
+  struct open op;
+  jboolean copyp;
+  jsize bufsz;
+  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");
+    goto end;
+  }
+
+  p = (*jni)->GetByteArrayElements(jni, buf, &copyp);
+  if (!p) goto end;
+
+  rc = recv(conn->fd, p + start, len, 0);
+  if (rc < 0) {
+    except_errno(jni, "java/io/IOException", errno);
+    goto end;
+  }
+  if (!rc) rc = -1;
+
+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;
+}
diff --git a/jni.java b/jni.java
new file mode 100644 (file)
index 0000000..03630a8
--- /dev/null
+++ b/jni.java
@@ -0,0 +1,22 @@
+package uk.org.distorted.tripe;
+
+class JNI {
+  static { System.loadLibrary("toy"); }
+  static class NativeObjectTypeException extends RuntimeException {
+    NativeObjectTypeException() { super(); }
+    NativeObjectTypeException(String msg) { super(msg); }
+  }
+
+  static final int CF_CLOSERD = 1, CF_CLOSEWR = 2;
+  static final int CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
+
+  static native void test();
+
+  static native Object make();
+  static native void check(Object toy);
+
+  static native Object connect();
+  static native void send(Object conn, byte[] buf, int start, int len);
+  static native int recv(Object conn, byte[] buf, int start, int len);
+  static native void close(Object conn, int how);
+}
diff --git a/main.scala b/main.scala
new file mode 100644 (file)
index 0000000..4560638
--- /dev/null
@@ -0,0 +1,64 @@
+package uk.org.distorted;
+
+import java.io.{InputStreamReader, OutputStreamWriter};
+import scala.collection.mutable.StringBuilder;
+import scala.util.control.Breaks;
+
+package object tripe {
+  def main(args: Array[String])
+  {
+    println("Hello from Scala");
+    JNI.test();
+    val toy = JNI.make();
+    for (i <- 0 until args.length) println(f"$i%2d: ${args(i)}%s");
+    //toy match { case toy: Array[Byte] => toy(1) = -1; case _ => () }
+    JNI.check(toy);
+
+    val conn = new Connection;
+    try {
+      val rd = new InputStreamReader(new ConnectionInputStream(conn));
+      val wr = new OutputStreamWriter(new ConnectionOutputStream(conn));
+
+      wr.write("Hello, world!\n"); wr.flush();
+
+      val buf = new Array[Char](4096);
+      val line = new StringBuilder;
+
+      val R = new Breaks;
+      val L = new Breaks;
+      var any = false;
+      R.breakable {
+       while (true) {
+         val n = rd.read(buf);
+         if (n <= 0) R.break;
+         var pos = 0;
+         L.breakable {
+           while (true) {
+             val nl = buf.indexOf('\n', pos);
+             if (nl == -1 || nl >= n) {
+               if (pos < n)
+                 { line.appendAll(buf, pos, n - pos); any = true; }
+               L.break;
+             }
+             val s = if (!any)
+               new String(buf, pos, nl - pos);
+             else {
+               line.appendAll(buf, pos, nl - pos);
+               val s = line.mkString;
+               line.clear(); any = false;
+               s
+             };
+             println(s"found line `$s'");
+             pos = nl + 1;
+           }
+         }
+       }
+      }
+
+      rd.close();
+      wr.close();
+    } finally {
+      conn.close();
+    }
+  }
+}
diff --git a/sock.scala b/sock.scala
new file mode 100644 (file)
index 0000000..773b0af
--- /dev/null
@@ -0,0 +1,33 @@
+package uk.org.distorted.tripe;
+
+import java.io.{InputStream, OutputStream};
+
+class Connection {
+  val conn = JNI.connect();
+  def close() { JNI.close(conn, JNI.CF_CLOSEMASK); }
+  override protected def finalize() { close(); }
+}
+
+class ConnectionInputStream(val conn: Connection) extends InputStream {
+  override def read(): Int = {
+    val buf = new Array[Byte](1);
+    val n = read(buf, 0, 1);
+    if (n < 0) -1 else buf(0)&0xff;
+  }
+  override def read(buf: Array[Byte]): Int =
+    read(buf, 0, buf.length);
+  override def read(buf: Array[Byte], start: Int, len: Int) =
+    JNI.recv(conn.conn, buf, start, len);
+  override def close() { JNI.close(conn.conn, JNI.CF_CLOSERD); }
+}
+
+class ConnectionOutputStream(val conn: Connection) extends OutputStream {
+  override def write(b: Int) = {
+    val buf = Array[Byte](b.toByte);
+    write(buf, 0, 1);
+  }
+  override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
+  override def write(buf: Array[Byte], start: Int, len: Int) =
+    JNI.send(conn.conn, buf, start, len);
+  override def close() { JNI.close(conn.conn, JNI.CF_CLOSEWR); }
+}