blob: ec07e4e2b3fad0cd52a2354df02f4a826713b6e5 [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// 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
#ifndef BOOST_COROUTINES_NO_DEPRECATION_WARNING
// users should define this if they directly include boost/asio/spawn.hpp,
// but by defining it here, warnings won't cause problems with a compile
#define BOOST_COROUTINES_NO_DEPRECATION_WARNING
#endif
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/callable_traits.hpp>
#include <sdbusplus/asio/detail/async_send_handler.hpp>
#include <sdbusplus/message.hpp>
#include <sdbusplus/utility/read_into_tuple.hpp>
#include <sdbusplus/utility/type_traits.hpp>
#include <chrono>
#include <string>
#include <tuple>
namespace sdbusplus
{
namespace asio
{
/// Root D-Bus IO object
/**
* A connection to a bus, through which messages may be sent or received.
*/
class connection : public sdbusplus::bus::bus
{
public:
// default to system bus
connection(boost::asio::io_context& io) :
sdbusplus::bus::bus(sdbusplus::bus::new_default()), io_(io), socket(io_)
{
socket.assign(get_fd());
read_wait();
}
connection(boost::asio::io_context& io, sd_bus* bus) :
sdbusplus::bus::bus(bus), io_(io), socket(io_)
{
socket.assign(get_fd());
read_wait();
}
~connection()
{
// The FD will be closed by the socket object, so assign null to the
// sd_bus object to avoid a double close() Ignore return codes here,
// because there's nothing we can do about errors
socket.release();
}
/** @brief Perform an asynchronous send of a message, executing the handler
* upon return and return
*
* @param[in] m - A message ready to send
* @param[in] handler - handler to execute upon completion; this may be an
* asio::yield_context to execute asynchronously as a
* coroutine
*
* @return If a yield context is passed as the handler, the return type is
* a message. If a function object is passed in as the handler,
* the return type is the result of the handler registration,
* while the resulting message will get passed into the handler.
*/
template <typename MessageHandler>
inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
void(boost::system::error_code,
message::message&))
async_send(message::message& m, MessageHandler&& handler,
uint64_t timeout = 0)
{
boost::asio::async_completion<
MessageHandler, void(boost::system::error_code, message::message)>
init(handler);
detail::async_send_handler<typename boost::asio::async_result<
MessageHandler, void(boost::system::error_code,
message::message)>::completion_handler_type>(
std::move(init.completion_handler))(get(), m, timeout);
return init.result.get();
}
/** @brief Perform an asynchronous method call, with input parameter packing
* and return value unpacking.
*
* @param[in] handler - A function object that is to be called as a
* continuation for the async dbus method call. The
* arguments to parse on the return are deduced from
* the handler's signature and then passed in along
* with an error code and optional message::message
* @param[in] service - The service to call.
* @param[in] objpath - The object's path for the call.
* @param[in] interf - The object's interface to call.
* @param[in] method - The object's method to call.
* @param[in] timeout - The timeout for the method call in usec (0 results
* in using the default value).
* @param[in] a... - Optional parameters for the method call.
*
* @return immediate return of the internal handler registration. The
* result of the actual asynchronous call will get unpacked from
* the message and passed into the handler when the call is
* complete.
*/
template <typename MessageHandler, typename... InputArgs>
void async_method_call_timed(MessageHandler&& handler,
const std::string& service,
const std::string& objpath,
const std::string& interf,
const std::string& method, uint64_t timeout,
const InputArgs&... a)
{
using FunctionTuple = boost::callable_traits::args_t<MessageHandler>;
using FunctionTupleType =
typename utility::decay_tuple<FunctionTuple>::type;
constexpr bool returnWithMsg = []() {
if constexpr (std::tuple_size_v<FunctionTupleType> > 1)
{
return std::is_same_v<
std::tuple_element_t<1, FunctionTupleType>,
sdbusplus::message::message>;
}
return false;
}();
using UnpackType =
typename utility::strip_first_n_args<returnWithMsg ? 2 : 1,
FunctionTupleType>::type;
auto applyHandler = [handler = std::forward<MessageHandler>(handler)](
boost::system::error_code ec,
message::message& r) mutable {
UnpackType responseData;
if (!ec)
{
try
{
utility::read_into_tuple(responseData, r);
}
catch (const std::exception& e)
{
// Set error code if not already set
ec = boost::system::errc::make_error_code(
boost::system::errc::invalid_argument);
}
}
// Note. Callback is called whether or not the unpack was
// successful to allow the user to implement their own handling
if constexpr (returnWithMsg)
{
auto response = std::tuple_cat(std::make_tuple(ec),
std::forward_as_tuple(r),
std::move(responseData));
std::apply(handler, response);
}
else
{
auto response = std::tuple_cat(std::make_tuple(ec),
std::move(responseData));
std::apply(handler, response);
}
};
message::message m;
boost::system::error_code ec;
try
{
m = new_method_call(service.c_str(), objpath.c_str(),
interf.c_str(), method.c_str());
m.append(a...);
}
catch (const exception::SdBusError& e)
{
ec = boost::system::errc::make_error_code(
static_cast<boost::system::errc::errc_t>(e.get_errno()));
applyHandler(ec, m);
return;
}
async_send(m, std::forward<decltype(applyHandler)>(applyHandler),
timeout);
}
/** @brief Perform an asynchronous method call, with input parameter packing
* and return value unpacking. Uses the default timeout value.
*
* @param[in] handler - A function object that is to be called as a
* continuation for the async dbus method call. The
* arguments to parse on the return are deduced from
* the handler's signature and then passed in along
* with an error code and optional message::message
* @param[in] service - The service to call.
* @param[in] objpath - The object's path for the call.
* @param[in] interf - The object's interface to call.
* @param[in] method - The object's method to call.
* @param[in] a... - Optional parameters for the method call.
*
* @return immediate return of the internal handler registration. The
* result of the actual asynchronous call will get unpacked from
* the message and passed into the handler when the call is
* complete.
*/
template <typename MessageHandler, typename... InputArgs>
void async_method_call(MessageHandler&& handler, const std::string& service,
const std::string& objpath,
const std::string& interf, const std::string& method,
const InputArgs&... a)
{
async_method_call_timed(std::forward<MessageHandler>(handler), service,
objpath, interf, method, 0, a...);
}
/** @brief Perform a yielding asynchronous method call, with input
* parameter packing and return value unpacking
*
* @param[in] yield - A yield context to async block upon.
* @param[in] ec - an error code that will be set for any errors
* @param[in] service - The service to call.
* @param[in] objpath - The object's path for the call.
* @param[in] interf - The object's interface to call.
* @param[in] method - The object's method to call.
* @param[in] a... - Optional parameters for the method call.
*
* @return Unpacked value of RetType
*/
template <typename... RetTypes, typename... InputArgs>
auto yield_method_call(boost::asio::yield_context yield,
boost::system::error_code& ec,
const std::string& service,
const std::string& objpath,
const std::string& interf, const std::string& method,
const InputArgs&... a)
{
message::message m;
try
{
m = new_method_call(service.c_str(), objpath.c_str(),
interf.c_str(), method.c_str());
m.append(a...);
}
catch (const exception::SdBusError& e)
{
ec = boost::system::errc::make_error_code(
static_cast<boost::system::errc::errc_t>(e.get_errno()));
}
message::message r;
if (!ec)
{
r = async_send(m, yield[ec]);
}
if constexpr (sizeof...(RetTypes) == 0)
{
// void return
return;
}
else if constexpr (sizeof...(RetTypes) == 1)
{
if constexpr (std::is_same<utility::first_type<RetTypes...>,
void>::value)
{
return;
}
else
{
// single item return
utility::first_type<RetTypes...> responseData{};
// before attempting to read, check ec and bail on error
if (ec)
{
return responseData;
}
try
{
r.read(responseData);
}
catch (const std::exception& e)
{
ec = boost::system::errc::make_error_code(
boost::system::errc::invalid_argument);
// responseData will be default-constructed...
}
return responseData;
}
}
else
{
// tuple of things to return
std::tuple<RetTypes...> responseData{};
// before attempting to read, check ec and bail on error
if (ec)
{
return responseData;
}
try
{
r.read(responseData);
}
catch (const std::exception& e)
{
ec = boost::system::errc::make_error_code(
boost::system::errc::invalid_argument);
// responseData will be default-constructed...
}
return responseData;
}
}
boost::asio::io_context& get_io_context()
{
return io_;
}
private:
boost::asio::io_context& io_;
boost::asio::posix::stream_descriptor socket;
void read_wait()
{
socket.async_read_some(
boost::asio::null_buffers(),
[&](const boost::system::error_code& /*ec*/, std::size_t) {
if (process_discard())
{
read_immediate();
}
else
{
read_wait();
}
});
}
void read_immediate()
{
boost::asio::post(io_, [&] {
if (process_discard())
{
read_immediate();
}
else
{
read_wait();
}
});
}
};
} // namespace asio
} // namespace sdbusplus