blob: 48c600ac60d2dd68b3ce1ef5f901929aebfdcddd [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))
Ed Tanous07d467b2021-02-23 14:48:37 -080049 {
James Feist3cb5fec2018-01-23 14:41:51 -080050 return false;
Ed Tanous07d467b2021-02-23 14:48:37 -080051 }
James Feist3cb5fec2018-01-23 14:41:51 -080052
James Feista3c180a2018-08-09 16:06:04 -070053 std::regex search(matchString);
James Feist3cb5fec2018-01-23 14:41:51 -080054 std::smatch match;
James Feista465ccc2019-02-08 12:51:01 -080055 for (const auto& p : fs::directory_iterator(dirPath))
James Feist3cb5fec2018-01-23 14:41:51 -080056 {
57 std::string path = p.path().string();
James Feistc95cb142018-02-26 10:41:42 -080058 if (std::regex_search(path, match, search))
James Feist3cb5fec2018-01-23 14:41:51 -080059 {
James Feista3c180a2018-08-09 16:06:04 -070060 foundPaths.emplace_back(p.path());
James Feist3cb5fec2018-01-23 14:41:51 -080061 }
62 }
63 return true;
James Feistb4383f42018-08-06 16:54:10 -070064}
65
Nikhil Potaded8884f12019-03-27 13:27:13 -070066bool getI2cDevicePaths(const fs::path& dirPath,
67 boost::container::flat_map<size_t, fs::path>& busPaths)
68{
69 if (!fs::exists(dirPath))
70 {
71 return false;
72 }
73
74 // Regex for matching the path
75 std::regex searchPath(std::string(R"(i2c-\d+$)"));
76 // Regex for matching the bus numbers
77 std::regex searchBus(std::string(R"(\w[^-]*$)"));
78 std::smatch matchPath;
79 std::smatch matchBus;
80 for (const auto& p : fs::directory_iterator(dirPath))
81 {
82 std::string path = p.path().string();
83 if (std::regex_search(path, matchPath, searchPath))
84 {
85 if (std::regex_search(path, matchBus, searchBus))
86 {
87 size_t bus = stoul(*matchBus.begin());
88 busPaths.insert(std::pair<size_t, fs::path>(bus, p.path()));
89 }
90 }
91 }
92
93 return true;
94}
95
James Feista465ccc2019-02-08 12:51:01 -080096bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
James Feistb4383f42018-08-06 16:54:10 -070097{
98 valijson::Schema schema;
99 valijson::SchemaParser parser;
100 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
101 parser.populateSchema(schemaAdapter, schema);
102 valijson::Validator validator;
103 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
Ed Tanous07d467b2021-02-23 14:48:37 -0800104 if (!validator.validate(schema, targetAdapter, nullptr))
James Feistb4383f42018-08-06 16:54:10 -0700105 {
106 return false;
107 }
108 return true;
Ed Tanous072e25d2018-12-16 21:45:20 -0800109}
James Feist1df06a42019-04-11 14:23:04 -0700110
111bool isPowerOn(void)
112{
113 if (!powerMatch)
114 {
115 throw std::runtime_error("Power Match Not Created");
116 }
117 return powerStatusOn;
118}
119
120void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
121{
James Feist1df06a42019-04-11 14:23:04 -0700122 powerMatch = std::make_unique<sdbusplus::bus::match::match>(
123 static_cast<sdbusplus::bus::bus&>(*conn),
James Feist61f5ac42020-03-11 14:37:25 -0700124 "type='signal',interface='" + std::string(properties::interface) +
125 "',path='" + std::string(power::path) + "',arg0='" +
126 std::string(power::interface) + "'",
127 [](sdbusplus::message::message& message) {
128 std::string objectName;
129 boost::container::flat_map<std::string, std::variant<std::string>>
130 values;
131 message.read(objectName, values);
132 auto findState = values.find(power::property);
133 if (findState != values.end())
134 {
135 powerStatusOn = boost::ends_with(
136 std::get<std::string>(findState->second), "Running");
137 }
138 });
139
140 conn->async_method_call(
141 [](boost::system::error_code ec,
142 const std::variant<std::string>& state) {
143 if (ec)
144 {
145 return;
146 }
147 powerStatusOn =
148 boost::ends_with(std::get<std::string>(state), "Running");
149 },
150 power::busname, power::path, properties::interface, properties::get,
151 power::interface, power::property);
James Feist481c5d52019-08-13 14:40:40 -0700152}
153
Matt Spinlere789bf12021-02-24 12:33:01 -0600154// Replaces the template character like the other version of this function,
155// but checks all properties on all interfaces provided to do the substitution
156// with.
157std::optional<std::string> templateCharReplace(
158 nlohmann::json::iterator& keyPair,
159 const boost::container::flat_map<
160 std::string, boost::container::flat_map<std::string, BasicVariantType>>&
161 allInterfaces,
162 const size_t foundDeviceIdx, const std::optional<std::string>& replaceStr)
163{
164 for (const auto& [interface, properties] : allInterfaces)
165 {
166 auto ret = templateCharReplace(keyPair, properties, foundDeviceIdx,
167 replaceStr);
168 if (ret)
169 {
170 return ret;
171 }
172 }
173 return std::nullopt;
174}
175
James Feist481c5d52019-08-13 14:40:40 -0700176// finds the template character (currently set to $) and replaces the value with
177// the field found in a dbus object i.e. $ADDRESS would get populated with the
178// ADDRESS field from a object on dbus
James Feist35f5e0e2020-03-16 14:02:27 -0700179std::optional<std::string> templateCharReplace(
James Feist481c5d52019-08-13 14:40:40 -0700180 nlohmann::json::iterator& keyPair,
181 const boost::container::flat_map<std::string, BasicVariantType>&
182 foundDevice,
James Feist35f5e0e2020-03-16 14:02:27 -0700183 const size_t foundDeviceIdx, const std::optional<std::string>& replaceStr)
James Feist481c5d52019-08-13 14:40:40 -0700184{
James Feist35f5e0e2020-03-16 14:02:27 -0700185 std::optional<std::string> ret = std::nullopt;
186
James Feist481c5d52019-08-13 14:40:40 -0700187 if (keyPair.value().type() == nlohmann::json::value_t::object ||
188 keyPair.value().type() == nlohmann::json::value_t::array)
189 {
190 for (auto nextLayer = keyPair.value().begin();
191 nextLayer != keyPair.value().end(); nextLayer++)
192 {
James Feist35f5e0e2020-03-16 14:02:27 -0700193 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx,
194 replaceStr);
James Feist481c5d52019-08-13 14:40:40 -0700195 }
James Feist35f5e0e2020-03-16 14:02:27 -0700196 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700197 }
198
199 std::string* strPtr = keyPair.value().get_ptr<std::string*>();
200 if (strPtr == nullptr)
201 {
James Feist35f5e0e2020-03-16 14:02:27 -0700202 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700203 }
204
205 boost::replace_all(*strPtr, std::string(templateChar) + "index",
206 std::to_string(foundDeviceIdx));
James Feist35f5e0e2020-03-16 14:02:27 -0700207 if (replaceStr)
208 {
209 boost::replace_all(*strPtr, *replaceStr,
210 std::to_string(foundDeviceIdx));
211 }
James Feist481c5d52019-08-13 14:40:40 -0700212
213 for (auto& foundDevicePair : foundDevice)
214 {
215 std::string templateName = templateChar + foundDevicePair.first;
216 boost::iterator_range<std::string::const_iterator> find =
217 boost::ifind_first(*strPtr, templateName);
218 if (find)
219 {
James Feist8c20feb2019-08-14 15:10:11 -0700220 constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
221 '/'};
James Feist481c5d52019-08-13 14:40:40 -0700222 size_t start = find.begin() - strPtr->begin();
James Feist8c20feb2019-08-14 15:10:11 -0700223 size_t nextItemIdx = start + templateName.size() + 1;
224
James Feist481c5d52019-08-13 14:40:40 -0700225 // check for additional operations
226 if (!start && find.end() == strPtr->end())
227 {
228 std::visit([&](auto&& val) { keyPair.value() = val; },
229 foundDevicePair.second);
James Feist35f5e0e2020-03-16 14:02:27 -0700230 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700231 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800232 if (nextItemIdx > strPtr->size() ||
233 std::find(mathChars.begin(), mathChars.end(),
234 strPtr->at(nextItemIdx)) == mathChars.end())
James Feist481c5d52019-08-13 14:40:40 -0700235 {
236 std::string val = std::visit(VariantToStringVisitor(),
237 foundDevicePair.second);
James Feistb0097d42019-08-15 09:24:13 -0700238 boost::ireplace_all(*strPtr,
239 templateChar + foundDevicePair.first, val);
James Feist8c20feb2019-08-14 15:10:11 -0700240 continue;
James Feist481c5d52019-08-13 14:40:40 -0700241 }
242
243 // save the prefix
244 std::string prefix = strPtr->substr(0, start);
245
James Feist8c20feb2019-08-14 15:10:11 -0700246 // operate on the rest
247 std::string end = strPtr->substr(nextItemIdx);
James Feist481c5d52019-08-13 14:40:40 -0700248
249 std::vector<std::string> split;
250 boost::split(split, end, boost::is_any_of(" "));
251
252 // need at least 1 operation and number
253 if (split.size() < 2)
254 {
255 std::cerr << "Syntax error on template replacement of "
256 << *strPtr << "\n";
257 for (const std::string& data : split)
258 {
259 std::cerr << data << " ";
260 }
261 std::cerr << "\n";
262 continue;
263 }
264
265 // we assume that the replacement is a number, because we can
266 // only do math on numbers.. we might concatenate strings in the
267 // future, but thats later
268 int number =
269 std::visit(VariantToIntVisitor(), foundDevicePair.second);
270
271 bool isOperator = true;
272 TemplateOperation next = TemplateOperation::addition;
273
274 auto it = split.begin();
275
276 for (; it != split.end(); it++)
277 {
278 if (isOperator)
279 {
280 if (*it == "+")
281 {
282 next = TemplateOperation::addition;
283 }
284 else if (*it == "-")
285 {
286 next = TemplateOperation::subtraction;
287 }
288 else if (*it == "*")
289 {
290 next = TemplateOperation::multiplication;
291 }
292 else if (*it == R"(%)")
293 {
294 next = TemplateOperation::modulo;
295 }
296 else if (*it == R"(/)")
297 {
298 next = TemplateOperation::division;
299 }
300 else
301 {
302 break;
303 }
304 }
305 else
306 {
307 int constant = 0;
308 try
309 {
310 constant = std::stoi(*it);
311 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500312 catch (const std::invalid_argument&)
James Feist481c5d52019-08-13 14:40:40 -0700313 {
314 std::cerr << "Parameter not supported for templates "
315 << *it << "\n";
316 continue;
317 }
318 switch (next)
319 {
320 case TemplateOperation::addition:
321 {
322 number += constant;
323 break;
324 }
325 case TemplateOperation::subtraction:
326 {
327 number -= constant;
328 break;
329 }
330 case TemplateOperation::multiplication:
331 {
332 number *= constant;
333 break;
334 }
335 case TemplateOperation::division:
336 {
337 number /= constant;
338 break;
339 }
340 case TemplateOperation::modulo:
341 {
342 number = number % constant;
343 break;
344 }
345
346 default:
347 break;
348 }
349 }
350 isOperator = !isOperator;
351 }
James Feist35f5e0e2020-03-16 14:02:27 -0700352
James Feist481c5d52019-08-13 14:40:40 -0700353 std::string result = prefix + std::to_string(number);
354
James Feist35f5e0e2020-03-16 14:02:27 -0700355 std::string replaced(find.begin(), find.end());
356 for (auto it2 = split.begin(); it2 != split.end(); it2++)
357 {
358 replaced += " ";
359 replaced += *it2;
360 if (it2 == it)
361 {
362 break;
363 }
364 }
365 ret = replaced;
366
James Feist481c5d52019-08-13 14:40:40 -0700367 if (it != split.end())
368 {
369 for (; it != split.end(); it++)
370 {
371 result += " " + *it;
372 }
373 }
374 keyPair.value() = result;
375
Zhikui Rena0d1b3f2021-10-05 16:21:56 -0700376 // We probably just invalidated the pointer abovei,
377 // reset and continue to handle multiple templates
378 strPtr = keyPair.value().get_ptr<std::string*>();
379 if (strPtr == nullptr)
380 {
381 break;
382 }
James Feist481c5d52019-08-13 14:40:40 -0700383 }
384 }
385
386 strPtr = keyPair.value().get_ptr<std::string*>();
387 if (strPtr == nullptr)
388 {
James Feist35f5e0e2020-03-16 14:02:27 -0700389 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700390 }
391
392 // convert hex numbers to ints
393 if (boost::starts_with(*strPtr, "0x"))
394 {
395 try
396 {
397 size_t pos = 0;
398 int64_t temp = std::stoul(*strPtr, &pos, 0);
399 if (pos == strPtr->size())
400 {
401 keyPair.value() = static_cast<uint64_t>(temp);
402 }
403 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500404 catch (const std::invalid_argument&)
James Feist8c505da2020-05-28 10:06:33 -0700405 {}
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500406 catch (const std::out_of_range&)
James Feist8c505da2020-05-28 10:06:33 -0700407 {}
James Feist481c5d52019-08-13 14:40:40 -0700408 }
409 // non-hex numbers
410 else
411 {
412 try
413 {
414 uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
415 keyPair.value() = temp;
416 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500417 catch (const boost::bad_lexical_cast&)
James Feist8c505da2020-05-28 10:06:33 -0700418 {}
James Feist481c5d52019-08-13 14:40:40 -0700419 }
James Feist35f5e0e2020-03-16 14:02:27 -0700420 return ret;
Xiang Liu2801a702020-01-20 14:29:34 -0800421}
Brad Bishop3cb8a602020-08-25 17:40:54 -0400422
Brad Bishop5d525412020-08-26 08:50:50 -0400423/// \brief JSON/DBus matching Callable for std::variant (visitor)
424///
425/// Default match JSON/DBus match implementation
426/// \tparam T The concrete DBus value type from BasicVariantType
427template <typename T>
428struct MatchProbe
429{
430 /// \param probe the probe statement to match against
431 /// \param value the property value being matched to a probe
432 /// \return true if the dbusValue matched the probe otherwise false
433 static bool match(const nlohmann::json& probe, const T& value)
434 {
435 return probe == value;
436 }
437};
438
439/// \brief JSON/DBus matching Callable for std::variant (visitor)
440///
441/// std::string specialization of MatchProbe enabling regex matching
442template <>
443struct MatchProbe<std::string>
444{
445 /// \param probe the probe statement to match against
446 /// \param value the string value being matched to a probe
447 /// \return true if the dbusValue matched the probe otherwise false
448 static bool match(const nlohmann::json& probe, const std::string& value)
449 {
450 if (probe.is_string())
451 {
452 try
453 {
454 std::regex search(probe);
455 std::smatch regMatch;
456 return std::regex_search(value, regMatch, search);
457 }
458 catch (const std::regex_error&)
459 {
460 std::cerr << "Syntax error in regular expression: " << probe
461 << " will never match";
462 }
463 }
464
465 // Skip calling nlohmann here, since it will never match a non-string
466 // to a std::string
467 return false;
468 }
469};
470
471/// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
472///
473/// Forward calls to the correct template instantiation of MatchProbe
474struct MatchProbeForwarder
475{
476 explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
477 {}
478 const nlohmann::json& probeRef;
479
480 template <typename T>
481 bool operator()(const T& dbusValue) const
482 {
483 return MatchProbe<T>::match(probeRef, dbusValue);
484 }
485};
486
Brad Bishop3cb8a602020-08-25 17:40:54 -0400487bool matchProbe(const nlohmann::json& probe, const BasicVariantType& dbusValue)
488{
Brad Bishop5d525412020-08-26 08:50:50 -0400489 return std::visit(MatchProbeForwarder(probe), dbusValue);
490}