diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 73a590d..de0c500 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -8,13 +8,18 @@ on: jobs: build: - name: ${{matrix.compiler.compiler}} ${{matrix.build_type}} + name: ${{matrix.compiler.name}} ${{matrix.asio.name}} ${{matrix.build_type}} runs-on: ubuntu-latest +# run in docker until jammy is ubuntu-latest + container: ubuntu:jammy strategy: matrix: compiler: - - { compiler: GCC, CC: gcc, CXX: g++ } - - { compiler: Clang, CC: clang, CXX: clang++ } + - { name: GCC, CC: gcc, CXX: g++ } + - { name: Clang, CC: clang, CXX: clang++ } + asio: + - { name: asio-boost, pkg: libboost-dev, standalone: OFF } + - { name: asio-standalone, pkg: libasio-dev, standalone: ON } build_type: [ Debug, Release ] steps: - uses: actions/checkout@v2 @@ -23,21 +28,25 @@ jobs: env: DEBIAN_FRONTEND: noninteractive run: | - sudo apt update && sudo apt install -yq libboost-all-dev ninja-build + apt update && apt install --no-install-recommends -yq ${{matrix.asio.pkg}} cmake g++ clang ninja-build +# sudo apt update && sudo apt install --no-install-recommends -yq ${{matrix.asio.pkg}} ninja-build - name: Configure CMake env: CC: ${{matrix.compiler.CC}} CXX: ${{matrix.compiler.CXX}} # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} + run: | + cmake -G Ninja -B ${{github.workspace}}/build \ + -DDBUS_ASIO_STANDALONE=${{matrix.asio.standalone}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} - name: Test - working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{matrix.build_type}} + run: | + cd ${{github.workspace}}/build; ctest -C ${{matrix.build_type}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e236ce7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +src/dbus_asio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fd4c9a5..a7d76c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,10 @@ set(CMAKE_CXX_VISIBILITY_PRESET protected) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold") set(HEADER_FILES + src/dbus_asio.h src/dbus_auth.h + src/dbus_connection.h + src/dbus_error.h src/dbus.h src/dbus_introspectable.h src/dbus_log.h @@ -15,17 +18,17 @@ set(HEADER_FILES src/dbus_messageprotocol.h src/dbus_messageostream.h src/dbus_messageistream.h - src/dbus_native.h + src/dbus_names.h src/dbus_octetbuffer.h src/dbus_platform.h src/dbus_transport.h + src/dbus_type.h + src/dbus_type_any.h src/dbus_type_array.h - src/dbus_type_base.h src/dbus_type_boolean.h src/dbus_type_byte.h src/dbus_type_dictentry.h src/dbus_type_double.h - src/dbus_type.h src/dbus_type_int16.h src/dbus_type_int32.h src/dbus_type_int64.h @@ -36,9 +39,9 @@ set(HEADER_FILES src/dbus_type_uint16.h src/dbus_type_uint32.h src/dbus_type_uint64.h + src/dbus_type_unixfd.h src/dbus_type_variant.h src/dbus_utils.h - src/dbus_validation.h ) add_library(dbus-asio SHARED @@ -47,22 +50,17 @@ add_library(dbus-asio SHARED src/dbus_log.cpp src/dbus_matchrule.cpp src/dbus_message.cpp - src/dbus_message_error.cpp src/dbus_messageistream.cpp - src/dbus_message_methodcall.cpp - src/dbus_message_methodreturn.cpp src/dbus_messageprotocol.cpp - src/dbus_message_signal.cpp - src/dbus_native.cpp - src/dbus_native_messages.cpp + src/dbus_names.cpp src/dbus_octetbuffer.cpp src/dbus_platform.cpp src/dbus_transport.cpp + src/dbus_type.cpp + src/dbus_type_any.cpp src/dbus_type_array.cpp - src/dbus_type_base.cpp src/dbus_type_boolean.cpp src/dbus_type_byte.cpp - src/dbus_type.cpp src/dbus_type_dictentry.cpp src/dbus_type_double.cpp src/dbus_type_int16.cpp @@ -75,37 +73,41 @@ add_library(dbus-asio SHARED src/dbus_type_uint16.cpp src/dbus_type_uint32.cpp src/dbus_type_uint64.cpp + src/dbus_type_unixfd.cpp src/dbus_type_variant.cpp src/dbus_utils.cpp - src/dbus_validation.cpp ) -# DBUS DEPENDENCIES -find_package(Boost COMPONENTS chrono system thread REQUIRED) -include_directories(${Boost_INCLUDE_DIR}) -set (PRIVATE_LIBS ${Boost_LIBRARIES}) -link_directories(${Boost_LIBRARY_DIRS}) - -# PTHREAD DEPENDENCIES -find_package (Threads REQUIRED) - -target_link_libraries (dbus-asio ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +# ASIO +option(DBUS_ASIO_STANDALONE "Use ASIO standalone library (non-boost)" OFF) +if(DBUS_ASIO_STANDALONE) + add_compile_definitions(ASIO_STANDALONE) + set(DBUS_ASIO_INCLUDE "asio.hpp") + set(DBUS_ASIO_NAMESPACE "::asio") + set(DBUS_ERROR_CODE_NAMESPACE "::std") + set(DBUS_ERROR_CODE_ERRC "::std::errc") + set(DBUS_MAKE_ERROR_CODE "::std::make_error_code") +else() + set(DBUS_ASIO_INCLUDE "boost/asio.hpp") + set(DBUS_ASIO_NAMESPACE "::boost::asio") + set(DBUS_ERROR_CODE_NAMESPACE "::boost::system") + set(DBUS_ERROR_CODE_ERRC "::boost::system::errc::errc_t") + set(DBUS_MAKE_ERROR_CODE "::boost::asio::error::make_error_code") + + find_package (Threads REQUIRED) + target_link_libraries(dbus-asio Threads::Threads) +endif() +set(DBUS_ASIO_H ${CMAKE_CURRENT_SOURCE_DIR}/src/dbus_asio.h) +configure_file(${DBUS_ASIO_H}.in ${DBUS_ASIO_H}) +unset(DBUS_ASIO_H) set_target_properties(dbus-asio PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 PUBLIC_HEADER "${HEADER_FILES}") -foreach(LIB ${PRIVATE_LIBS}) - get_filename_component(BARE_LIB ${LIB} NAME) - string(REGEX REPLACE ".so$" "" BARE_LIB "${BARE_LIB}") - string(REGEX REPLACE "^lib" "-l" BARE_LIB "${BARE_LIB}") - set(PLATFORM_LIBS "${PLATFORM_LIBS} ${BARE_LIB}") -endforeach() - -set(PLATFORM_LIBS "${PLATFORM_LIBS} -ldbus-asio") +set(PLATFORM_LIBS "-ldbus-asio") -message(${PLATFORM_LIBS}) configure_file(dbus-asio.pc.in dbus-asio.pc @ONLY) target_include_directories(dbus-asio PUBLIC src) diff --git a/dbus-asio.pc.in b/dbus-asio.pc.in index 4b71eb2..da95323 100644 --- a/dbus-asio.pc.in +++ b/dbus-asio.pc.in @@ -4,9 +4,8 @@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/libdbus-asio Name: @PROJECT_NAME@ -Description: "D-Bus ASIO Library" +Description: asynchronous C++ DBus library Version: @PROJECT_VERSION@ -Requires: Libs: -L${libdir} @PLATFORM_LIBS@ Cflags: -I${includedir} diff --git a/example/dbus-test.cpp b/example/dbus-test.cpp index 6d1ba83..db4c136 100644 --- a/example/dbus-test.cpp +++ b/example/dbus-test.cpp @@ -26,236 +26,277 @@ About unix_fd */ #include "dbus.h" +#include -void test1() +void handleNameAcquired(DBus::Connection::Ptr dbus) { - DBus::Log::setLevel(DBus::Log::WARNING); - - DBus::Log::write(DBus::Log::INFO, "System bus: %s\n", - DBus::Platform::getSystemBus().c_str()); - DBus::Log::write(DBus::Log::INFO, "Session bus: %s\n", - DBus::Platform::getSessionBus().c_str()); - // DBus::Native native(DBus::Platform::getSystemBus()); - DBus::Native native(DBus::Platform::getSessionBus()); - sleep(1); - - native.BeginAuth( - DBus::AuthenticationProtocol::AUTH_BASIC); // AUTH_BASIC or - // AUTH_NEGOTIATE_UNIX_FD - - // org.bluez.obex - use / and objectmanager - -#if 1 - DBus::Introspectable::Introspection introspection; - DBus::Introspectable::Interface iface("biz.brightsign.TestInterface"); - iface.addMethod(DBus::Introspectable::Method("Ping", "", "ss")); - iface.addMethod(DBus::Introspectable::Method("Echo2", "ss", "s")); - iface.addProperty(DBus::Introspectable::Property("p1", "s")); - iface.addProperty(DBus::Introspectable::Property("p2", "s")); - iface.addSignal(DBus::Introspectable::Signal("BroadcastStuff", "s")); - - introspection.addInterface(iface); - - std::string xml_introspection(introspection.serialize()); -#endif - - // Also, support methods etc - // native.SendAuthListMethods(); - - // Hello() is necessary before any communications take place - printf("Send hello message...\n"); - - DBus::Log::setLevel(DBus::Log::TRACE); - native.registerSignalHandler( + dbus->receiveSignal( "org.freedesktop.DBus.NameAcquired", - [&](const DBus::Message::Signal& signal) { + [dbus](const DBus::Message::Signal& signal) { + if (!signal) return; // Knowing the type is 's' we can be safe in assuming asString will work - std::string signalName = DBus::Type::asString(signal.getParameter(0)); - printf("RCV signalName : NameAcquired : %s\n", signalName.c_str()); + const auto name = DBus::Type::asString(signal.getParameter(0)); + std::cout << "RCV signal : NameAcquired : " << name << std::endl; + handleNameAcquired(dbus); }); +} - native.registerSignalHandler( +void handleNameOwnerChanged(DBus::Connection::Ptr dbus) +{ + dbus->receiveSignal( "org.freedesktop.DBus.NameOwnerChanged", - [&](const DBus::Message::Signal& signal) { - std::string signalName = DBus::Type::asString(signal.getParameter(0)); - printf("RCV signalName : NameOwnerChanged : %s\n", signalName.c_str()); + [dbus](const DBus::Message::Signal& signal) { + if (!signal) return; + const auto name = DBus::Type::asString(signal.getParameter(0)); + std::cout << "RCV signal : NameOwnerChanged : " << name << std::endl; + handleNameOwnerChanged(dbus); }); +} - native.callHello( - [](const DBus::Message::MethodReturn& msg) { - std::string signalName = DBus::Type::asString(msg.getParameter(0)); - printf("REPLY FROM HELLO : This is our unique name : %s\n", - signalName.c_str()); - }, - [](const DBus::Message::Error& msg) { - printf("ERROR FROM HELLO : %s\n", msg.getMessage().c_str()); - }); - // DBus::Log::setLevel(DBus::Log::WARNING); - // TODO: This should get removed, but _something_ i not buffering the method - // calls below - // TODO: Am I deadlocking? - // sleep(3); - -#if 0 - DBus::Message::MethodCallIdentifier test_ping("/org/example/TestObject", "org.example.TestInterface", "Ping"); - native.sendMethodCall(test_ping, - [] (const DBus::Message::MethodReturn &msg) { - printf("REPLY FROM ping : %s\n", DBus::Type::asString(msg.m_Body).c_str()); - }); -#endif - - native.registerMethodCallHandler( +void servePropertiesGet(DBus::Connection::Ptr dbus) +{ + dbus->receiveMethodCall( "org.freedesktop.DBus.Properties.Get", - [&](const DBus::Message::MethodCall& method) { - DBus::Message::MethodReturn result(method.getSerial()); - // TODO: return Serial should be in ctor for error and return - and - // FIRST PARAMETER - std::string interface = DBus::Type::asString(method.getParameter(0)); - std::string propertyName = DBus::Type::asString(method.getParameter(1)); - - // result.m_Parameters.add(DBus::Type::String("The result of " + - // interface + " of " + propertyName + " is 42. Always 42!")); - result.addParameter(DBus::Type::String("The result of " + interface + " of " + propertyName + " is 42. Always 42!")); - - if (propertyName == "p1") { - native.sendMethodReturn(method.getHeaderSender(), result); + [dbus](const DBus::Message::MethodCall& call) { + if (!call) return; + const auto interface = call.getParameter(0).asString(); + const auto property = call.getParameter(1).asString(); + + auto errorHandler = [](const DBus::Error& error){ + if (error) + std::cerr << "error while sending Get reply:" << error.message << std::endl; + }; + + if (property == "Answer") { + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::Uint16(42)); + dbus->sendMethodReturn(reply, errorHandler); + } else if (property == "Poetry") { + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String("The dead swans lay in the stagnant pool.")); + dbus->sendMethodReturn(reply, errorHandler); } else { - DBus::Message::Error err(method.getSerial(), + DBus::Message::Error err(call.getSender(), call.getSerial(), "biz.brightsign.Error.InvalidParameters", - "Parameter is not p1"); - native.sendError(method.getHeaderSender(), err); + "Requested property is unknown: " + property); + dbus->sendError(err, errorHandler); } + servePropertiesGet(dbus); }); +} -#if 1 - native.registerMethodCallHandler( - "org.freedesktop.DBus.Introspectable.Introspect", - [&](const DBus::Message::MethodCall& method) { - DBus::Message::MethodReturn result(method.getSerial()); - result.addParameter(DBus::Type::String(xml_introspection)); - native.sendMethodReturn(method.getHeaderSender(), result); - }); -#endif - -// TODO: finish org.freedesktop.DBus.Properties.GetAll to return the correct -// data as name:value pair looks wrong, but it doesn't crash. Whereas the a(sv) -// is right, but causes the sender to terminate the connection -#if 1 - // It is recommend you implement this method - even if you only return an - // empty string. Some apps (like d-feet) will wait for a reply, making them - // appear slow. - native.registerMethodCallHandler( +void servePropertiesGetAll(DBus::Connection::Ptr dbus) +{ + // TODO: finish org.freedesktop.DBus.Properties.GetAll to return the correct + // data as name:value pair looks wrong, but it doesn't crash. Whereas the a(sv) + // is right, but causes the sender to terminate the connection + dbus->receiveMethodCall( "org.freedesktop.DBus.Properties.GetAll", - [&](const DBus::Message::MethodCall& method) { + [dbus](const DBus::Message::MethodCall& method) { + if (!method) return; printf("GetAll\nCALLBACK METHOD : serial %.4x \n", method.getSerial()); printf("CALLBACK METHOD : sender %s \n", - method.getHeaderSender().c_str()); + method.getSender().c_str()); printf("CALLBACK METHOD : destination %s \n", - method.getHeaderDestination().c_str()); + method.getDestination().c_str()); printf("CALLBACK METHOD : serial %d \n", method.getSerial()); - DBus::Type::Array propertyList; - - DBus::Type::Struct s1; - s1.add(DBus::Type::String("p1")); - s1.add(DBus::Type::Variant(DBus::Type::String("s"))); - - propertyList.add(s1); - // native.sendMethodReturn(method.getSerial(), header.destination, - // header.sender, propertyList); - - // std::string result("p1:123,p2:44"); - std::string result(""); - DBus::Type::Generic body = DBus::Type::String(result); + DBus::Type::Struct p1; + p1.add(DBus::Type::String("Answer")); + p1.add(DBus::Type::Variant(DBus::Type::Uint16(42))); + + DBus::Type::Struct p2; + p2.add(DBus::Type::String("Poetry")); + p2.add(DBus::Type::Variant(DBus::Type::String("The dead swans lay in the stagnant pool."))); + + DBus::Type::Array properties; + properties.add(p1); + + DBus::Message::MethodReturn reply(method.getSender(), method.getSerial()); + reply.addParameter(properties); + dbus->sendMethodReturn(reply, + [](const DBus::Error& error){ + if (error) + std::cerr << "error while sending GetAll reply:" << error.message << std::endl; + }); + servePropertiesGetAll(dbus); + }); +} - // REM: The destination for the incoming message is us, the sender, in - // this context. +void serveIntrospect(DBus::Connection::Ptr dbus) +{ + static std::string xml; + if (xml.empty()) + { + DBus::Introspectable::Interface iface("biz.brightsign.TestInterface"); + iface.addMethod(DBus::Introspectable::Method("Ping", "", "ss")); + iface.addMethod(DBus::Introspectable::Method("Echo2", "ss", "s")); + iface.addProperty(DBus::Introspectable::Property("Answer", "q")); + iface.addProperty(DBus::Introspectable::Property("Poetry", "s")); + iface.addSignal(DBus::Introspectable::Signal("BroadcastStuff", "s")); + + DBus::Introspectable::Introspection introspection; + introspection.addInterface(iface); + xml = introspection.serialize(); + } + + dbus->receiveMethodCall( + "org.freedesktop.DBus.Introspectable.Introspect", + [dbus](const DBus::Message::MethodCall& call) { + if (!call) return; + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String(xml)); + dbus->sendMethodReturn(reply, + [](const DBus::Error& error){ + if (error) + std::cerr << "error serving introspection: " + << error.message << std::endl; + }); + serveIntrospect(dbus); + }); +} - // TODO - DBus::Message::MethodReturn result2(method.getSerial()); - result2.addParameter(body); - native.sendMethodReturn(method.getHeaderSender(), result2); - // native.sendMethodReturn(method.getSerial(), - //header.destination, header.sender, body); +void servePing(DBus::Connection::Ptr dbus) +{ + dbus->receiveMethodCall( + "biz.brightsign.TestInterface.Ping", + [dbus](const DBus::Message::MethodCall& call) { + if (!call) return; + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String("Ping??")); + reply.addParameter(DBus::Type::String("Pong!!")); + dbus->sendMethodReturn(reply, + [](const DBus::Error& error) { + if (error) + std::cerr << "error while sending Ping reply:" << error.message << std::endl; + }); + servePing(dbus); }); -#endif +} -#if 1 - native.registerMethodCallHandler( +void serveEcho2(DBus::Connection::Ptr dbus) +{ + dbus->receiveMethodCall( "biz.brightsign.TestInterface.Echo2", - [&](const DBus::Message::MethodCall& method) { - if (!method.isReplyExpected()) { - // NOP - } else if (method.getParameterCount() != 2) { - DBus::Message::Error err(method.getSerial(), - "biz.brightsign.Error.InvalidParameters", - "This needs 2 params."); - native.sendError(method.getHeaderSender(), err); - - } else { - std::string input1(DBus::Type::asString(method.getParameter(0))); - std::string input2(DBus::Type::asString(method.getParameter(1))); - DBus::Type::Generic body = DBus::Type::String("Echo of : " + input1 + " and " + input2); - // TODO - DBus::Message::MethodReturn result(method.getSerial()); - result.addParameter(body); - native.sendMethodReturn(method.getHeaderSender(), result); + [dbus](const DBus::Message::MethodCall& call) { + if (!call) return; + if (call.isReplyExpected()) { + auto errorHandler = [](const DBus::Error& error) { + if (error) + std::cerr << "error while sending Echo2 reply: " << error.message << std::endl; + }; + if (call.getParameterCount() != 2) { + DBus::Message::Error err(call.getSender(), call.getSerial(), + "biz.brightsign.Error.InvalidParameters", + "This needs 2 params."); + dbus->sendError(err, errorHandler); + } else { + const auto input1(call.getParameter(0).asString()); + const auto input2(call.getParameter(1).asString()); + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String("Echo of : " + input1 + " and " + input2)); + dbus->sendMethodReturn(reply, errorHandler); + } } + serveEcho2(dbus); }); +} - native.registerMethodCallHandler( - "biz.brightsign.TestInterface.Ping", - [&](const DBus::Message::MethodCall& method) { - DBus::Type::Array propertyList; - - propertyList.add(DBus::Type::DictEntry(DBus::Type::String("p1"), - DBus::Type::String("two"))); - - DBus::Message::MethodReturn result(method.getSerial()); - result.addParameter(DBus::Type::String("pong!!")); - native.sendMethodReturn(method.getHeaderSender(), result); +void serveOpenFile(DBus::Connection::Ptr dbus) +{ + dbus->receiveMethodCall( + "biz.brightsign.TestInterface.OpenFile", + [dbus](const DBus::Message::MethodCall& call) { + if (!call) return; + if (call.isReplyExpected()) { + auto errorHandler = [](const DBus::Error& error) { + if (error) + std::cerr << "error while sending OpenFile reply: " << error.message << std::endl; + }; + if (call.getParameterCount() != 1) { + DBus::Message::Error err(call.getSender(), call.getSerial(), + "biz.brightsign.Error.InvalidParameters", + "This needs 2 params."); + dbus->sendError(err, errorHandler); + } else { + const auto input1 = DBus::Type::asString(call.getParameter(0)); + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + int fd = 0; + reply.addParameter(DBus::Type::UnixFd(fd)); + dbus->sendMethodReturn(reply, errorHandler); + } + } + serveOpenFile(dbus); }); +} -#endif - - // Run a "server" - native.callRequestName( - "test.steev", 0, - [](const DBus::Message::MethodReturn& msg) { - uint32_t result = DBus::Type::asUint32(msg.getParameter(0)); +void test1() +{ + DBus::Log::setLevel(DBus::Log::TRACE); - if (result != DBus::Native::DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { - printf("Not Primary Owner\n"); - } - }, - [](const DBus::Message::Error& msg) { - printf("ERROR FROM callRequestName : %s\n", msg.getMessage().c_str()); - }); + DBus::Log::write(DBus::Log::INFO, "System bus: %s\n", + DBus::Platform::getSystemBus().c_str()); + DBus::Log::write(DBus::Log::INFO, "Session bus: %s\n", + DBus::Platform::getSessionBus().c_str()); - printf("Try and call ourselves...\n"); - DBus::Message::MethodCallParametersIn params; - params.add(std::string("one")); - params.add(std::string("two")); - DBus::Message::MethodCall concat( - DBus::Message::MethodCallIdentifier("/", "biz.brightsign.TestInterface", - "Echo2"), - params); - native.sendMethodCall( - "test.steev", concat, - [](const DBus::Message::MethodReturn& msg) { - std::string result = DBus::Type::asString(msg.getParameter(0)); - printf("REPLY FROM echo2 : This is our id : %s\n", result.c_str()); - }, - [](const DBus::Message::Error& msg) { - printf("ERROR FROM echo2 : %s\n", msg.getMessage().c_str()); + DBus::asio::io_context ioc; + DBus::Connection::Ptr dbus = DBus::Connection::create(ioc); + if (!dbus) + return; + + dbus->connectSessionBus( + [dbus](const DBus::Error& error, const std::string&, const std::string&) { + if (error) + return; + + handleNameAcquired(dbus); + handleNameOwnerChanged(dbus); + + servePropertiesGetAll(dbus); + servePropertiesGet(dbus); + serveIntrospect(dbus); + + servePing(dbus); + serveEcho2(dbus); + serveOpenFile(dbus); + + dbus->requestName( + "test.steev", DBus::RequestNameFlag::None, + [dbus](const DBus::Error& error, const DBus::Message::MethodReturn& msg) { + if (error) + return dbus->disconnect(); + + uint32_t result = DBus::Type::asUint32(msg.getParameter(0)); + if (result != DBus::RequestNameReply::PrimaryOwner) + std::cerr << "Not Primary Owner" << std::endl; + + std::cout << "Try and call ourselves..." << std::endl; + dbus->sendMethodCall( + { "test.steev", + { "/", "biz.brightsign.TestInterface", "Echo2" }, {"one", "two"} }, + [](const DBus::Error& error, const DBus::Message::MethodReturn& reply) { + const auto result = reply.getParameter(0).asString(); + std::cout << "REPLY FROM Echo2 : " << result << std::endl; + }); + + dbus->sendMethodCall( + { "test.steev", + { "/", "biz.brightsign.TestInterface", "OpenFile" }, {"/etc/passwd"} }, + [dbus](const DBus::Error& error, const DBus::Message::MethodReturn& reply) { + const auto result = DBus::Type::asUnixFd(reply.getParameter(0)); + std::cout << "REPLY FROM OpenFile : " << result << std::endl; + dbus->disconnect(); + + std::cout << '\n' << dbus->getStats(); + }); + }); }); - sleep(200); + ioc.run(); } -int main(int argc, const char* argv[]) +int main() { test1(); - return 0; } diff --git a/example/tests/pummel-client.cpp b/example/tests/pummel-client.cpp index 7e9bc85..3a73fea 100644 --- a/example/tests/pummel-client.cpp +++ b/example/tests/pummel-client.cpp @@ -11,26 +11,15 @@ using namespace std::chrono; size_t gTotalSuccesses = 0; std::mutex gMutexOutput; std::mutex gMutexSuccessCount; -std::mutex gMutexReplyErrorCount; -std::mutex gMutexReplyResultCount; bool testClient(const std::string& stubname, size_t iterations) { DBus::Log::setLevel(DBus::Log::INFO); - DBus::Native native(DBus::Platform::getSessionBus()); - - native.BeginAuth(DBus::AuthenticationProtocol::AUTH_BASIC); - - native.callHello( - [](const DBus::Message::MethodReturn& msg) { - std::cerr << "Ready. Client on " - << DBus::Type::asString(msg.getParameter(0)) << std::endl; - }, - [](const DBus::Message::Error& msg) { - std::cerr << "Failed to get hello message. Aborting early" << std::endl; - return -1; - }); + DBus::asio::io_context ioc; + DBus::Connection::Ptr dbus = DBus::Connection::create(ioc); + if (!dbus) + return false; milliseconds ms_start = duration_cast(system_clock::now().time_since_epoch()); @@ -38,80 +27,76 @@ bool testClient(const std::string& stubname, size_t iterations) size_t correct = 0; size_t errors = 0; size_t sent = 0; - for (; sent < iterations; ++sent) { - std::string expected(stubname); - std::string number(std::to_string(sent)); - expected += " "; - expected += number; - - DBus::Message::MethodCallParametersIn params; - params.add(stubname); - params.add(number); - - DBus::Message::MethodCall concat(DBus::Message::MethodCallIdentifier( - "/", "biz.brightsign.test", "concat"), - params); - native.sendMethodCall( - "biz.brightsign", concat, - [&replies, &correct, expected](const DBus::Message::MethodReturn& msg) { - std::lock_guard guard(gMutexReplyResultCount); - std::string result = DBus::Type::asString(msg.getParameter(0)); - ++replies; - if (result == expected) { - ++correct; - } - }, - [&errors](const DBus::Message::Error& msg) { - std::lock_guard guard(gMutexReplyErrorCount); - ++errors; - }); - - // If we have a short delay (e.g. 1ms) between messages, everything is happy - // for 10-10-100 If this is 10ms, then the timeouts below happen a lot more - // frequently. ATM, I can't tell if this is coincidence, or intentional. - // std::this_thread::sleep_for(message_sleep); - } - // We don't get a callback upon timeout. We use a separate loop to check for - // that posibility, so we don't hang in this method. libdbus (as used by the - // daemon) will timeout a request after 25 seconds. So, if there's anything - // left as that time, we're not going to get a reply, so timeout that and - // everything else. milliseconds timeout = std::chrono::seconds(45); - milliseconds ms_timeout_starts = duration_cast(system_clock::now().time_since_epoch()); - milliseconds ms_end; + DBus::asio::steady_timer timer(ioc); + timer.expires_after(timeout); + timer.async_wait( + [dbus] + (const DBus::error_code& error) + { + if (!error) + dbus->disconnect(); + }); - do { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + dbus->connect( + DBus::Platform::getSessionBus(), + DBus::AuthenticationProtocol::create(), + [dbus, &timer, &errors, &replies, &correct, &sent, &stubname, iterations] + (const DBus::Error& error, const std::string&, const std::string&) { + if (error) + return; + + while (sent < iterations) { + const std::string number(std::to_string(sent++)); + const std::string expected(stubname + ' ' + number); + + dbus->sendMethodCall( + { "biz.brightsign", + {"/", "biz.brightsign.test", "concat"}, {stubname, number} }, + [dbus, &timer, &errors, &replies, &correct, expected, iterations] + (const DBus::Error& error, const DBus::Message::MethodReturn& reply) { + if (error) + ++errors; + else { + ++replies; + const auto result = reply.getParameter(0).asString(); + if (result == expected) + ++correct; + } + + if (errors + replies == iterations) { + timer.cancel(); + dbus->disconnect(); + } + }); + + // If we have a short delay (e.g. 1ms) between messages, everything is happy + // for 10-10-100 If this is 10ms, then the timeouts below happen a lot more + // frequently. ATM, I can't tell if this is coincidence, or intentional. + // std::this_thread::sleep_for(message_sleep); + } + }); - ms_end = duration_cast(system_clock::now().time_since_epoch()); + ioc.run(); - if (ms_end - ms_timeout_starts > timeout) { - std::cerr << "Timing out..." << std::endl; - break; - } - } while (sent != replies + errors); + milliseconds ms_end = duration_cast(system_clock::now().time_since_epoch()); std::lock_guard guard(gMutexOutput); - std::cout << (correct == sent ? "SUCCESS" : "FAILURE"); std::cout << " " << stubname << ":: Results ::"; std::cout << " Sent: " << sent; std::cout << " Replies: " << replies; std::cout << " Correct: " << correct; std::cout << " Errors: " << errors; - std::cout << " Duration: " << std::to_string((ms_end - ms_start).count()) - << "ms"; + std::cout << " Duration: " << (ms_end - ms_start).count() << "ms"; std::cout << std::endl; std::cout << " Parameters: " << std::endl; - std::cout << " Timeout: " << std::to_string(timeout.count()).c_str() - << "ms" << std::endl; - std::cout << " Iterations: " << iterations; - std::cout << std::endl; + std::cout << " Timeout: " << timeout.count() << "ms" << std::endl; + std::cout << " Iterations: " << iterations << std::endl; - std::cout << native.getStats(); - std::cout << std::endl; + std::cout << dbus->getStats() << std::endl; return correct == sent; } diff --git a/example/tests/pummel-server.cpp b/example/tests/pummel-server.cpp index e6fe816..d292d09 100644 --- a/example/tests/pummel-server.cpp +++ b/example/tests/pummel-server.cpp @@ -1,147 +1,152 @@ #include "dbus.h" #include -bool gInterrupted = false; +void serveIntrospect(DBus::Connection::Ptr dbus, const std::string& xml) +{ + dbus->receiveMethodCall( + "org.freedesktop.DBus.Introspectable.Introspect", + [dbus, &xml](const DBus::Message::MethodCall& call) { + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String(xml)); + dbus->sendMethodReturn(reply, + [dbus, &xml](const DBus::Error& error) { + if (!error) + serveIntrospect(dbus, xml); + }); + }); -void s_signal_handler(int signal_value) { gInterrupted = true; } +} -void s_catch_signals(void) +// It is recommend you implement org.freedesktop.DBus.Properties.GetAll, +// even if you only return an empty string. Some apps (like d-feet) will +// issue a GetAll before the standard method call you've requested, and +// then wait for a reply, making them appear slow. +void serveGetAll(DBus::Connection::Ptr dbus) { - struct sigaction action; - action.sa_handler = s_signal_handler; - action.sa_flags = 0; - sigemptyset(&action.sa_mask); - sigaction(SIGINT, &action, NULL); - sigaction(SIGTERM, &action, NULL); + dbus->receiveMethodCall( + "org.freedesktop.DBus.Properties.GetAll", + [dbus](const DBus::Message::MethodCall& call) { + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String("")); + dbus->sendMethodReturn(reply, + [dbus](const DBus::Error& error) { + if (!error) + serveGetAll(dbus); + }); + }); } -void testServer() +void serveConcat(DBus::Connection::Ptr dbus) { - DBus::Log::setLevel(DBus::Log::WARNING); + dbus->receiveMethodCall( + "biz.brightsign.test.concat", + [dbus](const DBus::Message::MethodCall& call) { + if (call.isReplyExpected()) { + if (call.getParameterCount() != 2) { + DBus::Message::Error error( + call.getSender(), call.getSerial(), + "biz.brightsign.Error.InvalidParameters", + "This needs 2 params."); + + dbus->sendError( + error, + [dbus](const DBus::Error& error){ + if (error) { + std::cerr << "sendError failed: " << error.message << std::endl; + dbus->disconnect(); + } + }); + } else { + const auto input1 = call.getParameter(0).asString(); + const auto input2 = call.getParameter(1).asString(); + DBus::Message::MethodReturn reply(call.getSender(), call.getSerial()); + reply.addParameter(DBus::Type::String(input1 + " " + input2)); + + dbus->sendMethodReturn(reply, + [dbus](const DBus::Error& error) { + if (error) { + std::cerr << "concat failed: " << error.message << std::endl; + dbus->disconnect(); + } + }); + } + } + serveConcat(dbus); + }); +} - DBus::Native native(DBus::Platform::getSessionBus()); +int main() +{ + DBus::Log::setLevel(DBus::Log::WARNING); - native.BeginAuth(DBus::AuthenticationProtocol::AUTH_BASIC); + DBus::asio::io_context ioc; + auto dbus = DBus::Connection::create(ioc); + if (!dbus) + return 1; -#if 1 - DBus::Introspectable::Introspection introspection; DBus::Introspectable::Interface iface("biz.brightsign.test"); iface.addMethod(DBus::Introspectable::Method("concat", "ss", "s")); - + DBus::Introspectable::Introspection introspection; introspection.addInterface(iface); - std::string xml_introspection(introspection.serialize()); -#endif - - native.callHello( - [](const DBus::Message::MethodReturn& msg) { - std::cout << "Ready. Server on " - << DBus::Type::asString(msg.getParameter(0)) << std::endl; - }, - [](const DBus::Message::Error& msg) { - std::cout << "ERROR : Server did not say 'hello' correct " - << msg.getMessage() << std::endl; + dbus->connect( + DBus::Platform::getSessionBus(), + DBus::AuthenticationProtocol::create(), + [dbus, &introspection] + (const DBus::Error& error, const std::string&, const std::string&) { + if (error) + return; + + serveIntrospect(dbus, introspection.serialize()); + serveGetAll(dbus); + serveConcat(dbus); + + dbus->requestName( + "biz.brightsign", DBus::RequestNameFlag::None, + [dbus](const DBus::Error& error, const DBus::Message::MethodReturn& reply) { + if (error) { + std::cerr << "RequestName error: " << error.message << std::endl; + dbus->disconnect(); + return; + } + + auto result = DBus::Type::asUint32(reply.getParameter(0)); + std::cout << "Now running as biz.brightsign (" << result << ")" << std::endl; + if (result != DBus::RequestNameReply::PrimaryOwner) + std::cout << "Not Primary Owner." << std::endl; + }); }); - native.registerMethodCallHandler( - "org.freedesktop.DBus.Introspectable.Introspect", - [&](const DBus::Message::MethodCall& method) { - DBus::Message::MethodReturn result(method.getSerial()); - result.addParameter(DBus::Type::String(xml_introspection)); - native.sendMethodReturn(method.getHeaderSender(), result); + DBus::asio::signal_set signals(ioc, SIGINT, SIGTERM); + signals.async_wait( + [dbus] + (const DBus::error_code& error, int /*signal_number*/) { + if (!error) + dbus->disconnect(); }); - // It is recommend you implement this method - even if you only return an - // empty string. Some apps (like d-feet) will issue a GetAll before the - // standard method call you've requested, and then wait for a reply, making - // them appear slow. - native.registerMethodCallHandler( - "org.freedesktop.DBus.Properties.GetAll", - [&](const DBus::Message::MethodCall& method) { - // DBus::Type::Generic body = DBus::Type::String(""); - DBus::Message::MethodReturn result2(method.getSerial()); - result2.addParameter(DBus::Type::String("")); - native.sendMethodReturn(method.getHeaderSender(), result2); - }); - - native.registerMethodCallHandler( - "biz.brightsign.test.concat", - [&](const DBus::Message::MethodCall& method) { - if (!method.isReplyExpected()) { - // NOP - } else if (method.getParameterCount() != 2) { - DBus::Message::Error err(method.getSerial(), - "biz.brightsign.Error.InvalidParameters", - "This needs 2 params."); - native.sendError(method.getHeaderSender(), err); - - } else { - std::string input1(DBus::Type::asString(method.getParameter(0))); - std::string input2(DBus::Type::asString(method.getParameter(1))); - DBus::Type::Generic body = DBus::Type::String(input1 + " " + input2); - DBus::Message::MethodReturn result(method.getSerial()); - result.addParameter(body); - native.sendMethodReturn(method.getHeaderSender(), result); - } - }); - - native.callRequestName( - "biz.brightsign", 0, - [](const DBus::Message::MethodReturn& msg) { - uint32_t result = DBus::Type::asUint32(msg.getParameter(0)); - - std::cout << "Now running as biz.brightsign (" << result << ")" - << std::endl; - - if (result != DBus::Native::DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { - std::cout << "Not Primary Owner." << std::endl; - } - }, - [](const DBus::Message::Error& msg) { - std::cout << "ERROR FROM callRequestName : " << msg.getMessage() - << std::endl; - }); - - bool bRunLoop = true; - // Now do a mini-self test - DBus::Message::MethodCallParametersIn params; - params.add(std::string("one")); - params.add(std::string("two")); - DBus::Message::MethodCall concat( - DBus::Message::MethodCallIdentifier("/", "biz.brightsign.test", "concat"), - params); - native.sendMethodCall( - "biz.brightsign", concat, - [&bRunLoop](const DBus::Message::MethodReturn& msg) { - std::string result = DBus::Type::asString(msg.getParameter(0)); + dbus->sendMethodCall( + { "biz.brightsign", {"/", "biz.brightsign.test", "concat"}, {"one", "two"} }, + [dbus](const DBus::Error& error, const DBus::Message::MethodReturn& reply) { + if (error) { + std::cout + << "Terminating because concat has returned an error" + << std::endl; + dbus->disconnect(); + } + const auto result = reply.getParameter(0).asString(); if (result != "one two") { std::cout - << "Terminating because concat is returning incorrect base data." + << "Terminating because concat is returning incorrect base data" << std::endl; - bRunLoop = false; + dbus->disconnect(); } - }, - [&bRunLoop](const DBus::Message::Error& msg) { - std::cout << "Terminating because concat has returned an error." - << std::endl; - bRunLoop = false; }); - // Spin! - while (bRunLoop) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (gInterrupted) { - break; - } - } - std::cout << native.getStats(); -} + ioc.run(); -int main(int argc, const char* argv[]) -{ - s_catch_signals(); - testServer(); +//TODO std::cout << native.getStats(); return 0; } diff --git a/extern/catch2/catch.hpp b/extern/catch2/catch.hpp index f64422a..7e706f9 100644 --- a/extern/catch2/catch.hpp +++ b/extern/catch2/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.12.2 - * Generated: 2020-05-25 15:09:23.791719 + * Catch v2.13.7 + * Generated: 2021-07-28 20:29:27.753164 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,8 +14,8 @@ #define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 12 -#define CATCH_VERSION_PATCH 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 7 #ifdef __clang__ # pragma clang system_header @@ -66,13 +66,16 @@ #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -132,13 +135,9 @@ namespace Catch { #endif -#if defined(__cpp_lib_uncaught_exceptions) -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -// We have to avoid both ICC and Clang, because they try to mask themselves -// as gcc, and we want only GCC in this block -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) @@ -162,7 +161,7 @@ namespace Catch { // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) +# if !defined(__ibmxl__) && !defined(__CUDACC__) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif @@ -244,10 +243,6 @@ namespace Catch { # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) -# if _MSC_VER >= 1900 // Visual Studio 2015 or newer -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -# endif - // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) @@ -330,7 +325,10 @@ namespace Catch { // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if variant is available and usable @@ -373,10 +371,6 @@ namespace Catch { # define CATCH_CONFIG_CPP17_OPTIONAL #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) -# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) # define CATCH_CONFIG_CPP17_STRING_VIEW #endif @@ -775,7 +769,7 @@ constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) n #define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) #define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) #define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) #define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) #define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) #define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) @@ -1105,7 +1099,7 @@ struct AutoReg : NonCopyable { int index = 0; \ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ using expander = int[];\ - (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++, 0)... };/* NOLINT */ \ + (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ @@ -1151,7 +1145,7 @@ struct AutoReg : NonCopyable { constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */\ } \ }; \ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ @@ -1195,7 +1189,7 @@ struct AutoReg : NonCopyable { void reg_tests() { \ int index = 0; \ using expander = int[]; \ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++, 0)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ @@ -1229,7 +1223,7 @@ struct AutoReg : NonCopyable { int index = 0; \ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ using expander = int[];\ - (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++, 0)... };/* NOLINT */ \ + (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ @@ -1278,7 +1272,7 @@ struct AutoReg : NonCopyable { constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++, 0)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */ \ }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ @@ -1325,7 +1319,7 @@ struct AutoReg : NonCopyable { void reg_tests(){\ int index = 0;\ using expander = int[];\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++, 0)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ @@ -1829,8 +1823,8 @@ namespace Catch { #endif namespace Detail { - template - std::string rangeToString(InputIterator first, InputIterator last) { + template + std::string rangeToString(InputIterator first, Sentinel last) { ReusableStringStream rss; rss << "{ "; if (first != last) { @@ -2468,7 +2462,7 @@ namespace Catch { virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) virtual void benchmarkPreparing( std::string const& name ) = 0; @@ -4080,16 +4074,16 @@ namespace Generators { return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); } - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; template // Note: The type after -> is weird, because VS2015 cannot parse // the expression used in the typedef inside, when it is in // return type. Yeah. - auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval().get()) { + auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval().get()) { using UnderlyingType = typename decltype(generatorExpression())::type; - IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + IGeneratorTracker& tracker = acquireGeneratorTracker( generatorName, lineInfo ); if (!tracker.hasGenerator()) { tracker.setGenerator(pf::make_unique>(generatorExpression())); } @@ -4102,11 +4096,17 @@ namespace Generators { } // namespace Catch #define GENERATE( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) #define GENERATE_COPY( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) #define GENERATE_REF( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) // end catch_generators.hpp // start catch_generators_generic.hpp @@ -4516,6 +4516,7 @@ namespace Catch { virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; + virtual double minDuration() const = 0; virtual TestSpec const& testSpec() const = 0; virtual bool hasTestFilters() const = 0; virtual std::vector const& getTestsOrTags() const = 0; @@ -5288,6 +5289,7 @@ namespace Catch { Verbosity verbosity = Verbosity::Normal; WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; UseColour::YesOrNo useColour = UseColour::Auto; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -5338,6 +5340,7 @@ namespace Catch { bool warnAboutMissingAssertions() const override; bool warnAboutNoTests() const override; ShowDurations::OrNot showDurations() const override; + double minDuration() const override; RunTests::InWhatOrder runOrder() const override; unsigned int rngSeed() const override; UseColour::YesOrNo useColour() const override; @@ -5455,6 +5458,8 @@ namespace Catch { } // namespace Catch // end catch_outlier_classification.hpp + +#include #endif // CATCH_CONFIG_ENABLE_BENCHMARKING #include @@ -5715,6 +5720,9 @@ namespace Catch { // Returns double formatted as %.3f (format expected on output) std::string getFormattedDuration( double duration ); + //! Should the reporter show + bool shouldShowDuration( IConfig const& config, double duration ); + std::string serializeFilters( std::vector const& container ); template @@ -6108,8 +6116,6 @@ namespace Catch { static std::string getDescription(); - ReporterPreferences getPreferences() const override; - void noMatchingTestCases(std::string const& spec) override; void assertionStarting(AssertionInfo const&) override; @@ -6338,9 +6344,10 @@ namespace Catch { void writeTestCase(TestCaseNode const& testCaseNode); - void writeSection(std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode); + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); void writeAssertions(SectionNode const& sectionNode); void writeAssertion(AssertionStats const& stats); @@ -6875,7 +6882,7 @@ namespace Catch { } iters *= 2; } - throw optimized_away_error{}; + Catch::throw_exception(optimized_away_error{}); } } // namespace Detail } // namespace Benchmark @@ -6883,6 +6890,7 @@ namespace Catch { // end catch_run_for_at_least.hpp #include +#include namespace Catch { namespace Benchmark { @@ -7053,8 +7061,8 @@ namespace Catch { double b2 = bias - z1; double a1 = a(b1); double a2 = a(b2); - auto lo = std::max(cumn(a1), 0); - auto hi = std::min(cumn(a2), n - 1); + auto lo = (std::max)(cumn(a1), 0); + auto hi = (std::min)(cumn(a2), n - 1); return { point, resample[lo], resample[hi], confidence_level }; } @@ -7123,7 +7131,9 @@ namespace Catch { } template EnvironmentEstimate> estimate_clock_cost(FloatDuration resolution) { - auto time_limit = std::min(resolution * clock_cost_estimation_tick_limit, FloatDuration(clock_cost_estimation_time_limit)); + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration(clock_cost_estimation_time_limit)); auto time_clock = [](int k) { return Detail::measure([k] { for (int i = 0; i < k; ++i) { @@ -7463,23 +7473,37 @@ namespace TestCaseTracking { SourceLineInfo location; NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) { + return lhs.name == rhs.name + && lhs.location == rhs.location; + } }; - struct ITracker; + class ITracker; using ITrackerPtr = std::shared_ptr; - struct ITracker { - virtual ~ITracker(); + class ITracker { + NameAndLocation m_nameAndLocation; + + public: + ITracker(NameAndLocation const& nameAndLoc) : + m_nameAndLocation(nameAndLoc) + {} // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; + NameAndLocation const& nameAndLocation() const { + return m_nameAndLocation; + } + + virtual ~ITracker(); // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; + virtual bool hasStarted() const = 0; virtual ITracker& parent() = 0; @@ -7534,7 +7558,6 @@ namespace TestCaseTracking { }; using Children = std::vector; - NameAndLocation m_nameAndLocation; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; @@ -7543,11 +7566,13 @@ namespace TestCaseTracking { public: TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - NameAndLocation const& nameAndLocation() const override; bool isComplete() const override; bool isSuccessfullyCompleted() const override; bool isOpen() const override; bool hasChildren() const override; + bool hasStarted() const override { + return m_runState != NotStarted; + } void addChild( ITrackerPtr const& child ) override; @@ -7586,6 +7611,10 @@ namespace TestCaseTracking { void addInitialFilters( std::vector const& filters ); void addNextFilters( std::vector const& filters ); + //! Returns filters active in this tracker + std::vector const& getFilters() const; + //! Returns whitespace-trimmed name of the tracked section + std::string const& trimmedName() const; }; } // namespace TestCaseTracking @@ -7751,7 +7780,7 @@ namespace Catch { double sb = stddev.point; double mn = mean.point / n; double mg_min = mn / 2.; - double sg = std::min(mg_min / 4., sb / std::sqrt(n)); + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); double sg2 = sg * sg; double sb2 = sb * sb; @@ -7770,7 +7799,7 @@ namespace Catch { return (nc / n) * (sb2 - nc * sg2); }; - return std::min(var_out(1), var_out(std::min(c_max(0.), c_max(mg_min)))) / sb2; + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; } bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector::iterator first, std::vector::iterator last) { @@ -7910,7 +7939,11 @@ namespace Catch { #ifdef CATCH_PLATFORM_MAC - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #endif #elif defined(CATCH_PLATFORM_IPHONE) @@ -7956,86 +7989,58 @@ namespace Catch { // start catch_fatal_condition.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#if defined( CATCH_CONFIG_WINDOWS_SEH ) +#include namespace Catch { - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + // Wrapper for platform-specific fatal error (signals/SEH) handlers + // + // Tries to be cooperative with other handlers, and not step over + // other handlers. This means that unknown structured exceptions + // are passed on, previous signal handlers are called, and so on. + // + // Can only be instantiated once, and assumes that once a signal + // is caught, the binary will end up terminating. Thus, there + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform(); + public: + // Should also have platform-specific implementations as needed FatalConditionHandler(); - static void reset(); ~FatalConditionHandler(); - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; -} // namespace Catch - -#else - -namespace Catch { - struct FatalConditionHandler { - void reset(); + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } }; -} -#endif +} // end namespace Catch // end catch_fatal_condition.h #include @@ -8095,7 +8100,7 @@ namespace Catch { void sectionEnded( SectionEndInfo const& endInfo ) override; void sectionEndedEarly( SectionEndInfo const& endInfo ) override; - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) void benchmarkPreparing( std::string const& name ) override; @@ -8161,6 +8166,7 @@ namespace Catch { std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; bool m_lastAssertionPassed = false; bool m_shouldReportUnexpected = true; bool m_includeSuccessfulResults; @@ -9071,7 +9077,7 @@ namespace detail { } inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( std::tolower(c) ); } ); + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( unsigned char c ) { return static_cast( std::tolower(c) ); } ); if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") target = true; else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") @@ -9840,6 +9846,9 @@ namespace Catch { | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) ["-d"]["--durations"] ( "show test durations" ) + | Opt( config.minDuration, "seconds" ) + ["-D"]["--min-duration"] + ( "show test durations for tests taking at least the given number of seconds" ) | Opt( loadTestNamesFromFile, "filename" ) ["-f"]["--input-file"] ( "load test names to run from a file" ) @@ -9987,6 +9996,7 @@ namespace Catch { bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + double Config::minDuration() const { return m_data.minDuration; } RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } unsigned int Config::rngSeed() const { return m_data.rngSeed; } UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } @@ -10029,6 +10039,36 @@ namespace Catch { } // end catch_errno_guard.h +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h #include namespace Catch { @@ -10545,7 +10585,7 @@ namespace Catch { // Extracts the actual name part of an enum instance // In other words, it returns the Blue part of Bikeshed::Colour::Blue StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurence of ":" + // Find last occurrence of ":" size_t name_start = enumInstance.size(); while (name_start > 0 && enumInstance[name_start - 1] != ':') { --name_start; @@ -10707,25 +10747,47 @@ namespace Catch { // end catch_exception_translator_registry.cpp // start catch_fatal_condition.cpp -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif +#include + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { - // Report the error condition + //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } -} -#endif // signals/SEH handling + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked anecdotally, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. @@ -10738,7 +10800,7 @@ namespace Catch { { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -10749,38 +10811,50 @@ namespace Catch { return EXCEPTION_CONTINUE_SEARCH; } + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static PVOID exceptionHandlerHandle = nullptr; + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; + ULONG guaranteeSize = static_cast(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + void FatalConditionHandler::engage_platform() { // Register as first handler in current chain exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); + if (!exceptionHandlerHandle) { + CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); + } } - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; + void FatalConditionHandler::disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); } + exceptionHandlerHandle = nullptr; } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } +} // end namespace Catch -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; +#endif // CATCH_CONFIG_WINDOWS_SEH -} // namespace Catch +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) +#include namespace Catch { @@ -10789,10 +10863,6 @@ namespace Catch { const char* name; }; - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -10802,7 +10872,32 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; - void FatalConditionHandler::handleSignal( int sig ) { +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { char const * name = ""; for (auto const& def : signalDefs) { if (sig == def.id) { @@ -10810,16 +10905,33 @@ namespace Catch { break; } } - reset(); - reportFatal(name); + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { - isSet = true; + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -10831,40 +10943,17 @@ namespace Catch { } } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } + void FatalConditionHandler::disengage_platform() { + restorePreviousSignalHandlers(); } - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; - -} // namespace Catch - -#else - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -#endif // signals/SEH handling +} // end namespace Catch -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif +#endif // CATCH_CONFIG_POSIX_SIGNALS // end catch_fatal_condition.cpp // start catch_generators.cpp @@ -10883,8 +10972,8 @@ namespace Generators { GeneratorUntypedBase::~GeneratorUntypedBase() {} - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { - return getResultCapture().acquireGeneratorTracker( lineInfo ); + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); } } // namespace Generators @@ -11419,7 +11508,8 @@ namespace { return lhs == rhs; } - auto ulpDiff = std::abs(lc - rc); + // static cast as a workaround for IBM XLC + auto ulpDiff = std::abs(static_cast(lc - rc)); return static_cast(ulpDiff) <= maxUlpDiff; } @@ -11593,7 +11683,6 @@ Floating::WithinRelMatcher WithinRel(float target) { } // namespace Matchers } // namespace Catch - // end catch_matchers_floating.cpp // start catch_matchers_generic.cpp @@ -12009,7 +12098,7 @@ namespace Catch { if (tmpnam_s(m_buffer)) { CATCH_RUNTIME_ERROR("Could not get a temp filename"); } - if (fopen_s(&m_file, m_buffer, "w")) { + if (fopen_s(&m_file, m_buffer, "w+")) { char buffer[100]; if (strerror_s(buffer, errno)) { CATCH_RUNTIME_ERROR("Could not translate errno to a string"); @@ -12304,11 +12393,13 @@ namespace Catch { namespace Catch { class StartupExceptionRegistry { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) public: void add(std::exception_ptr const& exception) noexcept; std::vector const& getExceptions() const noexcept; private: std::vector m_exceptions; +#endif }; } // end namespace Catch @@ -12391,7 +12482,11 @@ namespace Catch { m_tagAliasRegistry.add( alias, tag, lineInfo ); } void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif } IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { return m_enumValuesRegistry; @@ -12495,17 +12590,32 @@ namespace Catch { std::shared_ptr tracker; ITracker& currentTracker = ctx.currentTracker(); - if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if (currentTracker.nameAndLocation() == nameAndLocation) { + auto thisTracker = currentTracker.parent().findChild(nameAndLocation); + assert(thisTracker); + assert(thisTracker->isGeneratorTracker()); + tracker = std::static_pointer_cast(thisTracker); + } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); assert( childTracker->isGeneratorTracker() ); tracker = std::static_pointer_cast( childTracker ); - } - else { + } else { tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker ); currentTracker.addChild( tracker ); } - if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( !tracker->isComplete() ) { tracker->open(); } @@ -12519,8 +12629,68 @@ namespace Catch { } void close() override { TrackerBase::close(); - // Generator interface only finds out if it has another item on atual move - if (m_runState == CompletedSuccessfully && m_generator->next()) { + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they _can_ + // start, and thus we should wait for them, or they cannot + // start (due to filters), and we shouldn't wait for them + auto* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = &( parent->parent() ); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( filters.begin(), + filters.end(), + static_cast( *child ) + .trimmedName() ) != + filters.end() ) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + if ( should_wait_for_child || + ( m_runState == CompletedSuccessfully && + m_generator->next() ) ) { m_children.clear(); m_runState = Executing; } @@ -12656,10 +12826,10 @@ namespace Catch { return true; } - auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { using namespace Generators; - GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); - assert( tracker.isOpen() ); + GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext, + TestCaseTracking::NameAndLocation( static_cast(generatorName), lineInfo ) ); m_lastAssertionInfo.lineInfo = lineInfo; return tracker; } @@ -12702,17 +12872,17 @@ namespace Catch { #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) void RunContext::benchmarkPreparing(std::string const& name) { - m_reporter->benchmarkPreparing(name); - } + m_reporter->benchmarkPreparing(name); + } void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { m_reporter->benchmarkStarting( info ); } void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { m_reporter->benchmarkEnded( stats ); } - void RunContext::benchmarkFailed(std::string const & error) { - m_reporter->benchmarkFailed(error); - } + void RunContext::benchmarkFailed(std::string const & error) { + m_reporter->benchmarkFailed(error); + } #endif // CATCH_CONFIG_ENABLE_BENCHMARKING void RunContext::pushScopedMessage(MessageInfo const & message) { @@ -12846,9 +13016,8 @@ namespace Catch { } void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } void RunContext::handleUnfinishedSections() { @@ -13433,6 +13602,7 @@ namespace Catch { // end catch_singletons.cpp // start catch_startup_exception_registry.cpp +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) namespace Catch { void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { CATCH_TRY { @@ -13448,6 +13618,7 @@ void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexce } } // end namespace Catch +#endif // end catch_startup_exception_registry.cpp // start catch_stream.cpp @@ -13632,7 +13803,7 @@ namespace Catch { namespace { char toLowerCh(char c) { - return static_cast( std::tolower( c ) ); + return static_cast( std::tolower( static_cast(c) ) ); } } @@ -14015,24 +14186,28 @@ namespace Catch { namespace { struct TestHasher { - explicit TestHasher(Catch::SimplePcg32& rng) { - basis = rng(); - basis <<= 32; - basis |= rng(); - } + using hash_t = uint64_t; - uint64_t basis; + explicit TestHasher( hash_t hashSuffix ): + m_hashSuffix{ hashSuffix } {} - uint64_t operator()(TestCase const& t) const { - // Modified FNV-1a hash - static constexpr uint64_t prime = 1099511628211; - uint64_t hash = basis; - for (const char c : t.name) { + uint32_t operator()( TestCase const& t ) const { + // FNV-1a hash with multiplication fold. + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { hash ^= c; hash *= prime; } - return hash; + hash ^= m_hashSuffix; + hash *= prime; + const uint32_t low{ static_cast( hash ) }; + const uint32_t high{ static_cast( hash >> 32 ) }; + return low * high; } + + private: + hash_t m_hashSuffix; }; } // end unnamed namespace @@ -14050,9 +14225,9 @@ namespace Catch { case RunTests::InRandomOrder: { seedRng( config ); - TestHasher h( rng() ); + TestHasher h{ config.rngSeed() }; - using hashedTest = std::pair; + using hashedTest = std::pair; std::vector indexed_tests; indexed_tests.reserve( unsortedTestCases.size() ); @@ -14215,15 +14390,12 @@ namespace TestCaseTracking { m_currentTracker = tracker; } - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ): + ITracker(nameAndLocation), m_ctx( ctx ), m_parent( parent ) {} - NameAndLocation const& TrackerBase::nameAndLocation() const { - return m_nameAndLocation; - } bool TrackerBase::isComplete() const { return m_runState == CompletedSuccessfully || m_runState == Failed; } @@ -14339,7 +14511,8 @@ namespace TestCaseTracking { bool SectionTracker::isComplete() const { bool complete = true; - if ((m_filters.empty() || m_filters[0] == "") + if (m_filters.empty() + || m_filters[0] == "" || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { complete = TrackerBase::isComplete(); } @@ -14384,6 +14557,14 @@ namespace TestCaseTracking { m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); } + std::vector const& SectionTracker::getFilters() const { + return m_filters; + } + + std::string const& SectionTracker::trimmedName() const { + return m_trimmed_name; + } + } // namespace TestCaseTracking using TestCaseTracking::ITracker; @@ -15118,11 +15299,48 @@ namespace Catch { // end catch_totals.cpp // start catch_uncaught_exceptions.cpp +// start catch_config_uncaught_exceptions.hpp + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP + +#if defined(_MSC_VER) +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif +#endif + +#include + +#if defined(__cpp_lib_uncaught_exceptions) \ + && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif // __cpp_lib_uncaught_exceptions + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +// end catch_config_uncaught_exceptions.hpp #include namespace Catch { bool uncaught_exceptions() { -#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) return std::uncaught_exceptions() > 0; #else return std::uncaught_exception(); @@ -15162,7 +15380,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 12, 2, "", 0 ); + static Version version( 2, 13, 7, "", 0 ); return version; } @@ -15564,6 +15782,17 @@ namespace Catch { return std::string(buffer); } + bool shouldShowDuration( IConfig const& config, double duration ) { + if ( config.showDurations() == ShowDurations::Always ) { + return true; + } + if ( config.showDurations() == ShowDurations::Never ) { + return false; + } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } + std::string serializeFilters( std::vector const& container ) { ReusableStringStream oss; bool first = true; @@ -15830,10 +16059,6 @@ class AssertionPrinter { return "Reports test results on a single line, suitable for IDEs"; } - ReporterPreferences CompactReporter::getPreferences() const { - return m_reporterPrefs; - } - void CompactReporter::noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << '\'' << std::endl; } @@ -15860,8 +16085,9 @@ class AssertionPrinter { } void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + double dur = _sectionStats.durationInSeconds; + if ( shouldShowDuration( *m_config, dur ) ) { + stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << std::endl; } } @@ -16281,8 +16507,9 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; } if (m_headerPrinted) { m_headerPrinted = false; @@ -16566,6 +16793,7 @@ CATCH_REGISTER_REPORTER("console", ConsoleReporter) #include #include #include +#include namespace Catch { @@ -16593,7 +16821,7 @@ namespace Catch { #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif - return std::string(timeStamp); + return std::string(timeStamp, timeStampSize-1); } std::string fileNameTag(const std::vector &tags) { @@ -16604,6 +16832,17 @@ namespace Catch { return it->substr(1); return std::string(); } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + } // anonymous namespace JunitReporter::JunitReporter( ReporterConfig const& _config ) @@ -16673,7 +16912,7 @@ namespace Catch { if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else - xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "time", formatDuration( suiteTime ) ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write properties if there are any @@ -16718,12 +16957,13 @@ namespace Catch { if ( !m_config->name().empty() ) className = m_config->name() + "." + className; - writeSection( className, "", rootSection ); + writeSection( className, "", rootSection, stats.testInfo.okToFail() ); } - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + '/' + name; @@ -16740,13 +16980,18 @@ namespace Catch { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time", formatDuration( sectionNode.stats.durationInSeconds ) ); // This is not ideal, but it should be enough to mimic gtest's // junit output. // Ideally the JUnit reporter would also handle `skipTest` // events and write those out appropriately. xml.writeAttribute( "status", "run" ); + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } + writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) @@ -16756,9 +17001,9 @@ namespace Catch { } for( auto const& childNode : sectionNode.childSections ) if( className.empty() ) - writeSection( name, "", *childNode ); + writeSection( name, "", *childNode, testOkToFail ); else - writeSection( className, name, *childNode ); + writeSection( className, name, *childNode, testOkToFail ); } void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { @@ -17180,6 +17425,10 @@ namespace Catch { .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testGroupStats.totals.testCases.passed ) + .writeAttribute( "failures", testGroupStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.testCases.failedButOk ); m_xml.endElement(); } @@ -17189,6 +17438,10 @@ namespace Catch { .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testRunStats.totals.testCases.passed ) + .writeAttribute( "failures", testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.testCases.failedButOk ); m_xml.endElement(); } diff --git a/src/dbus.h b/src/dbus.h index 9f5016d..f67d7c1 100644 --- a/src/dbus.h +++ b/src/dbus.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,18 +16,21 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_H -#define DBUS_H +#pragma once + +#include "dbus_asio.h" // Core elements and types #include "dbus_log.h" +#include "dbus_names.h" #include "dbus_platform.h" #include "dbus_utils.h" +#include "dbus_error.h" // Marshall/unmarshalling types #include "dbus_type.h" +#include "dbus_type_any.h" #include "dbus_type_array.h" -#include "dbus_type_base.h" #include "dbus_type_boolean.h" #include "dbus_type_byte.h" #include "dbus_type_dictentry.h" @@ -41,6 +45,7 @@ #include "dbus_type_uint16.h" #include "dbus_type_uint32.h" #include "dbus_type_uint64.h" +#include "dbus_type_unixfd.h" #include "dbus_type_variant.h" // Functionality @@ -49,10 +54,8 @@ #include "dbus_message.h" #include "dbus_messageostream.h" #include "dbus_messageprotocol.h" -#include "dbus_native.h" #include "dbus_transport.h" +#include "dbus_connection.h" // Introspective library #include "dbus_introspectable.h" - -#endif // DBUS_H diff --git a/src/dbus_asio.h.in b/src/dbus_asio.h.in new file mode 100644 index 0000000..5a046a4 --- /dev/null +++ b/src/dbus_asio.h.in @@ -0,0 +1,95 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#pragma once + +#include <@DBUS_ASIO_INCLUDE@> + +#include + +namespace DBus { + +namespace asio = @DBUS_ASIO_NAMESPACE@; + +using errc = @DBUS_ERROR_CODE_ERRC@; +using @DBUS_ERROR_CODE_NAMESPACE@::error_code; +using @DBUS_ERROR_CODE_NAMESPACE@::system_category; +using @DBUS_MAKE_ERROR_CODE@; + + +template +struct StoredToken : public std::enable_shared_from_this> +{ + using Ptr = std::shared_ptr>; + + template + static Ptr create(asio::io_context& ioc, CompletionToken&& token) + { + Ptr obj = std::make_shared>(ioc); + obj->store(std::move(token)); + return obj; + } + + StoredToken(asio::io_context& ioc) + : timer_(ioc) + {} + + void invoke(Args... args) + { + args_ = std::make_tuple(args...); + timer_.cancel(); + } + +protected: + template + void store(CompletionToken&& token) + { + // never expires + timer_.expires_at(std::chrono::steady_clock::time_point::max()); + + asio::async_initiate< + CompletionToken, void(Args...)>( + [self = this->shared_from_this()](auto&& handler) mutable + { + self->timer_.async_wait( + [self = self->shared_from_this(), handler = std::move(handler)] + (const error_code&) mutable + { +#if __cplusplus < 201703L + self->apply(std::move(handler), std::index_sequence_for{}); +#else + std::apply(handler, self->args_); +#endif + }); + }, + token); + } + +#if __cplusplus < 201703L + template + void apply(Handler&& handler, std::index_sequence) + { + handler(std::get(args_)...); + } +#endif + + std::tuple>...> args_; + asio::steady_timer timer_; +}; + +} // namespace DBus + diff --git a/src/dbus_auth.cpp b/src/dbus_auth.cpp index 21e9f5a..95388ae 100644 --- a/src/dbus_auth.cpp +++ b/src/dbus_auth.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,216 +17,48 @@ // . #include "dbus_auth.h" -#include "dbus_log.h" -#include "dbus_platform.h" -#include "dbus_utils.h" -/* -The AUTH handshake is: -Server Client - <--- AUTH -OK ----> - <--- (1) BEGIN (or) - <--- (2) NEGOTIATE_UNIX_FD -AGREE ----> - <--- BEGIN -*/ - -DBus::AuthenticationProtocol::AuthenticationProtocol( - std::shared_ptr& transport) - : m_Transport(transport) -{ -} - -void DBus::AuthenticationProtocol::reset() -{ - m_data.clear(); - sendAuth(m_AuthType); -} - -void DBus::AuthenticationProtocol::sendAuthListMethods() -{ - sendWire(std::string("AUTH\r\n")); -} - -// RCVD: REJECTED EXTERNAL DBUS_COOKIE_SHA1 ANONYMOUS -void DBus::AuthenticationProtocol::sendAuth( - DBus::AuthenticationProtocol::AuthRequired type /* = AUTH_BASIC*/) -{ - { - boost::recursive_mutex::scoped_lock guard(m_AuthTypeMutex); - m_AuthType = type; - } - - std::string auth("AUTH EXTERNAL "); - DBus::Utils::ConvertBinaryToHexString(auth, - std::to_string(Platform::getUID())); - auth += "\r\n"; - sendWire(auth); -} - -bool DBus::AuthenticationProtocol::processData() +DBus::AuthenticationProtocol::Ptr +DBus::AuthenticationProtocol::create() { - const size_t pos = m_data.find("\r\n"); - if (pos != std::string::npos) { - std::string command(m_data.data(), pos + 2); - return onCommand(command); - m_data = m_data.substr(pos + 2); + struct ShareableAuthProto : public AuthenticationProtocol { + ShareableAuthProto() + : AuthenticationProtocol() {}; }; - - return false; + return std::make_shared(); } -bool DBus::AuthenticationProtocol::onReceiveData(DBus::OctetBuffer& buffer) -{ - bool authenticated = false; - - while (buffer.size() && !authenticated) { - size_t pos = buffer.find('\n'); - pos = pos == std::string::npos ? buffer.size() : pos + 1; - m_data.append((const char*)buffer.data(), pos); - buffer.remove_prefix(pos); - authenticated = processData(); - }; - - return authenticated; -} +DBus::AuthenticationProtocol::AuthenticationProtocol() +{} -// -// Protected methods -// -bool DBus::AuthenticationProtocol::onOK( - const std::string& guid /* unused in this case */) +DBus::AuthenticationProtocol::Command +DBus::AuthenticationProtocol::extractCommand(std::string& response) { - std::string address; - DBus::Utils::ConvertHexStringToBinary(address, guid); - - bool send_begin = false; - - { - boost::recursive_mutex::scoped_lock guard(m_AuthTypeMutex); - if (m_AuthType == AUTH_BASIC) { - send_begin = true; + for (auto& c : m_commands) { + auto cmdSize = c.first.size(); + if (response.substr(0, cmdSize) != c.first) + continue; + if (response.size() > cmdSize) { + if (response[cmdSize] != ' ') + continue; + cmdSize += 1; } + // erase command from response + response.erase(0, cmdSize); + return c.second; } - - if (send_begin) { - sendBegin(); - return true; - } - - sendNegotiateUnixFD(); - return false; -} - -bool DBus::AuthenticationProtocol::onError(const std::string& error_message) -{ - DBus::Log::write(DBus::Log::ERROR, "DBus :: onError : %s\n", - error_message.c_str()); - return false; + return UNKNOWN; } -bool DBus::AuthenticationProtocol::onRejected( - const std::string& error_message) +std::string +DBus::AuthenticationProtocol::toHex(const std::string& input) { - // Split by space - // Store the list of available auth methods - // Optionally, retry. (determin by cb?) - DBus::Log::write(DBus::Log::WARNING, "DBus :: Reject : %s\n", - error_message.c_str()); - return false; -} - -bool DBus::AuthenticationProtocol::onAgreeUnixFD() -{ - DBus::Log::write(DBus::Log::INFO, "DBus :: onAgreeUnixFD\n"); - sendBegin(); - return true; -} - -bool DBus::AuthenticationProtocol::onAuth(const std::string&) { return false; } - -bool DBus::AuthenticationProtocol::onData(const std::string&) { return false; } - -bool DBus::AuthenticationProtocol::onCancel() { return false; } - -bool DBus::AuthenticationProtocol::onNegotiateUnixFD(const std::string&) -{ - return false; -} - -void DBus::AuthenticationProtocol::sendNegotiateUnixFD() -{ - sendWire(std::string("NEGOTIATE_UNIX_FD\r\n")); -} - -void DBus::AuthenticationProtocol::sendBegin() -{ - sendWire(std::string("BEGIN\r\n")); - m_Transport->onAuthComplete(); -} - -void DBus::AuthenticationProtocol::sendData(std::string& data) -{ - std::string packet("DATA "); - DBus::Utils::ConvertBinaryToHexString(packet, data); - packet += "\r\n"; - - sendWire(packet); -} - -void DBus::AuthenticationProtocol::sendWire(const std::string& data) -{ - m_Transport->sendStringDirect(data); -} - -// Return true once we have completed the auth state -bool DBus::AuthenticationProtocol::onCommand(const std::string& command) -{ - DBus::Log::write(DBus::Log::TRACE, "DBus :: CMD: %s\n", command.c_str()); - DBus::Log::writeHex(DBus::Log::TRACE, "DBus :: CMD: ", command); - - std::pair> - callback_list[8] = { - // Client - std::make_pair("OK", std::bind(&DBus::AuthenticationProtocol::onOK, this, std::placeholders::_1)), - std::make_pair("ERROR", - std::bind(&DBus::AuthenticationProtocol::onError, this, - std::placeholders::_1)), - std::make_pair("REJECTED", - std::bind(&DBus::AuthenticationProtocol::onRejected, - this, std::placeholders::_1)), - std::make_pair( - "AGREE_UNIX_FD", - std::bind(&DBus::AuthenticationProtocol::onAgreeUnixFD, this)), - std::make_pair("DATA", - std::bind(&DBus::AuthenticationProtocol::onData, this, - std::placeholders::_1)), - // Server (TODO) - std::make_pair("AUTH", - std::bind(&DBus::AuthenticationProtocol::onAuth, this, - std::placeholders::_1)), - std::make_pair( - "NEGOTIATE_UNIX_FD", - std::bind(&DBus::AuthenticationProtocol::onNegotiateUnixFD, this, - std::placeholders::_1)), - std::make_pair( - "CANCEL", - std::bind(&DBus::AuthenticationProtocol::onCancel, this)) - }; - - for (size_t i = 0; i < 8; ++i) { - if (command.substr(0, strlen(callback_list[i].first)) == callback_list[i].first) { - return callback_list[i].second( - command.substr(strlen(callback_list[i].first) + 1)); - } + std::ostringstream hexout; + hexout << std::hex << std::setfill('0'); + for (auto ch : input) { + hexout << std::setw(2) << static_cast(ch); } - - DBus::Log::write(DBus::Log::WARNING, - "DBus :: CMD: %s did not execute anything, so please " - "implement the method.\n", - command.c_str()); - - return false; + return hexout.str(); } + diff --git a/src/dbus_auth.h b/src/dbus_auth.h index ada3b41..fd420e4 100644 --- a/src/dbus_auth.h +++ b/src/dbus_auth.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,55 +16,264 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_AUTH_H -#define DBUS_AUTH_H +#pragma once +#include +#include + +#include "dbus_asio.h" +#include "dbus_utils.h" +#include "dbus_platform.h" #include "dbus_transport.h" -#include namespace DBus { // See Authentication state diagrams -class AuthenticationProtocol { +class AuthenticationProtocol : public std::enable_shared_from_this +{ public: - typedef enum AuthRequired { - AUTH_BASIC, - AUTH_NEGOTIATE_UNIX_FD, - } AuthRequired; + using Ptr = std::shared_ptr; + + struct Statistics { + std::size_t count_send; + std::size_t count_recv; + std::size_t bytes_send; + std::size_t bytes_recv; + }; + + static Ptr create(); + + bool unixFdNegotiated() { return m_unixFdNegotiated; } + + template + auto asyncAuthenticate( + Transport::Ptr transport, + CompletionToken&& token) + { + m_transport = transport; + m_state = Starting; + m_server_guid.clear(); + + return asio::async_compose< + CompletionToken, void(const error_code&, const std::string&)>( + [self = shared_from_this()] + (auto& composition, const error_code& error = {}, const std::size_t size = 0) mutable + { + if (size) { + ++self->m_stats.count_recv; + self->m_stats.bytes_recv += size; + } + if (error) + return composition.complete(error, ""); + + std::string& response = self->m_data; + Command command = UNKNOWN; + if (response.size()) { + Log::write(Log::TRACE, "DBus :: Recv : Auth Cmd :\n"); + Log::writeHex(Log::TRACE, " ", response); + if (response.size() < 3) + return composition.complete(make_error_code(errc::protocol_error), ""); + response.resize(response.size() - 2); // remove trailing \r\n + Log::write(Log::TRACE, " %s\n", response.c_str()); + command = self->extractCommand(response); + } - AuthenticationProtocol(std::shared_ptr& transport); + if (self->m_state == Starting) + self->sendCredentials(std::move(composition)); + else if (self->m_state == SendingCredentials) + self->sendAuth(std::move(composition)); + else if (self->m_state == WaitingForData) + if (command == DATA) + self->handleData(response, std::move(composition)); + else if (command == REJECTED) + self->handleRejected(response, std::move(composition)); + else if (command == ERROR) + self->handleError(response, std::move(composition)); + else if (command == OK) + self->handleOk(response, std::move(composition)); + else + self->sendError(response, std::move(composition)); + else if (self->m_state == WaitingForOK) + if (command == OK) + self->handleOk(response, std::move(composition)); + else if (command == REJECTED) + self->handleRejected(response, std::move(composition)); + else if (command == DATA || command == ERROR) + self->sendCancel(std::move(composition)); + else + self->sendError(response, std::move(composition)); + else if (self->m_state == WaitingForReject) + if (command == REJECTED) + self->handleRejected(response, std::move(composition)); + else + return composition.complete(make_error_code(errc::protocol_error), ""); + else if (self->m_state == WaitingForAgreeUnixFD) + if (command == AGREE_UNIX_FD) + self->handleAgreeUniXFD(std::move(composition)); + else + self->sendBegin(std::move(composition)); + else if (self->m_state == Finishing) + return composition.complete(error, self->m_server_guid); + else { + Log::write(Log::ERROR, "DBus :: ERROR : unhandled auth state"); + return composition.complete(make_error_code(errc::protocol_error), ""); + } + }, + token); + } - void reset(); - void sendAuthListMethods(); - void sendAuth(AuthenticationProtocol::AuthRequired type = AUTH_BASIC); - bool onReceiveData(OctetBuffer& buffer); + struct Statistics getStats() { return m_stats; } protected: - bool onOK(const std::string& guid /* unused in this case */); - bool onError(const std::string& error_message); - bool onRejected(const std::string& error_message); - bool onAgreeUnixFD(); - - bool onAuth(const std::string&); - bool onData(const std::string&); - bool onCancel(); - bool onNegotiateUnixFD(const std::string&); - - void sendNegotiateUnixFD(); - void sendBegin(); - void sendData(std::string& data); - void sendWire(const std::string& data); - // Return true once we have completed the auth state - bool onCommand(const std::string& command); - - bool processData(); - -private: + AuthenticationProtocol(); + + enum State { + Starting, + SendingCredentials, + WaitingForData, + WaitingForOK, + WaitingForReject, + WaitingForAgreeUnixFD, + Finishing + }; + + enum Command { OK, DATA, ERROR, REJECTED, AGREE_UNIX_FD, UNKNOWN }; + const std::array< std::pair, 5 > m_commands = { + std::make_pair("OK", OK), + std::make_pair("DATA", DATA), + std::make_pair("ERROR", ERROR), + std::make_pair("REJECTED", REJECTED), + std::make_pair("AGREE_UNIX_FD", AGREE_UNIX_FD), + }; + + Command extractCommand(std::string& response); + std::string toHex(const std::string& input); + + template + void sendCommand(bool expectResponse, std::string&& command, Handler&& handler) + { + ++m_stats.count_send; + m_stats.bytes_send += command.size(); + Log::write(Log::TRACE, "DBus :: Send : Auth Cmd : %ld bytes\n %s", + command.size(), (command[0] == 0 ? "\\0\n" : command.c_str())); + Log::writeHex(Log::TRACE, " ", command); + m_data = std::move(command); + m_transport->asyncAuthExchange( + expectResponse ? Transport::ResponseExpected : Transport::NoResponseExpected, + m_buffer, std::forward(handler)); + } + + template + void sendCredentials(Handler&& handler) + { + // No credentials sent here, but we have to send the null byte anyway + sendCommand(false, std::string(1, '\0'), std::forward(handler)); + m_state = SendingCredentials; + } + + template + void sendAuth(Handler&& handler) + { + std::ostringstream cmd; + const std::string uid = std::to_string(Platform::getUID()); + cmd << "AUTH EXTERNAL " << toHex(uid) << "\r\n"; + sendCommand(true, cmd.str(), std::forward(handler)); + m_state = WaitingForOK; + } + + template + void sendData(const std::string& data, Handler&& handler) + { + std::ostringstream cmd; + cmd << "DATA " << toHex(data) << "\r\n"; + sendCommand(true, cmd.str(), std::forward(handler)); + m_state = WaitingForData; + } + + template + void sendNegotiateUnixFD(Handler&& handler) + { + sendCommand(true, "NEGOTIATE_UNIX_FD\r\n", std::forward(handler)); + m_state = WaitingForAgreeUnixFD; + } + + template + void sendError(const std::string& message, Handler&& handler) + { + std::ostringstream cmd; + cmd << "ERROR"; + if (message.size()) + cmd << ' ' << message; + cmd << "\r\n"; + sendCommand(true, cmd.str(), std::forward(handler)); + m_state = WaitingForData; + } + + template + void sendCancel(Handler&& handler) + { + sendCommand(true, "CANCEL\r\n", std::forward(handler)); + m_state = WaitingForReject; + } + + template + void sendBegin(Handler&& handler) + { + sendCommand(false, "BEGIN\r\n", std::forward(handler)); + m_state = Finishing; + } + + template + void handleOk( + const std::string& guid, Handler&& handler) + { + m_server_guid = guid; + Log::write(Log::INFO, "DBus :: Auth OK : guid %s\n", + m_server_guid.c_str()); + sendNegotiateUnixFD(std::forward(handler)); + } + + template + void handleError( + const std::string& message, Handler&& handler) + { + Log::write(Log::ERROR, "DBus :: Auth ERROR : %s\n", + message.c_str()); + sendCancel(std::forward(handler)); + } + + template + void handleRejected( + const std::string& message, Handler&& handler) + { + Log::write(Log::WARNING, "DBus :: Auth REJECTED : %s\n", + message.c_str()); + handler(make_error_code(errc::protocol_error), 0); + } + + template + void handleAgreeUniXFD(Handler&& handler) + { + Log::write(Log::INFO, "DBus :: Auth AGREE_UNIX_FD\n"); + m_unixFdNegotiated = true; + sendBegin(std::forward(handler)); + } + + template + void handleData(const std::string& data, Handler&& handler) + { + Log::write(Log::INFO, "DBus :: Auth DATA %s\n", data.c_str()); + sendCancel(std::forward(handler)); + } + + std::shared_ptr m_transport; + State m_state = Starting; std::string m_data; - AuthenticationProtocol::AuthRequired m_AuthType; - mutable boost::recursive_mutex m_AuthTypeMutex; - std::shared_ptr m_Transport; + DynamicStringBuffer m_buffer = asio::dynamic_buffer(m_data); + std::string m_server_guid; + bool m_unixFdNegotiated = false; + + struct Statistics m_stats = {}; }; -} // namespace DBus -#endif // DBUS_AUTH_H +} // namespace DBus diff --git a/src/dbus_type_base.cpp b/src/dbus_connection.cpp similarity index 56% rename from src/dbus_type_base.cpp rename to src/dbus_connection.cpp index fc90979..d848c15 100644 --- a/src/dbus_type_base.cpp +++ b/src/dbus_connection.cpp @@ -1,5 +1,5 @@ // This file is part of dbus-asio -// Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,18 +15,21 @@ // file named COPYING. If you do not have this file see // . -#include "dbus_type_base.h" +#include "dbus_connection.h" -void DBus::Type::Base::setSignature(const std::string& type) +DBus::Connection::Ptr +DBus::Connection::create(DBus::asio::io_context& ioContext) { - m_Signature = type; + struct ShareableConnection : public Connection { + ShareableConnection(DBus::asio::io_context& ioContext) + : Connection(ioContext) {}; + }; + return std::make_shared(ioContext); } -std::string DBus::Type::Base::getSignature() const { return m_Signature; } - -std::string DBus::Type::Base::toString(const std::string& prefix) const +DBus::Connection::Connection(DBus::asio::io_context& ioContext) + : m_ioContext(ioContext) + , m_transport(DBus::Transport::create(ioContext)) { - return std::string(prefix + "\n"); } -std::string DBus::Type::Base::asString() const { return ""; } diff --git a/src/dbus_connection.h b/src/dbus_connection.h new file mode 100644 index 0000000..c0f6dcf --- /dev/null +++ b/src/dbus_connection.h @@ -0,0 +1,445 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#pragma once + +#include "dbus_asio.h" +#include "dbus_auth.h" +#include "dbus_messageprotocol.h" +#include "dbus_transport.h" + +#include +#include + +namespace DBus { + +static constexpr char const *Name = "org.freedesktop.DBus"; +static constexpr char const *Object = "/org/freedesktop/DBus"; +static constexpr char const *Interface = "org.freedesktop.DBus"; + +enum RequestNameFlag { + None = 0, + AllowReplacement = 1, + ReplaceExisting = 2, + DoNotQueue = 4 +}; + +enum RequestNameReply { + PrimaryOwner = 1, + InQueue = 2, + Exists = 3, + AlreadyOwner = 4 +}; + +enum ReleaseNameReply { + Released = 1, + NonExistent = 2, + NotOwner = 3 +}; + +class Connection : public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + static Ptr create(asio::io_context& ioContext) + { + struct ShareableConnection : public Connection { + ShareableConnection(asio::io_context& ioContext) + : Connection(ioContext) {}; + }; + return std::make_shared(ioContext); + } + + template + auto connectSystemBus(CompletionToken&& token) + { + return connect(Platform::getSystemBus(), std::move(token)); + } + + template + auto connectSessionBus(CompletionToken&& token) + { + return connect(Platform::getSessionBus(), std::move(token)); + } + + template + auto connect( + const std::string& busPath, + CompletionToken&& token) + { + return connect( + busPath, + AuthenticationProtocol::create(), + std::move(token)); + } + + template + auto connect( + const std::string& busPath, + AuthenticationProtocol::Ptr authProto, + CompletionToken&& token) + { + enum { starting, connecting, authenticating, requestingName }; + return asio::async_compose< + CompletionToken, void(const Error&, const std::string&, const std::string&)>( + [self = shared_from_this(), state = starting, &busPath, authProto] + (auto& composition, const Error& error = {}, const std::string& data = {}) mutable + { + switch (state) { + case starting: + state = connecting; + self->m_transport->asyncConnect(busPath, std::move(composition)); + return; + + case connecting: + if (error) + break; + state = authenticating; + authProto->asyncAuthenticate(self->m_transport, std::move(composition)); + return; + + case authenticating: + if (error) + break; + state = requestingName; + self->m_authStats = authProto->getStats(); + self->m_serverGuid = data; + self->m_msgProto = MessageProtocol::start(self->m_ioContext, self->m_transport); + self->hello(std::move(composition)); + return; + + case requestingName: + self->m_uniqueName = data; + break; + } + composition.complete(error, self->m_uniqueName, self->m_serverGuid); + }, token); + } + + void disconnect() + { + m_nextSerial = 1; + m_msgProto->stop(); + } + + bool connected() + { + return m_transport->connected(); + } + + template + auto sendMethodCall( + const Message::MethodCall& methodCall, + CompletionToken&& token) + { + return m_msgProto->sendMethodCall( + m_nextSerial++, methodCall, std::forward(token)); + } + + template + auto sendMethodReturn( + const Message::MethodReturn& methodReturn, + CompletionToken&& token) + { + return m_msgProto->sendMethodReturn( + m_nextSerial++, methodReturn, std::forward(token)); + } + + template + auto sendSignal( + const Message::Signal& signal, + CompletionToken&& token) + { + return m_msgProto->sendSignal( + m_nextSerial++, signal, std::forward(token)); + } + + template + auto sendError( + const Message::Error& error, + CompletionToken&& token) + { + return m_msgProto->sendError( + m_nextSerial++, error, std::forward(token)); + } + + template + auto receiveMethodCall( + const std::string& interface, + CompletionToken&& token) + { + return m_msgProto->receiveMethodCall( + interface, std::forward(token)); + } + + template + auto receiveSignal( + const std::string& signal, + CompletionToken&& token) + { + return m_msgProto->receiveSignal( + signal, std::forward(token)); + } + + auto cancelReceiveSignal(const std::string& signal) + { + return m_msgProto->cancelReceiveSignal(signal); + } + + template + auto receiveError( + CompletionToken&& token) + { + return m_msgProto->receiveError( + std::forward(token)); + } + + + ////////////////////////////////////////////// + // Methods for the standard DBus interfaces // + ////////////////////////////////////////////// + + template + auto getAllProperties( + const BusName& destination, + const ObjectPath& path, + const InterfaceName& interface, + CompletionToken&& token) + { + return sendMethodCall({ destination, + {path, "org.freedesktop.DBus.Properties", "GetAll"}, {interface} }, + std::forward(token)); + } + + template + auto getProperty( + const BusName& destination, + const ObjectPath& path, + const InterfaceName& interface, + const MemberName& property, + CompletionToken&& token) + { + return sendMethodCall({ destination, + {path, "org.freedesktop.DBus.Properties", "Get"}, {interface, property} }, + std::forward(token)); + } + + template + auto setProperty( + const BusName& destination, + const ObjectPath& path, + const InterfaceName& interface, + const MemberName& property, + CompletionToken&& token) + { + return sendMethodCall({ destination, + {path, "org.freedesktop.DBus.Properties", "Set"}, {interface, property} }, + std::forward(token)); + } + + + ////////////////////////////////////////// + // Methods for the Message Bus Messages // + ////////////////////////////////////////// + + template + auto requestName( + const WellKnownName& name, + std::uint32_t flags, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "RequestName"}, {name, flags} }, + std::forward(token)); + } + + template + auto releaseName( + const WellKnownName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "ReleaseName"}, {name} }, + std::forward(token)); + } + + template + auto listQueuedOwners( + const WellKnownName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "ListQueuedOwners"}, {name} }, + std::forward(token)); + } + + template + auto listNames(CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "ListNames"} }, + std::forward(token)); + } + + template + auto listActivatableNames(CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "ListActivatableNames"} }, + std::forward(token)); + } + + template + auto nameHasOwner( + const WellKnownName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "NameHasOwner"}, {name} }, + std::forward(token)); + } + + template + auto getNameOwner( + const WellKnownName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "GetNameOwner"}, {name} }, + std::forward(token)); + } + + template + auto getConnectionUnixUser( + const BusName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "GetConnectionUnixUser"}, {name} }, + std::forward(token)); + } + + template + auto getConnectionUnixProcessID( + const BusName& name, + CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "GetConnectionUnixProcessID"}, {name} }, + std::forward(token)); + } + + template + auto addMatch(const MatchRule& rule, CompletionToken&& token) const + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "AddMatch"}, {rule.str()} }, + std::forward(token)); + } + + template + auto removeMatch(const MatchRule& rule, CompletionToken&& token) const + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "RemoveMatch"}, {rule.str()} }, + std::forward(token)); + } + + template + auto getId(CompletionToken&& token) + { + return sendMethodCall({ DBus::Name, + {DBus::Object, DBus::Interface, "GetId"} }, + std::forward(token)); + } + + template + auto becomeMonitor( + const std::vector& rules, + CompletionToken&& token) + { + DBus::Type::Array rulesArray("as"); + for (const auto& rule : rules) + rulesArray.add(rule.str()); + + return sendMethodCall({DBus::Name, + {DBus::Object, DBus::Interface, "BecomeMonitor"}, {rulesArray, Type::Uint32(0)} }, + std::forward(token)); + } + + std::string getStats() const + { + std::ostringstream stats; + MessageProtocol::Statistics msgStats = m_msgProto->getStats(); + + stats << "Connection stats:" << '\n'; + stats << " count_send_auth_commands: " << m_authStats.count_send << '\n'; + stats << " count_receive_auth_commands: " << m_authStats.count_recv << '\n'; + stats << " count_send_methodcalls: " << msgStats.count_send_methodcalls << '\n'; + stats << " count_receive_methodcalls: " << msgStats.count_recv_methodcalls << '\n'; + stats << " count_send_methodreturns: " << msgStats.count_send_methodreturns << '\n'; + stats << " count_receive_methodreturns: " << msgStats.count_recv_methodreturns << '\n'; + stats << " count_send_errors: " << msgStats.count_send_errors << '\n'; + stats << " count_receive_errors: " << msgStats.count_recv_errors << '\n'; + stats << " count_send_signals: " << msgStats.count_send_signals << '\n'; + stats << " count_receive_signals: " << msgStats.count_recv_signals << '\n'; + stats << " bytes_send_auth: " << m_authStats.bytes_send << '\n'; + stats << " bytes_receive_auth: " << m_authStats.bytes_recv << '\n'; + stats << " bytes_send_message: " << msgStats.bytes_send << '\n'; + stats << " bytes_receive_message: " << msgStats.bytes_recv << '\n'; + + return stats.str(); + } + +protected: + Connection(asio::io_context& ioContext) + : m_ioContext(ioContext) + , m_transport(Transport::create(ioContext)) + {} + + template + auto hello(CompletionToken&& token) + { + return asio::async_initiate< + CompletionToken, void(const Error&, const std::string&)>( + [this](auto&& handler) mutable + { + sendMethodCall({ DBus::Name, {DBus::Object, DBus::Interface, "Hello"} }, + [self = shared_from_this(), handler = std::forward(handler)] + (const Error& error, const Message::MethodReturn& methodReturn) mutable + { + if (error) + handler(error, ""); + else + handler(error, DBus::Type::asString(methodReturn.getParameter(0))); + }); + }, token); + } + + struct AuthenticationProtocol::Statistics m_authStats = {}; + + asio::io_context& m_ioContext; + Transport::Ptr m_transport; + MessageProtocol::Ptr m_msgProto; + + std::string m_serverGuid; + std::string m_uniqueName; + + std::uint32_t m_nextSerial = 1; +}; + +} diff --git a/src/dbus_error.h b/src/dbus_error.h new file mode 100644 index 0000000..d8aa82a --- /dev/null +++ b/src/dbus_error.h @@ -0,0 +1,59 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +// Wrapper class for the ASIO ErrorCode and DBus::Message::Error + +#pragma once + +#include "dbus_message.h" +#include "dbus_asio.h" + +namespace DBus { + +struct Error { + Error() + : m_error(false) + {} + + Error(const std::string& message, const std::string& category = "") + : category(category) + , message(message) + , m_error(true) + {} + + Error(const error_code& error) + : category(error.category().name()) + , message(error.message()) + , m_error(error) + {} + + Error(const Message::Error& error) + : category(error.getName()) + , message(error.getMessage()) + , m_error(!category.empty()) + {} + + explicit operator bool() const { return m_error; } + + std::string category; + std::string message; + +protected: + bool m_error; +}; + +} diff --git a/src/dbus_introspectable.cpp b/src/dbus_introspectable.cpp index b48f080..c44f643 100644 --- a/src/dbus_introspectable.cpp +++ b/src/dbus_introspectable.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -164,24 +165,22 @@ DBus::Introspectable::Method::Method(const std::string& name, { } -std::string DBus::Introspectable::Method::serialize() const +std::string DBus::Introspectable::Method::serialize() { std::string result; - result += ""; + result += "\n"; - size_t idx = 0; - while (idx < m_InParams.size()) { - std::string sig(DBus::Type::extractSignature(m_InParams, idx)); - result += "\n"; - idx += sig.size(); + std::string code = m_InParams.getNextTypeCode(); + while (!code.empty()) { + result += "\n"; + code = m_InParams.getNextTypeCode(); } - // - idx = 0; - while (idx < m_OutParams.size()) { - std::string sig(DBus::Type::extractSignature(m_OutParams, idx)); - result += "\n"; - idx += sig.size(); + + code = m_OutParams.getNextTypeCode(); + while (!code.empty()) { + result += "\n"; + code = m_OutParams.getNextTypeCode(); } result += ""; diff --git a/src/dbus_introspectable.h b/src/dbus_introspectable.h index 1551401..0ffddb6 100644 --- a/src/dbus_introspectable.h +++ b/src/dbus_introspectable.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,8 +16,9 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_INTROSPECTABLE_H -#define DBUS_INTROSPECTABLE_H +#pragma once + +#include "dbus_type_signature.h" #include #include @@ -57,11 +59,11 @@ namespace Introspectable { Method(const std::string& name, const std::string& in_params, const std::string& out_params); - std::string serialize() const; + std::string serialize(); std::string m_Name; - std::string m_InParams; - std::string m_OutParams; + Type::Signature m_InParams; + Type::Signature m_OutParams; }; class Property { @@ -97,5 +99,3 @@ namespace Introspectable { // TODO: Support names for each property and signal } // namespace Introspectable } // namespace DBus - -#endif diff --git a/src/dbus_log.cpp b/src/dbus_log.cpp index b5e7982..227210a 100644 --- a/src/dbus_log.cpp +++ b/src/dbus_log.cpp @@ -44,28 +44,52 @@ void DBus::Log::write(size_t type, const char* msg, ...) void DBus::Log::writeHex(size_t type, const std::string& prefix, const std::string& hex) { - if (!isActive(type)) { + writeHex(type, prefix, hex.data(), hex.size()); +} + +void DBus::Log::writeHex(std::size_t type, const std::string& prefix, + const void *data, std::size_t size) +{ + if (data == nullptr || !isActive(type)) { return; } - write(type, prefix.c_str()); + std::size_t column = 0; + constexpr std::size_t maxColumns = 24; + constexpr std::size_t dividerAfter = 8; + char asciiBuf[maxColumns + maxColumns / dividerAfter + 1] = {}; + char *ascii = asciiBuf; + const unsigned char *first = static_cast(data); - size_t column = 0; - for (auto it : hex) { - write(type, "%.2x ", (uint8_t)it); - if (++column == 32) { - write(type, "\n"); + std::ostringstream oss; + oss << prefix; + for (std::size_t i = 0; i < size; ++i) { + const unsigned char byte = *(first + i); + oss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(byte) << ' '; + *ascii++ = ::isprint(byte) ? byte : '.'; + if (++column == maxColumns) { column = 0; - if (hex.size() % 32) { // pad the next line if there's likely to be one. - // i.e. not 32, 64, 96 length etc - write(type, std::string(prefix.length(), ' ').c_str()); // pad 2nd line to match prefix + oss << "| " << asciiBuf << '\n'; + std::memset(asciiBuf, 0, sizeof(asciiBuf)); + ascii = asciiBuf; + if (i + 1 < size) { // pad the next line if there is one. + oss << std::string(prefix.size(), ' '); } } + else if (column % dividerAfter == 0) { + oss << ' '; + *ascii++ = ' '; + } } - // Tidy up the last line - if (column) { - write(type, "\n"); + // Finish the last line + if (column > 0) { + const std::size_t bytes = (maxColumns - column) * 3; + const std::size_t dividers = ((maxColumns - 1) / dividerAfter) - (column / dividerAfter); + oss << std::string(bytes + dividers, ' ') << "| " << asciiBuf << '\n'; } + + write(type, oss.str().c_str()); } void DBus::Log::flush() { fflush(stderr); } diff --git a/src/dbus_log.h b/src/dbus_log.h index 34e1f40..11e3c53 100644 --- a/src/dbus_log.h +++ b/src/dbus_log.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace DBus { @@ -39,6 +41,8 @@ class Log { static void writeHex(size_t type, const std::string& prefix, const std::string& hex); + static void writeHex(std::size_t type, const std::string& prefix, + const void *data, std::size_t size); static void flush(); static void setLevel(size_t lowest_visible_level); diff --git a/src/dbus_matchrule.cpp b/src/dbus_matchrule.cpp index 38c828e..36dcbdc 100644 --- a/src/dbus_matchrule.cpp +++ b/src/dbus_matchrule.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,117 +16,148 @@ // file named COPYING. If you do not have this file see // . -#include -#include -#include -#include - -#include "dbus_log.h" #include "dbus_matchrule.h" -#include "dbus_message.h" +#include "dbus_type_objectpath.h" +#include "dbus_names.h" + +#include // https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules -DBus::MatchRule::MatchRule(const std::string& rule, - const Message::CallbackFunctionSignal& handler) +DBus::MatchRule& +DBus::MatchRule::type(Type type) { - // Parse the rule string into something programmatic - - // 1. Split by the comma - boost::char_separator separator { "," }; - boost::tokenizer> tokenizer { rule, separator }; - for (auto&& param : tokenizer) { - // 2. Then split each at the equals - std::vector keyvalue; - boost::split(keyvalue, param, boost::is_any_of("=")); - - // Valid keys are: type, sender, interface, member, path, path_namespace, - // destination, arg*, eavesdrop We're happy for [] to throw on malformed - // rules, since that's watch we'd do anyway in this case - - // 3. Strip the single quotes from the value - keyvalue[1].pop_back(); - keyvalue[1].erase(0, 1); - - // We store it somewhere convenient to speed the isMateched checks - if (keyvalue[0] == "type") { - type = keyvalue[1]; - } else if (keyvalue[0] == "sender") { - sender = keyvalue[1]; - } else if (keyvalue[0] == "interface") { - interface = keyvalue[1]; - } else if (keyvalue[0] == "member") { - member = keyvalue[1]; - } else if (keyvalue[0] == "path") { - path = keyvalue[1]; - } else if (keyvalue[0] == "path_namespace") { - path_namespace = keyvalue[1]; - } else if (keyvalue[0] == "destination") { - destination = keyvalue[1]; - } - - // TODO: eavesdrop - // TODO: arg - } + if (type == Type::MethodCall) + m_type = "method_call"; + else if (type == Type::MethodReturn) + m_type = "method_return"; + else if (type == Type::Signal) + m_type = "signal"; + else if (type == Type::Error) + m_type = "error"; + return *this; +} - // Using both path and path_namespace in the same match rule is not allowed. - if (path != "" && path_namespace != "") { - throw std::runtime_error( - "Match rules with both 'path' and 'path_namespace' are not allowed."); - } +DBus::MatchRule& +DBus::MatchRule::sender(const BusName& name) +{ + m_sender = name; + return *this; +} - // Finally, remember the callback - callback = handler; +DBus::MatchRule& +DBus::MatchRule::interface(const InterfaceName& name) +{ + m_interface = name; + return *this; } -bool DBus::MatchRule::isMatched(const DBus::Message::Signal& signal) +DBus::MatchRule& +DBus::MatchRule::member(const MemberName& name) { - // These checks are AND, so all set parameters must agree - if (sender != "" && sender != signal.getHeaderSender()) { - return false; - } + m_member = name; + return *this; +} - if (interface != "" && interface != signal.getHeaderInterface()) { - return false; - } +DBus::MatchRule& +DBus::MatchRule::path(const ObjectPath& name) +{ + if (!m_pathNamespace.empty()) + throw InvalidMatchRule( + "path and path_namespace are not allowed together."); + m_path = name; + return *this; +} - if (member != "" && member != signal.getHeaderMember()) { - return false; - } +DBus::MatchRule& +DBus::MatchRule::pathNamespace(const ObjectPath& name) +{ + if (!m_path.empty()) + throw InvalidMatchRule( + "path and path_namespace are not allowed together."); + m_pathNamespace = name; + return *this; +} - if (destination != "" && destination != signal.getHeaderDestination()) { - return false; - } +DBus::MatchRule& +DBus::MatchRule::destination(const UniqueName& name) +{ + m_destination = name; + return *this; +} - std::string signal_path(signal.getHeaderPath()); - if (path != "" && path != signal_path) { - return false; - } +DBus::MatchRule& +DBus::MatchRule::arg0Namespace(const NamespaceName& name) +{ + m_arg0Namespace = name; + return *this; +} - // For example, path_namespace='/com/example/foo' would match signals sent - // by /com/example/foo or by /com/example/foo/bar, but not by - // /com/example/foobar. - if (path_namespace != "") { - if (signal_path.find(path_namespace) != 0) { - return false; - } - - // This handles the 3rd case, by checking the character _after_ the match is - // found: if it's not the terminator or another path (i.e. a new /) the - // search fails. - auto next_char = signal_path.at(path_namespace.size()); - if (next_char != '\0' && next_char != '/') { - return false; - } - } +DBus::MatchRule& +DBus::MatchRule::arg(std::uint8_t index, const std::string& string) +{ + if (index > MaximumIndex) + throw InvalidMatchRule( + "arg index exceeds " + std::to_string(MaximumIndex)); + m_arg[index] = string; + escapeApostrophes(m_arg[index]); + return *this; +} - // TODO: eavesdrop - // TODO: arg +DBus::MatchRule& +DBus::MatchRule::argPath(std::uint8_t index, const std::string& string) +{ + if (index > MaximumIndex) + throw InvalidMatchRule( + "arg path index exceeds " + std::to_string(MaximumIndex)); + m_argPath[index] = string; + escapeApostrophes(m_argPath[index]); + return *this; +} - return true; +std::string DBus::MatchRule::str() const +{ + std::ostringstream matchRule; + + if (!m_type.empty()) + matchRule << ",type='" << m_type << "'"; + if (!m_sender.empty()) + matchRule << ",sender='" << m_sender << "'"; + if (!m_interface.empty()) + matchRule << ",interface='" << m_interface << "'"; + if (!m_member.empty()) + matchRule << ",member='" << m_member << "'"; + if (!m_path.empty()) + matchRule << ",path='" << m_path << "'"; + if (!m_pathNamespace.empty()) + matchRule << ",path_namespace='" << m_pathNamespace << "'"; + if (!m_destination.empty()) + matchRule << ",destination='" << m_destination << "'"; + if (!m_arg0Namespace.empty()) + matchRule << ",arg0namespace='" << m_arg0Namespace << "'"; + + for (auto it = m_arg.cbegin(); it != m_arg.cend(); ++it) + matchRule << ",arg" + std::to_string(it->first) + "='" << it->second << "'"; + + for (auto it = m_argPath.cbegin(); it != m_argPath.cend(); ++it) + matchRule << ",arg" + std::to_string(it->first) + "path='" << it->second << "'"; + + // Return empty string for match all rule + if (matchRule.tellp() == 0) + return ""; + + // Remove the leading comma from match rule + return matchRule.str().substr(1); } -void DBus::MatchRule::invoke(const DBus::Message::Signal& signal) +void DBus::MatchRule::escapeApostrophes(std::string& value) { - callback(signal); + const std::string what("'"); + const std::string with("'\\''"); + + std::size_t pos = 0; + while ((pos = value.find(what, pos)) != value.npos) { + value.replace(pos, what.size(), with); + pos += with.size(); + } } diff --git a/src/dbus_matchrule.h b/src/dbus_matchrule.h index 91786e6..e0429d8 100644 --- a/src/dbus_matchrule.h +++ b/src/dbus_matchrule.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,33 +16,58 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_MATCHRULE -#define DBUS_MATCHRULE +#pragma once -#include "dbus_message.h" +#include #include +#include namespace DBus { -class MatchRule { -public: - MatchRule(const std::string& rule, - const Message::CallbackFunctionSignal& handler); - - bool isMatched(const DBus::Message::Signal& signal); - void invoke(const DBus::Message::Signal& signal); - -protected: - std::string type; // NOTE: Only signals are currentl used/supported - std::string sender; - std::string interface; - std::string member; - std::string path; - std::string path_namespace; - std::string destination; - - Message::CallbackFunctionSignal callback; -}; -} // namespace DBus +// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules + +struct BusName; +struct UniqueName; +struct MemberName; +struct InterfaceName; +struct NamespaceName; + +class ObjectPath; + +using InvalidMatchRule = std::runtime_error; + + class MatchRule { + public: + static constexpr std::size_t MaximumIndex = 63; + enum class Type{ MethodCall, MethodReturn, Signal, Error }; + + MatchRule& type(Type type); + MatchRule& sender(const BusName& name); + MatchRule& interface(const InterfaceName& name); + MatchRule& member(const MemberName& name); + MatchRule& path(const ObjectPath& name); + MatchRule& pathNamespace(const ObjectPath& name); + MatchRule& destination(const UniqueName& name); + MatchRule& arg0Namespace(const NamespaceName& name); + MatchRule& arg(std::uint8_t index, const std::string& string); + MatchRule& argPath(std::uint8_t index, const std::string& string); -#endif // DBUS_MATCHRULE + std::string str() const; + + protected: + void escapeApostrophes(std::string& value); + + std::string m_type; + std::string m_sender; + std::string m_interface; + std::string m_member; + std::string m_path; + std::string m_pathNamespace; + std::string m_destination; + std::string m_arg0Namespace; + + std::map m_arg; + std::map m_argPath; + }; + +} // namespace DBus diff --git a/src/dbus_message.cpp b/src/dbus_message.cpp index 425264f..55f82b0 100644 --- a/src/dbus_message.cpp +++ b/src/dbus_message.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,8 +16,7 @@ // file named COPYING. If you do not have this file see // . -#include - +#include "dbus_type_any.h" #include "dbus_type_array.h" #include "dbus_type_byte.h" #include "dbus_type_signature.h" @@ -25,230 +25,494 @@ #include "dbus_type_uint32.h" #include "dbus_type_variant.h" +#include "dbus_log.h" +#include "dbus_names.h" #include "dbus_message.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" -uint32_t DBus::Message::Base::m_SerialCounter = 1; -boost::recursive_mutex DBus::Message::Base::m_SerialCounterMutex; +#include -DBus::Message::Base::Base(uint32_t serial) +// +// Message::Header::Fields +// +DBus::Message::Header::Fields::Fields(const Header& header) + : DBus::Type::Array("a(yv)") { - boost::recursive_mutex::scoped_lock guard(m_SerialCounterMutex); - m_Header.serial = serial ? serial : m_SerialCounter++; + // Check the required fields that could have been passed in empty + if (header.type != Message::Type::Signal && header.destination.empty()) + throw InvalidMessage("Message without destination"); + if (header.type == Message::Type::Signal) { + if (header.path.empty()) + throw InvalidMessage("Signal without ObjectPath"); + if (header.interface.empty()) + throw InvalidMessage("Signal without Interface"); + } + else if (header.type == Message::Type::MethodCall && header.path.empty()) + throw InvalidMessage("MethodCall without ObjectPath"); + + if (!header.destination.empty()) + add(Header::Field::Destination, header.destination); + if (!header.path.empty()) + add(Header::Field::Path, DBus::Type::ObjectPath(header.path)); + if (!header.interface.empty()) + add(Header::Field::Interface, header.interface); + if (!header.member.empty()) + add(Header::Field::Member, header.member); + if (!header.errorName.empty()) + add(Header::Field::ErrorName, header.errorName); + if (header.replySerial != 0) + add(Header::Field::ReplySerial, header.replySerial); + if (!header.sender.empty()) + add(Header::Field::Sender, header.sender); + if (!header.signature.empty()) + add(Header::Field::Signature, header.signature); + if (header.unixFds > 0) + add(Header::Field::UnixFds, header.unixFds); } -DBus::Message::Base::Base(const DBus::Type::Struct& header, - const std::string& body) +std::size_t +DBus::Message::Header::Fields::add(Field type, const DBus::Type::Any& value) { - // Capture the basic parameters - bool isLittleEndian = Type::asByte(header[0]) == 'l' ? true : false; - m_Header.type = Type::asByte(header[1]); - m_Header.flags = Type::asByte(header[2]); - m_Header.serial = Type::asUint32(header[5]); + DBus::Type::Struct field; + field.add(DBus::Type::Byte(type)); + field.add(DBus::Type::Variant(value)); + return DBus::Type::Array::add(field); +} - std::string signature; - const DBus::Type::Array& fields = DBus::Type::refArray(header[6]); - for (auto it : fields.getContents()) { - const DBus::Type::Struct& headerField = DBus::Type::refStruct(it); - uint8_t type = Type::asByte(headerField[0]); - switch (type) { - case Header::HEADER_PATH: - m_Header.path = DBus::Type::asString(headerField[1]); + + +// +// Message::Header +// +DBus::Message::Header::Header(OctetBuffer& message) +{ + size = getSize(message); + endianness = Message::endianness(message); + MessageIStream istream(message, swapRequired(endianness)); + + DBus::Type::Struct header("(yyyyuua(yv))"); + header.unmarshall(istream); + + // Capture the basic parameters + type = static_cast(DBus::Type::asByte(header[1])); + flags = DBus::Type::asByte(header[2]); + bodySize = DBus::Type::asUint32(header[4]); + serial = DBus::Type::asUint32(header[5]); + + for (auto& elem : DBus::Type::refArray(header[6])) { + const DBus::Type::Struct& field = DBus::Type::refStruct(elem); + Header::Field name = static_cast(DBus::Type::asByte(field[0])); + switch (name) { + case Header::Field::Path: + path = DBus::Type::asString(field[1]); break; - case Header::HEADER_INTERFACE: - m_Header.interface = DBus::Type::asString(headerField[1]); + case Header::Field::Interface: + interface = DBus::Type::asString(field[1]); break; - case Header::HEADER_MEMBER: - m_Header.member = DBus::Type::asString(headerField[1]); + case Header::Field::Member: + member = DBus::Type::asString(field[1]); break; - case Header::HEADER_DESTINATION: - m_Header.destination = DBus::Type::asString(headerField[1]); + case Header::Field::ErrorName: + errorName = DBus::Type::asString(field[1]); break; - case Header::HEADER_SENDER: - m_Header.sender = DBus::Type::asString(headerField[1]); + case Header::Field::ReplySerial: + replySerial = DBus::Type::asUint32(field[1]); break; - case Header::HEADER_SIGNATURE: - signature = DBus::Type::asString(headerField[1]); + case Header::Field::Destination: + destination = DBus::Type::asString(field[1]); break; - case Header::HEADER_REPLY_SERIAL: - m_Header.replySerial = DBus::Type::asUint32(headerField[1]); + case Header::Field::Sender: + sender = DBus::Type::asString(field[1]); + break; + case Header::Field::Signature: + signature = DBus::Type::refSignature(field[1]); + break; + case Header::Field::UnixFds: + unixFds = DBus::Type::asUint32(field[1]); break; - default: break; } } - // Empty bodies have no parameters - if (body.size()) { - parseParameters(isLittleEndian, body, signature); - } -} - -void DBus::Message::Base::parseParameters(bool isLittleEndian, - const std::string& bodydata, - const std::string& signature) -{ - DBus::Type::Struct parameter_fields; - - parameter_fields.setSignature("(" + signature + ")"); - - MessageIStream stream((uint8_t*)bodydata.data(), bodydata.size(), - isLittleEndian ? __BYTE_ORDER != __LITTLE_ENDIAN - : __BYTE_ORDER != __BIG_ENDIAN); - parameter_fields.unmarshall(stream); - - size_t count = parameter_fields.getEntries(); - for (size_t i = 0; i < count; ++i) { - m_Parameters.add(parameter_fields[i]); - } + message.remove_prefix(size); } -std::string -DBus::Message::Base::marshallMessage(const DBus::Type::Array& array) const +void DBus::Message::Header::marshall(MessageOStream& stream) { - MessageOStream header; - MessageOStream body; - - /* - The signature of the header is: - - "yyyyuua(yv)" - - Written out more readably, this is: - - BYTE, BYTE, BYTE, BYTE, UINT32, UINT32, ARRAY of STRUCT of - (BYTE,VARIANT) - */ + // The signature of the header is: + // "yyyyuua(yv)" + // + // Written out more readably, this is: + // BYTE, BYTE, BYTE, BYTE, UINT32, UINT32, ARRAY of STRUCT of (BYTE,VARIANT) // 1st BYTE // Endianness flag; ASCII 'l' for little-endian or ASCII 'B' for big-endian. // Both header and body are in this endianness. - header.writeByte(__BYTE_ORDER == __LITTLE_ENDIAN ? 'l' : 'B'); + stream.writeByte(__BYTE_ORDER == __LITTLE_ENDIAN ? 'l' : 'B'); // 2nd BYTE // Message type. Unknown types must be ignored. Currently-defined types are // described below. - header.writeByte(m_Header.type); + stream.writeByte(static_cast(type)); // 3rd BYTE // Bitwise OR of flags. Unknown flags must be ignored. Currently-defined flags // are described below. - header.writeByte(m_Header.flags); + stream.writeByte(flags); // 4th BYTE // Major protocol version of the sending application. If the major protocol // version of the receiving application does not match, the applications will // not be able to communicate and the D-Bus connection must be disconnected. // The major protocol version for this version of the specification is 1. - header.writeByte(1); + stream.writeByte(1); // 1st UINT32 // Length in bytes of the message body, starting from the end of the header. // The header ends after its alignment padding to an 8-boundary. - m_Parameters.marshallData(body); - - header.writeUint32(body.data.length()); + stream.writeUint32(bodySize); // 2nd UINT32 // The serial of this message, used as a cookie by the sender to identify the // reply corresponding to this request. This must not be zero. - header.writeUint32(m_Header.serial); + stream.writeUint32(serial); - // Header fields length - MessageOStream header_fields; + // ARRAY of STRUCT of(BYTE,VARIANT) i.e. the header fields + Fields headerFields(*this); + headerFields.marshall(stream); - // It appears, from studying the packet data, that the header field array is - // not marshalled with the length of the array data in bytes, as suggested in - // the spec. Presumably this is because the header fields size can be - // determined from the packet_size - body_size - standard_header_size - array.marshallContents(header_fields); - header.writeUint32((int32_t)header_fields.size()); + // Both header & header_fields constitute the header, which must end of an + // 8 byte boundary. (See the phrase: "The header ends after its alignment padding to + // an 8-boundary.") Therefore we add the padding here. + stream.pad8(); +} - // End of header preparation - // Both header & header_fields constitute the header, which must end of an 8 - // boundary. (See the phrase: "The header ends after its alignment padding to - // an 8-boundary.") Therefore we add the padding here. - MessageOStream packet; - packet.write(header); - packet.write(header_fields); +std::string +DBus::Message::Header::toString(const std::string& prefix) const +{ + std::ostringstream oss; + + oss << prefix << "Endianness: " << (endianness == Endian::Big ? "big" : "little") << '\n'; + oss << prefix << "Type: " << typeString(type) << '\n'; + oss << prefix << "Flags: " << flagString(flags) << '\n'; + oss << prefix << "Version: 1" << '\n'; + oss << prefix << "Body length: " << bodySize << '\n'; + oss << prefix << "Serial: #" << serial << '\n'; + + if (replySerial) + oss << prefix << "Reply Serial: #" << replySerial << '\n'; + if (!sender.empty()) + oss << prefix << "Sender: " << sender << '\n'; + if (!destination.empty()) + oss << prefix << "Destination: " << destination << '\n'; + if (!path.empty()) + oss << prefix << "Path: " << path << '\n'; + if (!interface.empty()) + oss << prefix << "Interface: " << interface << '\n'; + if (!member.empty()) + oss << prefix << "Member: " << member << '\n'; + if (!errorName.empty()) + oss << prefix << "Error Name: " << errorName << '\n'; + if (!signature.empty()) + oss << prefix << "Signature: " << signature.asString() << '\n'; + if (unixFds) + oss << prefix << "Unix FDs: " << unixFds << '\n'; + + return oss.str(); +} - // The body is required to start on an 8-boundary, but not end on one. - packet.pad8(); - packet.write(body); +std::size_t +DBus::Message::Header::getSize(const OctetBuffer& message) +{ + std::size_t size = Message::Header::MinimumSize; + if (message.size() < size) + return 0; // TODO: throw instead? + + // Add size of the header fields array + const Endian endianness = Message::endianness(message); + size += correctEndianess(endianness, *(uint32_t*)(message.data() + 12)); + // Add padding to an 8 byte boundary + size += (size % 8 == 0) ? 0 : 8 - (size % 8); + + if (size > Message::Header::MaximumSize) + throw std::out_of_range("DBus message error: maximum header size exceeded"); - return packet.data; + return size; } +std::size_t +DBus::Message::Header::getMessageSize(const OctetBuffer& message) +{ + if (message.size() < Message::Header::MinimumSize) + return 0; // TODO: throw instead? + + const Endian endianness = Message::endianness(message); + std::size_t body_size = correctEndianess(endianness, *(uint32_t*)(message.data() + 4)); + std::size_t msg_size = getSize(message) + body_size; + + if (msg_size > Message::MaximumSize) + throw std::out_of_range("DBus message error: maximum message size exceeded"); + + return msg_size; +} + + + // -// Parameters +// Message::Identifier +// +DBus::Message::Identifier::Identifier(const ObjectPath& path, const InterfaceName& interface, + const MemberName& member) + : path(path) + , interface(interface) + , member(member) +{} + + + +// +// Message::Parameters // std::string -DBus::Message::MethodCallParameters::getMarshallingSignature() const +DBus::Message::Parameters::getSignature() const { std::string signature; - for (auto it : m_Contents.m_TypeList) { - signature += DBus::Type::getMarshallingSignature(it); + for (const auto& type : m_parameters) { + signature += type.getSignature(); } return signature; } -void DBus::Message::MethodCallParameters::marshallData( +void DBus::Message::Parameters::marshall( MessageOStream& stream) const { - for (auto it : m_Contents.m_TypeList) { - DBus::Type::marshallData(it, stream); + for (const auto& type : m_parameters) { + type.marshall(stream); } } -size_t DBus::Message::MethodCallParameters::getParameterCount() const +size_t DBus::Message::Parameters::getParameterCount() const { - return m_Contents.m_TypeList.size(); + return m_parameters.size(); } -const DBus::Type::Generic& -DBus::Message::MethodCallParameters::getParameter(size_t idx) const +const DBus::Type::Any& +DBus::Message::Parameters::getParameter(size_t idx) const { - return m_Contents.m_TypeList[idx]; + return m_parameters[idx]; } -DBus::Message::MethodCallParametersIn::MethodCallParametersIn( - const Type::Generic& v) +std::string +DBus::Message::Parameters::toString(const std::string& prefix) const { - add(v); + std::ostringstream oss; + for (const auto& param : m_parameters) { + oss << prefix << param.toString(prefix); + } + return oss.str(); } -DBus::Message::MethodCallParametersIn::MethodCallParametersIn( - const std::string& v) +void DBus::Message::Parameters::add(const DBus::Type::Any& value) { - add(v); + m_parameters.push_back(value); } -DBus::Message::MethodCallParametersIn::MethodCallParametersIn( - const std::string& v1, uint32_t v2) + + +// +// Message +// +DBus::Message::Message(const Identifier& id, const Parameters& params) + : m_Parameters(params) +{ + m_Header.path = id.path; + m_Header.interface = id.interface; + m_Header.member = id.member; +} + +DBus::Message::Message(const Header& header, OctetBuffer& body) + : m_Header(header) +{ + // Empty bodies have no parameters + if (body.size()) { + unmarshallBody(header.endianness, header.signature, body); + } + if (Log::isActive(Log::TRACE)) { + std::string prefix = " "; + Log::write(Log::TRACE, "DBus :: Message : Header :\n%s", + header.toString(prefix).c_str()); + if (m_Header.bodySize) + Log::write(Log::TRACE, "DBus :: Message : Data :\n%s\n", + m_Parameters.toString(prefix).c_str()); + } +} + +void DBus::Message::unmarshallBody( + Endian endianness, + const DBus::Type::Signature& signature, + OctetBuffer& body) +{ + MessageIStream stream(body, swapRequired(endianness)); + DBus::Type::Struct parameter_fields(signature); + parameter_fields.unmarshall(stream); + + size_t count = parameter_fields.size(); + for (size_t i = 0; i < count; ++i) { + m_Parameters.add(parameter_fields[i]); + } +} + +DBus::MessageOStream +DBus::Message::marshall(std::uint32_t serial) const +{ + MessageOStream body; + m_Parameters.marshall(body); + + Header header = m_Header; + MessageOStream headerData; + header.serial = serial; + header.signature = m_Parameters.getSignature(); + header.bodySize = body.data.size(); + header.unixFds = body.fds.size(); + header.marshall(headerData); + + MessageOStream packet; + packet.write(headerData); + packet.write(body); + + if (Log::isActive(Log::TRACE)) { + std::string prefix = " "; + Log::write(Log::TRACE, "DBus :: Message : Header :\n%s", + header.toString(prefix).c_str()); + if (header.bodySize) + Log::write(Log::TRACE, "DBus :: Message : Data :\n%s", + m_Parameters.toString(prefix).c_str()); + } + + return packet; +} + + + +// +// Message::MethodCall +// +DBus::Message::MethodCall::MethodCall(const Message::Header& header, + OctetBuffer& body) + : Message(header, body) +{} + +DBus::Message::MethodCall::MethodCall( + const BusName& destination, + const Identifier& id, + const Parameters& params, + uint32_t flags) + : Message(id, params) +{ + + if (flags & Message::Flag::AllowInteractiveAuthorization) { + DBus::Log::write(Log::ERROR, + "DBus :: ALLOW_INTERACTIVE_AUTHORIZATION is not supported."); + flags &= ~Message::Flag::AllowInteractiveAuthorization; + } + // Ignore any extraneous flags, also. + flags &= Message::Flag::_Mask; + + m_Header.flags = flags; + m_Header.type = Message::Type::MethodCall; + m_Header.destination = destination; +} + + + +// +// Message::MethodReturn +// +DBus::Message::MethodReturn::MethodReturn( + const BusName& destination, uint32_t replySerial, + const Message::Parameters& params) +{ + m_Header.flags = Flag::NoReplyExpected; + m_Header.type = Message::Type::MethodReturn; + m_Header.destination = destination; + m_Header.replySerial = replySerial; + m_Parameters = params; +} + +DBus::Message::MethodReturn::MethodReturn(const Message::Header& header, + OctetBuffer& body) + : Message(header, body) +{} + + + +// +// Message::Error +// +DBus::Message::Error::Error( + const BusName& destination, + uint32_t replySerial, + const ErrorName& name, + const std::string& message) +{ + m_Header.flags = Flag::NoReplyExpected; + m_Header.type = Message::Type::Error; + m_Header.destination = destination; + m_Header.replySerial = replySerial; + m_Header.errorName = name; + m_Parameters.add(message); +} + +DBus::Message::Error::Error(const DBus::Message::Header& header, + OctetBuffer& body) + : Message(header, body) { - add(v1); - add(v2); + Log::write(Log::WARNING, + "DBus :: Error : received as reply to message #%d : %s\n", + getReplySerial(), getMessage().c_str()); } -void DBus::Message::MethodCallParametersIn::add(const Type::Generic& value) +std::string DBus::Message::Error::getName() const { - m_Contents.m_TypeList.push_back(value); + return getErrorName(); } -void DBus::Message::MethodCallParametersIn::add(uint8_t value) +std::string DBus::Message::Error::getMessage() const { - add(DBus::Type::Byte(value)); + if (m_Parameters.getParameterCount() == 0) + return ""; + return DBus::Type::asString(m_Parameters.getParameter(0)); } -void DBus::Message::MethodCallParametersIn::add(uint32_t value) + + +// +// Message::Signal +// +DBus::Message::Signal::Signal( + const Message::Identifier& id, + const Message::Parameters& params) + : Message(id, params) { - add(DBus::Type::Uint32(value)); + m_Header.flags = Flag::NoReplyExpected; + m_Header.type = Message::Type::Signal; } -void DBus::Message::MethodCallParametersIn::add(const std::string& value) +DBus::Message::Signal::Signal( + const BusName& destination, + const Message::Identifier& id, + const Message::Parameters& params) + : Signal(id, params) { - add(DBus::Type::String(value)); + m_Header.destination = destination; } + +DBus::Message::Signal::Signal(const Message::Header& header, + OctetBuffer& body) + : Message(header, body) +{} + + diff --git a/src/dbus_message.h b/src/dbus_message.h index 1c5e865..7e7ea9a 100644 --- a/src/dbus_message.h +++ b/src/dbus_message.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,243 +16,309 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_MESSAGE -#define DBUS_MESSAGE +#pragma once -#include "dbus_type.h" -#include +#include "dbus_type_array.h" +#include "dbus_octetbuffer.h" #include +#include + +#include namespace DBus { + +struct BusName; +struct ErrorName; +struct ObjectPath; +struct MemberName; +struct InterfaceName; class MessageOStream; -namespace Message { +using InvalidMessage = std::runtime_error; - class MethodCallIdentifier { - public: - MethodCallIdentifier(const std::string& object, const std::string& interface, - const std::string& method) - : m_Object(object) - , m_Interface(interface) - , m_Method(method) - { - } +class Message { +public: + static constexpr std::size_t MaximumSize = 134217728; /*128 MiB*/ - std::string m_Object; - std::string m_Interface; - std::string m_Method; + enum class Type { + Invalid = 0, + MethodCall = 1, + MethodReturn = 2, + Error = 3, + Signal = 4 }; - // + static std::string typeString(Type t) { + switch (t) { + case Type::MethodCall: return "MethodCall"; + case Type::MethodReturn: return "MethodReturn"; + case Type::Error: return "Error"; + case Type::Signal: return "Signal"; + default: return "INVALID"; + } + } + + class MethodCall; + class MethodReturn; + class Error; + class Signal; + + enum Flag { + None = 0, + NoReplyExpected = 0x01, + NoAutoStart = 0x02, + AllowInteractiveAuthorization = 0x04, + _Mask = 0x07, + }; - class MethodCallParameters { - public: - std::string getMarshallingSignature() const; - size_t getParameterCount() const; - void marshallData(MessageOStream& stream) const; + static std::string flagString(uint8_t flags) { + if (flags == 0) + return "None"; + std::ostringstream oss; + if (flags & NoReplyExpected) + oss << "NO_REPLY_EXPECTED "; + if (flags & NoAutoStart) + oss << "NO_AUTO_START "; + if (flags & AllowInteractiveAuthorization) + oss << "ALLOW_INTERACTIVE_AUTHORIZATION "; + return oss.str(); + } + + enum class Endian { Little, Big }; + + inline static Endian endianness(const OctetBuffer& message) + { + if (message[0] == 'l') + return Endian::Little; + else if (message[0] == 'B') + return Endian::Big; + else + throw std::runtime_error("message endian marker invalid"); + } + + inline static bool swapRequired(const Endian endian) + { + return (endian == Endian::Little && __BYTE_ORDER != __LITTLE_ENDIAN) + || (endian == Endian::Big && __BYTE_ORDER != __BIG_ENDIAN); + } + + inline static uint32_t correctEndianess(const Endian endian, uint32_t value) + { + return swapRequired(endian) ? bswap_32(value): value; + } + + struct Header { + // Initial header consists of byte, byte, byte, byte, uint32_t, uint32_t + // the next element is the size of the array of field info data + // making a total of 16 bytes + static constexpr std::size_t MinimumSize = 16; + static constexpr std::size_t MaximumSize = MinimumSize + DBus::Type::Array::MaximumSize; + + static std::size_t getSize(const OctetBuffer& message); + static std::size_t getMessageSize(const OctetBuffer& message); + + enum Field { + Invalid = 0, + // The object to send a call to, or the object a signal is emitted from. + Path = 1, + // The interface to invoke a method call on, or that a signal is emitted from. + Interface = 2, + // The member, either the method name or signal name. + Member = 3, + // The name of the error that occurred, for errors. + ErrorName = 4, + // The serial number of the message this message is a reply to. + ReplySerial = 5, + // The name of the connection this message is intended for. + Destination = 6, + // Unique name of the sending connection. + Sender = 7, + // The signature of the message body. + Signature = 8, + // The number of Unix file descriptors that accompany the message. + UnixFds = 9, + }; + + struct Fields : public DBus::Type::Array + { + Fields(const Header& header); + std::size_t add(Field type, const DBus::Type::Any& value); + }; - const Type::Generic& getParameter(size_t idx) const; + Header() = default; + Header(OctetBuffer& buffer); - protected: - Type::CompositeBlock m_Contents; - }; + void marshall(MessageOStream& stream); - class MethodCallParametersIn : public MethodCallParameters { - public: - // We have a series of ctor's to permit the basic prototypes to be declared - // inline, with MethodCallParametersIn("param") etc. Everyone else will need - // to use a temporary variable and add() - MethodCallParametersIn() {} - MethodCallParametersIn(const Type::Generic& v); - MethodCallParametersIn(const std::string& v); - MethodCallParametersIn(const std::string& v1, uint32_t v2); + std::string getFullName() const { + return interface + '.' + member; + } - void add(const Type::Generic& value); + std::string toString(const std::string& prefix = "") const; - void add(uint8_t value); - void add(uint32_t value); - void add(const std::string& value); - }; + std::size_t size = 0; - class Header { - public: - typedef enum { - TYPE_INVALID = 0, - TYPE_METHOD_CALL = 1, - TYPE_METHOD_RETURN = 2, - TYPE_ERROR = 3, - TYPE_SIGNAL = 4, - } Type; - - typedef enum { - FLAGS_NONE = 0, - FLAGS_NO_REPLY_EXPECTED = 0x01, - FLAGS_NO_AUTO_START = 0x02, - FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION = 0x04, - // - FLAGS_MASK = 0x07, - } Flags; - - enum { - HEADER_INVALID, - HEADER_PATH, // The object to send a call to, or the object a signal is - // emitted from. - HEADER_INTERFACE, // The interface to invoke a method call on, or that a - // signal is emitted from. Optional for method calls, - // required for signals. - HEADER_MEMBER, // The member, either the method name or signal name. - HEADER_ERROR_NAME, // The name of the error that occurred, for errors - HEADER_REPLY_SERIAL, // The serial number of the message this message is a - // reply to. - HEADER_DESTINATION, // The name of the connection this message is intended - // for. - HEADER_SENDER, // Unique name of the sending connection. - HEADER_SIGNATURE, // The signature of the message body. If omitted, it is - // assumed to be the empty signature "" (i.e. the body - // must be 0-length). - HEADER_UNIX_FDS, // The number of Unix file descriptors that accompany the - // message. - // - HEADER_FIELD_COUNT // = HEADER_UNIX_FDS+1 - } HeaderFields; - - uint8_t flags; - uint8_t type; - uint32_t serial; - uint32_t replySerial; + Endian endianness; + Type type = Type::Invalid; + uint8_t flags = 0; + uint32_t bodySize = 0; + uint32_t serial = 0; + // Header fields std::string path; std::string interface; std::string member; + std::string errorName; + uint32_t replySerial = 0; std::string destination; + DBus::Type::Signature signature; std::string sender; + uint32_t unixFds = 0; }; - class Base { - public: - Base(uint32_t serial = 0); - Base(const DBus::Type::Struct& header, const std::string& body); - - uint32_t getSerial() const { return m_Header.serial; } - - uint32_t getReplySerial() const { return m_Header.replySerial; } - - std::string getHeaderPath() const { return m_Header.path; } - - std::string getHeaderInterface() const { return m_Header.interface; } - - std::string getHeaderMember() const { return m_Header.member; } - - std::string getHeaderSender() const { return m_Header.sender; } - - std::string getHeaderDestination() const { return m_Header.destination; } - - const Type::Generic& getParameter(size_t idx) const - { - return m_Parameters.getParameter(idx); - } - - const size_t getParameterCount() const - { - return m_Parameters.getParameterCount(); - } - - const bool isReplyExpected() const - { - return !(m_Header.flags & DBus::Message::Header::FLAGS_NO_REPLY_EXPECTED) - ? true - : false; - } - - protected: - std::string marshallMessage(const DBus::Type::Array& header_fields) const; - - Header m_Header; - MethodCallParametersIn m_Parameters; - - private: - static uint32_t m_SerialCounter; - static boost::recursive_mutex m_SerialCounterMutex; + // Tuple of PATH, INTERFACE and MEMBER for MethodCall and Signal messages + struct Identifier { + Identifier(const ObjectPath& path, const InterfaceName& interface, + const MemberName& member); - void parseParameters(bool isLittleEndian, const std::string& bodydata, - const std::string& signature); - }; - - class MethodCall : public Message::Base { - public: - // This is for outgoing calls - MethodCall(const MethodCallIdentifier& name, - const MethodCallParametersIn& params = MethodCallParametersIn(), - uint32_t flags = 0); - // This is for receeiving method calls - MethodCall(const DBus::Type::Struct& header, const std::string& body); - - std::string getFullName() const; - std::string getObject() const; - std::string getInterface() const; - std::string getMethod() const; - - std::string marshall(const std::string& destination) const; - - private: - MethodCallIdentifier m_Name; + std::string path; + std::string interface; + std::string member; }; - class MethodReturn : public Message::Base { + class Parameters { public: - // This is for sending outgoing calls - MethodReturn(uint32_t serial); - // This is for receeiving method calls - MethodReturn(const DBus::Type::Struct& header, const std::string& body); + // We have a series of ctor's to permit the basic prototypes to be declared + // inline, with Parameters("param") etc. Everyone else will need + // to use a temporary variable and add() + Parameters() = default; + Parameters(Parameters&&) = default; + Parameters(const Parameters&) = default; - void addParameter(const Type::Generic& value); + template + Parameters(const Param&... param) + : m_parameters({ DBus::Type::Any(param)... }) + {} - std::string marshall(const std::string& destination) const; + Parameters& operator=(Parameters&&) = default; + Parameters& operator=(const Parameters&) = default; - uint32_t m_SerialReplyingTo; - }; + void add(const DBus::Type::Any& value); - class Error : public Message::Base { - public: - // This is for outgoing calls - Error(uint32_t serial, const std::string& name, const std::string& message); - // This is for receeiving method calls - Error(const DBus::Type::Struct& header, const std::string& body); + size_t getParameterCount() const; + const DBus::Type::Any& getParameter(size_t idx) const; - uint32_t getSerialOfReply() const; - std::string getMessage() const; + std::string getSignature() const; + void marshall(MessageOStream& stream) const; - std::string marshall(const std::string& destination) const; + std::string toString(const std::string& prefix = "") const; - private: - uint32_t m_SerialReplyingTo; // NOTE: Means the query one, on rcvr side - std::string m_Errorname; // e.g. biz.brightsign.Error.InvalidParameters - std::string m_Message; // i.e. the user text to display + protected: + std::vector m_parameters; }; - class Signal : public Base { - public: - // This is for outgoing calls - Signal(const MethodCallIdentifier& name); - // This is for receeiving method calls - Signal(const DBus::Type::Struct& header, const std::string& body); - - void addParameter(const Type::Generic& value); + Message() = default; + Message(const Message::Identifier& id, + const Message::Parameters& params = {}); + Message(const Header& header, OctetBuffer& body); + + explicit operator bool() const { return m_Header.type != Type::Invalid; } + + // Getters for header information + uint32_t getSerial() const { return m_Header.serial; } + uint32_t getReplySerial() const { return m_Header.replySerial; } + uint32_t getUnixFdCount() const { return m_Header.unixFds; } + std::string getPath() const { return m_Header.path; } + std::string getMember() const { return m_Header.member; } + std::string getSender() const { return m_Header.sender; } + std::string getSignature() const { return m_Header.signature.asString(); } + std::string getInterface() const { return m_Header.interface; } + std::string getErrorName() const { return m_Header.errorName; } + std::string getDestination() const { return m_Header.destination; } + + void addParameter(const DBus::Type::Any& value) + { + m_Parameters.add(value); + } + + const DBus::Type::Any& getParameter(size_t idx) const + { + return m_Parameters.getParameter(idx); + } + + size_t getParameterCount() const + { + return m_Parameters.getParameterCount(); + } + + bool isReplyExpected() const + { + return (m_Header.flags & Flag::NoReplyExpected) == 0; + } + + MessageOStream marshall(std::uint32_t serial) const; + +protected: + void unmarshallBody( + Endian endianness, + const DBus::Type::Signature& signature, + OctetBuffer& bodydata); + + Header m_Header; + Parameters m_Parameters; +}; + +class Message::MethodCall : public Message { +public: + MethodCall() = default; + // This is for outgoing method calls + MethodCall( + const BusName& destination, const Identifier& id, + const Parameters& params = Parameters(), uint32_t flags = 0); + // This is for receiving method calls + MethodCall(const Header& header, OctetBuffer& body); + + inline std::string getFullName() const { return m_Header.getFullName(); } + inline std::string getObject() const { return getPath(); } + inline std::string getMethod() const { return getMember(); } +}; + +class Message::MethodReturn : public Message { +public: + MethodReturn() = default; + // This is for sending outgoing replies + MethodReturn(const BusName& destination, uint32_t replySerial, + const Parameters& params = {}); + // This is for receiving method returns + MethodReturn(const Header& header, OctetBuffer& body); +}; + +class Message::Error : public Message { +public: + Error() = default; + // This is for outgoing Errors + Error(const BusName & destination, uint32_t replySerial, + const ErrorName& name, const std::string& message); + // This is for receiving Errors + Error(const Header& header, OctetBuffer& body); + + std::string getName() const; + std::string getMessage() const; +}; + +class Message::Signal : public Message { +public: + Signal() = default; + // This is for outgoing broadcast signals + Signal(const Identifier& id, + const Parameters& params = {}); + // This is for outgoing unicast signals + Signal(const BusName& destination, + const Identifier& id, + const Parameters& params = {}); + // This is for receiving signals + Signal(const Header& header, OctetBuffer& body); +}; - std::string marshall(const std::string& destination) const; - private: - MethodCallIdentifier m_SignalName; - }; - - typedef std::function - CallbackFunctionMethodCall; - typedef std::function - CallbackFunctionMethodReturn; - typedef std::function CallbackFunctionError; - typedef std::function - CallbackFunctionSignal; -} // namespace Message } // namespace DBus - -#endif // DBUS_MESSAGE diff --git a/src/dbus_message_error.cpp b/src/dbus_message_error.cpp deleted file mode 100644 index 211c5a2..0000000 --- a/src/dbus_message_error.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_log.h" - -#include "dbus_message.h" -#include "dbus_messageistream.h" -#include "dbus_type_array.h" -#include "dbus_type_byte.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_struct.h" -#include "dbus_type_uint32.h" -#include "dbus_type_variant.h" - -DBus::Message::Error::Error(uint32_t serial, const std::string& name, - const std::string& message) - : Message::Base() - , m_SerialReplyingTo(serial) - , m_Errorname(name) - , m_Message(message) -{ - m_Header.flags = 0; - m_Header.type = Message::Header::TYPE_ERROR; - // - m_Parameters.add(DBus::Type::String(m_Message)); -} - -DBus::Message::Error::Error(const DBus::Type::Struct& header, - const std::string& body) - : Message::Base(header, body) -{ - const bool isLittleEndian = Type::asByte(header[0]) == 'l' ? true : false; - - DBus::Type::Struct dest; - dest.setSignature("(s)"); - - MessageIStream stream((uint8_t*)body.data(), body.size(), - isLittleEndian ? __BYTE_ORDER != __LITTLE_ENDIAN - : __BYTE_ORDER != __BIG_ENDIAN); - dest.unmarshall(stream); - - const std::string m_Message = DBus::Type::asString(dest[0]); - m_SerialReplyingTo = getReplySerial(); - - Log::write(Log::WARNING, - "DBus :: Received an error from message serial #%d : %s\n", - m_SerialReplyingTo, m_Message.c_str()); -} - -uint32_t DBus::Message::Error::getSerialOfReply() const -{ - return m_SerialReplyingTo; -} - -std::string DBus::Message::Error::getMessage() const { return m_Message; } - -std::string -DBus::Message::Error::marshall(const std::string& destination) const -{ - DBus::Type::Array array; - DBus::Type::Struct sDestination; - sDestination.add(DBus::Type::Byte(DBus::Message::Header::HEADER_DESTINATION)); - sDestination.add(DBus::Type::Variant(DBus::Type::String(destination))); - array.add(sDestination); - - DBus::Type::Struct sErrorName; - sErrorName.add(DBus::Type::Byte(DBus::Message::Header::HEADER_ERROR_NAME)); - sErrorName.add(DBus::Type::Variant(DBus::Type::String(m_Errorname))); - array.add(sErrorName); - - DBus::Type::Struct sReplySerial; - sReplySerial.add( - DBus::Type::Byte(DBus::Message::Header::HEADER_REPLY_SERIAL)); - sReplySerial.add(DBus::Type::Variant(DBus::Type::Uint32(m_SerialReplyingTo))); - array.add(sReplySerial); - - if (m_Parameters.getParameterCount()) { - DBus::Type::Struct sSignature; - sSignature.add(DBus::Type::Byte(DBus::Message::Header::HEADER_SIGNATURE)); - sSignature.add(DBus::Type::Variant( - DBus::Type::Signature(m_Parameters.getMarshallingSignature()))); - array.add(sSignature); - } - - return marshallMessage(array); -} diff --git a/src/dbus_message_methodcall.cpp b/src/dbus_message_methodcall.cpp deleted file mode 100644 index 8f4408a..0000000 --- a/src/dbus_message_methodcall.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_log.h" -#include "dbus_type_array.h" -#include "dbus_type_byte.h" -#include "dbus_type_objectpath.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_struct.h" -#include "dbus_type_variant.h" - -#include "dbus_message.h" - -DBus::Message::MethodCall::MethodCall(const DBus::Type::Struct& header, - const std::string& body) - : Message::Base(header, body) - , m_Name(m_Header.path, m_Header.interface, m_Header.member) -{ - m_Header.flags = 0; - m_Header.type = Message::Header::TYPE_METHOD_CALL; -} - -DBus::Message::MethodCall::MethodCall(const Message::MethodCallIdentifier& name, - const MethodCallParametersIn& params, - uint32_t flags) - : Message::Base() - , m_Name(name) -{ - - if (flags & Message::Header::FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) { - DBus::Log::write( - Log::ERROR, - "DBus :: ALLOW_INTERACTIVE_AUTHORIZATION is not yet supported."); - flags &= ~Message::Header::FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION; - } - // Ignore any extraneous flags, also. - flags &= Message::Header::FLAGS_MASK; - - m_Header.flags = flags; - m_Header.type = Message::Header::TYPE_METHOD_CALL; - m_Parameters = params; -} - -std::string -DBus::Message::MethodCall::marshall(const std::string& destination) const -{ - - // ARRAY of STRUCT of (BYTE,VARIANT) - // An array of zero or more header fields where the byte is the field code, - // and the variant is the field m_Value. The message type determines which - // fields are required. - DBus::Type::Array array; - DBus::Type::Struct sPath; - sPath.add(DBus::Type::Byte(DBus::Message::Header::HEADER_PATH)); - sPath.add(DBus::Type::Variant(DBus::Type::ObjectPath(m_Name.m_Object))); - array.add(sPath); - - // This is necessary, to prevent errors from the daemon,although it is marked - // as optional in the spec - DBus::Type::Struct sDestination; - sDestination.add(DBus::Type::Byte(DBus::Message::Header::HEADER_DESTINATION)); - sDestination.add(DBus::Type::Variant(DBus::Type::String(destination))); - array.add(sDestination); - - DBus::Type::Struct sInterface; - sInterface.add(DBus::Type::Byte(DBus::Message::Header::HEADER_INTERFACE)); - sInterface.add(DBus::Type::Variant(DBus::Type::String(m_Name.m_Interface))); - array.add(sInterface); - - DBus::Type::Struct sMember; - sMember.add(DBus::Type::Byte(DBus::Message::Header::HEADER_MEMBER)); - sMember.add(DBus::Type::Variant(DBus::Type::String(m_Name.m_Method))); - array.add(sMember); - - if (m_Parameters.getParameterCount()) { - DBus::Type::Struct sSignature; - sSignature.add(DBus::Type::Byte(DBus::Message::Header::HEADER_SIGNATURE)); - sSignature.add(DBus::Type::Variant( - DBus::Type::Signature(m_Parameters.getMarshallingSignature()))); - array.add(sSignature); - } - - return marshallMessage(array); -} - -std::string DBus::Message::MethodCall::getFullName() const -{ - return getInterface() + "." + getMethod(); -} - -std::string DBus::Message::MethodCall::getObject() const -{ - return m_Name.m_Object; -} - -std::string DBus::Message::MethodCall::getInterface() const -{ - return m_Name.m_Interface; -} - -std::string DBus::Message::MethodCall::getMethod() const -{ - return m_Name.m_Method; -} diff --git a/src/dbus_message_methodreturn.cpp b/src/dbus_message_methodreturn.cpp deleted file mode 100644 index a30b3af..0000000 --- a/src/dbus_message_methodreturn.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_log.h" -#include "dbus_type_array.h" -#include "dbus_type_byte.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_struct.h" -#include "dbus_type_uint32.h" -#include "dbus_type_variant.h" - -#include "dbus_message.h" - -DBus::Message::MethodReturn::MethodReturn(uint32_t serial) - : Message::Base() - , m_SerialReplyingTo(serial) -{ - m_Header.flags = 0; - m_Header.type = Message::Header::TYPE_METHOD_RETURN; -} - -DBus::Message::MethodReturn::MethodReturn(const DBus::Type::Struct& header, - const std::string& body) - : Message::Base(header, body) -{ - - m_Header.flags = 0; - m_Header.type = Message::Header::TYPE_METHOD_RETURN; - m_SerialReplyingTo = getReplySerial(); - - Log::write(Log::TRACE, "DBus :: Received a method return with serial #%d\n", - m_SerialReplyingTo); -} - -void DBus::Message::MethodReturn::addParameter(const Type::Generic& value) -{ - m_Parameters.add(value); -} - -std::string -DBus::Message::MethodReturn::marshall(const std::string& destination) const -{ - DBus::Type::Array array; - DBus::Type::Struct sDestination; - sDestination.add(DBus::Type::Byte(DBus::Message::Header::HEADER_DESTINATION)); - sDestination.add(DBus::Type::Variant(DBus::Type::String(destination))); - array.add(sDestination); - - DBus::Type::Struct sReplySerial; - sReplySerial.add( - DBus::Type::Byte(DBus::Message::Header::HEADER_REPLY_SERIAL)); - sReplySerial.add(DBus::Type::Variant(DBus::Type::Uint32(m_SerialReplyingTo))); - array.add(sReplySerial); - - if (m_Parameters.getParameterCount()) { - DBus::Type::Struct sSignature; - sSignature.add(DBus::Type::Byte(DBus::Message::Header::HEADER_SIGNATURE)); - sSignature.add(DBus::Type::Variant( - DBus::Type::Signature(m_Parameters.getMarshallingSignature()))); - array.add(sSignature); - } - - return marshallMessage(array); -} diff --git a/src/dbus_message_signal.cpp b/src/dbus_message_signal.cpp deleted file mode 100644 index c3ce188..0000000 --- a/src/dbus_message_signal.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_log.h" -#include "dbus_type.h" -#include "dbus_type_array.h" -#include "dbus_type_byte.h" -#include "dbus_type_objectpath.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_struct.h" -#include "dbus_type_variant.h" - -#include "dbus_message.h" - -DBus::Message::Signal::Signal(const Message::MethodCallIdentifier& name) - : Message::Base() - , m_SignalName(name) -{ - m_Header.flags = 0; - m_Header.type = Message::Header::TYPE_SIGNAL; -} - -DBus::Message::Signal::Signal(const DBus::Type::Struct& header, - const std::string& body) - : Message::Base(header, body) - , m_SignalName(m_Header.path, m_Header.interface, m_Header.member) -{ - - if (Log::isActive(Log::TRACE)) { - std::string data; - std::string postfix; - - for (size_t i = 0; i < m_Parameters.getParameterCount(); ++i) { - data += postfix; - data += DBus::Type::toString(m_Parameters.getParameter(i)); - postfix = "\n"; - } - - Log::write(Log::TRACE, "DBus :: Signal : Header :\n%s", - DBus::Type::toString(header).c_str()); - Log::write(Log::TRACE, "DBus :: Signal : Data :\n%s\n", data.c_str()); - } -} - -void DBus::Message::Signal::addParameter(const Type::Generic& value) -{ - m_Parameters.add(value); -} - -std::string -DBus::Message::Signal::marshall(const std::string& destination) const -{ - DBus::Type::Array array; - DBus::Type::Struct sPath; - sPath.add(DBus::Type::Byte(DBus::Message::Header::HEADER_PATH)); - sPath.add(DBus::Type::Variant(DBus::Type::ObjectPath(m_SignalName.m_Object))); - array.add(sPath); - - if (destination.size()) { - // Unicast signal to a single destination - DBus::Type::Struct sDestination; - sDestination.add(DBus::Type::Byte(DBus::Message::Header::HEADER_DESTINATION)); - sDestination.add(DBus::Type::Variant(DBus::Type::String(destination))); - array.add(sDestination); - } - - DBus::Type::Struct sInterface; - sInterface.add(DBus::Type::Byte(DBus::Message::Header::HEADER_INTERFACE)); - sInterface.add( - DBus::Type::Variant(DBus::Type::String(m_SignalName.m_Interface))); - array.add(sInterface); - - DBus::Type::Struct sMember; - sMember.add(DBus::Type::Byte(DBus::Message::Header::HEADER_MEMBER)); - sMember.add(DBus::Type::Variant(DBus::Type::String(m_SignalName.m_Method))); - array.add(sMember); - - if (m_Parameters.getParameterCount()) { - DBus::Type::Struct sSignature; - sSignature.add(DBus::Type::Byte(DBus::Message::Header::HEADER_SIGNATURE)); - sSignature.add(DBus::Type::Variant( - DBus::Type::Signature(m_Parameters.getMarshallingSignature()))); - array.add(sSignature); - } - - return marshallMessage(array); -} diff --git a/src/dbus_messageistream.cpp b/src/dbus_messageistream.cpp index 9b2c48f..7c8a66c 100644 --- a/src/dbus_messageistream.cpp +++ b/src/dbus_messageistream.cpp @@ -23,16 +23,15 @@ namespace DBus { -MessageIStream::MessageIStream(const uint8_t* data, size_t size, - bool swapByteOrder) - : m_data(data, size) +MessageIStream::MessageIStream(OctetBuffer& data, bool swapByteOrder) + : m_data(data) , m_offset(0) , m_swapByteOrder(swapByteOrder) { } MessageIStream::MessageIStream(MessageIStream& stream, size_t size) - : m_data(stream.m_data.data(), size) + : m_data(stream.m_data, size) , m_offset(stream.m_offset) , m_swapByteOrder(stream.m_swapByteOrder) { @@ -85,6 +84,13 @@ void MessageIStream::read(std::string& string, size_t size) m_offset += size; } +int MessageIStream::readUnixFd() +{ + std::uint32_t index; + read(&index); + return m_data.getUnixFd(index); +} + bool MessageIStream::empty() { return m_data.empty(); } } // namespace DBus diff --git a/src/dbus_messageistream.h b/src/dbus_messageistream.h index eadaa79..67b4b0f 100644 --- a/src/dbus_messageistream.h +++ b/src/dbus_messageistream.h @@ -49,7 +49,7 @@ class MessageIStream { } public: - MessageIStream(const uint8_t* data, size_t size, bool swapByteOrder); + MessageIStream(OctetBuffer& data, bool swapByteOrder); MessageIStream(MessageIStream& stream, size_t size); bool empty(); @@ -58,6 +58,7 @@ class MessageIStream { void read(uint8_t* value, size_t size); void read(double* value); void read(std::string& string, size_t size); + int readUnixFd(); template void read(T* value) diff --git a/src/dbus_messageostream.h b/src/dbus_messageostream.h index d02a70c..6c8ca12 100644 --- a/src/dbus_messageostream.h +++ b/src/dbus_messageostream.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,17 +16,24 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_MESSAGEOSTREAM_H -#define DBUS_MESSAGEOSTREAM_H +#pragma once #include "dbus_utils.h" +#include "dbus_octetbuffer.h" // for UnixFdBuffer +#include #include +#include +#include +#include namespace DBus { class MessageOStream { public: + using Ptr = std::shared_ptr; + std::string data; + UnixFdBuffer fds; size_t size() const { return data.length(); } @@ -77,9 +85,12 @@ class MessageOStream { void write(const std::string& str) { data.append(str.data(), str.length()); } - void write(const MessageOStream& stream) + void write(const MessageOStream& other) { - data.append(stream.data.data(), stream.data.length()); + data.append(other.data.data(), other.data.length()); + if (fds.size()) + throw std::runtime_error("internal error: overwriting UnixFds"); + fds = other.fds; } void writeString(const std::string& str) @@ -107,6 +118,21 @@ class MessageOStream { writeByte(0); } + void writeUnixFd(int fd) + { + if (fds.size() > 253) + throw std::runtime_error("more than 253 UnixFds"); + fds.push_back(fd); + writeUint32(fds.size() - 1); + } + + void clearUnixFds() + { + for (auto fd : fds) + ::close(fd); + fds.clear(); + } + void pad(size_t padding) { data.append(DBus::Utils::getPadding(padding, data.length()), 0); @@ -118,6 +144,5 @@ class MessageOStream { void pad8() { pad(8); } }; -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_messageprotocol.cpp b/src/dbus_messageprotocol.cpp index c4ebdb1..cd7f9c0 100644 --- a/src/dbus_messageprotocol.cpp +++ b/src/dbus_messageprotocol.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,8 +16,6 @@ // file named COPYING. If you do not have this file see // . -#include - #include "dbus_log.h" #include "dbus_utils.h" @@ -29,223 +28,123 @@ #include "dbus_messageostream.h" #include "dbus_messageprotocol.h" -#include - -namespace { - -inline bool SwapRequired(uint8_t c) -{ - return (c == 'l' && __BYTE_ORDER != __LITTLE_ENDIAN) || (c == 'B' && __BYTE_ORDER != __BIG_ENDIAN); -} - -inline uint32_t CorrectEndianess(uint8_t c, uint32_t value) -{ - if (SwapRequired(c)) { - return bswap_32(value); - } - return value; -} - -static const size_t MAX_ARRAY_SIZE = 67108864; -static const size_t MAX_MESSAGE_SIZE = 134217728; - -} // namespace - -DBus::MessageProtocol::MessageProtocol() - : m_State(STATE_GETHEADERSIZE) - , m_headerSize(0) - , m_bodySize(0) -{ - setMethodCallHandler(std::bind(&MessageProtocol::onReceiveMethodCall, this, - std::placeholders::_1)); - setMethodReturnHandler(std::bind(&MessageProtocol::onReceiveMethodReturn, - this, std::placeholders::_1)); - setErrorHandler( - std::bind(&MessageProtocol::onReceiveError, this, std::placeholders::_1)); - setSignalHandler(std::bind(&MessageProtocol::onReceiveSignal, this, - std::placeholders::_1)); - - reset(); -} - -void DBus::MessageProtocol::reset() { startMessage(); } - -void DBus::MessageProtocol::setMethodCallHandler( - const DBus::Message::CallbackFunctionMethodCall& callback) -{ - std::lock_guard lock(m_CallbackMutex); - m_MethodCallCallback = callback; -} - -void DBus::MessageProtocol::setMethodReturnHandler( - const DBus::Message::CallbackFunctionMethodReturn& callback) -{ - std::lock_guard lock(m_CallbackMutex); - m_MethodReturnCallback = callback; -} +#include -void DBus::MessageProtocol::setErrorHandler( - const DBus::Message::CallbackFunctionError& callback) +DBus::MessageProtocol::Ptr +DBus::MessageProtocol::start(asio::io_context& ioContext, Transport::Ptr transport) { - std::lock_guard lock(m_CallbackMutex); - m_ErrorCallback = callback; + struct ShareableMsgProto : public MessageProtocol { + ShareableMsgProto(asio::io_context& ioContext, Transport::Ptr transport) + : MessageProtocol(ioContext, transport) {}; + }; + auto obj = std::make_shared(ioContext, transport); + obj->asyncReadMessage(ReadState::Peek); + return obj; } -void DBus::MessageProtocol::setSignalHandler( - const DBus::Message::CallbackFunctionSignal& callback) +DBus::MessageProtocol::MessageProtocol(asio::io_context& ioContext, Transport::Ptr transport) + : m_ioContext(ioContext) + , m_transport(transport) { - std::lock_guard lock(m_CallbackMutex); - m_SignalCallback = callback; + m_messageBuffer.reserve(256); } -void DBus::MessageProtocol::startMessage() +DBus::MessageProtocol::ReadBuffer +DBus::MessageProtocol::makeReadBuffer(ReadState state, MessageBuffer& buffer) { - m_State = STATE_GETHEADERSIZE; - - m_HeaderStruct.clear(); - m_HeaderStruct.setSignature("(yyyyuua(yv))"); - m_headerSize = 0; - m_bodySize = 0; -} - -bool DBus::MessageProtocol::getHeaderSize(OctetBuffer& buffer) -{ - // Initial header consists of byte, byte, byte, byte, uint32_t, uint32_t - // the next element is the size of the array of field info data - // making a total of 16 bytes - if (buffer.size() >= 16) { - // Read the size of the array - m_headerSize = *(uint32_t*)(buffer.data() + 12); - m_headerSize = CorrectEndianess(buffer[0], m_headerSize); - if (m_headerSize > MAX_ARRAY_SIZE) { - throw std::out_of_range("DBus message error: Maximum size exceeded"); - } - - // Add the 16 bytes of the header - m_headerSize += 16; - // The header MUST finish on an 8 byte boundary - m_headerSize += (m_headerSize % 8 == 0) ? 0 : 8 - (m_headerSize % 8); - - m_State = STATE_UNMARSHALLHEADER; - return true; - } - return false; -} - -bool DBus::MessageProtocol::unmarshallHeader(OctetBuffer& buffer) -{ - if (buffer.size() >= m_headerSize) { - // When all of the header is buffered the header can be unmarshalled - MessageIStream istream(buffer.data(), m_headerSize, - SwapRequired(buffer[0])); - m_HeaderStruct.unmarshall(istream); - buffer.remove_prefix(m_headerSize); - // The body of the message is next - m_bodySize = Type::asUint32(m_HeaderStruct[4]); - - if ((m_headerSize + m_bodySize) > MAX_MESSAGE_SIZE) { - throw std::out_of_range("DBus message error: Maximum size exceeded"); - } - - m_State = STATE_GETBODY; - return true; - } - return false; -} - -bool DBus::MessageProtocol::getBody(OctetBuffer& buffer) -{ - if (buffer.size() >= m_bodySize) { - std::string body((const char*)buffer.data(), m_bodySize); - buffer.remove_prefix(m_bodySize); - onBodyComplete(body); - return true; + if (state == ReadState::Peek) { + buffer.resize(Message::Header::MinimumSize); } - return false; -} - -void DBus::MessageProtocol::processData(OctetBuffer& buffer) -{ - while (buffer.size()) { - if (m_State == STATE_GETHEADERSIZE) { - if (!getHeaderSize(buffer)) { - return; - } - } - - if (m_State == STATE_UNMARSHALLHEADER) { - if (!unmarshallHeader(buffer)) { - return; - } - } - - if (m_State == STATE_GETBODY) { - if (!getBody(buffer)) { - return; - } - } + else { // ReadState::Receive + OctetBuffer header = { buffer.data(), buffer.size() }; + buffer.resize(Message::Header::getMessageSize(header)); } + return { buffer.data(), buffer.size() }; } -void DBus::MessageProtocol::onReceiveData(OctetBuffer& buffer) +void DBus::MessageProtocol::asyncReadMessage(ReadState state) { - // If a whole message is contained in the buffer - // it can be processed without copying but only if - // there is no data already cached - if (m_octetCache.empty()) { - processData(buffer); - } - - // Append any remaining data to the data cache - m_octetCache.append(buffer.data(), buffer.size()); - buffer.remove_prefix(buffer.size()); - - // If the data cache is not empty process it - if (!m_octetCache.empty()) { - OctetBuffer cachedBuffer(m_octetCache.data(), m_octetCache.size()); - processData(cachedBuffer); - m_octetCache.erase(0, m_octetCache.size() - cachedBuffer.size()); + if (state == ReadState::Peek) { + m_transport->asyncPeek( + makeReadBuffer(state, m_messageBuffer), + [self = shared_from_this()] + (const error_code& error, std::size_t bytes_read) + { + if (error) + return self->invokeErrorHandler(0, error); + if (bytes_read == 0) + return self->releasePendingHandlers(); + if (bytes_read < Message::Header::MinimumSize) + return self->invokeErrorHandler(0, { + "short read peeking header", + "message protocol :: read message" }); + + self->asyncReadMessage(ReadState::Receive); + }); + } else { + m_transport->asyncRead( + makeReadBuffer(state, m_messageBuffer), + m_unixFdBuffer, + [self = shared_from_this()] + (error_code error, std::size_t bytes_read) mutable + { + if (error) + return self->invokeErrorHandler(0, error); + if (bytes_read == 0) + return self->releasePendingHandlers(); + + MessageBuffer& msg = self->m_messageBuffer; + UnixFdBuffer& fds = self->m_unixFdBuffer; + OctetBuffer message(msg.data(), msg.size(), fds); + + if (bytes_read < Message::Header::MinimumSize || + bytes_read < Message::Header::getMessageSize(message)) + return self->invokeErrorHandler(0, { + "short read receiving message", + "message protocol :: read message" }); + + Log::write(Log::TRACE, "\nDBus :: Receive : Message Data : %ld bytes, %ld FDs\n", + msg.size(), fds.size()); + Log::writeHex(Log::TRACE, " ", msg.data(), msg.size()); + self->dispatchMessage(message); + + msg.clear(); + fds.clear(); + self->asyncReadMessage(ReadState::Peek); + }); } } -void DBus::MessageProtocol::onBodyComplete(const std::string& body) +void DBus::MessageProtocol::dispatchMessage(OctetBuffer& message) { - Log::write(Log::TRACE, "DBus :: Unmarshall : Body complete.\n"); - - try - { - uint8_t type = Type::asByte(m_HeaderStruct[1]); - std::lock_guard lock(m_CallbackMutex); - switch (type) { - case TYPE_METHOD: - m_MethodCallCallback(Message::MethodCall(m_HeaderStruct, body)); - break; - - case TYPE_METHOD_RETURN: - m_MethodReturnCallback(Message::MethodReturn(m_HeaderStruct, body)); - break; - - case TYPE_ERROR: - m_ErrorCallback(Message::Error(m_HeaderStruct, body)); - break; - - case TYPE_SIGNAL: - m_SignalCallback(Message::Signal(m_HeaderStruct, body)); - break; - - default: - Log::write(Log::WARNING, "DBus :: Unknown message type %d received\n", - type); - } - Log::write(Log::TRACE, - "DBus :: Message type %d dispatched : new message initialized\n", - type); + m_stats.bytes_recv += message.size(); + const Message::Header header(message); + Log::write(Log::TRACE, "DBus :: Recv : dispatching %s message\n", + Message::typeString(header.type).c_str()); + + switch (header.type) { + case Message::Type::MethodCall: + ++m_stats.count_recv_methodcalls; + invokeMethodCallHandler(header.getFullName(), Message::MethodCall(header, message)); + break; + + case Message::Type::MethodReturn: + ++m_stats.count_recv_methodreturns; + invokeMethodReturnHandler(header.replySerial, Message::MethodReturn(header, message)); + break; + + case Message::Type::Signal: + ++m_stats.count_recv_signals; + invokeSignalHandler(header.getFullName(), Message::Signal(header, message)); + break; + + case Message::Type::Error: + ++m_stats.count_recv_errors; + invokeErrorHandler(header.replySerial, Message::Error(header, message)); + break; + + default: + Log::write(Log::WARNING, "DBus :: Ignoring unknown message type %d\n", + static_cast>(header.type)); } - catch(const std::exception & e) - { - // Catch exceptions from std::function such as bad_function_call. No action required. - Log::write(Log::INFO, "DBus :: Exception caught in onBodyComplete: %s\n", e.what()); - } - - startMessage(); } diff --git a/src/dbus_messageprotocol.h b/src/dbus_messageprotocol.h index fea8128..40d69b9 100644 --- a/src/dbus_messageprotocol.h +++ b/src/dbus_messageprotocol.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,71 +16,384 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_MESSAGEPROTOCOL -#define DBUS_MESSAGEPROTOCOL +#pragma once -#include +#include "dbus_asio.h" +#include "dbus_error.h" #include "dbus_message.h" -#include "dbus_octetbuffer.h" +#include "dbus_messageostream.h" #include "dbus_type_struct.h" +#include "dbus_transport.h" + +#include +#include namespace DBus { -class MessageProtocol { +using MessageBuffer = std::vector; + +class MessageProtocol : public std::enable_shared_from_this { public: - MessageProtocol(); - - void reset(); - void - setMethodCallHandler(const Message::CallbackFunctionMethodCall& callback); - void - setMethodReturnHandler(const Message::CallbackFunctionMethodReturn& callback); - void setErrorHandler(const Message::CallbackFunctionError& callback); - void setSignalHandler(const Message::CallbackFunctionSignal& callback); - - void onReceiveData(OctetBuffer& buffer); - void onBodyComplete(const std::string& body); - -private: - enum { STATE_GETHEADERSIZE, - STATE_UNMARSHALLHEADER, - STATE_GETBODY }; - - enum { - TYPE_METHOD = 1, - TYPE_METHOD_RETURN = 2, - TYPE_ERROR = 3, - TYPE_SIGNAL = 4 + using Ptr = std::shared_ptr; + + struct Statistics { + std::size_t count_send_errors; + std::size_t count_send_signals; + std::size_t count_send_methodcalls; + std::size_t count_send_methodreturns; + std::size_t count_recv_errors; + std::size_t count_recv_signals; + std::size_t count_recv_methodcalls; + std::size_t count_recv_methodreturns; + std::size_t bytes_send; + std::size_t bytes_recv; }; - size_t m_State; - size_t m_headerSize; - size_t m_bodySize; - // TODO:?? Move the handler into a separate message class? We can't get 2 - // interspersed message so it's a 1:1 relationship between protocol handler - // and its message, but it might be useful elsewhere. - DBus::Type::Struct m_HeaderStruct; - std::basic_string m_octetCache; - - // - std::recursive_mutex m_CallbackMutex; - Message::CallbackFunctionMethodCall m_MethodCallCallback; - Message::CallbackFunctionMethodReturn m_MethodReturnCallback; - Message::CallbackFunctionError m_ErrorCallback; - Message::CallbackFunctionSignal m_SignalCallback; - - void startMessage(); - - bool getHeaderSize(OctetBuffer& buffer); - bool unmarshallHeader(OctetBuffer& buffer); - bool getBody(OctetBuffer& buffer); - void processData(OctetBuffer& buffer); - - void onReceiveMethodCall(const DBus::Message::MethodCall& result) {} - void onReceiveMethodReturn(const DBus::Message::MethodReturn& result) {} - void onReceiveError(const DBus::Message::Error& result) {} - void onReceiveSignal(const DBus::Message::Signal& result) {} + static Ptr start(asio::io_context& ioContext, Transport::Ptr transport); + + void stop() + { + stopping = true; + m_transport->disconnect(); + } + + template + auto sendMethodCall( + std::uint32_t serial, + const Message::MethodCall& methodCall, + CompletionToken&& token) + { + Log::write(Log::TRACE, "DBus :: Send : preparing METHOD_CALL message\n"); + auto replySerial = methodCall.isReplyExpected() ? serial : 0; + + return asio::async_initiate< + CompletionToken, void(const Error&, const Message::MethodReturn&)>( + [this](auto&& handler, MessageOStream&& data, uint32_t replySerial) mutable + { + ++m_stats.count_send_methodcalls; + m_stats.bytes_send += data.size(); + auto payload = std::make_shared(std::move(data)); + + // Store handler if there is a reply expected + if (replySerial) { + storeMethodReturnHandler(replySerial, std::move(handler)); + m_transport->asyncWrite( + payload, + [self = shared_from_this(), replySerial, payload] + (const error_code& error, std::size_t) mutable -> void + { + if (error) + self->invokeErrorHandler(replySerial, error); + }); + } else { + m_transport->asyncWrite( + payload, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error, std::size_t) mutable -> void + { + handler(error, Message::MethodReturn()); + }); + } + }, + token, methodCall.marshall(serial), replySerial); + } + + template + auto sendMethodReturn( + std::uint32_t serial, + const Message::MethodReturn& methodReturn, + CompletionToken&& token) + { + Log::write(Log::TRACE, "DBus :: Send : preparing METHOD_RETURN message\n"); + return asio::async_initiate< + CompletionToken, void(const Error&)>( + [this](auto&& handler, MessageOStream&& data) { + ++m_stats.count_send_methodreturns; + m_stats.bytes_send += data.size(); + auto payload = std::make_shared(std::move(data)); + m_transport->asyncWrite( + payload, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error, std::size_t) mutable -> void + { + handler(error); + }); + }, token, methodReturn.marshall(serial)); + } + + template + auto sendSignal( + std::uint32_t serial, + const Message::Signal& signal, + CompletionToken&& token) + { + Log::write(Log::TRACE, "DBus :: Send : preparing SIGNAL message\n"); + return asio::async_initiate< + CompletionToken, void(const Error&)>( + [this](auto&& handler, MessageOStream&& data) { + ++m_stats.count_send_signals; + m_stats.bytes_send += data.size(); + auto payload = std::make_shared(std::move(data)); + m_transport->asyncWrite( + payload, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error, std::size_t) mutable -> void + { + handler(error); + }); + }, token, signal.marshall(serial)); + } + + template + auto sendError( + std::uint32_t serial, + const Message::Error& error, + CompletionToken&& token) + { + Log::write(Log::TRACE, "DBus :: Send : preparing ERROR message\n"); + return asio::async_initiate< + CompletionToken, void(const Error&)>( + [this](auto&& handler, MessageOStream&& data) { + ++m_stats.count_send_errors; + m_stats.bytes_send += data.size(); + auto payload = std::make_shared(std::move(data)); + m_transport->asyncWrite( + payload, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error, std::size_t) mutable -> void + { + handler(error); + }); + }, token, error.marshall(serial)); + } + + + template + auto receiveMethodCall( + const std::string& interface, + CompletionToken&& token) + { + return asio::async_initiate< + CompletionToken, void(const Message::MethodCall&)>( + [this](auto&& handler, const std::string& interface) { + this->storeMethodCallHandler(interface, std::forward(handler)); + }, token, interface); + } + + template + auto receiveSignal( + const std::string& signal, + CompletionToken&& token) + { + return asio::async_initiate< + CompletionToken, void(const Message::Signal&)>( + [this](auto&& handler, const std::string& signal) { + this->storeSignalHandler(signal, std::forward(handler)); + }, token, signal); + } + + bool cancelReceiveSignal(const std::string& signal) + { + // Invoke handler for this exact signal + auto it = m_signalHandlers.find(signal); + if (it != m_signalHandlers.end()) { + m_signalHandlers.erase(it); + return true; + } + return false; + } + + template + auto receiveError(CompletionToken&& token) + { + return asio::async_initiate< + CompletionToken, void(const Error&)>( + [this](auto&& handler) { + m_errorHandler = StoredErrorHandler::create( + m_ioContext, std::move(handler)); + }, token); + } + + struct Statistics getStats() { return m_stats; } + +protected: + MessageProtocol(asio::io_context& ioContext, Transport::Ptr transport); + + template + bool storeMethodReturnHandler(uint32_t serial, Handler&& handler) + { + const bool ok = m_methodReturnHandlers.emplace( + std::make_pair( + serial, + StoredMethodReturnHandler::create( + m_ioContext, + std::move(handler)))).second; + if (!ok) + Log::write(Log::ERROR, "DBus :: METHOD RETURN : could not store handler\n"); + return ok; + } + + template + bool storeMethodCallHandler(const std::string& interface, Handler&& handler) + { + const bool ok = m_methodCallHandlers.emplace( + std::make_pair( + interface, + StoredMethodCallHandler::create( + m_ioContext, + std::move(handler)))).second; + if (!ok) + Log::write(Log::ERROR, "DBus :: METHOD CALL : could not store handler\n"); + return ok; + } + + template + bool storeSignalHandler(const std::string& signal, Handler&& handler) + { + const bool ok = m_signalHandlers.emplace( + std::make_pair( + signal, + StoredSignalHandler::create( + m_ioContext, + std::move(handler)))).second; + if (!ok) + Log::write(Log::ERROR, "DBus :: SIGNAL : could not store handler\n"); + return ok; + } + + void invokeMethodReturnHandler( + uint32_t replySerial, + const Message::MethodReturn& methodReturn) + { + const auto it = m_methodReturnHandlers.find(replySerial); + if (it != m_methodReturnHandlers.end()) { + it->second->invoke({}, methodReturn); + m_methodReturnHandlers.erase(it); + return; + } + + Log::write(Log::WARNING, "DBus :: METHOD RETURN : unexpected : " \ + "reply to #%u\n", replySerial); + } + + void invokeMethodCallHandler( + const std::string& interface, + const Message::MethodCall& methodCall) + { + // Invoke handler for this exact interface + auto it = m_methodCallHandlers.find(interface); + if (it != m_methodCallHandlers.end()) { + it->second->invoke(methodCall); + m_methodCallHandlers.erase(it); + return; + } + + // Invoke handler for any method of this interface + const std::string any_method(interface, 0, interface.rfind('.')); + it = m_methodCallHandlers.find(any_method); + if (it != m_methodCallHandlers.end()) { + it->second->invoke(methodCall); + m_methodCallHandlers.erase(it); + return; + } + + // Invoke handler for any interface (catch all) + it = m_methodCallHandlers.find(""); + if (it != m_methodCallHandlers.end()) { + it->second->invoke(methodCall); + m_methodCallHandlers.erase(it); + return; + } + + Log::write(Log::INFO, "DBus :: METHOD CALL : unhandled : %s\n", + interface.c_str()); + } + + void invokeSignalHandler( + const std::string& name, + const Message::Signal& signal) + { + // Invoke handler for this exact signal + auto it = m_signalHandlers.find(name); + if (it != m_signalHandlers.end()) { + it->second->invoke(signal); + m_signalHandlers.erase(it); + return; + } + + // Invoke handler for any signal (catch all) + it = m_signalHandlers.find(""); + if (it != m_signalHandlers.end()) { + it->second->invoke(signal); + m_signalHandlers.erase(it); + return; + } + + Log::write(Log::INFO, "DBus :: SIGNAL : %s unhandled\n", + name.c_str()); + } + + void invokeErrorHandler( + uint32_t replySerial, + const Error& error) + { + if (replySerial) { + const auto it = m_methodReturnHandlers.find(replySerial); + if (it != m_methodReturnHandlers.end()) { + it->second->invoke(error, {}); + m_methodReturnHandlers.erase(it); + return; + } + } + + if (m_errorHandler) { + m_errorHandler->invoke(error); + m_errorHandler.reset(); + return; + } + + Log::write(Log::WARNING, "DBus :: ERROR : unhandled : %s " \ + "(reply to #%u)\n", error.message.c_str(), replySerial); + } + + void releasePendingHandlers() + { + for (auto& elem : m_methodReturnHandlers) + elem.second->invoke({}, {}); + for (auto& elem : m_methodCallHandlers) + elem.second->invoke({}); + for (auto& elem : m_signalHandlers) + elem.second->invoke({}); + if (m_errorHandler) + m_errorHandler->invoke({}); + } + + using StoredMethodReturnHandler = StoredToken; + using StoredMethodCallHandler = StoredToken; + using StoredSignalHandler = StoredToken; + using StoredErrorHandler = StoredToken; + + std::map m_methodReturnHandlers; + std::map m_methodCallHandlers; + std::map m_signalHandlers; + StoredErrorHandler::Ptr m_errorHandler; + + using ReadBuffer = asio::mutable_buffer; + + enum class ReadState { Peek, Receive }; + ReadBuffer makeReadBuffer(ReadState state, MessageBuffer& buffer); + + void asyncReadMessage(ReadState state); + void dispatchMessage(OctetBuffer& message); + + bool stopping = false; + MessageBuffer m_messageBuffer; + UnixFdBuffer m_unixFdBuffer; + asio::io_context& m_ioContext; + Transport::Ptr m_transport; + + struct Statistics m_stats = {}; }; + } // namespace DBus -#endif // DBUS_MESSAGEPROTOCOL diff --git a/src/dbus_names.cpp b/src/dbus_names.cpp new file mode 100644 index 0000000..0509c1b --- /dev/null +++ b/src/dbus_names.cpp @@ -0,0 +1,227 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#include "dbus_names.h" + +static constexpr std::array nameStr = { + "bus name", + "unique connection name", + "well-known name", + "error name", + "interface name", + "namespace name", + "member name" +}; + +DBus::Name::operator const char*() const +{ + return m_name.c_str(); +} + +DBus::Name::operator bool() const +{ + return !m_name.empty(); +} + +// Restrictions that apply to names in general: +// +// * Each name must only contain the ASCII characters +// "[A-Z][a-z][0-9]_" and must not begin with a digit. +// * Names must not exceed the maximum name length of 255. +// * Names must contain at least one character. +// +// Additions for interface, error and bus name: +// +// * Names are composed of 2 or more elements separated by a +// period ('.') character. +// * All elements must contain at least one character. +// +// Additions for bus names: +// +// * Bus names that start with a colon (':') character are unique +// connection names. Other bus names are called well-known bus names. +// * Elements that are part of a unique connection name may +// begin with a digit. +// * Elements can also contain the hyphen character ('-'), while it +// being discouraged in new bus names. +void +DBus::Name::validate(const std::string& name, Type type) const +{ + if (name.empty()) + throw InvalidName("name is empty"); + if (name.size() > MaximumSize) + throw InvalidName(name.substr(0, 16) + "... exceeds " + + std::to_string(MaximumSize) + " characters"); + + bool isUnique = false; + if (name[0] == ':') { + if (type != BusName && type != UniqueName) + throw InvalidName(name + " is not a " + nameStr[type]); + isUnique = true; + } else if (type == UniqueName) { + throw InvalidName(name + " is not a " + nameStr[type]); + } + + const bool allowHyphen = + type == BusName || type == UniqueName || type == WellKnownName; + const bool needPeriod = type != MemberName && type != NamespaceName; + const bool allowPeriod = type != MemberName; + bool havePeriod = false; + + char prev = '.'; + for (int i = isUnique ? 1 : 0; i < name.size(); ++i) { + const char ch = name[i]; + if (allowPeriod && ch == '.') { + if (prev == '.') + throw InvalidName(name +" has empty element"); + havePeriod = true; + } + + if (!isUnique && prev == '.' && (ch >= '0' && ch <= '9')) { + const char *element = allowPeriod ? " element" : ""; + throw InvalidName(name + element + " starts with digit"); + } + + if ((allowHyphen && ch == '-') || (allowPeriod && ch == '.') || + ch == '_' || (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) + prev = ch; + else + throw InvalidName(name +" has invalid character"); + } + + if (needPeriod && !havePeriod) + throw InvalidName(name +" doesn't have two elements"); +} + + +DBus::BusName::BusName(const std::string& name) +{ + validate(name, Type::BusName); + m_name = name; +} + +DBus::UniqueName::UniqueName(const std::string& name) +{ + validate(name, Type::UniqueName); + m_name = name; +} + +DBus::WellKnownName::WellKnownName(const std::string& name) +{ + validate(name, Type::WellKnownName); + m_name = name; +} + +DBus::InterfaceName::InterfaceName(const std::string& name) +{ + validate(name, Type::InterfaceName); + m_name = name; +} + +DBus::ErrorName::ErrorName(const std::string& name) +{ + validate(name, Type::ErrorName); + m_name = name; +} + +DBus::NamespaceName::NamespaceName(const std::string& name) +{ + validate(name, Type::NamespaceName); + m_name = name; +} + +DBus::MemberName::MemberName(const std::string& name) +{ + validate(name, Type::MemberName); + m_name = name; +} + + + +DBus::BusName::BusName(const char* name) + : BusName(std::string(name)) +{} + +DBus::UniqueName::UniqueName(const char* name) + : UniqueName(std::string(name)) +{} + +DBus::WellKnownName::WellKnownName(const char* name) + : WellKnownName(std::string(name)) +{} + +DBus::InterfaceName::InterfaceName(const char* name) + : InterfaceName(std::string(name)) +{} + +DBus::ErrorName::ErrorName(const char* name) + : ErrorName(std::string(name)) +{} + +DBus::NamespaceName::NamespaceName(const char* name) + : NamespaceName(std::string(name)) +{} + +DBus::MemberName::MemberName(const char* name) + : MemberName(std::string(name)) +{} + + + +// The following rules define a valid object path. +// +// * The path may be of any length. +// * The path must begin with an ASCII '/' character, and must consist of +// elements separated by slash characters. +// * Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" +// * No element may be the empty string. +// * Multiple '/' characters cannot occur in sequence. +// * A trailing '/' character is not allowed unless the path is the root +// path (a single '/' character). +// +DBus::ObjectPath::ObjectPath(const std::string& path) +{ + if (path.empty()) + throw InvalidObjectPath("path is empty"); + + if (path.front() != '/') + throw InvalidObjectPath(path + " doesn't start with slash"); + + if (path.size() > 1) { + if (path.back() == '/') + throw InvalidObjectPath(path + " ends with slash"); + + char prev = ' '; + for (const auto& ch : path) { + if (ch == '/' && prev == '/') + throw InvalidObjectPath(path + " has // sequence"); + + if ( ch == '/' || ch == '_' || (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') ) + prev = ch; + else + throw InvalidObjectPath(path + " has invalid character"); + } + } + m_name = path; +} + +DBus::ObjectPath::ObjectPath(const char* path) + : ObjectPath(std::string(path)) +{} + diff --git a/src/dbus_names.h b/src/dbus_names.h new file mode 100644 index 0000000..e0b0ee3 --- /dev/null +++ b/src/dbus_names.h @@ -0,0 +1,104 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#pragma once + +#include +#include +#include + +namespace DBus { + + using InvalidName = std::runtime_error; + using InvalidObjectPath = std::runtime_error; + + + class Name { + public: + static constexpr std::size_t MaximumSize = 255; + operator const char*() const; + explicit operator bool() const; + + protected: + enum Type { + BusName, UniqueName, WellKnownName, ErrorName, + InterfaceName, NamespaceName, MemberName }; + Name() = default; + void validate(const std::string& name, Type type) const; + std::string m_name; + }; + + struct BusName : public Name { + BusName() = default; + BusName(BusName&&) = default; + BusName(const BusName&) = default; + BusName(const char* name); + BusName(const std::string& name); + }; + + struct UniqueName : public BusName { + UniqueName(UniqueName&&) = default; + UniqueName(const UniqueName&) = default; + UniqueName(const char* name); + UniqueName(const std::string& name); + }; + + struct WellKnownName : public BusName { + WellKnownName(WellKnownName&&) = default; + WellKnownName(const WellKnownName&) = default; + WellKnownName(const char* name); + WellKnownName(const std::string& name); + }; + + struct InterfaceName : public Name { + InterfaceName() = default; + InterfaceName(InterfaceName&&) = default; + InterfaceName(const InterfaceName&) = default; + InterfaceName(const char* name); + InterfaceName(const std::string& name); + }; + + struct ErrorName : public Name { + ErrorName(ErrorName&&) = default; + ErrorName(const ErrorName&) = default; + ErrorName(const char* name); + ErrorName(const std::string& name); + }; + + struct NamespaceName : public Name { + NamespaceName(NamespaceName&&) = default; + NamespaceName(const NamespaceName&) = default; + NamespaceName(const char* name); + NamespaceName(const std::string& name); + }; + + struct MemberName : public Name { + MemberName(MemberName&&) = default; + MemberName(const MemberName&) = default; + MemberName(const char* name); + MemberName(const std::string& name); + }; + + struct ObjectPath : public Name { + ObjectPath() = default; + ObjectPath(ObjectPath&&) = default; + ObjectPath(const ObjectPath&) = default; + ObjectPath(const char* path); + ObjectPath(const std::string& path); + }; + +} diff --git a/src/dbus_native.cpp b/src/dbus_native.cpp deleted file mode 100644 index acca103..0000000 --- a/src/dbus_native.cpp +++ /dev/null @@ -1,267 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_native.h" -#include "dbus_auth.h" -#include "dbus_log.h" -#include "dbus_matchrule.h" -#include "dbus_message.h" -#include "dbus_messageprotocol.h" - -std::string DBus::Native::DBusDaemon("org.freedesktop.DBus"); - -DBus::Native::Native(const std::string& busname) - : m_MessageProtocol(new DBus::MessageProtocol()) -{ - m_Transport.reset(new DBus::Transport(busname)); - m_AuthenticationProtocol.reset(new DBus::AuthenticationProtocol(m_Transport)); - m_Transport->setDataHandler( - std::bind(&Native::onReceiveAuthData, this, std::placeholders::_1)); - m_MessageProtocol->setMethodCallHandler( - std::bind(&Native::onReceiveMethodCall, this, std::placeholders::_1)); - m_MessageProtocol->setMethodReturnHandler( - std::bind(&Native::onReceiveMethodReturn, this, std::placeholders::_1)); - m_MessageProtocol->setErrorHandler( - std::bind(&Native::onReceiveError, this, std::placeholders::_1)); - m_MessageProtocol->setSignalHandler( - std::bind(&Native::onReceiveSignal, this, std::placeholders::_1)); -} - -DBus::Native::~Native() -{ - m_Transport->setDataHandler([](DBus::OctetBuffer&) {}); - m_MessageProtocol->setMethodCallHandler( - [](const DBus::Message::MethodCall& method) {}); - m_MessageProtocol->setMethodReturnHandler( - [](const DBus::Message::MethodReturn& method) {}); - m_MessageProtocol->setErrorHandler([](const DBus::Message::Error& method) {}); - m_MessageProtocol->setSignalHandler( - [](const DBus::Message::Signal& method) {}); -} - -void DBus::Native::BeginAuth(AuthenticationProtocol::AuthRequired type) -{ - m_AuthenticationProtocol->sendAuth(type); -} - -void DBus::Native::onReceiveAuthData(OctetBuffer& buffer) -{ - try { - const size_t bufferSize = buffer.size(); - if (m_AuthenticationProtocol->onReceiveData(buffer)) { - m_Stats.bytes_auth = bufferSize - buffer.size(); - onReceiveMessageData(buffer); - m_Transport->setDataHandler(std::bind(&Native::onReceiveMessageData, this, - std::placeholders::_1)); - } - } catch (const std::exception& e) { - DBus::Log::write( - DBus::Log::ERROR, - "DBus :: Native : onReceiveAuthData has thrown an exception : %s\n", - e.what()); - m_AuthenticationProtocol->reset(); - } -} - -void DBus::Native::onReceiveMessageData(OctetBuffer& buffer) -{ - try { - // AFAIK, we can ever leave the message protocol, once auth'd, so the return - // value can be safely ignored. (To exit the communication we just close the - // socket.) - m_Stats.bytes_message += buffer.size(); - m_MessageProtocol->onReceiveData(buffer); - } catch (const std::exception& e) { - DBus::Log::write( - DBus::Log::ERROR, - "DBus :: Native : onReceiveMessageData has thrown an exception : %s\n", - e.what()); - m_MessageProtocol->reset(); - } -} - -void DBus::Native::registerMethodCallHandler( - const std::string& name, - const DBus::Message::CallbackFunctionMethodCall& handler) -{ - boost::recursive_mutex::scoped_lock guard(m_MethodCallMapMutex); - - m_MethodCallMap.emplace(name, handler); -} - -void DBus::Native::registerSignalHandler( - const std::string& name, - const DBus::Message::CallbackFunctionSignal& handler) -{ - boost::recursive_mutex::scoped_lock guard(m_SignalMapMutex); - - m_SignalMap.emplace(name, handler); -} - -void DBus::Native::unRegisterMethodCallHandler( - const std::string& name) -{ - boost::recursive_mutex::scoped_lock guard(m_MethodCallMapMutex); - - m_MethodCallMap.erase(name); -} - -void DBus::Native::unRegisterSignalHandler( - const std::string& name) -{ - boost::recursive_mutex::scoped_lock guard(m_SignalMapMutex); - - m_SignalMap.erase(name); -} - -// -// Send messages out -// -void DBus::Native::sendMethodCall( - const std::string& destination, const DBus::Message::MethodCall& method, - const DBus::Message::CallbackFunctionMethodReturn& success, - const DBus::Message::CallbackFunctionError& failure) -{ - ++m_Stats.count_sent_methodcalls; - if (method.isReplyExpected()) { - boost::recursive_mutex::scoped_lock guard(m_MessageQueueMutex); - - m_MessageQueue.emplace(method.getSerial(), - Native::CallbackPair(success, failure)); - } - m_Transport->sendString(method.marshall(destination)); -} - -void DBus::Native::sendMethodReturn(const std::string& destination, - const DBus::Message::MethodReturn& result) -{ - ++m_Stats.count_sent_methodreturns; - m_Transport->sendString(result.marshall(destination)); -} - -void DBus::Native::sendError(const std::string& destination, - const DBus::Message::Error& err) -{ - ++m_Stats.count_sent_errors; - m_Transport->sendString(err.marshall(destination)); -} - -void DBus::Native::sendSignal(const std::string& destination, - const DBus::Message::Signal& signal) -{ - ++m_Stats.count_sent_signals; - m_Transport->sendString(signal.marshall(destination)); -} - -void DBus::Native::broadcastSignal(const DBus::Message::Signal& signal) -{ - ++m_Stats.count_sent_signals; - m_Transport->sendString(signal.marshall("")); -} - -// -// Handle incoming messages -// -void DBus::Native::onReceiveMethodCall( - const DBus::Message::MethodCall& method) -{ - DBus::Log::write(DBus::Log::INFO, "DBus :: onReceiveMethodCall : %s\n", - method.getFullName().c_str()); - ++m_Stats.count_receive_methodcalls; - - boost::recursive_mutex::scoped_lock guard(m_MethodCallMapMutex); - auto it = m_MethodCallMap.find(method.getFullName()); - - if (it != m_MethodCallMap.end()) { - it->second(method); - } -} - -void DBus::Native::onReceiveMethodReturn( - const DBus::Message::MethodReturn& result) -{ - boost::recursive_mutex::scoped_lock guard(m_MessageQueueMutex); - auto it = m_MessageQueue.find(result.m_SerialReplyingTo); - ++m_Stats.count_receive_methodreturns; - - if (it != m_MessageQueue.end()) { - it->second.success(result); - m_MessageQueue.erase(it); - } -} - -void DBus::Native::onReceiveError(const DBus::Message::Error& error) -{ - boost::recursive_mutex::scoped_lock guard(m_MessageQueueMutex); - auto it = m_MessageQueue.find(error.getSerialOfReply()); - - if (it != m_MessageQueue.end()) { - it->second.failure(error); - m_MessageQueue.erase(it); - } - DBus::Log::write(DBus::Log::WARNING, "DBus :: Error : %s\n", - error.getMessage().c_str()); -} - -void DBus::Native::onReceiveSignal(const DBus::Message::Signal& signal) -{ - boost::recursive_mutex::scoped_lock guard(m_SignalMapMutex); - ++m_Stats.count_receive_signals; - - std::string fullname = signal.getHeaderInterface() + "." + signal.getHeaderMember(); - auto it = m_SignalMap.find(fullname); - - if (it != m_SignalMap.end()) { - it->second(signal); - } - - { - boost::recursive_mutex::scoped_lock guard(m_RulesMapMutex); - for (auto it : m_RulesMap) { - if (it.second.isMatched(signal)) { - it.second.invoke(signal); - } - } - } -} - -std::string DBus::Native::getStats() const -{ - std::stringstream ss; - - ss << "Native stats:" << std::endl; - ss << " count_sent_methodcalls: " << m_Stats.count_sent_methodcalls - << std::endl; - ss << " count_sent_methodreturns: " << m_Stats.count_sent_methodreturns - << std::endl; - ss << " count_sent_errors: " << m_Stats.count_sent_errors << std::endl; - ss << " count_sent_signals: " << m_Stats.count_sent_signals << std::endl; - - ss << " count_receive_methodcalls: " << m_Stats.count_receive_methodcalls - << std::endl; - ss << " count_receive_methodreturns: " << m_Stats.count_receive_methodreturns - << std::endl; - ss << " count_receive_errors: " << m_Stats.count_receive_errors << std::endl; - ss << " count_receive_signals: " << m_Stats.count_receive_signals - << std::endl; - - ss << " bytes_auth: " << m_Stats.bytes_auth << std::endl; - ss << " bytes_message: " << m_Stats.bytes_message << std::endl; - - ss << m_Transport->getStats(); - return ss.str(); -} diff --git a/src/dbus_native.h b/src/dbus_native.h deleted file mode 100644 index 61cdb8f..0000000 --- a/src/dbus_native.h +++ /dev/null @@ -1,173 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#ifndef DBUS_NATIVE -#define DBUS_NATIVE - -#include "dbus_auth.h" -#include "dbus_matchrule.h" -#include "dbus_message.h" -#include "dbus_messageprotocol.h" -#include "dbus_transport.h" - -namespace DBus { - -class Native { -public: - Native(const std::string& busname); - ~Native(); - - void BeginAuth(AuthenticationProtocol::AuthRequired type); - void onReceiveAuthData(OctetBuffer& buffer); - void onReceiveMessageData(OctetBuffer& buffer); - - void - registerMethodCallHandler(const std::string& name, - const Message::CallbackFunctionMethodCall& handler); - void - unRegisterMethodCallHandler(const std::string& name); - // registerSignalHandler checks only for matching interfaces, and is a - // convenience method. It is preferably to use callAddMatch in most cases. - void registerSignalHandler(const std::string& name, - const Message::CallbackFunctionSignal& handler); - void unRegisterSignalHandler(const std::string& name); - - void - sendMethodCall(const std::string& destination, - const DBus::Message::MethodCall& msg, - const DBus::Message::CallbackFunctionMethodReturn& success = - [](const DBus::Message::MethodReturn& msg) {}, - const DBus::Message::CallbackFunctionError& failure = - [](const DBus::Message::Error& msg) {}); - void sendMethodReturn(const std::string& destination, - const DBus::Message::MethodReturn& result); - void sendError(const std::string& destination, - const DBus::Message::Error& err); - void sendSignal(const std::string& destination, - const DBus::Message::Signal& signal); - void broadcastSignal(const DBus::Message::Signal& signal); - - void onReceiveMethodCall(const DBus::Message::MethodCall& method); - void onReceiveMethodReturn(const Message::MethodReturn& reply); - void onReceiveError(const Message::Error& error); - void onReceiveSignal(const Message::Signal& signal); - - // Message Bus Messages (see dbus_native_messages.cpp for implementation) - // Q. Move elsewhere? (because of the myriad flag definitions these need) - void callHello(const Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callGetUnixProcessId( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callGetConnectionUnixUser( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callRequestName(const std::string& name, uint32_t flags, - const Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callReleaseName(const std::string& name, - const Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void - callListQueuedOwners(const std::string& bus_name, - const Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callListNames(const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callListActivatableNames( - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callNameHasOwner(const std::string& name, - const Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure); - void callAddMatch(const std::string& rule, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure, - const Message::CallbackFunctionSignal& handler); - void callRemoveMatch(const std::string& rule); - - std::string getStats() const; - - enum { - DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01, - DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02, - DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04 - } RequestNameFlagsIn; - - enum { - DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x01, - DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 0x02, - DBUS_REQUEST_NAME_REPLY_EXISTS = 0x03, - DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 0x04 - } RequestNameReplyOut; - - enum { - DBUS_RELEASE_NAME_REPLY_RELEASED = 0x01, - DBUS_RELEASE_NAME_REPLY_NON_EXISTENT = 0x02, - DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 0x03 - } ReleaseNameReplyOut; - -private: - struct CallbackPair { - CallbackPair(const Message::CallbackFunctionMethodReturn& s, - const Message::CallbackFunctionError& f) - : success(s) - , failure(f) - { - } - - Message::CallbackFunctionMethodReturn success; - Message::CallbackFunctionError failure; - }; - - struct Stats { - size_t bytes_auth = 0; - size_t bytes_message = 0; - size_t count_sent_methodcalls = 0; - size_t count_sent_methodreturns = 0; - size_t count_sent_errors = 0; - size_t count_sent_signals = 0; - size_t count_receive_methodcalls = 0; - size_t count_receive_methodreturns = 0; - size_t count_receive_errors = 0; - size_t count_receive_signals = 0; - } m_Stats; - - mutable boost::recursive_mutex m_MessageQueueMutex; - std::map m_MessageQueue; - - mutable boost::recursive_mutex m_MethodCallMapMutex; - std::map m_MethodCallMap; - - mutable boost::recursive_mutex m_SignalMapMutex; - std::map m_SignalMap; - - mutable boost::recursive_mutex m_RulesMapMutex; - std::map m_RulesMap; - - std::unique_ptr m_MessageProtocol; - std::unique_ptr m_AuthenticationProtocol; - std::shared_ptr m_Transport; - -public: - static std::string DBusDaemon; -}; -} // namespace DBus - -#endif diff --git a/src/dbus_native_messages.cpp b/src/dbus_native_messages.cpp deleted file mode 100644 index 4ee46ef..0000000 --- a/src/dbus_native_messages.cpp +++ /dev/null @@ -1,182 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#include "dbus_log.h" - -#include "dbus_type.h" -#include "dbus_type_base.h" -#include "dbus_type_struct.h" - -#include "dbus_auth.h" -#include "dbus_matchrule.h" -#include "dbus_message.h" -#include "dbus_messageprotocol.h" -#include "dbus_native.h" - -#define MESSAGE_BUS_OBJECT "/org/freedesktop/DBus" -#define MESSAGE_BUS_INTERFACE "org.freedesktop.DBus" - -// This file contains only the implementations of the primary "Message Bus -// Messages" They're included primarily as a convenience to application -// developers, as the implementation is trivial. -void DBus::Native::callHello( - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - DBus::Message::MethodCall method(DBus::Message::MethodCallIdentifier( - MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, "Hello")); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callGetUnixProcessId( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "GetConnectionUnixProcessID"); - Message::MethodCallParametersIn inparams(name); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callGetConnectionUnixUser( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "GetConnectionUnixUser"); - Message::MethodCallParametersIn inparams(name); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callRequestName( - const std::string& name, uint32_t flags, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "RequestName"); - Message::MethodCallParametersIn inparams(name, flags); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callReleaseName( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "ReleaseName"); - Message::MethodCallParametersIn inparams(name); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callListQueuedOwners( - const std::string& bus_name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "ListQueuedOwners"); - Message::MethodCallParametersIn inparams(bus_name); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callListNames( - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - DBus::Message::MethodCall method(Message::MethodCallIdentifier( - MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, "ListNames")); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callListActivatableNames( - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - DBus::Message::MethodCall method(Message::MethodCallIdentifier( - MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, "ListActivatableNames")); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callNameHasOwner( - const std::string& name, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "NameHasOwner"); - Message::MethodCallParametersIn inparams(name); - Message::MethodCall method(id, inparams); - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callAddMatch( - const std::string& rulestring, - const DBus::Message::CallbackFunctionMethodReturn& success, - const Message::CallbackFunctionError& failure, - const Message::CallbackFunctionSignal& handler) -{ - // Note: Rules are specified as a string of comma separated key/value pairs - // Possible keys you can match on are type, sender, interface, member, path, - // destination and numbered keys to match message args (keys are 'arg0', - // 'arg1', etc.) - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "AddMatch"); - Message::MethodCallParametersIn inparams(rulestring); - Message::MethodCall method(id, inparams); - - { - boost::recursive_mutex::scoped_lock guard(m_RulesMapMutex); - DBus::MatchRule newrule(rulestring, handler); - m_RulesMap.emplace(rulestring, newrule); - } - - sendMethodCall(DBus::Native::DBusDaemon, method, success, failure); -} - -void DBus::Native::callRemoveMatch(const std::string& rule) -{ - Message::MethodCallIdentifier id(MESSAGE_BUS_OBJECT, MESSAGE_BUS_INTERFACE, - "RemoveMatch"); - Message::MethodCallParametersIn inparams(rule); - Message::MethodCall method(id, inparams, - DBus::Message::Header::FLAGS_NO_REPLY_EXPECTED); - - sendMethodCall(DBus::Native::DBusDaemon, method); - - { - boost::recursive_mutex::scoped_lock guard(m_RulesMapMutex); - m_RulesMap.erase(rule); - } -} diff --git a/src/dbus_octetbuffer.cpp b/src/dbus_octetbuffer.cpp index 6b64513..dfc4394 100644 --- a/src/dbus_octetbuffer.cpp +++ b/src/dbus_octetbuffer.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,6 +17,7 @@ // . #include "dbus_octetbuffer.h" +#include "dbus_log.h" #include #include #include @@ -25,6 +27,22 @@ namespace DBus { OctetBuffer::OctetBuffer(const uint8_t* data, size_t size) : m_data(data) , m_size(size) + , m_fds() +{ +} + +OctetBuffer::OctetBuffer(OctetBuffer& other, std::size_t size) + : m_data(other.m_data) + , m_size(size) + , m_fds(other.m_fds) +{ +} + +OctetBuffer::OctetBuffer(const std::uint8_t* data, std::size_t dataSize, + const UnixFdBuffer& fds) + : m_data(data) + , m_size(dataSize) + , m_fds(fds) { } @@ -71,4 +89,11 @@ size_t OctetBuffer::find(uint8_t byte) const return std::string::npos; } +int OctetBuffer::getUnixFd(std::uint32_t index) const +{ + if (index < m_fds.size()) + return m_fds[index]; + throw std::out_of_range("OctetBuffer::getUnixFd index out of range"); +} + } // namespace DBus diff --git a/src/dbus_octetbuffer.h b/src/dbus_octetbuffer.h index caaecc7..6d0ac4a 100644 --- a/src/dbus_octetbuffer.h +++ b/src/dbus_octetbuffer.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,20 +16,26 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_OCTECTSTREAM_H -#define DBUS_OCTECTSTREAM_H +#pragma once #include #include +#include namespace DBus { +using UnixFdBuffer = std::vector; + class OctetBuffer { const uint8_t* m_data; size_t m_size; + UnixFdBuffer m_fds; public: OctetBuffer(const uint8_t* data, size_t size); + OctetBuffer(OctetBuffer& other, std::size_t size); + OctetBuffer(const uint8_t* data, std::size_t dataSize, + const UnixFdBuffer& fds); size_t size() const; const uint8_t* data() const; @@ -37,8 +44,7 @@ class OctetBuffer { uint8_t operator[](unsigned long index) const; void copy(uint8_t* data, size_t size) const; size_t find(uint8_t byte) const; + int getUnixFd(std::uint32_t index) const; }; } // namespace DBus - -#endif diff --git a/src/dbus_transport.cpp b/src/dbus_transport.cpp index c5b8334..b5aa47d 100644 --- a/src/dbus_transport.cpp +++ b/src/dbus_transport.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,191 +17,19 @@ // . #include "dbus_transport.h" -#include "dbus_log.h" -#include -#include -using namespace std::chrono_literals; - -DBus::Transport::Transport(const std::string& path) - : m_Busname(path) - , m_ReadyToSend(false) - , m_socket(m_io_context) - , m_ShuttingDown(false) -{ - setDataHandler( - std::bind(&Transport::onReceiveData, this, std::placeholders::_1)); - - m_socket.connect(m_Busname); - - // The "special credentials passing NUL byte" is required, even for protocols - // that can send credentials without needing one. Otherwise, the server may - // disconnect us. - sendOctetDirect('\0'); - - m_socket.async_read_some( - boost::asio::buffer(m_DataBuffer, BufferSize), - boost::bind(&Transport::handle_read_data, this, - boost::placeholders::_1, boost::placeholders::_2)); - - // We use a second thread for the io context until further notice - m_io_context_thread = boost::thread(boost::bind(&boost::asio::io_context::run, &m_io_context)); -} - -void DBus::Transport::handle_read_data(const boost::system::error_code& error, - std::size_t bytes_transferred) -{ - boost::recursive_mutex::scoped_lock guard(m_CallbackMutex); - OctetBuffer buffer(m_DataBuffer, bytes_transferred); - m_ReceiveDataCallback(buffer); - - if (error) { - if (error.category() == boost::asio::error::misc_category && error.value() == boost::asio::error::misc_errors::eof) { - if (!m_ShuttingDown) - Log::write( - Log::ERROR, - "DBus :: Transport received slightly unexpected end of stream\n"); - } else - Log::write(Log::ERROR, "DBus :: Transport error. %s (%d)\n", - error.message().c_str(), error.value()); - } else - m_socket.async_read_some( - boost::asio::buffer(m_DataBuffer, BufferSize), - boost::bind(&Transport::handle_read_data, this, boost::placeholders::_1, boost::placeholders::_2)); -} - -DBus::Transport::~Transport() -{ - // EOF on read is now expected - m_ShuttingDown = true; - - { - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - m_ReadyToSend = false; - } - - { - // Take the callback mutex so that we don't call m_socket.shutdown() at the - // same time as handle_read_data() is calling async_read_some() on the same - // object - boost::recursive_mutex::scoped_lock guard(m_CallbackMutex); - boost::system::error_code ec; - m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - if (ec) { - DBus::Log::write( - DBus::Log::ERROR, - "DBus :: Transport :: Socket shutdown failed: (%d) \"%s\"\n", - ec.value(), ec.message().c_str()); - } - } - - // Wait for pending async_writes to complete - if (!m_io_context_thread.try_join_for(boost::chrono::seconds(30))) { - DBus::Log::write(DBus::Log::ERROR, - "DBus :: Transport :: IO service thread cannot join\n"); - abort(); - } -} - -void DBus::Transport::onAuthComplete() -{ - DBus::Log::write(DBus::Log::INFO, - "DBus :: Transport :: Authorisation has completed.\n"); - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - for (auto msg : m_BufferedMessages) { - sendStringDirect(msg); - ++m_Stats.count_messagespumped; - } - m_BufferedMessages.clear(); - m_ReadyToSend = true; -} - -void DBus::Transport::addToMessageQueue(const std::string& data) +DBus::Transport::Ptr +DBus::Transport::create(asio::io_context& io_context) { - DBus::Log::write(DBus::Log::INFO, "DBus :: Transport :: No BEGIN has been " - "received, so message is queued.\n"); - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - m_BufferedMessages.push_back(data); - ++m_Stats.count_messagesqueued; + struct ShareableTransport : public Transport { + ShareableTransport(asio::io_context& io_context) + : Transport(io_context) {}; + }; + return std::make_shared(io_context); } -void DBus::Transport::sendString(const std::string& data) -{ - DBus::Log::write(DBus::Log::TRACE, "DBus :: SEND: %s\n", data.c_str()); - DBus::Log::writeHex(DBus::Log::TRACE, "DBus :: DATA: ", data); - - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - if (m_ReadyToSend) { - sendStringDirect(data); - } else { - addToMessageQueue(data); - } -} - -void DBus::Transport::sendOctetDirect(uint8_t data) -{ - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - std::shared_ptr buf(new std::string()); - buf.get()->push_back(static_cast(data)); - boost::asio::async_write( - m_socket, boost::asio::buffer(*buf.get()), - boost::bind(&Transport::handle_write_output, this, buf, boost::placeholders::_1, boost::placeholders::_2)); -} -void DBus::Transport::sendStringDirect(const std::string& data) +DBus::Transport::Transport(asio::io_context& ioContext) + : m_socket(ioContext) { - DBus::Log::write(DBus::Log::TRACE, "DBus :: SENDDIRECT: %s\n", data.c_str()); - DBus::Log::writeHex(DBus::Log::TRACE, "DBus :: SENDDIRECT: \n", data); - boost::recursive_mutex::scoped_lock guard(m_SendMutex); - - // We don't know when the write will complete, so we copy the buffer - std::shared_ptr buf(new std::string(data)); - boost::asio::async_write( - m_socket, boost::asio::buffer(*buf.get()), - boost::bind(&Transport::handle_write_output, this, buf, boost::placeholders::_1, boost::placeholders::_2)); - ++m_Stats.count_messagessent; - m_Stats.bytes_sent += data.length(); -} - -void DBus::Transport::handle_write_output( - std::shared_ptr buf_written, - const boost::system::error_code& error, std::size_t bytes_transferred) -{ - buf_written.reset(); - if (error) { - DBus::Log::write(DBus::Log::ERROR, "DBus :: ERROR in async_write : %s\n", - error.message().c_str()); - } -} - -void DBus::Transport::setDataHandler( - const ReceiveDataCallbackFunction& callback) -{ - boost::recursive_mutex::scoped_lock guard(m_CallbackMutex); - m_ReceiveDataCallback = callback; -} - -void DBus::Transport::onReceiveData(OctetBuffer&) -{ - // NOP - This stub gives our initial callback somewhere to go - - // This could happen if the thread reads data before native.cpp updates the - // callback. - DBus::Log::write(DBus::Log::WARNING, - "DBus :: Transport : onReceiveData is processing data, " - "whereas it should really have been directed elsewhere via " - "setDataHandler\n"); -} - -std::string DBus::Transport::getStats() const -{ - std::stringstream ss; - - ss << "Transport stats:" << std::endl; - ss << " count_messages_sent: " << m_Stats.count_messagessent << std::endl; - ss << " count_messages_queued: " << m_Stats.count_messagesqueued << std::endl; - ss << " count_messages_pumped: " << m_Stats.count_messagespumped << std::endl; - ss << " bytes_sent: " << m_Stats.bytes_sent << std::endl; - ss << " bytes_read: " << m_Stats.bytes_read << std::endl; - return ss.str(); } diff --git a/src/dbus_transport.h b/src/dbus_transport.h index 130d75f..23dff8c 100644 --- a/src/dbus_transport.h +++ b/src/dbus_transport.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,74 +16,204 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TRANSPORT_H -#define DBUS_TRANSPORT_H +#pragma once -#include "dbus_octetbuffer.h" -#include -#include +#include "dbus_asio.h" +#include "dbus_log.h" +#include "dbus_messageistream.h" +#include "dbus_messageostream.h" -#define USE_ASIO_NORMAL 1 -#define USE_ASIO_ACCESSOR 0 -#define USE_NATIVE_SOCKETS 0 +#include +#include +#include namespace DBus { -typedef std::function ReceiveDataCallbackFunction; +using DynamicStringBuffer = + asio::dynamic_string_buffer< + std::string::value_type, + std::string::traits_type, + std::string::allocator_type>; -class Transport { +class Transport : public std::enable_shared_from_this { public: - Transport(const std::string& path); - ~Transport(); - - void sendString(const std::string& data); - void handle_write_output(std::shared_ptr buf_written, - const boost::system::error_code& error, - std::size_t bytes_transferred); - void handle_read_data(const boost::system::error_code& error, - std::size_t bytes_transferred); - - void ThreadFunction(); - - void setDataHandler(const ReceiveDataCallbackFunction& callback); - void onReceiveData(OctetBuffer& buffer); - void onAuthComplete(); - void addToMessageQueue(const std::string& data); + using Ptr = std::shared_ptr; + using EndpointType = asio::local::stream_protocol::socket::endpoint_type; + + static Ptr create(asio::io_context& io_context); + + template + void asyncConnect(const std::string& bus_path, Handler&& handler) + { + m_socket.async_connect(bus_path, std::forward(handler)); + } + + void disconnect() + { + if (m_socket.is_open()) + m_socket.close(); + } + + bool connected() + { + return m_socket.is_open(); + } + + template + void asyncPeek(const MutableBuffer& buffer, Handler&& handler) + { + m_socket.async_receive(buffer, asio::socket_base::message_peek, + [self = shared_from_this(), buffer, handler = std::move(handler)] + (const error_code& error, std::size_t bytes_read) + { + if (!self->m_socket.is_open()) + return handler({}, 0); + if (error && error.value() == EAGAIN) + return self->asyncPeek(buffer, handler); + handler(error, bytes_read); + }); + } + + template + void asyncRead(const MutableBuffer& buffer, UnixFdBuffer& fds, Handler&& handler) + { + m_socket.async_wait(asio::local::stream_protocol::socket::wait_read, + [self = shared_from_this(), buffer, &fds, handler = std::move(handler)] + (const error_code& error) mutable + { + if (!self->m_socket.is_open()) + return handler({}, 0); + if (error && error.value() == EAGAIN) + return self->asyncRead(buffer, fds, handler); + if (error) + return handler(error, 0); + + struct iovec iov; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char fdbuf[CMSG_SPACE(self->m_maxRecvUnixFds * sizeof(int))]; + msg.msg_control = fdbuf; + msg.msg_controllen = sizeof(fdbuf); + + ssize_t read_total = 0; + do { + iov = { static_cast(buffer.data()) + read_total, + buffer.size() - read_total }; + ssize_t read = ::recvmsg(self->m_socket.native_handle(), &msg, 0); + if (read == 0) + return handler({}, 0); + if (read == -1) { + if (errno == EAGAIN) + continue; + return handler(error_code(errno, system_category()), 0); + } + + read_total += read; + + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) + continue; + + int *fdptr = (int *)CMSG_DATA(cmsg); + std::size_t len = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + fds.assign(fdptr, fdptr + len); + } + } while (buffer.size() - read_total > 0); + + handler({}, read_total); + }); + } + + template + void asyncWrite(MessageOStream::Ptr payload, Handler&& handler) + { + asio::const_buffer buffer( payload->data.data(), payload->data.size() ); + Log::write(Log::TRACE, "DBus :: Send : Message Data : %ld bytes, %ld FDs\n", + buffer.size(), payload->fds.size()); + Log::writeHex(Log::TRACE, " ", buffer.data(), buffer.size()); + + if (payload->fds.empty()) { + asio::async_write(m_socket, buffer, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error, std::size_t size) mutable { + Log::write(Log::TRACE, "DBus :: Send : message complete\n"); + handler(error, size); + }); + } else { + // send unix fds along the data + m_socket.async_wait(asio::local::stream_protocol::socket::wait_write, + [self = shared_from_this(), payload, handler = std::move(handler)] + (const error_code& error) mutable + { + if (error) + return handler(error, 0); + + struct iovec iov = { + const_cast(payload->data.data()), + payload->data.size() }; + + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + const size_t fdlen = payload->fds.size() * sizeof(int); + char fdbuf[CMSG_SPACE(fdlen)]; + msg.msg_control = fdbuf; + msg.msg_controllen = sizeof(fdbuf); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(fdlen); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + std::memcpy(CMSG_DATA(cmsg), payload->fds.data(), fdlen); + ssize_t ret = ::sendmsg(self->m_socket.native_handle(), &msg, 0); + payload->clearUnixFds(); + + Log::write(Log::TRACE, "DBus :: Send : message complete\n"); + if (ret == -1) + handler(error_code(errno, system_category()), 0); + else + handler({}, ret); + }); + } + } std::string getStats() const; protected: - friend class AuthenticationProtocol; // this is to give access to - // sendStringDirect, to bypass the - // buffering - void sendOctetDirect(uint8_t data); - void sendStringDirect(const std::string& data); - - std::string m_Busname; - bool m_ReadyToSend; - boost::asio::io_context m_io_context; - boost::asio::local::stream_protocol::socket m_socket; - - const static size_t BufferSize = 1024; - uint8_t m_DataBuffer[BufferSize]; - ReceiveDataCallbackFunction m_ReceiveDataCallback; - - mutable boost::recursive_mutex m_SendMutex; - mutable boost::recursive_mutex m_CallbackMutex; - std::vector m_BufferedMessages; - std::atomic m_ShuttingDown; - - boost::thread m_io_context_thread; - -private: - struct Stats { - size_t count_messagessent = 0; - size_t count_messagesqueued = 0; - size_t count_messagespumped = 0; - size_t bytes_sent = 0; - size_t bytes_read = 0; - } m_Stats; + Transport(asio::io_context& ioContext); + + friend class AuthenticationProtocol; + using AuthProtoMode = enum { NoResponseExpected, ResponseExpected }; + + template + void asyncAuthExchange(AuthProtoMode mode, DynamicBuffer&& buffer, Handler&& handler) + { + if (mode == NoResponseExpected) + asio::async_write(m_socket, std::forward(buffer), std::forward(handler)); + else { + asio::async_write(m_socket, std::forward(buffer), + [self = shared_from_this(), &buffer, handler = std::forward(handler)] + (const error_code& error, std::size_t bytes_transferred) mutable { + if (error) { + handler(error, bytes_transferred); + return; + } + asio::async_read_until(self->m_socket, std::forward(buffer), "\n", std::forward(handler)); + } + ); + } + } + + asio::local::stream_protocol::socket m_socket; + std::size_t m_maxRecvUnixFds = 16; }; -} // namespace DBus -#endif // DBUS_TRANSPORT_H +} // namespace DBus diff --git a/src/dbus_type.cpp b/src/dbus_type.cpp index ae8c90f..2e37dac 100644 --- a/src/dbus_type.cpp +++ b/src/dbus_type.cpp @@ -15,12 +15,11 @@ // file named COPYING. If you do not have this file see // . -#include "dbus_type.h" #include "dbus_log.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" +#include "dbus_type_any.h" #include "dbus_type_array.h" -#include "dbus_type_base.h" #include "dbus_type_boolean.h" #include "dbus_type_byte.h" #include "dbus_type_dictentry.h" @@ -35,391 +34,159 @@ #include "dbus_type_uint16.h" #include "dbus_type_uint32.h" #include "dbus_type_uint64.h" +#include "dbus_type_unixfd.h" #include "dbus_type_variant.h" // -// Helper methods to extract navtive types from the opaque 'Generic' type +// Helper methods to extract native types from the opaque 'Any' type // -uint8_t DBus::Type::asByte(const Generic& v) +int DBus::Type::asUnixFd(const Any& v) { - return std::stoi(Type::asString(v)); + return static_cast(v).dup(); } -uint32_t DBus::Type::asUint32(const Generic& v) +bool DBus::Type::asBoolean(const Any& v) { - return std::stoi(Type::asString(v)); + return static_cast(v); } -// Generate a compact, machine-friendly, version of the data. -// (compare to toString, which is intended for humans debugging) -std::string DBus::Type::asString(const DBus::Type::Generic& value) +double DBus::Type::asDouble(const Any& v) { - // It is possible to receive an empty value when dealing with void parameter - // lists, or with uninitialised types from header fields. - if (value.empty()) { - return ""; - } -#define TYPE_ASSTRING(typename) \ - { \ - static Type::Generic is_type = typename(); \ - if (ti == is_type.type()) { \ - return boost::any_cast(value).asString(); \ - } \ - } - const std::type_info& ti = value.type(); - - TYPE_ASSTRING(Type::Byte); - TYPE_ASSTRING(Type::Boolean); - TYPE_ASSTRING(Type::Int16); - TYPE_ASSTRING(Type::Uint16); - TYPE_ASSTRING(Type::Int32); - TYPE_ASSTRING(Type::Uint32); - TYPE_ASSTRING(Type::Int64); - TYPE_ASSTRING(Type::Uint64); - TYPE_ASSTRING(Type::Double); - TYPE_ASSTRING(Type::String); - TYPE_ASSTRING(Type::Array); - TYPE_ASSTRING(Type::Variant); - TYPE_ASSTRING(Type::Struct); - TYPE_ASSTRING(Type::ObjectPath); - TYPE_ASSTRING(Type::Signature); - TYPE_ASSTRING(Type::DictEntry); - -#undef TYPE_ASSTRING - - Log::write(DBus::Log::ERROR, "DBus :: asString type not implemented (%s)\n", - ti.name()); - - return ""; + return static_cast(v); } -const DBus::Type::Array& DBus::Type::refArray(const Generic& value) +std::string DBus::Type::asString(const Any& v) { - return boost::any_cast(value); + return static_cast(v); } -const DBus::Type::Struct& DBus::Type::refStruct(const Generic& value) +std::uint8_t DBus::Type::asByte(const Any& v) { - return boost::any_cast(value); + return static_cast(v); } -const DBus::Type::Signature& DBus::Type::refSignature(const Generic& value) +std::int16_t DBus::Type::asInt16(const Any& v) { - return boost::any_cast(value); + return static_cast(v); } -const DBus::Type::Variant& DBus::Type::refVariant(const Generic& value) +std::int32_t DBus::Type::asInt32(const Any& v) { - return boost::any_cast(value); + return static_cast(v); } -// -// Mapping methods to convert between abstract Generic types, to specific -// classes -// -DBus::Type::Generic DBus::Type::create(const std::string& type) +std::int64_t DBus::Type::asInt64(const Any& v) { -#define TYPE_CREATE(typename) \ - if (type[0] == typename ::s_StaticTypeCode[0]) { \ - typename vv; \ - vv.setSignature(type); \ - Type::Generic v(vv); \ - return v; \ - } - - TYPE_CREATE(Type::Byte); - TYPE_CREATE(Type::Boolean); - TYPE_CREATE(Type::Int16); - TYPE_CREATE(Type::Uint16); - TYPE_CREATE(Type::Int32); - TYPE_CREATE(Type::Uint32); - TYPE_CREATE(Type::Int64); - TYPE_CREATE(Type::Uint64); - TYPE_CREATE(Type::Double); - TYPE_CREATE(Type::String); - TYPE_CREATE(Type::Array); - TYPE_CREATE(Type::Variant); - TYPE_CREATE(Type::Struct); - TYPE_CREATE(Type::ObjectPath); - TYPE_CREATE(Type::Signature); - TYPE_CREATE(Type::DictEntry); - -#undef TYPE_CREATE - - Log::write(DBus::Log::ERROR, - "DBus :: Can not create a signature type of (%s)\n", type.c_str()); - - return Type::Generic(); + return static_cast(v); } -std::string -DBus::Type::getMarshallingSignature(const DBus::Type::Generic& value) +std::uint16_t DBus::Type::asUint16(const Any& v) { - // It is possible to receive an empty value when dealing with void parameter - // lists. - if (value.empty()) { - return ""; - } -#define TYPE_MARSHALL_SIGNATURE(typename) \ - { \ - static Type::Generic is_type = typename(); \ - if (ti == is_type.type()) { \ - return typename ::s_StaticTypeCode; \ - } \ - } -// if (ti == is_type.type()) { return -//boost::any_cast(value).getSignature(); } } -#define TYPE_MARSHALL_SIGNATURE2(typename) \ - { \ - static Type::Generic is_type = typename(); \ - if (ti == is_type.type()) { \ - return boost::any_cast(value).getSignature(); \ - } \ - } - const std::type_info& ti = value.type(); - - TYPE_MARSHALL_SIGNATURE(Type::Byte); - TYPE_MARSHALL_SIGNATURE(Type::Boolean); - TYPE_MARSHALL_SIGNATURE(Type::Int16); - TYPE_MARSHALL_SIGNATURE(Type::Uint16); - TYPE_MARSHALL_SIGNATURE(Type::Int32); - TYPE_MARSHALL_SIGNATURE(Type::Uint32); - TYPE_MARSHALL_SIGNATURE(Type::Int64); - TYPE_MARSHALL_SIGNATURE(Type::Uint64); - TYPE_MARSHALL_SIGNATURE(Type::Double); - TYPE_MARSHALL_SIGNATURE(Type::String); - TYPE_MARSHALL_SIGNATURE2(Type::Array); - TYPE_MARSHALL_SIGNATURE(Type::Variant); - TYPE_MARSHALL_SIGNATURE2(Type::Struct); - TYPE_MARSHALL_SIGNATURE(Type::ObjectPath); - TYPE_MARSHALL_SIGNATURE(Type::Signature); - TYPE_MARSHALL_SIGNATURE2(Type::DictEntry); - - Log::write(DBus::Log::ERROR, - "DBus :: Marshalling signature type not implemented (%s)\n", - ti.name()); - -#undef TYPE_MARSHALL_SIGNATURE - - return "?"; + return static_cast(v); } -// TODO: macro-in-a-macro to try and reduce duplicated list of types - -void DBus::Type::marshallData(const DBus::Type::Generic& value, - MessageOStream& stream) +std::uint32_t DBus::Type::asUint32(const Any& v) { - // It is possible to receive an empty value when dealing with void parameter - // lists. - if (value.empty()) { - return; - } - std::string type(getMarshallingSignature(value)); - -#define TYPE_MARSHALL_DATA(typename) \ - if (type[0] == typename ::s_StaticTypeCode[0]) { \ - boost::any_cast(value).marshall(stream); \ - return; \ - } - - TYPE_MARSHALL_DATA(Type::Byte); - TYPE_MARSHALL_DATA(Type::Boolean); - TYPE_MARSHALL_DATA(Type::Int16); - TYPE_MARSHALL_DATA(Type::Uint16); - TYPE_MARSHALL_DATA(Type::Int32); - TYPE_MARSHALL_DATA(Type::Uint32); - TYPE_MARSHALL_DATA(Type::Int64); - TYPE_MARSHALL_DATA(Type::Uint64); - TYPE_MARSHALL_DATA(Type::Double); - TYPE_MARSHALL_DATA(Type::String); - TYPE_MARSHALL_DATA(Type::Array); - TYPE_MARSHALL_DATA(Type::Variant); - TYPE_MARSHALL_DATA(Type::Struct); - TYPE_MARSHALL_DATA(Type::ObjectPath); - TYPE_MARSHALL_DATA(Type::Signature); - TYPE_MARSHALL_DATA(Type::DictEntry); - - Log::write(DBus::Log::ERROR, - "DBus :: Marshalling data of type %s is not implemented.\n", - type.c_str()); - -#undef TYPE_MARSHALL_DATA + return static_cast(v); } -// Process one octet of data, into the given type. Returns true if that type has -// completed. -void DBus::Type::unmarshallData(DBus::Type::Generic& result, - MessageIStream& stream) +std::uint64_t DBus::Type::asUint64(const Any& v) { -// TODO: Consider whether a template could handle this -#define TYPE_UNMARSHALL_DATA(typename) \ - if (type[0] == typename ::s_StaticTypeCode[0]) { \ - typename& v = boost::any_cast(result); \ - v.unmarshall(stream); \ - return; \ - } - - std::string type = getMarshallingSignature(result); + return static_cast(v); +} - TYPE_UNMARSHALL_DATA(Type::Byte); - TYPE_UNMARSHALL_DATA(Type::Boolean); - TYPE_UNMARSHALL_DATA(Type::Int16); - TYPE_UNMARSHALL_DATA(Type::Uint16); - TYPE_UNMARSHALL_DATA(Type::Int32); - TYPE_UNMARSHALL_DATA(Type::Uint32); - TYPE_UNMARSHALL_DATA(Type::Int64); - TYPE_UNMARSHALL_DATA(Type::Uint64); - TYPE_UNMARSHALL_DATA(Type::Double); - TYPE_UNMARSHALL_DATA(Type::String); - TYPE_UNMARSHALL_DATA(Type::Array); - TYPE_UNMARSHALL_DATA(Type::Variant); - TYPE_UNMARSHALL_DATA(Type::Struct); - TYPE_UNMARSHALL_DATA(Type::ObjectPath); - TYPE_UNMARSHALL_DATA(Type::Signature); - TYPE_UNMARSHALL_DATA(Type::DictEntry); - Log::write(DBus::Log::ERROR, - "DBus :: Unmarshalling type not implemented (%s)\n", type.c_str()); -#undef TYPE_UNMARSHALL_DATA +const DBus::Type::Array& DBus::Type::refArray(const Any& value) +{ + return static_cast(value); } -// Generate an expanded description of the data type provided. -std::string DBus::Type::toString(const DBus::Type::Generic& value, - const std::string& prefix) +const DBus::Type::Struct& DBus::Type::refStruct(const Any& value) { -#define TYPE_TOSTRING(typename) \ - { \ - static Type::Generic is_type = typename(); \ - if (ti == is_type.type()) { \ - return boost::any_cast(value).toString(prefix); \ - } \ - } - const std::type_info& ti = value.type(); - - TYPE_TOSTRING(Type::Byte); - TYPE_TOSTRING(Type::Boolean); - TYPE_TOSTRING(Type::Int16); - TYPE_TOSTRING(Type::Uint16); - TYPE_TOSTRING(Type::Int32); - TYPE_TOSTRING(Type::Uint32); - TYPE_TOSTRING(Type::Int64); - TYPE_TOSTRING(Type::Uint64); - TYPE_TOSTRING(Type::Double); - TYPE_TOSTRING(Type::String); - TYPE_TOSTRING(Type::Array); - TYPE_TOSTRING(Type::Variant); - TYPE_TOSTRING(Type::Struct); - TYPE_TOSTRING(Type::ObjectPath); - TYPE_TOSTRING(Type::Signature); - TYPE_TOSTRING(Type::DictEntry); - -#undef TYPE_TOSTRING - - Log::write(DBus::Log::ERROR, "DBus :: toString type not implemented (%s)\n", - ti.name()); + return static_cast(value); +} - return "?unknown?"; +const DBus::Type::Variant& DBus::Type::refVariant(const Any& value) +{ + return static_cast(value); } -size_t DBus::Type::getAlignment(const std::string& declaration) +const DBus::Type::Signature& DBus::Type::refSignature(const Any& value) { -#define TYPE_UNMARSHALL_ALIGNMENT(typename) \ - if (declaration[0] == typename ::s_StaticTypeCode[0]) { \ - typename vv; \ - return vv.getAlignment(); \ - } + return static_cast(value); +} - TYPE_UNMARSHALL_ALIGNMENT(Type::Byte); - TYPE_UNMARSHALL_ALIGNMENT(Type::Boolean); - TYPE_UNMARSHALL_ALIGNMENT(Type::Int16); - TYPE_UNMARSHALL_ALIGNMENT(Type::Uint16); - TYPE_UNMARSHALL_ALIGNMENT(Type::Int32); - TYPE_UNMARSHALL_ALIGNMENT(Type::Uint32); - TYPE_UNMARSHALL_ALIGNMENT(Type::Int64); - TYPE_UNMARSHALL_ALIGNMENT(Type::Uint64); - TYPE_UNMARSHALL_ALIGNMENT(Type::Double); - TYPE_UNMARSHALL_ALIGNMENT(Type::String); - TYPE_UNMARSHALL_ALIGNMENT(Type::Array); - TYPE_UNMARSHALL_ALIGNMENT(Type::Variant); - TYPE_UNMARSHALL_ALIGNMENT(Type::Struct); - TYPE_UNMARSHALL_ALIGNMENT(Type::ObjectPath); - TYPE_UNMARSHALL_ALIGNMENT(Type::Signature); - TYPE_UNMARSHALL_ALIGNMENT(Type::DictEntry); +const DBus::Type::DictEntry& DBus::Type::refDictEntry(const Any& value) +{ + return static_cast(value); +} -#undef TYPE_UNMARSHALL_ALIGNMENT - return 1; -} // -// Other methods +// Mapping methods to convert between abstract Generic types, to specific +// classes // -std::string DBus::Type::extractSignature(const std::string& declaration, - size_t idx) +std::size_t DBus::Type::getAlignment(const std::string& typeCode) { - std::string result; - - // Handle arrays - if (declaration[idx] == '(') { - // Find the matching bracket, and skip all internal signatures - size_t number_of_brackets = 0; - for (size_t i = idx; i < declaration.size(); ++i) { - result += declaration[i]; - - if (declaration[i] == '(') { - ++number_of_brackets; - } else if (declaration[i] == ')') { - if (--number_of_brackets == 0) { - return result; - } - } - } - // - Log::write(Log::ERROR, "DBus :: The declaration is invalid due to " - "mis-matched brackets in the array signature."); - } - // Handle dictentry - else if (declaration[idx] == '{') { - if (idx == 0) { - Log::write(Log::ERROR, - "DBus :: The declaration is invalid because a dictentry must " - "be inside an array container type."); - } - // Find the matching brace, and skip all internal signatures - size_t number_of_brackets = 0; - for (size_t i = idx; i < declaration.size(); ++i) { - result += declaration[i]; - - if (declaration[i] == '{') { - ++number_of_brackets; - } else if (declaration[i] == '}') { - if (--number_of_brackets == 0) { - return result; - } - } - } - // - Log::write(Log::ERROR, "DBus :: The declaration is invalid due to " - "mis-matched braces in the dictentry signature."); - } - - result += declaration[idx]; - - if (declaration[idx] == 'a') { - result += extractSignature(declaration, idx + 1); + switch (typeCode[0]) + { + case Byte::code: return Byte::alignment; + case Boolean::code: return Boolean::alignment; + case Int16::code: return Int16::alignment; + case Int32::code: return Int32::alignment; + case Int64::code: return Int64::alignment; + case Uint16::code: return Uint16::alignment; + case Uint32::code: return Uint32::alignment; + case Uint64::code: return Uint64::alignment; + case Double::code: return Double::alignment; + case UnixFd::code: return UnixFd::alignment; + case String::code: return String::alignment; + case ObjectPath::code: return ObjectPath::alignment; + case Signature::code: return Signature::alignment; + case Array::code: return Array::alignment; + case Variant::code: return Variant::alignment; + case Struct::code: return Struct::alignment; + case DictEntry::code: return DictEntry::alignment; + default: + throw std::runtime_error("Type::getAlignment() called with invalid type code " + typeCode); } - - return result; } -std::string DBus::Type::getMarshallingSignature( - const std::vector& value) +DBus::Type::Any DBus::Type::create(const std::string& typeCode) { - std::string result; - - for (auto it : value) { - result += getMarshallingSignature(it); + switch (typeCode[0]) + { + case Byte::code: return Byte(); + case Boolean::code: return Boolean(); + case Int16::code: return Int16(); + case Int32::code: return Int32(); + case Int64::code: return Int64(); + case Uint16::code: return Uint16(); + case Uint32::code: return Uint32(); + case Uint64::code: return Uint64(); + case Double::code: return Double(); + case UnixFd::code: return UnixFd(); + case String::code: return String(); + case ObjectPath::code: return ObjectPath(); + case Signature::code: return Signature(); + case Array::code: return Array(typeCode); + case Variant::code: return Variant(); + case Struct::code: return Struct(typeCode); + case DictEntry::code: return DictEntry(typeCode); + default: + throw std::runtime_error("Type::create() called with invalid type code " + typeCode); } - return result; } + +bool DBus::Type::isBasicTypeCode(const int code) { + return (code == Byte::code || code == Boolean::code || + code == Int16::code || code == Uint16::code || + code == Int32::code || code == Uint32::code || + code == Int64::code || code == Uint64::code || + code == Double::code || code == UnixFd::code || + code == String::code || code == ObjectPath::code || + code == Signature::code); +} + diff --git a/src/dbus_type.h b/src/dbus_type.h index 7179518..2ac916c 100644 --- a/src/dbus_type.h +++ b/src/dbus_type.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,54 +16,87 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_H -#define DBUS_TYPE_H +#pragma once #include #include - -// Our base type, used for implementation convenience -#include "dbus_type_base.h" +#include namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - class Array; - class Struct; - class Signature; - class Variant; + class MessageIStream; + class MessageOStream; - // Helper methods to return native types - uint8_t asByte(const Generic& v); - uint32_t asUint32(const Generic& v); - std::string asString(const Generic& v); - std::string asObjectPath(const Generic& v); + class Type { + public: + class Any; + class Basic; + class Container; - // Helper methods to return type-safe DBus references - const Type::Array& refArray(const Generic& v); - const Type::Struct& refStruct(const Generic& v); - const Type::Signature& refSignature(const Generic& v); - const Type::Variant& refVariant(const Generic& v); + class Int16; + class Int32; + class Int64; - // Generic type handling code - DBus::Type::Generic create(const std::string& type); + class Uint16; + class Uint32; + class Uint64; - void marshallData(const DBus::Type::Generic& any, MessageOStream& stream); - void unmarshallData(DBus::Type::Generic& result, MessageIStream& stream); + class Byte; + class Boolean; + class UnixFd; + class Double; - std::string toString(const DBus::Type::Generic& data, - const std::string& prefix = ""); + class String; + class ObjectPath; + class Signature; - size_t getAlignment(const std::string& declaration); + class Array; + class Struct; + class DictEntry; + class Variant; - std::string extractSignature(const std::string& declaration, size_t idx); + // Virtual methods subclasses implement + virtual ~Type() = default; + virtual std::string getName() const = 0; + virtual std::string getSignature() const = 0; + virtual std::size_t getAlignment() const = 0; - std::string getMarshallingSignature(const DBus::Type::Generic& value); - std::string - getMarshallingSignature(const std::vector& value); -} // namespace Type -} // namespace DBus + virtual void marshall(MessageOStream&) const = 0; + virtual void unmarshall(MessageIStream&) = 0; + + // each non-container type should end with \n + // as convenience to the calling function + virtual std::string toString(const std::string& /*prefix*/) const = 0; + virtual std::string asString() const = 0; + + // Helper methods to return native types + static int asUnixFd(const Any& v); + static bool asBoolean(const Any& v); + static double asDouble(const Any& v); + static std::string asString(const Any& v); + static std::uint8_t asByte(const Any& v); + static std::int16_t asInt16(const Any& v); + static std::int32_t asInt32(const Any& v); + static std::int64_t asInt64(const Any& v); + static std::uint16_t asUint16(const Any& v); + static std::uint32_t asUint32(const Any& v); + static std::uint64_t asUint64(const Any& v); + + // Helper methods to return type-safe DBus references + static const Array& refArray(const Any& v); + static const Struct& refStruct(const Any& v); + static const Variant& refVariant(const Any& v); + static const Signature& refSignature(const Any& v); + static const DictEntry& refDictEntry(const Any& v); -#endif + // Helper Methods for during marshalling + static DBus::Type::Any create(const std::string& type); + static std::size_t getAlignment(const std::string& typeCode); + static bool isBasicTypeCode(const int code); + }; + + class Type::Basic : public Type {}; + + class Type::Container : public Type {}; + +} // namespace DBus diff --git a/src/dbus_type_any.cpp b/src/dbus_type_any.cpp new file mode 100644 index 0000000..5771f92 --- /dev/null +++ b/src/dbus_type_any.cpp @@ -0,0 +1,509 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#include "dbus_names.h" +#include "dbus_type_any.h" +#include "dbus_type_array.h" +#include "dbus_type_struct.h" +#include "dbus_type_variant.h" +#include "dbus_type_dictentry.h" + +#include + +DBus::Type::Any& DBus::Type::Any::clone(const Any& v) +{ + m_valueType = v.m_valueType; + switch (m_valueType) { + case Value::Byte: new(&m_byte) Byte(v); break; + case Value::Double: new(&m_double) Double(v); break; + case Value::UnixFd: new(&m_unixFd) UnixFd(v); break; + case Value::Boolean: new(&m_boolean) Boolean(v); break; + case Value::Int16: new(&m_int16) Int16(v); break; + case Value::Int32: new(&m_int32) Int32(v); break; + case Value::Int64: new(&m_int64) Int64(v); break; + case Value::Uint16: new(&m_uint16) Uint16(v); break; + case Value::Uint32: new(&m_uint32) Uint32(v); break; + case Value::Uint64: new(&m_uint64) Uint64(v); break; + case Value::String: new(&m_string) String(v); break; + case Value::Signature: new(&m_signature) Signature(v); break; + case Value::ObjectPath: new(&m_objectPath) ObjectPath(v); break; + + case Value::Array: m_array = new Array(*v.m_array); break; + case Value::Struct: m_struct = new Struct(*v.m_struct); break; + case Value::Variant: m_variant = new Variant(*v.m_variant); break; + case Value::DictEntry: m_dictEntry = new DictEntry(*v.m_dictEntry); break; + + case Value::None: break; + } + return *this; +} + +void DBus::Type::Any::destroy() +{ + switch(m_valueType) { + case Value::Byte: m_byte.~Byte(); break; + case Value::Signature: m_signature.~Signature(); break; + case Value::ObjectPath: m_objectPath.~ObjectPath(); break; + case Value::Boolean: m_boolean.~Boolean(); break; + case Value::Int16: m_int16.~Int16(); break; + case Value::Int32: m_int32.~Int32(); break; + case Value::Int64: m_int64.~Int64(); break; + case Value::Uint16: m_uint16.~Uint16(); break; + case Value::Uint32: m_uint32.~Uint32(); break; + case Value::Uint64: m_uint64.~Uint64(); break; + case Value::Double: m_double.~Double(); break; + case Value::UnixFd: m_unixFd.~UnixFd(); break; + case Value::String: m_string.~String(); break; + + case Value::Array: delete m_array; break; + case Value::Struct: delete m_struct; break; + case Value::Variant: delete m_variant; break; + case Value::DictEntry: delete m_dictEntry; break; + + case Value::None: break; + } +} + + + +DBus::Type::Any::Any(const Any& v) +{ + clone(v); +} + +DBus::Type::Any::Any(const std::uint8_t& v) + : m_valueType(Value::Byte) +{ + new(&m_byte) Byte(v); +} + +DBus::Type::Any::Any(const Byte& v) + : m_valueType(Value::Byte) +{ + new(&m_byte) Byte(v); +} + +DBus::Type::Any::Any(const bool& v) + : m_valueType(Value::Boolean) +{ + new(&m_boolean) Boolean(v); +} + +DBus::Type::Any::Any(const Boolean& v) + : m_valueType(Value::Boolean) +{ + new(&m_boolean) Boolean(v); +} + +DBus::Type::Any::Any(const std::int16_t& v) + : m_valueType(Value::Int16) +{ + new(&m_int16) Int16(v); +} + +DBus::Type::Any::Any(const Int16& v) + : m_valueType(Value::Int16) +{ + new(&m_int16) Int16(v); +} + +DBus::Type::Any::Any(const std::int32_t& v) + : m_valueType(Value::Int32) +{ + new(&m_int32) Int32(v); +} + +DBus::Type::Any::Any(const Int32& v) + : m_valueType(Value::Int32) +{ + new(&m_int32) Int32(v); +} + +DBus::Type::Any::Any(const std::int64_t& v) + : m_valueType(Value::Int64) +{ + new(&m_int64) Int64(v); +} + +DBus::Type::Any::Any(const Int64& v) + : m_valueType(Value::Int64) +{ + new(&m_int64) Int64(v); +} + +DBus::Type::Any::Any(const std::uint16_t& v) + : m_valueType(Value::Uint16) +{ + new(&m_uint16) Uint16(v); +} + +DBus::Type::Any::Any(const Uint16& v) + : m_valueType(Value::Uint16) +{ + new(&m_uint16) Uint16(v); +} + +DBus::Type::Any::Any(const std::uint32_t& v) + : m_valueType(Value::Uint32) +{ + new(&m_uint32) Uint32(v); +} + +DBus::Type::Any::Any(const Uint32& v) + : m_valueType(Value::Uint32) +{ + new(&m_uint32) Uint32(v); +} + +DBus::Type::Any::Any(const std::uint64_t& v) + : m_valueType(Value::Uint64) +{ + new(&m_uint64) Uint64(v); +} + +DBus::Type::Any::Any(const Uint64& v) + : m_valueType(Value::Uint64) +{ + new(&m_uint64) Uint64(v); +} + +DBus::Type::Any::Any(const UnixFd& v) + : m_valueType(Value::UnixFd) +{ + new(&m_unixFd) UnixFd(v); +} + +DBus::Type::Any::Any(const double& v) + : m_valueType(Value::Double) +{ + new(&m_double) Double(v); +} + +DBus::Type::Any::Any(const Double& v) + : m_valueType(Value::Double) +{ + new(&m_double) Double(v); +} + +DBus::Type::Any::Any(const char* v) + : m_valueType(Value::String) +{ + new(&m_string) String(v); +} + +DBus::Type::Any::Any(const std::string& v) + : m_valueType(Value::String) +{ + new(&m_string) String(v); +} + +DBus::Type::Any::Any(const Name& v) + : m_valueType(Value::String) +{ + new(&m_string) String(v); +} + +DBus::Type::Any::Any(const String& v) + : m_valueType(Value::String) +{ + new(&m_string) String(v); +} + +DBus::Type::Any::Any(const Signature& v) + : m_valueType(Value::Signature) +{ + new(&m_signature) Signature(v); +} + +DBus::Type::Any::Any(const DBus::ObjectPath& v) + : m_valueType(Value::ObjectPath) +{ + new(&m_objectPath) ObjectPath(v); +} + +DBus::Type::Any::Any(const DBus::Type::ObjectPath& v) + : m_valueType(Value::ObjectPath) +{ + new(&m_objectPath) ObjectPath(v); +} + + +DBus::Type::Any::Any(const Array& v) + : m_valueType(Value::Array) + , m_array(new Array(v)) +{} + +DBus::Type::Any::Any(const Struct& v) + : m_valueType(Value::Struct) + , m_struct(new Struct(v)) +{} + +DBus::Type::Any::Any(const Variant& v) + : m_valueType(Value::Variant) + , m_variant(new Variant(v)) +{} + +DBus::Type::Any::Any(const DictEntry& v) + : m_valueType(Value::DictEntry) + , m_dictEntry(new DictEntry(v)) +{} + + + +DBus::Type::Any& DBus::Type::Any::operator=(const Any& v) +{ + destroy(); + return clone(v); +} + + + +template +const T& DBus::Type::Any::tryVariantTo() const +{ + std::string what; + if (m_valueType == Value::None) + what = "empty object"; + else + what = value().getName(); + + if (m_valueType == Value::Variant) { + try { + return static_cast(m_variant->getValue()); + } catch (const BadCast& e) { + throw BadCast(what + " > " + e.what()); + } catch (...) { + throw; + } + } + + throw BadCast(what + ": cannot cast to " + T::name); +} + +DBus::Type::Any::operator const Byte&() const +{ + if (m_valueType == Value::Byte) + return m_byte; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Boolean&() const +{ + if (m_valueType == Value::Boolean) + return m_boolean; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Int16&() const +{ + if (m_valueType == Value::Int16) + return m_int16; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Int32&() const +{ + if (m_valueType == Value::Int32) + return m_int32; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Int64&() const +{ + if (m_valueType == Value::Int64) + return m_int64; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Uint16&() const +{ + if (m_valueType == Value::Uint16) + return m_uint16; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Uint32&() const +{ + if (m_valueType == Value::Uint32) + return m_uint32; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Uint64&() const +{ + if (m_valueType == Value::Uint64) + return m_uint64; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Double&() const +{ + if (m_valueType == Value::Double) + return m_double; + return tryVariantTo(); +} + +DBus::Type::Any::operator const UnixFd&() const +{ + if (m_valueType == Value::UnixFd) + return m_unixFd; + return tryVariantTo(); +} + +DBus::Type::Any::operator const String&() const +{ + if (m_valueType == Value::String) + return m_string; + if (m_valueType == Value::ObjectPath) + return m_objectPath; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Signature&() const +{ + if (m_valueType == Value::Signature) + return m_signature; + return tryVariantTo(); +} + +DBus::Type::Any::operator const ObjectPath&() const +{ + if (m_valueType == Value::ObjectPath) + return m_objectPath; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Array&() const +{ + if (m_valueType == Value::Array) + return *m_array; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Struct&() const +{ + if (m_valueType == Value::Struct) + return *m_struct; + return tryVariantTo(); +} + +DBus::Type::Any::operator const Variant&() const +{ + if (m_valueType == Value::Variant) + return *m_variant; + std::string what; + if (m_valueType == Value::None) + what = "empty object"; + else + what = value().getName(); + throw BadCast(what + ": can't cast to Variant"); +} + +DBus::Type::Any::operator const DictEntry&() const +{ + if (m_valueType == Value::DictEntry) + return *m_dictEntry; + return tryVariantTo(); +} + +bool DBus::Type::Any::isBasicType() const +{ + return Type::isBasicTypeCode(getSignature()[0]); +} + +const DBus::Type& DBus::Type::Any::value() const +{ + switch(m_valueType) { + case Value::Byte: return m_byte; + case Value::Boolean: return m_boolean; + case Value::Int16: return m_int16; + case Value::Int32: return m_int32; + case Value::Int64: return m_int64; + case Value::Uint16: return m_uint16; + case Value::Uint32: return m_uint32; + case Value::Uint64: return m_uint64; + case Value::Double: return m_double; + case Value::UnixFd: return m_unixFd; + case Value::String: return m_string; + case Value::Signature: return m_signature; + case Value::ObjectPath: return m_objectPath; + case Value::Array: return *m_array; + case Value::Struct: return *m_struct; + case Value::Variant: return *m_variant; + case Value::DictEntry: return *m_dictEntry; + default: + throw std::runtime_error("value: Any object has no type"); + } +} + +std::string DBus::Type::Any::getName() const +{ + if (m_valueType == Value::None) + return "Any{none}"; + else + return "Any{" + value().getName() + "}"; +} + +std::size_t DBus::Type::Any::getAlignment() const +{ + return value().getAlignment(); +} + +std::string DBus::Type::Any::getSignature() const +{ + return value().getSignature(); +} + +void DBus::Type::Any::marshall(MessageOStream& stream) const +{ + return value().marshall(stream); +} + +void DBus::Type::Any::unmarshall(MessageIStream& stream) +{ + switch(m_valueType) { + case Value::Byte: return m_byte.unmarshall(stream); + case Value::Boolean: return m_boolean.unmarshall(stream); + case Value::Int16: return m_int16.unmarshall(stream); + case Value::Int32: return m_int32.unmarshall(stream); + case Value::Int64: return m_int64.unmarshall(stream); + case Value::Uint16: return m_uint16.unmarshall(stream); + case Value::Uint32: return m_uint32.unmarshall(stream); + case Value::Uint64: return m_uint64.unmarshall(stream); + case Value::Double: return m_double.unmarshall(stream); + case Value::UnixFd: return m_unixFd.unmarshall(stream); + case Value::String: return m_string.unmarshall(stream); + case Value::Signature: return m_signature.unmarshall(stream); + case Value::ObjectPath: return m_objectPath.unmarshall(stream); + case Value::Array: return m_array->unmarshall(stream); + case Value::Struct: return m_struct->unmarshall(stream); + case Value::Variant: return m_variant->unmarshall(stream); + case Value::DictEntry: return m_dictEntry->unmarshall(stream); + case Value::None: + throw std::runtime_error("unmarshall: Any object has no type"); + } +} + +std::string DBus::Type::Any::toString(const std::string& prefix) const +{ + return value().toString(prefix); +} + +std::string DBus::Type::Any::asString() const +{ + return value().asString(); +} diff --git a/src/dbus_type_any.h b/src/dbus_type_any.h new file mode 100644 index 0000000..9c24ea7 --- /dev/null +++ b/src/dbus_type_any.h @@ -0,0 +1,161 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#pragma once + +#include "dbus_type_boolean.h" +#include "dbus_type_byte.h" +#include "dbus_type_double.h" +#include "dbus_type_int16.h" +#include "dbus_type_int32.h" +#include "dbus_type_int64.h" +#include "dbus_type_objectpath.h" +#include "dbus_type_signature.h" +#include "dbus_type_string.h" +#include "dbus_type_uint16.h" +#include "dbus_type_uint32.h" +#include "dbus_type_uint64.h" +#include "dbus_type_unixfd.h" + +#include + +namespace DBus { + + struct Name; + struct ObjectPath; + + class Type::Any : public Type { + protected: + enum class Value { + None, Byte, Boolean, UnixFd, Double, + Int16, Int32, Int64, Uint16, Uint32, Uint64, + String, ObjectPath, Signature, + Array, Struct, Variant, DictEntry + }; + + Value m_valueType = Value::None; + union { + Byte m_byte; + Boolean m_boolean; + UnixFd m_unixFd; + Double m_double; + + Int16 m_int16; + Int32 m_int32; + Int64 m_int64; + + Uint16 m_uint16; + Uint32 m_uint32; + Uint64 m_uint64; + + String m_string; + ObjectPath m_objectPath; + Signature m_signature; + + Array *m_array; + Struct *m_struct; + Variant *m_variant; + DictEntry *m_dictEntry; + }; + + template const T& tryVariantTo() const; + + const Type& value() const; + Any& clone(const Any& v); + void destroy(); + + public: + Any() {} + Any(const Any& v); + Any(const std::uint8_t& v); + Any(const Byte& v); + Any(const bool& v); + Any(const Boolean& v); + Any(const UnixFd& v); + Any(const double& v); + Any(const Double& v); + Any(const std::int16_t& v); + Any(const std::int32_t& v); + Any(const std::int64_t& v); + Any(const Int16& v); + Any(const Int32& v); + Any(const Int64& v); + Any(const std::uint16_t& v); + Any(const std::uint32_t& v); + Any(const std::uint64_t& v); + Any(const Uint16& v); + Any(const Uint32& v); + Any(const Uint64& v); + Any(const char* v); + Any(const std::string& v); + Any(const Name& v); + Any(const String& v); + Any(const DBus::ObjectPath& v); + Any(const DBus::Type::ObjectPath& v); + Any(const Signature& v); + Any(const Array& v); + Any(const Struct& v); + Any(const Variant& v); + Any(const DictEntry& v); + ~Any() override { destroy(); } + + Any& operator=(const Any&); + + explicit operator const Byte&() const; + explicit operator const Boolean&() const; + explicit operator const UnixFd&() const; + explicit operator const Double&() const; + explicit operator const Int16&() const; + explicit operator const Int32&() const; + explicit operator const Int64&() const; + explicit operator const Uint16&() const; + explicit operator const Uint32&() const; + explicit operator const Uint64&() const; + explicit operator const String&() const; + explicit operator const ObjectPath&() const; + explicit operator const Signature&() const; + explicit operator const Array&() const; + explicit operator const Struct&() const; + explicit operator const Variant&() const; + explicit operator const DictEntry&() const; + + bool isBasicType() const; + + std::string getName() const override; + std::size_t getAlignment() const override; + std::string getSignature() const override; + + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; + + std::string toString(const std::string& prefix = "") const override; + std::string asString() const override; + + class BadCast : public std::bad_cast { + const std::string m_what; + public: + BadCast(const std::string& what) + : m_what(what) + {} + + const char* what() const noexcept override { + return m_what.c_str(); + } + }; + }; + +} // namespace DBus diff --git a/src/dbus_type_array.cpp b/src/dbus_type_array.cpp index da4fbf4..2c9fc47 100644 --- a/src/dbus_type_array.cpp +++ b/src/dbus_type_array.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,182 +17,84 @@ // . #include "dbus_type_array.h" -#include "dbus_type.h" -#include "dbus_type_boolean.h" -#include "dbus_type_byte.h" -#include "dbus_type_dictentry.h" -#include "dbus_type_double.h" -#include "dbus_type_int16.h" -#include "dbus_type_int32.h" -#include "dbus_type_int64.h" -#include "dbus_type_objectpath.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_struct.h" -#include "dbus_type_uint16.h" -#include "dbus_type_uint32.h" -#include "dbus_type_uint64.h" -#include "dbus_type_variant.h" -#include - +#include "dbus_type_any.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" +#include "dbus_log.h" -const std::string DBus::Type::Array::s_StaticTypeCode("a"); - -size_t DBus::Type::Array::size() const { return contents.size(); } - -size_t DBus::Type::Array::add(const DBus::Type::Byte& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Boolean& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::ObjectPath& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Int16& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Uint16& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Int32& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Uint32& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Int64& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Uint64& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Double& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::String& v) -{ - contents.push_back(v); - return size(); -} - -size_t DBus::Type::Array::add(const DBus::Type::Variant& v) -{ - contents.push_back(v); - return size(); -} +#include -size_t DBus::Type::Array::add(const DBus::Type::Signature& v) -{ - contents.push_back(v); - return size(); -} +DBus::Type::Array::Array(const std::string& signature) + : m_signature(signature.substr(1)) +{} -size_t DBus::Type::Array::add(const DBus::Type::Struct& s) +std::size_t DBus::Type::Array::size() const { - contents.push_back(s); - return size(); + return m_contents.size(); } -size_t DBus::Type::Array::add(const DBus::Type::DictEntry& s) +std::size_t DBus::Type::Array::add(const DBus::Type::Any& v) { - contents.push_back(s); + if (m_signature.empty()) + m_signature = v.getSignature(); + else if (m_signature != v.getSignature()) + throw std::runtime_error( + "adding value to Array with wrong signature: " + v.getSignature()); + m_contents.push_back(v); return size(); } -const std::vector& DBus::Type::Array::getContents() const -{ - return contents; -} - void DBus::Type::Array::marshall(MessageOStream& stream) const { const size_t sizePos = stream.size(); stream.writeUint32(0); // Size does not include any padding to the first element - stream.pad(Type::getAlignment(Type::extractSignature(getSignature(), 1))); + stream.pad(Type::getAlignment(m_signature)); const size_t contentsStartPos = stream.size(); - marshallContents(stream); + for (auto& elem : m_contents) { + elem.marshall(stream); + } const uint32_t contentsSize = stream.size() - contentsStartPos; + if (contentsSize > Array::MaximumSize) + throw std::out_of_range("Array " + getSignature() + + ": size " + std::to_string(contentsSize) + " exceeds 64 MiB"); stream.data.replace(sizePos, 4, (char*)&contentsSize, sizeof(uint32_t)); } -void DBus::Type::Array::marshallContents(MessageOStream& stream) const -{ - for (size_t i = 0; i < contents.size(); ++i) { - DBus::Type::marshallData(contents[i], stream); - } -} - void DBus::Type::Array::unmarshall(MessageIStream& stream) { uint32_t size = 0; stream.read(&size); - std::string signature = Type::extractSignature(getSignature(), 1); - stream.align(Type::getAlignment(signature)); + if (size > Array::MaximumSize) + throw std::out_of_range("Array " + getSignature() + + ": size " + std::to_string(size) + " exceeds 64 MiB"); + + stream.align(Type::getAlignment(m_signature)); MessageIStream arrayStream(stream, size); while (!arrayStream.empty()) { - contents.push_back(DBus::Type::create(signature)); - DBus::Type::unmarshallData(contents.back(), arrayStream); + m_contents.push_back(DBus::Type::create(m_signature)); + m_contents.back().unmarshall(arrayStream); }; } -std::string DBus::Type::Array::getSignature() const -{ - if (contents.size()) { - return "a" + DBus::Type::getMarshallingSignature(contents[0]); - } - - return m_Signature; -} - std::string DBus::Type::Array::toString(const std::string& prefix) const { std::stringstream ss; - ss << prefix << "Array (" << getSignature() << ") [\n"; - for (size_t i = 0; i < contents.size(); ++i) { - ss << prefix << " [" << i << "] =\n"; - ss << DBus::Type::toString(contents[i], prefix + " "); + ss << name << " " << getSignature() << " : [\n"; + for (size_t i = 0; i < m_contents.size(); ++i) { + ss << prefix << " [" << i << "] " + << m_contents[i].toString(prefix + " "); } ss << prefix << "]\n"; return ss.str(); } -std::string DBus::Type::Array::asString() const { return "[array]"; } +std::string DBus::Type::Array::asString() const +{ + return getName() + " " + getSignature(); +} diff --git a/src/dbus_type_array.h b/src/dbus_type_array.h index 132aa4f..312b224 100644 --- a/src/dbus_type_array.h +++ b/src/dbus_type_array.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,70 +16,47 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_ARRAY_H -#define DBUS_TYPE_ARRAY_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type_any.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - class Byte; - class Boolean; - class ObjectPath; - class Int16; - class Uint16; - class Int32; - class Uint32; - class Int64; - class Uint64; - class Double; - class String; - class Signature; - class Variant; - class Struct; - class DictEntry; - - class Array : public Base { + class Type::Array : public Container { public: - size_t size() const; + Array() = default; + Array(const std::string& signature); + + static constexpr std::size_t MaximumSize = 67108864 /*64 MiB*/; - size_t add(const DBus::Type::Byte& v); - size_t add(const DBus::Type::Boolean& v); - size_t add(const DBus::Type::ObjectPath& v); - size_t add(const DBus::Type::Int16& v); - size_t add(const DBus::Type::Uint16& v); - size_t add(const DBus::Type::Int32& v); - size_t add(const DBus::Type::Uint32& v); - size_t add(const DBus::Type::Int64& v); - size_t add(const DBus::Type::Uint64& v); - size_t add(const DBus::Type::Double& v); - size_t add(const DBus::Type::String& v); - size_t add(const DBus::Type::Variant& v); - size_t add(const DBus::Type::Signature& v); - size_t add(const DBus::Type::Struct& s); - size_t add(const DBus::Type::DictEntry& s); + static constexpr const char *name = "Array"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 'a'; - size_t getAlignment() const { return 4; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); - void marshallContents(MessageOStream& stream) const; + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; } + std::string getSignature() const override { return code + m_signature; } - std::string getSignature() const; + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string& prefix = "") const override; + std::string asString() const override; - const std::vector& getContents() const; + std::size_t size() const; + std::size_t add(const Any& v); - static const std::string s_StaticTypeCode; + using ConstIterator = std::vector::const_iterator; + ConstIterator begin() const { return m_contents.cbegin(); } + ConstIterator end() const { return m_contents.cend(); } + + const Any& operator[](std::size_t pos) const { return m_contents.at(pos); } + const Array* sis() const {return this;} protected: - std::vector contents; + std::string m_signature; + std::vector m_contents; }; -} // namespace Type + } // namespace DBus -#endif diff --git a/src/dbus_type_base.h b/src/dbus_type_base.h deleted file mode 100644 index 19bf869..0000000 --- a/src/dbus_type_base.h +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#ifndef DBUS_TYPE_BASE_H -#define DBUS_TYPE_BASE_H - -#include -#include - -namespace DBus { - -namespace Type { - - // variant didn't work because the DBus variant type allows structs within - // structs, meaning we need a forward reference. Any suggestsions? - // typedef boost::variant Generic; - - // I dropped any when attempting to retrieve the specific type at compile time. - // (Since I needed it to bind the unmarshall method) Even grabbing a ptr to the - // base class seemed impossible. I returned to it when a custom class seemed too - // much effort. https://theboostcpplibraries.com/boost.any - typedef boost::any Generic; - // TODO: I would like to have Generic derive from boost:any so the asByte - // methods can be added to it. But there's a lot of plumbing since operator= et - // al need templating. It'd be as quick to copy+paste boost::any as Generic and - // add the methods there. (I don't like that, tho.) - - // This describes multiple complete types. e.g. ss, i(ii) - // (There doesn't appear to be an official name other than "multiple complete - // type" and I couldn't think of better) - struct CompositeBlock { - std::vector m_TypeList; - }; - - // This base class exists primarily to act as marshalling helpers. - // The use of boost::any prohibits our ability to grab typed data, and use - // virtual methods. - class Base { - public: - Base() {} - - size_t getAlignment() const { return 1; } - void setSignature(const std::string& type); - std::string getSignature() const; - - std::string toString(const std::string& prefix) - const; // each non-container type should end with \n as as convenience to - // the calling function - std::string asString() const; - - protected: - std::string m_Signature; - }; -} // namespace Type -} // namespace DBus - -#endif diff --git a/src/dbus_type_boolean.cpp b/src/dbus_type_boolean.cpp index 4651dc5..b2eb5aa 100644 --- a/src/dbus_type_boolean.cpp +++ b/src/dbus_type_boolean.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -20,43 +21,32 @@ #include "dbus_messageostream.h" #include -const std::string DBus::Type::Boolean::s_StaticTypeCode("b"); - -DBus::Type::Boolean::Boolean() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Boolean::Boolean(uint32_t v) +DBus::Type::Boolean::Boolean(bool v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Boolean::marshall(MessageOStream& stream) const { - stream.writeBoolean(m_Value ? true : false); + stream.writeBoolean(m_Value); } void DBus::Type::Boolean::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + uint32_t tmp; + stream.read(&tmp); + m_Value = tmp == 0 ? false : true; } -std::string DBus::Type::Boolean::toString(const std::string& prefix) const +std::string DBus::Type::Boolean::toString(const std::string&) const { std::stringstream ss; - - ss << prefix << "Boolean "; - ss << (m_Value ? "True" : "Talse") << "\n"; - + ss << "Boolean " << m_Value << '\n'; return ss.str(); } std::string DBus::Type::Boolean::asString() const { std::stringstream ss; - ss << (size_t)m_Value; + ss << m_Value; return ss.str(); } diff --git a/src/dbus_type_boolean.h b/src/dbus_type_boolean.h index deaa268..1443315 100644 --- a/src/dbus_type_boolean.h +++ b/src/dbus_type_boolean.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,36 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_BOOLEAN_H -#define DBUS_TYPE_BOOLEAN_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Boolean : public Base { + class Type::Boolean : public Basic { public: - Boolean(); - Boolean(uint32_t v); + Boolean() = default; + Boolean(bool v); + + static constexpr const char *name = "Boolean"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 'b'; - size_t getAlignment() const { return 4; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - static const std::string s_StaticTypeCode; + std::string toString(const std::string&) const override; + std::string asString() const override; + + operator bool() const { return m_Value; }; protected: - uint32_t m_Value; + bool m_Value = false; }; -} // namespace Type + } // namespace DBus -#endif diff --git a/src/dbus_type_byte.cpp b/src/dbus_type_byte.cpp index 528fc63..e29d355 100644 --- a/src/dbus_type_byte.cpp +++ b/src/dbus_type_byte.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,21 +22,9 @@ #include #include -const std::string DBus::Type::Byte::s_StaticTypeCode("y"); - -DBus::Type::Byte::Byte() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Byte::Byte(size_t v) +DBus::Type::Byte::Byte(const std::uint8_t& v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Byte::Byte(const Byte& b) { m_Value = b.m_Value; } +{} void DBus::Type::Byte::marshall(MessageOStream& stream) const { @@ -47,15 +36,15 @@ void DBus::Type::Byte::unmarshall(MessageIStream& data) m_Value = data.read(); } -std::string DBus::Type::Byte::toString(const std::string& prefix) const +std::string DBus::Type::Byte::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Byte "; - ss << (size_t)m_Value << " (0x" << std::hex << std::setfill('0') - << std::setw(2) << int(uint8_t(m_Value)) << ")\n"; + oss << "Byte " + << (size_t)m_Value << " (0x" << std::hex << std::setfill('0') + << std::setw(2) << int(uint8_t(m_Value)) << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Byte::asString() const diff --git a/src/dbus_type_byte.h b/src/dbus_type_byte.h index a51d72d..044b8aa 100644 --- a/src/dbus_type_byte.h +++ b/src/dbus_type_byte.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,36 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_BYTE_H -#define DBUS_TYPE_BYTE_H +#pragma once -#include "dbus_type_base.h" -namespace DBus { -class MessageOStream; -class MessageIStream; +#include "dbus_type.h" -namespace Type { +namespace DBus { - class Byte : public Base { + class Type::Byte : public Basic { public: - Byte(); - Byte(size_t v); - Byte(const Byte& b); + Byte() = default; + Byte(const std::uint8_t& b); + + static constexpr const char *name = "Byte"; + static constexpr const char alignment = 1; + static constexpr const char code = 'y'; - std::string getSignature() const { return s_StaticTypeCode; } + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& data); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& data) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::uint8_t() const { return m_Value; }; protected: - uint8_t m_Value; + std::uint8_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_dictentry.cpp b/src/dbus_type_dictentry.cpp index 55c62c0..f1a7c57 100644 --- a/src/dbus_type_dictentry.cpp +++ b/src/dbus_type_dictentry.cpp @@ -15,18 +15,18 @@ // file named COPYING. If you do not have this file see // . +#include "dbus_type_signature.h" #include "dbus_type_dictentry.h" #include "dbus_type.h" #include "dbus_type_string.h" #include "dbus_type_uint32.h" -#include #include "dbus_message.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" #include "dbus_messageprotocol.h" -#include "dbus_validation.h" +#include /* Structs and dict entries are marshalled in the same way as their contents, but their alignment is always to an 8-byte boundary, even if their contents would @@ -45,73 +45,67 @@ The first field in the DICT_ENTRY is always the key. A message is considered corrupt if the same key occurs twice in the same array of DICT_ENTRY. However, for performance reasons implementations are not required to reject dicts with duplicate keys. - - */ -const std::string DBus::Type::DictEntry::s_StaticTypeCode("{"); - -DBus::Type::DictEntry::DictEntry(const DBus::Type::Generic& key, - const DBus::Type::Generic& value) +DBus::Type::DictEntry::DictEntry(const std::string& signature) + : m_signature(signature.substr(1, signature.size() - 2)) { - set(key, value); } -DBus::Type::DictEntry::DictEntry(const std::string& key, std::string& value) +DBus::Type::DictEntry::DictEntry(const DBus::Type::Any& key, + const DBus::Type::Any& value) { - set(DBus::Type::String(key), DBus::Type::String(value)); + set(key, value); } -DBus::Type::DictEntry::DictEntry(const std::string& key, uint32_t value) +void DBus::Type::DictEntry::set(const DBus::Type::Any& key, + const DBus::Type::Any& value) { - set(DBus::Type::String(key), DBus::Type::Uint32(value)); + if (!key.isBasicType()) + throw std::runtime_error("DictEntry key has invalid basic type: " + + key.getSignature()); + m_Value = std::make_pair(key, value); } -// -void DBus::Type::DictEntry::set(const DBus::Type::Generic& key, - const DBus::Type::Generic& value) +std::string DBus::Type::DictEntry::getSignature() const { - m_Value = std::make_pair(key, value); - m_Signature = "{" + Type::getMarshallingSignature(m_Value.first) + Type::getMarshallingSignature(m_Value.second) + "}"; + return code_start + key().getSignature() + value().getSignature() + code_end; } -std::string DBus::Type::DictEntry::getSignature() const { return m_Signature; } - void DBus::Type::DictEntry::marshall(MessageOStream& stream) const { - stream.pad8(); - DBus::Type::marshallData(m_Value.first, stream); - DBus::Type::marshallData(m_Value.second, stream); + key().marshall(stream); + value().marshall(stream); } void DBus::Type::DictEntry::unmarshall(MessageIStream& stream) { stream.align(8); - const char key_type = getSignature().at(1); - DBus::Validation::throwOnInvalidBasicType(key_type); - m_Value.first = Type::create(std::string(1, key_type)); - m_Value.second = Type::create(DBus::Type::extractSignature(getSignature(), 2)); - DBus::Type::unmarshallData(m_Value.first, stream); - DBus::Type::unmarshallData(m_Value.second, stream); + Any key = Type::create(m_signature.getNextTypeCode()); + Any value = Type::create(m_signature.getNextTypeCode()); + + key.unmarshall(stream); + value.unmarshall(stream); + + set(key, value); } std::string DBus::Type::DictEntry::toString(const std::string& prefix) const { - std::stringstream ss; - std::string contents_prefix(prefix); - contents_prefix += " "; - - ss << prefix << "DictEntry (" << getSignature() << ") : {\n"; - ss << prefix - << " key: " << DBus::Type::toString(m_Value.first, contents_prefix); - ss << prefix - << " value: " << DBus::Type::toString(m_Value.second, contents_prefix); - ss << prefix << "}\n"; - - return ss.str(); + std::ostringstream oss; + + oss << "DictEntry " << getSignature() << " : {\n"; + oss << prefix << " key: " << key().toString(); + oss << prefix << " value: " << value().toString(prefix + " "); + oss << prefix << "}\n"; + + return oss.str(); } -std::string DBus::Type::DictEntry::asString() const { return "[DictEntry]"; } +std::string DBus::Type::DictEntry::asString() const +{ + return getName() + " " + getSignature(); +} diff --git a/src/dbus_type_dictentry.h b/src/dbus_type_dictentry.h index 5713714..5a5f1a4 100644 --- a/src/dbus_type_dictentry.h +++ b/src/dbus_type_dictentry.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,41 +16,44 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_DICTENTRY_H -#define DBUS_TYPE_DICTENTRY_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type_any.h" namespace DBus { -class MessageOStream; -class MessageIStream; + class Signature; -namespace Type { - - class DictEntry : public Base { + class Type::DictEntry : public Container { public: - DictEntry() {} - DictEntry(const DBus::Type::Generic& key, const DBus::Type::Generic& value); - DictEntry(const std::string& key, std::string& value); - DictEntry(const std::string& key, uint32_t value); + DictEntry(const std::string& signature); + DictEntry(const DBus::Type::Any& key, const DBus::Type::Any& value); + + static constexpr const char *name = "DictEntry"; + static constexpr const char code_start = '{'; + static constexpr const char code_end = '}'; + + static constexpr std::size_t alignment = 8; + static constexpr const char code = code_start; - std::string getSignature() const; - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - void set(const DBus::Type::Generic& key, const DBus::Type::Generic& value); + const Any& key() const { return m_Value.first; }; + const Any& value() const { return m_Value.second; }; - static const std::string s_StaticTypeCode; + std::string toString(const std::string& prefix = "") const override; + std::string asString() const override; + + void set(const DBus::Type::Any& key, const DBus::Type::Any& value); protected: - std::pair m_Value; + Signature m_signature; + std::pair m_Value = {}; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_double.cpp b/src/dbus_type_double.cpp index 439eadd..8c8de3b 100644 --- a/src/dbus_type_double.cpp +++ b/src/dbus_type_double.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -20,19 +21,9 @@ #include "dbus_messageostream.h" #include -const std::string DBus::Type::Double::s_StaticTypeCode("d"); - -DBus::Type::Double::Double() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Double::Double(double v) +DBus::Type::Double::Double(const double& v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Double::marshall(MessageOStream& stream) const { @@ -44,19 +35,14 @@ void DBus::Type::Double::unmarshall(MessageIStream& stream) stream.read(&m_Value); } -std::string DBus::Type::Double::toString(const std::string& prefix) const +std::string DBus::Type::Double::toString(const std::string&) const { - std::stringstream ss; - - ss << prefix << "Double "; - ss << m_Value << "\n"; - - return ss.str(); + std::ostringstream oss; + oss << "Double " << m_Value << "\n"; + return oss.str(); } std::string DBus::Type::Double::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_double.h b/src/dbus_type_double.h index c20589f..c8afe34 100644 --- a/src/dbus_type_double.h +++ b/src/dbus_type_double.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_DOUBLE_H -#define DBUS_TYPE_DOUBLE_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Double : public Base { + class Type::Double : public Basic { public: - Double(); - Double(double v); + Double() = default; + Double(const double& v); + + static constexpr const char *name = "Double"; + static constexpr std::size_t alignment = 8; + static constexpr const char code = 'd'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator double() const { return m_Value; }; protected: - double m_Value; + double m_Value = 0.0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_int16.cpp b/src/dbus_type_int16.cpp index 58dd02b..2f10e52 100644 --- a/src/dbus_type_int16.cpp +++ b/src/dbus_type_int16.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Int16::s_StaticTypeCode("n"); - -DBus::Type::Int16::Int16() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Int16::Int16(int16_t v) +DBus::Type::Int16::Int16(const std::int16_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Int16::marshall(MessageOStream& stream) const { @@ -42,18 +33,18 @@ void DBus::Type::Int16::marshall(MessageOStream& stream) const void DBus::Type::Int16::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Int16::toString(const std::string& prefix) const +std::string DBus::Type::Int16::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Int16 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(2) - << m_Value << ")\n"; + oss << "Int16 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(4) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Int16::asString() const diff --git a/src/dbus_type_int16.h b/src/dbus_type_int16.h index 093a0dd..169b669 100644 --- a/src/dbus_type_int16.h +++ b/src/dbus_type_int16.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_INT16_H -#define DBUS_TYPE_INT16_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Int16 : public Base { + class Type::Int16 : public Basic { public: - Int16(); - Int16(int16_t v); + Int16() = default; + Int16(const std::int16_t v); + + static constexpr const char *name = "Int16"; + static constexpr std::size_t alignment = 2; + static constexpr const char code = 'n'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - size_t getAlignment() const { return 2; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::int16_t() const { return m_Value; }; protected: - int16_t m_Value; + std::int16_t m_Value; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_int32.cpp b/src/dbus_type_int32.cpp index 5f3509d..f818061 100644 --- a/src/dbus_type_int32.cpp +++ b/src/dbus_type_int32.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Int32::s_StaticTypeCode("i"); - -DBus::Type::Int32::Int32() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Int32::Int32(int32_t v) +DBus::Type::Int32::Int32(const std::int32_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Int32::marshall(MessageOStream& stream) const { @@ -42,23 +33,21 @@ void DBus::Type::Int32::marshall(MessageOStream& stream) const void DBus::Type::Int32::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Int32::toString(const std::string& prefix) const +std::string DBus::Type::Int32::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Int32 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(4) - << m_Value << ")\n"; + oss << "Int32 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(8) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Int32::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_int32.h b/src/dbus_type_int32.h index c45c2f0..ec3bd8f 100644 --- a/src/dbus_type_int32.h +++ b/src/dbus_type_int32.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,36 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_INT32_H -#define DBUS_TYPE_INT32_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" +#include namespace DBus { -class MessageOStream; -class MessageIStream; - -namespace Type { - - class Int32 : public Base { + class Type::Int32 : public Basic { public: - Int32(); - Int32(int32_t v); + Int32() = default; + Int32(const std::int32_t v); + + static constexpr const char *name = "Int32"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 'i'; - size_t getAlignment() const { return 4; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - static const std::string s_StaticTypeCode; + std::string toString(const std::string&) const override; + std::string asString() const override; + operator std::int32_t() const { return m_Value; }; protected: - int32_t m_Value; + std::int32_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_int64.cpp b/src/dbus_type_int64.cpp index 7c8024e..4d5156f 100644 --- a/src/dbus_type_int64.cpp +++ b/src/dbus_type_int64.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Int64::s_StaticTypeCode("x"); - -DBus::Type::Int64::Int64() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Int64::Int64(int64_t v) +DBus::Type::Int64::Int64(const std::int64_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Int64::marshall(MessageOStream& stream) const { @@ -42,23 +33,21 @@ void DBus::Type::Int64::marshall(MessageOStream& stream) const void DBus::Type::Int64::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Int64::toString(const std::string& prefix) const +std::string DBus::Type::Int64::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Int64 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(8) - << m_Value << ")\n"; + oss << "Int64 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(16) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Int64::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_int64.h b/src/dbus_type_int64.h index 57d1cbe..1dcc7b7 100644 --- a/src/dbus_type_int64.h +++ b/src/dbus_type_int64.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_INT64_H -#define DBUS_TYPE_INT64_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Int64 : public Base { + class Type::Int64 : public Basic { public: - Int64(); - Int64(int64_t v); + Int64() = default; + Int64(const std::int64_t v); + + static constexpr const char *name = "Int64"; + static constexpr std::size_t alignment = 8; + static constexpr const char code = 'x'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::int64_t() const { return m_Value; }; protected: - int64_t m_Value; + std::int64_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_objectpath.cpp b/src/dbus_type_objectpath.cpp index 5694f8a..22d05c3 100644 --- a/src/dbus_type_objectpath.cpp +++ b/src/dbus_type_objectpath.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,23 +16,25 @@ // file named COPYING. If you do not have this file see // . +#include "dbus_names.h" #include "dbus_type_objectpath.h" #include "dbus_messageostream.h" -#include "dbus_type_string.h" -const std::string DBus::Type::ObjectPath::s_StaticTypeCode("o"); - -DBus::Type::ObjectPath::ObjectPath() { setSignature(s_StaticTypeCode); } +DBus::Type::ObjectPath::ObjectPath(const char* v) + : ObjectPath(DBus::ObjectPath(v)) +{} DBus::Type::ObjectPath::ObjectPath(const std::string& v) + : ObjectPath(DBus::ObjectPath(v)) +{} + +DBus::Type::ObjectPath::ObjectPath(const DBus::ObjectPath& v) : String(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::ObjectPath::marshall(MessageOStream& stream) const { - // Exactly the same as STRING except the content must be a valid object path - // (see above). + if (m_Value.empty()) + throw InvalidObjectPath("Cannot marshall empty ObjectPath"); Type::String::marshall(stream); } diff --git a/src/dbus_type_objectpath.h b/src/dbus_type_objectpath.h index aad658a..117fefb 100644 --- a/src/dbus_type_objectpath.h +++ b/src/dbus_type_objectpath.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,26 +16,28 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_OBJECTPATH_H -#define DBUS_TYPE_OBJECTPATH_H +#pragma once #include "dbus_type_string.h" namespace DBus { -class MessageOStream; -namespace Type { + struct ObjectPath; - class ObjectPath : public String { + class Type::ObjectPath : public String { public: - ObjectPath(); + ObjectPath() = default; + ObjectPath(const char* v); ObjectPath(const std::string& v); + ObjectPath(const DBus::ObjectPath& v); - std::string getSignature() const { return s_StaticTypeCode; } - void marshall(MessageOStream& stream) const; - static const std::string s_StaticTypeCode; + static constexpr const char *name = "ObjectPath"; + static constexpr const char code = 'o'; + + std::string getName() const override { return name; } + std::string getSignature() const override { return std::string(1, code); }; + + void marshall(MessageOStream& stream) const override; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_signature.cpp b/src/dbus_type_signature.cpp index 6d0b95f..598e38b 100644 --- a/src/dbus_type_signature.cpp +++ b/src/dbus_type_signature.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,21 +17,132 @@ // . #include "dbus_type_signature.h" +#include "dbus_type_struct.h" +#include "dbus_type_array.h" +#include "dbus_type_dictentry.h" +#include "dbus_type_variant.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" -#include - -const std::string DBus::Type::Signature::s_StaticTypeCode("g"); +#include "dbus_log.h" -DBus::Type::Signature::Signature() { setSignature(s_StaticTypeCode); } +#include -DBus::Type::Signature::Signature(const std::string& v) +DBus::Type::Signature::Signature(const std::string& v, std::size_t level) : m_Value(v) { - setSignature(s_StaticTypeCode); -} + auto logAndThrow = [this](const char *error) { + Log::write(Log::ERROR, + "DBus :: signature '%s' invalid : %s\n", + m_Value.c_str(), error); + throw InvalidSignature(m_Value + "' : " + error); + }; + + enum Container { Array, Struct, DictEntry }; + + struct ContainerStack { + inline std::size_t size() { return m_stack.size(); }; + inline std::size_t level() { return m_arrayLevel + m_structLevel; }; + + inline bool push(Container type) { + m_stack.push_back(type); + if (type == Struct) + return ++m_structLevel <= MaxStructDepth; + else if (type == Array) + return ++m_arrayLevel <= MaxArrayDepth; + return true; + }; + + inline void pop() { + if (m_stack.empty()) + return; + if (topmostIs(Struct)) + --m_structLevel; + else if (topmostIs(Array)) + --m_arrayLevel; + m_stack.resize(m_stack.size() - 1); + while (topmostIs(Array)) + pop(); + }; + + inline bool topmostIs(const Container type) const { + return m_stack.size() && m_stack.back() == type; + }; + + protected: + std::size_t m_arrayLevel = 0; + std::size_t m_structLevel = 0; + std::vector m_stack; + }; + + if (m_Value.size() > MaxLength) + logAndThrow("length exceeds 255"); -const std::string& DBus::Type::Signature::getValue() const { return m_Value; } + ContainerStack stack; + bool haveStructElement; + bool haveDictEntryKey; + bool haveDictEntryVal; + + for (const int code : m_Value) { + // Check the key type if we're inside a DICT_ENTRY + if (stack.topmostIs(DictEntry)) { + if (!haveDictEntryKey) { + if (!isBasicTypeCode(code)) + logAndThrow("DictEntry key is not a basic type"); + haveDictEntryKey = true; + continue; + } else if (code != Type::DictEntry::code_end) { + if (haveDictEntryVal) + logAndThrow("DictEntry has multiple values"); + haveDictEntryVal = true; + } + } + // Count anything as element if we're inside a STRUCT + else if (stack.topmostIs(Struct) && code != Type::Struct::code_end) { + haveStructElement = true; + } + + if (code == Array::code) { + if (stack.push(Array) == false) + logAndThrow("Arrays nested more than 32 times"); + if (level + stack.level() > MaxDepth) + logAndThrow("total message depth larger then 64"); + } else if (code == Struct::code_start) { + if (stack.push(Struct) == false) + logAndThrow("Structs nested more than 32 times"); + if (level + stack.level() > MaxDepth) + logAndThrow("total message depth larger then 64"); + haveStructElement = false; + } else if (code == Struct::code_end) { + if (!stack.topmostIs(Struct) || !haveStructElement) + logAndThrow("Struct end unexpected"); + stack.pop(); + } else if (code == DictEntry::code_start) { + if (!stack.topmostIs(Array)) + logAndThrow("DictEntry outside of an ARRAY"); + stack.push(DictEntry); + haveDictEntryKey = false; + haveDictEntryVal = false; + } else if (code == DictEntry::code_end) { + if (!stack.topmostIs(DictEntry) || !haveDictEntryVal) + logAndThrow("DictEntry end unexpected"); + stack.pop(); + } else if (isBasicTypeCode(code) || code == Variant::code) { + if (stack.topmostIs(Array)) + stack.pop(); + } else { + logAndThrow("unknown type code"); + } + } + + if (stack.size()) { + if (stack.topmostIs(DictEntry)) + logAndThrow("DictEntry incomplete"); + else if (stack.topmostIs(Array)) + logAndThrow("Array incomplete"); + else if (stack.topmostIs(Struct)) + logAndThrow("Struct incomplete"); + } +} void DBus::Type::Signature::marshall(MessageOStream& stream) const { @@ -44,13 +156,60 @@ void DBus::Type::Signature::unmarshall(MessageIStream& stream) stream.read(); // read null; } -std::string DBus::Type::Signature::toString(const std::string& prefix) const +std::string DBus::Type::Signature::getNextTypeCode() { - std::stringstream ss; + if (m_typeCodeIndex >= m_Value.size()) { + m_typeCodeIndex = 0; + return ""; + } + + // Handle STRUCT + if (m_Value[m_typeCodeIndex] == Struct::code_start) { + // Find the matching parenthesis, and skip over element codes + std::size_t open_brackets = 1; + std::size_t start = m_typeCodeIndex; + while (++m_typeCodeIndex < m_Value.size()) { + if (m_Value[m_typeCodeIndex] == Struct::code_start) { + ++open_brackets; + } else if (m_Value[m_typeCodeIndex] == Struct::code_end) { + if (--open_brackets == 0) { + return m_Value.substr(start, ++m_typeCodeIndex - start); + } + } + } + } + // Handle DICT_ENTRY + else if (m_Value[m_typeCodeIndex] == DictEntry::code_start) { + // Find the matching brace, and skip over element codes + std::size_t open_brackets = 1; + std::size_t start = m_typeCodeIndex; + while (++m_typeCodeIndex < m_Value.size()) { + if (m_Value[m_typeCodeIndex] == DictEntry::code_start) { + ++open_brackets; + } else if (m_Value[m_typeCodeIndex] == DictEntry::code_end) { + if (--open_brackets == 0) { + return m_Value.substr(start, ++m_typeCodeIndex - start); + } + } + } + } - ss << prefix << "Signature (" << m_Value << ")\n"; + std::string typeCode(1, m_Value[m_typeCodeIndex]); + // Handle ARRAY + if (m_Value[m_typeCodeIndex++] == Array::code) + typeCode += getNextTypeCode(); - return ss.str(); + return typeCode; } -std::string DBus::Type::Signature::asString() const { return m_Value; } +std::string DBus::Type::Signature::toString(const std::string&) const +{ + std::ostringstream oss; + oss << "Signature '" << m_Value << "'\n"; + return oss.str(); +} + +std::string DBus::Type::Signature::asString() const +{ + return m_Value; +} diff --git a/src/dbus_type_signature.h b/src/dbus_type_signature.h index 8231601..54c58a5 100644 --- a/src/dbus_type_signature.h +++ b/src/dbus_type_signature.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,54 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_SIGNATURE_H -#define DBUS_TYPE_SIGNATURE_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" + +#include namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { + using InvalidSignature = std::runtime_error; - class Signature : public Base { + class Type::Signature : public Basic { public: - Signature(); - Signature(const std::string& v); + Signature() = default; + Signature(const Signature& other) = default; + Signature(const std::string& v, std::size_t height = 0); + + Signature& operator=(const Signature& other) = default; + + bool operator==(const Signature& other) { + return m_Value == other.m_Value; } + bool operator!=(const Signature& other) { + return m_Value != other.m_Value; } + + static constexpr const char *name = "Signature"; + static constexpr std::size_t alignment = 1; + static constexpr const char code = 'g'; - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; } + std::string getSignature() const override { return std::string(1, code); } - std::string toString(const std::string& prefix = "") const; - std::string asString() const; - const std::string& getValue() const; + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - static const std::string s_StaticTypeCode; + std::string toString(const std::string&) const override; + std::string asString() const override; + + bool empty() const { return m_Value.empty(); } + std::string getNextTypeCode(); protected: + static constexpr std::size_t MaxLength = 255; + static constexpr std::size_t MaxStructDepth = 32; + static constexpr std::size_t MaxArrayDepth = 32; + static constexpr std::size_t MaxDepth = MaxStructDepth + MaxArrayDepth; + + std::size_t m_typeCodeIndex = 0; std::string m_Value; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_string.cpp b/src/dbus_type_string.cpp index 9fb2db7..18e6edb 100644 --- a/src/dbus_type_string.cpp +++ b/src/dbus_type_string.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,15 +22,14 @@ #include "dbus_type.h" #include -const std::string DBus::Type::String::s_StaticTypeCode("s"); -DBus::Type::String::String() { setSignature(s_StaticTypeCode); } +DBus::Type::String::String(const char* v) + : m_Value(v) +{} DBus::Type::String::String(const std::string& v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::String::marshall(MessageOStream& stream) const { @@ -47,13 +47,16 @@ void DBus::Type::String::unmarshall(MessageIStream& stream) stream.read(); // null byte } -std::string DBus::Type::String::toString(const std::string& prefix) const +std::string DBus::Type::String::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "String (" << m_Value.size() << ") \"" << m_Value << "\"\n"; + oss << "String (" << m_Value.size() << ") \"" << m_Value << "\"\n"; - return ss.str(); + return oss.str(); } -std::string DBus::Type::String::asString() const { return m_Value; } +std::string DBus::Type::String::asString() const +{ + return m_Value; +} diff --git a/src/dbus_type_string.h b/src/dbus_type_string.h index 1963f59..087af34 100644 --- a/src/dbus_type_string.h +++ b/src/dbus_type_string.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,36 +16,36 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_STRING_H -#define DBUS_TYPE_STRING_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class String : public Base { + class Type::String : public Basic { public: - String(); + String() = default; + String(const char* v); String(const std::string& v); - std::string getSignature() const { return s_StaticTypeCode; } - size_t getAlignment() const { return 4; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + static constexpr const char *name = "String"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 's'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; + + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::string() const { return m_Value; }; protected: std::string m_Value; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_struct.cpp b/src/dbus_type_struct.cpp index 56313ed..8213b26 100644 --- a/src/dbus_type_struct.cpp +++ b/src/dbus_type_struct.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,24 +16,13 @@ // file named COPYING. If you do not have this file see // . +#include "dbus_type_signature.h" #include "dbus_type_struct.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" -#include "dbus_type_boolean.h" -#include "dbus_type_byte.h" -#include "dbus_type_dictentry.h" -#include "dbus_type_double.h" -#include "dbus_type_int16.h" -#include "dbus_type_int32.h" -#include "dbus_type_int64.h" -#include "dbus_type_objectpath.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_uint16.h" -#include "dbus_type_uint32.h" -#include "dbus_type_uint64.h" -#include "dbus_type_variant.h" + #include +#include /* Structs and dict entries are marshalled in the same way as their contents, but @@ -40,101 +30,45 @@ their alignment is always to an 8-byte boundary, even if their contents would normally be less strictly aligned. */ -std::string DBus::Type::Struct::s_StaticTypeCode("("); - -DBus::Type::Struct::Struct() -{ - // setSignature(s_StaticTypeCode); -} - -void DBus::Type::Struct::add(const DBus::Type::Byte& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Boolean& v) +DBus::Type::Struct::Struct(const Signature& signature) + : m_Signature(signature) { - m_Value.push_back(v); } -void DBus::Type::Struct::add(const DBus::Type::ObjectPath& v) +DBus::Type::Struct::Struct(const std::string& signature) + : m_Signature(signature.substr(1, signature.size() - 2)) { - m_Value.push_back(v); } -void DBus::Type::Struct::add(const DBus::Type::Int16& v) +void DBus::Type::Struct::add(const DBus::Type::Any& v) { - m_Value.push_back(v); + m_Signature = m_Signature.asString() + v.getSignature(); + m_elements.push_back(v); } -void DBus::Type::Struct::add(const DBus::Type::Uint16& v) +std::string DBus::Type::Struct::getSignature() const { - m_Value.push_back(v); -} + if (m_Signature.empty()) + throw std::runtime_error("Struct::getSignature() called when undefined"); -void DBus::Type::Struct::add(const DBus::Type::Int32& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Uint32& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Int64& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Uint64& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Double& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::String& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Variant& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Signature& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::Struct& v) -{ - m_Value.push_back(v); -} - -void DBus::Type::Struct::add(const DBus::Type::DictEntry& v) -{ - m_Value.push_back(v); + return code_start + m_Signature.asString() + code_end; } // BUGWARN: This should work in all cases, but I'm thinking of moving it into // a utility method (like all unmarshall). -void DBus::Type::Struct::clear() { m_Value.clear(); } +void DBus::Type::Struct::clear() +{ + m_elements.clear(); +} void DBus::Type::Struct::marshall(MessageOStream& stream) const { - // A struct must start on an 8-byte boundary regardless of the type of the // struct fields. stream.pad8(); - for (auto it : m_Value) { - DBus::Type::marshallData(it, stream); + for (auto element : m_elements) { + element.marshall(stream); } } @@ -143,28 +77,29 @@ void DBus::Type::Struct::unmarshall(MessageIStream& stream) // A struct must start on an 8-byte boundary regardless of the type of the // struct fields. stream.align(8); - size_t signatureIndex = 1; // skip first '(' - do { - const std::string signature = Type::extractSignature(m_Signature, signatureIndex); - m_Value.push_back(DBus::Type::create(signature)); - DBus::Type::unmarshallData(m_Value.back(), stream); - signatureIndex += signature.size(); - } while (signatureIndex < m_Signature.size() - 1); + std::string code = m_Signature.getNextTypeCode(); + while (!code.empty()) { + m_elements.push_back(DBus::Type::create(code)); + m_elements.back().unmarshall(stream); + code = m_Signature.getNextTypeCode(); + } } std::string DBus::Type::Struct::toString(const std::string& prefix) const { - std::stringstream ss; + std::ostringstream oss; + const std::string contents_prefix(prefix + " "); - ss << prefix << "Struct " << getSignature() << " <\n"; - std::string contents_prefix(prefix); - contents_prefix += " "; - for (auto it : m_Value) { - ss << DBus::Type::toString(it, contents_prefix); + oss << name << " " << getSignature() << " : (\n"; + for (auto element : m_elements) { + oss << contents_prefix << element.toString(contents_prefix); } - ss << prefix << ">\n"; + oss << prefix << ")\n"; - return ss.str(); + return oss.str(); } -std::string DBus::Type::Struct::asString() const { return "[struct]"; } +std::string DBus::Type::Struct::asString() const +{ + return getName() + " " + getSignature(); +} diff --git a/src/dbus_type_struct.h b/src/dbus_type_struct.h index 143cca3..4c39c4d 100644 --- a/src/dbus_type_struct.h +++ b/src/dbus_type_struct.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,75 +16,44 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_STRUCT_H -#define DBUS_TYPE_STRUCT_H +#pragma once -#include "dbus_type.h" +#include "dbus_type_any.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - class Byte; - class Boolean; - class ObjectPath; - class Int16; - class Uint16; - class Int32; - class Uint32; - class Int64; - class Uint64; - class Double; - class String; - class Variant; - class Struct; - class DictEntry; - - class Struct : public Base { + class Type::Struct : public Container { public: - Struct(); + Struct() = default; + Struct(const Signature& signature); + Struct(const std::string& signature); - void clear(); + static constexpr const char code_start = '('; + static constexpr const char code_end = ')'; - void add(const DBus::Type::Byte& v); - void add(const DBus::Type::Boolean& v); - void add(const DBus::Type::ObjectPath& v); - void add(const DBus::Type::Int16& v); - void add(const DBus::Type::Uint16& v); - void add(const DBus::Type::Int32& v); - void add(const DBus::Type::Uint32& v); - void add(const DBus::Type::Int64& v); - void add(const DBus::Type::Uint64& v); - void add(const DBus::Type::Double& v); - void add(const DBus::Type::String& v); - void add(const DBus::Type::Variant& v); - void add(const DBus::Type::Signature& v); - void add(const DBus::Type::Struct& v); - void add(const DBus::Type::DictEntry& v); + static constexpr const char *name = "Struct"; + static constexpr std::size_t alignment = 8; + static constexpr const char code = code_start; - std::string getSignature() const - { - // if no entries??? - return "(" + DBus::Type::getMarshallingSignature(m_Value) + ")"; - } + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override; - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string& prefix = "") const override; + std::string asString() const override; - size_t getEntries() const { return m_Value.size(); } - const Generic& operator[](std::size_t idx) const { return m_Value[idx]; } + void clear(); + void add(const DBus::Type::Any& v); - static std::string s_StaticTypeCode; + std::size_t size() const { return m_elements.size(); } + const Any& operator[](std::size_t pos) const { return m_elements.at(pos); } private: - std::vector m_Value; + Signature m_Signature; + std::vector m_elements; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_uint16.cpp b/src/dbus_type_uint16.cpp index e422bd6..6ee5f24 100644 --- a/src/dbus_type_uint16.cpp +++ b/src/dbus_type_uint16.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Uint16::s_StaticTypeCode("q"); - -DBus::Type::Uint16::Uint16() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Uint16::Uint16(uint16_t v) +DBus::Type::Uint16::Uint16(const std::uint16_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Uint16::marshall(MessageOStream& stream) const { @@ -42,23 +33,21 @@ void DBus::Type::Uint16::marshall(MessageOStream& stream) const void DBus::Type::Uint16::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Uint16::toString(const std::string& prefix) const +std::string DBus::Type::Uint16::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Uint16 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(2) - << m_Value << ")\n"; + oss << "Uint16 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(4) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Uint16::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_uint16.h b/src/dbus_type_uint16.h index 6355c7a..b71dccb 100644 --- a/src/dbus_type_uint16.h +++ b/src/dbus_type_uint16.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_UINT16_H -#define DBUS_TYPE_UINT16_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Uint16 : public Base { + class Type::Uint16 : public Basic { public: - Uint16(); - Uint16(uint16_t v); + Uint16() = default; + Uint16(const std::uint16_t v); + + static constexpr const char *name = "Uint16"; + static constexpr std::size_t alignment = 2; + static constexpr const char code = 'q'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - size_t getAlignment() const { return 2; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::uint16_t() const { return m_Value; }; protected: - uint16_t m_Value; + std::uint16_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_uint32.cpp b/src/dbus_type_uint32.cpp index 9afbbc9..a5936fc 100644 --- a/src/dbus_type_uint32.cpp +++ b/src/dbus_type_uint32.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Uint32::s_StaticTypeCode("u"); - -DBus::Type::Uint32::Uint32() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Uint32::Uint32(uint32_t v) +DBus::Type::Uint32::Uint32(const std::uint32_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Uint32::marshall(MessageOStream& stream) const { @@ -42,23 +33,21 @@ void DBus::Type::Uint32::marshall(MessageOStream& stream) const void DBus::Type::Uint32::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Uint32::toString(const std::string& prefix) const +std::string DBus::Type::Uint32::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Uint32 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(4) - << m_Value << ")\n"; + oss << "Uint32 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(8) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Uint32::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_uint32.h b/src/dbus_type_uint32.h index 577adbc..8949398 100644 --- a/src/dbus_type_uint32.h +++ b/src/dbus_type_uint32.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,38 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_UINT32_H -#define DBUS_TYPE_UINT32_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Uint32 : public Base { + class Type::Uint32 : public Basic { public: - Uint32(); - Uint32(uint32_t v); + Uint32() = default; + Uint32(const std::uint32_t v); + + static constexpr const char *name = "Uint32"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 'u'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - // std::string getSignature() const { return - //s_StaticTypeCode; - //} - size_t getAlignment() const { return 4; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::uint32_t() const { return m_Value; }; protected: - uint32_t m_Value; + std::uint32_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_uint64.cpp b/src/dbus_type_uint64.cpp index 7ec60c1..76c701c 100644 --- a/src/dbus_type_uint64.cpp +++ b/src/dbus_type_uint64.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -21,19 +22,9 @@ #include #include -const std::string DBus::Type::Uint64::s_StaticTypeCode("t"); - -DBus::Type::Uint64::Uint64() - : m_Value(0) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Uint64::Uint64(uint64_t v) +DBus::Type::Uint64::Uint64(const std::uint64_t v) : m_Value(v) -{ - setSignature(s_StaticTypeCode); -} +{} void DBus::Type::Uint64::marshall(MessageOStream& stream) const { @@ -42,23 +33,21 @@ void DBus::Type::Uint64::marshall(MessageOStream& stream) const void DBus::Type::Uint64::unmarshall(MessageIStream& stream) { - stream.read(&m_Value); + stream.read(&m_Value); } -std::string DBus::Type::Uint64::toString(const std::string& prefix) const +std::string DBus::Type::Uint64::toString(const std::string&) const { - std::stringstream ss; + std::ostringstream oss; - ss << prefix << "Uint64 "; - ss << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(8) - << m_Value << ")\n"; + oss << "Uint64 " + << m_Value << " (0x" << std::hex << std::setfill('0') << std::setw(16) + << m_Value << ")\n"; - return ss.str(); + return oss.str(); } std::string DBus::Type::Uint64::asString() const { - std::stringstream ss; - ss << m_Value; - return ss.str(); + return std::to_string(m_Value); } diff --git a/src/dbus_type_uint64.h b/src/dbus_type_uint64.h index 8e528da..71a497b 100644 --- a/src/dbus_type_uint64.h +++ b/src/dbus_type_uint64.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,35 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_UINT64_H -#define DBUS_TYPE_UINT64_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - - class Uint64 : public Base { + class Type::Uint64 : public Basic { public: - Uint64(); - Uint64(uint64_t v); + Uint64() = default; + Uint64(const std::uint64_t v); + + static constexpr const char *name = "Uint64"; + static constexpr std::size_t alignment = 8; + static constexpr const char code = 't'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); }; - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; + std::string toString(const std::string&) const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + operator std::uint64_t() const { return m_Value; }; protected: - uint64_t m_Value; + std::uint64_t m_Value = 0; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_type_unixfd.cpp b/src/dbus_type_unixfd.cpp new file mode 100644 index 0000000..c181683 --- /dev/null +++ b/src/dbus_type_unixfd.cpp @@ -0,0 +1,86 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#include "dbus_type_unixfd.h" +#include "dbus_messageistream.h" +#include "dbus_messageostream.h" + +#include +#include + +#include +#include + +DBus::Type::UnixFd::UnixFd() + : m_fd(-1) +{} + +DBus::Type::UnixFd::UnixFd(int fd) + : m_fd(::dup(fd)) +{} + +DBus::Type::UnixFd::UnixFd(const UnixFd& other) + : m_fd(::dup(other.m_fd)) +{} + +DBus::Type::UnixFd::~UnixFd() { + ::close(m_fd); +} + +void DBus::Type::UnixFd::marshall(MessageOStream& stream) const +{ + stream.writeUnixFd(dup()); +} + +#include "dbus_log.h" +void DBus::Type::UnixFd::unmarshall(MessageIStream& stream) +{ + m_fd = ::dup(stream.readUnixFd()); +} + +std::string DBus::Type::UnixFd::toString(const std::string&) const +{ + std::ostringstream oss; + oss << name << " " << fdName() << " (" << m_fd << ")\n"; + return oss.str(); +} + +std::string DBus::Type::UnixFd::asString() const +{ + return std::to_string(m_fd); +} + +std::string DBus::Type::UnixFd::fdName() const +{ + if (m_fd < 0) + return "invalid"; + + char fd_name[512] = "unknown"; + int dirfd = ::open("/proc/self/fd/", O_DIRECTORY|O_PATH); + + if (dirfd != -1) { + ::readlinkat(dirfd, asString().c_str(), fd_name, sizeof(fd_name)); + ::close(dirfd); + } + + return fd_name; +} + +int DBus::Type::UnixFd::dup() const { + return ::dup(m_fd); +} + diff --git a/src/dbus_type_unixfd.h b/src/dbus_type_unixfd.h new file mode 100644 index 0000000..6088d34 --- /dev/null +++ b/src/dbus_type_unixfd.h @@ -0,0 +1,52 @@ +// This file is part of dbus-asio +// Copyright 2022 OpenVPN Inc. +// +// This library is free software: you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, version 3, or at your +// option any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The GNU Lesser General Public License version 3 is included in the +// file named COPYING. If you do not have this file see +// . + +#pragma once + +#include "dbus_type.h" + +namespace DBus { + + class Type::UnixFd : public Basic { + public: + UnixFd(); + UnixFd(int fd); + UnixFd(const UnixFd& other); + ~UnixFd() override; + + static constexpr const char *name = "UnixFd"; + static constexpr std::size_t alignment = 4; + static constexpr const char code = 'h'; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; } + std::string getSignature() const override { return std::string(1, code); } + + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; + + std::string toString(const std::string& = "") const override; + std::string asString() const override; + std::string fdName() const; + + int dup() const; + + protected: + int m_fd; + }; + +} // namespace DBus diff --git a/src/dbus_type_variant.cpp b/src/dbus_type_variant.cpp index 0d16a6d..7c86f3f 100644 --- a/src/dbus_type_variant.cpp +++ b/src/dbus_type_variant.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -16,20 +17,16 @@ // . #include "dbus_type_variant.h" +#include "dbus_type_signature.h" +#include "dbus_type.h" #include "dbus_messageistream.h" #include "dbus_messageostream.h" -#include "dbus_type.h" -#include "dbus_type_objectpath.h" -#include "dbus_type_signature.h" -#include "dbus_type_string.h" -#include "dbus_type_uint32.h" -#include -const std::string DBus::Type::Variant::s_StaticTypeCode("v"); +#include /* Variants are marshalled as the SIGNATURE of the contents (which must be a single -complete type), followed by a marshalled m_Value with the type given by that +complete type), followed by a marshalled value with the type given by that signature. The variant has the same 1-byte alignment as the signature, which means that @@ -38,37 +35,12 @@ cause a total message depth to be larger than 64, including other container types such as structures. (See Valid Signatures.) */ -DBus::Type::Variant::Variant() { setSignature(s_StaticTypeCode); } - -DBus::Type::Variant::Variant(const DBus::Type::ObjectPath& v) - : m_Value(v) - , m_ContainedSignature(v.getSignature()) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Variant::Variant(const DBus::Type::String& v) - : m_Value(v) - , m_ContainedSignature(v.getSignature()) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Variant::Variant(const DBus::Type::Uint32& v) - : m_Value(v) - , m_ContainedSignature(v.getSignature()) -{ - setSignature(s_StaticTypeCode); -} - -DBus::Type::Variant::Variant(const DBus::Type::Signature& v) +DBus::Type::Variant::Variant(const DBus::Type::Any& v) : m_Value(v) - , m_ContainedSignature(v.getSignature()) { - setSignature(s_StaticTypeCode); } -const DBus::Type::Generic& DBus::Type::Variant::getValue() const +const DBus::Type::Any& DBus::Type::Variant::getValue() const { return m_Value; } @@ -77,34 +49,32 @@ void DBus::Type::Variant::marshall(MessageOStream& stream) const { // The marshalled SIGNATURE of a single complete type... - stream.writeSignature(m_ContainedSignature); + stream.writeSignature(m_Value.getSignature()); - // ...followed by a marshaled m_Value with the type given in the signature. - DBus::Type::marshallData(m_Value, stream); + // ...followed by a marshaled value with the type given in the signature. + m_Value.marshall(stream); } void DBus::Type::Variant::unmarshall(MessageIStream& stream) { Type::Signature signature; signature.unmarshall(stream); - m_ContainedSignature = signature.getValue(); - m_Value = DBus::Type::create(signature.getValue()); - DBus::Type::unmarshallData(m_Value, stream); + m_Value = DBus::Type::create(signature.asString()); + m_Value.unmarshall(stream); } std::string DBus::Type::Variant::toString(const std::string& prefix) const { - std::stringstream ss; + std::ostringstream oss; + const std::string content_prefix(prefix + " "); - ss << prefix << "Variant (" << m_ContainedSignature << ")\n"; - std::string contents_prefix(prefix); - contents_prefix += " "; - ss << DBus::Type::toString(m_Value, contents_prefix); + oss << "Variant (" << m_Value.getSignature() << ")\n"; + oss << content_prefix << m_Value.toString(content_prefix); - return ss.str(); + return oss.str(); } std::string DBus::Type::Variant::asString() const { - return DBus::Type::asString(m_Value); + return m_Value.asString(); } diff --git a/src/dbus_type_variant.h b/src/dbus_type_variant.h index 343c288..9984614 100644 --- a/src/dbus_type_variant.h +++ b/src/dbus_type_variant.h @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -15,44 +16,35 @@ // file named COPYING. If you do not have this file see // . -#ifndef DBUS_TYPE_VARIANT_H -#define DBUS_TYPE_VARIANT_H +#pragma once -#include "dbus_type_base.h" +#include "dbus_type_any.h" namespace DBus { -class MessageOStream; -class MessageIStream; -namespace Type { - class ObjectPath; - class Signature; - class String; - class Uint32; - - class Variant : public Base { + class Type::Variant : public Container { public: - Variant(); - Variant(const DBus::Type::ObjectPath& v); - Variant(const DBus::Type::String& v); - Variant(const DBus::Type::Uint32& v); - Variant(const DBus::Type::Signature& v); + Variant() = default; + Variant(const Any& v); + + static constexpr const char *name = "Variant"; + static constexpr const char code = 'v'; + static constexpr std::size_t alignment = 1; + + std::string getName() const override { return name; } + std::size_t getAlignment() const override { return alignment; }; + std::string getSignature() const override { return std::string(1, code); } - size_t getAlignment() const { return 8; } - void marshall(MessageOStream& stream) const; - void unmarshall(MessageIStream& stream); + void marshall(MessageOStream& stream) const override; + void unmarshall(MessageIStream& stream) override; - std::string toString(const std::string& prefix = "") const; - std::string asString() const; - const DBus::Type::Generic& getValue() const; + std::string toString(const std::string& prefix = "") const override; + std::string asString() const override; - static const std::string s_StaticTypeCode; + const Any& getValue() const; private: - DBus::Type::Generic m_Value; - std::string m_ContainedSignature; + Any m_Value; }; -} // namespace Type -} // namespace DBus -#endif +} // namespace DBus diff --git a/src/dbus_validation.cpp b/src/dbus_validation.cpp deleted file mode 100644 index b17af83..0000000 --- a/src/dbus_validation.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . -#include "dbus_validation.h" -#include - -namespace DBus { -namespace Validation { - bool isValidBasicType(char type) - { - switch (type) { - case 'y': - case 'b': - case 'n': - case 'q': - case 'i': - case 'u': - case 'x': - case 't': - case 'd': - case 'h': - case 's': - case 'o': - case 'g': - return true; - default: - return false; - } - } - - void throwOnInvalidBasicType(char type) - { - if (!isValidBasicType(type)) - throw std::runtime_error(std::string("Invalid basic type: ") + type); - } - - void throwOnInvalidBasicType(const std::string& type) - { - if (type.size() != 1 || !isValidBasicType(type[0])) - throw std::runtime_error(std::string("Invalid basic type: ") + type); - } -} // namespace Validation -} // namespace DBus diff --git a/src/dbus_validation.h b/src/dbus_validation.h deleted file mode 100644 index d33ff35..0000000 --- a/src/dbus_validation.h +++ /dev/null @@ -1,32 +0,0 @@ -// This file is part of dbus-asio -// Copyright 2018-2020 Brightsign LLC -// -// This library is free software: you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public License -// as published by the Free Software Foundation, version 3, or at your -// option any later version. -// -// This library is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// The GNU Lesser General Public License version 3 is included in the -// file named COPYING. If you do not have this file see -// . - -#ifndef DBUS_VALIDATION_H -#define DBUS_VALIDATION_H - -#include -#include - -namespace DBus { -namespace Validation { - bool isValidBasicType(char type); - void throwOnInvalidBasicType(char type); - void throwOnInvalidBasicType(const std::string& type); -} // namespace Validation -} // namespace DBus - -#endif // DBUS_VALIDATION_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7424c3e..5d52fe6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,6 +2,16 @@ cmake_minimum_required(VERSION 3.7.1) add_executable(dbus_asio_test dbus_asio_test.cpp) target_link_libraries(dbus_asio_test LINK_PRIVATE dbus-asio) +add_executable(dbus_names_unittest dbus_names_unittest.cpp test_main.cpp) +target_include_directories(dbus_names_unittest PRIVATE ../extern) +target_link_libraries(dbus_names_unittest LINK_PRIVATE dbus-asio) +add_test(NAME run_dbus_names_unittest COMMAND dbus_names_unittest) + +add_executable(dbus_matchrule_unittest dbus_matchrule_unittest.cpp test_main.cpp) +target_include_directories(dbus_matchrule_unittest PRIVATE ../extern) +target_link_libraries(dbus_matchrule_unittest LINK_PRIVATE dbus-asio) +add_test(NAME run_dbus_matchrule_unittest COMMAND dbus_matchrule_unittest) + add_executable(dbus_platform_unittest dbus_platform_unittest.cpp test_main.cpp) target_include_directories(dbus_platform_unittest PRIVATE ../extern) target_link_libraries(dbus_platform_unittest LINK_PRIVATE dbus-asio) @@ -17,11 +27,6 @@ target_include_directories(dbus_messageistream_unittest PRIVATE ../extern) target_link_libraries(dbus_messageistream_unittest LINK_PRIVATE dbus-asio) add_test(NAME run_dbus_messageistream_unittest COMMAND dbus_messageistream_unittest) -add_executable(dbus_validation_unittest dbus_validation_unittest.cpp test_main.cpp) -target_include_directories(dbus_validation_unittest PRIVATE ../extern) -target_link_libraries(dbus_validation_unittest LINK_PRIVATE dbus-asio) -add_test(NAME run_dbus_validation_unittest COMMAND dbus_validation_unittest) - add_executable(dbus_type_array_unittest dbus_type_array_unittest.cpp test_main.cpp) target_include_directories(dbus_type_array_unittest PRIVATE ../extern) target_link_libraries(dbus_type_array_unittest LINK_PRIVATE dbus-asio) @@ -77,6 +82,11 @@ target_include_directories(dbus_type_uint64_unittest PRIVATE ../extern) target_link_libraries(dbus_type_uint64_unittest LINK_PRIVATE dbus-asio) add_test(NAME run_dbus_type_uint64_unittest COMMAND dbus_type_uint64_unittest) +add_executable(dbus_type_unixfd_unittest dbus_type_unixfd_unittest.cpp test_main.cpp) +target_include_directories(dbus_type_unixfd_unittest PRIVATE ../extern) +target_link_libraries(dbus_type_unixfd_unittest LINK_PRIVATE dbus-asio) +add_test(NAME run_dbus_type_unixfd_unittest COMMAND dbus_type_unixfd_unittest) + add_executable(dbus_type_double_unittest dbus_type_double_unittest.cpp test_main.cpp) target_include_directories(dbus_type_double_unittest PRIVATE ../extern) target_link_libraries(dbus_type_double_unittest LINK_PRIVATE dbus-asio) @@ -107,7 +117,7 @@ target_include_directories(dbus_octetbuffer_unittest PRIVATE ../extern) target_link_libraries(dbus_octetbuffer_unittest LINK_PRIVATE dbus-asio) add_test(NAME run_dbus_octetbuffer_unittest COMMAND dbus_octetbuffer_unittest) -add_executable(dbus_messageprotocol_unittest dbus_messageprotocol_unittest.cpp test_main.cpp) -target_include_directories(dbus_messageprotocol_unittest PRIVATE ../extern) -target_link_libraries(dbus_messageprotocol_unittest LINK_PRIVATE dbus-asio) -add_test(NAME run_messageprotocol_unittest COMMAND dbus_messageprotocol_unittest) +#add_executable(dbus_messageprotocol_unittest dbus_messageprotocol_unittest.cpp test_main.cpp) +#target_include_directories(dbus_messageprotocol_unittest PRIVATE ../extern) +#target_link_libraries(dbus_messageprotocol_unittest LINK_PRIVATE dbus-asio) +#add_test(NAME run_messageprotocol_unittest COMMAND dbus_messageprotocol_unittest) diff --git a/tests/dbus_asio_test.cpp b/tests/dbus_asio_test.cpp index 94573a8..b893418 100644 --- a/tests/dbus_asio_test.cpp +++ b/tests/dbus_asio_test.cpp @@ -1,5 +1,6 @@ // This file is part of dbus-asio // Copyright 2018 Brightsign LLC +// Copyright 2022 OpenVPN Inc. // // This library is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License @@ -26,11 +27,28 @@ int main() DBus::Platform::getSystemBus().c_str()); DBus::Log::write(DBus::Log::INFO, "Session bus: %s\n", DBus::Platform::getSessionBus().c_str()); - DBus::Native native(DBus::Platform::getSessionBus()); - sleep(1); - native.BeginAuth( - DBus::AuthenticationProtocol::AUTH_BASIC); // AUTH_BASIC or - // AUTH_NEGOTIATE_UNIX_FD + DBus::asio::io_context ioc; + auto conn = DBus::Connection::create(ioc); + + conn->connect( + DBus::Platform::getSessionBus(), + DBus::AuthenticationProtocol::create(), + [conn](const DBus::Error& e, const std::string& guid, const std::string& name) + { + if (e) + DBus::Log::write( + DBus::Log::ERROR, + "error: %s (%s)\n", e.message.c_str(), e.category.c_str()); + + DBus::Log::write( + DBus::Log::INFO, + ">>> server guid: %s my bus name: %s\n", guid.c_str(), name.c_str()); + + conn->disconnect(); + }); + + ioc.run(); + return 0; } diff --git a/tests/dbus_matchrule_unittest.cpp b/tests/dbus_matchrule_unittest.cpp new file mode 100644 index 0000000..60808ef --- /dev/null +++ b/tests/dbus_matchrule_unittest.cpp @@ -0,0 +1,77 @@ +#include "dbus_matchrule.h" +#include "dbus_names.h" +#include +#include + +namespace DBus { +namespace test { + +TEST_CASE("MatchRule wildcard") +{ + REQUIRE(MatchRule().str() == ""); +} + +TEST_CASE("MatchRule::type()") +{ + REQUIRE(MatchRule().type(MatchRule::Type::MethodCall).str() == "type='method_call'"); + REQUIRE(MatchRule().type(MatchRule::Type::MethodReturn).str() == "type='method_return'"); + REQUIRE(MatchRule().type(MatchRule::Type::Error).str() == "type='error'"); + REQUIRE(MatchRule().type(MatchRule::Type::Signal).str() == "type='signal'"); +} + +TEST_CASE("MatchRule::sender()") +{ + REQUIRE(MatchRule().sender(":1.234").str() == "sender=':1.234'"); + REQUIRE(MatchRule().sender("well-known.name").str() == "sender='well-known.name'"); +} + +TEST_CASE("MatchRule::interface()") +{ + REQUIRE(MatchRule().interface("inter_face.Name").str() == "interface='inter_face.Name'"); +} + +TEST_CASE("MatchRule::member()") +{ + REQUIRE(MatchRule().member("MemberName_").str() == "member='MemberName_'"); +} + +TEST_CASE("MatchRule::path()") +{ + REQUIRE(MatchRule().path("/Org/Foo").str() == "path='/Org/Foo'"); + REQUIRE_THROWS_AS(MatchRule().path("/Org/Foo").pathNamespace("/Org/Bar"), InvalidMatchRule); +} + +TEST_CASE("MatchRule::pathNamespace()") +{ + REQUIRE(MatchRule().pathNamespace("/Org/Foo").str() == "path_namespace='/Org/Foo'"); + REQUIRE_THROWS_AS(MatchRule().pathNamespace("/Org/Foo").path("/Org/Bar"), InvalidMatchRule); +} + +TEST_CASE("MatchRule::destination()") +{ + REQUIRE(MatchRule().destination(":1.234").str() == "destination=':1.234'"); +} + +TEST_CASE("MatchRule::arg0Namespace()") +{ + REQUIRE(MatchRule().arg0Namespace("Name.Space").str() == "arg0namespace='Name.Space'"); +} + +TEST_CASE("MatchRule::arg()") +{ + REQUIRE(MatchRule().arg(0,"foo").arg(42,"bar").str() == "arg0='foo',arg42='bar'"); + REQUIRE(MatchRule().arg(0,"'").str() == "arg0=''\\'''"); + REQUIRE(MatchRule().arg(63,"\\").str() == "arg63='\\'"); + REQUIRE_THROWS_AS(MatchRule().arg(64,"/Org/Foo"), InvalidMatchRule); +} + +TEST_CASE("MatchRule::argPath()") +{ + REQUIRE(MatchRule().argPath(63,"/foo/bar/").str() == "arg63path='/foo/bar/'"); + REQUIRE(MatchRule().argPath(0,"'").str() == "arg0path=''\\'''"); + REQUIRE(MatchRule().argPath(63,"\\").str() == "arg63path='\\'"); + REQUIRE_THROWS_AS(MatchRule().argPath(64,"/Org/Foo"), InvalidMatchRule); +} + +} // namespace test +} // namespace DBus diff --git a/tests/dbus_messageistream_unittest.cpp b/tests/dbus_messageistream_unittest.cpp index 093607a..7bc488d 100644 --- a/tests/dbus_messageistream_unittest.cpp +++ b/tests/dbus_messageistream_unittest.cpp @@ -32,8 +32,9 @@ namespace test { for (size_t i = 0; i < sizeof(T); i++) { memcpy(&data[i > 0 ? sizeof(T) : 0], &value, sizeof(T)); - MessageIStream leStream(data.data(), data.size(), false); - MessageIStream beStream(data.data(), data.size(), true); + OctetBuffer buf(data.data(), data.size()); + MessageIStream leStream(buf, false); + MessageIStream beStream(buf, true); std::string str; leStream.read(str, i); @@ -52,7 +53,8 @@ namespace test { TEST_CASE("Read byte") { const uint8_t data = 34; - MessageIStream stream(&data, 1, false); + OctetBuffer buf(&data, 1); + MessageIStream stream(buf, false); REQUIRE(stream.read() == 34); CHECK_THROWS(stream.read()); @@ -61,8 +63,9 @@ namespace test { TEST_CASE("Read data") { const uint8_t data[4] = { 34, 35, 36, 37 }; + OctetBuffer buf(data, sizeof(data)); uint8_t readData[3]; - MessageIStream stream(data, 4, false); + MessageIStream stream(buf, false); stream.read(readData, 3); REQUIRE(memcmp(data, readData, 3) == 0); @@ -72,13 +75,14 @@ namespace test { TEST_CASE("Read double") { double value = 0.75483; - MessageIStream leStream((const uint8_t*)&value, sizeof(double), false); + OctetBuffer buf((const uint8_t*)&value, sizeof(double)); + MessageIStream leStream(buf, false); uint64_t swapped_value; memcpy(&swapped_value, &value, sizeof(value)); swapped_value = __bswap_64(swapped_value); - MessageIStream beStream((const uint8_t*)&swapped_value, sizeof(double), - true); + buf = {(const uint8_t*)&swapped_value, sizeof(double)}; + MessageIStream beStream(buf, true); double readValue = 0.0; leStream.read(&readValue); @@ -94,7 +98,8 @@ namespace test { TEST_CASE("Read string") { std::string value("Hello world"); - MessageIStream stream((const uint8_t*)value.data(), value.size(), false); + OctetBuffer buf((const uint8_t*)value.data(), value.size()); + MessageIStream stream(buf, false); std::string readValue = "Test"; stream.read(readValue, value.size()); @@ -106,7 +111,8 @@ namespace test { TEST_CASE("Is empty") { std::string value("Hello world"); - MessageIStream stream((const uint8_t*)value.data(), value.size(), false); + OctetBuffer buf((const uint8_t*)value.data(), value.size()); + MessageIStream stream(buf, false); REQUIRE(stream.empty() == false); diff --git a/tests/dbus_names_unittest.cpp b/tests/dbus_names_unittest.cpp new file mode 100644 index 0000000..2b746b5 --- /dev/null +++ b/tests/dbus_names_unittest.cpp @@ -0,0 +1,252 @@ +#include "dbus_names.h" +#include +#include + +namespace DBus { +namespace test { + +using Catch::Matchers::Contains; + +TEST_CASE("BusName Validation") +{ + REQUIRE_NOTHROW(BusName("-._")); + REQUIRE_NOTHROW(BusName(":ab.7")); + REQUIRE_NOTHROW(BusName(":1.234")); + REQUIRE_NOTHROW(BusName("a.b.c.d")); + REQUIRE_NOTHROW(BusName("_1.valid-name")); + REQUIRE_NOTHROW(BusName("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-")); + REQUIRE_NOTHROW(BusName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(BusName(""), "name is empty"); + REQUIRE_THROWS_WITH(BusName("."), ". has empty element"); + REQUIRE_THROWS_WITH(BusName(":"), ": doesn't have two elements"); + REQUIRE_THROWS_WITH(BusName(":.b"), ":.b has empty element"); + REQUIRE_THROWS_WITH(BusName("a..b"), "a..b has empty element"); + REQUIRE_THROWS_WITH(BusName("a!.b"), "a!.b has invalid character"); + REQUIRE_THROWS_WITH(BusName(".invalid.name"), ".invalid.name has empty element"); + REQUIRE_THROWS_WITH(BusName("invalid_name"), "invalid_name doesn't have two elements"); + REQUIRE_THROWS_WITH(BusName("1.invalid.name"), "1.invalid.name element starts with digit"); + REQUIRE_THROWS_WITH(BusName("invalid.name.2nd"), "invalid.name.2nd element starts with digit"); + REQUIRE_THROWS_WITH(BusName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + Contains(" exceeds 255 characters")); +} + +TEST_CASE("WellKnownName Validation") +{ + REQUIRE_NOTHROW(WellKnownName("-._")); + REQUIRE_NOTHROW(WellKnownName("a.b.c.d")); + REQUIRE_NOTHROW(WellKnownName("_1.valid-name")); + REQUIRE_NOTHROW(WellKnownName("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-")); + REQUIRE_NOTHROW(WellKnownName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(WellKnownName(""), "name is empty"); + REQUIRE_THROWS_WITH(WellKnownName("."), ". has empty element"); + REQUIRE_THROWS_WITH(WellKnownName(":"), ": is not a well-known name"); + REQUIRE_THROWS_WITH(WellKnownName(":.b"), ":.b is not a well-known name"); + REQUIRE_THROWS_WITH(WellKnownName("a..b"), "a..b has empty element"); + REQUIRE_THROWS_WITH(WellKnownName("a!.b"), "a!.b has invalid character"); + REQUIRE_THROWS_WITH(WellKnownName(":ab.7"), ":ab.7 is not a well-known name"); + REQUIRE_THROWS_WITH(WellKnownName(":1.234"), ":1.234 is not a well-known name"); + REQUIRE_THROWS_WITH(WellKnownName(".invalid.name"), ".invalid.name has empty element"); + REQUIRE_THROWS_WITH(WellKnownName("invalid_name"), "invalid_name doesn't have two elements"); + REQUIRE_THROWS_WITH(WellKnownName("1.invalid.name"), "1.invalid.name element starts with digit"); + REQUIRE_THROWS_WITH(WellKnownName("invalid.name.2nd"), "invalid.name.2nd element starts with digit"); + REQUIRE_THROWS_WITH(WellKnownName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + Contains(" exceeds 255 characters")); +} + +TEST_CASE("UniqueName Validation") +{ + REQUIRE_NOTHROW(UniqueName(":_.-")); + REQUIRE_NOTHROW(UniqueName(":ab.7")); + REQUIRE_NOTHROW(UniqueName(":1.234")); + REQUIRE_NOTHROW(UniqueName(":a.b.c.d")); + REQUIRE_NOTHROW(UniqueName(":_1.valid-name")); + REQUIRE_NOTHROW(UniqueName(":abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-")); + REQUIRE_NOTHROW(UniqueName( + ":OKAY56789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(UniqueName(""), "name is empty"); + REQUIRE_THROWS_WITH(UniqueName("."), ". is not a unique connection name"); + REQUIRE_THROWS_WITH(UniqueName(":"), ": doesn't have two elements"); + REQUIRE_THROWS_WITH(UniqueName("-._"), "-._ is not a unique connection name"); + REQUIRE_THROWS_WITH(UniqueName(":.b"), ":.b has empty element"); + REQUIRE_THROWS_WITH(UniqueName(":a..b"), ":a..b has empty element"); + REQUIRE_THROWS_WITH(UniqueName(":a:.b"), ":a:.b has invalid character"); + REQUIRE_THROWS_WITH(UniqueName("a.b.c.d"), "a.b.c.d is not a unique connection name"); + REQUIRE_THROWS_WITH(UniqueName(":.invalid.name"), ":.invalid.name has empty element"); + REQUIRE_THROWS_WITH(UniqueName(":invalid_name"), ":invalid_name doesn't have two elements"); + REQUIRE_THROWS_WITH(UniqueName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + Contains(" exceeds 255 characters")); +} + +TEST_CASE("ErrorName Validation") +{ + REQUIRE_NOTHROW(ErrorName("_._")); + REQUIRE_NOTHROW(ErrorName("a.b.c.d")); + REQUIRE_NOTHROW(ErrorName("_1.valid.Name")); + REQUIRE_NOTHROW(ErrorName("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789")); + REQUIRE_NOTHROW(ErrorName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(ErrorName(""), "name is empty"); + REQUIRE_THROWS_WITH(ErrorName("."), ". has empty element"); + REQUIRE_THROWS_WITH(ErrorName(":"), ": is not a error name"); + REQUIRE_THROWS_WITH(ErrorName("-._"), "-._ has invalid character"); + REQUIRE_THROWS_WITH(ErrorName(".b"), ".b has empty element"); + REQUIRE_THROWS_WITH(ErrorName("ab.7"), "ab.7 element starts with digit"); + REQUIRE_THROWS_WITH(ErrorName("a..b"), "a..b has empty element"); + REQUIRE_THROWS_WITH(ErrorName("a .b"), "a .b has invalid character"); + REQUIRE_THROWS_WITH(ErrorName(".invalid.name"), ".invalid.name has empty element"); + REQUIRE_THROWS_WITH(ErrorName("invalid_name"), "invalid_name doesn't have two elements"); + REQUIRE_THROWS_WITH(ErrorName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + "INVALID789abcdef... exceeds 255 characters"); +} + +TEST_CASE("InterfaceName Validation") +{ + REQUIRE_NOTHROW(InterfaceName("_._")); + REQUIRE_NOTHROW(InterfaceName("a.b.c.d")); + REQUIRE_NOTHROW(InterfaceName("_1.valid.Name")); + REQUIRE_NOTHROW(InterfaceName("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789")); + REQUIRE_NOTHROW(InterfaceName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(InterfaceName(""), "name is empty"); + REQUIRE_THROWS_WITH(InterfaceName("."), ". has empty element"); + REQUIRE_THROWS_WITH(InterfaceName(":"), ": is not a interface name"); + REQUIRE_THROWS_WITH(InterfaceName("-._"), "-._ has invalid character"); + REQUIRE_THROWS_WITH(InterfaceName(".b"), ".b has empty element"); + REQUIRE_THROWS_WITH(InterfaceName("ab.7"), "ab.7 element starts with digit"); + REQUIRE_THROWS_WITH(InterfaceName("a..b"), "a..b has empty element"); + REQUIRE_THROWS_WITH(InterfaceName("a .b"), "a .b has invalid character"); + REQUIRE_THROWS_WITH(InterfaceName(".invalid.name"), ".invalid.name has empty element"); + REQUIRE_THROWS_WITH(InterfaceName("invalid_name"), "invalid_name doesn't have two elements"); + REQUIRE_THROWS_WITH(InterfaceName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + "INVALID789abcdef... exceeds 255 characters"); +} + +TEST_CASE("NamespaceName Validation") +{ + REQUIRE_NOTHROW(NamespaceName("_")); + REQUIRE_NOTHROW(NamespaceName("ValidName")); + REQUIRE_NOTHROW(NamespaceName("a.b.c.d")); + REQUIRE_NOTHROW(NamespaceName("_1.valid.Name")); + REQUIRE_NOTHROW(NamespaceName("abcdefghijklmnopqrstuvwxyz.ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789")); + REQUIRE_NOTHROW(NamespaceName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(NamespaceName(""), "name is empty"); + REQUIRE_THROWS_WITH(NamespaceName("."), ". has empty element"); + REQUIRE_THROWS_WITH(NamespaceName(":"), ": is not a namespace name"); + REQUIRE_THROWS_WITH(NamespaceName("-._"), "-._ has invalid character"); + REQUIRE_THROWS_WITH(NamespaceName(".b"), ".b has empty element"); + REQUIRE_THROWS_WITH(NamespaceName("ab.7"), "ab.7 element starts with digit"); + REQUIRE_THROWS_WITH(NamespaceName("a..b"), "a..b has empty element"); + REQUIRE_THROWS_WITH(NamespaceName("a .b"), "a .b has invalid character"); + REQUIRE_THROWS_WITH(NamespaceName(".invalid.name"), ".invalid.name has empty element"); + REQUIRE_THROWS_WITH(NamespaceName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCD.F" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + "INVALID789abcdef... exceeds 255 characters"); +} + +TEST_CASE("MemberName Validation") +{ + REQUIRE_NOTHROW(MemberName("_")); + REQUIRE_NOTHROW(MemberName("Abcd")); + REQUIRE_NOTHROW(MemberName("_1validName")); + REQUIRE_NOTHROW(MemberName("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")); + REQUIRE_NOTHROW(MemberName( + "OKAY456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDE")); + + REQUIRE_THROWS_WITH(MemberName(""), "name is empty"); + REQUIRE_THROWS_WITH(MemberName("."), ". has invalid character"); + REQUIRE_THROWS_WITH(MemberName(":"), ": is not a member name"); + REQUIRE_THROWS_WITH(MemberName("-"), "- has invalid character"); + REQUIRE_THROWS_WITH(MemberName("7"), "7 starts with digit"); + REQUIRE_THROWS_WITH(MemberName( + "INVALID789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF" + "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"), + "INVALID789abcdef... exceeds 255 characters"); +} + +TEST_CASE("ObjectPath Validation") +{ + REQUIRE_NOTHROW(ObjectPath("/")); + REQUIRE_NOTHROW(ObjectPath("/foo")); + REQUIRE_NOTHROW(ObjectPath("/foo/bar")); + REQUIRE_NOTHROW(ObjectPath("/___/1___")); + REQUIRE_NOTHROW(ObjectPath("/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")); + + REQUIRE_THROWS_WITH(ObjectPath(""), "path is empty"); + REQUIRE_THROWS_WITH(ObjectPath("foo/"), "foo/ doesn't start with slash"); + REQUIRE_THROWS_WITH(ObjectPath("/foo/"), "/foo/ ends with slash"); + REQUIRE_THROWS_WITH(ObjectPath("/foo!/bar"), "/foo!/bar has invalid character"); + REQUIRE_THROWS_WITH(ObjectPath("/foo//bar"), "/foo//bar has // sequence"); +} + +TEST_CASE("DBus::Name operators") +{ + REQUIRE(static_cast(BusName("a.b"))); + REQUIRE(static_cast(InterfaceName("a.b"))); + REQUIRE(static_cast(ObjectPath("/a/b"))); + + REQUIRE_FALSE(BusName()); + REQUIRE_FALSE(InterfaceName()); + REQUIRE_FALSE(ObjectPath()); + + std::ostringstream test; + test << UniqueName(":1.2"); + REQUIRE(test.str() == ":1.2"); +} + +} // namespace test +} // namespace DBus diff --git a/tests/dbus_type_array_unittest.cpp b/tests/dbus_type_array_unittest.cpp index 3fa2469..6998dee 100644 --- a/tests/dbus_type_array_unittest.cpp +++ b/tests/dbus_type_array_unittest.cpp @@ -1,5 +1,6 @@ #include "dbus_messageistream.h" #include "dbus_messageostream.h" +#include "dbus_octetbuffer.h" #include "dbus_messageprotocol.h" #include "dbus_type.h" #include "dbus_type_array.h" @@ -27,20 +28,19 @@ namespace test { int32_t v2) { // An array of structures - Type::Array array; - array.setSignature("a(i)"); + Type::Array array("a(i)"); - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); array.unmarshall(istream); REQUIRE(array.size() == 2); - const Type::Struct& struct1 = boost::any_cast(array.getContents()[0]); - const Type::Struct& struct2 = boost::any_cast(array.getContents()[1]); + const Type::Struct& struct1 = DBus::Type::refStruct(array[0]); + const Type::Struct& struct2 = DBus::Type::refStruct(array[1]); - REQUIRE(Type::asString(struct1[0]) == std::to_string(v1)); - REQUIRE(Type::asString(struct2[0]) == std::to_string(v2)); + REQUIRE(Type::asInt32(struct1[0]) == v1); + REQUIRE(Type::asInt32(struct2[0]) == v2); } void TestUnmarshallFromMessageIStream(unsigned byteOrder, int32_t v1, @@ -75,7 +75,7 @@ namespace test { TEST_CASE("Marshall and unmarshall array of structs") { - Type::Array array; + Type::Array array("a(i)"); Type::Struct struct1; DBus::Type::Int32 value1(-1); Type::Struct struct2; @@ -83,7 +83,6 @@ namespace test { struct1.add(value1); struct2.add(value2); - array.setSignature("a(i)"); array.add(struct1); array.add(struct2); MessageOStream stream; @@ -164,7 +163,7 @@ namespace test { { input.add(DBus::Type::Signature("i")); input.add(DBus::Type::Signature("(id)")); - input.add(DBus::Type::Signature("{id}")); + input.add(DBus::Type::Signature("a{id}")); input.add(DBus::Type::Signature("as")); } @@ -173,10 +172,9 @@ namespace test { MessageOStream ostream; input.marshall(ostream); - Type::Array output; - output.setSignature(input.getSignature()); - MessageIStream istream((uint8_t*)ostream.data.data(), ostream.data.size(), - false); + Type::Array output(input.getSignature()); + OctetBuffer buf((uint8_t*)ostream.data.data(), ostream.data.size()); + MessageIStream istream(buf, false); output.unmarshall(istream); REQUIRE(input.toString() == output.toString()); diff --git a/tests/dbus_type_boolean_unittest.cpp b/tests/dbus_type_boolean_unittest.cpp index f464fb3..0fc1cf2 100644 --- a/tests/dbus_type_boolean_unittest.cpp +++ b/tests/dbus_type_boolean_unittest.cpp @@ -34,8 +34,8 @@ namespace test { stream.writeInt32(true); Type::Boolean boolean; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(1, boolean, istream); } diff --git a/tests/dbus_type_byte_unittest.cpp b/tests/dbus_type_byte_unittest.cpp index 0f461da..1632e8a 100644 --- a/tests/dbus_type_byte_unittest.cpp +++ b/tests/dbus_type_byte_unittest.cpp @@ -11,7 +11,8 @@ namespace test { { std::basic_string data; data.push_back(65); - MessageIStream istream(data.data(), data.size(), false); + OctetBuffer buf(data.data(), data.size()); + MessageIStream istream(buf, false); Type::Byte byte; byte.unmarshall(istream); @@ -23,8 +24,8 @@ namespace test { MessageOStream stream; stream.writeByte(213); - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); Type::Byte byte; byte.unmarshall(istream); REQUIRE(byte.asString() == std::to_string(213)); diff --git a/tests/dbus_type_dictentry_unittest.cpp b/tests/dbus_type_dictentry_unittest.cpp index 414c0f6..92a14bc 100644 --- a/tests/dbus_type_dictentry_unittest.cpp +++ b/tests/dbus_type_dictentry_unittest.cpp @@ -21,18 +21,17 @@ namespace test { const std::string& stream, const std::string& key, uint32_t value) { - Type::DictEntry dictEntry; - dictEntry.setSignature("{su}"); - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + Type::DictEntry dictEntry("{su}"); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); dictEntry.unmarshall(istream); std::stringstream ss; - ss << "DictEntry ({su}) : {" << std::endl; + ss << "DictEntry {su} : {" << std::endl; ss << " key: String (" << key.size() << ") \"" << key << "\"" << std::endl; ss << " value: Uint32 " << value << " (0x"; - ss << std::hex << std::setw(4) << std::setfill('0') << value; + ss << std::hex << std::setw(8) << std::setfill('0') << value; ss << std::dec << std::setw(0) << ")" << std::endl; ss << "}" << std::endl; @@ -66,24 +65,23 @@ namespace test { const int32_t key, const std::pair& value) { - Type::DictEntry dictEntry; - dictEntry.setSignature("{i(dy)}"); - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + Type::DictEntry dictEntry("{i(dy)}"); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); dictEntry.unmarshall(istream); std::stringstream ss; - ss << "DictEntry ({i(dy)}) : {" << std::endl; + ss << "DictEntry {i(dy)} : {" << std::endl; ss << " key: Int32 " << key << " (0x"; - ss << std::hex << std::setw(4) << std::setfill('0') << key; + ss << std::hex << std::setw(8) << std::setfill('0') << key; ss << std::dec << std::setw(0) << ")" << std::endl; - ss << " value: Struct (dy) <" << std::endl; + ss << " value: Struct (dy) : (" << std::endl; ss << " Double " << value.first << std::endl; ss << " Byte " << static_cast(value.second) << " (0x"; ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(value.second); ss << std::dec << std::setw(0) << ")" << std::endl; - ss << " >" << std::endl; + ss << " )" << std::endl; ss << "}" << std::endl; REQUIRE(dictEntry.toString() == ss.str()); @@ -195,10 +193,10 @@ namespace test { 0x00, }; - Type::DictEntry dictEntry; - dictEntry.setSignature("{vu}"); - MessageIStream istream(encoded.data(), encoded.size(), false); - REQUIRE_THROWS_WITH(dictEntry.unmarshall(istream), "Invalid basic type: v"); + Type::DictEntry dictEntry("{vu}"); + OctetBuffer buf(encoded.data(), encoded.size()); + MessageIStream istream(buf, false); + REQUIRE_THROWS_WITH(dictEntry.unmarshall(istream), "DictEntry key has invalid basic type: v"); } } // namespace test diff --git a/tests/dbus_type_double_unittest.cpp b/tests/dbus_type_double_unittest.cpp index 2ac51da..626adaa 100644 --- a/tests/dbus_type_double_unittest.cpp +++ b/tests/dbus_type_double_unittest.cpp @@ -24,12 +24,11 @@ namespace test { if (byteOrder != __BYTE_ORDER) u = __bswap_64(u); #endif - MessageIStream stream((uint8_t*)&u, sizeof(u), byteOrder != __BYTE_ORDER); + OctetBuffer buf((uint8_t*)&u, sizeof(u)); + MessageIStream stream(buf, byteOrder != __BYTE_ORDER); dbusType.unmarshall(stream); - std::stringstream ss; - ss << value; - REQUIRE(dbusType.asString() == ss.str()); + REQUIRE(static_cast(dbusType) == value); } TEST_CASE("Unmarshall double little endian from MessageIStream") @@ -51,12 +50,11 @@ namespace test { stream.writeDouble(value); Type::Double dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.size(), false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.size()); + MessageIStream istream(buf, false); dbusType.unmarshall(istream); - std::stringstream ss; - ss << value; - REQUIRE(dbusType.asString() == ss.str()); + REQUIRE(static_cast(dbusType) == value); } } // namespace test diff --git a/tests/dbus_type_int16_unittest.cpp b/tests/dbus_type_int16_unittest.cpp index e0ff688..8db227d 100644 --- a/tests/dbus_type_int16_unittest.cpp +++ b/tests/dbus_type_int16_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt16(value); Type::Int16 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_int32_unittest.cpp b/tests/dbus_type_int32_unittest.cpp index 29dfac3..d7608d1 100644 --- a/tests/dbus_type_int32_unittest.cpp +++ b/tests/dbus_type_int32_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt32(value); Type::Int32 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_int64_unittest.cpp b/tests/dbus_type_int64_unittest.cpp index 0375bc6..8931d73 100644 --- a/tests/dbus_type_int64_unittest.cpp +++ b/tests/dbus_type_int64_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt64(value); Type::Int64 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_multiple_unittest.cpp b/tests/dbus_type_multiple_unittest.cpp index bfa79ad..393a823 100644 --- a/tests/dbus_type_multiple_unittest.cpp +++ b/tests/dbus_type_multiple_unittest.cpp @@ -23,78 +23,82 @@ namespace test { { Type::Struct dbusStruct1; dbusStruct1.add(Type::Byte(85)); - dbusStruct1.add(Type::String("BrightSign")); + dbusStruct1.add("BrightSign"); Type::Struct dbusStruct2; dbusStruct2.add(Type::Byte(42)); - dbusStruct2.add(Type::String("Arthur")); + dbusStruct2.add("Arthur"); Type::Struct dbusStruct3; dbusStruct3.add(Type::Byte(99)); - dbusStruct3.add(Type::String("Red Balloons")); + dbusStruct3.add("Red Balloons"); - Type::Array dbusArray; - dbusArray.add(Type::DictEntry(Type::String("KeyKey"), dbusStruct1)); - dbusArray.add(Type::DictEntry(Type::String("Another key"), dbusStruct2)); - dbusArray.add(Type::DictEntry(Type::String("Anarchy"), dbusStruct3)); + Type::Array dbusArray1; + dbusArray1.add(Type::DictEntry("KeyKey", dbusStruct1)); + dbusArray1.add(Type::DictEntry("Another key", dbusStruct2)); + dbusArray1.add(Type::DictEntry("Anarchy", dbusStruct3)); + + Type::Array dbusArray2; + dbusArray2.add(Type::DictEntry("Key", dbusArray1)); Type::Struct dbusStructTop; - dbusStructTop.add(Type::String("This is a long string value")); - dbusStructTop.add(Type::DictEntry(Type::String("Key"), dbusArray)); + dbusStructTop.add("This is a long string value"); + dbusStructTop.add(dbusArray2); - Type::DictEntry input(Type::Int32(131073), dbusStructTop); + Type::Array input; + input.add(Type::DictEntry(Type::Int32(131073), dbusStructTop)); - REQUIRE(DBus::Type::getMarshallingSignature(input) == "{i(s{sa{s(ys)}})}"); + REQUIRE(input.getSignature() == "a{i(sa{sa{s(ys)}})}"); REQUIRE('\n' + input.toString() == R"( -DictEntry ({i(s{sa{s(ys)}})}) : { - key: Int32 131073 (0x20001) - value: Struct (s{sa{s(ys)}}) < - String (27) "This is a long string value" - DictEntry ({sa{s(ys)}}) : { - key: String (3) "Key" - value: Array (a{s(ys)}) [ - [0] = - DictEntry ({s(ys)}) : { - key: String (6) "KeyKey" - value: Struct (ys) < - Byte 85 (0x55) - String (10) "BrightSign" - > - } - [1] = - DictEntry ({s(ys)}) : { - key: String (11) "Another key" - value: Struct (ys) < - Byte 42 (0x2a) - String (6) "Arthur" - > - } - [2] = - DictEntry ({s(ys)}) : { - key: String (7) "Anarchy" - value: Struct (ys) < - Byte 99 (0x63) - String (12) "Red Balloons" - > - } +Array a{i(sa{sa{s(ys)}})} : [ + [0] DictEntry {i(sa{sa{s(ys)}})} : { + key: Int32 131073 (0x00020001) + value: Struct (sa{sa{s(ys)}}) : ( + String (27) "This is a long string value" + Array a{sa{s(ys)}} : [ + [0] DictEntry {sa{s(ys)}} : { + key: String (3) "Key" + value: Array a{s(ys)} : [ + [0] DictEntry {s(ys)} : { + key: String (6) "KeyKey" + value: Struct (ys) : ( + Byte 85 (0x55) + String (10) "BrightSign" + ) + } + [1] DictEntry {s(ys)} : { + key: String (11) "Another key" + value: Struct (ys) : ( + Byte 42 (0x2a) + String (6) "Arthur" + ) + } + [2] DictEntry {s(ys)} : { + key: String (7) "Anarchy" + value: Struct (ys) : ( + Byte 99 (0x63) + String (12) "Red Balloons" + ) + } + ] + } ] - } - > -} + ) + } +] )"); MessageOStream ostream; input.marshall(ostream); - REQUIRE(ostream.data.size() == 165); + REQUIRE(ostream.data.size() == 181); SECTION("Complete") { - Type::DictEntry output; - output.setSignature(input.getSignature()); - MessageIStream istream((uint8_t*)ostream.data.data(), ostream.data.size(), - false); + Type::Array output(input.getSignature()); + OctetBuffer buf((uint8_t*)ostream.data.data(), ostream.data.size()); + MessageIStream istream(buf, false); output.unmarshall(istream); REQUIRE(input.toString() == output.toString()); @@ -110,10 +114,10 @@ DictEntry ({i(s{sa{s(ys)}})}) : { { INFO("Truncate at " << truncate_at << " from " << ostream.data.size()); std::string data = ostream.data.substr(0, truncate_at); - Type::DictEntry output; - output.setSignature(input.getSignature()); + Type::Array output(input.getSignature()); - MessageIStream istream((uint8_t*)data.data(), data.size(), false); + OctetBuffer buf((uint8_t*)data.data(), data.size()); + MessageIStream istream(buf, false); REQUIRE_THROWS(output.unmarshall(istream)); } } diff --git a/tests/dbus_type_signature_unittest.cpp b/tests/dbus_type_signature_unittest.cpp index 2604c7d..b73540d 100644 --- a/tests/dbus_type_signature_unittest.cpp +++ b/tests/dbus_type_signature_unittest.cpp @@ -15,8 +15,8 @@ namespace test { { // An array of structures Type::Signature signature; - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder == __BYTE_ORDER); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder == __BYTE_ORDER); signature.unmarshall(istream); REQUIRE(signature.asString() == str); @@ -47,7 +47,7 @@ namespace test { TEST_CASE("Marshall and unmarshall signature") { - const std::string str("{ii}"); + const std::string str("a{ii}"); Type::Signature dbusString(str); MessageOStream stream; diff --git a/tests/dbus_type_string_unittest.cpp b/tests/dbus_type_string_unittest.cpp index 27da47a..1a57e7f 100644 --- a/tests/dbus_type_string_unittest.cpp +++ b/tests/dbus_type_string_unittest.cpp @@ -16,8 +16,8 @@ namespace test { { // An array of structures Type::String dbusString; - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); dbusString.unmarshall(istream); REQUIRE(dbusString.asString() == str); diff --git a/tests/dbus_type_struct_unittest.cpp b/tests/dbus_type_struct_unittest.cpp index d1e9c17..0434e7d 100644 --- a/tests/dbus_type_struct_unittest.cpp +++ b/tests/dbus_type_struct_unittest.cpp @@ -13,16 +13,15 @@ namespace test { const std::string& stream, uint32_t v1, uint32_t v2) { - Type::Struct dbusStruct; - dbusStruct.setSignature("(uu)"); + Type::Struct dbusStruct("(uu)"); - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); dbusStruct.unmarshall(istream); - REQUIRE(dbusStruct.getEntries() == 2); - REQUIRE(Type::asString(dbusStruct[0]) == std::to_string(v1)); - REQUIRE(Type::asString(dbusStruct[1]) == std::to_string(v2)); + REQUIRE(dbusStruct.size() == 2); + REQUIRE(Type::asUint32(dbusStruct[0]) == v1); + REQUIRE(Type::asUint32(dbusStruct[1]) == v2); } void TestUnmarshallMessageIStream(unsigned byteOrder, uint32_t v1, diff --git a/tests/dbus_type_uint16_unittest.cpp b/tests/dbus_type_uint16_unittest.cpp index 5e497f8..bd80313 100644 --- a/tests/dbus_type_uint16_unittest.cpp +++ b/tests/dbus_type_uint16_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt16(value); Type::Uint16 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_uint32_unittest.cpp b/tests/dbus_type_uint32_unittest.cpp index 4b023bb..ac15534 100644 --- a/tests/dbus_type_uint32_unittest.cpp +++ b/tests/dbus_type_uint32_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt32(value); Type::Uint32 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_uint64_unittest.cpp b/tests/dbus_type_uint64_unittest.cpp index 08b7c4d..34298ee 100644 --- a/tests/dbus_type_uint64_unittest.cpp +++ b/tests/dbus_type_uint64_unittest.cpp @@ -29,8 +29,8 @@ namespace test { stream.writeInt64(value); Type::Uint64 dbusType; - MessageIStream istream((uint8_t*)stream.data.data(), stream.data.size(), - false); + OctetBuffer buf((uint8_t*)stream.data.data(), stream.data.size()); + MessageIStream istream(buf, false); TestUnmarshallFromMessageIStream(value, dbusType, istream); } diff --git a/tests/dbus_type_unixfd_unittest.cpp b/tests/dbus_type_unixfd_unittest.cpp new file mode 100644 index 0000000..bfd8e89 --- /dev/null +++ b/tests/dbus_type_unixfd_unittest.cpp @@ -0,0 +1,47 @@ +#include "dbus_messageistream.h" +#include "dbus_messageostream.h" +#include "dbus_type_unixfd.h" +#include + +#include +#include + +namespace DBus { +namespace test { + +using Catch::Matchers::Equals; + + TEST_CASE("Marshall and unmarshall unix fd") + { + int fd = ::open("/tmp/unixfd_unittest", O_CREAT, S_IRUSR); + CHECK(fd != -1); + + Type::UnixFd unixFdOut(fd); + ::close(fd); // unixFdOut owns a dup(2) of fd at this point + ::unlink("/tmp/unixfd_unittest"); + + MessageOStream ostream; + unixFdOut.marshall(ostream); + REQUIRE(ostream.fds.size() == 1); + + OctetBuffer buf( + reinterpret_cast(ostream.data.data()), + ostream.data.size(), + ostream.fds); + MessageIStream istream(buf, false); + Type::UnixFd unixFdIn; + unixFdIn.unmarshall(istream); + + // UnixFds should reference the same file, but with different fds + REQUIRE_THAT(unixFdOut.fdName(), Equals(unixFdIn.fdName())); + REQUIRE_THAT(unixFdOut.asString(), !Equals(unixFdIn.asString())); + + // UnixFd::dup() should produce yet another fd to the file + int dup = unixFdIn.dup(); + REQUIRE_THAT(std::to_string(dup), !Equals(unixFdIn.asString())); + ::close(dup); + } + + +} // namespace test +} // namespace DBus diff --git a/tests/dbus_type_variant_unittest.cpp b/tests/dbus_type_variant_unittest.cpp index ae3be2a..701916d 100644 --- a/tests/dbus_type_variant_unittest.cpp +++ b/tests/dbus_type_variant_unittest.cpp @@ -16,8 +16,8 @@ namespace test { uint32_t value) { Type::Variant variant; - MessageIStream istream((uint8_t*)stream.data(), stream.size(), - byteOrder != __LITTLE_ENDIAN); + OctetBuffer buf((uint8_t*)stream.data(), stream.size()); + MessageIStream istream(buf, byteOrder != __LITTLE_ENDIAN); variant.unmarshall(istream); REQUIRE(variant.asString() == std::to_string(value)); @@ -77,10 +77,9 @@ namespace test { MessageOStream ostream; input.marshall(ostream); - Type::Variant output; - output.setSignature(input.getSignature()); - MessageIStream istream((uint8_t*)ostream.data.data(), ostream.data.size(), - false); + Type::Variant output(input.getSignature()); + OctetBuffer buf((uint8_t*)ostream.data.data(), ostream.data.size()); + MessageIStream istream(buf, false); output.unmarshall(istream); REQUIRE(input.toString() == output.toString()); diff --git a/tests/dbus_validation_unittest.cpp b/tests/dbus_validation_unittest.cpp deleted file mode 100644 index 17beb49..0000000 --- a/tests/dbus_validation_unittest.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "dbus_validation.h" -#include - -TEST_CASE("Basic types char") -{ - using namespace DBus::Validation; - - CHECK_NOTHROW(throwOnInvalidBasicType('y')); - CHECK_NOTHROW(throwOnInvalidBasicType('b')); - CHECK_NOTHROW(throwOnInvalidBasicType('n')); - CHECK_NOTHROW(throwOnInvalidBasicType('q')); - CHECK_NOTHROW(throwOnInvalidBasicType('u')); - CHECK_NOTHROW(throwOnInvalidBasicType('x')); - CHECK_NOTHROW(throwOnInvalidBasicType('t')); - CHECK_NOTHROW(throwOnInvalidBasicType('d')); - CHECK_NOTHROW(throwOnInvalidBasicType('h')); - CHECK_NOTHROW(throwOnInvalidBasicType('s')); - CHECK_NOTHROW(throwOnInvalidBasicType('o')); - CHECK_NOTHROW(throwOnInvalidBasicType('g')); - - CHECK_THROWS(throwOnInvalidBasicType('a')); - CHECK_THROWS(throwOnInvalidBasicType('v')); - CHECK_THROWS(throwOnInvalidBasicType('(')); - CHECK_THROWS(throwOnInvalidBasicType('{')); - CHECK_THROWS(throwOnInvalidBasicType('m')); - CHECK_THROWS(throwOnInvalidBasicType('*')); - CHECK_THROWS(throwOnInvalidBasicType('?')); - CHECK_THROWS(throwOnInvalidBasicType('@')); - CHECK_THROWS(throwOnInvalidBasicType('&')); - CHECK_THROWS(throwOnInvalidBasicType('^')); -} - -TEST_CASE("Basic types string") -{ - using namespace DBus::Validation; - - CHECK_NOTHROW(throwOnInvalidBasicType("y")); - CHECK_NOTHROW(throwOnInvalidBasicType("g")); - - CHECK_THROWS(throwOnInvalidBasicType("")); - CHECK_THROWS(throwOnInvalidBasicType("ai")); - CHECK_THROWS(throwOnInvalidBasicType("v")); - CHECK_THROWS(throwOnInvalidBasicType("(ii)")); - CHECK_THROWS(throwOnInvalidBasicType("{su}")); -} diff --git a/tests/test_unmarshall.h b/tests/test_unmarshall.h index 677fe2e..4a8bacf 100644 --- a/tests/test_unmarshall.h +++ b/tests/test_unmarshall.h @@ -37,7 +37,8 @@ namespace test { std::basic_string data; data.append((uint8_t*)&writeValue, sizeof(T)); - MessageIStream istream(data.data(), data.size(), byteOrder != __BYTE_ORDER); + OctetBuffer buf(data.data(), data.size()); + MessageIStream istream(buf, byteOrder != __BYTE_ORDER); TestUnmarshallFromMessageIStream(value, dbusType, istream); }