Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for varying width null terminator for (get/put)ZeroTerminatedByteArray() #112

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions jni/jffi/MemoryIO.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down
56 changes: 55 additions & 1 deletion src/main/java/com/kenai/jffi/Foreign.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>address</code>
* 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.
*
Expand All @@ -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.
*
* <b>Note</b> 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.
Expand Down Expand Up @@ -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 <code>address</code>
* and stop once a zero byte is encountered. The returned byte array does not
Expand All @@ -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 <code>address</code>
* 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.
*
Expand All @@ -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.
*
* <b>Note</b> 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.
*
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/com/kenai/jffi/MemoryIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.
*
* <b>Note</b> 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.
*
Expand Down Expand Up @@ -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);
}

}

Expand Down Expand Up @@ -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);
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/kenai/jffi/UnsafeMemoryIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>MemoryIO</code> using sun.misc.Unsafe
Expand Down
10 changes: 9 additions & 1 deletion src/test/java/com/kenai/jffi/MemoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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' };
Expand Down