| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 1 | #include "dbus_interface.hpp" | 
|  | 2 |  | 
|  | 3 | #include "perform_probe.hpp" | 
| Christopher Meis | 59ef1e7 | 2025-04-16 08:53:25 +0200 | [diff] [blame] | 4 | #include "utils.hpp" | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 5 |  | 
|  | 6 | #include <boost/algorithm/string/case_conv.hpp> | 
|  | 7 | #include <boost/container/flat_map.hpp> | 
|  | 8 |  | 
| Christopher Meis | 59ef1e7 | 2025-04-16 08:53:25 +0200 | [diff] [blame] | 9 | #include <fstream> | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 10 | #include <regex> | 
|  | 11 | #include <string> | 
|  | 12 | #include <vector> | 
|  | 13 |  | 
|  | 14 | using JsonVariantType = | 
|  | 15 | std::variant<std::vector<std::string>, std::vector<double>, std::string, | 
|  | 16 | int64_t, uint64_t, double, int32_t, uint32_t, int16_t, | 
|  | 17 | uint16_t, uint8_t, bool>; | 
|  | 18 |  | 
|  | 19 | namespace dbus_interface | 
|  | 20 | { | 
|  | 21 |  | 
|  | 22 | const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); | 
|  | 23 | const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); | 
|  | 24 |  | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 25 | void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface) | 
|  | 26 | { | 
|  | 27 | try | 
|  | 28 | { | 
|  | 29 | iface->initialize(); | 
|  | 30 | } | 
|  | 31 | catch (std::exception& e) | 
|  | 32 | { | 
|  | 33 | std::cerr << "Unable to initialize dbus interface : " << e.what() | 
|  | 34 | << "\n" | 
|  | 35 | << "object Path : " << iface->get_object_path() << "\n" | 
|  | 36 | << "interface name : " << iface->get_interface_name() << "\n"; | 
|  | 37 | } | 
|  | 38 | } | 
|  | 39 |  | 
| Alexander Hansen | 57604ed | 2025-06-27 13:22:28 +0200 | [diff] [blame] | 40 | std::shared_ptr<sdbusplus::asio::dbus_interface> | 
|  | 41 | EMDBusInterface::createInterface( | 
|  | 42 | sdbusplus::asio::object_server& objServer, const std::string& path, | 
|  | 43 | const std::string& interface, const std::string& parent, bool checkNull) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 44 | { | 
|  | 45 | // on first add we have no reason to check for null before add, as there | 
|  | 46 | // won't be any. For dynamically added interfaces, we check for null so that | 
|  | 47 | // a constant delete/add will not create a memory leak | 
|  | 48 |  | 
|  | 49 | auto ptr = objServer.add_interface(path, interface); | 
|  | 50 | auto& dataVector = inventory[parent]; | 
|  | 51 | if (checkNull) | 
|  | 52 | { | 
|  | 53 | auto it = std::find_if(dataVector.begin(), dataVector.end(), | 
|  | 54 | [](const auto& p) { return p.expired(); }); | 
|  | 55 | if (it != dataVector.end()) | 
|  | 56 | { | 
|  | 57 | *it = ptr; | 
|  | 58 | return ptr; | 
|  | 59 | } | 
|  | 60 | } | 
|  | 61 | dataVector.emplace_back(ptr); | 
|  | 62 | return ptr; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | void createDeleteObjectMethod( | 
|  | 66 | const std::string& jsonPointerPath, | 
|  | 67 | const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, | 
|  | 68 | sdbusplus::asio::object_server& objServer, | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 69 | nlohmann::json& systemConfiguration, boost::asio::io_context& io) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 70 | { | 
|  | 71 | std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; | 
|  | 72 | iface->register_method( | 
|  | 73 | "Delete", [&objServer, &systemConfiguration, interface, | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 74 | jsonPointerPath{std::string(jsonPointerPath)}, &io]() { | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 75 | std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface = | 
|  | 76 | interface.lock(); | 
|  | 77 | if (!dbusInterface) | 
|  | 78 | { | 
|  | 79 | // this technically can't happen as the pointer is pointing to | 
|  | 80 | // us | 
|  | 81 | throw DBusInternalError(); | 
|  | 82 | } | 
|  | 83 | nlohmann::json::json_pointer ptr(jsonPointerPath); | 
|  | 84 | systemConfiguration[ptr] = nullptr; | 
|  | 85 |  | 
|  | 86 | // todo(james): dig through sdbusplus to find out why we can't | 
|  | 87 | // delete it in a method call | 
|  | 88 | boost::asio::post(io, [&objServer, dbusInterface]() mutable { | 
|  | 89 | objServer.remove_interface(dbusInterface); | 
|  | 90 | }); | 
|  | 91 |  | 
| Christopher Meis | f725257 | 2025-06-11 13:22:05 +0200 | [diff] [blame] | 92 | if (!writeJsonFiles(systemConfiguration)) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 93 | { | 
|  | 94 | std::cerr << "error setting json file\n"; | 
|  | 95 | throw DBusInternalError(); | 
|  | 96 | } | 
|  | 97 | }); | 
|  | 98 | } | 
|  | 99 |  | 
| Alexander Hansen | d72dc33 | 2025-06-10 10:49:24 +0200 | [diff] [blame] | 100 | static bool checkArrayElementsSameType(nlohmann::json& value) | 
|  | 101 | { | 
|  | 102 | nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>(); | 
|  | 103 | if (arr == nullptr) | 
|  | 104 | { | 
|  | 105 | return false; | 
|  | 106 | } | 
|  | 107 |  | 
|  | 108 | if (arr->empty()) | 
|  | 109 | { | 
|  | 110 | return true; | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | nlohmann::json::value_t firstType = value[0].type(); | 
|  | 114 | return std::ranges::all_of(value, [firstType](const nlohmann::json& el) { | 
|  | 115 | return el.type() == firstType; | 
|  | 116 | }); | 
|  | 117 | } | 
|  | 118 |  | 
| Alexander Hansen | 91e360e | 2025-06-10 12:21:30 +0200 | [diff] [blame] | 119 | static nlohmann::json::value_t getDBusType( | 
|  | 120 | const nlohmann::json& value, nlohmann::json::value_t type, | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 121 | sdbusplus::asio::PropertyPermission permission) | 
|  | 122 | { | 
|  | 123 | const bool array = value.type() == nlohmann::json::value_t::array; | 
|  | 124 |  | 
|  | 125 | if (permission == sdbusplus::asio::PropertyPermission::readWrite) | 
|  | 126 | { | 
|  | 127 | // all setable numbers are doubles as it is difficult to always | 
|  | 128 | // create a configuration file with all whole numbers as decimals | 
|  | 129 | // i.e. 1.0 | 
|  | 130 | if (array) | 
|  | 131 | { | 
|  | 132 | if (value[0].is_number()) | 
|  | 133 | { | 
| Alexander Hansen | 91e360e | 2025-06-10 12:21:30 +0200 | [diff] [blame] | 134 | return nlohmann::json::value_t::number_float; | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 135 | } | 
|  | 136 | } | 
|  | 137 | else if (value.is_number()) | 
|  | 138 | { | 
| Alexander Hansen | 91e360e | 2025-06-10 12:21:30 +0200 | [diff] [blame] | 139 | return nlohmann::json::value_t::number_float; | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 140 | } | 
|  | 141 | } | 
|  | 142 |  | 
| Alexander Hansen | 91e360e | 2025-06-10 12:21:30 +0200 | [diff] [blame] | 143 | return type; | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | static void populateInterfacePropertyFromJson( | 
|  | 147 | nlohmann::json& systemConfiguration, const std::string& path, | 
|  | 148 | const nlohmann::json& key, const nlohmann::json& value, | 
|  | 149 | nlohmann::json::value_t type, | 
|  | 150 | std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, | 
|  | 151 | sdbusplus::asio::PropertyPermission permission) | 
|  | 152 | { | 
|  | 153 | const auto modifiedType = getDBusType(value, type, permission); | 
|  | 154 |  | 
|  | 155 | switch (modifiedType) | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 156 | { | 
|  | 157 | case (nlohmann::json::value_t::boolean): | 
|  | 158 | { | 
| Alexander Hansen | 0123f8a | 2025-06-27 12:11:08 +0200 | [diff] [blame] | 159 | // todo: array of bool isn't detected correctly by | 
|  | 160 | // sdbusplus, change it to numbers | 
|  | 161 | addValueToDBus<uint64_t, bool>(key, value, *iface, permission, | 
|  | 162 | systemConfiguration, path); | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 163 | break; | 
|  | 164 | } | 
|  | 165 | case (nlohmann::json::value_t::number_integer): | 
|  | 166 | { | 
|  | 167 | addValueToDBus<int64_t>(key, value, *iface, permission, | 
|  | 168 | systemConfiguration, path); | 
|  | 169 | break; | 
|  | 170 | } | 
|  | 171 | case (nlohmann::json::value_t::number_unsigned): | 
|  | 172 | { | 
|  | 173 | addValueToDBus<uint64_t>(key, value, *iface, permission, | 
|  | 174 | systemConfiguration, path); | 
|  | 175 | break; | 
|  | 176 | } | 
|  | 177 | case (nlohmann::json::value_t::number_float): | 
|  | 178 | { | 
|  | 179 | addValueToDBus<double>(key, value, *iface, permission, | 
|  | 180 | systemConfiguration, path); | 
|  | 181 | break; | 
|  | 182 | } | 
|  | 183 | case (nlohmann::json::value_t::string): | 
|  | 184 | { | 
|  | 185 | addValueToDBus<std::string>(key, value, *iface, permission, | 
|  | 186 | systemConfiguration, path); | 
|  | 187 | break; | 
|  | 188 | } | 
|  | 189 | default: | 
|  | 190 | { | 
|  | 191 | std::cerr << "Unexpected json type in system configuration " << key | 
|  | 192 | << ": " << value.type_name() << "\n"; | 
|  | 193 | break; | 
|  | 194 | } | 
|  | 195 | } | 
|  | 196 | } | 
|  | 197 |  | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 198 | // adds simple json types to interface's properties | 
|  | 199 | void populateInterfaceFromJson( | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 200 | boost::asio::io_context& io, nlohmann::json& systemConfiguration, | 
|  | 201 | const std::string& jsonPointerPath, | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 202 | std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, | 
|  | 203 | nlohmann::json& dict, sdbusplus::asio::object_server& objServer, | 
|  | 204 | sdbusplus::asio::PropertyPermission permission) | 
|  | 205 | { | 
|  | 206 | for (const auto& [key, value] : dict.items()) | 
|  | 207 | { | 
|  | 208 | auto type = value.type(); | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 209 | if (value.type() == nlohmann::json::value_t::array) | 
|  | 210 | { | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 211 | if (value.empty()) | 
|  | 212 | { | 
|  | 213 | continue; | 
|  | 214 | } | 
|  | 215 | type = value[0].type(); | 
| Alexander Hansen | d72dc33 | 2025-06-10 10:49:24 +0200 | [diff] [blame] | 216 | if (!checkArrayElementsSameType(value)) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 217 | { | 
|  | 218 | std::cerr << "dbus format error" << value << "\n"; | 
|  | 219 | continue; | 
|  | 220 | } | 
|  | 221 | } | 
|  | 222 | if (type == nlohmann::json::value_t::object) | 
|  | 223 | { | 
|  | 224 | continue; // handled elsewhere | 
|  | 225 | } | 
|  | 226 |  | 
|  | 227 | std::string path = jsonPointerPath; | 
|  | 228 | path.append("/").append(key); | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 229 |  | 
| Alexander Hansen | 17f8e6a | 2025-06-10 12:10:09 +0200 | [diff] [blame] | 230 | populateInterfacePropertyFromJson(systemConfiguration, path, key, value, | 
|  | 231 | type, iface, permission); | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 232 | } | 
|  | 233 | if (permission == sdbusplus::asio::PropertyPermission::readWrite) | 
|  | 234 | { | 
|  | 235 | createDeleteObjectMethod(jsonPointerPath, iface, objServer, | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 236 | systemConfiguration, io); | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 237 | } | 
|  | 238 | tryIfaceInitialize(iface); | 
|  | 239 | } | 
|  | 240 |  | 
| Alexander Hansen | 57604ed | 2025-06-27 13:22:28 +0200 | [diff] [blame] | 241 | void EMDBusInterface::createAddObjectMethod( | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 242 | boost::asio::io_context& io, const std::string& jsonPointerPath, | 
|  | 243 | const std::string& path, nlohmann::json& systemConfiguration, | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 244 | sdbusplus::asio::object_server& objServer, const std::string& board) | 
|  | 245 | { | 
|  | 246 | std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface( | 
|  | 247 | objServer, path, "xyz.openbmc_project.AddObject", board); | 
|  | 248 |  | 
|  | 249 | iface->register_method( | 
|  | 250 | "AddObject", | 
|  | 251 | [&systemConfiguration, &objServer, | 
|  | 252 | jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)}, | 
| Alexander Hansen | 57604ed | 2025-06-27 13:22:28 +0200 | [diff] [blame] | 253 | board, &io, | 
|  | 254 | this](const boost::container::flat_map<std::string, JsonVariantType>& | 
|  | 255 | data) { | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 256 | nlohmann::json::json_pointer ptr(jsonPointerPath); | 
|  | 257 | nlohmann::json& base = systemConfiguration[ptr]; | 
|  | 258 | auto findExposes = base.find("Exposes"); | 
|  | 259 |  | 
|  | 260 | if (findExposes == base.end()) | 
|  | 261 | { | 
|  | 262 | throw std::invalid_argument("Entity must have children."); | 
|  | 263 | } | 
|  | 264 |  | 
|  | 265 | // this will throw invalid-argument to sdbusplus if invalid json | 
|  | 266 | nlohmann::json newData{}; | 
|  | 267 | for (const auto& item : data) | 
|  | 268 | { | 
|  | 269 | nlohmann::json& newJson = newData[item.first]; | 
|  | 270 | std::visit( | 
|  | 271 | [&newJson](auto&& val) { | 
|  | 272 | newJson = std::forward<decltype(val)>(val); | 
|  | 273 | }, | 
|  | 274 | item.second); | 
|  | 275 | } | 
|  | 276 |  | 
|  | 277 | auto findName = newData.find("Name"); | 
|  | 278 | auto findType = newData.find("Type"); | 
|  | 279 | if (findName == newData.end() || findType == newData.end()) | 
|  | 280 | { | 
|  | 281 | throw std::invalid_argument("AddObject missing Name or Type"); | 
|  | 282 | } | 
|  | 283 | const std::string* type = findType->get_ptr<const std::string*>(); | 
|  | 284 | const std::string* name = findName->get_ptr<const std::string*>(); | 
|  | 285 | if (type == nullptr || name == nullptr) | 
|  | 286 | { | 
|  | 287 | throw std::invalid_argument("Type and Name must be a string."); | 
|  | 288 | } | 
|  | 289 |  | 
|  | 290 | bool foundNull = false; | 
|  | 291 | size_t lastIndex = 0; | 
|  | 292 | // we add in the "exposes" | 
|  | 293 | for (const auto& expose : *findExposes) | 
|  | 294 | { | 
|  | 295 | if (expose.is_null()) | 
|  | 296 | { | 
|  | 297 | foundNull = true; | 
|  | 298 | continue; | 
|  | 299 | } | 
|  | 300 |  | 
|  | 301 | if (expose["Name"] == *name && expose["Type"] == *type) | 
|  | 302 | { | 
|  | 303 | throw std::invalid_argument( | 
|  | 304 | "Field already in JSON, not adding"); | 
|  | 305 | } | 
|  | 306 |  | 
|  | 307 | if (foundNull) | 
|  | 308 | { | 
|  | 309 | continue; | 
|  | 310 | } | 
|  | 311 |  | 
|  | 312 | lastIndex++; | 
|  | 313 | } | 
|  | 314 |  | 
| Christopher Meis | f725257 | 2025-06-11 13:22:05 +0200 | [diff] [blame] | 315 | std::ifstream schemaFile(std::string(schemaDirectory) + "/" + | 
|  | 316 | boost::to_lower_copy(*type) + ".json"); | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 317 | // todo(james) we might want to also make a list of 'can add' | 
|  | 318 | // interfaces but for now I think the assumption if there is a | 
|  | 319 | // schema avaliable that it is allowed to update is fine | 
|  | 320 | if (!schemaFile.good()) | 
|  | 321 | { | 
|  | 322 | throw std::invalid_argument( | 
|  | 323 | "No schema avaliable, cannot validate."); | 
|  | 324 | } | 
|  | 325 | nlohmann::json schema = | 
|  | 326 | nlohmann::json::parse(schemaFile, nullptr, false, true); | 
|  | 327 | if (schema.is_discarded()) | 
|  | 328 | { | 
|  | 329 | std::cerr << "Schema not legal" << *type << ".json\n"; | 
|  | 330 | throw DBusInternalError(); | 
|  | 331 | } | 
| Christopher Meis | f725257 | 2025-06-11 13:22:05 +0200 | [diff] [blame] | 332 | if (!validateJson(schema, newData)) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 333 | { | 
|  | 334 | throw std::invalid_argument("Data does not match schema"); | 
|  | 335 | } | 
|  | 336 | if (foundNull) | 
|  | 337 | { | 
|  | 338 | findExposes->at(lastIndex) = newData; | 
|  | 339 | } | 
|  | 340 | else | 
|  | 341 | { | 
|  | 342 | findExposes->push_back(newData); | 
|  | 343 | } | 
| Christopher Meis | f725257 | 2025-06-11 13:22:05 +0200 | [diff] [blame] | 344 | if (!writeJsonFiles(systemConfiguration)) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 345 | { | 
|  | 346 | std::cerr << "Error writing json files\n"; | 
|  | 347 | throw DBusInternalError(); | 
|  | 348 | } | 
|  | 349 | std::string dbusName = *name; | 
|  | 350 |  | 
|  | 351 | std::regex_replace(dbusName.begin(), dbusName.begin(), | 
|  | 352 | dbusName.end(), illegalDbusMemberRegex, "_"); | 
|  | 353 |  | 
|  | 354 | std::shared_ptr<sdbusplus::asio::dbus_interface> interface = | 
|  | 355 | createInterface(objServer, path + "/" + dbusName, | 
|  | 356 | "xyz.openbmc_project.Configuration." + *type, | 
|  | 357 | board, true); | 
|  | 358 | // permission is read-write, as since we just created it, must be | 
|  | 359 | // runtime modifiable | 
|  | 360 | populateInterfaceFromJson( | 
| Alexander Hansen | a555acf | 2025-06-27 11:59:10 +0200 | [diff] [blame] | 361 | io, systemConfiguration, | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 362 | jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), | 
|  | 363 | interface, newData, objServer, | 
|  | 364 | sdbusplus::asio::PropertyPermission::readWrite); | 
|  | 365 | }); | 
|  | 366 | tryIfaceInitialize(iface); | 
|  | 367 | } | 
|  | 368 |  | 
|  | 369 | std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& | 
| Alexander Hansen | 57604ed | 2025-06-27 13:22:28 +0200 | [diff] [blame] | 370 | EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) | 
| Christopher Meis | 12bea9b | 2025-04-03 10:14:42 +0200 | [diff] [blame] | 371 | { | 
|  | 372 | return inventory[device["Name"].get<std::string>()]; | 
|  | 373 | } | 
|  | 374 |  | 
|  | 375 | } // namespace dbus_interface |