diff options
Diffstat (limited to 'agent/data')
-rw-r--r-- | agent/data/enums.hpp | 57 | ||||
-rw-r--r-- | agent/data/scalar.hpp | 150 | ||||
-rw-r--r-- | agent/data/table.hpp | 348 | ||||
-rw-r--r-- | agent/data/table/item.hpp | 157 |
4 files changed, 712 insertions, 0 deletions
diff --git a/agent/data/enums.hpp b/agent/data/enums.hpp new file mode 100644 index 0000000..bd6eb94 --- /dev/null +++ b/agent/data/enums.hpp @@ -0,0 +1,57 @@ +/** + * @brief DBus enums to base type converter. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include <string> +#include <map> + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ + +template <typename T> struct DBusEnum +{ + std::string base; + std::map<std::string, T> values; + T wrongValue; + + T get(const std::string& str) const + { + const auto len = base.length(); + if (0 == str.compare(0, len, base)) + { + const auto& it = values.find(str.substr(len + 1)); + if (it != values.end()) + { + return it->second; + } + } + + return wrongValue; + } +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/scalar.hpp b/agent/data/scalar.hpp new file mode 100644 index 0000000..2fc4a24 --- /dev/null +++ b/agent/data/scalar.hpp @@ -0,0 +1,150 @@ +/** + * @brief Export DBus property to scalar MIB value + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" +#include <memory> + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ +template <typename T> class Scalar +{ + public: + using value_t = std::variant<T>; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Scalar() = delete; + Scalar(const Scalar&) = delete; + Scalar& operator=(const Scalar&) = delete; + Scalar(Scalar&&) = default; + Scalar& operator=(Scalar&&) = default; + ~Scalar() = default; + + /** + * @brief Object constructor + */ + Scalar(const std::string& path, const std::string& iface, + const std::string& prop, const T& initValue) : + _value(initValue), + _path(path), _iface(iface), _prop(prop), + _onChangedMatch(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::propertiesChanged( + path.c_str(), iface.c_str()), + std::bind(&Scalar<T>::onPropertyChanged, this, + std::placeholders::_1)) + { + } + + /** + * @brief Sent request to DBus object and store new value of property + */ + void update() + { + try + { + auto service = sdbusplus::helper::helper::getService(_path, _iface); + auto r = sdbusplus::helper::helper::callMethod( + service, _path, sdbusplus::helper::PROPERTIES_IFACE, "Get", + _iface, _prop); + + value_t var; + r.read(var); + setValue(var); + } + catch (const sdbusplus::exception::SdBusError&) + { + // Corresponding service is not started yet. + // When service is started, we'll receive data with + // `propertiesChanged` signal. + // So, this catch block is just for silencing the exception. + } + } + + const T& getValue() const + { + return _value; + } + + const std::string& getPath() const + { + return _path; + } + + const std::string& getInterface() const + { + return _iface; + } + + const std::string& getProperty() const + { + return _prop; + } + + protected: + /** + * @brief DBus signal `PropertiesChanged` handler + */ + void onPropertyChanged(sdbusplus::message::message& m) + { + std::string iface; + std::map<std::string, value_t> data; + std::vector<std::string> v; + + m.read(iface, data, v); + + if (data.find(_prop) != data.end()) + { + setValue(data[_prop]); + } + } + + /** + * @brief Setter for actual value + */ + virtual void setValue(value_t& var) + { + auto newValue = std::get<T>(var); + std::swap(_value, newValue); + } + + private: + T _value; + std::string _path; + std::string _iface; + std::string _prop; + + sdbusplus::bus::match::match _onChangedMatch; +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/table.hpp b/agent/data/table.hpp new file mode 100644 index 0000000..ab891c4 --- /dev/null +++ b/agent/data/table.hpp @@ -0,0 +1,348 @@ +/** + * @brief Export DBus objects tree to MIB table + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ + +/** + * @brief MIB Table implementation. + */ +template <typename ItemType> class Table +{ + public: + using interfaces_t = std::vector<std::string>; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Table() = delete; + Table(const Table&) = delete; + Table& operator=(const Table&) = delete; + Table(Table&&) = default; + Table& operator=(Table&&) = default; + ~Table() = default; + + /** + * @brief Object constructor + * + * @param folder - DBus folder where required objects exist. + * @param interfaces - List of required DBus properties interfaces + */ + Table(const std::string& folder, const interfaces_t interfaces = {}) : + _path(folder), _interfaces(interfaces) + { + _matches.emplace_back(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::interfacesAdded(), + std::bind(&Table<ItemType>::onInterfacesAdded, + this, std::placeholders::_1)); + _matches.emplace_back(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::interfacesRemoved(), + std::bind(&Table<ItemType>::onInterfacesRemoved, + this, std::placeholders::_1)); + } + + /** + * @brief Force update table items + */ + void update() + { + auto data = sdbusplus::helper::helper::getSubTree(_path, _interfaces); + + // Drop sensors if it not present in answer + auto path = _path + "/"; + for (auto it = _items.begin(); it != _items.end();) + { + if (data.find(path + (*it)->name) == data.end()) + { + it = dropItem(it); + } + else + { + ++it; + } + } + + // Update existing and create new items. + using fields_map_t = typename ItemType::fields_map_t; + for (const auto& pi : data) + { + for (const auto& bi : pi.second) + { + auto fields = + sdbusplus::helper::helper::callMethodAndRead<fields_map_t>( + bi.first, pi.first, sdbusplus::helper::PROPERTIES_IFACE, + "GetAll", ""); + getItem(pi.first).setFields(fields); + } + } + } + + /** + * @brief Register MIB handlers + * + * @param name - Name of table in MIB + * @param table_oid - OID of table + * @param table_oid_len - Table OID length + * @param min_column - Minimum columns number + * @param max_column - Maximum columns number + */ + void init_mib(const char* name, const oid* table_oid, size_t table_oid_len, + size_t min_column, size_t max_column) + { + netsnmp_handler_registration* reg = netsnmp_create_handler_registration( + name, Table<ItemType>::snmp_handler, table_oid, table_oid_len, + HANDLER_CAN_RONLY); + + netsnmp_table_registration_info* table_info = + SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info); + netsnmp_table_helper_add_indexes(table_info, ASN_OCTET_STR, 0); + table_info->min_column = min_column; + table_info->max_column = max_column; + + netsnmp_iterator_info* iinfo = + SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info); + iinfo->get_first_data_point = Table<ItemType>::get_first_data_point; + iinfo->get_next_data_point = Table<ItemType>::get_next_data_point; + iinfo->table_reginfo = table_info; + iinfo->myvoid = this; + + netsnmp_register_table_iterator(reg, iinfo); + } + + protected: + using ItemPtr = std::unique_ptr<ItemType>; + using Items = std::vector<ItemPtr>; + + /** + * @brief DBus signal `InterfacesAdded` handler. + */ + void onInterfacesAdded(sdbusplus::message::message& m) + { + using Data = std::map<std::string, typename ItemType::fields_map_t>; + + sdbusplus::message::object_path path; + Data data; + m.read(path, data); + + if (0 == path.str.compare(0, _path.length(), _path)) + { + // Skip unnecessary objects + bool isOwned = _interfaces.empty(); + if (!isOwned) + { + auto it = std::find_first_of( + _interfaces.begin(), _interfaces.end(), data.begin(), + data.end(), + [](const std::string& iface, + const typename Data::value_type& item) { + return iface == item.first; + }); + isOwned = (it != _interfaces.end()); + } + + if (isOwned) + { + auto& item = getItem(path.str); + + for (const auto& [iface, fields] : data) + { + item.setFields(fields); + } + } + } + } + + /** + * @brief DBus signal `InterfacesRemoved` handler. + */ + void onInterfacesRemoved(sdbusplus::message::message& m) + { + sdbusplus::message::object_path path; + std::vector<std::string> data; + try + { + m.read(path, data); + } + catch (const sdbusplus::exception::SdBusError& e) + { + TRACE_ERROR("data/table: Failed to parse signal data. " + "ERROR='%s', REPLY_SIG='%s', PATH='%s', " + "IFACE='%s', MEMBER='%s', object path='%s'\n", + e.what(), m.get_signature(), m.get_path(), + m.get_interface(), m.get_member(), path.str.c_str()); + } + + if (0 == path.str.compare(0, _path.length(), _path)) + { + bool isOwned = _interfaces.empty(); + if (!isOwned) + { + auto it = + std::find_first_of(_interfaces.begin(), _interfaces.end(), + data.begin(), data.end()); + isOwned = (it != _interfaces.end()); + } + + if (isOwned) + { + dropItem(path.str); + } + } + } + + /** + * @brief Create new item if does not exist and return reference. + */ + ItemType& getItem(const std::string& path) + { + auto name = path.substr(_path.length() + 1); // Skip following '/' + auto it = std::lower_bound(_items.begin(), _items.end(), name); + if (it != _items.end() && (*it)->name == name) + { + return *(*it); + } + + it = _items.emplace(it, std::make_unique<ItemType>(_path, name)); + (*it)->onCreate(); + return *(*it); + } + + /** + * @brief Drop item specified by iterator. + * + * @param it - Items iterator + * + * @return Iterator pointing to the new location of the item that followed + * the item erased. + */ + typename Items::iterator dropItem(typename Items::iterator it) + { + if (it != _items.end()) + { + (*it)->onDestroy(); + it = _items.erase(it); + } + return it; + } + + /** + * @brief Drop item. + */ + void dropItem(const std::string& path) + { + auto name = path.substr(_path.length() + 1); // Skip following '/' + auto it = std::lower_bound(_items.begin(), _items.end(), name); + if (it != _items.end() && (*it)->name == name) + { + dropItem(it); + } + } + + /** + * @brief Iterator hook routines + */ + static netsnmp_variable_list* + get_first_data_point(void** loop_ctx, void** data_ctx, + netsnmp_variable_list* idx_data, + netsnmp_iterator_info* data) + { + *loop_ctx = reinterpret_cast<void*>(0); + return Table<ItemType>::get_next_data_point(loop_ctx, data_ctx, + idx_data, data); + } + static netsnmp_variable_list* + get_next_data_point(void** loop_ctx, void** data_ctx, + netsnmp_variable_list* idx_data, + netsnmp_iterator_info* data) + { + auto index = reinterpret_cast<size_t>(*loop_ctx); + auto& table = *reinterpret_cast<Table<ItemType>*>(data->myvoid); + + if (index < table._items.size()) + { + auto& item = table._items[index]; + + snmp_set_var_value(idx_data, item->name.c_str(), + item->name.length()); + + *data_ctx = item.get(); + *loop_ctx = reinterpret_cast<void*>(index + 1); + return idx_data; + } + + return nullptr; + } + + /** + * @brief handles snmp requests for the table data. + */ + static int snmp_handler(netsnmp_mib_handler* handler, + netsnmp_handler_registration* reginfo, + netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* requests) + { + switch (reqinfo->mode) + { + case MODE_GET: + for (auto request = requests; request; request = request->next) + { + auto entry = reinterpret_cast<ItemType*>( + netsnmp_extract_iterator_context(request)); + + if (!entry) + { + netsnmp_set_request_error(reqinfo, request, + SNMP_NOSUCHINSTANCE); + continue; + } + + entry->get_snmp_reply(reqinfo, request); + } + break; + } + + return SNMP_ERR_NOERROR; + } + + std::string _path; + interfaces_t _interfaces; + std::vector<sdbusplus::bus::match::match> _matches; + Items _items; +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/table/item.hpp b/agent/data/table/item.hpp new file mode 100644 index 0000000..7c4c0eb --- /dev/null +++ b/agent/data/table/item.hpp @@ -0,0 +1,157 @@ +/** + * @brief MIB tables item definition. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ +namespace table +{ + +/** + * @brief MIB Table row implementation. + */ +template <typename... T> struct Item +{ + /* + * The `std::variant` allows to keep duplicates of types, + * but `std::get<>()` and `std::holds_alternative<>()` is ill-formed + * in this case. + * + * So we can't use here: + * using variant_t = std::variant<T...>; + * and we should specify all possible types. + */ + using variant_t = std::variant<int64_t, std::string, bool, uint8_t, double>; + + using values_t = std::tuple<T...>; + using fields_map_t = std::map<std::string, variant_t>; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Item() = delete; + Item(const Item&) = delete; + Item& operator=(const Item&) = delete; + Item(Item&&) = default; + Item& operator=(Item&&) = default; + ~Item() = default; + + /** + * @brief Object constructor + * + * @param folder - Base folder for DBus object path + * @param name - DBus object path relative by folder + * @param args - Default values for each fields + */ + Item(const std::string& folder, const std::string& name, T&&... args) : + name(name), data(std::forward<T>(args)...), + changedMatch(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::propertiesChanged( + folder + "/" + name), + std::bind(&Item<T...>::onPropertiesChanged, this, + std::placeholders::_1)) + { + } + + /** + * @brief PropertiesChanged signal handler + */ + virtual void onPropertiesChanged(sdbusplus::message::message& m) + { + std::string iface; + fields_map_t data; + std::vector<std::string> v; + m.read(iface, data, v); + + setFields(data); + } + + /** + * @brief Store fields values recieved from DBus + */ + virtual void setFields(const fields_map_t& fields) = 0; + + /** + * @brief Called after object has been created. + */ + virtual void onCreate() + { + } + + /** + * @brief Called before object will be destroyed. + */ + virtual void onDestroy() + { + } + + /** + * @brief String fields vlaues helper + */ + template <size_t Index> + void setField(const fields_map_t& fieldsMap, const char* propertyName) + { + using FieldType = typename std::tuple_element<Index, values_t>::type; + if (fieldsMap.find(propertyName) != fieldsMap.end() && + std::holds_alternative<FieldType>(fieldsMap.at(propertyName))) + { + std::get<Index>(data) = + std::get<FieldType>(fieldsMap.at(propertyName)); + } + } + + /** + * @brief Fill snmp reply with fields values + */ + virtual void get_snmp_reply(netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* request) const = 0; + + std::string name; + values_t data; + + private: + sdbusplus::bus::match::match changedMatch; +}; + +/** + * @brief Used for std::lower_bound throw vector of smartpointers. + */ +template <typename ItemType> +inline bool operator<(const std::unique_ptr<ItemType>& o, const std::string& s) +{ + return o->name < s; +} + +} // namespace table +} // namespace data +} // namespace snmp +} // namespace phosphor |