blob: d75b446247b6153f4e92b92efbcfa37190d82ec7 [file] [log] [blame]
/*
* 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.
*/
#pragma once
#include <android/util/ProtoOutputStream.h>
#include <private/android_logger.h>
#include <optional>
#include <string>
#include <vector>
#include "FieldValue.h"
#include "utils/RestrictedPolicyManager.h"
namespace android {
namespace os {
namespace statsd {
// stats_event.h socket types. Keep in sync.
/* ERRORS */
#define ERROR_NO_TIMESTAMP 0x1
#define ERROR_NO_ATOM_ID 0x2
#define ERROR_OVERFLOW 0x4
#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
#define ERROR_INVALID_ANNOTATION_ID 0x40
#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
#define ERROR_TOO_MANY_ANNOTATIONS 0x100
#define ERROR_TOO_MANY_FIELDS 0x200
#define ERROR_INVALID_VALUE_TYPE 0x400
#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
#define ERROR_ATOM_ID_INVALID_POSITION 0x2000
#define ERROR_LIST_TOO_LONG 0x4000
/* TYPE IDS */
#define INT32_TYPE 0x00
#define INT64_TYPE 0x01
#define STRING_TYPE 0x02
#define LIST_TYPE 0x03
#define FLOAT_TYPE 0x04
#define BOOL_TYPE 0x05
#define BYTE_ARRAY_TYPE 0x06
#define OBJECT_TYPE 0x07
#define KEY_VALUE_PAIRS_TYPE 0x08
#define ATTRIBUTION_CHAIN_TYPE 0x09
#define ERROR_TYPE 0x0F
struct InstallTrainInfo {
int64_t trainVersionCode;
std::string trainName;
int32_t status;
std::vector<int64_t> experimentIds;
bool requiresStaging;
bool rollbackEnabled;
bool requiresLowLatencyMonitor;
};
/**
* This class decodes the structured, serialized encoding of an atom into a
* vector of FieldValues.
*/
class LogEvent {
public:
/**
* \param uid user id of the logging caller
* \param pid process id of the logging caller
*/
explicit LogEvent(int32_t uid, int32_t pid);
/**
* Parses the atomId, timestamp, and vector of values from a buffer
* containing the StatsEvent/AStatsEvent encoding of an atom.
*
* \param buf a buffer that begins at the start of the serialized atom (it
* should not include the android_log_header_t or the StatsEventTag)
* \param len size of the buffer
*
* \return success of the parsing
*/
bool parseBuffer(const uint8_t* buf, size_t len);
struct BodyBufferInfo {
const uint8_t* buffer = nullptr;
size_t bufferSize = 0;
uint8_t numElements = 0;
};
/**
* @brief Parses atom header which consists of atom id, timestamp
* and atom level annotations
* Updates the value of isValid()
* @return BodyBufferInfo to be used for parseBody()
*/
BodyBufferInfo parseHeader(const uint8_t* buf, size_t len);
/**
* @brief Parses atom body which consists of header.numElements elements
* Should be called only with BodyBufferInfo if when logEvent.isValid() == true
* \return success of the parsing
*/
bool parseBody(const BodyBufferInfo& bodyInfo);
// Constructs a BinaryPushStateChanged LogEvent from API call.
explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
const std::vector<uint8_t>& experimentIds, int32_t userId);
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const InstallTrainInfo& installTrainInfo);
~LogEvent() {}
/**
* Get the timestamp associated with this event.
*/
inline int64_t GetLogdTimestampNs() const { return mLogdTimestampNs; }
inline int64_t GetElapsedTimestampNs() const { return mElapsedTimestampNs; }
/**
* Get the tag for this event.
*/
inline int GetTagId() const { return mTagId; }
/**
* Get the uid of the logging client.
* Returns -1 if the uid is unknown/has not been set.
*/
inline int32_t GetUid() const { return mLogUid; }
/**
* Get the pid of the logging client.
* Returns -1 if the pid is unknown/has not been set.
*/
inline int32_t GetPid() const { return mLogPid; }
/**
* Get the nth value, starting at 1.
*
* Returns BAD_INDEX if the index is larger than the number of elements.
* Returns BAD_TYPE if the index is available but the data is the wrong type.
*/
int64_t GetLong(size_t key, status_t* err) const;
int GetInt(size_t key, status_t* err) const;
const char* GetString(size_t key, status_t* err) const;
bool GetBool(size_t key, status_t* err) const;
float GetFloat(size_t key, status_t* err) const;
std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
/**
* Return a string representation of this event.
*/
std::string ToString() const;
/**
* Write this object to a ProtoOutputStream.
*/
void ToProto(android::util::ProtoOutputStream& out) const;
/**
* Set elapsed timestamp if the original timestamp is missing.
*/
void setElapsedTimestampNs(int64_t timestampNs) {
mElapsedTimestampNs = timestampNs;
}
/**
* Set the timestamp if the original logd timestamp is missing.
*/
void setLogdWallClockTimestampNs(int64_t timestampNs) {
mLogdTimestampNs = timestampNs;
}
inline int size() const {
return mValues.size();
}
const std::vector<FieldValue>& getValues() const {
return mValues;
}
std::vector<FieldValue>* getMutableValues() {
return &mValues;
}
// Default value = false
inline bool shouldTruncateTimestamp() const {
return mTruncateTimestamp;
}
inline uint8_t getNumUidFields() const {
return mNumUidFields;
}
// Returns whether this LogEvent has an AttributionChain.
// If it does and indexRange is not a nullptr, populate indexRange with the start and end index
// of the AttributionChain within mValues.
bool hasAttributionChain(std::pair<size_t, size_t>* indexRange = nullptr) const;
// Returns the index of the exclusive state field within the FieldValues vector if
// an exclusive state exists. If there is no exclusive state field, returns -1.
//
// If the index within the atom definition is desired, do the following:
// const std::optional<size_t>& vectorIndex = LogEvent.getExclusiveStateFieldIndex();
// if (!vectorIndex) {
// FieldValue& v = LogEvent.getValues()[vectorIndex.value()];
// int atomIndex = v.mField.getPosAtDepth(0);
// }
// Note that atomIndex is 1-indexed.
inline std::optional<size_t> getExclusiveStateFieldIndex() const {
return mExclusiveStateFieldIndex;
}
// If a reset state is not sent in the StatsEvent, returns -1. Note that a
// reset state is sent if and only if a reset should be triggered.
inline int getResetState() const {
return mResetState;
}
template <class T>
status_t updateValue(size_t key, T& value, Type type) {
int field = getSimpleField(key);
for (auto& fieldValue : mValues) {
if (fieldValue.mField.getField() == field) {
if (fieldValue.mValue.getType() == type) {
fieldValue.mValue = Value(value);
return OK;
} else {
return BAD_TYPE;
}
}
}
return BAD_INDEX;
}
bool isValid() const {
return mValid;
}
/**
* @brief Returns true if only header was parsed
*/
bool isParsedHeaderOnly() const {
return mParsedHeaderOnly;
}
/**
* Only use this if copy is absolutely needed.
*/
LogEvent(const LogEvent&) = default;
inline StatsdRestrictionCategory getRestrictionCategory() const {
return mRestrictionCategory;
}
inline bool isRestricted() const {
return mRestrictionCategory != CATEGORY_NO_RESTRICTION;
}
private:
void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseAnnotations(uint8_t numAnnotations, std::optional<uint8_t> numElements = std::nullopt,
std::optional<size_t> firstUidInChainIndex = std::nullopt);
void parseIsUidAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
void parseTruncateTimestampAnnotation(uint8_t annotationType);
void parsePrimaryFieldAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements,
std::optional<size_t> firstUidInChainIndex);
void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
std::optional<size_t> firstUidInChainIndex);
void parseExclusiveStateAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
void parseTriggerStateResetAnnotation(uint8_t annotationType,
std::optional<uint8_t> numElements);
void parseStateNestedAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
void parseRestrictionCategoryAnnotation(uint8_t annotationType);
void parseFieldRestrictionAnnotation(uint8_t annotationType);
bool checkPreviousValueType(Type expected);
bool getRestrictedMetricsFlag();
/**
* The below two variables are only valid during the execution of
* parseBuffer. There are no guarantees about the state of these variables
* before/after.
*/
const uint8_t* mBuf;
uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
bool mValid = true; // stores whether the event we received from the socket is valid
bool mParsedHeaderOnly = false; // stores whether the only header was parsed skipping the body
/**
* Side-effects:
* If there is enough space in buffer to read value of type T
* - move mBuf past the value that was just read
* - decrement mRemainingLen by size of T
* Else
* - set mValid to false
*/
template <class T>
T readNextValue() {
T value;
if (mRemainingLen < sizeof(T)) {
mValid = false;
value = 0; // all primitive types can successfully cast 0
} else {
// When alignof(T) == 1, hopefully the compiler can optimize away
// this conditional as always true.
if ((reinterpret_cast<uintptr_t>(mBuf) % alignof(T)) == 0) {
// We're properly aligned, and can safely make this assignment.
value = *((T*)mBuf);
} else {
// We need to use memcpy. It's slower, but safe.
memcpy(&value, mBuf, sizeof(T));
}
mBuf += sizeof(T);
mRemainingLen -= sizeof(T);
}
return value;
}
template <class T>
void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) {
Field f = Field(mTagId, pos, depth);
// only decorate last position for depths with repeated fields (depth 1)
if (depth > 0 && last[1]) f.decorateLastPos(1);
Value v = Value(value);
mValues.push_back(FieldValue(f, v));
}
// The items are naturally sorted in DFS order as we read them. this allows us to do fast
// matching.
std::vector<FieldValue> mValues;
// The timestamp set by the logd.
int64_t mLogdTimestampNs;
// The elapsed timestamp set by statsd log writer.
int64_t mElapsedTimestampNs;
// The atom tag of the event (defaults to 0 if client does not
// appropriately set the atom id).
int mTagId = 0;
// The uid of the logging client (defaults to -1).
int32_t mLogUid = -1;
// The pid of the logging client (defaults to -1).
int32_t mLogPid = -1;
// Annotations
bool mTruncateTimestamp = false;
int mResetState = -1;
StatsdRestrictionCategory mRestrictionCategory = CATEGORY_NO_RESTRICTION;
size_t mNumUidFields = 0;
std::optional<size_t> mAttributionChainStartIndex;
std::optional<size_t> mAttributionChainEndIndex;
std::optional<size_t> mExclusiveStateFieldIndex;
};
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
} // namespace statsd
} // namespace os
} // namespace android