blob: 5b413e99cc3b9037cb106e91c94bc6fc173a8236 [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.
*/
#define LOG_TAG "incident_helper"
#include "ih_util.h"
#include <algorithm>
#include <sstream>
#include <unistd.h>
bool isValidChar(char c) {
uint8_t v = (uint8_t)c;
return (v >= (uint8_t)'a' && v <= (uint8_t)'z')
|| (v >= (uint8_t)'A' && v <= (uint8_t)'Z')
|| (v >= (uint8_t)'0' && v <= (uint8_t)'9')
|| (v == (uint8_t)'_');
}
std::string trim(const std::string& s, const std::string& charset) {
const auto head = s.find_first_not_of(charset);
if (head == std::string::npos) return "";
const auto tail = s.find_last_not_of(charset);
return s.substr(head, tail - head + 1);
}
static inline std::string toLowerStr(const std::string& s) {
std::string res(s);
std::transform(res.begin(), res.end(), res.begin(), ::tolower);
return res;
}
static inline std::string trimDefault(const std::string& s) {
return trim(s, DEFAULT_WHITESPACE);
}
static inline std::string trimHeader(const std::string& s) {
return toLowerStr(trimDefault(s));
}
static inline bool isNumber(const std::string& s) {
std::string::const_iterator it = s.begin();
while (it != s.end() && std::isdigit(*it)) ++it;
return !s.empty() && it == s.end();
}
// This is similiar to Split in android-base/file.h, but it won't add empty string
static void split(const std::string& line, std::vector<std::string>& words,
const trans_func& func, const std::string& delimiters) {
words.clear(); // clear the buffer before split
size_t base = 0;
size_t found;
while (true) {
found = line.find_first_of(delimiters, base);
if (found != base) {
std::string word = (*func) (line.substr(base, found - base));
if (!word.empty()) {
words.push_back(word);
}
}
if (found == line.npos) break;
base = found + 1;
}
}
header_t parseHeader(const std::string& line, const std::string& delimiters) {
header_t header;
trans_func f = &trimHeader;
split(line, header, f, delimiters);
return header;
}
record_t parseRecord(const std::string& line, const std::string& delimiters) {
record_t record;
trans_func f = &trimDefault;
split(line, record, f, delimiters);
return record;
}
bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
indices.clear();
size_t lastIndex = 0;
int i = 0;
while (headerNames[i] != NULL) {
string s = headerNames[i];
lastIndex = line.find(s, lastIndex);
if (lastIndex == string::npos) {
fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
return false;
}
lastIndex += s.length();
indices.push_back(lastIndex);
i++;
}
return true;
}
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
record_t record;
int lastIndex = 0;
int lastBeginning = 0;
int lineSize = (int)line.size();
for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
int idx = *it;
if (idx <= lastIndex) {
// We saved up until lastIndex last time, so we should start at
// lastIndex + 1 this time.
idx = lastIndex + 1;
}
if (idx > lineSize) {
if (lastIndex < idx && lastIndex < lineSize) {
// There's a little bit more for us to save, which we'll do
// outside of the loop.
break;
}
// If we're past the end of the line AND we've already saved everything up to the end.
fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
record.clear(); // The indices are wrong, return empty.
return record;
}
while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
lastBeginning = lastIndex;
lastIndex = idx;
}
if (lineSize - lastIndex > 0) {
int beginning = lastIndex;
if (record.size() == indices.size()) {
// We've already encountered all of the columns...put whatever is
// left in the last column.
record.pop_back();
beginning = lastBeginning;
}
record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
}
return record;
}
void printRecord(const record_t& record) {
fprintf(stderr, "Record: { ");
if (record.size() == 0) {
fprintf(stderr, "}\n");
return;
}
for(size_t i = 0; i < record.size(); ++i) {
if(i != 0) fprintf(stderr, "\", ");
fprintf(stderr, "\"%s", record[i].c_str());
}
fprintf(stderr, "\" }\n");
}
bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
if (head == std::string::npos) return false;
int len = (int)line->length();
int i = 0;
int j = head;
while (key[i] != '\0') {
if (j >= len || key[i++] != line->at(j++)) {
return false;
}
}
if (endAtDelimiter) {
// this means if the line only have prefix or no delimiter, we still return false.
if (j == len || isValidChar(line->at(j))) return false;
}
line->assign(trimDefault(line->substr(j)));
return true;
}
bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) {
const auto tail = line->find_last_not_of(DEFAULT_WHITESPACE);
if (tail == std::string::npos) return false;
int i = 0;
while (key[++i] != '\0'); // compute the size of the key
int j = tail;
while (i > 0) {
if (j < 0 || key[--i] != line->at(j--)) {
return false;
}
}
if (endAtDelimiter) {
// this means if the line only have suffix or no delimiter, we still return false.
if (j < 0 || isValidChar(line->at(j))) return false;
}
line->assign(trimDefault(line->substr(0, j+1)));
return true;
}
std::string behead(std::string* line, const char cut) {
auto found = line->find_first_of(cut);
if (found == std::string::npos) {
std::string head = line->substr(0);
line->assign("");
return head;
}
std::string head = line->substr(0, found);
while(line->at(found) == cut) found++; // trim more cut of the rest
line->assign(line->substr(found));
return head;
}
int toInt(const std::string& s) {
return atoi(s.c_str());
}
long long toLongLong(const std::string& s) {
return atoll(s.c_str());
}
double toDouble(const std::string& s) {
return atof(s.c_str());
}
// ==============================================================================
Reader::Reader(const int fd)
{
mFile = fdopen(fd, "r");
mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : "";
}
Reader::~Reader()
{
if (mFile != NULL) fclose(mFile);
}
bool Reader::readLine(std::string* line) {
if (mFile == NULL) return false;
char* buf = NULL;
size_t len = 0;
ssize_t read = getline(&buf, &len, mFile);
if (read != -1) {
std::string s(buf);
line->assign(trim(s, DEFAULT_NEWLINE));
} else if (errno == EINVAL) {
mStatus = "Bad Argument";
}
free(buf);
return read != -1;
}
bool Reader::ok(std::string* error) {
error->assign(mStatus);
return mStatus.empty();
}
// ==============================================================================
Table::Table(const char* names[], const uint64_t ids[], const int count)
:mEnums(),
mEnumValuesByName()
{
map<std::string, uint64_t> fields;
for (int i = 0; i < count; i++) {
fields[names[i]] = ids[i];
}
mFields = fields;
}
Table::~Table()
{
}
void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
{
if (mFields.find(field) == mFields.end()) {
fprintf(stderr, "Field '%s' not found", string(field).c_str());
return;
}
map<std::string, int> enu;
for (int i = 0; i < enumSize; i++) {
enu[enumNames[i]] = enumValues[i];
}
mEnums[field] = enu;
}
void
Table::addEnumNameToValue(const char* enumName, const int enumValue)
{
mEnumValuesByName[enumName] = enumValue;
}
bool
Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
if (mFields.find(name) == mFields.end()) return false;
uint64_t found = mFields[name];
record_t repeats; // used for repeated fields
switch ((found & FIELD_COUNT_MASK) | (found & FIELD_TYPE_MASK)) {
case FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE:
case FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT:
proto->write(found, toDouble(value));
break;
case FIELD_COUNT_SINGLE | FIELD_TYPE_STRING:
case FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES:
proto->write(found, value);
break;
case FIELD_COUNT_SINGLE | FIELD_TYPE_INT64:
case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64:
case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64:
case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64:
case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64:
proto->write(found, toLongLong(value));
break;
case FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL:
if (strcmp(toLowerStr(value).c_str(), "true") == 0 || strcmp(value.c_str(), "1") == 0) {
proto->write(found, true);
break;
}
if (strcmp(toLowerStr(value).c_str(), "false") == 0 || strcmp(value.c_str(), "0") == 0) {
proto->write(found, false);
break;
}
return false;
case FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM:
// if the field has its own enum mapping, use this, otherwise use general name to value mapping.
if (mEnums.find(name) != mEnums.end()) {
if (mEnums[name].find(value) != mEnums[name].end()) {
proto->write(found, mEnums[name][value]);
} else {
proto->write(found, 0); // TODO: should get the default enum value (Unknown)
}
} else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
proto->write(found, mEnumValuesByName[value]);
} else if (isNumber(value)) {
proto->write(found, toInt(value));
} else {
return false;
}
break;
case FIELD_COUNT_SINGLE | FIELD_TYPE_INT32:
case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32:
case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32:
case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32:
case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32:
proto->write(found, toInt(value));
break;
// REPEATED TYPE below:
case FIELD_COUNT_REPEATED | FIELD_TYPE_INT32:
repeats = parseRecord(value, COMMA_DELIMITER);
for (size_t i=0; i<repeats.size(); i++) {
proto->write(found, toInt(repeats[i]));
}
break;
case FIELD_COUNT_REPEATED | FIELD_TYPE_STRING:
repeats = parseRecord(value, COMMA_DELIMITER);
for (size_t i=0; i<repeats.size(); i++) {
proto->write(found, repeats[i]);
}
break;
default:
return false;
}
return true;
}
// ================================================================================
Message::Message(Table* table)
:mTable(table),
mPreviousField(""),
mTokens(),
mSubMessages()
{
}
Message::~Message()
{
}
void
Message::addSubMessage(uint64_t fieldId, Message* fieldMsg)
{
for (auto iter = mTable->mFields.begin(); iter != mTable->mFields.end(); iter++) {
if (iter->second == fieldId) {
mSubMessages[iter->first] = fieldMsg;
return;
}
}
}
bool
Message::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
// If the field name can be found, it means the name is a primitive field.
if (mTable->mFields.find(name) != mTable->mFields.end()) {
endSession(proto);
// The only edge case is for example ro.hardware itself is a message, so a field called "value"
// would be defined in proto Ro::Hardware and it must be the first field.
if (mSubMessages.find(name) != mSubMessages.end()) {
startSession(proto, name);
return mSubMessages[name]->insertField(proto, "value", value);
} else {
return mTable->insertField(proto, name, value);
}
}
// Try to find the message field which is the prefix of name, so the value would be inserted
// recursively into the submessage.
string mutableName = name;
for (auto iter = mSubMessages.begin(); iter != mSubMessages.end(); iter++) {
string fieldName = iter->first;
string prefix = fieldName + "_"; // underscore is the delimiter in the name
if (stripPrefix(&mutableName, prefix.c_str())) {
if (mPreviousField != fieldName) {
endSession(proto);
startSession(proto, fieldName);
}
return mSubMessages[fieldName]->insertField(proto, mutableName, value);
}
}
// Can't find the name in proto definition, handle it separately.
return false;
}
void
Message::startSession(ProtoOutputStream* proto, const string& name)
{
uint64_t fieldId = mTable->mFields[name];
uint64_t token = proto->start(fieldId);
mPreviousField = name;
mTokens.push(token);
}
void
Message::endSession(ProtoOutputStream* proto)
{
if (mPreviousField == "") return;
if (mSubMessages.find(mPreviousField) != mSubMessages.end()) {
mSubMessages[mPreviousField]->endSession(proto);
}
proto->end(mTokens.top());
mTokens.pop();
mPreviousField = "";
}