diff --git a/.classpath b/.classpath index dcf4178c..06bdd110 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,6 @@ - diff --git a/jni/Android.mk b/jni/Android.mk index 25109a1f..9488e6e0 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -26,7 +26,10 @@ LOCAL_SRC_FILES:= \ net_sqlcipher_database_SQLiteQuery.cpp \ net_sqlcipher_database_SQLiteStatement.cpp \ net_sqlcipher_CursorWindow.cpp \ - CursorWindow.cpp + CursorWindow.cpp \ + Unicode.cpp \ + net_sqlcipher_AshmemCursorWindow.cpp \ + AshmemCursorWindow.cpp # net_sqlcipher_database_sqlcipher_SQLiteDebug.cpp LOCAL_C_INCLUDES += \ @@ -52,7 +55,7 @@ LOCAL_CFLAGS += -U__APPLE__ LOCAL_LDFLAGS += -L../external/android-libs/$(TARGET_ARCH_ABI) -L../external/libs/$(TARGET_ARCH_ABI)/ # libs from the NDK -LOCAL_LDLIBS += -ldl -llog +LOCAL_LDLIBS += -ldl -llog -lcutils # libnativehelper and libandroid_runtime are included with Android but not the NDK LOCAL_LDLIBS += -lnativehelper -landroid_runtime -lutils -lbinder # these are build in the ../external section diff --git a/jni/Application.mk b/jni/Application.mk index ea9d6e28..68c67a66 100644 --- a/jni/Application.mk +++ b/jni/Application.mk @@ -4,3 +4,4 @@ APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk # fixes this error when building external/android-sqlite/android/sqlite3_android.cpp # icu4c/common/unicode/std_string.h:39:18: error: string: No such file or directory APP_STL := stlport_shared +NDK_TOOLCHAIN_VERSION=4.4.3 diff --git a/jni/AshmemCursorWindow.cpp b/jni/AshmemCursorWindow.cpp new file mode 100644 index 00000000..b750a902 --- /dev/null +++ b/jni/AshmemCursorWindow.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2006-2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "AshmemCursorWindow" + +#include +#include +#include +#include "AshmemCursorWindow.h" + +#include +#include + +#include +#include +#include + +using namespace android; + +namespace sqlcipher { + +AshmemCursorWindow::AshmemCursorWindow(const android::String8& name, int ashmemFd, + void* data, size_t size, bool readOnly) : + mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) { + mHeader = static_cast(mData); +} + +AshmemCursorWindow::~AshmemCursorWindow() { + ::munmap(mData, mSize); + ::close(mAshmemFd); +} + +status_t AshmemCursorWindow::create(const android::String8& name, size_t size, AshmemCursorWindow** outAshmemCursorWindow) { + android::String8 ashmemName("AshmemCursorWindow: "); + ashmemName.append(name); + + status_t result; + int ashmemFd = ashmem_create_region(ashmemName.string(), size); + if (ashmemFd < 0) { + result = -errno; + } else { + result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE); + if (result >= 0) { + void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); + if (data == MAP_FAILED) { + result = -errno; + } else { + result = ashmem_set_prot_region(ashmemFd, PROT_READ); + if (result >= 0) { + AshmemCursorWindow* window = new AshmemCursorWindow(name, ashmemFd, + data, size, false /*readOnly*/); + result = window->clear(); + if (!result) { + LOG_WINDOW("Created new AshmemCursorWindow: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%d, mData=%p", + window->mHeader->freeOffset, + window->mHeader->numRows, + window->mHeader->numColumns, + window->mSize, window->mData); + *outAshmemCursorWindow = window; + return OK; + } + delete window; + } + } + ::munmap(data, size); + } + ::close(ashmemFd); + } + *outAshmemCursorWindow = NULL; + return result; +} + +status_t AshmemCursorWindow::createFromParcel(Parcel* parcel, AshmemCursorWindow** outCursorWindow) { + android::String8 name = parcel->readString8(); + + status_t result; + int ashmemFd = parcel->readFileDescriptor(); + if (ashmemFd == int(BAD_TYPE)) { + result = BAD_TYPE; + } else { + ssize_t size = ashmem_get_size_region(ashmemFd); + if (size < 0) { + result = UNKNOWN_ERROR; + } else { + int dupAshmemFd = ::dup(ashmemFd); + if (dupAshmemFd < 0) { + result = -errno; + } else { + void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0); + if (data == MAP_FAILED) { + result = -errno; + } else { + AshmemCursorWindow* window = new AshmemCursorWindow(name, dupAshmemFd, + data, size, true /*readOnly*/); + LOG_WINDOW("Created AshmemCursorWindow from parcel: freeOffset=%d, " + "numRows=%d, numColumns=%d, mSize=%d, mData=%p", + window->mHeader->freeOffset, + window->mHeader->numRows, + window->mHeader->numColumns, + window->mSize, window->mData); + *outCursorWindow = window; + return OK; + } + ::close(dupAshmemFd); + } + } + } + *outCursorWindow = NULL; + return result; +} + +status_t AshmemCursorWindow::writeToParcel(Parcel* parcel) { + status_t status = parcel->writeString8(mName); + if (!status) { + status = parcel->writeDupFileDescriptor(mAshmemFd); + } + return status; +} + +status_t AshmemCursorWindow::clear() { + if (mReadOnly) { + return INVALID_OPERATION; + } + + mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk); + mHeader->firstChunkOffset = sizeof(Header); + mHeader->numRows = 0; + mHeader->numColumns = 0; + + RowSlotChunk* firstChunk = static_cast(offsetToPtr(mHeader->firstChunkOffset)); + firstChunk->nextChunkOffset = 0; + return OK; +} + +status_t AshmemCursorWindow::setNumColumns(uint32_t numColumns) { + if (mReadOnly) { + return INVALID_OPERATION; + } + + uint32_t cur = mHeader->numColumns; + if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) { + LOG_WINDOW("Trying to go from %d columns to %d", cur, numColumns); + return INVALID_OPERATION; + } + mHeader->numColumns = numColumns; + return OK; +} + +status_t AshmemCursorWindow::allocRow() { + if (mReadOnly) { + return INVALID_OPERATION; + } + + // Fill in the row slot + RowSlot* rowSlot = allocRowSlot(); + if (rowSlot == NULL) { + return NO_MEMORY; + } + + // Allocate the slots for the field directory + size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot); + uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/); + if (!fieldDirOffset) { + mHeader->numRows--; + LOG_WINDOW("The row failed, so back out the new row accounting " + "from allocRowSlot %d", mHeader->numRows); + return NO_MEMORY; + } + FieldSlot* fieldDir = static_cast(offsetToPtr(fieldDirOffset)); + memset(fieldDir, 0, fieldDirSize); + + LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", + mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset); + rowSlot->offset = fieldDirOffset; + return OK; +} + +status_t AshmemCursorWindow::freeLastRow() { + if (mReadOnly) { + return INVALID_OPERATION; + } + + if (mHeader->numRows > 0) { + mHeader->numRows--; + } + return OK; +} + +uint32_t AshmemCursorWindow::alloc(size_t size, bool aligned) { + uint32_t padding; + if (aligned) { + // 4 byte alignment + padding = (~mHeader->freeOffset + 1) & 3; + } else { + padding = 0; + } + + uint32_t offset = mHeader->freeOffset + padding; + uint32_t nextFreeOffset = offset + size; + if (nextFreeOffset > mSize) { + LOG_WINDOW("Window is full: requested allocation %d bytes, " + "free space %d bytes, window size %d bytes", + size, freeSpace(), mSize); + return 0; + } + + mHeader->freeOffset = nextFreeOffset; + return offset; +} + +AshmemCursorWindow::RowSlot* AshmemCursorWindow::getRowSlot(uint32_t row) { + uint32_t chunkPos = row; + RowSlotChunk* chunk = static_cast( + offsetToPtr(mHeader->firstChunkOffset)); + while (chunkPos >= NEW_ROW_SLOT_CHUNK_NUM_ROWS) { + chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); + chunkPos -= NEW_ROW_SLOT_CHUNK_NUM_ROWS; + } + return &chunk->slots[chunkPos]; +} + +AshmemCursorWindow::RowSlot* AshmemCursorWindow::allocRowSlot() { + uint32_t chunkPos = mHeader->numRows; + RowSlotChunk* chunk = static_cast( + offsetToPtr(mHeader->firstChunkOffset)); + while (chunkPos > NEW_ROW_SLOT_CHUNK_NUM_ROWS) { + chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); + chunkPos -= NEW_ROW_SLOT_CHUNK_NUM_ROWS; + } + if (chunkPos == NEW_ROW_SLOT_CHUNK_NUM_ROWS) { + if (!chunk->nextChunkOffset) { + chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/); + if (!chunk->nextChunkOffset) { + return NULL; + } + } + chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); + chunk->nextChunkOffset = 0; + chunkPos = 0; + } + mHeader->numRows += 1; + return &chunk->slots[chunkPos]; +} + +AshmemCursorWindow::FieldSlot* AshmemCursorWindow::getFieldSlot(uint32_t row, uint32_t column) { + if (row >= mHeader->numRows || column >= mHeader->numColumns) { + LOG_WINDOW("Failed to read row %d, column %d from a AshmemCursorWindow which " + "has %d rows, %d columns.", + row, column, mHeader->numRows, mHeader->numColumns); + return NULL; + } + RowSlot* rowSlot = getRowSlot(row); + if (!rowSlot) { + LOG_WINDOW("Failed to find rowSlot for row %d.", row); + return NULL; + } + FieldSlot* fieldDir = static_cast(offsetToPtr(rowSlot->offset)); + return &fieldDir[column]; +} + +status_t AshmemCursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) { + return putBlobOrString(row, column, value, size, NEW_FIELD_TYPE_BLOB); +} + +status_t AshmemCursorWindow::putString(uint32_t row, uint32_t column, const char* value, + size_t sizeIncludingNull) { + return putBlobOrString(row, column, value, sizeIncludingNull, NEW_FIELD_TYPE_STRING); +} + +status_t AshmemCursorWindow::putBlobOrString(uint32_t row, uint32_t column, + const void* value, size_t size, int32_t type) { + if (mReadOnly) { + return INVALID_OPERATION; + } + + FieldSlot* fieldSlot = getFieldSlot(row, column); + if (!fieldSlot) { + return BAD_VALUE; + } + + uint32_t offset = alloc(size); + if (!offset) { + return NO_MEMORY; + } + + memcpy(offsetToPtr(offset), value, size); + + fieldSlot->type = type; + fieldSlot->data.buffer.offset = offset; + fieldSlot->data.buffer.size = size; + return OK; +} + +status_t AshmemCursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) { + if (mReadOnly) { + return INVALID_OPERATION; + } + + FieldSlot* fieldSlot = getFieldSlot(row, column); + if (!fieldSlot) { + return BAD_VALUE; + } + + fieldSlot->type = NEW_FIELD_TYPE_INTEGER; + fieldSlot->data.l = value; + return OK; +} + +status_t AshmemCursorWindow::putDouble(uint32_t row, uint32_t column, double value) { + if (mReadOnly) { + return INVALID_OPERATION; + } + + FieldSlot* fieldSlot = getFieldSlot(row, column); + if (!fieldSlot) { + return BAD_VALUE; + } + + fieldSlot->type = NEW_FIELD_TYPE_FLOAT; + fieldSlot->data.d = value; + return OK; +} + +status_t AshmemCursorWindow::putNull(uint32_t row, uint32_t column) { + if (mReadOnly) { + return INVALID_OPERATION; + } + + FieldSlot* fieldSlot = getFieldSlot(row, column); + if (!fieldSlot) { + return BAD_VALUE; + } + + fieldSlot->type = NEW_FIELD_TYPE_NULL; + fieldSlot->data.buffer.offset = 0; + fieldSlot->data.buffer.size = 0; + return OK; +} + +}; // namespace android diff --git a/jni/AshmemCursorWindow.h b/jni/AshmemCursorWindow.h new file mode 100644 index 00000000..f0899318 --- /dev/null +++ b/jni/AshmemCursorWindow.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID__DATABASE_NEWWINDOW_H +#define _ANDROID__DATABASE_NEWWINDOW_H + +#include + +#include +#include +#include + +#include +#include + +#if LOG_NDEBUG + +#define IF_LOG_WINDOW() if (false) +#define LOG_WINDOW(...) + +#else + +#define IF_LOG_WINDOW() IF_LOG(LOG_DEBUG, "AshmemCursorWindow") +#define LOG_WINDOW(...) LOG(LOG_DEBUG, "AshmemCursorWindow", __VA_ARGS__) + +#endif + +using namespace android; + +namespace sqlcipher { + +/** + * This class stores a set of rows from a database in a buffer. The begining of the + * window has first chunk of RowSlots, which are offsets to the row directory, followed by + * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case + * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a + * FieldSlot per column, which has the size, offset, and type of the data for that field. + * Note that the data types come from sqlite3.h. + * + * Strings are stored in UTF-8. + */ +class AshmemCursorWindow { + AshmemCursorWindow(const String8& name, int ashmemFd, + void* data, size_t size, bool readOnly); + +public: + /* Field types. */ + enum { + NEW_FIELD_TYPE_NULL = 0, + NEW_FIELD_TYPE_INTEGER = 1, + NEW_FIELD_TYPE_FLOAT = 2, + NEW_FIELD_TYPE_STRING = 3, + NEW_FIELD_TYPE_BLOB = 4, + }; + + /* Opaque type that describes a field slot. */ + struct FieldSlot { + private: + int32_t type; + union { + double d; + int64_t l; + struct { + uint32_t offset; + uint32_t size; + } buffer; + } data; + + friend class AshmemCursorWindow; + } __attribute((packed)); + + ~AshmemCursorWindow(); + + static status_t create(const String8& name, size_t size, AshmemCursorWindow** outCursorWindow); + static status_t createFromParcel(Parcel* parcel, AshmemCursorWindow** outCursorWindow); + + status_t writeToParcel(Parcel* parcel); + + inline String8 name() { return mName; } + inline size_t size() { return mSize; } + inline size_t freeSpace() { return mSize - mHeader->freeOffset; } + inline uint32_t getNumRows() { return mHeader->numRows; } + inline uint32_t getNumColumns() { return mHeader->numColumns; } + + status_t clear(); + status_t setNumColumns(uint32_t numColumns); + + /** + * Allocate a row slot and its directory. + * The row is initialized will null entries for each field. + */ + status_t allocRow(); + status_t freeLastRow(); + + status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size); + status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull); + status_t putLong(uint32_t row, uint32_t column, int64_t value); + status_t putDouble(uint32_t row, uint32_t column, double value); + status_t putNull(uint32_t row, uint32_t column); + + /** + * Gets the field slot at the specified row and column. + * Returns null if the requested row or column is not in the window. + */ + FieldSlot* getFieldSlot(uint32_t row, uint32_t column); + + inline int32_t getFieldSlotType(FieldSlot* fieldSlot) { + return fieldSlot->type; + } + + inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) { + return fieldSlot->data.l; + } + + inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) { + return fieldSlot->data.d; + } + + inline const char* getFieldSlotValueString(FieldSlot* fieldSlot, + size_t* outSizeIncludingNull) { + *outSizeIncludingNull = fieldSlot->data.buffer.size; + return static_cast(offsetToPtr(fieldSlot->data.buffer.offset)); + } + + inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) { + *outSize = fieldSlot->data.buffer.size; + return offsetToPtr(fieldSlot->data.buffer.offset); + } + +private: + static const size_t NEW_ROW_SLOT_CHUNK_NUM_ROWS = 100; + + struct Header { + // Offset of the lowest unused byte in the window. + uint32_t freeOffset; + + // Offset of the first row slot chunk. + uint32_t firstChunkOffset; + + uint32_t numRows; + uint32_t numColumns; + }; + + struct RowSlot { + uint32_t offset; + }; + + struct RowSlotChunk { + RowSlot slots[NEW_ROW_SLOT_CHUNK_NUM_ROWS]; + uint32_t nextChunkOffset; + }; + + String8 mName; + int mAshmemFd; + void* mData; + size_t mSize; + bool mReadOnly; + Header* mHeader; + + inline void* offsetToPtr(uint32_t offset) { + return static_cast(mData) + offset; + } + + inline uint32_t offsetFromPtr(void* ptr) { + return static_cast(ptr) - static_cast(mData); + } + + /** + * Allocate a portion of the window. Returns the offset + * of the allocation, or 0 if there isn't enough space. + * If aligned is true, the allocation gets 4 byte alignment. + */ + uint32_t alloc(size_t size, bool aligned = false); + + RowSlot* getRowSlot(uint32_t row); + RowSlot* allocRowSlot(); + + status_t putBlobOrString(uint32_t row, uint32_t column, + const void* value, size_t size, int32_t type); +}; + +}; // namespace android + +#endif diff --git a/jni/Unicode.cpp b/jni/Unicode.cpp new file mode 100644 index 00000000..711a7957 --- /dev/null +++ b/jni/Unicode.cpp @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Unicode.h" + +#include + +#ifdef HAVE_WINSOCK +# undef nhtol +# undef htonl +# undef nhtos +# undef htons + +# ifdef HAVE_LITTLE_ENDIAN +# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) +# define htonl(x) ntohl(x) +# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) ) +# define htons(x) ntohs(x) +# else +# define ntohl(x) (x) +# define htonl(x) (x) +# define ntohs(x) (x) +# define htons(x) (x) +# endif +#else +# include +#endif + +extern "C" { + +static const char32_t kByteMask = 0x000000BF; +static const char32_t kByteMark = 0x00000080; + +// Surrogates aren't valid for UTF-32 characters, so define some +// constants that will let us screen them out. +static const char32_t kUnicodeSurrogateHighStart = 0x0000D800; +static const char32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; +static const char32_t kUnicodeSurrogateLowStart = 0x0000DC00; +static const char32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; +static const char32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; +static const char32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; +static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF; + +// Mask used to set appropriate bits in first byte of UTF-8 sequence, +// indexed by number of bytes in the sequence. +// 0xxxxxxx +// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000 +// 110yyyyx 10xxxxxx +// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0 +// 1110yyyy 10yxxxxx 10xxxxxx +// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0 +// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx +// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0 +static const char32_t kFirstByteMark[] = { + 0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0 +}; + +// -------------------------------------------------------------------------- +// UTF-32 +// -------------------------------------------------------------------------- + +/** + * Return number of UTF-8 bytes required for the character. If the character + * is invalid, return size of 0. + */ +static inline size_t utf32_codepoint_utf8_length(char32_t srcChar) +{ + // Figure out how many bytes the result will require. + if (srcChar < 0x00000080) { + return 1; + } else if (srcChar < 0x00000800) { + return 2; + } else if (srcChar < 0x00010000) { + if ((srcChar < kUnicodeSurrogateStart) || (srcChar > kUnicodeSurrogateEnd)) { + return 3; + } else { + // Surrogates are invalid UTF-32 characters. + return 0; + } + } + // Max code point for Unicode is 0x0010FFFF. + else if (srcChar <= kUnicodeMaxCodepoint) { + return 4; + } else { + // Invalid UTF-32 character. + return 0; + } +} + +// Write out the source character to . + +static inline void utf32_codepoint_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes) +{ + dstP += bytes; + switch (bytes) + { /* note: everything falls through. */ + case 4: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 3: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 2: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 1: *--dstP = (uint8_t)(srcChar | kFirstByteMark[bytes]); + } +} + +size_t strlen32(const char32_t *s) +{ + const char32_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + +size_t strnlen32(const char32_t *s, size_t maxlen) +{ + const char32_t *ss = s; + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +static inline int32_t utf32_at_internal(const char* cur, size_t *num_read) +{ + const char first_char = *cur; + if ((first_char & 0x80) == 0) { // ASCII + *num_read = 1; + return *cur; + } + cur++; + char32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = first_char; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80; + (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + to_ignore_mask |= mask; + utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1))); + + *num_read = num_to_read; + return static_cast(utf32); +} + +int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index) +{ + if (index >= src_len) { + return -1; + } + size_t dummy_index; + if (next_index == NULL) { + next_index = &dummy_index; + } + size_t num_read; + int32_t ret = utf32_at_internal(src + index, &num_read); + if (ret >= 0) { + *next_index = index + num_read; + } + + return ret; +} + +ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return -1; + } + + size_t ret = 0; + const char32_t *end = src + src_len; + while (src < end) { + ret += utf32_codepoint_utf8_length(*src++); + } + return ret; +} + +void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char32_t *cur_utf32 = src; + const char32_t *end_utf32 = src + src_len; + char *cur = dst; + while (cur_utf32 < end_utf32) { + size_t len = utf32_codepoint_utf8_length(*cur_utf32); + utf32_codepoint_to_utf8((uint8_t *)cur, *cur_utf32++, len); + cur += len; + } + *cur = '\0'; +} + +// -------------------------------------------------------------------------- +// UTF-16 +// -------------------------------------------------------------------------- + +int strcmp16(const char16_t *s1, const char16_t *s2) +{ + char16_t ch; + int d = 0; + + while ( 1 ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +int strncmp16(const char16_t *s1, const char16_t *s2, size_t n) +{ + char16_t ch; + int d = 0; + + while ( n-- ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +char16_t *strcpy16(char16_t *dst, const char16_t *src) +{ + char16_t *q = dst; + const char16_t *p = src; + char16_t ch; + + do { + *q++ = ch = *p++; + } while ( ch ); + + return dst; +} + +size_t strlen16(const char16_t *s) +{ + const char16_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + + +char16_t *strncpy16(char16_t *dst, const char16_t *src, size_t n) +{ + char16_t *q = dst; + const char16_t *p = src; + char ch; + + while (n) { + n--; + *q++ = ch = *p++; + if ( !ch ) + break; + } + + *q = 0; + + return dst; +} + +size_t strnlen16(const char16_t *s, size_t maxlen) +{ + const char16_t *ss = s; + + /* Important: the maxlen test must precede the reference through ss; + since the byte beyond the maximum may segfault */ + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2) +{ + const char16_t* e1 = s1+n1; + const char16_t* e2 = s2+n2; + + while (s1 < e1 && s2 < e2) { + const int d = (int)*s1++ - (int)*s2++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)*s2) + : (n1 > n2 + ? ((int)*s1 - 0) + : 0); +} + +int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2) +{ + const char16_t* e1 = s1H+n1; + const char16_t* e2 = s2N+n2; + + while (s1H < e1 && s2N < e2) { + const char16_t c2 = ntohs(*s2N); + const int d = (int)*s1H++ - (int)c2; + s2N++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)ntohs(*s2N)) + : (n1 > n2 + ? ((int)*s1H - 0) + : 0); +} + +void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char16_t* cur_utf16 = src; + const char16_t* const end_utf16 = src + src_len; + char *cur = dst; + while (cur_utf16 < end_utf16) { + char32_t utf32; + // surrogate pairs + if ((*cur_utf16 & 0xFC00) == 0xD800) { + utf32 = (*cur_utf16++ - 0xD800) << 10; + utf32 |= *cur_utf16++ - 0xDC00; + utf32 += 0x10000; + } else { + utf32 = (char32_t) *cur_utf16++; + } + const size_t len = utf32_codepoint_utf8_length(utf32); + utf32_codepoint_to_utf8((uint8_t*)cur, utf32, len); + cur += len; + } + *cur = '\0'; +} + +// -------------------------------------------------------------------------- +// UTF-8 +// -------------------------------------------------------------------------- + +ssize_t utf8_length(const char *src) +{ + const char *cur = src; + size_t ret = 0; + while (*cur != '\0') { + const char first_char = *cur++; + if ((first_char & 0x80) == 0) { // ASCII + ret += 1; + continue; + } + // (UTF-8's character must not be like 10xxxxxx, + // but 110xxxxx, 1110xxxx, ... or 1111110x) + if ((first_char & 0x40) == 0) { + return -1; + } + + int32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = 0; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80; + num_to_read < 5 && (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx + return -1; + } + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + // "first_char" must be (110xxxxx - 11110xxx) + if (num_to_read == 5) { + return -1; + } + to_ignore_mask |= mask; + utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1)); + if (utf32 > kUnicodeMaxCodepoint) { + return -1; + } + + ret += num_to_read; + } + return ret; +} + +ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return -1; + } + + size_t ret = 0; + const char16_t* const end = src + src_len; + while (src < end) { + if ((*src & 0xFC00) == 0xD800 && (src + 1) < end + && (*++src & 0xFC00) == 0xDC00) { + // surrogate pairs are always 4 bytes. + ret += 4; + src++; + } else { + ret += utf32_codepoint_utf8_length((char32_t) *src++); + } + } + return ret; +} + +/** + * Returns 1-4 based on the number of leading bits. + * + * 1111 -> 4 + * 1110 -> 3 + * 110x -> 2 + * 10xx -> 1 + * 0xxx -> 1 + */ +static inline size_t utf8_codepoint_len(uint8_t ch) +{ + return ((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1; +} + +static inline void utf8_shift_and_mask(uint32_t* codePoint, const uint8_t byte) +{ + *codePoint <<= 6; + *codePoint |= 0x3F & byte; +} + +size_t utf8_to_utf32_length(const char *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return 0; + } + size_t ret = 0; + const char* cur; + const char* end; + size_t num_to_skip; + for (cur = src, end = src + src_len, num_to_skip = 1; + cur < end; + cur += num_to_skip, ret++) { + const char first_char = *cur; + num_to_skip = 1; + if ((first_char & 0x80) == 0) { // ASCII + continue; + } + int32_t mask; + + for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) { + } + } + return ret; +} + +void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char* cur = src; + const char* const end = src + src_len; + char32_t* cur_utf32 = dst; + while (cur < end) { + size_t num_read; + *cur_utf32++ = static_cast(utf32_at_internal(cur, &num_read)); + cur += num_read; + } + *cur_utf32 = 0; +} + +static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length) +{ + uint32_t unicode; + + switch (length) + { + case 1: + return src[0]; + case 2: + unicode = src[0] & 0x1f; + utf8_shift_and_mask(&unicode, src[1]); + return unicode; + case 3: + unicode = src[0] & 0x0f; + utf8_shift_and_mask(&unicode, src[1]); + utf8_shift_and_mask(&unicode, src[2]); + return unicode; + case 4: + unicode = src[0] & 0x07; + utf8_shift_and_mask(&unicode, src[1]); + utf8_shift_and_mask(&unicode, src[2]); + utf8_shift_and_mask(&unicode, src[3]); + return unicode; + default: + return 0xffff; + } + + //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result); +} + +ssize_t utf8_to_utf16_length(const uint8_t* u8str, size_t u8len) +{ + const uint8_t* const u8end = u8str + u8len; + const uint8_t* u8cur = u8str; + + /* Validate that the UTF-8 is the correct len */ + size_t u16measuredLen = 0; + while (u8cur < u8end) { + u16measuredLen++; + int u8charLen = utf8_codepoint_len(*u8cur); + uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8charLen); + if (codepoint > 0xFFFF) u16measuredLen++; // this will be a surrogate pair in utf16 + u8cur += u8charLen; + } + + /** + * Make sure that we ended where we thought we would and the output UTF-16 + * will be exactly how long we were told it would be. + */ + if (u8cur != u8end) { + return -1; + } + + return u16measuredLen; +} + +char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* u8str, size_t u8len, char16_t* u16str) +{ + const uint8_t* const u8end = u8str + u8len; + const uint8_t* u8cur = u8str; + char16_t* u16cur = u16str; + + while (u8cur < u8end) { + size_t u8len = utf8_codepoint_len(*u8cur); + uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8len); + + // Convert the UTF32 codepoint to one or more UTF16 codepoints + if (codepoint <= 0xFFFF) { + // Single UTF16 character + *u16cur++ = (char16_t) codepoint; + } else { + // Multiple UTF16 characters with surrogates + codepoint = codepoint - 0x10000; + *u16cur++ = (char16_t) ((codepoint >> 10) + 0xD800); + *u16cur++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00); + } + + u8cur += u8len; + } + return u16cur; +} + +void utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str) { + char16_t* end = utf8_to_utf16_no_null_terminator(u8str, u8len, u16str); + *end = 0; +} + +} diff --git a/jni/Unicode.h b/jni/Unicode.h new file mode 100644 index 00000000..5d6a29f7 --- /dev/null +++ b/jni/Unicode.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_UNICODE_H +#define ANDROID_UNICODE_H + +#include +#include + +extern "C" { + +typedef uint32_t char32_t; +typedef uint16_t char16_t; + +// Standard string functions on char16_t strings. +int strcmp16(const char16_t *, const char16_t *); +int strncmp16(const char16_t *s1, const char16_t *s2, size_t n); +size_t strlen16(const char16_t *); +size_t strnlen16(const char16_t *, size_t); +char16_t *strcpy16(char16_t *, const char16_t *); +char16_t *strncpy16(char16_t *, const char16_t *, size_t); + +// Version of comparison that supports embedded nulls. +// This is different than strncmp() because we don't stop +// at a nul character and consider the strings to be different +// if the lengths are different (thus we need to supply the +// lengths of both strings). This can also be used when +// your string is not nul-terminated as it will have the +// equivalent result as strcmp16 (unlike strncmp16). +int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2); + +// Version of strzcmp16 for comparing strings in different endianness. +int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2); + +// Standard string functions on char32_t strings. +size_t strlen32(const char32_t *); +size_t strnlen32(const char32_t *, size_t); + +/** + * Measure the length of a UTF-32 string in UTF-8. If the string is invalid + * such as containing a surrogate character, -1 will be returned. + */ +ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len); + +/** + * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not + * large enough to store the string, the part of the "src" string is stored + * into "dst" as much as possible. See the examples for more detail. + * Returns the size actually used for storing the string. + * dst" is not null-terminated when dst_len is fully used (like strncpy). + * + * Example 1 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" >= 7 + * -> + * Returned value == 6 + * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0 + * (note that "dst" is null-terminated) + * + * Example 2 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" == 5 + * -> + * Returned value == 3 + * "dst" becomes \xE3\x81\x82\0 + * (note that "dst" is null-terminated, but \u3044 is not stored in "dst" + * since "dst" does not have enough size to store the character) + * + * Example 3 + * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84) + * "src_len" == 2 + * "dst_len" == 6 + * -> + * Returned value == 6 + * "dst" becomes \xE3\x81\x82\xE3\x81\x84 + * (note that "dst" is NOT null-terminated, like strncpy) + */ +//void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst); + +/** + * Returns the unicode value at "index". + * Returns -1 when the index is invalid (equals to or more than "src_len"). + * If returned value is positive, it is able to be converted to char32_t, which + * is unsigned. Then, if "next_index" is not NULL, the next index to be used is + * stored in "next_index". "next_index" can be NULL. + */ +int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index); + + +/** + * Returns the UTF-8 length of UTF-16 string "src". + */ +ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len); + +/** + * Converts a UTF-16 string to UTF-8. The destination buffer must be large + * enough to fit the UTF-16 as measured by utf16_to_utf8_length with an added + * NULL terminator. + */ +//void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst); + +/** + * Returns the length of "src" when "src" is valid UTF-8 string. + * Returns 0 if src is NULL or 0-length string. Returns -1 when the source + * is an invalid string. + * + * This function should be used to determine whether "src" is valid UTF-8 + * characters with valid unicode codepoints. "src" must be null-terminated. + * + * If you are going to use other utf8_to_... functions defined in this header + * with string which may not be valid UTF-8 with valid codepoint (form 0 to + * 0x10FFFF), you should use this function before calling others, since the + * other functions do not check whether the string is valid UTF-8 or not. + * + * If you do not care whether "src" is valid UTF-8 or not, you should use + * strlen() as usual, which should be much faster. + */ +//ssize_t utf8_length(const char *src); + +/** + * Measure the length of a UTF-32 string. + */ +size_t utf8_to_utf32_length(const char *src, size_t src_len); + +/** + * Stores a UTF-32 string converted from "src" in "dst". "dst" must be large + * enough to store the entire converted string as measured by + * utf8_to_utf32_length plus space for a NULL terminator. + */ +//void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst); + +/** + * Returns the UTF-16 length of UTF-8 string "src". + */ +ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen); + +/** + * Convert UTF-8 to UTF-16 including surrogate pairs. + * Returns a pointer to the end of the string (where a null terminator might go + * if you wanted to add one). + */ +char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* src, size_t srcLen, char16_t* dst); + +} + +#endif diff --git a/jni/net_sqlcipher_AshmemCursorWindow.cpp b/jni/net_sqlcipher_AshmemCursorWindow.cpp new file mode 100644 index 00000000..bf4e4255 --- /dev/null +++ b/jni/net_sqlcipher_AshmemCursorWindow.cpp @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "AshmemCursorWindow" + +#include +#include +#include + +#include +//#include +#include + +#include +#include +#include + +#include "AshmemCursorWindow.h" +#include "Unicode.h" +#include "android_util_Binder.h" +#include "sqlite3_exception.h" + +namespace sqlcipher { + +static struct { + jfieldID data; + jfieldID sizeCopied; +} gCharArrayBufferClassInfo; + +static jstring gEmptyString; + +static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) { + android::String8 msg; + char *str = new char[100]; + sprintf(str, "Couldn't read row %d, col %d from AshmemCursorWindow. " + "Make sure the Cursor is initialized correctly before accessing data from it.", row, column); + //msg.appendFormat("Couldn't read row %d, col %d from AshmemCursorWindow. " + // "Make sure the Cursor is initialized correctly before accessing data from it.", + // row, column); + msg.append(str); + free(str); + jniThrowException(env, "java/lang/IllegalStateException", msg.string()); +} + +static void throwUnknownTypeException(JNIEnv * env, jint type) { + android::String8 msg; + char *str = new char[100]; + sprintf(str, "UNKNOWN type %d", type); + msg.append(str); + free(str); + //msg.appendFormat("UNKNOWN type %d", type); + jniThrowException(env, "java/lang/IllegalStateException", msg.string()); +} + +static jint nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) { + android::String8 name; + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); + name.setTo(nameStr); + env->ReleaseStringUTFChars(nameObj, nameStr); + + AshmemCursorWindow* window; + status_t status = AshmemCursorWindow::create(name, cursorWindowSize, &window); + if (status || !window) { + LOG_WINDOW("Could not allocate AshmemCursorWindow '%s' of size %d due to error %d.", + name.string(), cursorWindowSize, status); + return 0; + } + + LOG_WINDOW("nativeInitializeEmpty: window = %p", window); + return reinterpret_cast(window); +} + +static jint nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + + AshmemCursorWindow* window; + status_t status = AshmemCursorWindow::createFromParcel(parcel, &window); + if (status || !window) { + LOG_WINDOW("Could not create AshmemCursorWindow from Parcel due to error %d.", status); + return 0; + } + + LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p", + window->getNumRows(), window->getNumColumns(), window); + return reinterpret_cast(window); +} + +static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + if (window) { + LOG_WINDOW("Closing window %p", window); + delete window; + } +} + +static jstring nativeGetName(JNIEnv* env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + return env->NewStringUTF(window->name().string()); +} + +static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jint windowPtr, + jobject parcelObj) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + Parcel* parcel = parcelForJavaObject(env, parcelObj); + + status_t status = window->writeToParcel(parcel); + if (status) { + android::String8 msg; + char *str = new char[100]; + sprintf(str, "Could not write AshmemCursorWindow to Parcel due to error %d.", status); + msg.append(str); + free(str); + //msg.appendFormat("Could not write AshmemCursorWindow to Parcel due to error %d.", status); + jniThrowRuntimeException(env, msg.string()); + } +} + +static void nativeClear(JNIEnv * env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Clearing window %p", window); + status_t status = window->clear(); + if (status) { + LOG_WINDOW("Could not clear window. error=%d", status); + } +} + +static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + return window->getNumRows(); +} + +static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jint windowPtr, + jint columnNum) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + status_t status = window->setNumColumns(columnNum); + return status == OK; +} + +static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + status_t status = window->allocRow(); + return status == OK; +} + +static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jint windowPtr) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + window->freeLastRow(); +} + +static jint nativeGetType(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + // FIXME: This is really broken but we have CTS tests that depend + // on this legacy behavior. + //throwExceptionWithRowCol(env, row, column); + return AshmemCursorWindow::NEW_FIELD_TYPE_NULL; + } + return window->getFieldSlotType(fieldSlot); +} + +static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + throwExceptionWithRowCol(env, row, column); + return NULL; + } + + int32_t type = window->getFieldSlotType(fieldSlot); + if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB || type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) { + size_t size; + const void* value = window->getFieldSlotValueBlob(fieldSlot, &size); + jbyteArray byteArray = env->NewByteArray(size); + if (!byteArray) { + env->ExceptionClear(); + throw_sqlite3_exception(env, "Native could not create new byte[]"); + return NULL; + } + env->SetByteArrayRegion(byteArray, 0, size, static_cast(value)); + return byteArray; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) { + throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob "); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) { + throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob "); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) { + // do nothing + } else { + throwUnknownTypeException(env, type); + } + return NULL; +} + +static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Getting string for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + throwExceptionWithRowCol(env, row, column); + return NULL; + } + + int32_t type = window->getFieldSlotType(fieldSlot); + if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) { + size_t sizeIncludingNull; + const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); + if (sizeIncludingNull <= 1) { + return gEmptyString; + } + // Convert to UTF-16 here instead of calling NewStringUTF. NewStringUTF + // doesn't like UTF-8 strings with high codepoints. It actually expects + // Modified UTF-8 with encoded surrogate pairs. + String16 utf16(value, sizeIncludingNull - 1); + return env->NewString(reinterpret_cast(utf16.string()), utf16.size()); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) { + int64_t value = window->getFieldSlotValueLong(fieldSlot); + char buf[32]; + snprintf(buf, sizeof(buf), "%lld", value); + return env->NewStringUTF(buf); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) { + double value = window->getFieldSlotValueDouble(fieldSlot); + char buf[32]; + snprintf(buf, sizeof(buf), "%g", value); + return env->NewStringUTF(buf); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) { + return NULL; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to string"); + return NULL; + } else { + throwUnknownTypeException(env, type); + return NULL; + } +} + +static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) { + jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj, + gCharArrayBufferClassInfo.data)); + if (dataObj && size) { + jsize capacity = env->GetArrayLength(dataObj); + if (size_t(capacity) < size) { + env->DeleteLocalRef(dataObj); + dataObj = NULL; + } + } + if (!dataObj) { + jsize capacity = size; + if (capacity < 64) { + capacity = 64; + } + dataObj = env->NewCharArray(capacity); // might throw OOM + if (dataObj) { + env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj); + } + } + return dataObj; +} + +static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj, + const char* str, size_t len) { + ssize_t size = utf8_to_utf16_length(reinterpret_cast(str), len); + if (size < 0) { + size = 0; // invalid UTF8 string + } + jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size); + if (dataObj) { + if (size) { + jchar* data = static_cast(env->GetPrimitiveArrayCritical(dataObj, NULL)); + utf8_to_utf16_no_null_terminator(reinterpret_cast(str), len, + reinterpret_cast(data)); + env->ReleasePrimitiveArrayCritical(dataObj, data, 0); + } + env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size); + } +} + +static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) { + jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0); + if (dataObj) { + env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0); + } +} + +static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column, jobject bufferObj) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Copying string for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + throwExceptionWithRowCol(env, row, column); + return; + } + + int32_t type = window->getFieldSlotType(fieldSlot); + if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) { + size_t sizeIncludingNull; + const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); + if (sizeIncludingNull > 1) { + fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1); + } else { + clearCharArrayBuffer(env, bufferObj); + } + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) { + int64_t value = window->getFieldSlotValueLong(fieldSlot); + char buf[32]; + snprintf(buf, sizeof(buf), "%lld", value); + fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf)); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) { + double value = window->getFieldSlotValueDouble(fieldSlot); + char buf[32]; + snprintf(buf, sizeof(buf), "%g", value); + fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf)); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) { + clearCharArrayBuffer(env, bufferObj); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to string"); + } else { + throwUnknownTypeException(env, type); + } +} + +static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Getting long for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + throwExceptionWithRowCol(env, row, column); + return 0; + } + + int32_t type = window->getFieldSlotType(fieldSlot); + if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) { + return window->getFieldSlotValueLong(fieldSlot); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) { + size_t sizeIncludingNull; + const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); + return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) { + return jlong(window->getFieldSlotValueDouble(fieldSlot)); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) { + return 0; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to long"); + return 0; + } else { + throwUnknownTypeException(env, type); + return 0; + } +} + +static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); + + AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); + if (!fieldSlot) { + throwExceptionWithRowCol(env, row, column); + return 0.0; + } + + int32_t type = window->getFieldSlotType(fieldSlot); + if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) { + return window->getFieldSlotValueDouble(fieldSlot); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) { + size_t sizeIncludingNull; + const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); + return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) { + return jdouble(window->getFieldSlotValueLong(fieldSlot)); + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) { + return 0.0; + } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) { + throw_sqlite3_exception(env, "Unable to convert BLOB to double"); + return 0.0; + } else { + throwUnknownTypeException(env, type); + return 0.0; + } +} + +static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr, + jbyteArray valueObj, jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + jsize len = env->GetArrayLength(valueObj); + + void* value = env->GetPrimitiveArrayCritical(valueObj, NULL); + status_t status = window->putBlob(row, column, value, len); + env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT); + + if (status) { + LOG_WINDOW("Failed to put blob. error=%d", status); + return false; + } + + LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len); + return true; +} + +static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr, + jstring valueObj, jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + + size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1; + const char* valueStr = env->GetStringUTFChars(valueObj, NULL); + if (!valueStr) { + LOG_WINDOW("value can't be transferred to UTFChars"); + return false; + } + status_t status = window->putString(row, column, valueStr, sizeIncludingNull); + env->ReleaseStringUTFChars(valueObj, valueStr); + + if (status) { + LOG_WINDOW("Failed to put string. error=%d", status); + return false; + } + + LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull); + return true; +} + +static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr, + jlong value, jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + status_t status = window->putLong(row, column, value); + + if (status) { + LOG_WINDOW("Failed to put long. error=%d", status); + return false; + } + + LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value); + return true; +} + +static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr, + jdouble value, jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + status_t status = window->putDouble(row, column, value); + + if (status) { + LOG_WINDOW("Failed to put double. error=%d", status); + return false; + } + + LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value); + return true; +} + +static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr, + jint row, jint column) { + AshmemCursorWindow* window = reinterpret_cast(windowPtr); + status_t status = window->putNull(row, column); + + if (status) { + LOG_WINDOW("Failed to put null. error=%d", status); + return false; + } + + LOG_WINDOW("%d,%d is NULL", row, column); + return true; +} + +static JNINativeMethod sMethods[] = +{ + /* name, signature, funcPtr */ + { "nativeCreate", "(Ljava/lang/String;I)I", + (void*)nativeCreate }, + { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I", + (void*)nativeCreateFromParcel }, + { "nativeDispose", "(I)V", + (void*)nativeDispose }, + { "nativeWriteToParcel", "(ILandroid/os/Parcel;)V", + (void*)nativeWriteToParcel }, + { "nativeGetName", "(I)Ljava/lang/String;", + (void*)nativeGetName }, + { "nativeClear", "(I)V", + (void*)nativeClear }, + { "nativeGetNumRows", "(I)I", + (void*)nativeGetNumRows }, + { "nativeSetNumColumns", "(II)Z", + (void*)nativeSetNumColumns }, + { "nativeAllocRow", "(I)Z", + (void*)nativeAllocRow }, + { "nativeFreeLastRow", "(I)V", + (void*)nativeFreeLastRow }, + { "nativeGetType", "(III)I", + (void*)nativeGetType }, + { "nativeGetBlob", "(III)[B", + (void*)nativeGetBlob }, + { "nativeGetString", "(III)Ljava/lang/String;", + (void*)nativeGetString }, + { "nativeGetLong", "(III)J", + (void*)nativeGetLong }, + { "nativeGetDouble", "(III)D", + (void*)nativeGetDouble }, + { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V", + (void*)nativeCopyStringToBuffer }, + { "nativePutBlob", "(I[BII)Z", + (void*)nativePutBlob }, + { "nativePutString", "(ILjava/lang/String;II)Z", + (void*)nativePutString }, + { "nativePutLong", "(IJII)Z", + (void*)nativePutLong }, + { "nativePutDouble", "(IDII)Z", + (void*)nativePutDouble }, + { "nativePutNull", "(III)Z", + (void*)nativePutNull }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_database_AshmemCursorWindow(JNIEnv * env) +{ + LOGE("Registering AshmemCursorWindow methods \n"); + jclass clazz; + FIND_CLASS(clazz, "android/database/CharArrayBuffer"); + + GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz, + "data", "[C"); + GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz, + "sizeCopied", "I"); + + gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF(""))); + LOG_FATAL_IF(!gEmptyString, "Unable to create empty string"); + + return AndroidRuntime::registerNativeMethods(env, "net/sqlcipher/AshmemCursorWindow", + sMethods, NELEM(sMethods)); +} + +} // namespace android diff --git a/jni/net_sqlcipher_database_SQLiteDatabase.cpp b/jni/net_sqlcipher_database_SQLiteDatabase.cpp index b8095689..d344b06f 100644 --- a/jni/net_sqlcipher_database_SQLiteDatabase.cpp +++ b/jni/net_sqlcipher_database_SQLiteDatabase.cpp @@ -608,6 +608,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_database_SQLiteStatement(env); register_android_database_CursorWindow(env); + register_android_database_AshmemCursorWindow(env); //register_android_database_SQLiteDebug(env); diff --git a/jni/net_sqlcipher_database_SQLiteQuery.cpp b/jni/net_sqlcipher_database_SQLiteQuery.cpp index 39eb2e29..51af0a86 100644 --- a/jni/net_sqlcipher_database_SQLiteQuery.cpp +++ b/jni/net_sqlcipher_database_SQLiteQuery.cpp @@ -30,6 +30,7 @@ #include #include "CursorWindow.h" +#include "AshmemCursorWindow.h" #include "sqlite3_exception.h" @@ -314,6 +315,237 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, } } +enum CopyRowResult { + CPR_OK, + CPR_FULL, + CPR_ERROR, +}; + +static CopyRowResult copyRow(JNIEnv* env, AshmemCursorWindow* window, + sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) { + // Allocate a new field directory for the row. + status_t status = window->allocRow(); + if (status) { + LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d", + startPos, addedRows, status); + return CPR_FULL; + } + + // Pack the row into the window. + CopyRowResult result = CPR_OK; + for (int i = 0; i < numColumns; i++) { + int type = sqlite3_column_type(statement, i); + if (type == SQLITE_TEXT) { + // TEXT data + const char* text = reinterpret_cast( + sqlite3_column_text(statement, i)); + + // SQLite does not include the NULL terminator in size, but does + // ensure all strings are NULL terminated, so increase size by + // one to make sure we store the terminator. + size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1; + status = window->putString(addedRows, i, text, sizeIncludingNull); + if (status) { + LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d", + sizeIncludingNull, startPos + addedRows, i, status); + result = CPR_FULL; + break; + } + LOG_WINDOW("%d,%d is TEXT with %u bytes", + startPos + addedRows, i, sizeIncludingNull); + } else if (type == SQLITE_INTEGER) { + // INTEGER data + int64_t value = sqlite3_column_int64(statement, i); + status = window->putLong(addedRows, i, value); + if (status) { + LOG_WINDOW("Failed allocating space for a long in column %d, error=%d", + i, status); + result = CPR_FULL; + break; + } + LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value); + } else if (type == SQLITE_FLOAT) { + // FLOAT data + double value = sqlite3_column_double(statement, i); + status = window->putDouble(addedRows, i, value); + if (status) { + LOG_WINDOW("Failed allocating space for a double in column %d, error=%d", + i, status); + result = CPR_FULL; + break; + } + LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value); + } else if (type == SQLITE_BLOB) { + // BLOB data + const void* blob = sqlite3_column_blob(statement, i); + size_t size = sqlite3_column_bytes(statement, i); + status = window->putBlob(addedRows, i, blob, size); + if (status) { + LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d", + size, startPos + addedRows, i, status); + result = CPR_FULL; + break; + } + LOG_WINDOW("%d,%d is Blob with %u bytes", + startPos + addedRows, i, size); + } else if (type == SQLITE_NULL) { + // NULL field + status = window->putNull(addedRows, i); + if (status) { + LOG_WINDOW("Failed allocating space for a null in column %d, error=%d", + i, status); + result = CPR_FULL; + break; + } + LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i); + } else { + // Unknown data + LOGE("Unknown column type when filling database window"); + throw_sqlite3_exception(env, "Unknown column type when filling window"); + result = CPR_ERROR; + break; + } + } + // Free the last row if if was not successfully copied. + if (result != CPR_OK) { + window->freeLastRow(); + } + + return result; +} + +static jint native_fill_ashmem_window(JNIEnv* env, jobject object, jint windowPtr, + jint startPos, jint offsetParam, jint maxRead, jint lastPos) +{ + sqlite3_stmt * statement = GET_STATEMENT(env, object); + int numRows = lastPos; + maxRead += lastPos; + int numColumns; + int boundParams; + int retryCount = 0; + int totalRows = 0; + int addedRows = 0; + bool windowFull = false; + bool gotException = false; + AshmemCursorWindow *window; + + if (statement == NULL) { + LOGE("Invalid statement in fillWindow()"); + jniThrowException(env, "java/lang/IllegalStateException", + "Attempting to access a deactivated, closed, or empty cursor"); + return 0; + } + + // Only do the binding if there is a valid offsetParam. If no binding needs to be done + // offsetParam will be set to 0, an invliad value. + if(offsetParam > 0) { + // Bind the offset parameter, telling the program which row to start with + int err = sqlite3_bind_int(statement, offsetParam, startPos); + if (err != SQLITE_OK) { + LOGE("Unable to bind offset position, offsetParam = %d", offsetParam); + jniThrowException(env, "java/lang/IllegalArgumentException", + sqlite3_errmsg(GET_HANDLE(env, object))); + return 0; + } + LOG_WINDOW("Bound to startPos %d", startPos); + } else { + LOG_WINDOW("Not binding to startPos %d", startPos); + } + + // Get the native window + window = reinterpret_cast(windowPtr); + if (!window) { + LOGE("Invalid CursorWindow"); + jniThrowException(env, "java/lang/IllegalArgumentException", + "Bad CursorWindow"); + return 0; + } + LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d", window->getNumRows(), window->size(), window->freeSpace()); + + status_t status = window->clear(); + if (status) { + LOGE("Failed to clear the cursor window, status=%d", status); + throw_sqlite3_exception(env, GET_HANDLE(env, object), "window->clear() failed"); + return 0; + } + + numColumns = sqlite3_column_count(statement); + status = window->setNumColumns(numColumns); + if (status) { + LOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns); + throw_sqlite3_exception(env, GET_HANDLE(env, object), "numColumns mismatch"); + return 0; + } + + retryCount = 0; + if (startPos > 0) { + int num = skip_rows(statement, startPos); + if (num < 0) { + throw_sqlite3_exception(env, GET_HANDLE(env, object)); + return 0; + } else if (num < startPos) { + LOGE("startPos %d > actual rows %d", startPos, num); + return num; + } + } + + while (!gotException && (!windowFull)) { + int err = sqlite3_step(statement); + if (err == SQLITE_ROW) { + LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows); + retryCount = 0; + totalRows += 1; + + // Skip the row if the window is full or we haven't reached the start position yet. + if (startPos >= totalRows || windowFull) { + continue; + } + + CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows); + if (cpr == CPR_OK) { + addedRows += 1; + } else if (cpr == CPR_FULL) { + windowFull = true; + } else { + gotException = true; + } + } else if (err == SQLITE_DONE) { + // All rows processed, bail + LOG_WINDOW("Processed all rows"); + break; + } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) { + // The table is locked, retry + LOG_WINDOW("Database locked, retrying"); + if (retryCount > 50) { + LOGE("Bailing on database busy rety"); + break; + } + + // Sleep to give the thread holding the lock a chance to finish + usleep(1000); + + retryCount++; + continue; + } else { + throw_sqlite3_exception(env, GET_HANDLE(env, object)); + break; + } + } + + LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows" + "to the window in %d bytes", + statement, totalRows, addedRows, window->size() - window->freeSpace()); + sqlite3_reset(statement); + + // Report the total number of rows on request. + if (startPos > totalRows) { + LOGE("startPos %d > actual rows %d", startPos, totalRows); + } + + jlong result = jlong(startPos) << 32 | jlong(totalRows); + return result; +} + static jint native_column_count(JNIEnv* env, jobject object) { sqlite3_stmt * statement = GET_STATEMENT(env, object); @@ -336,6 +568,7 @@ static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_fill_window", "(Lnet/sqlcipher/CursorWindow;IIII)I", (void *)native_fill_window}, + {"native_fill_ashmem_window", "(IIIII)I", (void *)native_fill_ashmem_window}, {"native_column_count", "()I", (void*)native_column_count}, {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name}, }; diff --git a/jni/sqlcipher_loading.h b/jni/sqlcipher_loading.h index a057fa94..21ea94e8 100644 --- a/jni/sqlcipher_loading.h +++ b/jni/sqlcipher_loading.h @@ -38,5 +38,7 @@ int register_android_database_SQLiteDebug(JNIEnv *env); int register_android_database_CursorWindow(JNIEnv *env); +int register_android_database_AshmemCursorWindow(JNIEnv *env); + } diff --git a/libs/guava-r09.jar b/libs/guava-r09.jar deleted file mode 100644 index f8da8b1c..00000000 Binary files a/libs/guava-r09.jar and /dev/null differ diff --git a/project.properties b/project.properties index 5a709453..ea89160e 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-7 +target=android-8 diff --git a/src/com/google/common/collect/Maps.java b/src/com/google/common/collect/Maps.java new file mode 100644 index 00000000..bb653a68 --- /dev/null +++ b/src/com/google/common/collect/Maps.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import java.util.HashMap; + +/** + * Provides static methods for creating mutable {@code Maps} instances easily. + */ +// Copied file from froyo/frameworks/base/core/java/com/google/android/collect/Maps.java +public class Maps { + /** + * Creates a {@code HashMap} instance. + * + * @return a newly-created, initially-empty {@code HashMap} + */ + public static HashMap newHashMap() { + return new HashMap(); + } +} diff --git a/src/net/sqlcipher/AbstractCursor.java b/src/net/sqlcipher/AbstractCursor.java index 45cae57a..361f92d6 100644 --- a/src/net/sqlcipher/AbstractCursor.java +++ b/src/net/sqlcipher/AbstractCursor.java @@ -68,7 +68,7 @@ public byte[] getBlob(int column) { /** * returns a pre-filled window, return NULL if no such window */ - public CursorWindow getWindow() { + public AbstractCursorWindow getWindow() { return null; } diff --git a/src/net/sqlcipher/AbstractCursorWindow.java b/src/net/sqlcipher/AbstractCursorWindow.java new file mode 100644 index 00000000..f2eb87a3 --- /dev/null +++ b/src/net/sqlcipher/AbstractCursorWindow.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import android.os.Parcelable; + +/** + * A buffer containing multiple cursor rows. + */ +public abstract class AbstractCursorWindow extends android.database.CursorWindow implements Parcelable { + public static int BINDER_CURSOR_WINDOW = 1; + public static int ASHMEM_CURSOR_WINDOW = 1; + + /** + * Creates a new empty window. + * + * @param localWindow true if this window will be used in this process only + */ + public AbstractCursorWindow(boolean localWindow) { + super(localWindow); + } + + abstract public int getType(int row, int column); + abstract public int getCursorWindowType(); +} diff --git a/src/net/sqlcipher/AbstractWindowedCursor.java b/src/net/sqlcipher/AbstractWindowedCursor.java index ec48e709..1bd1d841 100644 --- a/src/net/sqlcipher/AbstractWindowedCursor.java +++ b/src/net/sqlcipher/AbstractWindowedCursor.java @@ -227,7 +227,7 @@ protected void checkPosition() } @Override - public CursorWindow getWindow() { + public AbstractCursorWindow getWindow() { return mWindow; } @@ -235,7 +235,7 @@ public CursorWindow getWindow() { * Set a new cursor window to cursor, usually set a remote cursor window * @param window cursor window */ - public void setWindow(CursorWindow window) { + public void setWindow(AbstractCursorWindow window) { if (mWindow != null) { mWindow.close(); } @@ -250,5 +250,5 @@ public boolean hasWindow() { * This needs be updated in {@link #onMove} by subclasses, and * needs to be set to NULL when the contents of the cursor change. */ - protected CursorWindow mWindow; + protected AbstractCursorWindow mWindow; } diff --git a/src/net/sqlcipher/AshmemCursorWindow.java b/src/net/sqlcipher/AshmemCursorWindow.java new file mode 100644 index 00000000..40959182 --- /dev/null +++ b/src/net/sqlcipher/AshmemCursorWindow.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sqlcipher; + +import android.database.CharArrayBuffer; +import android.database.sqlite.SQLiteException; +import android.os.Binder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.util.Log; +import android.util.SparseIntArray; + +import net.sqlcipher.Cursor; + +/** + * A buffer containing multiple cursor rows. + *

