blob: 7024e478273ec96c146b7e9ad3cd3185a16da3a7 [file] [log] [blame]
Andrew Jefferyf07c5ed2021-12-01 14:22:04 +10301/*
2// Copyright (c) 2018 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*/
16/// \file PerformProbe.cpp
17#include "EntityManager.hpp"
18
19#include <boost/algorithm/string/replace.hpp>
20
21#include <regex>
22
23constexpr const bool debug = false;
24
25/* Keep this in sync with EntityManager.cpp */
26static const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
27 probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
28 {"TRUE", probe_type_codes::TRUE_T},
29 {"AND", probe_type_codes::AND},
30 {"OR", probe_type_codes::OR},
31 {"FOUND", probe_type_codes::FOUND},
32 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
33
34// probes dbus interface dictionary for a key with a value that matches a regex
35// When an interface passes a probe, also save its D-Bus path with it.
36bool probeDbus(const std::string& interface,
37 const std::map<std::string, nlohmann::json>& matches,
38 FoundDeviceT& devices, const std::shared_ptr<PerformScan>& scan,
39 bool& foundProbe)
40{
41 bool foundMatch = false;
42 foundProbe = false;
43
44 for (const auto& [path, interfaces] : scan->dbusProbeObjects)
45 {
46 auto it = interfaces.find(interface);
47 if (it == interfaces.end())
48 {
49 continue;
50 }
51
52 foundProbe = true;
53
54 bool deviceMatches = true;
55 const boost::container::flat_map<std::string, BasicVariantType>&
56 properties = it->second;
57
58 for (const auto& [matchProp, matchJSON] : matches)
59 {
60 auto deviceValue = properties.find(matchProp);
61 if (deviceValue != properties.end())
62 {
63 deviceMatches =
64 deviceMatches && matchProbe(matchJSON, deviceValue->second);
65 }
66 else
67 {
68 // Move on to the next DBus path
69 deviceMatches = false;
70 break;
71 }
72 }
73 if (deviceMatches)
74 {
75 if constexpr (debug)
76 {
77 std::cerr << "probeDBus: Found probe match on " << path << " "
78 << interface << "\n";
79 }
80 devices.emplace_back(properties, path);
81 foundMatch = true;
82 }
83 }
84 return foundMatch;
85}
86
87// default probe entry point, iterates a list looking for specific types to
88// call specific probe functions
89bool probe(const std::vector<std::string>& probeCommand,
90 const std::shared_ptr<PerformScan>& scan, FoundDeviceT& foundDevs)
91{
92 const static std::regex command(R"(\((.*)\))");
93 std::smatch match;
94 bool ret = false;
95 bool matchOne = false;
96 bool cur = true;
97 probe_type_codes lastCommand = probe_type_codes::FALSE_T;
98 bool first = true;
99
100 for (auto& probe : probeCommand)
101 {
102 bool foundProbe = false;
103 boost::container::flat_map<const char*, probe_type_codes,
104 CmpStr>::const_iterator probeType;
105
106 for (probeType = probeTypes.begin(); probeType != probeTypes.end();
107 ++probeType)
108 {
109 if (probe.find(probeType->first) != std::string::npos)
110 {
111 foundProbe = true;
112 break;
113 }
114 }
115 if (foundProbe)
116 {
117 switch (probeType->second)
118 {
119 case probe_type_codes::FALSE_T:
120 {
121 cur = false;
122 break;
123 }
124 case probe_type_codes::TRUE_T:
125 {
126 cur = true;
127 break;
128 }
129 case probe_type_codes::MATCH_ONE:
130 {
131 // set current value to last, this probe type shouldn't
132 // affect the outcome
133 cur = ret;
134 matchOne = true;
135 break;
136 }
137 /*case probe_type_codes::AND:
138 break;
139 case probe_type_codes::OR:
140 break;
141 // these are no-ops until the last command switch
142 */
143 case probe_type_codes::FOUND:
144 {
145 if (!std::regex_search(probe, match, command))
146 {
147 std::cerr << "found probe syntax error " << probe
148 << "\n";
149 return false;
150 }
151 std::string commandStr = *(match.begin() + 1);
152 boost::replace_all(commandStr, "'", "");
153 cur = (std::find(scan->passedProbes.begin(),
154 scan->passedProbes.end(),
155 commandStr) != scan->passedProbes.end());
156 break;
157 }
158 default:
159 {
160 break;
161 }
162 }
163 }
164 // look on dbus for object
165 else
166 {
167 if (!std::regex_search(probe, match, command))
168 {
169 std::cerr << "dbus probe syntax error " << probe << "\n";
170 return false;
171 }
172 std::string commandStr = *(match.begin() + 1);
173 // convert single ticks and single slashes into legal json
174 boost::replace_all(commandStr, "'", "\"");
175 boost::replace_all(commandStr, R"(\)", R"(\\)");
176 auto json = nlohmann::json::parse(commandStr, nullptr, false);
177 if (json.is_discarded())
178 {
179 std::cerr << "dbus command syntax error " << commandStr << "\n";
180 return false;
181 }
182 // we can match any (string, variant) property. (string, string)
183 // does a regex
184 std::map<std::string, nlohmann::json> dbusProbeMap =
185 json.get<std::map<std::string, nlohmann::json>>();
186 auto findStart = probe.find('(');
187 if (findStart == std::string::npos)
188 {
189 return false;
190 }
191 std::string probeInterface = probe.substr(0, findStart);
192 cur = probeDbus(probeInterface, dbusProbeMap, foundDevs, scan,
193 foundProbe);
194 }
195
196 // some functions like AND and OR only take affect after the
197 // fact
198 if (lastCommand == probe_type_codes::AND)
199 {
200 ret = cur && ret;
201 }
202 else if (lastCommand == probe_type_codes::OR)
203 {
204 ret = cur || ret;
205 }
206
207 if (first)
208 {
209 ret = cur;
210 first = false;
211 }
212 lastCommand = probeType != probeTypes.end() ? probeType->second
213 : probe_type_codes::FALSE_T;
214 }
215
216 // probe passed, but empty device
217 if (ret && foundDevs.size() == 0)
218 {
219 foundDevs.emplace_back(
220 boost::container::flat_map<std::string, BasicVariantType>{},
221 std::string{});
222 }
223 if (matchOne && ret)
224 {
225 // match the last one
226 auto last = foundDevs.back();
227 foundDevs.clear();
228
229 foundDevs.emplace_back(std::move(last));
230 }
231 return ret;
232}
233
234PerformProbe::PerformProbe(
235 const std::vector<std::string>& probeCommand,
236 std::shared_ptr<PerformScan>& scanPtr,
237 std::function<void(FoundDeviceT&, const DBusProbeObjectT&)>&& callback) :
238 _probeCommand(probeCommand),
239 scan(scanPtr), _callback(std::move(callback))
240{}
241PerformProbe::~PerformProbe()
242{
243 FoundDeviceT foundDevs;
244 if (probe(_probeCommand, scan, foundDevs))
245 {
246 _callback(foundDevs, scan->dbusProbeObjects);
247 }
248}