diff options
| -rw-r--r-- | core/jni/android_app_PropertyInvalidatedCache.cpp | 73 | ||||
| -rw-r--r-- | core/jni/android_app_PropertyInvalidatedCache.h | 184 |
2 files changed, 160 insertions, 97 deletions
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp index ead66660a0a4..12585d5f8137 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.cpp +++ b/core/jni/android_app_PropertyInvalidatedCache.cpp @@ -28,24 +28,77 @@ #include "core_jni_helpers.h" #include "android_app_PropertyInvalidatedCache.h" +namespace android::app::PropertyInvalidatedCache { + +// These provide run-time access to the sizing parameters. +int NonceStore::getMaxNonce() const { + return kMaxNonce; +} + +size_t NonceStore::getMaxByte() const { + return kMaxByte; +} + +// Fetch a nonce, returning UNSET if the index is out of range. This method specifically +// does not throw or generate an error if the index is out of range; this allows the method +// to be called in a CriticalNative JNI API. +int64_t NonceStore::getNonce(int index) const { + if (index < 0 || index >= kMaxNonce) { + return UNSET; + } else { + return nonce()[index]; + } +} + +// Set a nonce and return true. Return false if the index is out of range. This method +// specifically does not throw or generate an error if the index is out of range; this +// allows the method to be called in a CriticalNative JNI API. +bool NonceStore::setNonce(int index, int64_t value) { + if (index < 0 || index >= kMaxNonce) { + return false; + } else { + nonce()[index] = value; + return true; + } +} + +// Fetch just the byte-block hash +int32_t NonceStore::getHash() const { + return mByteHash; +} + +// Copy the byte block to the target and return the current hash. +int32_t NonceStore::getByteBlock(block_t* block, size_t len) const { + memcpy(block, (void*) byteBlock(), std::min(kMaxByte, len)); + return mByteHash; +} + +// Set the byte block and the hash. +void NonceStore::setByteBlock(int hash, const block_t* block, size_t len) { + memcpy((void*) byteBlock(), block, len = std::min(kMaxByte, len)); + mByteHash = hash; +} + +} // namespace android::app::PropertyInvalidatedCache; + namespace { using namespace android::app::PropertyInvalidatedCache; // Convert a jlong to a nonce block. This is a convenience function that should be inlined by // the compiler. -inline SystemCacheNonce* sysCache(jlong ptr) { - return reinterpret_cast<SystemCacheNonce*>(ptr); +inline NonceStore* nonceCache(jlong ptr) { + return reinterpret_cast<NonceStore*>(ptr); } // Return the number of nonces in the nonce block. jint getMaxNonce(JNIEnv*, jclass, jlong ptr) { - return sysCache(ptr)->getMaxNonce(); + return nonceCache(ptr)->getMaxNonce(); } // Return the number of string bytes in the nonce block. jint getMaxByte(JNIEnv*, jclass, jlong ptr) { - return sysCache(ptr)->getMaxByte(); + return nonceCache(ptr)->getMaxByte(); } // Set the byte block. The first int is the hash to set and the second is the array to copy. @@ -56,25 +109,25 @@ void setByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "null byte block"); return; } - sysCache(ptr)->setByteBlock(hash, value.get(), value.size()); + nonceCache(ptr)->setByteBlock(hash, value.get(), value.size()); } // Fetch the byte block. If the incoming hash is the same as the local hash, the Java layer is // presumed to have an up-to-date copy of the byte block; do not copy byte array. The local // hash is returned. jint getByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) { - if (sysCache(ptr)->getHash() == hash) { + if (nonceCache(ptr)->getHash() == hash) { return hash; } ScopedByteArrayRW value(env, val); - return sysCache(ptr)->getByteBlock(value.get(), value.size()); + return nonceCache(ptr)->getByteBlock(value.get(), value.size()); } // Fetch the byte block hash. // // This is a CriticalNative method and therefore does not get the JNIEnv or jclass parameters. jint getByteBlockHash(jlong ptr) { - return sysCache(ptr)->getHash(); + return nonceCache(ptr)->getHash(); } // Get a nonce value. So that this method can be CriticalNative, it returns 0 if the value is @@ -83,7 +136,7 @@ jint getByteBlockHash(jlong ptr) { // // This method is @CriticalNative and does not take a JNIEnv* or jclass argument. jlong getNonce(jlong ptr, jint index) { - return sysCache(ptr)->getNonce(index); + return nonceCache(ptr)->getNonce(index); } // Set a nonce value. So that this method can be CriticalNative, it returns a boolean: false if @@ -92,7 +145,7 @@ jlong getNonce(jlong ptr, jint index) { // // This method is @CriticalNative and does not take a JNIEnv* or jclass argument. jboolean setNonce(jlong ptr, jint index, jlong value) { - return sysCache(ptr)->setNonce(index, value); + return nonceCache(ptr)->setNonce(index, value); } static const JNINativeMethod gMethods[] = { diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h index eefa8fa88624..00aa281b572f 100644 --- a/core/jni/android_app_PropertyInvalidatedCache.h +++ b/core/jni/android_app_PropertyInvalidatedCache.h @@ -18,129 +18,139 @@ #include <memory.h> #include <atomic> +#include <cstdint> -namespace android { -namespace app { -namespace PropertyInvalidatedCache { +namespace android::app::PropertyInvalidatedCache { /** - * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The - * byte array has an associated hash. This class provides methods to read and write the fields - * of the block but it does not interpret the fields. - * - * On initialization, all fields are set to zero. - * - * In general, methods do not report errors. This allows the methods to be used in - * CriticalNative JNI APIs. - * - * The template is parameterized by the number of nonces it supports and the number of bytes in - * the string block. + * A head of a CacheNonce object. This contains all the fields that have a fixed size and + * location. Fields with a variable location are found via offsets. The offsets make this + * object position-independent, which is required because it is in shared memory and would be + * mapped into different virtual addresses for different processes. */ -template<int maxNonce, size_t maxByte> class CacheNonce { - - // The value of an unset field. - static const int UNSET = 0; - +class NonceStore { + protected: // A convenient typedef. The jbyteArray element type is jbyte, which the compiler treats as // signed char. typedef signed char block_t; - // The array of nonces - volatile std::atomic<int64_t> mNonce[maxNonce]; + // The nonce type. + typedef std::atomic<int64_t> nonce_t; - // The byte array. This is not atomic but it is guarded by the mByteHash. - volatile block_t mByteBlock[maxByte]; + // Atomics should be safe to use across processes if they are lock free. + static_assert(nonce_t::is_always_lock_free == true); - // The hash that validates the byte block - volatile std::atomic<int32_t> mByteHash; + // The value of an unset field. + static constexpr int UNSET = 0; - // Pad the class to a multiple of 8 bytes. - int32_t _pad; + // The size of the nonce array. + const int32_t kMaxNonce; - public: + // The size of the byte array. + const size_t kMaxByte; - // The expected size of this instance. This is a compile-time constant and can be used in a - // static assertion. - static const int expectedSize = - maxNonce * sizeof(std::atomic<int64_t>) - + sizeof(std::atomic<int32_t>) - + maxByte * sizeof(block_t) - + sizeof(int32_t); + // The offset to the nonce array. + const size_t mNonceOffset; - // These provide run-time access to the sizing parameters. - int getMaxNonce() const { - return maxNonce; - } + // The offset to the byte array. + const size_t mByteOffset; - size_t getMaxByte() const { - return maxByte; - } + // The byte block hash. This is fixed and at a known offset, so leave it in the base class. + volatile std::atomic<int32_t> mByteHash; - // Construct and initialize the memory. - CacheNonce() { - for (int i = 0; i < maxNonce; i++) { - mNonce[i] = UNSET; - } - mByteHash = UNSET; - memset((void*) mByteBlock, UNSET, sizeof(mByteBlock)); + // The constructor is protected! It only makes sense when called from a subclass. + NonceStore(int kMaxNonce, size_t kMaxByte, volatile nonce_t* nonce, volatile block_t* block) : + kMaxNonce(kMaxNonce), + kMaxByte(kMaxByte), + mNonceOffset(offset(this, const_cast<nonce_t*>(nonce))), + mByteOffset(offset(this, const_cast<block_t*>(block))) { } + public: + + // These provide run-time access to the sizing parameters. + int getMaxNonce() const; + size_t getMaxByte() const; + // Fetch a nonce, returning UNSET if the index is out of range. This method specifically // does not throw or generate an error if the index is out of range; this allows the method // to be called in a CriticalNative JNI API. - int64_t getNonce(int index) const { - if (index < 0 || index >= maxNonce) { - return UNSET; - } else { - return mNonce[index]; - } - } + int64_t getNonce(int index) const; // Set a nonce and return true. Return false if the index is out of range. This method // specifically does not throw or generate an error if the index is out of range; this // allows the method to be called in a CriticalNative JNI API. - bool setNonce(int index, int64_t value) { - if (index < 0 || index >= maxNonce) { - return false; - } else { - mNonce[index] = value; - return true; - } - } + bool setNonce(int index, int64_t value); // Fetch just the byte-block hash - int32_t getHash() const { - return mByteHash; - } + int32_t getHash() const; // Copy the byte block to the target and return the current hash. - int32_t getByteBlock(block_t* block, size_t len) const { - memcpy(block, (void*) mByteBlock, std::min(maxByte, len)); - return mByteHash; - } + int32_t getByteBlock(block_t* block, size_t len) const; // Set the byte block and the hash. - void setByteBlock(int hash, const block_t* block, size_t len) { - memcpy((void*) mByteBlock, block, len = std::min(maxByte, len)); - mByteHash = hash; + void setByteBlock(int hash, const block_t* block, size_t len); + + private: + + // A convenience function to compute the offset between two unlike pointers. + static size_t offset(void const* base, void const* member) { + return reinterpret_cast<uintptr_t>(member) - reinterpret_cast<std::uintptr_t>(base); + } + + // Return the address of the nonce array. + volatile nonce_t* nonce() const { + // The array is located at an offset from <this>. + return reinterpret_cast<nonce_t*>( + reinterpret_cast<std::uintptr_t>(this) + mNonceOffset); + } + + // Return the address of the byte block array. + volatile block_t* byteBlock() const { + // The array is located at an offset from <this>. + return reinterpret_cast<block_t*>( + reinterpret_cast<std::uintptr_t>(this) + mByteOffset); } }; /** - * Sizing parameters for the system_server PropertyInvalidatedCache support. A client can - * retrieve the values through the accessors in CacheNonce instances. + * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes. The + * byte array has an associated hash. This class provides methods to read and write the fields + * of the block but it does not interpret the fields. + * + * On initialization, all fields are set to zero. + * + * In general, methods do not report errors. This allows the methods to be used in + * CriticalNative JNI APIs. + * + * The template is parameterized by the number of nonces it supports and the number of bytes in + * the string block. */ -static const int MAX_NONCE = 64; -static const int BYTE_BLOCK_SIZE = 8192; +template<int maxNonce, size_t maxByte> class CacheNonce : public NonceStore { + + // The array of nonces + volatile nonce_t mNonce[maxNonce]; -// The CacheNonce for system server holds 64 nonces with a string block of 8192 bytes. -typedef CacheNonce<MAX_NONCE, BYTE_BLOCK_SIZE> SystemCacheNonce; + // The byte array. This is not atomic but it is guarded by the mByteHash. + volatile block_t mByteBlock[maxByte]; + + public: + // Construct and initialize the memory. + CacheNonce() : + NonceStore(maxNonce, maxByte, &mNonce[0], &mByteBlock[0]) + { + for (int i = 0; i < maxNonce; i++) { + mNonce[i] = UNSET; + } + mByteHash = UNSET; + memset((void*) mByteBlock, UNSET, sizeof(mByteBlock)); + } +}; -// The goal of this assertion is to ensure that the data structure is the same size across 32-bit -// and 64-bit systems. -static_assert(sizeof(SystemCacheNonce) == SystemCacheNonce::expectedSize, - "Unexpected SystemCacheNonce size"); +// The CacheNonce for system server holds 64 nonces with a string block of 8192 bytes. This is +// more than enough for system_server PropertyInvalidatedCache support. The configuration +// values are not defined as visible constants. Clients should use the accessors on the +// SystemCacheNonce instance if they need the sizing parameters. +typedef CacheNonce</* max nonce */ 64, /* byte block size */ 8192> SystemCacheNonce; -} // namespace PropertyInvalidatedCache -} // namespace app -} // namespace android +} // namespace android.app.PropertyInvalidatedCache |