summaryrefslogtreecommitdiff
path: root/cmds/incident_helper/src/ih_util.h
blob: 09dc8e6fdbfc545ab88f7f86170bf81f7124b46c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * 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.
 */

#ifndef INCIDENT_HELPER_UTIL_H
#define INCIDENT_HELPER_UTIL_H

#include <map>
#include <stack>
#include <string>
#include <vector>

#include <android/util/ProtoOutputStream.h>

using namespace android::util;

typedef std::vector<std::string> header_t;
typedef std::vector<std::string> record_t;
typedef std::string (*trans_func) (const std::string&);

const std::string DEFAULT_WHITESPACE = " \t";
const std::string DEFAULT_NEWLINE = "\r\n";
const std::string TAB_DELIMITER = "\t";
const std::string COMMA_DELIMITER = ",";
const std::string PIPE_DELIMITER = "|";
const std::string PARENTHESES_DELIMITER = "()";

// returns true if c is a-zA-Z0-9 or underscore
bool isValidChar(char c);

// trim the string with the given charset
std::string trim(const std::string& s, const std::string& charset);

/**
 * When a text has a table format like this
 * line 1: HeadA HeadB HeadC
 * line 2: v1    v2    v3
 * line 3: v11   v12   v13
 *
 * We want to parse the line in structure given the delimiter.
 * parseHeader is used to parse the firse line of the table and returns a list of strings in lower case
 * parseRecord is used to parse other lines and returns a list of strings
 * empty strings are skipped
 */
header_t parseHeader(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);

/**
 * Gets the list of end indices of each word in the line and places it in the given vector,
 * clearing out the vector beforehand. These indices can be used with parseRecordByColumns.
 * Will return false if there was a problem getting the indices. headerNames
 * must be NULL terminated.
 */
bool getColumnIndices(std::vector<int>& indices, const char* headerNames[], const std::string& line);

/**
 * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
 * This function allows to parse record by its header's column position' indices, must in ascending order.
 * 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.
 */
record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);

/** Prints record_t to stderr */
void printRecord(const record_t& record);

/**
 * When the line starts/ends with the given key, the function returns true
 * as well as the line argument is changed to the rest trimmed part of the original.
 * e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
 * "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
 * otherwise the line is not changed.
 *
 * In order to prevent two values have same prefix which cause entering to incorrect conditions,
 * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid
 * character or digits, this feature is off by default.
 * i.e. ABC%some value, ABCD%other value
 */
bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false);
bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);

/**
 * behead the given line by the cut, return the head and reassign the line to be the rest.
 */
std::string behead(std::string* line, const char cut);

/**
 * Converts string to the desired type
 */
int toInt(const std::string& s);
long long toLongLong(const std::string& s);
double toDouble(const std::string& s);

/**
 * Reader class reads data from given fd in streaming fashion.
 * The buffer size is controlled by capacity parameter.
 */
class Reader
{
public:
    explicit Reader(const int fd);
    ~Reader();

    bool readLine(std::string* line);
    bool ok(std::string* error);

private:
    FILE* mFile;
    std::string mStatus;
};

/**
 * The Table class is constructed from two arrays generated by the given message with
 * option (stream_proto.stream_msg).enable_fields_mapping = true.
 * The names are each field's names in the message and must corresponding to the header/name of
 * the text to be parsed, and the ids are the streaming proto encoded field ids.
 *
 * This class then allows users to insert the table values to proto based on its header.
 *
 * Advance feature: if some fields in the message are enums, user must explicitly add the
 * mapping from enum name string to its enum values.
 */
class Message;
class Table
{
friend class Message;
public:
    Table(const char* names[], const uint64_t ids[], const int count);
    ~Table();

    // Add enum names to values for parsing purpose.
    void addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize);

    // Manually add enum names to values mapping, useful when an Enum type is used by
    // a number of fields, there must not be any enum name conflicts.
    void addEnumNameToValue(const char* enumName, const int enumValue);

    // Based on given name, find the right field id, parse the text value and insert to proto.
    // Return false if the given name can't be found.
    bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
private:
    std::map<std::string, uint64_t> mFields;
    std::map<std::string, std::map<std::string, int>> mEnums;
    std::map<std::string, int> mEnumValuesByName;
};

/**
 * Reconstructs a typical proto message given its message Table, adds submessage fields explicitly.
 * It allows user to insert nested proto values purely by the names. See insertField for detail.
 */
class Message
{
public:
    explicit Message(Table* table);
    ~Message();

    // Reconstructs the typical proto message by adding its message fields.
    void addSubMessage(uint64_t fieldId, Message* fieldMsg);

    // Inserts value if the given name has the corresponding field in its message and return true.
    // It will recursively search the name in submessages and find the correct field to insert.
    // For example, when the name is dalvik_vm_heapsize, and the message's corresponding proto is:
    //     message Properties {
    //         message DalvikVm {
    //             int32 heapsize = 1;
    //             bool  usejit = 2;
    //         }
    //         DalvikVm dalvik_vm = 1;
    //         string hack_in = 2;
    //     }
    // The value will be inserted into field heapsize in dalvik_vm submessage.
    //
    // Also value belongs to same submessage MUST be inserted contiguously.
    // For example, dalvik_vm_usejit must be inserted directly after dalvik_vm_heapsize, otherwise
    // if hack_in attempts to be inserted before dalvik_vm_usejit, value of usejit isn't added as expected.
    bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);

    // Starts a new message field proto session.
    void startSession(ProtoOutputStream* proto, const std::string& name);

    // Ends the previous message field proto session.
    void endSession(ProtoOutputStream* proto);
private:
    Table* mTable;
    std::string mPreviousField;
    std::stack<uint64_t> mTokens;
    std::map<std::string, Message*> mSubMessages;
};

#endif  // INCIDENT_HELPER_UTIL_H