From 31fee237cd55bae6532c5c5db8b82a118cd9efbc Mon Sep 17 00:00:00 2001 From: demon36 Date: Fri, 27 Aug 2021 14:31:47 +0200 Subject: [PATCH] add support for varying width null terminator for (get/put)ZeroTerminatedByteArray() --- jni/jffi/MemoryIO.c | 92 +++++++++++++++++++ src/main/java/com/kenai/jffi/Foreign.java | 56 ++++++++++- src/main/java/com/kenai/jffi/MemoryIO.java | 39 ++++++++ .../java/com/kenai/jffi/UnsafeMemoryIO.java | 6 ++ src/test/java/com/kenai/jffi/MemoryTest.java | 10 +- 5 files changed, 201 insertions(+), 2 deletions(-) diff --git a/jni/jffi/MemoryIO.c b/jni/jffi/MemoryIO.c index 29c78e80..6ebfca28 100644 --- a/jni/jffi/MemoryIO.c +++ b/jni/jffi/MemoryIO.c @@ -144,6 +144,29 @@ getArrayChecked(JNIEnv* env, jlong address, jobject obj, jint offset, jint lengt },); } +static jsize +UTFStrLen(const char* str, jsize maxlen, jint nullTerminatorWidth) +{ + jsize matchingBytesCount = 0; + const char* char_ptr = str; + const char* end_char_ptr = str + maxlen; + while (char_ptr < end_char_ptr) { + if (*char_ptr == '\0') { + matchingBytesCount++; + char_ptr++; + } else { + char_ptr += nullTerminatorWidth - matchingBytesCount;//jump to start of next character + matchingBytesCount = 0; + continue; + } + if (matchingBytesCount == nullTerminatorWidth) { + char_ptr -= nullTerminatorWidth;//trim to the last byte just before null terminator + return char_ptr - str; + } + } + return maxlen; +} + #define UNSAFE(J, N) GET(J, N) PUT(J, N) COPY(J, N) UNSAFE(Byte, jbyte); @@ -347,6 +370,23 @@ Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArray__JI(JNIEnv* env, jobject return bytes; } +/* + * Class: com_kenai_jffi_Foreign + * Method: getZeroTerminatedByteArray + * Signature: (JII)[B + */ +JNIEXPORT jbyteArray JNICALL +Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArray__JII(JNIEnv* env, jobject self, jlong address, jint maxlen, jint nullTerminatorWidth) +{ + const char *str = (const char*) j2p(address); + jsize len = UTFStrLen(str, maxlen, nullTerminatorWidth); + jbyteArray bytes = (*env)->NewByteArray(env, len); + (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *) str); + + return bytes; +} + + JNIEXPORT jbyteArray JNICALL Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JI(JNIEnv* env, jobject self, jlong address, jint maxlen) { @@ -361,6 +401,24 @@ Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JI(JNIEnv* env, j return bytes; } +/* + * Class: com_kenai_jffi_Foreign + * Method: getZeroTerminatedByteArrayChecked + * Signature: (JII)[B + */ +JNIEXPORT jbyteArray JNICALL +Java_com_kenai_jffi_Foreign_getZeroTerminatedByteArrayChecked__JII(JNIEnv* env, jobject self, jlong address, jint maxlen, jint nullTerminatorWidth) +{ + const char *str = (const char*) j2p(address); + jsize len; + + PROT(len = UTFStrLen(str, maxlen, nullTerminatorWidth), NULL); + jbyteArray bytes = (*env)->NewByteArray(env, len); + (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *) str); + + return bytes; +} + /* * Class: com_kenai_jffi_Foreign * Method: putZeroTerminatedByteArray @@ -374,6 +432,24 @@ Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArray(JNIEnv *env, jobject self *((char *) (uintptr_t) address + length) = '\0'; } +/* + * Class: com_kenai_jffi_Foreign + * Method: putZeroTerminatedByteArray + * Signature: (J[BIII)V + */ +JNIEXPORT void JNICALL +Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArray__J_3BIII(JNIEnv *env, jobject self, + jlong address, jbyteArray data, jint offset, jint length, jint nullTerminatorWidth) +{ + (*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address)); + char *str = (char*) j2p(address); + jint i; + for(i = 0; i < nullTerminatorWidth; i++){ + str[address + length + i] = '\0'; + } +} + + JNIEXPORT void JNICALL Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked(JNIEnv *env, jobject self, jlong address, jbyteArray data, jint offset, jint length) @@ -383,6 +459,22 @@ Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked(JNIEnv *env, jobje (*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address)); } +/* + * Class: com_kenai_jffi_Foreign + * Method: putZeroTerminatedByteArrayChecked + * Signature: (J[BIII)V + */ +JNIEXPORT void JNICALL +Java_com_kenai_jffi_Foreign_putZeroTerminatedByteArrayChecked__J_3BIII(JNIEnv *env, jobject self, + jlong address, jbyteArray data, jint offset, jint length, jint nullTerminatorWidth) +{ + char* cp = (char *) (uintptr_t) address; + jint i; + PROT({ *cp = 0; for(i = 0; i < nullTerminatorWidth; i++) cp[address + length + i] = '\0';},); + (*env)->GetByteArrayRegion(env, data, offset, length, (jbyte *)j2p(address)); +} + + /* * Class: com_kenai_jffi_Foreign * Method: allocateMemory diff --git a/src/main/java/com/kenai/jffi/Foreign.java b/src/main/java/com/kenai/jffi/Foreign.java index 6706aacb..20110f78 100644 --- a/src/main/java/com/kenai/jffi/Foreign.java +++ b/src/main/java/com/kenai/jffi/Foreign.java @@ -1425,6 +1425,20 @@ static native void invokePointerParameterArray(long callContext, long functionCo */ static native byte[] getZeroTerminatedByteArray(long address, int maxlen); + /** + * Copies a zero (nul) terminated by array from native memory. + * + * This method will search for a varying size null terminator, starting from address + * and stop once the terminator is encountered. The returned byte array does not + * contain the terminating zero bytes. + * + * @param address The address to copy the array from + * @param maxlen The maximum number of bytes to search for the nul terminator + * @param nullTerminatorWidth size of null terminator in bytes + * @return A byte array containing the bytes copied from native memory. + */ + static native byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth); + /** * Copies a java byte array to native memory and appends a NUL terminating byte. * @@ -1436,6 +1450,19 @@ static native void invokePointerParameterArray(long callContext, long functionCo * @param length The number of bytes to copy to native memory */ static native void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length); + + /** + * Copies a java byte array to native memory and appends a varying width null terminator. + * + * Note A total of length + nullTerminatorWidth bytes is written to native memory. + * + * @param address The address to copy to. + * @param data The byte array to copy to native memory + * @param offset The offset within the byte array to begin copying from + * @param length The number of bytes to copy to native memory + * @param nullTerminatorWidth size of null terminator in bytes + */ + static native void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth); /** * Reads an 8 bit integer from a native memory location. @@ -1757,7 +1784,7 @@ static native void invokePointerParameterArray(long callContext, long functionCo static native byte[] getZeroTerminatedByteArrayChecked(long address); /** - * Copies a zero Checked(nul) terminated by array from native memory. + * Copies a zero Checked(nul) terminated byte array from native memory. * * This method will search for a zero byte, starting from address * and stop once a zero byte is encountered. The returned byte array does not @@ -1769,6 +1796,20 @@ static native void invokePointerParameterArray(long callContext, long functionCo */ static native byte[] getZeroTerminatedByteArrayChecked(long address, int maxlen); + /** + * Copies a zero Checked(nul) terminated byte array from native memory. + * + * This method will search for a varying length null terminator, starting from address + * and stop once the terminator is encountered. The returned byte array does not + * contain the terminating zero bytes. + * + * @param address The address to copy the array from + * @param maxlen The maximum number of bytes to search for the nul terminator + * @param nullTerminatorWidth size of null terminator in bytes + * @return A byte array containing the bytes copied from native memory. + */ + static native byte[] getZeroTerminatedByteArrayChecked(long address, int maxlen, int nullTerminatorWidth); + /** * Copies a java byte array to native memory and appends a NUL terminating byte. * @@ -1781,6 +1822,19 @@ static native void invokePointerParameterArray(long callContext, long functionCo */ static native void putZeroTerminatedByteArrayChecked(long address, byte[] data, int offset, int length); + /** + * Copies a java byte array to native memory and appends a varying width null terminator. + * + * Note A total of length + nullTerminatorWidth bytes is written to native memory. + * + * @param address The address to copy to. + * @param data The byte array to copy to native memory + * @param offset The offset within the byte array to begin copying from + * @param length The number of bytes to copy to native memory + * @param nullTerminatorWidth size of null terminator in bytes + */ + static native void putZeroTerminatedByteArrayChecked(long address, byte[] data, int offset, int length, int nullTerminatorWidth); + /** * Creates a new Direct ByteBuffer for a native memory region. * diff --git a/src/main/java/com/kenai/jffi/MemoryIO.java b/src/main/java/com/kenai/jffi/MemoryIO.java index ddab5e87..729f40df 100644 --- a/src/main/java/com/kenai/jffi/MemoryIO.java +++ b/src/main/java/com/kenai/jffi/MemoryIO.java @@ -522,6 +522,20 @@ public final void freeMemory(long address) { */ public abstract byte[] getZeroTerminatedByteArray(long address, int maxlen); + /** + * Reads a byte array from native memory, stopping when a null terminator is found, + * or the maximum length is reached. + * + * This can be used to read non single byte terminator strings from native memory. + * + * @param address The address to read the data from. + * @param maxlen The limit of the memory area to scan for null terminator. + * @param nullTerminatorWidth size of null terminator in bytes + * @return The byte array containing a copy of the native data. Any zero + * byte is stripped from the end. + */ + public abstract byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth); + @Deprecated public final byte[] getZeroTerminatedByteArray(long address, long maxlen) { return getZeroTerminatedByteArray(address, (int) maxlen); @@ -539,6 +553,19 @@ public final byte[] getZeroTerminatedByteArray(long address, long maxlen) { */ public abstract void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length); + /** + * Copies a java byte array to native memory and appends a varying width NUL terminator. + * + * Note A total of length + 1 bytes is written to native memory. + * + * @param address The address to copy to. + * @param data The byte array to copy to native memory + * @param offset The offset within the byte array to begin copying from + * @param length The number of bytes to copy to native memory + * @param nullTerminatorWidth size of null terminator in bytes + */ + public abstract void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth); + /** * Finds the location of a byte value in a native memory region. * @@ -693,9 +720,15 @@ public final byte[] getZeroTerminatedByteArray(long address) { public final byte[] getZeroTerminatedByteArray(long address, int maxlen) { return Foreign.getZeroTerminatedByteArray(address, maxlen); } + public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) { + return Foreign.getZeroTerminatedByteArray(address, maxlen, nullTerminatorWidth); + } public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) { Foreign.putZeroTerminatedByteArray(address, data, offset, length); } + public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) { + Foreign.putZeroTerminatedByteArray(address, data, offset, length, nullTerminatorWidth); + } } @@ -822,9 +855,15 @@ public final byte[] getZeroTerminatedByteArray(long address) { public final byte[] getZeroTerminatedByteArray(long address, int maxlen) { return Foreign.getZeroTerminatedByteArrayChecked(address, maxlen); } + public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) { + return Foreign.getZeroTerminatedByteArrayChecked(address, maxlen, nullTerminatorWidth); + } public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) { Foreign.putZeroTerminatedByteArrayChecked(address, data, offset, length); } + public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) { + Foreign.putZeroTerminatedByteArrayChecked(address, data, offset, length, nullTerminatorWidth); + } } /** diff --git a/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java b/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java index 7b23d039..52be7f49 100644 --- a/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java +++ b/src/main/java/com/kenai/jffi/UnsafeMemoryIO.java @@ -120,9 +120,15 @@ public final byte[] getZeroTerminatedByteArray(long address) { public final byte[] getZeroTerminatedByteArray(long address, int maxlen) { return Foreign.getZeroTerminatedByteArray(address, maxlen); } + public final byte[] getZeroTerminatedByteArray(long address, int maxlen, int nullTerminatorWidth) { + return Foreign.getZeroTerminatedByteArray(address, maxlen, nullTerminatorWidth); + } public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length) { Foreign.putZeroTerminatedByteArray(address, data, offset, length); } + public final void putZeroTerminatedByteArray(long address, byte[] data, int offset, int length, int nullTerminatorWidth) { + Foreign.putZeroTerminatedByteArray(address, data, offset, length, nullTerminatorWidth); + } /** * A 32 bit optimized implementation of MemoryIO using sun.misc.Unsafe diff --git a/src/test/java/com/kenai/jffi/MemoryTest.java b/src/test/java/com/kenai/jffi/MemoryTest.java index 93f78c0e..b0fe4f43 100644 --- a/src/test/java/com/kenai/jffi/MemoryTest.java +++ b/src/test/java/com/kenai/jffi/MemoryTest.java @@ -7,6 +7,7 @@ import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; +import java.nio.charset.StandardCharsets; public class MemoryTest { @@ -54,7 +55,14 @@ public void tearDown() { byte[] string = MemoryIO.getInstance().getZeroTerminatedByteArray(memory, 4); assertArrayEquals(MAGIC, string); } - + @Test public void zeroTerminatedArrayWithVaryingTerminatorWidth() { + byte[] MAGIC = new String("goodbye").getBytes(StandardCharsets.UTF_16LE); + byte[] nullTerminator = new String("\0").getBytes(StandardCharsets.UTF_16LE); + long memory = MemoryIO.getInstance().allocateMemory(MAGIC.length + nullTerminator.length, true); + MemoryIO.getInstance().putZeroTerminatedByteArray(memory, MAGIC, 0, MAGIC.length, nullTerminator.length); + assertArrayEquals("String not written to native memory", MAGIC, + MemoryIO.getInstance().getZeroTerminatedByteArray(memory, MAGIC.length + nullTerminator.length, nullTerminator.length)); + } @Test public void putZeroTerminatedByteArray() { final byte[] DIRTY = { 'd', 'i', 'r', 't', 'y' }; final byte[] MAGIC = { 't', 'e', 's', 't' };