1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_rpc/fuzz/argparse.h"
16
17 #include <cctype>
18 #include <cstring>
19
20 #include "pw_assert/check.h"
21 #include "pw_log/log.h"
22 #include "pw_string/string_builder.h"
23
24 namespace pw::rpc::fuzz {
25 namespace {
26
27 // Visitor to `ArgVariant` used by `ParseArgs` below.
28 struct ParseVisitor {
29 std::string_view arg0;
30 std::string_view arg1;
31
32 template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ParseVisitor33 ParseStatus operator()(Parser& parser) {
34 return parser.Parse(arg0, arg1);
35 }
36 };
37
38 // Visitor to `ArgVariant` used by `GetArg` below.
39 struct ValueVisitor {
40 std::string_view name;
41
42 template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ValueVisitor43 std::optional<ArgVariant> operator()(Parser& parser) {
44 std::optional<ArgVariant> result;
45 if (parser.short_name() == name || parser.long_name() == name) {
46 result.emplace(parser.value());
47 }
48 return result;
49 }
50 };
51
52 // Visitor to `ArgVariant` used by `PrintUsage` below.
53 const size_t kMaxUsageLen = 256;
54 struct UsageVisitor {
55 StringBuffer<kMaxUsageLen>* buffer;
56
operator ()pw::rpc::fuzz::__anon5e30d65c0111::UsageVisitor57 void operator()(const BoolParser& parser) const {
58 auto short_name = parser.short_name();
59 auto long_name = parser.long_name();
60 *buffer << " [" << short_name << "|--[no-]" << long_name.substr(2) << "]";
61 }
62
63 template <typename T>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::UsageVisitor64 void operator()(const UnsignedParser<T>& parser) const {
65 auto short_name = parser.short_name();
66 auto long_name = parser.long_name();
67 *buffer << " ";
68 if (!parser.positional()) {
69 *buffer << "[";
70 if (!short_name.empty()) {
71 *buffer << short_name << "|";
72 }
73 *buffer << long_name << " ";
74 }
75 for (const auto& c : long_name) {
76 *buffer << static_cast<char>(toupper(c));
77 }
78 if (!parser.positional()) {
79 *buffer << "]";
80 }
81 }
82 };
83
84 // Visitor to `ArgVariant` used by `ResetArg` below.
85 struct ResetVisitor {
86 std::string_view name;
87
88 template <typename Parser>
operator ()pw::rpc::fuzz::__anon5e30d65c0111::ResetVisitor89 bool operator()(Parser& parser) {
90 if (parser.short_name() != name && parser.long_name() != name) {
91 return false;
92 }
93 parser.Reset();
94 return true;
95 }
96 };
97
98 } // namespace
99
ArgParserBase(std::string_view name)100 ArgParserBase::ArgParserBase(std::string_view name) : long_name_(name) {
101 PW_CHECK(!name.empty());
102 PW_CHECK(name != "--");
103 positional_ =
104 name[0] != '-' || (name.size() > 2 && name.substr(0, 2) != "--");
105 }
106
ArgParserBase(std::string_view shortopt,std::string_view longopt)107 ArgParserBase::ArgParserBase(std::string_view shortopt,
108 std::string_view longopt)
109 : short_name_(shortopt), long_name_(longopt) {
110 PW_CHECK(shortopt.size() == 2);
111 PW_CHECK(shortopt[0] == '-');
112 PW_CHECK(shortopt != "--");
113 PW_CHECK(longopt.size() > 2);
114 PW_CHECK(longopt.substr(0, 2) == "--");
115 positional_ = false;
116 }
117
Match(std::string_view arg)118 bool ArgParserBase::Match(std::string_view arg) {
119 if (arg.empty()) {
120 return false;
121 }
122 if (!positional_) {
123 return arg == short_name_ || arg == long_name_;
124 }
125 if (!std::holds_alternative<std::monostate>(value_)) {
126 return false;
127 }
128 if ((arg.size() == 2 && arg[0] == '-') ||
129 (arg.size() > 2 && arg.substr(0, 2) == "--")) {
130 PW_LOG_WARN("Argument parsed for '%s' appears to be a flag: '%s'",
131 long_name_.data(),
132 arg.data());
133 }
134 return true;
135 }
136
GetValue() const137 const ArgVariant& ArgParserBase::GetValue() const {
138 return std::holds_alternative<std::monostate>(value_) ? initial_ : value_;
139 }
140
BoolParser(std::string_view name)141 BoolParser::BoolParser(std::string_view name) : ArgParserBase(name) {}
BoolParser(std::string_view shortopt,std::string_view longopt)142 BoolParser::BoolParser(std::string_view shortopt, std::string_view longopt)
143 : ArgParserBase(shortopt, longopt) {}
144
set_default(bool value)145 BoolParser& BoolParser::set_default(bool value) {
146 set_initial(value);
147 return *this;
148 }
149
Parse(std::string_view arg0,std::string_view arg1)150 ParseStatus BoolParser::Parse(std::string_view arg0,
151 [[maybe_unused]] std::string_view arg1) {
152 if (Match(arg0)) {
153 set_value(true);
154 return kParsedOne;
155 }
156 if (arg0.size() > 5 && arg0.substr(0, 5) == "--no-" &&
157 arg0.substr(5) == long_name().substr(2)) {
158 set_value(false);
159 return kParsedOne;
160 }
161 return kParseMismatch;
162 }
163
UnsignedParserBase(std::string_view name)164 UnsignedParserBase::UnsignedParserBase(std::string_view name)
165 : ArgParserBase(name) {}
UnsignedParserBase(std::string_view shortopt,std::string_view longopt)166 UnsignedParserBase::UnsignedParserBase(std::string_view shortopt,
167 std::string_view longopt)
168 : ArgParserBase(shortopt, longopt) {}
169
Parse(std::string_view arg0,std::string_view arg1,uint64_t max)170 ParseStatus UnsignedParserBase::Parse(std::string_view arg0,
171 std::string_view arg1,
172 uint64_t max) {
173 auto result = kParsedOne;
174 if (!Match(arg0)) {
175 return kParseMismatch;
176 }
177 if (!positional()) {
178 if (arg1.empty()) {
179 PW_LOG_ERROR("Missing value for flag '%s'", arg0.data());
180 return kParseFailure;
181 }
182 arg0 = arg1;
183 result = kParsedTwo;
184 }
185 char* endptr;
186 unsigned long long value = strtoull(arg0.data(), &endptr, 0);
187 if (*endptr) {
188 PW_LOG_ERROR("Failed to parse number from '%s'", arg0.data());
189 return kParseFailure;
190 }
191 if (value > max) {
192 PW_LOG_ERROR("Parsed value is too large: %llu", value);
193 return kParseFailure;
194 }
195 set_value(static_cast<uint64_t>(value));
196 return result;
197 }
198
ParseArgs(Vector<ArgParserVariant> & parsers,int argc,char ** argv)199 Status ParseArgs(Vector<ArgParserVariant>& parsers, int argc, char** argv) {
200 for (int i = 1; i < argc; ++i) {
201 auto arg0 = std::string_view(argv[i]);
202 auto arg1 =
203 i == (argc - 1) ? std::string_view() : std::string_view(argv[i + 1]);
204 bool parsed = false;
205 for (auto& parser : parsers) {
206 switch (std::visit(ParseVisitor{.arg0 = arg0, .arg1 = arg1}, parser)) {
207 case kParsedOne:
208 break;
209 case kParsedTwo:
210 ++i;
211 break;
212 case kParseMismatch:
213 continue;
214 case kParseFailure:
215 PW_LOG_ERROR("Failed to parse '%s'", arg0.data());
216 return Status::InvalidArgument();
217 }
218 parsed = true;
219 break;
220 }
221 if (!parsed) {
222 PW_LOG_ERROR("Unrecognized argument: '%s'", arg0.data());
223 return Status::InvalidArgument();
224 }
225 }
226 return OkStatus();
227 }
228
PrintUsage(const Vector<ArgParserVariant> & parsers,std::string_view argv0)229 void PrintUsage(const Vector<ArgParserVariant>& parsers,
230 std::string_view argv0) {
231 StringBuffer<kMaxUsageLen> buffer;
232 buffer << "usage: " << argv0;
233 for (auto& parser : parsers) {
234 std::visit(UsageVisitor{.buffer = &buffer}, parser);
235 }
236 PW_LOG_INFO("%s", buffer.c_str());
237 }
238
GetArg(const Vector<ArgParserVariant> & parsers,std::string_view name)239 std::optional<ArgVariant> GetArg(const Vector<ArgParserVariant>& parsers,
240 std::string_view name) {
241 for (auto& parser : parsers) {
242 if (auto result = std::visit(ValueVisitor{.name = name}, parser);
243 result.has_value()) {
244 return result;
245 }
246 }
247 return std::optional<ArgVariant>();
248 }
249
ResetArg(Vector<ArgParserVariant> & parsers,std::string_view name)250 Status ResetArg(Vector<ArgParserVariant>& parsers, std::string_view name) {
251 for (auto& parser : parsers) {
252 if (std::visit(ResetVisitor{.name = name}, parser)) {
253 return OkStatus();
254 }
255 }
256 return Status::InvalidArgument();
257 }
258
259 } // namespace pw::rpc::fuzz
260