blob: d9ab4edef1195c3fe2a9ef720c16a6a58cc35763 [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2017 Intel Corporation
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*/
Brad Bishop1fb9f3f2020-08-28 08:15:13 -040016/// \file Utils.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
James Feist481c5d52019-08-13 14:40:40 -070018#include "Utils.hpp"
19
20#include "VariantVisitors.hpp"
21
22#include <boost/algorithm/string/classification.hpp>
23#include <boost/algorithm/string/find.hpp>
24#include <boost/algorithm/string/predicate.hpp>
25#include <boost/algorithm/string/replace.hpp>
26#include <boost/algorithm/string/split.hpp>
27#include <boost/container/flat_map.hpp>
28#include <boost/lexical_cast.hpp>
James Feist1df06a42019-04-11 14:23:04 -070029#include <sdbusplus/bus/match.hpp>
James Feistb4383f42018-08-06 16:54:10 -070030#include <valijson/adapters/nlohmann_json_adapter.hpp>
31#include <valijson/schema.hpp>
32#include <valijson/schema_parser.hpp>
33#include <valijson/validator.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080034
James Feist8c505da2020-05-28 10:06:33 -070035#include <filesystem>
36#include <fstream>
37#include <regex>
38
James Feist481c5d52019-08-13 14:40:40 -070039constexpr const char* templateChar = "$";
40
Ed Tanous072e25d2018-12-16 21:45:20 -080041namespace fs = std::filesystem;
James Feist1df06a42019-04-11 14:23:04 -070042static bool powerStatusOn = false;
43static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
James Feist3cb5fec2018-01-23 14:41:51 -080044
James Feista465ccc2019-02-08 12:51:01 -080045bool findFiles(const fs::path& dirPath, const std::string& matchString,
46 std::vector<fs::path>& foundPaths)
James Feist3cb5fec2018-01-23 14:41:51 -080047{
James Feista3c180a2018-08-09 16:06:04 -070048 if (!fs::exists(dirPath))
James Feist3cb5fec2018-01-23 14:41:51 -080049 return false;
50
James Feista3c180a2018-08-09 16:06:04 -070051 std::regex search(matchString);
James Feist3cb5fec2018-01-23 14:41:51 -080052 std::smatch match;
James Feista465ccc2019-02-08 12:51:01 -080053 for (const auto& p : fs::directory_iterator(dirPath))
James Feist3cb5fec2018-01-23 14:41:51 -080054 {
55 std::string path = p.path().string();
James Feistc95cb142018-02-26 10:41:42 -080056 if (std::regex_search(path, match, search))
James Feist3cb5fec2018-01-23 14:41:51 -080057 {
James Feista3c180a2018-08-09 16:06:04 -070058 foundPaths.emplace_back(p.path());
James Feist3cb5fec2018-01-23 14:41:51 -080059 }
60 }
61 return true;
James Feistb4383f42018-08-06 16:54:10 -070062}
63
Nikhil Potaded8884f12019-03-27 13:27:13 -070064bool getI2cDevicePaths(const fs::path& dirPath,
65 boost::container::flat_map<size_t, fs::path>& busPaths)
66{
67 if (!fs::exists(dirPath))
68 {
69 return false;
70 }
71
72 // Regex for matching the path
73 std::regex searchPath(std::string(R"(i2c-\d+$)"));
74 // Regex for matching the bus numbers
75 std::regex searchBus(std::string(R"(\w[^-]*$)"));
76 std::smatch matchPath;
77 std::smatch matchBus;
78 for (const auto& p : fs::directory_iterator(dirPath))
79 {
80 std::string path = p.path().string();
81 if (std::regex_search(path, matchPath, searchPath))
82 {
83 if (std::regex_search(path, matchBus, searchBus))
84 {
85 size_t bus = stoul(*matchBus.begin());
86 busPaths.insert(std::pair<size_t, fs::path>(bus, p.path()));
87 }
88 }
89 }
90
91 return true;
92}
93
James Feista465ccc2019-02-08 12:51:01 -080094bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
James Feistb4383f42018-08-06 16:54:10 -070095{
96 valijson::Schema schema;
97 valijson::SchemaParser parser;
98 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
99 parser.populateSchema(schemaAdapter, schema);
100 valijson::Validator validator;
101 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
102 if (!validator.validate(schema, targetAdapter, NULL))
103 {
104 return false;
105 }
106 return true;
Ed Tanous072e25d2018-12-16 21:45:20 -0800107}
James Feist1df06a42019-04-11 14:23:04 -0700108
109bool isPowerOn(void)
110{
111 if (!powerMatch)
112 {
113 throw std::runtime_error("Power Match Not Created");
114 }
115 return powerStatusOn;
116}
117
118void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
119{
James Feist1df06a42019-04-11 14:23:04 -0700120 powerMatch = std::make_unique<sdbusplus::bus::match::match>(
121 static_cast<sdbusplus::bus::bus&>(*conn),
James Feist61f5ac42020-03-11 14:37:25 -0700122 "type='signal',interface='" + std::string(properties::interface) +
123 "',path='" + std::string(power::path) + "',arg0='" +
124 std::string(power::interface) + "'",
125 [](sdbusplus::message::message& message) {
126 std::string objectName;
127 boost::container::flat_map<std::string, std::variant<std::string>>
128 values;
129 message.read(objectName, values);
130 auto findState = values.find(power::property);
131 if (findState != values.end())
132 {
133 powerStatusOn = boost::ends_with(
134 std::get<std::string>(findState->second), "Running");
135 }
136 });
137
138 conn->async_method_call(
139 [](boost::system::error_code ec,
140 const std::variant<std::string>& state) {
141 if (ec)
142 {
143 return;
144 }
145 powerStatusOn =
146 boost::ends_with(std::get<std::string>(state), "Running");
147 },
148 power::busname, power::path, properties::interface, properties::get,
149 power::interface, power::property);
James Feist481c5d52019-08-13 14:40:40 -0700150}
151
152// finds the template character (currently set to $) and replaces the value with
153// the field found in a dbus object i.e. $ADDRESS would get populated with the
154// ADDRESS field from a object on dbus
James Feist35f5e0e2020-03-16 14:02:27 -0700155std::optional<std::string> templateCharReplace(
James Feist481c5d52019-08-13 14:40:40 -0700156 nlohmann::json::iterator& keyPair,
157 const boost::container::flat_map<std::string, BasicVariantType>&
158 foundDevice,
James Feist35f5e0e2020-03-16 14:02:27 -0700159 const size_t foundDeviceIdx, const std::optional<std::string>& replaceStr)
James Feist481c5d52019-08-13 14:40:40 -0700160{
James Feist35f5e0e2020-03-16 14:02:27 -0700161 std::optional<std::string> ret = std::nullopt;
162
James Feist481c5d52019-08-13 14:40:40 -0700163 if (keyPair.value().type() == nlohmann::json::value_t::object ||
164 keyPair.value().type() == nlohmann::json::value_t::array)
165 {
166 for (auto nextLayer = keyPair.value().begin();
167 nextLayer != keyPair.value().end(); nextLayer++)
168 {
James Feist35f5e0e2020-03-16 14:02:27 -0700169 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx,
170 replaceStr);
James Feist481c5d52019-08-13 14:40:40 -0700171 }
James Feist35f5e0e2020-03-16 14:02:27 -0700172 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700173 }
174
175 std::string* strPtr = keyPair.value().get_ptr<std::string*>();
176 if (strPtr == nullptr)
177 {
James Feist35f5e0e2020-03-16 14:02:27 -0700178 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700179 }
180
181 boost::replace_all(*strPtr, std::string(templateChar) + "index",
182 std::to_string(foundDeviceIdx));
James Feist35f5e0e2020-03-16 14:02:27 -0700183 if (replaceStr)
184 {
185 boost::replace_all(*strPtr, *replaceStr,
186 std::to_string(foundDeviceIdx));
187 }
James Feist481c5d52019-08-13 14:40:40 -0700188
189 for (auto& foundDevicePair : foundDevice)
190 {
191 std::string templateName = templateChar + foundDevicePair.first;
192 boost::iterator_range<std::string::const_iterator> find =
193 boost::ifind_first(*strPtr, templateName);
194 if (find)
195 {
James Feist8c20feb2019-08-14 15:10:11 -0700196 constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
197 '/'};
James Feist481c5d52019-08-13 14:40:40 -0700198 size_t start = find.begin() - strPtr->begin();
James Feist8c20feb2019-08-14 15:10:11 -0700199 size_t nextItemIdx = start + templateName.size() + 1;
200
James Feist481c5d52019-08-13 14:40:40 -0700201 // check for additional operations
202 if (!start && find.end() == strPtr->end())
203 {
204 std::visit([&](auto&& val) { keyPair.value() = val; },
205 foundDevicePair.second);
James Feist35f5e0e2020-03-16 14:02:27 -0700206 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700207 }
James Feist8c20feb2019-08-14 15:10:11 -0700208 else if (nextItemIdx > strPtr->size() ||
209 std::find(mathChars.begin(), mathChars.end(),
210 strPtr->at(nextItemIdx)) == mathChars.end())
James Feist481c5d52019-08-13 14:40:40 -0700211 {
212 std::string val = std::visit(VariantToStringVisitor(),
213 foundDevicePair.second);
James Feistb0097d42019-08-15 09:24:13 -0700214 boost::ireplace_all(*strPtr,
215 templateChar + foundDevicePair.first, val);
James Feist8c20feb2019-08-14 15:10:11 -0700216 continue;
James Feist481c5d52019-08-13 14:40:40 -0700217 }
218
219 // save the prefix
220 std::string prefix = strPtr->substr(0, start);
221
James Feist8c20feb2019-08-14 15:10:11 -0700222 // operate on the rest
223 std::string end = strPtr->substr(nextItemIdx);
James Feist481c5d52019-08-13 14:40:40 -0700224
225 std::vector<std::string> split;
226 boost::split(split, end, boost::is_any_of(" "));
227
228 // need at least 1 operation and number
229 if (split.size() < 2)
230 {
231 std::cerr << "Syntax error on template replacement of "
232 << *strPtr << "\n";
233 for (const std::string& data : split)
234 {
235 std::cerr << data << " ";
236 }
237 std::cerr << "\n";
238 continue;
239 }
240
241 // we assume that the replacement is a number, because we can
242 // only do math on numbers.. we might concatenate strings in the
243 // future, but thats later
244 int number =
245 std::visit(VariantToIntVisitor(), foundDevicePair.second);
246
247 bool isOperator = true;
248 TemplateOperation next = TemplateOperation::addition;
249
250 auto it = split.begin();
251
252 for (; it != split.end(); it++)
253 {
254 if (isOperator)
255 {
256 if (*it == "+")
257 {
258 next = TemplateOperation::addition;
259 }
260 else if (*it == "-")
261 {
262 next = TemplateOperation::subtraction;
263 }
264 else if (*it == "*")
265 {
266 next = TemplateOperation::multiplication;
267 }
268 else if (*it == R"(%)")
269 {
270 next = TemplateOperation::modulo;
271 }
272 else if (*it == R"(/)")
273 {
274 next = TemplateOperation::division;
275 }
276 else
277 {
278 break;
279 }
280 }
281 else
282 {
283 int constant = 0;
284 try
285 {
286 constant = std::stoi(*it);
287 }
288 catch (std::invalid_argument&)
289 {
290 std::cerr << "Parameter not supported for templates "
291 << *it << "\n";
292 continue;
293 }
294 switch (next)
295 {
296 case TemplateOperation::addition:
297 {
298 number += constant;
299 break;
300 }
301 case TemplateOperation::subtraction:
302 {
303 number -= constant;
304 break;
305 }
306 case TemplateOperation::multiplication:
307 {
308 number *= constant;
309 break;
310 }
311 case TemplateOperation::division:
312 {
313 number /= constant;
314 break;
315 }
316 case TemplateOperation::modulo:
317 {
318 number = number % constant;
319 break;
320 }
321
322 default:
323 break;
324 }
325 }
326 isOperator = !isOperator;
327 }
James Feist35f5e0e2020-03-16 14:02:27 -0700328
James Feist481c5d52019-08-13 14:40:40 -0700329 std::string result = prefix + std::to_string(number);
330
James Feist35f5e0e2020-03-16 14:02:27 -0700331 std::string replaced(find.begin(), find.end());
332 for (auto it2 = split.begin(); it2 != split.end(); it2++)
333 {
334 replaced += " ";
335 replaced += *it2;
336 if (it2 == it)
337 {
338 break;
339 }
340 }
341 ret = replaced;
342
James Feist481c5d52019-08-13 14:40:40 -0700343 if (it != split.end())
344 {
345 for (; it != split.end(); it++)
346 {
347 result += " " + *it;
348 }
349 }
350 keyPair.value() = result;
351
352 // We probably just invalidated the pointer above, so set it to null
353 strPtr = nullptr;
354 break;
355 }
356 }
357
358 strPtr = keyPair.value().get_ptr<std::string*>();
359 if (strPtr == nullptr)
360 {
James Feist35f5e0e2020-03-16 14:02:27 -0700361 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700362 }
363
364 // convert hex numbers to ints
365 if (boost::starts_with(*strPtr, "0x"))
366 {
367 try
368 {
369 size_t pos = 0;
370 int64_t temp = std::stoul(*strPtr, &pos, 0);
371 if (pos == strPtr->size())
372 {
373 keyPair.value() = static_cast<uint64_t>(temp);
374 }
375 }
376 catch (std::invalid_argument&)
James Feist8c505da2020-05-28 10:06:33 -0700377 {}
James Feist481c5d52019-08-13 14:40:40 -0700378 catch (std::out_of_range&)
James Feist8c505da2020-05-28 10:06:33 -0700379 {}
James Feist481c5d52019-08-13 14:40:40 -0700380 }
381 // non-hex numbers
382 else
383 {
384 try
385 {
386 uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
387 keyPair.value() = temp;
388 }
389 catch (boost::bad_lexical_cast&)
James Feist8c505da2020-05-28 10:06:33 -0700390 {}
James Feist481c5d52019-08-13 14:40:40 -0700391 }
James Feist35f5e0e2020-03-16 14:02:27 -0700392 return ret;
Xiang Liu2801a702020-01-20 14:29:34 -0800393}
Brad Bishop3cb8a602020-08-25 17:40:54 -0400394
Brad Bishop5d525412020-08-26 08:50:50 -0400395/// \brief JSON/DBus matching Callable for std::variant (visitor)
396///
397/// Default match JSON/DBus match implementation
398/// \tparam T The concrete DBus value type from BasicVariantType
399template <typename T>
400struct MatchProbe
401{
402 /// \param probe the probe statement to match against
403 /// \param value the property value being matched to a probe
404 /// \return true if the dbusValue matched the probe otherwise false
405 static bool match(const nlohmann::json& probe, const T& value)
406 {
407 return probe == value;
408 }
409};
410
411/// \brief JSON/DBus matching Callable for std::variant (visitor)
412///
413/// std::string specialization of MatchProbe enabling regex matching
414template <>
415struct MatchProbe<std::string>
416{
417 /// \param probe the probe statement to match against
418 /// \param value the string value being matched to a probe
419 /// \return true if the dbusValue matched the probe otherwise false
420 static bool match(const nlohmann::json& probe, const std::string& value)
421 {
422 if (probe.is_string())
423 {
424 try
425 {
426 std::regex search(probe);
427 std::smatch regMatch;
428 return std::regex_search(value, regMatch, search);
429 }
430 catch (const std::regex_error&)
431 {
432 std::cerr << "Syntax error in regular expression: " << probe
433 << " will never match";
434 }
435 }
436
437 // Skip calling nlohmann here, since it will never match a non-string
438 // to a std::string
439 return false;
440 }
441};
442
443/// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
444///
445/// Forward calls to the correct template instantiation of MatchProbe
446struct MatchProbeForwarder
447{
448 explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
449 {}
450 const nlohmann::json& probeRef;
451
452 template <typename T>
453 bool operator()(const T& dbusValue) const
454 {
455 return MatchProbe<T>::match(probeRef, dbusValue);
456 }
457};
458
Brad Bishop3cb8a602020-08-25 17:40:54 -0400459bool matchProbe(const nlohmann::json& probe, const BasicVariantType& dbusValue)
460{
Brad Bishop5d525412020-08-26 08:50:50 -0400461 return std::visit(MatchProbeForwarder(probe), dbusValue);
462}
463
464bool matchProbeOld(const nlohmann::json& probe,
465 const BasicVariantType& dbusValue)
466{
Brad Bishop3cb8a602020-08-25 17:40:54 -0400467 bool deviceMatches = true;
468
469 switch (probe.type())
470 {
471 case nlohmann::json::value_t::string:
472 {
473 std::regex search(probe.get<std::string>());
474 std::smatch regMatch;
475
476 // convert value to string respresentation
477 std::string probeValue =
478 std::visit(VariantToStringVisitor(), dbusValue);
479 if (!std::regex_search(probeValue, regMatch, search))
480 {
481 deviceMatches = false;
482 break;
483 }
484 break;
485 }
486 case nlohmann::json::value_t::boolean:
487 case nlohmann::json::value_t::number_unsigned:
488 {
489 unsigned int probeValue =
490 std::visit(VariantToUnsignedIntVisitor(), dbusValue);
491
492 if (probeValue != probe.get<unsigned int>())
493 {
494 deviceMatches = false;
495 }
496 break;
497 }
498 case nlohmann::json::value_t::number_integer:
499 {
500 int probeValue = std::visit(VariantToIntVisitor(), dbusValue);
501
502 if (probeValue != probe.get<int>())
503 {
504 deviceMatches = false;
505 }
506 break;
507 }
508 case nlohmann::json::value_t::number_float:
509 {
510 float probeValue = std::visit(VariantToFloatVisitor(), dbusValue);
511
512 if (probeValue != probe.get<float>())
513 {
514 deviceMatches = false;
515 }
516 break;
517 }
518 default:
519 {
520 std::cerr << "unexpected dbus probe type " << probe.type_name()
521 << "\n";
522 deviceMatches = false;
523 }
524 }
525 return deviceMatches;
526}