/* * Copyright (C) 2017 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 STATSD_DEBUG false // STOPSHIP if true #include "Log.h" #include "logd/LogEvent.h" #include #include #include #include "flags/FlagProvider.h" #include "stats_annotations.h" #include "stats_log_util.h" #include "statslog_statsd.h" namespace android { namespace os { namespace statsd { // for TrainInfo experiment id serialization const int FIELD_ID_EXPERIMENT_ID = 1; using namespace android::util; using android::base::StringPrintf; using android::util::ProtoOutputStream; using std::string; using std::vector; namespace { uint8_t getTypeId(uint8_t typeInfo) { return typeInfo & 0x0F; // type id in lower 4 bytes } uint8_t getNumAnnotations(uint8_t typeInfo) { return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes } } // namespace LogEvent::LogEvent(int32_t uid, int32_t pid) : mLogdTimestampNs(getWallClockNs()), mLogUid(uid), mLogPid(pid) { } LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging, bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state, const std::vector& experimentIds, int32_t userId) { mLogdTimestampNs = getWallClockNs(); mElapsedTimestampNs = getElapsedRealtimeNs(); mTagId = util::BINARY_PUSH_STATE_CHANGED; mLogUid = AIBinder_getCallingUid(); mLogPid = AIBinder_getCallingPid(); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled))); mValues.push_back( FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId))); } LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& trainInfo) { mLogdTimestampNs = wallClockTimestampNs; mElapsedTimestampNs = elapsedTimestampNs; mTagId = util::TRAIN_INFO; mValues.push_back( FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); std::vector experimentIdsProto; writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue(); addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int64_t value = readNextValue(); addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numBytes = readNextValue(); if ((uint32_t)numBytes > mRemainingLen) { mValid = false; return; } string value = string((char*)mBuf, numBytes); mBuf += numBytes; mRemainingLen -= numBytes; addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { float value = readNextValue(); addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { // cast to int32_t because FieldValue does not support bools int32_t value = (int32_t)readNextValue(); addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numBytes = readNextValue(); if ((uint32_t)numBytes > mRemainingLen) { mValid = false; return; } vector value(mBuf, mBuf + numBytes); mBuf += numBytes; mRemainingLen -= numBytes; addToValues(pos, depth, value, last); parseAnnotations(numAnnotations); } void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t numPairs = readNextValue(); for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) { last[1] = (pos[1] == numPairs); // parse key pos[2] = 1; parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); // parse value last[2] = true; uint8_t typeInfo = readNextValue(); switch (getTypeId(typeInfo)) { case INT32_TYPE: pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case INT64_TYPE: pos[2] = 3; parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case STRING_TYPE: pos[2] = 4; parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; case FLOAT_TYPE: pos[2] = 5; parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0); break; default: mValid = false; } } parseAnnotations(numAnnotations); pos[1] = pos[2] = 1; last[1] = last[2] = false; } void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { std::optional firstUidInChainIndex = mValues.size(); const uint8_t numNodes = readNextValue(); if (numNodes > INT8_MAX) mValid = false; for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { last[1] = (pos[1] == numNodes); // parse uid pos[2] = 1; parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0); // parse tag pos[2] = 2; last[2] = true; parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); } if (mValues.size() > (firstUidInChainIndex.value() + 1)) { // At least one node was successfully parsed. mAttributionChainStartIndex = firstUidInChainIndex; mAttributionChainEndIndex = mValues.size() - 1; } else { firstUidInChainIndex = std::nullopt; mValid = false; } if (mValid) { parseAnnotations(numAnnotations, /*numElements*/ std::nullopt, firstUidInChainIndex); } pos[1] = pos[2] = 1; last[1] = last[2] = false; } void LogEvent::parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { const uint8_t numElements = readNextValue(); const uint8_t typeInfo = readNextValue(); const uint8_t typeId = getTypeId(typeInfo); if (numElements > INT8_MAX) mValid = false; for (pos[1] = 1; pos[1] <= numElements; pos[1]++) { last[1] = (pos[1] == numElements); // The top-level array is at depth 0, and all of its elements are at depth 1. // Once nested fields are supported, array elements will be at top-level depth + 1. switch (typeId) { case INT32_TYPE: parseInt32(pos, /*depth=*/1, last, /*numAnnotations=*/0); break; case INT64_TYPE: parseInt64(pos, /*depth=*/1, last, /*numAnnotations=*/0); break; case FLOAT_TYPE: parseFloat(pos, /*depth=*/1, last, /*numAnnotations=*/0); break; case BOOL_TYPE: parseBool(pos, /*depth=*/1, last, /*numAnnotations=*/0); break; case STRING_TYPE: parseString(pos, /*depth=*/1, last, /*numAnnotations=*/0); break; default: mValid = false; break; } } parseAnnotations(numAnnotations, numElements); pos[1] = 1; last[1] = false; } // Assumes that mValues is not empty bool LogEvent::checkPreviousValueType(Type expected) const { return mValues[mValues.size() - 1].mValue.getType() == expected; } void LogEvent::parseIsUidAnnotation(uint8_t annotationType, std::optional numElements) { // Need to set numElements if not an array. if (!numElements) { numElements = 1; } // If array is empty, skip uid parsing. if (numElements == 0 && annotationType == BOOL_TYPE) { readNextValue(); return; } // Allowed types: INT, repeated INT if (mValues.empty() || numElements > mValues.size() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) { VLOG("Atom ID %d error while parseIsUidAnnotation()", mTagId); mValid = false; return; } bool isUid = readNextValue(); if (isUid) { mNumUidFields += numElements.value(); } for (int i = 1; i <= numElements; i++) { mValues[mValues.size() - i].mAnnotations.setUidField(isUid); } } void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { if (!mValues.empty() || annotationType != BOOL_TYPE) { VLOG("Atom ID %d error while parseTruncateTimestampAnnotation()", mTagId); mValid = false; return; } mTruncateTimestamp = readNextValue(); } void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType, std::optional numElements, std::optional firstUidInChainIndex) { // Allowed types: all types except for attribution chains and repeated fields. if (mValues.empty() || annotationType != BOOL_TYPE || firstUidInChainIndex || numElements) { VLOG("Atom ID %d error while parsePrimaryFieldAnnotation()", mTagId); mValid = false; return; } const bool primaryField = readNextValue(); mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField); } void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, std::optional firstUidInChainIndex) { // Allowed types: attribution chains if (mValues.empty() || annotationType != BOOL_TYPE || !firstUidInChainIndex) { VLOG("Atom ID %d error while parsePrimaryFieldFirstUidAnnotation()", mTagId); mValid = false; return; } if (mValues.size() < firstUidInChainIndex.value() + 1) { // AttributionChain is empty. VLOG("Atom ID %d error while parsePrimaryFieldFirstUidAnnotation()", mTagId); mValid = false; android_errorWriteLog(0x534e4554, "174485572"); return; } const bool primaryField = readNextValue(); mValues[firstUidInChainIndex.value()].mAnnotations.setPrimaryField(primaryField); } void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType, std::optional numElements) { // Allowed types: BOOL if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) || numElements) { VLOG("Atom ID %d error while parseExclusiveStateAnnotation()", mTagId); mValid = false; return; } const bool exclusiveState = readNextValue(); mExclusiveStateFieldIndex = mValues.size() - 1; mValues[getExclusiveStateFieldIndex().value()].mAnnotations.setExclusiveState(exclusiveState); } void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType, std::optional numElements) { // Allowed types: INT if (mValues.empty() || annotationType != INT32_TYPE || !checkPreviousValueType(INT) || numElements) { VLOG("Atom ID %d error while parseTriggerStateResetAnnotation()", mTagId); mValid = false; return; } mResetState = readNextValue(); } void LogEvent::parseStateNestedAnnotation(uint8_t annotationType, std::optional numElements) { // Allowed types: BOOL if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) || numElements) { VLOG("Atom ID %d error while parseStateNestedAnnotation()", mTagId); mValid = false; return; } bool nested = readNextValue(); mValues[mValues.size() - 1].mAnnotations.setNested(nested); } void LogEvent::parseRestrictionCategoryAnnotation(uint8_t annotationType) { // Allowed types: INT, field value should be empty since this is atom-level annotation. if (!mValues.empty() || annotationType != INT32_TYPE) { mValid = false; return; } int value = readNextValue(); // should be one of predefined category in StatsLog.java switch (value) { case ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC: case ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE: case ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION: case ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE: break; default: mValid = false; return; } mRestrictionCategory = static_cast(value); return; } void LogEvent::parseFieldRestrictionAnnotation(uint8_t annotationType) { // Allowed types: BOOL if (mValues.empty() || annotationType != BOOL_TYPE) { mValid = false; return; } // Read the value so that the rest of the event is correctly parsed // TODO: store the field annotations once the metrics need to parse them. readNextValue(); return; } // firstUidInChainIndex is a default parameter that is only needed when parsing // annotations for attribution chains. // numElements is a default param that is only needed when parsing annotations for repeated fields void LogEvent::parseAnnotations(uint8_t numAnnotations, std::optional numElements, std::optional firstUidInChainIndex) { for (uint8_t i = 0; i < numAnnotations; i++) { uint8_t annotationId = readNextValue(); uint8_t annotationType = readNextValue(); switch (annotationId) { case ASTATSLOG_ANNOTATION_ID_IS_UID: parseIsUidAnnotation(annotationType, numElements); break; case ASTATSLOG_ANNOTATION_ID_TRUNCATE_TIMESTAMP: parseTruncateTimestampAnnotation(annotationType); break; case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD: parsePrimaryFieldAnnotation(annotationType, numElements, firstUidInChainIndex); break; case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID: parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex); break; case ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE: parseExclusiveStateAnnotation(annotationType, numElements); break; case ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET: parseTriggerStateResetAnnotation(annotationType, numElements); break; case ASTATSLOG_ANNOTATION_ID_STATE_NESTED: parseStateNestedAnnotation(annotationType, numElements); break; case ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY: if (isAtLeastU()) { parseRestrictionCategoryAnnotation(annotationType); } else { mValid = false; } break; // Currently field restrictions are ignored, so we parse but do not store them. case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING: case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION: if (isAtLeastU()) { parseFieldRestrictionAnnotation(annotationType); } else { mValid = false; } break; default: VLOG("Atom ID %d error while parseAnnotations() - wrong annotationId(%d)", mTagId, annotationId); mValid = false; return; } } } LogEvent::BodyBufferInfo LogEvent::parseHeader(const uint8_t* buf, size_t len) { BodyBufferInfo bodyInfo; mParsedHeaderOnly = true; mBuf = buf; mRemainingLen = (uint32_t)len; // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID uint8_t typeInfo = readNextValue(); if (getTypeId(typeInfo) != OBJECT_TYPE) { mValid = false; mBuf = nullptr; return bodyInfo; } uint8_t numElements = readNextValue(); if (numElements < 2 || numElements > INT8_MAX) { mValid = false; mBuf = nullptr; return bodyInfo; } typeInfo = readNextValue(); if (getTypeId(typeInfo) != INT64_TYPE) { mValid = false; mBuf = nullptr; return bodyInfo; } mElapsedTimestampNs = readNextValue(); numElements--; typeInfo = readNextValue(); if (getTypeId(typeInfo) != INT32_TYPE) { mValid = false; mBuf = nullptr; return bodyInfo; } mTagId = readNextValue(); numElements--; parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations bodyInfo.numElements = numElements; bodyInfo.buffer = mBuf; bodyInfo.bufferSize = mRemainingLen; mBuf = nullptr; return bodyInfo; } bool LogEvent::parseBody(const BodyBufferInfo& bodyInfo) { mParsedHeaderOnly = false; mBuf = bodyInfo.buffer; mRemainingLen = (uint32_t)bodyInfo.bufferSize; int32_t pos[] = {1, 1, 1}; bool last[] = {false, false, false}; // While this number is not guaranteed to be correct due to repeated fields and // attribution chains, it still positively affects performance and reduces the number // of vector buffer reallocations. mValues.reserve(bodyInfo.numElements); for (pos[0] = 1; pos[0] <= bodyInfo.numElements && mValid; pos[0]++) { last[0] = (pos[0] == bodyInfo.numElements); uint8_t typeInfo = readNextValue(); uint8_t typeId = getTypeId(typeInfo); switch (typeId) { case BOOL_TYPE: parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case INT32_TYPE: parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case INT64_TYPE: parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case FLOAT_TYPE: parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case BYTE_ARRAY_TYPE: parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case STRING_TYPE: parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case KEY_VALUE_PAIRS_TYPE: parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case ATTRIBUTION_CHAIN_TYPE: parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case LIST_TYPE: parseArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; case ERROR_TYPE: /* mErrorBitmask =*/readNextValue(); mValid = false; break; default: mValid = false; break; } } if (mRemainingLen != 0) mValid = false; mBuf = nullptr; return mValid; } // This parsing logic is tied to the encoding scheme used in StatsEvent.java and // stats_event.c bool LogEvent::parseBuffer(const uint8_t* buf, size_t len) { BodyBufferInfo bodyInfo = parseHeader(buf, len); // emphasize intention to parse the body, however atom data could be incomplete // if header/body parsing was failed due to invalid buffer content for example mParsedHeaderOnly = false; // early termination if header is invalid if (!mValid) { mBuf = nullptr; return false; } return parseBody(bodyInfo); } int64_t LogEvent::GetLong(size_t key, status_t* err) const { // TODO(b/110561208): encapsulate the magical operations in Field struct as static functions int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == LONG) { return value.mValue.long_value; } else if (value.mValue.getType() == INT) { return value.mValue.int_value; } else { *err = BAD_TYPE; return 0; } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return 0; } int LogEvent::GetInt(size_t key, status_t* err) const { int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == INT) { return value.mValue.int_value; } else { *err = BAD_TYPE; return 0; } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return 0; } const char* LogEvent::GetString(size_t key, status_t* err) const { int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == STRING) { return value.mValue.str_value.c_str(); } else { *err = BAD_TYPE; return 0; } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return NULL; } bool LogEvent::GetBool(size_t key, status_t* err) const { int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == INT) { return value.mValue.int_value != 0; } else if (value.mValue.getType() == LONG) { return value.mValue.long_value != 0; } else { *err = BAD_TYPE; return false; } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return false; } float LogEvent::GetFloat(size_t key, status_t* err) const { int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == FLOAT) { return value.mValue.float_value; } else { *err = BAD_TYPE; return 0.0; } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return 0.0; } std::vector LogEvent::GetStorage(size_t key, status_t* err) const { int field = getSimpleField(key); for (const auto& value : mValues) { if (value.mField.getField() == field) { if (value.mValue.getType() == STORAGE) { return value.mValue.storage_value; } else { *err = BAD_TYPE; return vector(); } } if ((size_t)value.mField.getPosAtDepth(0) > key) { break; } } *err = BAD_INDEX; return vector(); } string LogEvent::ToString() const { string result; result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs, (long long)mElapsedTimestampNs, mTagId); string annotations; if (mTruncateTimestamp) { annotations = "TRUNCATE_TS"; } if (mResetState != -1) { annotations += annotations.size() ? ", RESET_STATE" : "RESET_STATE"; } if (annotations.size()) { result += " [" + annotations + "] "; } if (isParsedHeaderOnly()) { result += " ParsedHeaderOnly }"; return result; } for (const auto& value : mValues) { result += StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString(); result += value.mAnnotations.toString() + " "; } result += " }"; return result; } void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } bool LogEvent::hasAttributionChain(std::pair* indexRange) const { if (!mAttributionChainStartIndex || !mAttributionChainEndIndex) { return false; } if (nullptr != indexRange) { indexRange->first = mAttributionChainStartIndex.value(); indexRange->second = mAttributionChainEndIndex.value(); } return true; } void writeExperimentIdsToProto(const std::vector& experimentIds, std::vector* protoOut) { ProtoOutputStream proto; for (const auto& expId : experimentIds) { proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, (long long)expId); } protoOut->resize(proto.size()); size_t pos = 0; sp reader = proto.data(); while (reader->readBuffer() != NULL) { size_t toRead = reader->currentToRead(); std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead); pos += toRead; reader->move(toRead); } } } // namespace statsd } // namespace os } // namespace android