Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 69034085 authored by Lee Shombert's avatar Lee Shombert Committed by Android (Google) Code Review
Browse files

Merge "Refactor native store for PIC nonces" into main

parents f2d99a52 54cc588f
Loading
Loading
Loading
Loading
+63 −10
Original line number Diff line number Diff line
@@ -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[] = {
+97 −87
Original line number Diff line number Diff line
@@ -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