1 /*
2  * Copyright (C) 2020 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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "mediametrics::stringutils"
19 #include <utils/Log.h>
20 
21 #include "StringUtils.h"
22 
23 #include <charconv>
24 
25 #include "AudioTypes.h"
26 
27 namespace android::mediametrics::stringutils {
28 
tokenizer(std::string::const_iterator & it,const std::string::const_iterator & end,const char * reserved)29 std::string tokenizer(std::string::const_iterator& it,
30         const std::string::const_iterator& end, const char *reserved)
31 {
32     // consume leading white space
33     for (; it != end && std::isspace(*it); ++it);
34     if (it == end) return {};
35 
36     auto start = it;
37     // parse until we hit a reserved keyword or space
38     if (strchr(reserved, *it)) return {start, ++it};
39     for (;;) {
40         ++it;
41         if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
42     }
43 }
44 
split(const std::string & flags,const char * delim)45 std::vector<std::string> split(const std::string& flags, const char *delim)
46 {
47     std::vector<std::string> result;
48     for (auto it = flags.begin(); ; ) {
49         auto flag = tokenizer(it, flags.end(), delim);
50         if (flag.empty() || !std::isalnum(flag[0])) return result;
51         result.emplace_back(std::move(flag));
52 
53         // look for the delimeter and discard
54         auto token = tokenizer(it, flags.end(), delim);
55         if (token.size() != 1 || strchr(delim, token[0]) == nullptr) return result;
56     }
57 }
58 
parseVector(const std::string & str,std::vector<int32_t> * vector)59 bool parseVector(const std::string &str, std::vector<int32_t> *vector) {
60     std::vector<int32_t> values;
61     const char *p = str.c_str();
62     const char *last = p + str.size();
63     while (p != last) {
64         if (*p == ',' || *p == '{' || *p == '}') {
65             p++;
66         }
67         int32_t value = -1;
68         auto [ptr, error] = std::from_chars(p, last, value);
69         if (error == std::errc::invalid_argument || error == std::errc::result_out_of_range) {
70             return false;
71         }
72         p = ptr;
73         values.push_back(value);
74     }
75     *vector = std::move(values);
76     return true;
77 }
78 
getDeviceAddressPairs(const std::string & devices)79 std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string& devices)
80 {
81     std::vector<std::pair<std::string, std::string>> result;
82 
83     // Currently, the device format is EXACTLY
84     // (device1, addr1)|(device2, addr2)|...
85 
86     static constexpr char delim[] = "()|,";
87     for (auto it = devices.begin(); ; ) {
88         auto token = tokenizer(it, devices.end(), delim);
89         if (token != "(") return result;
90 
91         auto device = tokenizer(it, devices.end(), delim);
92         if (device.empty() || !std::isalnum(device[0])) return result;
93 
94         token = tokenizer(it, devices.end(), delim);
95         if (token != ",") return result;
96 
97         // special handling here for empty addresses
98         auto address = tokenizer(it, devices.end(), delim);
99         if (address.empty() || !std::isalnum(device[0])) return result;
100         if (address == ")") {  // no address, just the ")"
101             address.clear();
102         } else {
103             token = tokenizer(it, devices.end(), delim);
104             if (token != ")") return result;
105         }
106 
107         result.emplace_back(std::move(device), std::move(address));
108 
109         token = tokenizer(it, devices.end(), delim);
110         if (token != "|") return result;  // this includes end of string detection
111     }
112 }
113 
replace(std::string & str,const char * targetChars,const char replaceChar)114 size_t replace(std::string &str, const char *targetChars, const char replaceChar)
115 {
116     size_t replaced = 0;
117     for (char &c : str) {
118         if (strchr(targetChars, c) != nullptr) {
119             c = replaceChar;
120             ++replaced;
121         }
122     }
123     return replaced;
124 }
125 
126 template <types::AudioEnumCategory CATEGORY>
127 std::pair<std::string /* external statsd */, std::string /* internal */>
parseDevicePairs(const std::string & devicePairs)128 parseDevicePairs(const std::string& devicePairs) {
129     std::pair<std::string, std::string> result{};
130     const auto devaddrvec = stringutils::getDeviceAddressPairs(devicePairs);
131     for (const auto& [device, addr] : devaddrvec) { // addr ignored for now.
132         if (!result.second.empty()) {
133             result.second.append("|"); // delimit devices with '|'.
134             result.first.append("|");
135         }
136         result.second.append(device);
137         result.first.append(types::lookup<CATEGORY, std::string>(device));
138     }
139     return result;
140 }
141 
142 std::pair<std::string /* external statsd */, std::string /* internal */>
parseOutputDevicePairs(const std::string & devicePairs)143 parseOutputDevicePairs(const std::string& devicePairs) {
144     return parseDevicePairs<types::OUTPUT_DEVICE>(devicePairs);
145 }
146 
147 std::pair<std::string /* external statsd */, std::string /* internal */>
parseInputDevicePairs(const std::string & devicePairs)148 parseInputDevicePairs(const std::string& devicePairs) {
149     return parseDevicePairs<types::INPUT_DEVICE>(devicePairs);
150 }
151 
152 } // namespace android::mediametrics::stringutils
153