| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "MediaAnalyticsItem" |
| |
| #include <inttypes.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <mutex> |
| #include <set> |
| |
| #include <binder/Parcel.h> |
| #include <utils/Errors.h> |
| #include <utils/Log.h> |
| #include <utils/SortedVector.h> |
| #include <utils/threads.h> |
| |
| #include <binder/IServiceManager.h> |
| #include <media/IMediaAnalyticsService.h> |
| #include <media/MediaAnalyticsItem.h> |
| #include <private/android_filesystem_config.h> |
| |
| // Max per-property string size before truncation in toString(). |
| // Do not make too large, as this is used for dumpsys purposes. |
| static constexpr size_t kMaxPropertyStringSize = 4096; |
| |
| namespace android { |
| |
| #define DEBUG_SERVICEACCESS 0 |
| #define DEBUG_API 0 |
| #define DEBUG_ALLOCATIONS 0 |
| |
| // after this many failed attempts, we stop trying [from this process] and just say that |
| // the service is off. |
| #define SVC_TRIES 2 |
| |
| MediaAnalyticsItem* MediaAnalyticsItem::convert(mediametrics_handle_t handle) { |
| MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle; |
| return item; |
| } |
| |
| mediametrics_handle_t MediaAnalyticsItem::convert(MediaAnalyticsItem *item ) { |
| mediametrics_handle_t handle = (mediametrics_handle_t) item; |
| return handle; |
| } |
| |
| MediaAnalyticsItem::~MediaAnalyticsItem() { |
| if (DEBUG_ALLOCATIONS) { |
| ALOGD("Destroy MediaAnalyticsItem @ %p", this); |
| } |
| clear(); |
| } |
| |
| void MediaAnalyticsItem::clear() { |
| |
| // clean allocated storage from key |
| mKey.clear(); |
| |
| // clean attributes |
| // contents of the attributes |
| for (size_t i = 0 ; i < mPropCount; i++ ) { |
| mProps[i].clear(); |
| } |
| // the attribute records themselves |
| if (mProps != NULL) { |
| free(mProps); |
| mProps = NULL; |
| } |
| mPropSize = 0; |
| mPropCount = 0; |
| |
| return; |
| } |
| |
| // make a deep copy of myself |
| MediaAnalyticsItem *MediaAnalyticsItem::dup() { |
| MediaAnalyticsItem *dst = new MediaAnalyticsItem(this->mKey); |
| |
| if (dst != NULL) { |
| // key as part of constructor |
| dst->mPid = this->mPid; |
| dst->mUid = this->mUid; |
| dst->mPkgName = this->mPkgName; |
| dst->mPkgVersionCode = this->mPkgVersionCode; |
| dst->mTimestamp = this->mTimestamp; |
| |
| // properties aka attributes |
| dst->growProps(this->mPropCount); |
| for(size_t i=0;i<mPropCount;i++) { |
| dst->mProps[i] = this->mProps[i]; |
| } |
| dst->mPropCount = this->mPropCount; |
| } |
| |
| return dst; |
| } |
| |
| MediaAnalyticsItem &MediaAnalyticsItem::setTimestamp(nsecs_t ts) { |
| mTimestamp = ts; |
| return *this; |
| } |
| |
| nsecs_t MediaAnalyticsItem::getTimestamp() const { |
| return mTimestamp; |
| } |
| |
| MediaAnalyticsItem &MediaAnalyticsItem::setPid(pid_t pid) { |
| mPid = pid; |
| return *this; |
| } |
| |
| pid_t MediaAnalyticsItem::getPid() const { |
| return mPid; |
| } |
| |
| MediaAnalyticsItem &MediaAnalyticsItem::setUid(uid_t uid) { |
| mUid = uid; |
| return *this; |
| } |
| |
| uid_t MediaAnalyticsItem::getUid() const { |
| return mUid; |
| } |
| |
| MediaAnalyticsItem &MediaAnalyticsItem::setPkgName(const std::string &pkgName) { |
| mPkgName = pkgName; |
| return *this; |
| } |
| |
| MediaAnalyticsItem &MediaAnalyticsItem::setPkgVersionCode(int64_t pkgVersionCode) { |
| mPkgVersionCode = pkgVersionCode; |
| return *this; |
| } |
| |
| int64_t MediaAnalyticsItem::getPkgVersionCode() const { |
| return mPkgVersionCode; |
| } |
| |
| |
| // find the proper entry in the list |
| size_t MediaAnalyticsItem::findPropIndex(const char *name) const |
| { |
| size_t i = 0; |
| for (; i < mPropCount; i++) { |
| if (mProps[i].isNamed(name)) break; |
| } |
| return i; |
| } |
| |
| MediaAnalyticsItem::Prop *MediaAnalyticsItem::findProp(const char *name) const { |
| const size_t i = findPropIndex(name); |
| if (i < mPropCount) { |
| return &mProps[i]; |
| } |
| return nullptr; |
| } |
| |
| // consider this "find-or-allocate". |
| // caller validates type and uses clearPropValue() accordingly |
| MediaAnalyticsItem::Prop *MediaAnalyticsItem::allocateProp(const char *name) { |
| const size_t i = findPropIndex(name); |
| if (i < mPropCount) { |
| return &mProps[i]; // already have it, return |
| } |
| |
| Prop *prop = allocateProp(); // get a new prop |
| if (prop == nullptr) return nullptr; |
| prop->setName(name); |
| return prop; |
| } |
| |
| MediaAnalyticsItem::Prop *MediaAnalyticsItem::allocateProp() { |
| if (mPropCount == mPropSize && growProps() == false) { |
| ALOGE("%s: failed allocation for new properties", __func__); |
| return nullptr; |
| } |
| return &mProps[mPropCount++]; |
| } |
| |
| // used within the summarizers; return whether property existed |
| bool MediaAnalyticsItem::removeProp(const char *name) { |
| const size_t i = findPropIndex(name); |
| if (i < mPropCount) { |
| mProps[i].clear(); |
| if (i != mPropCount-1) { |
| // in the middle, bring last one down to fill gap |
| mProps[i].swap(mProps[mPropCount-1]); |
| } |
| mPropCount--; |
| return true; |
| } |
| return false; |
| } |
| |
| // remove indicated keys and their values |
| // return value is # keys removed |
| size_t MediaAnalyticsItem::filter(size_t n, const char *attrs[]) { |
| size_t zapped = 0; |
| for (size_t i = 0; i < n; ++i) { |
| const char *name = attrs[i]; |
| size_t j = findPropIndex(name); |
| if (j >= mPropCount) { |
| // not there |
| continue; |
| } else if (j + 1 == mPropCount) { |
| // last one, shorten |
| zapped++; |
| mProps[j].clear(); |
| mPropCount--; |
| } else { |
| // in the middle, bring last one down and shorten |
| zapped++; |
| mProps[j].clear(); |
| mProps[j] = mProps[mPropCount-1]; |
| mPropCount--; |
| } |
| } |
| return zapped; |
| } |
| |
| // remove any keys NOT in the provided list |
| // return value is # keys removed |
| size_t MediaAnalyticsItem::filterNot(size_t n, const char *attrs[]) { |
| std::set<std::string> check(attrs, attrs + n); |
| size_t zapped = 0; |
| for (size_t j = 0; j < mPropCount;) { |
| if (check.find(mProps[j].getName()) != check.end()) { |
| ++j; |
| continue; |
| } |
| if (j + 1 == mPropCount) { |
| // last one, shorten |
| zapped++; |
| mProps[j].clear(); |
| mPropCount--; |
| break; |
| } else { |
| // in the middle, bring last one down and shorten |
| zapped++; |
| mProps[j].clear(); |
| mProps[j] = mProps[mPropCount-1]; |
| mPropCount--; |
| } |
| } |
| return zapped; |
| } |
| |
| bool MediaAnalyticsItem::growProps(int increment) |
| { |
| if (increment <= 0) { |
| increment = kGrowProps; |
| } |
| int nsize = mPropSize + increment; |
| Prop *ni = (Prop *)realloc(mProps, sizeof(Prop) * nsize); |
| |
| if (ni != NULL) { |
| for (int i = mPropSize; i < nsize; i++) { |
| new (&ni[i]) Prop(); // placement new |
| } |
| mProps = ni; |
| mPropSize = nsize; |
| return true; |
| } else { |
| ALOGW("MediaAnalyticsItem::growProps fails"); |
| return false; |
| } |
| } |
| |
| // Parcel / serialize things for binder calls |
| // |
| |
| status_t MediaAnalyticsItem::readFromParcel(const Parcel& data) { |
| int32_t version; |
| status_t status = data.readInt32(&version); |
| if (status != NO_ERROR) return status; |
| |
| switch (version) { |
| case 0: |
| return readFromParcel0(data); |
| default: |
| ALOGE("%s: unsupported parcel version: %d", __func__, version); |
| return INVALID_OPERATION; |
| } |
| } |
| |
| status_t MediaAnalyticsItem::readFromParcel0(const Parcel& data) { |
| const char *s = data.readCString(); |
| mKey = s == nullptr ? "" : s; |
| int32_t pid, uid; |
| status_t status = data.readInt32(&pid) ?: data.readInt32(&uid); |
| if (status != NO_ERROR) return status; |
| mPid = (pid_t)pid; |
| mUid = (uid_t)uid; |
| s = data.readCString(); |
| mPkgName = s == nullptr ? "" : s; |
| int32_t count; |
| int64_t version, timestamp; |
| status = data.readInt64(&version) ?: data.readInt64(×tamp) ?: data.readInt32(&count); |
| if (status != NO_ERROR) return status; |
| if (count < 0) return BAD_VALUE; |
| mPkgVersionCode = version; |
| mTimestamp = timestamp; |
| for (int i = 0; i < count ; i++) { |
| Prop *prop = allocateProp(); |
| status_t status = prop->readFromParcel(data); |
| if (status != NO_ERROR) return status; |
| } |
| return NO_ERROR; |
| } |
| |
| status_t MediaAnalyticsItem::writeToParcel(Parcel *data) const { |
| if (data == nullptr) return BAD_VALUE; |
| |
| const int32_t version = 0; |
| status_t status = data->writeInt32(version); |
| if (status != NO_ERROR) return status; |
| |
| switch (version) { |
| case 0: |
| return writeToParcel0(data); |
| default: |
| ALOGE("%s: unsupported parcel version: %d", __func__, version); |
| return INVALID_OPERATION; |
| } |
| } |
| |
| status_t MediaAnalyticsItem::writeToParcel0(Parcel *data) const { |
| status_t status = |
| data->writeCString(mKey.c_str()) |
| ?: data->writeInt32(mPid) |
| ?: data->writeInt32(mUid) |
| ?: data->writeCString(mPkgName.c_str()) |
| ?: data->writeInt64(mPkgVersionCode) |
| ?: data->writeInt64(mTimestamp); |
| if (status != NO_ERROR) return status; |
| |
| data->writeInt32((int32_t)mPropCount); |
| for (size_t i = 0 ; i < mPropCount; ++i) { |
| status = mProps[i].writeToParcel(data); |
| if (status != NO_ERROR) return status; |
| } |
| return NO_ERROR; |
| } |
| |
| const char *MediaAnalyticsItem::toCString() { |
| return toCString(PROTO_LAST); |
| } |
| |
| const char * MediaAnalyticsItem::toCString(int version) { |
| std::string val = toString(version); |
| return strdup(val.c_str()); |
| } |
| |
| std::string MediaAnalyticsItem::toString() const { |
| return toString(PROTO_LAST); |
| } |
| |
| std::string MediaAnalyticsItem::toString(int version) const { |
| std::string result; |
| char buffer[kMaxPropertyStringSize]; |
| |
| snprintf(buffer, sizeof(buffer), "[%d:%s:%d:%d:%lld:%s:%zu:", |
| version, mKey.c_str(), mPid, mUid, (long long)mTimestamp, |
| mPkgName.c_str(), mPropCount); |
| result.append(buffer); |
| for (size_t i = 0 ; i < mPropCount; ++i) { |
| mProps[i].toString(buffer, sizeof(buffer)); |
| result.append(buffer); |
| } |
| result.append("]"); |
| return result; |
| } |
| |
| // for the lazy, we offer methods that finds the service and |
| // calls the appropriate daemon |
| bool MediaAnalyticsItem::selfrecord() { |
| ALOGD_IF(DEBUG_API, "%s: delivering %s", __func__, this->toString().c_str()); |
| sp<IMediaAnalyticsService> svc = getInstance(); |
| if (svc != NULL) { |
| status_t status = svc->submit(this); |
| if (status != NO_ERROR) { |
| ALOGW("%s: failed to record: %s", __func__, this->toString().c_str()); |
| return false; |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| namespace mediametrics { |
| //static |
| bool BaseItem::isEnabled() { |
| // completely skip logging from certain UIDs. We do this here |
| // to avoid the multi-second timeouts while we learn that |
| // sepolicy will not let us find the service. |
| // We do this only for a select set of UIDs |
| // The sepolicy protection is still in place, we just want a faster |
| // response from this specific, small set of uids. |
| |
| // This is checked only once in the lifetime of the process. |
| const uid_t uid = getuid(); |
| switch (uid) { |
| case AID_RADIO: // telephony subsystem, RIL |
| return false; |
| } |
| |
| int enabled = property_get_int32(MediaAnalyticsItem::EnabledProperty, -1); |
| if (enabled == -1) { |
| enabled = property_get_int32(MediaAnalyticsItem::EnabledPropertyPersist, -1); |
| } |
| if (enabled == -1) { |
| enabled = MediaAnalyticsItem::EnabledProperty_default; |
| } |
| return enabled > 0; |
| } |
| |
| // monitor health of our connection to the metrics service |
| class MediaMetricsDeathNotifier : public IBinder::DeathRecipient { |
| virtual void binderDied(const wp<IBinder> &) { |
| ALOGW("Reacquire service connection on next request"); |
| BaseItem::dropInstance(); |
| } |
| }; |
| |
| static sp<MediaMetricsDeathNotifier> sNotifier; |
| // static |
| sp<IMediaAnalyticsService> BaseItem::sAnalyticsService; |
| static std::mutex sServiceMutex; |
| static int sRemainingBindAttempts = SVC_TRIES; |
| |
| // static |
| void BaseItem::dropInstance() { |
| std::lock_guard _l(sServiceMutex); |
| sRemainingBindAttempts = SVC_TRIES; |
| sAnalyticsService = nullptr; |
| } |
| |
| // static |
| bool BaseItem::submitBuffer(const char *buffer, size_t size) { |
| /* |
| MediaAnalyticsItem item; |
| status_t status = item.readFromByteString(buffer, size); |
| ALOGD("%s: status:%d, size:%zu, item:%s", __func__, status, size, item.toString().c_str()); |
| return item.selfrecord(); |
| */ |
| |
| ALOGD_IF(DEBUG_API, "%s: delivering %zu bytes", __func__, size); |
| sp<IMediaAnalyticsService> svc = getInstance(); |
| if (svc != nullptr) { |
| const status_t status = svc->submitBuffer(buffer, size); |
| if (status != NO_ERROR) { |
| ALOGW("%s: failed(%d) to record: %zu bytes", __func__, status, size); |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| //static |
| sp<IMediaAnalyticsService> BaseItem::getInstance() { |
| static const char *servicename = "media.metrics"; |
| static const bool enabled = isEnabled(); // singleton initialized |
| |
| if (enabled == false) { |
| ALOGD_IF(DEBUG_SERVICEACCESS, "disabled"); |
| return nullptr; |
| } |
| std::lock_guard _l(sServiceMutex); |
| // think of remainingBindAttempts as telling us whether service == nullptr because |
| // (1) we haven't tried to initialize it yet |
| // (2) we've tried to initialize it, but failed. |
| if (sAnalyticsService == nullptr && sRemainingBindAttempts > 0) { |
| const char *badness = ""; |
| sp<IServiceManager> sm = defaultServiceManager(); |
| if (sm != nullptr) { |
| sp<IBinder> binder = sm->getService(String16(servicename)); |
| if (binder != nullptr) { |
| sAnalyticsService = interface_cast<IMediaAnalyticsService>(binder); |
| sNotifier = new MediaMetricsDeathNotifier(); |
| binder->linkToDeath(sNotifier); |
| } else { |
| badness = "did not find service"; |
| } |
| } else { |
| badness = "No Service Manager access"; |
| } |
| if (sAnalyticsService == nullptr) { |
| if (sRemainingBindAttempts > 0) { |
| sRemainingBindAttempts--; |
| } |
| ALOGD_IF(DEBUG_SERVICEACCESS, "%s: unable to bind to service %s: %s", |
| __func__, servicename, badness); |
| } |
| } |
| return sAnalyticsService; |
| } |
| |
| } // namespace mediametrics |
| |
| // merge the info from 'incoming' into this record. |
| // we finish with a union of this+incoming and special handling for collisions |
| bool MediaAnalyticsItem::merge(MediaAnalyticsItem *incoming) { |
| |
| // if I don't have key or session id, take them from incoming |
| // 'this' should never be missing both of them... |
| if (mKey.empty()) { |
| mKey = incoming->mKey; |
| } |
| |
| // for each attribute from 'incoming', resolve appropriately |
| int nattr = incoming->mPropCount; |
| for (int i = 0 ; i < nattr; i++ ) { |
| Prop *iprop = &incoming->mProps[i]; |
| const char *p = iprop->mName; |
| size_t len = strlen(p); |
| |
| // should ignore a zero length name... |
| if (len == 0) { |
| continue; |
| } |
| |
| Prop *oprop = findProp(iprop->mName); |
| |
| if (oprop == NULL) { |
| // no oprop, so we insert the new one |
| oprop = allocateProp(p); |
| if (oprop != NULL) { |
| *oprop = *iprop; |
| } else { |
| ALOGW("dropped property '%s'", iprop->mName); |
| } |
| } else { |
| *oprop = *iprop; |
| } |
| } |
| |
| // not sure when we'd return false... |
| return true; |
| } |
| |
| namespace { |
| |
| template <typename T> |
| status_t insert(const T& val, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t size = sizeof(val); |
| if (*bufferpptr + size > bufferptrmax) { |
| ALOGE("%s: buffer exceeded with size %zu", __func__, size); |
| return BAD_VALUE; |
| } |
| memcpy(*bufferpptr, &val, size); |
| *bufferpptr += size; |
| return NO_ERROR; |
| } |
| |
| template <> |
| status_t insert(const char * const& val, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t size = strlen(val) + 1; |
| if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) { |
| ALOGE("%s: buffer exceeded with size %zu", __func__, size); |
| return BAD_VALUE; |
| } |
| memcpy(*bufferpptr, val, size); |
| *bufferpptr += size; |
| return NO_ERROR; |
| } |
| |
| template <> |
| __unused |
| status_t insert(char * const& val, char **bufferpptr, char *bufferptrmax) |
| { |
| return insert((const char *)val, bufferpptr, bufferptrmax); |
| } |
| |
| template <typename T> |
| status_t extract(T *val, const char **bufferpptr, const char *bufferptrmax) |
| { |
| const size_t size = sizeof(*val); |
| if (*bufferpptr + size > bufferptrmax) { |
| ALOGE("%s: buffer exceeded with size %zu", __func__, size); |
| return BAD_VALUE; |
| } |
| memcpy(val, *bufferpptr, size); |
| *bufferpptr += size; |
| return NO_ERROR; |
| } |
| |
| template <> |
| status_t extract(char **val, const char **bufferpptr, const char *bufferptrmax) |
| { |
| const char *ptr = *bufferpptr; |
| while (*ptr != 0) { |
| if (ptr >= bufferptrmax) { |
| ALOGE("%s: buffer exceeded", __func__); |
| return BAD_VALUE; |
| } |
| ++ptr; |
| } |
| const size_t size = (ptr - *bufferpptr) + 1; |
| *val = (char *)malloc(size); |
| memcpy(*val, *bufferpptr, size); |
| *bufferpptr += size; |
| return NO_ERROR; |
| } |
| |
| } // namespace |
| |
| status_t MediaAnalyticsItem::writeToByteString(char **pbuffer, size_t *plength) const |
| { |
| if (pbuffer == nullptr || plength == nullptr) |
| return BAD_VALUE; |
| |
| // get size |
| const size_t keySizeZeroTerminated = strlen(mKey.c_str()) + 1; |
| if (keySizeZeroTerminated > UINT16_MAX) { |
| ALOGW("%s: key size %zu too large", __func__, keySizeZeroTerminated); |
| return INVALID_OPERATION; |
| } |
| const uint16_t version = 0; |
| const uint32_t header_size = |
| sizeof(uint32_t) // total size |
| + sizeof(header_size) // header size |
| + sizeof(version) // encoding version |
| + sizeof(uint16_t) // key size |
| + keySizeZeroTerminated // key, zero terminated |
| + sizeof(int32_t) // pid |
| + sizeof(int32_t) // uid |
| + sizeof(int64_t) // timestamp |
| ; |
| |
| uint32_t size = header_size |
| + sizeof(uint32_t) // # properties |
| ; |
| for (size_t i = 0 ; i < mPropCount; ++i) { |
| const size_t propSize = mProps[i].getByteStringSize(); |
| if (propSize > UINT16_MAX) { |
| ALOGW("%s: prop %zu size %zu too large", __func__, i, propSize); |
| return INVALID_OPERATION; |
| } |
| if (__builtin_add_overflow(size, propSize, &size)) { |
| ALOGW("%s: item size overflow at property %zu", __func__, i); |
| return INVALID_OPERATION; |
| } |
| } |
| |
| // since we fill every byte in the buffer (there is no padding), |
| // malloc is used here instead of calloc. |
| char * const build = (char *)malloc(size); |
| if (build == nullptr) return NO_MEMORY; |
| |
| char *filling = build; |
| char *buildmax = build + size; |
| if (insert((uint32_t)size, &filling, buildmax) != NO_ERROR |
| || insert(header_size, &filling, buildmax) != NO_ERROR |
| || insert(version, &filling, buildmax) != NO_ERROR |
| || insert((uint16_t)keySizeZeroTerminated, &filling, buildmax) != NO_ERROR |
| || insert(mKey.c_str(), &filling, buildmax) != NO_ERROR |
| || insert((int32_t)mPid, &filling, buildmax) != NO_ERROR |
| || insert((int32_t)mUid, &filling, buildmax) != NO_ERROR |
| || insert((int64_t)mTimestamp, &filling, buildmax) != NO_ERROR |
| || insert((uint32_t)mPropCount, &filling, buildmax) != NO_ERROR) { |
| ALOGE("%s:could not write header", __func__); // shouldn't happen |
| free(build); |
| return INVALID_OPERATION; |
| } |
| for (size_t i = 0 ; i < mPropCount; ++i) { |
| if (mProps[i].writeToByteString(&filling, buildmax) != NO_ERROR) { |
| free(build); |
| // shouldn't happen |
| ALOGE("%s:could not write prop %zu of %zu", __func__, i, mPropCount); |
| return INVALID_OPERATION; |
| } |
| } |
| |
| if (filling != buildmax) { |
| ALOGE("%s: problems populating; wrote=%d planned=%d", |
| __func__, (int)(filling - build), (int)size); |
| free(build); |
| return INVALID_OPERATION; |
| } |
| *pbuffer = build; |
| *plength = size; |
| return NO_ERROR; |
| } |
| |
| status_t MediaAnalyticsItem::readFromByteString(const char *bufferptr, size_t length) |
| { |
| if (bufferptr == nullptr) return BAD_VALUE; |
| |
| const char *read = bufferptr; |
| const char *readend = bufferptr + length; |
| |
| uint32_t size; |
| uint32_t header_size; |
| uint16_t version; |
| uint16_t key_size; |
| char *key = nullptr; |
| int32_t pid; |
| int32_t uid; |
| int64_t timestamp; |
| uint32_t propCount; |
| if (extract(&size, &read, readend) != NO_ERROR |
| || extract(&header_size, &read, readend) != NO_ERROR |
| || extract(&version, &read, readend) != NO_ERROR |
| || extract(&key_size, &read, readend) != NO_ERROR |
| || extract(&key, &read, readend) != NO_ERROR |
| || extract(&pid, &read, readend) != NO_ERROR |
| || extract(&uid, &read, readend) != NO_ERROR |
| || extract(×tamp, &read, readend) != NO_ERROR |
| || size > length |
| || strlen(key) + 1 != key_size |
| || header_size > size) { |
| free(key); |
| ALOGW("%s: invalid header", __func__); |
| return INVALID_OPERATION; |
| } |
| mKey = key; |
| free(key); |
| const size_t pos = read - bufferptr; |
| if (pos > header_size) { |
| ALOGW("%s: invalid header pos:%zu > header_size:%u", |
| __func__, pos, header_size); |
| return INVALID_OPERATION; |
| } else if (pos < header_size) { |
| ALOGW("%s: mismatched header pos:%zu < header_size:%u, advancing", |
| __func__, pos, header_size); |
| read += (header_size - pos); |
| } |
| if (extract(&propCount, &read, readend) != NO_ERROR) { |
| ALOGD("%s: cannot read prop count", __func__); |
| return INVALID_OPERATION; |
| } |
| mPid = pid; |
| mUid = uid; |
| mTimestamp = timestamp; |
| for (size_t i = 0; i < propCount; ++i) { |
| Prop *prop = allocateProp(); |
| if (prop->readFromByteString(&read, readend) != NO_ERROR) { |
| ALOGW("%s: cannot read prop %zu", __func__, i); |
| return INVALID_OPERATION; |
| } |
| } |
| return NO_ERROR; |
| } |
| |
| status_t MediaAnalyticsItem::Prop::writeToParcel(Parcel *data) const |
| { |
| switch (mType) { |
| case kTypeInt32: |
| return data->writeCString(mName) |
| ?: data->writeInt32(mType) |
| ?: data->writeInt32(u.int32Value); |
| case kTypeInt64: |
| return data->writeCString(mName) |
| ?: data->writeInt32(mType) |
| ?: data->writeInt64(u.int64Value); |
| case kTypeDouble: |
| return data->writeCString(mName) |
| ?: data->writeInt32(mType) |
| ?: data->writeDouble(u.doubleValue); |
| case kTypeRate: |
| return data->writeCString(mName) |
| ?: data->writeInt32(mType) |
| ?: data->writeInt64(u.rate.first) |
| ?: data->writeInt64(u.rate.second); |
| case kTypeCString: |
| return data->writeCString(mName) |
| ?: data->writeInt32(mType) |
| ?: data->writeCString(u.CStringValue); |
| default: |
| ALOGE("%s: found bad type: %d, name %s", __func__, mType, mName); |
| return BAD_VALUE; |
| } |
| } |
| |
| status_t MediaAnalyticsItem::Prop::readFromParcel(const Parcel& data) |
| { |
| const char *key = data.readCString(); |
| if (key == nullptr) return BAD_VALUE; |
| int32_t type; |
| status_t status = data.readInt32(&type); |
| if (status != NO_ERROR) return status; |
| switch (type) { |
| case kTypeInt32: |
| status = data.readInt32(&u.int32Value); |
| break; |
| case kTypeInt64: |
| status = data.readInt64(&u.int64Value); |
| break; |
| case kTypeDouble: |
| status = data.readDouble(&u.doubleValue); |
| break; |
| case kTypeCString: { |
| const char *s = data.readCString(); |
| if (s == nullptr) return BAD_VALUE; |
| set(s); |
| break; |
| } |
| case kTypeRate: { |
| std::pair<int64_t, int64_t> rate; |
| status = data.readInt64(&rate.first) |
| ?: data.readInt64(&rate.second); |
| if (status == NO_ERROR) { |
| set(rate); |
| } |
| break; |
| } |
| default: |
| ALOGE("%s: reading bad item type: %d", __func__, mType); |
| return BAD_VALUE; |
| } |
| if (status == NO_ERROR) { |
| setName(key); |
| mType = (Type)type; |
| } |
| return status; |
| } |
| |
| void MediaAnalyticsItem::Prop::toString(char *buffer, size_t length) const |
| { |
| switch (mType) { |
| case kTypeInt32: |
| snprintf(buffer, length, "%s=%d:", mName, u.int32Value); |
| break; |
| case MediaAnalyticsItem::kTypeInt64: |
| snprintf(buffer, length, "%s=%lld:", mName, (long long)u.int64Value); |
| break; |
| case MediaAnalyticsItem::kTypeDouble: |
| snprintf(buffer, length, "%s=%e:", mName, u.doubleValue); |
| break; |
| case MediaAnalyticsItem::kTypeRate: |
| snprintf(buffer, length, "%s=%lld/%lld:", |
| mName, (long long)u.rate.first, (long long)u.rate.second); |
| break; |
| case MediaAnalyticsItem::kTypeCString: |
| // TODO sanitize string for ':' '=' |
| snprintf(buffer, length, "%s=%s:", mName, u.CStringValue); |
| break; |
| default: |
| ALOGE("%s: bad item type: %d for %s", __func__, mType, mName); |
| if (length > 0) buffer[0] = 0; |
| break; |
| } |
| } |
| |
| size_t MediaAnalyticsItem::Prop::getByteStringSize() const |
| { |
| const size_t header = |
| sizeof(uint16_t) // length |
| + sizeof(uint8_t) // type |
| + strlen(mName) + 1; // mName + 0 termination |
| size_t payload = 0; |
| switch (mType) { |
| case MediaAnalyticsItem::kTypeInt32: |
| payload = sizeof(u.int32Value); |
| break; |
| case MediaAnalyticsItem::kTypeInt64: |
| payload = sizeof(u.int64Value); |
| break; |
| case MediaAnalyticsItem::kTypeDouble: |
| payload = sizeof(u.doubleValue); |
| break; |
| case MediaAnalyticsItem::kTypeRate: |
| payload = sizeof(u.rate.first) + sizeof(u.rate.second); |
| break; |
| case MediaAnalyticsItem::kTypeCString: |
| payload = strlen(u.CStringValue) + 1; |
| break; |
| default: |
| ALOGE("%s: found bad prop type: %d, name %s", |
| __func__, mType, mName); // no payload computed |
| break; |
| } |
| return header + payload; |
| } |
| |
| namespace mediametrics { |
| |
| // TODO: fold into a template later. |
| status_t BaseItem::writeToByteString( |
| const char *name, int32_t value, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value); |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeInt32, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax) |
| ?: insert(value, bufferpptr, bufferptrmax); |
| } |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, int64_t value, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value); |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeInt64, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax) |
| ?: insert(value, bufferpptr, bufferptrmax); |
| } |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, double value, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value); |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeDouble, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax) |
| ?: insert(value, bufferpptr, bufferptrmax); |
| } |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1 + 8 + 8; |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeRate, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax) |
| ?: insert(value.first, bufferpptr, bufferptrmax) |
| ?: insert(value.second, bufferpptr, bufferptrmax); |
| } |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, char * const &value, char **bufferpptr, char *bufferptrmax) |
| { |
| return writeToByteString(name, (const char *)value, bufferpptr, bufferptrmax); |
| } |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1 + strlen(value) + 1; |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax) |
| ?: insert(value, bufferpptr, bufferptrmax); |
| } |
| |
| |
| status_t BaseItem::writeToByteString( |
| const char *name, const none_t &, char **bufferpptr, char *bufferptrmax) |
| { |
| const size_t len = 2 + 1 + strlen(name) + 1; |
| if (len > UINT16_MAX) return BAD_VALUE; |
| return insert((uint16_t)len, bufferpptr, bufferptrmax) |
| ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax) |
| ?: insert(name, bufferpptr, bufferptrmax); |
| } |
| |
| } // namespace mediametrics |
| |
| status_t MediaAnalyticsItem::Prop::writeToByteString( |
| char **bufferpptr, char *bufferptrmax) const |
| { |
| switch (mType) { |
| case kTypeInt32: |
| return BaseItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax); |
| case kTypeInt64: |
| return BaseItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax); |
| case kTypeDouble: |
| return BaseItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax); |
| case kTypeRate: |
| return BaseItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax); |
| case kTypeCString: |
| return BaseItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax); |
| case kTypeNone: |
| return BaseItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax); |
| default: |
| ALOGE("%s: found bad prop type: %d, name %s", |
| __func__, mType, mName); // no payload sent |
| return BAD_VALUE; |
| } |
| } |
| |
| status_t MediaAnalyticsItem::Prop::readFromByteString( |
| const char **bufferpptr, const char *bufferptrmax) |
| { |
| uint16_t len; |
| char *name; |
| uint8_t type; |
| status_t status = extract(&len, bufferpptr, bufferptrmax) |
| ?: extract(&type, bufferpptr, bufferptrmax) |
| ?: extract(&name, bufferpptr, bufferptrmax); |
| if (status != NO_ERROR) return status; |
| if (mName != nullptr) { |
| free(mName); |
| } |
| mName = name; |
| if (mType == kTypeCString) { |
| free(u.CStringValue); |
| u.CStringValue = nullptr; |
| } |
| mType = (Type)type; |
| switch (mType) { |
| case kTypeInt32: |
| return extract(&u.int32Value, bufferpptr, bufferptrmax); |
| case kTypeInt64: |
| return extract(&u.int64Value, bufferpptr, bufferptrmax); |
| case kTypeDouble: |
| return extract(&u.doubleValue, bufferpptr, bufferptrmax); |
| case kTypeRate: |
| return extract(&u.rate.first, bufferpptr, bufferptrmax) |
| ?: extract(&u.rate.second, bufferpptr, bufferptrmax); |
| case kTypeCString: |
| status = extract(&u.CStringValue, bufferpptr, bufferptrmax); |
| if (status != NO_ERROR) mType = kTypeNone; |
| return status; |
| case kTypeNone: |
| return NO_ERROR; |
| default: |
| mType = kTypeNone; |
| ALOGE("%s: found bad prop type: %d, name %s", |
| __func__, mType, mName); // no payload sent |
| return BAD_VALUE; |
| } |
| } |
| |
| } // namespace android |