1 /*
2  ** Copyright 2011, The Android Open Source Project
3  **
4  ** Licensed under the Apache License, Version 2.0 (the "License");
5  ** you may not use this file except in compliance with the License.
6  ** You may obtain a copy of the License at
7  **
8  **     http://www.apache.org/licenses/LICENSE-2.0
9  **
10  ** Unless required by applicable law or agreed to in writing, software
11  ** distributed under the License is distributed on an "AS IS" BASIS,
12  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  ** See the License for the specific language governing permissions and
14  ** limitations under the License.
15  */
16 
17 //#define LOG_NDEBUG 0
18 
19 #include "BlobCache.h"
20 
21 #include <errno.h>
22 #include <inttypes.h>
23 
24 #if defined(__ANDROID__)
25 #include <cutils/properties.h>
26 #else
27 #include <string.h>
28 #include <algorithm>
29 static const char property_value[] = "[HOST]";
30 #define PROPERTY_VALUE_MAX (sizeof(property_value) - 1)
property_get(const char * key,char * value,const char * default_value)31 static int property_get(const char* key, char* value, const char* default_value) {
32     if (!strcmp(key, "ro.build.id")) {
33         memcpy(value, property_value, PROPERTY_VALUE_MAX);
34         return PROPERTY_VALUE_MAX;
35     }
36     if (default_value) {
37         const size_t len = std::max(strlen(default_value) + 1, size_t(PROPERTY_VALUE_MAX));
38         memcpy(value, default_value, len);
39     }
40     return 0;
41 }
42 #endif
43 
44 #include <log/log.h>
45 
46 #include <algorithm>
47 #include <chrono>
48 
49 namespace android {
50 
51 // BlobCache::Header::mMagicNumber value
52 static const uint32_t blobCacheMagic = ('_' << 24) + ('B' << 16) + ('b' << 8) + '$';
53 
54 // BlobCache::Header::mBlobCacheVersion value
55 static const uint32_t blobCacheVersion = 3;
56 
57 // BlobCache::Header::mDeviceVersion value
58 static const uint32_t blobCacheDeviceVersion = 1;
59 
BlobCache(size_t maxKeySize,size_t maxValueSize,size_t maxTotalSize,Policy policy)60 BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, Policy policy)
61     : mMaxKeySize(maxKeySize),
62       mMaxValueSize(maxValueSize),
63       mMaxTotalSize(maxTotalSize),
64       mPolicySelect(policy.first),
65       mPolicyCapacity(policy.second),
66       mTotalSize(0),
67       mAccessCount(0) {
68     int64_t now = std::chrono::steady_clock::now().time_since_epoch().count();
69 #ifdef _WIN32
70     srand(now);
71 #else
72     mRandState[0] = (now >> 0) & 0xFFFF;
73     mRandState[1] = (now >> 16) & 0xFFFF;
74     mRandState[2] = (now >> 32) & 0xFFFF;
75 #endif
76     ALOGV("initializing random seed using %lld", (unsigned long long)now);
77 }
78 
set(const void * key,size_t keySize,const void * value,size_t valueSize)79 void BlobCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
80     if (mMaxKeySize < keySize) {
81         ALOGV("set: not caching because the key is too large: %zu (limit: %zu)", keySize,
82               mMaxKeySize);
83         return;
84     }
85     if (mMaxValueSize < valueSize) {
86         ALOGV("set: not caching because the value is too large: %zu (limit: %zu)", valueSize,
87               mMaxValueSize);
88         return;
89     }
90     if (mMaxTotalSize < keySize + valueSize) {
91         ALOGV("set: not caching because the combined key/value size is too "
92               "large: %zu (limit: %zu)",
93               keySize + valueSize, mMaxTotalSize);
94         return;
95     }
96     if (keySize == 0) {
97         ALOGW("set: not caching because keySize is 0");
98         return;
99     }
100     if (valueSize <= 0) {
101         ALOGW("set: not caching because valueSize is 0");
102         return;
103     }
104 
105     std::shared_ptr<Blob> dummyKey(new Blob(key, keySize, false));
106     CacheEntry dummyEntry(dummyKey, NULL, 0);
107 
108     while (true) {
109         auto index = std::lower_bound(mCacheEntries.begin(), mCacheEntries.end(), dummyEntry);
110         if (index == mCacheEntries.end() || dummyEntry < *index) {
111             // Create a new cache entry.
112             std::shared_ptr<Blob> keyBlob(new Blob(key, keySize, true));
113             std::shared_ptr<Blob> valueBlob(new Blob(value, valueSize, true));
114             size_t newEntrySize = keySize + valueSize;
115             size_t newTotalSize = mTotalSize + newEntrySize;
116             if (mMaxTotalSize < newTotalSize) {
117                 if (isCleanable()) {
118                     // Clean the cache and try again.
119                     if (!clean(newEntrySize, NoEntry)) {
120                         // We have some kind of logic error -- perhaps
121                         // an inconsistency between isCleanable() and
122                         // findDownTo().
123                         ALOGE("set: not caching new key/value pair because "
124                               "cleaning failed");
125                         break;
126                     }
127                     continue;
128                 } else {
129                     ALOGV("set: not caching new key/value pair because the "
130                           "total cache size limit would be exceeded: %zu "
131                           "(limit: %zu)",
132                           keySize + valueSize, mMaxTotalSize);
133                     break;
134                 }
135             }
136             mCacheEntries.insert(index, CacheEntry(keyBlob, valueBlob, ++mAccessCount));
137             mTotalSize = newTotalSize;
138             ALOGV("set: created new cache entry with %zu byte key and %zu byte value", keySize,
139                   valueSize);
140         } else {
141             // Update the existing cache entry.
142             std::shared_ptr<Blob> valueBlob(new Blob(value, valueSize, true));
143             std::shared_ptr<Blob> oldValueBlob(index->getValue());
144             size_t newTotalSize = mTotalSize + valueSize - oldValueBlob->getSize();
145             if (mMaxTotalSize < newTotalSize) {
146                 if (isCleanable()) {
147                     // Clean the cache and try again.
148                     if (!clean(index->getKey()->getSize() + valueSize,
149                                index - mCacheEntries.begin())) {
150                         // We have some kind of logic error -- perhaps
151                         // an inconsistency between isCleanable() and
152                         // findDownTo().
153                         ALOGE("set: not caching new value because "
154                               "cleaning failed");
155                         break;
156                     }
157                     continue;
158                 } else {
159                     ALOGV("set: not caching new value because the total cache "
160                           "size limit would be exceeded: %zu (limit: %zu)",
161                           keySize + valueSize, mMaxTotalSize);
162                     break;
163                 }
164             }
165             index->setValue(valueBlob);
166             index->setRecency(++mAccessCount);
167             mTotalSize = newTotalSize;
168             ALOGV("set: updated existing cache entry with %zu byte key and %zu byte "
169                   "value",
170                   keySize, valueSize);
171         }
172         break;
173     }
174 }
175 
get(const void * key,size_t keySize,void * value,size_t valueSize)176 size_t BlobCache::get(const void* key, size_t keySize, void* value, size_t valueSize) {
177     void* dummy;
178     return get(key, keySize, &dummy, [value, valueSize](size_t allocSize) {
179         return (allocSize <= valueSize ? value : nullptr);
180     });
181 }
182 
get(const void * key,size_t keySize,void ** value,std::function<void * (size_t)> alloc)183 size_t BlobCache::get(const void* key, size_t keySize, void** value,
184                       std::function<void*(size_t)> alloc) {
185     if (mMaxKeySize < keySize) {
186         ALOGV("get: not searching because the key is too large: %zu (limit %zu)", keySize,
187               mMaxKeySize);
188         *value = nullptr;
189         return 0;
190     }
191     std::shared_ptr<Blob> dummyKey(new Blob(key, keySize, false));
192     CacheEntry dummyEntry(dummyKey, NULL, 0);
193     auto index = std::lower_bound(mCacheEntries.begin(), mCacheEntries.end(), dummyEntry);
194     if (index == mCacheEntries.end() || dummyEntry < *index) {
195         ALOGV("get: no cache entry found for key of size %zu", keySize);
196         *value = nullptr;
197         return 0;
198     }
199 
200     // The key was found. Return the value if we can allocate a buffer.
201     std::shared_ptr<Blob> valueBlob(index->getValue());
202     size_t valueBlobSize = valueBlob->getSize();
203     void* buf = alloc(valueBlobSize);
204     if (buf != nullptr) {
205         ALOGV("get: copying %zu bytes to caller's buffer", valueBlobSize);
206         memcpy(buf, valueBlob->getData(), valueBlobSize);
207         *value = buf;
208         index->setRecency(++mAccessCount);
209     } else {
210         ALOGV("get: cannot allocate caller's buffer: needs %zu", valueBlobSize);
211         *value = nullptr;
212     }
213     return valueBlobSize;
214 }
215 
align4(size_t size)216 static inline size_t align4(size_t size) {
217     return (size + 3) & ~3;
218 }
219 
getFlattenedSize() const220 size_t BlobCache::getFlattenedSize() const {
221     size_t size = align4(sizeof(Header) + PROPERTY_VALUE_MAX);
222     for (const CacheEntry& e : mCacheEntries) {
223         std::shared_ptr<Blob> const& keyBlob = e.getKey();
224         std::shared_ptr<Blob> const& valueBlob = e.getValue();
225         size += align4(sizeof(EntryHeader) + keyBlob->getSize() + valueBlob->getSize());
226     }
227     return size;
228 }
229 
flatten(void * buffer,size_t size) const230 int BlobCache::flatten(void* buffer, size_t size) const {
231     // Write the cache header
232     if (size < sizeof(Header)) {
233         ALOGE("flatten: not enough room for cache header");
234         return 0;
235     }
236     Header* header = reinterpret_cast<Header*>(buffer);
237     header->mMagicNumber = blobCacheMagic;
238     header->mBlobCacheVersion = blobCacheVersion;
239     header->mDeviceVersion = blobCacheDeviceVersion;
240     header->mNumEntries = mCacheEntries.size();
241     char buildId[PROPERTY_VALUE_MAX];
242     header->mBuildIdLength = property_get("ro.build.id", buildId, "");
243     memcpy(header->mBuildId, buildId, header->mBuildIdLength);
244 
245     // Write cache entries
246     uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer);
247     off_t byteOffset = align4(sizeof(Header) + header->mBuildIdLength);
248     for (const CacheEntry& e : mCacheEntries) {
249         std::shared_ptr<Blob> const& keyBlob = e.getKey();
250         std::shared_ptr<Blob> const& valueBlob = e.getValue();
251         size_t keySize = keyBlob->getSize();
252         size_t valueSize = valueBlob->getSize();
253 
254         size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
255         size_t totalSize = align4(entrySize);
256         if (byteOffset + totalSize > size) {
257             ALOGE("flatten: not enough room for cache entries");
258             return -EINVAL;
259         }
260 
261         EntryHeader* eheader = reinterpret_cast<EntryHeader*>(&byteBuffer[byteOffset]);
262         eheader->mKeySize = keySize;
263         eheader->mValueSize = valueSize;
264 
265         memcpy(eheader->mData, keyBlob->getData(), keySize);
266         memcpy(eheader->mData + keySize, valueBlob->getData(), valueSize);
267 
268         if (totalSize > entrySize) {
269             // We have padding bytes. Those will get written to storage, and contribute to the CRC,
270             // so make sure we zero-them to have reproducible results.
271             memset(eheader->mData + keySize + valueSize, 0, totalSize - entrySize);
272         }
273 
274         byteOffset += totalSize;
275     }
276 
277     return 0;
278 }
279 
unflatten(void const * buffer,size_t size)280 int BlobCache::unflatten(void const* buffer, size_t size) {
281     // All errors should result in the BlobCache being in an empty state.
282     mCacheEntries.clear();
283 
284     // Read the cache header
285     if (size < sizeof(Header)) {
286         ALOGE("unflatten: not enough room for cache header");
287         return -EINVAL;
288     }
289     const Header* header = reinterpret_cast<const Header*>(buffer);
290     if (header->mMagicNumber != blobCacheMagic) {
291         ALOGE("unflatten: bad magic number: %" PRIu32, header->mMagicNumber);
292         return -EINVAL;
293     }
294     char buildId[PROPERTY_VALUE_MAX];
295     int len = property_get("ro.build.id", buildId, "");
296     if (header->mBlobCacheVersion != blobCacheVersion ||
297         header->mDeviceVersion != blobCacheDeviceVersion || len != header->mBuildIdLength ||
298         strncmp(buildId, header->mBuildId, len)) {
299         // We treat version mismatches as an empty cache.
300         return 0;
301     }
302 
303     // Read cache entries
304     const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer);
305     off_t byteOffset = align4(sizeof(Header) + header->mBuildIdLength);
306     size_t numEntries = header->mNumEntries;
307     for (size_t i = 0; i < numEntries; i++) {
308         if (byteOffset + sizeof(EntryHeader) > size) {
309             mCacheEntries.clear();
310             ALOGE("unflatten: not enough room for cache entry header");
311             return -EINVAL;
312         }
313 
314         const EntryHeader* eheader = reinterpret_cast<const EntryHeader*>(&byteBuffer[byteOffset]);
315         size_t keySize = eheader->mKeySize;
316         size_t valueSize = eheader->mValueSize;
317         size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
318 
319         size_t totalSize = align4(entrySize);
320         if (byteOffset + totalSize > size) {
321             mCacheEntries.clear();
322             ALOGE("unflatten: not enough room for cache entry");
323             return -EINVAL;
324         }
325 
326         const uint8_t* data = eheader->mData;
327         set(data, keySize, data + keySize, valueSize);
328 
329         byteOffset += totalSize;
330     }
331 
332     return 0;
333 }
334 
blob_random()335 long int BlobCache::blob_random() {
336 #ifdef _WIN32
337     return rand();
338 #else
339     return nrand48(mRandState);
340 #endif
341 }
342 
findVictim()343 size_t BlobCache::findVictim() {
344     switch (mPolicySelect) {
345         case Select::RANDOM:
346             return size_t(blob_random() % (mCacheEntries.size()));
347         case Select::LRU:
348             return std::min_element(mCacheEntries.begin(), mCacheEntries.end(),
349                                     [](const CacheEntry& a, const CacheEntry& b) {
350                                         return a.getRecency() < b.getRecency();
351                                     }) -
352                    mCacheEntries.begin();
353         default:
354             ALOGE("findVictim: unknown mPolicySelect: %d", mPolicySelect);
355             return 0;
356     }
357 }
358 
findDownTo(size_t newEntrySize,size_t onBehalfOf)359 size_t BlobCache::findDownTo(size_t newEntrySize, size_t onBehalfOf) {
360     auto oldEntrySize = [this, onBehalfOf]() -> size_t {
361         if (onBehalfOf == NoEntry) return 0;
362         const auto& entry = mCacheEntries[onBehalfOf];
363         return entry.getKey()->getSize() + entry.getValue()->getSize();
364     };
365     switch (mPolicyCapacity) {
366         case Capacity::HALVE:
367             return mMaxTotalSize / 2;
368         case Capacity::FIT:
369             return mMaxTotalSize - (newEntrySize - oldEntrySize());
370         case Capacity::FIT_HALVE:
371             return std::min(mMaxTotalSize - (newEntrySize - oldEntrySize()), mMaxTotalSize / 2);
372         default:
373             ALOGE("findDownTo: unknown mPolicyCapacity: %d", mPolicyCapacity);
374             return 0;
375     }
376 }
377 
isFit(Capacity capacity)378 bool BlobCache::isFit(Capacity capacity) {
379     switch (capacity) {
380         case Capacity::HALVE:
381             return false;
382         case Capacity::FIT:
383         case Capacity::FIT_HALVE:
384             return true;
385         default:
386             ALOGE("isFit: unknown capacity: %d", capacity);
387             return false;
388     }
389 }
390 
clean(size_t newEntrySize,size_t onBehalfOf)391 bool BlobCache::clean(size_t newEntrySize, size_t onBehalfOf) {
392     // Remove a selected cache entry until the total cache size does
393     // not exceed downTo.
394     const size_t downTo = findDownTo(newEntrySize, onBehalfOf);
395 
396     bool cleaned = false;
397     while (mTotalSize > downTo) {
398         const size_t i = findVictim();
399         const CacheEntry& entry(mCacheEntries[i]);
400         const size_t entrySize = entry.getKey()->getSize() + entry.getValue()->getSize();
401         mTotalSize -= entrySize;
402         mCacheEntries.erase(mCacheEntries.begin() + i);
403         cleaned = true;
404     }
405     return cleaned;
406 }
407 
isCleanable() const408 bool BlobCache::isCleanable() const {
409     switch (mPolicyCapacity) {
410         case Capacity::HALVE:
411             return mTotalSize > mMaxTotalSize / 2;
412         default:
413             ALOGE("isCleanable: unknown mPolicyCapacity: %d", mPolicyCapacity);
414             [[fallthrough]];
415         case Capacity::FIT:
416         case Capacity::FIT_HALVE:
417             return mTotalSize > 0;
418     }
419 }
420 
Blob(const void * data,size_t size,bool copyData)421 BlobCache::Blob::Blob(const void* data, size_t size, bool copyData)
422     : mData(copyData ? malloc(size) : data), mSize(size), mOwnsData(copyData) {
423     if (data != NULL && copyData) {
424         memcpy(const_cast<void*>(mData), data, size);
425     }
426 }
427 
~Blob()428 BlobCache::Blob::~Blob() {
429     if (mOwnsData) {
430         free(const_cast<void*>(mData));
431     }
432 }
433 
operator <(const Blob & rhs) const434 bool BlobCache::Blob::operator<(const Blob& rhs) const {
435     if (mSize == rhs.mSize) {
436         return memcmp(mData, rhs.mData, mSize) < 0;
437     } else {
438         return mSize < rhs.mSize;
439     }
440 }
441 
getData() const442 const void* BlobCache::Blob::getData() const {
443     return mData;
444 }
445 
getSize() const446 size_t BlobCache::Blob::getSize() const {
447     return mSize;
448 }
449 
CacheEntry()450 BlobCache::CacheEntry::CacheEntry() : mRecency(0) {}
451 
CacheEntry(const std::shared_ptr<Blob> & key,const std::shared_ptr<Blob> & value,uint32_t recency)452 BlobCache::CacheEntry::CacheEntry(const std::shared_ptr<Blob>& key,
453                                   const std::shared_ptr<Blob>& value, uint32_t recency)
454     : mKey(key), mValue(value), mRecency(recency) {}
455 
CacheEntry(const CacheEntry & ce)456 BlobCache::CacheEntry::CacheEntry(const CacheEntry& ce)
457     : mKey(ce.mKey), mValue(ce.mValue), mRecency(ce.mRecency) {}
458 
operator <(const CacheEntry & rhs) const459 bool BlobCache::CacheEntry::operator<(const CacheEntry& rhs) const {
460     return *mKey < *rhs.mKey;
461 }
462 
operator =(const CacheEntry & rhs)463 const BlobCache::CacheEntry& BlobCache::CacheEntry::operator=(const CacheEntry& rhs) {
464     mKey = rhs.mKey;
465     mValue = rhs.mValue;
466     mRecency = rhs.mRecency;
467     return *this;
468 }
469 
getKey() const470 std::shared_ptr<BlobCache::Blob> BlobCache::CacheEntry::getKey() const {
471     return mKey;
472 }
473 
getValue() const474 std::shared_ptr<BlobCache::Blob> BlobCache::CacheEntry::getValue() const {
475     return mValue;
476 }
477 
setValue(const std::shared_ptr<Blob> & value)478 void BlobCache::CacheEntry::setValue(const std::shared_ptr<Blob>& value) {
479     mValue = value;
480 }
481 
getRecency() const482 uint32_t BlobCache::CacheEntry::getRecency() const {
483     return mRecency;
484 }
485 
setRecency(uint32_t recency)486 void BlobCache::CacheEntry::setRecency(uint32_t recency) {
487     mRecency = recency;
488 }
489 
490 }  // namespace android
491