+ * A {@link AbstractCursorWindow} is read-write when initially created and used locally. + * When sent to a remote process (by writing it to a {@link Parcel}), the remote process + * receives a read-only view of the cursor window. Typically the cursor window + * will be allocated by the producer, filled with data, and then sent to the + * consumer for reading. + *

+ */ +public class AshmemCursorWindow extends AbstractCursorWindow implements Parcelable { + private static final String STATS_TAG = "CursorWindowStats"; + + /** The cursor window size. resource xml file specifies the value in kB. + * convert it to bytes here by multiplying with 1024. + */ + // FIXME currently using default value of 2048 + private static final int sCursorWindowSize = 2048 * 1024; + /*Resources.getSystem().getInteger( + com.android.internal.R.integer.config_cursorWindowSize) * 1024;*/ + + /** + * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY) + * @hide + */ + public int mWindowPtr; + + private int mStartPos; + private final String mName; + + // FIXME enable CloseGurad + //private final CloseGuard mCloseGuard = CloseGuard.get(); + + private static native int nativeCreate(String name, int cursorWindowSize); + private static native int nativeCreateFromParcel(Parcel parcel); + private static native void nativeDispose(int windowPtr); + private static native void nativeWriteToParcel(int windowPtr, Parcel parcel); + + private static native void nativeClear(int windowPtr); + + private static native int nativeGetNumRows(int windowPtr); + private static native boolean nativeSetNumColumns(int windowPtr, int columnNum); + private static native boolean nativeAllocRow(int windowPtr); + private static native void nativeFreeLastRow(int windowPtr); + + private static native int nativeGetType(int windowPtr, int row, int column); + private static native byte[] nativeGetBlob(int windowPtr, int row, int column); + private static native String nativeGetString(int windowPtr, int row, int column); + private static native long nativeGetLong(int windowPtr, int row, int column); + private static native double nativeGetDouble(int windowPtr, int row, int column); + private static native void nativeCopyStringToBuffer(int windowPtr, int row, int column, + CharArrayBuffer buffer); + + private static native boolean nativePutBlob(int windowPtr, byte[] value, int row, int column); + private static native boolean nativePutString(int windowPtr, String value, int row, int column); + private static native boolean nativePutLong(int windowPtr, long value, int row, int column); + private static native boolean nativePutDouble(int windowPtr, double value, int row, int column); + private static native boolean nativePutNull(int windowPtr, int row, int column); + + private static native String nativeGetName(int windowPtr); + + /** + * Creates a new empty cursor window and gives it a name. + *

+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to + * set the number of columns before adding any rows to the cursor. + *

+ * + * @param name The name of the cursor window, or null if none. + */ + public AshmemCursorWindow(String name) { + super(false /*using deprecated localWindow super constructor*/); + mStartPos = 0; + mName = name != null && name.length() != 0 ? name : ""; + mWindowPtr = nativeCreate(mName, sCursorWindowSize); + if (mWindowPtr == 0) { + throw new SQLiteException("Cursor window allocation of " + + (sCursorWindowSize / 1024) + " kb failed. " + printStats()); + } + //mCloseGuard.open("close"); + recordNewWindow(Binder.getCallingPid(), mWindowPtr); + } + + /** + * Creates a new empty cursor window. + *

+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to + * set the number of columns before adding any rows to the cursor. + *

+ * + * @param localWindow True if this window will be used in this process only, + * false if it might be sent to another processes. This argument is ignored. + * + * @deprecated There is no longer a distinction between local and remote + * cursor windows. Use the {@link #CursorWindow(String)} constructor instead. + */ + @Deprecated + public AshmemCursorWindow(boolean localWindow) { + this((String)null); + } + + private AshmemCursorWindow(Parcel source) { + super(false /*using deprecated localWindow super constructor*/); + mStartPos = source.readInt(); + mWindowPtr = nativeCreateFromParcel(source); + if (mWindowPtr == 0) { + throw new SQLiteException("Cursor window could not be " + + "created from binder."); + } + mName = nativeGetName(mWindowPtr); + //mCloseGuard.open("close"); + } + + @Override + protected void finalize() { + try { + //if (mCloseGuard != null) { + // mCloseGuard.warnIfOpen(); + //} + dispose(); + } finally { + super.finalize(); + } + } + + private void dispose() { + //if (mCloseGuard != null) { + // mCloseGuard.close(); + //} + if (mWindowPtr != 0) { + recordClosingOfWindow(mWindowPtr); + nativeDispose(mWindowPtr); + mWindowPtr = 0; + } + } + + /** + * Gets the name of this cursor window, never null. + * @hide + */ + public String getName() { + return mName; + } + + /** + * Clears out the existing contents of the window, making it safe to reuse + * for new data. + *

+ * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), + * and number of columns in the cursor are all reset to zero. + *

+ */ + public void clear() { + acquireReference(); + try { + mStartPos = 0; + nativeClear(mWindowPtr); + } finally { + releaseReference(); + } + } + + /** + * Gets the start position of this cursor window. + *

+ * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + *

+ * + * @return The zero-based start position. + */ + public int getStartPosition() { + return mStartPos; + } + + /** + * Sets the start position of this cursor window. + *

+ * The start position is the zero-based index of the first row that this window contains + * relative to the entire result set of the {@link Cursor}. + *

+ * + * @param pos The new zero-based start position. + */ + public void setStartPosition(int pos) { + mStartPos = pos; + } + + /** + * Gets the number of rows in this window. + * + * @return The number of rows in this cursor window. + */ + public int getNumRows() { + acquireReference(); + try { + return nativeGetNumRows(mWindowPtr); + } finally { + releaseReference(); + } + } + + /** + * Sets the number of columns in this window. + *

+ * This method must be called before any rows are added to the window, otherwise + * it will fail to set the number of columns if it differs from the current number + * of columns. + *

+ * + * @param columnNum The new number of columns. + * @return True if successful. + */ + public boolean setNumColumns(int columnNum) { + acquireReference(); + try { + return nativeSetNumColumns(mWindowPtr, columnNum); + } finally { + releaseReference(); + } + } + + /** + * Allocates a new row at the end of this cursor window. + * + * @return True if successful, false if the cursor window is out of memory. + */ + public boolean allocRow(){ + acquireReference(); + try { + return nativeAllocRow(mWindowPtr); + } finally { + releaseReference(); + } + } + + /** + * Frees the last row in this cursor window. + */ + public void freeLastRow(){ + acquireReference(); + try { + nativeFreeLastRow(mWindowPtr); + } finally { + releaseReference(); + } + } + + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isNull(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_NULL; + } + + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or + * {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isBlob(int row, int column) { + int type = getType(row, column); + return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; + } + + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_INTEGER}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isLong(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; + } + + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_FLOAT}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isFloat(int row, int column) { + return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; + } + + /** + * Returns true if the field at the specified row and column index + * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} + * or {@link Cursor#FIELD_TYPE_NULL}. + * @deprecated Use {@link #getType(int, int)} instead. + */ + @Deprecated + public boolean isString(int row, int column) { + int type = getType(row, column); + return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; + } + + /** + * Returns the type of the field at the specified row and column index. + *

+ * The returned field types are: + *

    + *
  • {@link Cursor#FIELD_TYPE_NULL}
  • + *
  • {@link Cursor#FIELD_TYPE_INTEGER}
  • + *
  • {@link Cursor#FIELD_TYPE_FLOAT}
  • + *
  • {@link Cursor#FIELD_TYPE_STRING}
  • + *
  • {@link Cursor#FIELD_TYPE_BLOB}
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The field type. + */ + public int getType(int row, int column) { + acquireReference(); + try { + return nativeGetType(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Gets the value of the field at the specified row and column index as a byte array. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is null.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result + * is the blob value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the array of bytes that make up the internal representation of the + * string value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or + * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a byte array. + */ + public byte[] getBlob(int row, int column) { + acquireReference(); + try { + return nativeGetBlob(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Gets the value of the field at the specified row and column index as a string. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is null.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the string value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is a string representation of the integer in decimal, obtained by formatting the + * value with the printf family of functions using + * format specifier %lld.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is a string representation of the floating-point value in decimal, obtained by + * formatting the value with the printf family of functions using + * format specifier %g.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a string. + */ + public String getString(int row, int column) { + acquireReference(); + try { + return nativeGetString(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Copies the text of the field at the specified row and column index into + * a {@link CharArrayBuffer}. + *

+ * The buffer is populated as follows: + *

    + *
  • If the buffer is too small for the value to be copied, then it is + * automatically resized.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer + * is set to an empty string.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer + * is set to the contents of the string.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer + * is set to a string representation of the integer in decimal, obtained by formatting the + * value with the printf family of functions using + * format specifier %lld.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is + * set to a string representation of the floating-point value in decimal, obtained by + * formatting the value with the printf family of functions using + * format specifier %g.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically + * resized if the requested string is larger than the buffer's current capacity. + */ + public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("CharArrayBuffer should not be null"); + } + acquireReference(); + try { + nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer); + } finally { + releaseReference(); + } + } + + /** + * Gets the value of the field at the specified row and column index as a long. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is 0L.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with strtoll. + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the long value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the floating-point value converted to a long.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a long. + */ + public long getLong(int row, int column) { + acquireReference(); + try { + return nativeGetLong(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Gets the value of the field at the specified row and column index as a + * double. + *

+ * The result is determined as follows: + *

    + *
  • If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result + * is 0.0.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result + * is the value obtained by parsing the string value with strtod. + *
  • If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result + * is the integer value converted to a double.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result + * is the double value.
  • + *
  • If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a + * {@link SQLiteException} is thrown.
  • + *
+ *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a double. + */ + public double getDouble(int row, int column) { + acquireReference(); + try { + return nativeGetDouble(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Gets the value of the field at the specified row and column index as a + * short. + *

+ * The result is determined by invoking {@link #getLong} and converting the + * result to short. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as a short. + */ + public short getShort(int row, int column) { + return (short) getLong(row, column); + } + + /** + * Gets the value of the field at the specified row and column index as an + * int. + *

+ * The result is determined by invoking {@link #getLong} and converting the + * result to int. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as an int. + */ + public int getInt(int row, int column) { + return (int) getLong(row, column); + } + + /** + * Gets the value of the field at the specified row and column index as a + * float. + *

+ * The result is determined by invoking {@link #getDouble} and converting the + * result to float. + *

+ * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return The value of the field as an float. + */ + public float getFloat(int row, int column) { + return (float) getDouble(row, column); + } + + /** + * Copies a byte array into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putBlob(byte[] value, int row, int column) { + acquireReference(); + try { + return nativePutBlob(mWindowPtr, value, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Copies a string into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putString(String value, int row, int column) { + acquireReference(); + try { + return nativePutString(mWindowPtr, value, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Puts a long integer into the field at the specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putLong(long value, int row, int column) { + acquireReference(); + try { + return nativePutLong(mWindowPtr, value, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Puts a double-precision floating point value into the field at the + * specified row and column index. + * + * @param value The value to store. + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putDouble(double value, int row, int column) { + acquireReference(); + try { + return nativePutDouble(mWindowPtr, value, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + /** + * Puts a null value into the field at the specified row and column index. + * + * @param row The zero-based row index. + * @param column The zero-based column index. + * @return True if successful. + */ + public boolean putNull(int row, int column) { + acquireReference(); + try { + return nativePutNull(mWindowPtr, row - mStartPos, column); + } finally { + releaseReference(); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public AshmemCursorWindow createFromParcel(Parcel source) { + return new AshmemCursorWindow(source); + } + + public AshmemCursorWindow[] newArray(int size) { + return new AshmemCursorWindow[size]; + } + }; + + public static AshmemCursorWindow newFromParcel(Parcel p) { + return CREATOR.createFromParcel(p); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + acquireReference(); + try { + dest.writeInt(mStartPos); + nativeWriteToParcel(mWindowPtr, dest); + } finally { + releaseReference(); + } + + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + releaseReference(); + } + } + + @Override + protected void onAllReferencesReleased() { + dispose(); + } + + private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); + + private void recordNewWindow(int pid, int window) { + synchronized (sWindowToPidMap) { + sWindowToPidMap.put(window, pid); + if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { + Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); + } + } + } + + private void recordClosingOfWindow(int window) { + synchronized (sWindowToPidMap) { + if (sWindowToPidMap.size() == 0) { + // this means we are not in the ContentProvider. + return; + } + sWindowToPidMap.delete(window); + } + } + + private String printStats() { + StringBuilder buff = new StringBuilder(); + int myPid = Process.myPid(); + int total = 0; + SparseIntArray pidCounts = new SparseIntArray(); + synchronized (sWindowToPidMap) { + int size = sWindowToPidMap.size(); + if (size == 0) { + // this means we are not in the ContentProvider. + return ""; + } + for (int indx = 0; indx < size; indx++) { + int pid = sWindowToPidMap.valueAt(indx); + int value = pidCounts.get(pid); + pidCounts.put(pid, ++value); + } + } + int numPids = pidCounts.size(); + for (int i = 0; i < numPids;i++) { + buff.append(" (# cursors opened by "); + int pid = pidCounts.keyAt(i); + if (pid == myPid) { + buff.append("this proc="); + } else { + buff.append("pid " + pid + "="); + } + int num = pidCounts.get(pid); + buff.append(num + ")"); + total += num; + } + // limit the returned string size to 1000 + String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); + return "# Open Cursors=" + total + s; + } + + @Override + public int getCursorWindowType() { + return ASHMEM_CURSOR_WINDOW; + } + + @Override + public String toString() { + return getName() + " {" + Integer.toHexString(mWindowPtr) + "}"; + } +} diff --git a/src/net/sqlcipher/CursorWindow.java b/src/net/sqlcipher/CursorWindow.java index 8ab6dbfc..f020662e 100644 --- a/src/net/sqlcipher/CursorWindow.java +++ b/src/net/sqlcipher/CursorWindow.java @@ -31,7 +31,7 @@ /** * A buffer containing multiple cursor rows. */ -public class CursorWindow extends android.database.CursorWindow implements Parcelable { +public class CursorWindow extends net.sqlcipher.AbstractCursorWindow implements Parcelable { /** The pointer to the native window class */ @SuppressWarnings("unused") @@ -366,7 +366,6 @@ public boolean isString(int row, int col) { private native boolean isString_native(int row, int col); private native boolean isInteger_native(int row, int col); private native boolean isFloat_native(int row, int col); - private native int getType_native(int row, int col); /** @@ -627,4 +626,9 @@ protected void onAllReferencesReleased() { super.onAllReferencesReleased(); } + + @Override + public int getCursorWindowType() { + return BINDER_CURSOR_WINDOW; + } } diff --git a/src/net/sqlcipher/database/SQLiteCursor.java b/src/net/sqlcipher/database/SQLiteCursor.java index 4f293519..314f3879 100644 --- a/src/net/sqlcipher/database/SQLiteCursor.java +++ b/src/net/sqlcipher/database/SQLiteCursor.java @@ -17,6 +17,8 @@ package net.sqlcipher.database; import net.sqlcipher.AbstractWindowedCursor; +import net.sqlcipher.AbstractCursorWindow; +import net.sqlcipher.AshmemCursorWindow; import net.sqlcipher.CursorWindow; import net.sqlcipher.SQLException; @@ -127,7 +129,7 @@ private void sendMessage() { } public void run() { // use cached mWindow, to avoid get null mWindow - CursorWindow cw = mWindow; + AbstractCursorWindow cw = mWindow; Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND); // the cursor's state doesn't change while (true) { @@ -275,8 +277,13 @@ public int getCount() { private void fillWindow (int startPos) { if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); + // Use Ashmem Cursor Window for ICS or higher versions + if (android.os.Build.VERSION.SDK_INT >= 14 /*android.os.Build.VERSION_CODES.ICS*/) { + mWindow = new AshmemCursorWindow("SQLiteCursor"); + } else { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } } else { mCursorState++; queryThreadLock(); @@ -558,7 +565,7 @@ public boolean requery() { } @Override - public void setWindow(CursorWindow window) { + public void setWindow(AbstractCursorWindow window) { if (mWindow != null) { mCursorState++; queryThreadLock(); @@ -621,8 +628,13 @@ public void fillWindow(int startPos, android.database.CursorWindow window) { } */ if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); + // Use Ashmem Cursor Window for ICS or higher version + if (android.os.Build.VERSION.SDK_INT >= 14 /*android.os.Build.VERSION_CODES.ICS*/) { + mWindow = new AshmemCursorWindow("SQLiteCursor"); + } else { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(true /* the window is local only */); + } } else { mCursorState++; queryThreadLock(); diff --git a/src/net/sqlcipher/database/SQLiteQuery.java b/src/net/sqlcipher/database/SQLiteQuery.java index e0bb0d47..d7dbce64 100644 --- a/src/net/sqlcipher/database/SQLiteQuery.java +++ b/src/net/sqlcipher/database/SQLiteQuery.java @@ -58,7 +58,7 @@ public class SQLiteQuery extends SQLiteProgram { * @param window The window to fill into * @return number of total rows in the query */ - /* package */ int fillWindow(CursorWindow window, + /* package */ int fillWindow(AbstractCursorWindow window, int maxRead, int lastPos) { long timeStart = SystemClock.uptimeMillis(); mDatabase.lock(); @@ -70,8 +70,14 @@ public class SQLiteQuery extends SQLiteProgram { // if the start pos is not equal to 0, then most likely window is // too small for the data set, loading by another thread // is not safe in this situation. the native code will ignore maxRead - int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, + int numRows; + if (window.getCursorWindowType() == AbstractCursorWindow.ASHMEM_CURSOR_WINDOW) { + numRows = native_fill_ashmem_window(((AshmemCursorWindow) window).mWindowPtr, window.getStartPosition(), mOffsetIndex, + maxRead, lastPos); + } else { + numRows = native_fill_window((CursorWindow)window, window.getStartPosition(), mOffsetIndex, maxRead, lastPos); + } // Logging if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { @@ -188,6 +194,9 @@ public void bindString(int index, String value) { private final native int native_fill_window(CursorWindow window, int startPos, int offsetParam, int maxRead, int lastPos); + private final native int native_fill_ashmem_window(int windowPtr, + int startPos, int offsetParam, int maxRead, int lastPos); + private final native int native_column_count(); private final native String native_column_name(int columnIndex);