Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #ifndef INCIDENT_HELPER_UTIL_H
     18 #define INCIDENT_HELPER_UTIL_H
     19 
     20 #include <map>
     21 #include <stack>
     22 #include <string>
     23 #include <vector>
     24 
     25 #include <android/util/ProtoOutputStream.h>
     26 
     27 using namespace android::util;
     28 
     29 typedef std::vector<std::string> header_t;
     30 typedef std::vector<std::string> record_t;
     31 typedef std::string (*trans_func) (const std::string&);
     32 
     33 const std::string DEFAULT_WHITESPACE = " \t";
     34 const std::string DEFAULT_NEWLINE = "\r\n";
     35 const std::string TAB_DELIMITER = "\t";
     36 const std::string COMMA_DELIMITER = ",";
     37 const std::string PIPE_DELIMITER = "|";
     38 const std::string PARENTHESES_DELIMITER = "()";
     39 
     40 // returns true if c is a-zA-Z0-9 or underscore
     41 bool isValidChar(char c);
     42 
     43 // trim the string with the given charset
     44 std::string trim(const std::string& s, const std::string& charset);
     45 
     46 /**
     47  * When a text has a table format like this
     48  * line 1: HeadA HeadB HeadC
     49  * line 2: v1    v2    v3
     50  * line 3: v11   v12   v13
     51  *
     52  * We want to parse the line in structure given the delimiter.
     53  * parseHeader is used to parse the firse line of the table and returns a list of strings in lower case
     54  * parseRecord is used to parse other lines and returns a list of strings
     55  * empty strings are skipped
     56  */
     57 header_t parseHeader(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
     58 record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
     59 
     60 /**
     61  * Gets the list of end indices of each word in the line and places it in the given vector,
     62  * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
     63  * Will return false if there was a problem getting the indices. headerNames
     64  * must be NULL terminated.
     65  */
     66 bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);
     67 
     68 /**
     69  * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
     70  * This function allows to parse record by its header's column position' indices, must in ascending order.
     71  * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
     72  */
     73 record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
     74 
     75 /** Prints record_t to stderr */
     76 void printRecord(const record_t& record);
     77 
     78 /**
     79  * When the line starts/ends with the given key, the function returns true
     80  * as well as the line argument is changed to the rest trimmed part of the original.
     81  * e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
     82  * "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
     83  * otherwise the line is not changed.
     84  *
     85  * In order to prevent two values have same prefix which cause entering to incorrect conditions,
     86  * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid
     87  * character or digits, this feature is off by default.
     88  * i.e. ABC%some value, ABCD%other value
     89  */
     90 bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false);
     91 bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);
     92 
     93 /**
     94  * behead the given line by the cut, return the head and reassign the line to be the rest.
     95  */
     96 std::string behead(std::string* line, const char cut);
     97 
     98 /**
     99  * Converts string to the desired type
    100  */
    101 int toInt(const std::string& s);
    102 long long toLongLong(const std::string& s);
    103 double toDouble(const std::string& s);
    104 
    105 /**
    106  * Reader class reads data from given fd in streaming fashion.
    107  * The buffer size is controlled by capacity parameter.
    108  */
    109 class Reader
    110 {
    111 public:
    112     Reader(const int fd);
    113     ~Reader();
    114 
    115     bool readLine(std::string* line);
    116     bool ok(std::string* error);
    117 
    118 private:
    119     FILE* mFile;
    120     std::string mStatus;
    121 };
    122 
    123 /**
    124  * The Table class is constructed from two arrays generated by the given message with
    125  * option (stream_proto.stream_msg).enable_fields_mapping = true.
    126  * The names are each field's names in the message and must corresponding to the header/name of
    127  * the text to be parsed, and the ids are the streaming proto encoded field ids.
    128  *
    129  * This class then allows users to insert the table values to proto based on its header.
    130  *
    131  * Advance feature: if some fields in the message are enums, user must explicitly add the
    132  * mapping from enum name string to its enum values.
    133  */
    134 class Message;
    135 class Table
    136 {
    137 friend class Message;
    138 public:
    139     Table(const char* names[], const uint64_t ids[], const int count);
    140     ~Table();
    141 
    142     // Add enum names to values for parsing purpose.
    143     void addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize);
    144 
    145     // Manually add enum names to values mapping, useful when an Enum type is used by
    146     // a number of fields, there must not be any enum name conflicts.
    147     void addEnumNameToValue(const char* enumName, const int enumValue);
    148 
    149     // Based on given name, find the right field id, parse the text value and insert to proto.
    150     // Return false if the given name can't be found.
    151     bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
    152 private:
    153     std::map<std::string, uint64_t> mFields;
    154     std::map<std::string, std::map<std::string, int>> mEnums;
    155     std::map<std::string, int> mEnumValuesByName;
    156 };
    157 
    158 /**
    159  * Reconstructs a typical proto message given its message Table, adds submessage fields explicitly.
    160  * It allows user to insert nested proto values purely by the names. See insertField for detail.
    161  */
    162 class Message
    163 {
    164 public:
    165     Message(Table* table);
    166     ~Message();
    167 
    168     // Reconstructs the typical proto message by adding its message fields.
    169     void addSubMessage(uint64_t fieldId, Message* fieldMsg);
    170 
    171     // Inserts value if the given name has the corresponding field in its message and return true.
    172     // It will recursively search the name in submessages and find the correct field to insert.
    173     // For example, when the name is dalvik_vm_heapsize, and the message's corresponding proto is:
    174     //     message Properties {
    175     //         message DalvikVm {
    176     //             int32 heapsize = 1;
    177     //             bool  usejit = 2;
    178     //         }
    179     //         DalvikVm dalvik_vm = 1;
    180     //         string hack_in = 2;
    181     //     }
    182     // The value will be inserted into field heapsize in dalvik_vm submessage.
    183     //
    184     // Also value belongs to same submessage MUST be inserted contiguously.
    185     // For example, dalvik_vm_usejit must be inserted directly after dalvik_vm_heapsize, otherwise
    186     // if hack_in attempts to be inserted before dalvik_vm_usejit, value of usejit isn't added as expected.
    187     bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
    188 
    189     // Starts a new message field proto session.
    190     void startSession(ProtoOutputStream* proto, const std::string& name);
    191 
    192     // Ends the previous message field proto session.
    193     void endSession(ProtoOutputStream* proto);
    194 private:
    195     Table* mTable;
    196     std::string mPreviousField;
    197     std::stack<uint64_t> mTokens;
    198     std::map<std::string, Message*> mSubMessages;
    199 };
    200 
    201 #endif  // INCIDENT_HELPER_UTIL_H
    202