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