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