blob: 173d79ff9b264c79649cd984c57a6a2526914d9a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains JNI conversions to/from Java types for the types declared
// in lib.h. The other file that contains functions related to the types in
// lib.h is types.go. We need to place the JNI code in a separate file in order
// to use build tags to restrict the compilation to Java and Android.
//
// All "x.extractToJava" methods leave "x" in the same state as "x.free".
// +build java android
// +build cgo
package main
import "unsafe"
// #include <stdlib.h>
// #include "jni_wrapper.h"
// #include "lib.h"
import "C"
// newVBytesFromJava creates a v23_syncbase_Bytes from a jbyteArray.
func newVBytesFromJava(env *C.JNIEnv, array C.jbyteArray) C.v23_syncbase_Bytes {
r := C.v23_syncbase_Bytes{}
n := C.GetArrayLength(env, array)
r.n = C.int(n)
r.p = (*C.uint8_t)(C.malloc(C.size_t(r.n)))
C.GetByteArrayRegion(env, array, 0, n, (*C.jbyte)(unsafe.Pointer(r.p)))
// We don't have to check for exceptions because GetByteArrayRegion can
// only throw ArrayIndexOutOfBoundsException and we know the requested
// amount of elements is valid.
return r
}
// extractToJava constructs a jbyteArray from a v23_syncbase_Bytes. The pointer
// inside v23_syncbase_Bytes will be freed.
func (x *C.v23_syncbase_Bytes) extractToJava(env *C.JNIEnv) C.jbyteArray {
obj := C.NewByteArray(env, C.jsize(x.n))
if C.ExceptionOccurred(env) != nil {
panic("NewByteArray OutOfMemoryError exception")
}
C.SetByteArrayRegion(env, obj, 0, C.jsize(x.n), (*C.jbyte)(unsafe.Pointer(x.p)))
// We don't have to check for exceptions because SetByteArrayRegion can
// only throw ArrayIndexOutOfBoundsException and we know the requested
// amount of elements is valid.
x.free()
return obj
}
// newVIdFromJava creates a v23_syncbase_Id from a jobject.
func newVIdFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_Id {
blessing := C.jstring(C.GetObjectField(env, obj, idClass.blessing))
if C.ExceptionOccurred(env) != nil {
panic("newVIdFromJava exception while retrieving Id.blessing")
}
name := C.jstring(C.GetObjectField(env, obj, idClass.name))
if C.ExceptionOccurred(env) != nil {
panic("newVIdFromJava exception while retrieving Id.name")
}
return C.v23_syncbase_Id{
blessing: newVStringFromJava(env, blessing),
name: newVStringFromJava(env, name),
}
}
// extractToJava constructs a jstring from a v23_syncbase_String. The pointer
// inside v23_syncbase_String will be freed. The code is somewhat complicated
// and inefficient because the NewStringUTF from JNI only works with modified
// UTF-8 strings (inner nulls are encoded as 0xC0, 0x80 and the string is
// terminated with a null).
func (x *C.v23_syncbase_String) extractToJava(env *C.JNIEnv) C.jstring {
n := int(x.n)
srcPtr := uintptr(unsafe.Pointer(x.p))
numNulls := 0
for i := 0; i < n; i++ {
if *(*byte)(unsafe.Pointer(srcPtr + uintptr(i))) == 0 {
numNulls++
}
}
tmp := C.malloc(C.size_t(n + numNulls + 1))
defer C.free(tmp)
tmpPtr := uintptr(tmp)
j := 0
for i := 0; i < n; i, j = i+1, j+1 {
if *(*byte)(unsafe.Pointer(srcPtr + uintptr(i))) != 0 {
*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = *(*byte)(unsafe.Pointer(srcPtr + uintptr(i)))
continue
}
*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0xC0
j++
*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0x80
}
*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0
r := C.NewStringUTF(env, (*C.char)(tmp))
if C.ExceptionOccurred(env) != nil {
panic("NewStringUTF OutOfMemoryError exception")
}
x.free()
return r
}
// newVStringFromJava creates a v23_syncbase_String from a jstring.
func newVStringFromJava(env *C.JNIEnv, s C.jstring) C.v23_syncbase_String {
r := C.v23_syncbase_String{}
if s == nil {
return r
}
// Note that GetStringUTFLength does not include a trailing zero.
n := int(C.GetStringUTFLength(env, s))
r.n = C.int(n)
// TODO(razvanm): The JNI documentation doesn't clearly specify whether
// the string returned by GetStringUTFRegion is null-terminated. What
// I empirically found was that the heap gets corrupted if we allocate
// the exact amount of bytes and the string size is of certain sizes (to
// be more specific, size of the form 24 + 16 * x). Adding a single
// extra byte seems to avoid the issue. I only checked sizes up to 32K.
//
// Some interesting perspective on the JNI's brokenness:
// http://www.club.cc.cmu.edu/~cmccabe/blog_jni_flaws.html
r.p = (*C.char)(C.malloc(C.size_t(r.n + 1)))
p := uintptr(unsafe.Pointer(r.p))
// Note that we need to use GetStringLength and not GetStringUTFLength
// because we need to indicate how many Unicode characters we want to be
// copied.
C.GetStringUTFRegion(env, s, 0, C.GetStringLength(env, s), r.p)
// We don't have to check for exceptions because GetStringUTFRegion can
// only throw StringIndexOutOfBoundsException and we know the requested
// amount of characters is valid.
j := 0
for i := 0; i < n; i, j = i+1, j+1 {
if i+1 < n && *(*byte)(unsafe.Pointer(p + uintptr(i))) == 0xC0 && *(*byte)(unsafe.Pointer(p + uintptr(i+1))) == 0x80 {
*(*byte)(unsafe.Pointer(p + uintptr(j))) = 0
i++
continue
}
if j == i {
continue
}
*(*byte)(unsafe.Pointer(p + uintptr(j))) = *(*byte)(unsafe.Pointer(p + uintptr(i)))
}
r.p = (*C.char)(C.realloc(unsafe.Pointer(r.p), (C.size_t)(j)))
return r
}
// newVSyncgroupMemberInfoFromJava creates a v23_syncbase_SyncgroupMemberInfo
// from a jobject.
func newVSyncgroupMemberInfoFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_SyncgroupMemberInfo {
syncPriority := C.GetIntField(env, obj, syncgroupMemberInfoClass.syncPriority)
if C.ExceptionOccurred(env) != nil {
panic("newVSyncgroupMemberInfoFromJava exception while retrieving SyncgroupMemberInfo.syncPriority")
}
blobDevType := C.GetIntField(env, obj, syncgroupMemberInfoClass.blobDevType)
if C.ExceptionOccurred(env) != nil {
panic("newVSyncgroupMemberInfoFromJava exception while retrieving SyncgroupMemberInfo.blobDevType")
}
return C.v23_syncbase_SyncgroupMemberInfo{
syncPriority: C.uint8_t(syncPriority),
blobDevType: C.uint8_t(blobDevType),
}
}
// newVSyngroupSpecFromJava creates a v23_syncbase_SyncgroupSpec from a jobject.
func newVSyngroupSpecFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_SyncgroupSpec {
description := C.jstring(C.GetObjectField(env, obj, syncgroupSpecClass.description))
if C.ExceptionOccurred(env) != nil {
panic("newVSyngroupSpecFromJava exception while retrieving SyncgroupSpec.description")
}
publishSyncbaseName := C.jstring(C.GetObjectField(env, obj, syncgroupSpecClass.publishSyncbaseName))
if C.ExceptionOccurred(env) != nil {
panic("newVSyngroupSpecFromJava exception while retrieving SyncgroupSpec.publishSyncbaseName")
}
// TODO(razvanm): construct a proper Permissions object based on the
// C.v23_syncbase_Permissions.
collections := C.GetObjectField(env, obj, syncgroupSpecClass.collections)
if C.ExceptionOccurred(env) != nil {
panic("newVSyngroupSpecFromJava exception while retrieving SyncgroupSpec.collections")
}
collectionsIds := newVIdsFromJava(env, collections)
mountTables := C.GetObjectField(env, obj, syncgroupSpecClass.mountTables)
if C.ExceptionOccurred(env) != nil {
panic("newVSyngroupSpecFromJava exception while retrieving SyncgroupSpec.mountTables")
}
mountTablesStrings := newVStringsFromJava(env, mountTables)
return C.v23_syncbase_SyncgroupSpec{
description: newVStringFromJava(env, description),
publishSyncbaseName: newVStringFromJava(env, publishSyncbaseName),
collections: collectionsIds,
mountTables: mountTablesStrings,
}
}