#pragma once #include #include #include #include #include #include #include #include #include #include namespace sdbusplus { namespace message { /** @brief Read data from an sdbus message. * * (This is an empty no-op function that is useful in some cases for * variadic template reasons.) */ inline void read(sdbusplus::SdBusInterface* /*intf*/, sd_bus_message* /*m*/) {} /** @brief Read data from an sdbus message. * * @param[in] m - The message to read from. * @tparam Args - C++ types of arguments to read from message. * @param[out] args - References to place contents read from message. * * This function will, at compile-time, deduce the DBus types of the passed * C++ values and call the sd_bus_message_read functions with the * appropriate type parameters. It may also do conversions, where needed, * to convert C++ types into C representations (eg. string, vector). */ template void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args); namespace details { /** @brief Utility to identify C++ types that may not be grouped into a * single sd_bus_message_read call and instead need special * handling. * * @tparam T - Type for identification. * * User-defined types are expected to inherit from std::false_type. * Enums are converted from strings, so must be done one at a time. * */ template struct can_read_multiple : std::conditional_t, std::false_type, std::true_type> {}; // unix_fd's int needs to be wrapped template <> struct can_read_multiple : std::false_type {}; // std::string needs a char* conversion. template <> struct can_read_multiple : std::false_type {}; // object_path needs a char* conversion. template <> struct can_read_multiple : std::false_type {}; // signature needs a char* conversion. template <> struct can_read_multiple : std::false_type {}; // bool needs to be resized to int, per sdbus documentation. template <> struct can_read_multiple : std::false_type {}; // std::vector/map/unordered_vector/set need loops template struct can_read_multiple< T, typename std::enable_if_t || utility::has_emplace_back_method_v>> : std::false_type {}; // std::pair needs to be broken down into components. template struct can_read_multiple> : std::false_type {}; // std::tuple needs to be broken down into components. template struct can_read_multiple> : std::false_type {}; // variant needs to be broken down into components. template struct can_read_multiple> : std::false_type {}; template inline constexpr bool can_read_multiple_v = can_read_multiple::value; /** @brief Utility to read a single C++ element from a sd_bus_message. * * User-defined types are expected to specialize this template in order to * get their functionality. * * @tparam S - Type of element to read. */ template struct read_single { // Downcast template using Td = types::details::type_id_downcast_t; /** @brief Do the operation to read element. * * @tparam T - Type of element to read. * * Template parameters T (function) and S (class) are different * to allow the function to be utilized for many variants of S: * S&, S&&, const S&, volatile S&, etc. The type_id_downcast is used * to ensure T and S are equivalent. For 'char*', this also allows * use for 'char[N]' types. * * @param[in] m - sd_bus_message to read from. * @param[out] t - The reference to read item into. */ template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) requires(!std::is_enum_v>) { // For this default implementation, we need to ensure that only // basic types are used. static_assert(std::is_fundamental_v> || std::is_convertible_v, const char*> || std::is_convertible_v, details::unix_fd_type>, "Non-basic types are not allowed."); constexpr auto dbusType = std::get<0>(types::type_id()); int r = intf->sd_bus_message_read_basic(m, dbusType, &t); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_read_basic fundamental"); } } template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) requires(std::is_enum_v>) { std::string value{}; sdbusplus::message::read(intf, m, value); auto r = sdbusplus::message::convert_from_string>(value); if (!r) { throw sdbusplus::exception::InvalidEnumString(); } t = *r; } }; template using read_single_t = read_single>; /** @brief Specialization of read_single for various string class types. * * Supports std::strings, details::string_wrapper and * details::string_path_wrapper. */ template requires(std::is_same_v || std::is_same_v || std::is_same_v) struct read_single { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = std::get<0>(types::type_id()); const char* str = nullptr; int r = intf->sd_bus_message_read_basic(m, dbusType, &str); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_read_basic string"); } t = S(str); } }; /** @brief Specialization of read_single for bools. */ template requires(std::is_same_v) struct read_single { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = std::get<0>(types::type_id()); int i = 0; int r = intf->sd_bus_message_read_basic(m, dbusType, &i); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_read_basic bool"); } t = (i != 0); } }; /** @brief Specialization of read_single for std::vectors. */ template requires(utility::has_emplace_back_method_v) struct read_single { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = utility::tuple_to_array(types::type_id()); int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data() + 1); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_enter_container emplace_back_container"); } while (!(r = intf->sd_bus_message_at_end(m, false))) { types::details::type_id_downcast_t s; sdbusplus::message::read(intf, m, s); t.emplace_back(std::move(s)); } if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_at_end emplace_back_container"); } r = intf->sd_bus_message_exit_container(m); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_exit_container emplace_back_container"); } } }; /** @brief Specialization of read_single for std::map. */ template requires(utility::has_emplace_method_v) struct read_single { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = utility::tuple_to_array(types::type_id()); int r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, dbusType.data() + 1); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_enter_container emplace_container"); } while (!(r = intf->sd_bus_message_at_end(m, false))) { types::details::type_id_downcast_t s; sdbusplus::message::read(intf, m, s); t.emplace(std::move(s)); } if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_at_end emplace_container"); } r = intf->sd_bus_message_exit_container(m); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_exit_container emplace_container"); } } }; /** @brief Specialization of read_single for std::tuples and std::pairs. */ template requires requires(S& s) { std::get<0>(s); } struct read_single { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = utility::tuple_to_array(types::type_id_tuple()); // Tuples use TYPE_STRUCT, pair uses TYPE_DICT_ENTRY. // Use the presence of `t.first` to determine if it is a pair. constexpr auto tupleType = [&]() { if constexpr (requires { t.first; }) { return SD_BUS_TYPE_DICT_ENTRY; } return SD_BUS_TYPE_STRUCT; }(); int r = intf->sd_bus_message_enter_container(m, tupleType, dbusType.data()); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_enter_container tuple"); } std::apply( [&](auto&... args) { sdbusplus::message::read(intf, m, args...); }, t); r = intf->sd_bus_message_exit_container(m); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_exit_container tuple"); } } }; /** @brief Specialization of read_single for std::variant. */ template struct read_single> { // Downcast template using Td = types::details::type_id_downcast_t; template static void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { constexpr auto dbusType = utility::tuple_to_array(types::type_id()); int r = intf->sd_bus_message_verify_type(m, SD_BUS_TYPE_VARIANT, dbusType.data()); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_verify_type variant"); } if (!r) { if constexpr (sizeof...(Args1) == 0) { r = intf->sd_bus_message_skip(m, "v"); if (r < 0) { throw exception::SdBusError(-r, "sd_bus_message_skip variant"); } t = std::remove_reference_t{}; } else { read(intf, m, t); } return; } r = intf->sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, dbusType.data()); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_enter_container variant"); } // If this type is an enum or string, we don't know which is the // valid parsing. Delegate to 'convert_from_string' so we do the // correct conversion. if constexpr (std::is_enum_v> || std::is_same_v>) { std::string str{}; sdbusplus::message::read(intf, m, str); auto ret = sdbusplus::message::convert_from_string>( str); if (!ret) { throw sdbusplus::exception::InvalidEnumString(); } t = std::move(*ret); } else // otherwise, read it out directly. { std::remove_reference_t t1; sdbusplus::message::read(intf, m, t1); t = std::move(t1); } r = intf->sd_bus_message_exit_container(m); if (r < 0) { throw exception::SdBusError( -r, "sd_bus_message_exit_container variant"); } } template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { read(intf, m, t); } }; /** @brief Specialization of read_single for std::monostate. */ template requires(std::is_same_v) struct read_single { template static void op(sdbusplus::SdBusInterface*, sd_bus_message*, T&&) {} }; template void tuple_item_read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, T&& t) { sdbusplus::message::read(intf, m, t); } template struct ReadHelper { template static void op(sdbusplus::SdBusInterface* intf, sd_bus_message* m, std::tuple field_tuple) { auto& field = std::get(field_tuple); if constexpr (Index > 1) { ReadHelper::op(intf, m, std::move(field_tuple)); } tuple_item_read(intf, m, field); } }; /** @brief Read a tuple from the sd_bus_message. * * @tparam Tuple - The tuple type to read. * @param[out] t - The tuple to read into. * * A tuple of 2 or more entries can be read as a set with * sd_bus_message_read. * * A tuple of 1 entry can be read with sd_bus_message_read_basic. * * A tuple of 0 entries is a no-op. * */ template void read_tuple(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t) { if constexpr (std::tuple_size_v >= 2) { ReadHelper>::op(intf, m, std::move(t)); } else if constexpr (std::tuple_size_v == 1) { using itemType = decltype(std::get<0>(t)); read_single_t::op(intf, m, std::forward(std::get<0>(t))); } } /** @brief Group a sequence of C++ types for reading from an sd_bus_message. * @tparam Tuple - A tuple of previously analyzed types. * @tparam Arg - The argument to analyze for grouping. * * Specialization for when can_read_multiple_v is true. */ template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg); /** @brief Group a sequence of C++ types for reading from an sd_bus_message. * @tparam Tuple - A tuple of previously analyzed types. * @tparam Arg - The argument to analyze for grouping. * * Specialization for when can_read_multiple_v is false. */ template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg); /** @brief Group a sequence of C++ types for reading from an sd_bus_message. * @tparam Tuple - A tuple of previously analyzed types. * @tparam Arg - The argument to analyze for grouping. * @tparam Rest - The remaining arguments left to analyze. * * Specialization for when can_read_multiple_v is true. */ template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest); /** @brief Group a sequence of C++ types for reading from an sd_bus_message. * @tparam Tuple - A tuple of previously analyzed types. * @tparam Arg - The argument to analyze for grouping. * @tparam Rest - The remaining arguments left to analyze. * * Specialization for when can_read_multiple_v is false. */ template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest); template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg) { // Last element of a sequence and can_read_multiple, so add it to // the tuple and call read_tuple. read_tuple(intf, m, std::tuple_cat(std::forward(t), std::forward_as_tuple(std::forward(arg)))); } template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg) { // Last element of a sequence but !can_read_multiple, so call // read_tuple on the previous elements and separately this single // element. read_tuple(intf, m, std::forward(t)); read_tuple(intf, m, std::forward_as_tuple(std::forward(arg))); } template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest) { // Not the last element of a sequence and can_read_multiple, so add it // to the tuple and keep grouping. read_grouping(intf, m, std::tuple_cat(std::forward(t), std::forward_as_tuple(std::forward(arg))), std::forward(rest)...); } template std::enable_if_t>> read_grouping(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Tuple&& t, Arg&& arg, Rest&&... rest) { // Not the last element of a sequence but !can_read_multiple, so call // read_tuple on the previous elements and separately this single // element and then group the remaining elements. read_tuple(intf, m, std::forward(t)); read_tuple(intf, m, std::forward_as_tuple(std::forward(arg))); read_grouping(intf, m, std::make_tuple(), std::forward(rest)...); } } // namespace details template void read(sdbusplus::SdBusInterface* intf, sd_bus_message* m, Args&&... args) { details::read_grouping(intf, m, std::make_tuple(), std::forward(args)...); } } // namespace message } // namespace sdbusplus