From 3a2f1a4ba4cc824e192c9d144b317a215e79c8d6 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Wed, 16 May 2018 10:20:55 +0100 Subject: [PATCH] Initial commit. --- Makefile | 61 +++++++++++++ jni.c | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jni.java | 22 +++++ main.scala | 64 ++++++++++++++ sock.scala | 33 +++++++ 5 files changed, 466 insertions(+) create mode 100644 Makefile create mode 100644 jni.c create mode 100644 jni.java create mode 100644 main.scala create mode 100644 sock.scala diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..9f9e44d --- /dev/null +++ b/jni.c @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#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, ©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)); +} + +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, ©p); + 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, ©p); + 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 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 index 0000000..4560638 --- /dev/null +++ b/main.scala @@ -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 index 0000000..773b0af --- /dev/null +++ b/sock.scala @@ -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); } +} -- 2.11.0