/* * 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 #include #include 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& 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& indices, const char** headerNames, const std::string& line) { indices.clear(); size_t lastIndex = 0; int i = 0; while (headerNames[i] != nullptr) { std::string s = headerNames[i]; lastIndex = line.find(s, lastIndex); if (lastIndex == std::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& indices, const std::string& delimiters) { record_t record; int lastIndex = 0; int lastBeginning = 0; int lineSize = (int)line.size(); for (std::vector::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() && !record.empty()) { // 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"); mBuffer = new char[1024]; mStatus = mFile == nullptr ? "Invalid fd " + std::to_string(fd) : ""; } Reader::~Reader() { if (mFile != nullptr) fclose(mFile); delete[] mBuffer; } bool Reader::readLine(std::string* line) { if (mFile == nullptr) return false; size_t len = 0; ssize_t read = getline(&mBuffer, &len, mFile); if (read != -1) { std::string s(mBuffer); line->assign(trim(s, DEFAULT_NEWLINE)); return true; } if (!feof(mFile)) { mStatus = "Error reading file. Ferror: " + std::to_string(ferror(mFile)); } return false; } bool Reader::ok(std::string* error) { if (mStatus.empty()) { return true; } error->assign(mStatus); return false; } // ============================================================================== Table::Table(const char* names[], const uint64_t ids[], const int count) :mEnums(), mEnumValuesByName() { std::map 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", field); return; } std::map 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; iwrite(found, toInt(repeats[i])); } break; case FIELD_COUNT_REPEATED | FIELD_TYPE_STRING: repeats = parseRecord(value, COMMA_DELIMITER); for (size_t i=0; iwrite(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. std::string mutableName = name; for (auto iter = mSubMessages.begin(); iter != mSubMessages.end(); iter++) { std::string fieldName = iter->first; std::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 std::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 = ""; }