Loading core/jni/android_app_PropertyInvalidatedCache.cpp +63 −10 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 Loading @@ -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 Loading @@ -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[] = { Loading core/jni/android_app_PropertyInvalidatedCache.h +97 −87 Original line number Diff line number Diff line Loading @@ -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 Loading
core/jni/android_app_PropertyInvalidatedCache.cpp +63 −10 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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 Loading @@ -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 Loading @@ -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[] = { Loading
core/jni/android_app_PropertyInvalidatedCache.h +97 −87 Original line number Diff line number Diff line Loading @@ -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