blob: 8025e49fa9a905f2f38536e5ea3fecc95e78c5c6 [file] [log] [blame]
/*
* 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(&timestamp) ?: 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(&timestamp, &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