From 4842404f31b83c886fb3e930243531b01cfe6182 Mon Sep 17 00:00:00 2001 From: Bjorn Reese Date: Fri, 29 Dec 2017 15:19:23 +0100 Subject: [PATCH] Import and remake of the aware project --- CMakeLists.txt | 66 +++++ LICENSE_1_0.txt | 23 ++ cmake/FindAvahi.cmake | 41 +++ cmake/FindDNSSD.cmake | 27 ++ cmake/FindmDNSResponder.cmake | 80 +++++ example/dnssd/CMakeLists.txt | 11 + example/dnssd/zannounce.cpp | 49 ++++ example/dnssd/zmonitor.cpp | 93 ++++++ include/trial/aware/announce_socket.hpp | 36 +++ include/trial/aware/contact.hpp | 77 +++++ include/trial/aware/dnssd/factory.hpp | 39 +++ include/trial/aware/error.hpp | 33 +++ include/trial/aware/factory.hpp | 27 ++ include/trial/aware/monitor_socket.hpp | 36 +++ src/CMakeLists.txt | 15 + src/contact.cpp | 87 ++++++ src/dnssd/CMakeLists.txt | 43 +++ src/dnssd/avahi/announce_socket.cpp | 34 +++ src/dnssd/avahi/announce_socket.hpp | 44 +++ src/dnssd/avahi/announcer.cpp | 212 ++++++++++++++ src/dnssd/avahi/announcer.hpp | 57 ++++ src/dnssd/avahi/browser.cpp | 186 ++++++++++++ src/dnssd/avahi/browser.hpp | 57 ++++ src/dnssd/avahi/client.cpp | 144 +++++++++ src/dnssd/avahi/client.hpp | 52 ++++ src/dnssd/avahi/error.cpp | 70 +++++ src/dnssd/avahi/error.hpp | 31 ++ src/dnssd/avahi/factory.cpp | 34 +++ src/dnssd/avahi/monitor_socket.cpp | 133 +++++++++ src/dnssd/avahi/monitor_socket.hpp | 50 ++++ src/dnssd/avahi/poller.cpp | 331 +++++++++++++++++++++ src/dnssd/avahi/poller.hpp | 40 +++ src/dnssd/avahi/service.cpp | 31 ++ src/dnssd/avahi/service.hpp | 52 ++++ src/dnssd/avahi/utility.cpp | 48 +++ src/dnssd/avahi/utility.hpp | 31 ++ src/dnssd/mdns/announce_socket.cpp | 120 ++++++++ src/dnssd/mdns/announce_socket.hpp | 57 ++++ src/dnssd/mdns/announcer.cpp | 148 ++++++++++ src/dnssd/mdns/announcer.hpp | 62 ++++ src/dnssd/mdns/browser.cpp | 106 +++++++ src/dnssd/mdns/browser.hpp | 59 ++++ src/dnssd/mdns/dns_sd.hpp | 56 ++++ src/dnssd/mdns/error.cpp | 133 +++++++++ src/dnssd/mdns/error.hpp | 31 ++ src/dnssd/mdns/factory.cpp | 34 +++ src/dnssd/mdns/handle.cpp | 125 ++++++++ src/dnssd/mdns/handle.hpp | 62 ++++ src/dnssd/mdns/monitor.cpp | 375 ++++++++++++++++++++++++ src/dnssd/mdns/monitor.hpp | 134 +++++++++ src/dnssd/mdns/monitor_socket.cpp | 126 ++++++++ src/dnssd/mdns/monitor_socket.hpp | 60 ++++ src/dnssd/mdns/properties.cpp | 55 ++++ src/dnssd/mdns/properties.hpp | 46 +++ src/dnssd/mdns/resolver.cpp | 201 +++++++++++++ src/dnssd/mdns/resolver.hpp | 59 ++++ src/dnssd/mdns/service.cpp | 36 +++ src/dnssd/mdns/service.hpp | 53 ++++ src/dnssd/mdns/throw_on_error.cpp | 30 ++ src/dnssd/mdns/throw_on_error.hpp | 27 ++ src/dnssd/mdns/utility.cpp | 48 +++ src/dnssd/mdns/utility.hpp | 31 ++ src/error.cpp | 60 ++++ src/native_socket.cpp | 41 +++ src/native_socket.hpp | 69 +++++ 65 files changed, 4864 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE_1_0.txt create mode 100644 cmake/FindAvahi.cmake create mode 100644 cmake/FindDNSSD.cmake create mode 100644 cmake/FindmDNSResponder.cmake create mode 100644 example/dnssd/CMakeLists.txt create mode 100644 example/dnssd/zannounce.cpp create mode 100644 example/dnssd/zmonitor.cpp create mode 100644 include/trial/aware/announce_socket.hpp create mode 100644 include/trial/aware/contact.hpp create mode 100644 include/trial/aware/dnssd/factory.hpp create mode 100644 include/trial/aware/error.hpp create mode 100644 include/trial/aware/factory.hpp create mode 100644 include/trial/aware/monitor_socket.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/contact.cpp create mode 100644 src/dnssd/CMakeLists.txt create mode 100644 src/dnssd/avahi/announce_socket.cpp create mode 100644 src/dnssd/avahi/announce_socket.hpp create mode 100644 src/dnssd/avahi/announcer.cpp create mode 100644 src/dnssd/avahi/announcer.hpp create mode 100644 src/dnssd/avahi/browser.cpp create mode 100644 src/dnssd/avahi/browser.hpp create mode 100644 src/dnssd/avahi/client.cpp create mode 100644 src/dnssd/avahi/client.hpp create mode 100644 src/dnssd/avahi/error.cpp create mode 100644 src/dnssd/avahi/error.hpp create mode 100644 src/dnssd/avahi/factory.cpp create mode 100644 src/dnssd/avahi/monitor_socket.cpp create mode 100644 src/dnssd/avahi/monitor_socket.hpp create mode 100644 src/dnssd/avahi/poller.cpp create mode 100644 src/dnssd/avahi/poller.hpp create mode 100644 src/dnssd/avahi/service.cpp create mode 100644 src/dnssd/avahi/service.hpp create mode 100644 src/dnssd/avahi/utility.cpp create mode 100644 src/dnssd/avahi/utility.hpp create mode 100644 src/dnssd/mdns/announce_socket.cpp create mode 100644 src/dnssd/mdns/announce_socket.hpp create mode 100644 src/dnssd/mdns/announcer.cpp create mode 100644 src/dnssd/mdns/announcer.hpp create mode 100644 src/dnssd/mdns/browser.cpp create mode 100644 src/dnssd/mdns/browser.hpp create mode 100644 src/dnssd/mdns/dns_sd.hpp create mode 100644 src/dnssd/mdns/error.cpp create mode 100644 src/dnssd/mdns/error.hpp create mode 100644 src/dnssd/mdns/factory.cpp create mode 100644 src/dnssd/mdns/handle.cpp create mode 100644 src/dnssd/mdns/handle.hpp create mode 100644 src/dnssd/mdns/monitor.cpp create mode 100644 src/dnssd/mdns/monitor.hpp create mode 100644 src/dnssd/mdns/monitor_socket.cpp create mode 100644 src/dnssd/mdns/monitor_socket.hpp create mode 100644 src/dnssd/mdns/properties.cpp create mode 100644 src/dnssd/mdns/properties.hpp create mode 100644 src/dnssd/mdns/resolver.cpp create mode 100644 src/dnssd/mdns/resolver.hpp create mode 100644 src/dnssd/mdns/service.cpp create mode 100644 src/dnssd/mdns/service.hpp create mode 100644 src/dnssd/mdns/throw_on_error.cpp create mode 100644 src/dnssd/mdns/throw_on_error.hpp create mode 100644 src/dnssd/mdns/utility.cpp create mode 100644 src/dnssd/mdns/utility.hpp create mode 100644 src/error.cpp create mode 100644 src/native_socket.cpp create mode 100644 src/native_socket.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8cea59d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +############################################################################### +# +# Copyright (C) 2017 Bjorn Reese +# +# 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) +# +############################################################################### + +cmake_minimum_required(VERSION 3.0) +project(trial.aware) + +option(WITH_DNSSD "Build with DNS-SD" TRUE) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") + +set(TRIAL_AWARE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) +set(TRIAL_AWARE_BUILD_DIR ${CMAKE_BINARY_DIR}) +set(LIBRARY_OUTPUT_PATH ${TRIAL_AWARE_BUILD_DIR}/lib) +set(EXECUTABLE_OUTPUT_PATH ${TRIAL_AWARE_BUILD_DIR}/bin) + +set(CMAKE_CXX_STANDARD 11) + +if (MSVC) + set(TRIAL_AWARE_WARNING_FLAGS /W4) +else() + set(TRIAL_AWARE_WARNING_FLAGS -Wall -Wextra -pedantic) +endif() + +############################################################################### +# Boost package +############################################################################### + +find_package(Boost 1.55.0 REQUIRED QUIET COMPONENTS system) + +############################################################################### +# DNS-SD package +############################################################################### + +if (WITH_DNSSD) + find_package(DNSSD REQUIRED) + if (TARGET DNSSD::Avahi) + message(STATUS "DNS-SD backend: Avahi") + elseif (TARGET DNSSD::mDNSResponder) + message(STATUS "DNS-SD backend: mDNSResponder") + else() + message(FATAL_ERROR "DNS-SD backend: Not found") + endif() +endif() + +############################################################################### +# Trial.Aware package +############################################################################### + +include_directories(${TRIAL_AWARE_ROOT}/include) +add_subdirectory(src) + +############################################################################### +# Examples +############################################################################### + +if (WITH_DNSSD) + add_subdirectory(example/dnssd EXCLUDE_FROM_ALL) + add_custom_target(example DEPENDS dnssd-example) +endif() diff --git a/LICENSE_1_0.txt b/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/cmake/FindAvahi.cmake b/cmake/FindAvahi.cmake new file mode 100644 index 0000000..f6f4705 --- /dev/null +++ b/cmake/FindAvahi.cmake @@ -0,0 +1,41 @@ +# FindAvahi +# +# Find Avahi +# +# :: +# Avahi_INCLUDE_DIR +# Avahi_LIBRARY +# Avahi_FOUND +# +# Defines DNSSD::Avahi imported target + +find_package(PkgConfig QUIET REQUIRED) +pkg_check_modules(Avahi QUIET avahi-client) + +if (Avahi_FOUND) + find_library(Avahi_COMMON_LIBRARY NAMES avahi-common PATHS ${Avahi_LIBDIR}) + find_library(Avahi_CLIENT_LIBRARY NAMES avahi-client PATHS ${Avahi_LIBDIR}) + + if (NOT TARGET DNSSD::AvahiCommon) + add_library(DNSSD::AvahiCommon SHARED IMPORTED GLOBAL) + set_target_properties(DNSSD::AvahiCommon PROPERTIES + IMPORTED_LOCATION "${Avahi_COMMON_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${Avahi_CFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${Avahi_INCLUDE_DIR}" + ) + endif() + if (NOT TARGET DNSSD::Avahi) + add_library(DNSSD::Avahi SHARED IMPORTED GLOBAL) + set_target_properties(DNSSD::Avahi PROPERTIES + IMPORTED_LOCATION "${Avahi_CLIENT_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${Avahi_CFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${Avahi_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES DNSSD::AvahiCommon + ) + endif() + +elseif(Avahi_FIND_REQUIRED) + if (NOT Avahi_FIND_QUIETLY) + message(FATAL_ERROR "Avahi package not found") + endif() +endif() diff --git a/cmake/FindDNSSD.cmake b/cmake/FindDNSSD.cmake new file mode 100644 index 0000000..f14835d --- /dev/null +++ b/cmake/FindDNSSD.cmake @@ -0,0 +1,27 @@ +# FindDNSSD +# +# Find DNS Service Discovery backend + +option(DNSSD_WITH_AVAHI "Check for Avahi" ON) +set(DNSSD_WITH_MDNSRESPONDER "" CACHE PATH "Check for mDNSResponder using build directory") + +if (APPLE) + # Included in libSystem + set(DNSSD_FOUND TRUE) + # FIXME: Create library alias instead + +elseif(WIN32) + # FIXME + +else() # Posix + if (DNSSD_WITH_AVAHI) + find_package(Avahi QUIET) + endif() + + if (NOT DNSSD_FOUND AND DNSSD_WITH_MDNSRESPONDER) + if (NOT DNSSD_WITH_MDNSRESPONDER STREQUAL "") + set(mDNSResponder_BUILD_DIR ${DNSSD_WITH_MDNSRESPONDER}) + endif() + find_package(mDNSResponder QUIET) + endif() +endif() diff --git a/cmake/FindmDNSResponder.cmake b/cmake/FindmDNSResponder.cmake new file mode 100644 index 0000000..cb5811b --- /dev/null +++ b/cmake/FindmDNSResponder.cmake @@ -0,0 +1,80 @@ +# FindmDNSResponder +# +# Find Apple mDNSResponder +# +# :: +# mDNSResponder_INCLUDE_DIR +# mDNSResponder_LIBRARY +# mDNSResponder_FOUND +# +# mDNSResponder_LIBRARIES +# mDNSResponder_INCLUDE_DIRS +# +# Defines DNSSD::mDNSResponder imported target + +if (mDNSResponder_BUILD_DIR) + set(mDNSResponder_INCDIR "${mDNSResponder_BUILD_DIR}/mDNSShared") + set(mDNSResponder_LIBDIR "${mDNSResponder_BUILD_DIR}/mDNSPosix/build/prod") +endif() + +find_path(mDNSResponder_INCLUDE_DIR + NAMES dns_sd.h + PATHS "${mDNSResponder_INCDIR}") + +find_library(mDNSResponder_LIBRARY + NAMES dns_sd + PATHS "${mDNSResponder_LIBDIR}" +) + +set(mDNSResponder_FOUND FALSE CACHE BOOL "" FORCE) +if (mDNSResponder_INCLUDE_DIR AND mDNSResponder_LIBRARY) + if (EXISTS ${mDNSResponder_INCLUDE_DIR}/dns_sd.h) + file(STRINGS ${mDNSResponder_INCLUDE_DIR}/dns_sd.h HEADER_FILE NEWLINE_CONSUME) + string(REGEX MATCH "#define _DNS_SD_H[^\n]*" VERSION_LINE ${HEADER_FILE}) + string(REGEX REPLACE + "#define _DNS_SD_H[ \t]*([0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*" + "\\1.\\2.\\3" + mDNSResponder_VERSION + ${VERSION_LINE}) + if ("${mDNSResponder_VERSION}" STREQUAL "${VERSION_LINE}") + set(mDNSResponder_VERSION 0) + else() + set(mDNSResponder_FOUND TRUE CACHE BOOL "" FORCE) + endif() + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(mDNSResponder + FOUND_VAR mDNSResponder_FOUND + REQUIRED_VARS mDNSResponder_LIBRARY mDNSResponder_INCLUDE_DIR + VERSION_VAR mDNSResponder_VERSION +) + +if(mDNSResponder_FOUND) + set(mDNSResponder_LIBRARIES ${mDNSResponder_LIBRARY}) + set(mDNSResponder_INCLUDE_DIRS ${mDNSResponder_INCLUDE_DIR}) + set(mDNSResponder_DEFINITIONS "") + + if (NOT TARGET DNSSD::mDNSResponder) + add_library(DNSSD::mDNSResponder SHARED IMPORTED GLOBAL) + set_target_properties(DNSSD::mDNSResponder PROPERTIES + IMPORTED_LOCATION "${mDNSResponder_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${mDNSResponder_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${mDNSResponder_INCLUDE_DIR}" + ) + endif() + +elseif(mDNSResponder_FIND_REQUIRED) + if (NOT mDNSResponder_FIND_QUIETLY) + message(FATAL_ERROR "mDNSResponder package not found") + endif() +endif() + +mark_as_advanced( + mDNSResponder_FOUND + mDNSResponder_VERSION + mDNSResponder_INCLUDE_DIR + mDNSResponder_LIBRARY + mDNSResponder_INCDIR + mDNSResponder_LIBDIR) diff --git a/example/dnssd/CMakeLists.txt b/example/dnssd/CMakeLists.txt new file mode 100644 index 0000000..21ca2c0 --- /dev/null +++ b/example/dnssd/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(zannounce + zannounce.cpp + ) +target_link_libraries(zannounce aware) + +add_executable(zmonitor + zmonitor.cpp + ) +target_link_libraries(zmonitor aware) + +add_custom_target(dnssd-example DEPENDS zannounce zmonitor) diff --git a/example/dnssd/zannounce.cpp b/example/dnssd/zannounce.cpp new file mode 100644 index 0000000..5ac7ba2 --- /dev/null +++ b/example/dnssd/zannounce.cpp @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + trial::aware::contact::properties_type properties; + properties["key"] = "value"; + auto contact = trial::aware::contact(argv[1]) + .name(argv[2]) + .address(boost::asio::ip::address()) + .port(3834) + .properties(properties); + + boost::asio::io_service io; + trial::aware::dnssd::factory factory; + auto announcer = factory.make_announce(io); + announcer->async_announce( + contact, + [&contact] (const boost::system::error_code& error) + { + std::cout << "Announced: " << error.message() << std::endl; + if (!error) + { + std::cout << contact.type() << " = " << contact.name() << std::endl; + } + }); + io.run(); + return 0; +} diff --git a/example/dnssd/zmonitor.cpp b/example/dnssd/zmonitor.cpp new file mode 100644 index 0000000..d824d75 --- /dev/null +++ b/example/dnssd/zmonitor.cpp @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +class my_monitor +{ +public: + my_monitor(boost::asio::io_service& io, + trial::aware::factory& factory) + : socket(factory.make_monitor(io)) + {} + + void async_monitor(trial::aware::contact& contact) + { + socket->async_monitor(contact, + std::bind(&my_monitor::process_monitor, + this, + std::placeholders::_1, + std::ref(contact))); + } + +private: + void process_monitor(const boost::system::error_code& error, + trial::aware::contact& contact) + { + if (!error) + { + if (contact.empty()) + { + std::cout << "Removed:" << std::endl; + std::cout << " type = " << contact.type() << std::endl; + std::cout << " name = " << contact.name() << std::endl; + } + else + { + std::cout << "Added:" << std::endl; + std::cout << " type = " << contact.type() << std::endl; + std::cout << " name = " << contact.name() << std::endl; + std::cout << " endpoint = " << contact.address().to_string() << ":" << contact.port() << std::endl; + auto properties = contact.properties(); + for (const auto& property : properties) + { + std::cout << " " << property.first << " = " << property.second << std::endl; + } + } + // Launch the next monitor operation + async_monitor(contact); + } + else if (error == boost::asio::error::operation_aborted) + { + // Ignore + } + else + { + std::cout << "Error = " << error << std::endl; + } + } + +private: + std::unique_ptr socket; +}; + +int main(int argc, char *argv[]) +{ + if (argc != 2) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + boost::asio::io_service io; + trial::aware::dnssd::factory factory; + my_monitor monitor(io, factory); + + trial::aware::contact contact(argv[1]); + monitor.async_monitor(contact); + io.run(); + + return 0; +} diff --git a/include/trial/aware/announce_socket.hpp b/include/trial/aware/announce_socket.hpp new file mode 100644 index 0000000..825b305 --- /dev/null +++ b/include/trial/aware/announce_socket.hpp @@ -0,0 +1,36 @@ +#ifndef TRIAL_AWARE_ANNOUNCE_SOCKET_HPP +#define TRIAL_AWARE_ANNOUNCE_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace trial +{ +namespace aware +{ + +class announce_socket +{ +public: + using async_announce_handler = std::function; + + virtual ~announce_socket() = default; + + virtual void async_announce(aware::contact&, async_announce_handler) = 0; +}; + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_ANNOUNCE_SOCKET_HPP diff --git a/include/trial/aware/contact.hpp b/include/trial/aware/contact.hpp new file mode 100644 index 0000000..3e5f564 --- /dev/null +++ b/include/trial/aware/contact.hpp @@ -0,0 +1,77 @@ +#ifndef TRIAL_AWARE_CONTACT_HPP +#define TRIAL_AWARE_CONTACT_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace trial +{ +namespace aware +{ + +class contact +{ +public: + using index_type = int; + using address_type = boost::asio::ip::address; + using port_type = unsigned short; + using properties_type = std::map; + + contact(std::string type); + contact(const contact&) = default; + contact(contact&&) = default; + contact& operator= (const contact&) = default; + contact& operator= (contact&&) = default; + + // Setters + contact& name(std::string); + contact& domain(std::string); + contact& index(index_type); + contact& address(address_type); + contact& port(port_type); + contact& properties(properties_type); + + // Getters + bool empty() const; + const std::string& type() const & { return data.type; } + const std::string& name() const & { return data.name; } + const std::string& domain() const & { return data.domain; } + index_type index() const & { return data.index; } + const address_type& address() const & { return data.address; } + port_type port() const & { return data.port; } + const properties_type& properties() const & { return data.properties; } + + bool operator== (const contact&) const; + bool operator< (const contact&) const; + +public: + static constexpr index_type wildcard = -1; + +private: + struct + { + std::string type; + std::string name; + std::string domain; + address_type address; + properties_type properties; + index_type index; + port_type port; + } data; +}; + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_CONTACT_HPP diff --git a/include/trial/aware/dnssd/factory.hpp b/include/trial/aware/dnssd/factory.hpp new file mode 100644 index 0000000..6bc4f44 --- /dev/null +++ b/include/trial/aware/dnssd/factory.hpp @@ -0,0 +1,39 @@ +#ifndef TRIAL_AWARE_DNSSD_FACTORY_HPP +#define TRIAL_AWARE_DNSSD_FACTORY_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace trial +{ +namespace aware +{ +namespace dnssd +{ + +// DNS Service Discovery + +class factory + : public aware::factory +{ +public: + virtual std::unique_ptr make_announce(boost::asio::io_service&) override; + virtual std::unique_ptr make_monitor(boost::asio::io_service&) override; +}; + +} // namespace dnssd +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_DNSSD_FACTORY_HPP diff --git a/include/trial/aware/error.hpp b/include/trial/aware/error.hpp new file mode 100644 index 0000000..52728f0 --- /dev/null +++ b/include/trial/aware/error.hpp @@ -0,0 +1,33 @@ +#ifndef TRIAL_AWARE_ERROR_HPP +#define TRIAL_AWARE_ERROR_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ + +enum errc +{ + success = 0, +}; + +const boost::system::error_category& category(); +boost::system::error_code make_error_code(aware::errc); +boost::system::error_condition make_error_condition(aware::errc); + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_ERROR_HPP diff --git a/include/trial/aware/factory.hpp b/include/trial/aware/factory.hpp new file mode 100644 index 0000000..69b1444 --- /dev/null +++ b/include/trial/aware/factory.hpp @@ -0,0 +1,27 @@ +#ifndef TRIAL_AWARE_FACTORY_HPP +#define TRIAL_AWARE_FACTORY_HPP + +#include +#include + +namespace trial +{ +namespace aware +{ + +class announce_socket; +class monitor_socket; + +class factory +{ +public: + virtual ~factory() = default; + + virtual std::unique_ptr make_announce(boost::asio::io_service&) = 0; + virtual std::unique_ptr make_monitor(boost::asio::io_service&) = 0; +}; + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_FACTORY_HPP diff --git a/include/trial/aware/monitor_socket.hpp b/include/trial/aware/monitor_socket.hpp new file mode 100644 index 0000000..c06fce5 --- /dev/null +++ b/include/trial/aware/monitor_socket.hpp @@ -0,0 +1,36 @@ +#ifndef TRIAL_AWARE_MONITOR_SOCKET_HPP +#define TRIAL_AWARE_MONITOR_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace trial +{ +namespace aware +{ + +class monitor_socket +{ +public: + using async_monitor_handler = std::function; + + virtual ~monitor_socket() = default; + + virtual void async_monitor(aware::contact&, async_monitor_handler) = 0; +}; + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MONITOR_SOCKET_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..18f63f3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(aware-common + contact.cpp + error.cpp + native_socket.cpp + ) + +target_compile_options(aware-common PRIVATE ${TRIAL_AWARE_WARNING_FLAGS}) +target_link_libraries(aware-common Boost::system) + +include_directories(aware-common BEFORE PRIVATE .) + +if (WITH_DNSSD) + add_subdirectory(dnssd) + add_library(aware ALIAS aware-dnssd) +endif() diff --git a/src/contact.cpp b/src/contact.cpp new file mode 100644 index 0000000..f8c9b7b --- /dev/null +++ b/src/contact.cpp @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ + +contact::contact(std::string value) +{ + data.type = std::move(value); + data.index = wildcard; +} + +contact& contact::name(std::string value) +{ + data.name = std::move(value); + return *this; +} + +contact& contact::domain(std::string value) +{ + data.domain = std::move(value); + return *this; +} + +contact& contact::index(index_type value) +{ + data.index = value; + return *this; +} + +contact& contact::address(address_type value) +{ + data.address = std::move(value); + return *this; +} + +contact& contact::port(port_type value) +{ + data.port = value; + return *this; +} + +contact& contact::properties(properties_type value) +{ + data.properties = std::move(value); + return *this; +} + +bool contact::empty() const +{ + return data.address.is_unspecified(); +} + +bool contact::operator== (const contact& other) const +{ + return (((data.index == other.data.index) || + (data.index == wildcard) || + (other.data.index == wildcard)) && + (data.type == other.data.type) && + (data.name == other.data.name) && + (data.domain == other.data.domain)); +} + +bool contact::operator< (const contact& other) const +{ + return ((data.index < other.data.index) || + ((data.index == other.data.index) && + ((data.type < other.data.type) || + ((data.type == other.data.type) && + ((data.name < other.data.name) || + ((data.name == other.data.name) && + (data.domain < other.data.domain))))))); +} + +} // namespace aware +} // namespace trial diff --git a/src/dnssd/CMakeLists.txt b/src/dnssd/CMakeLists.txt new file mode 100644 index 0000000..a7adc4e --- /dev/null +++ b/src/dnssd/CMakeLists.txt @@ -0,0 +1,43 @@ +if (TARGET DNSSD::Avahi) + + add_library(aware-dnssd + avahi/announce_socket.cpp + avahi/announcer.cpp + avahi/browser.cpp + avahi/client.cpp + avahi/error.cpp + avahi/factory.cpp + avahi/monitor_socket.cpp + avahi/poller.cpp + avahi/service.cpp + avahi/utility.cpp + ) + + target_link_libraries(aware-dnssd aware-common DNSSD::Avahi) + +elseif (TARGET DNSSD::mDNSResponder) + + add_library(aware-dnssd + mdns/announce_socket.cpp + mdns/announcer.cpp + mdns/browser.cpp + mdns/error.cpp + mdns/factory.cpp + mdns/handle.cpp + mdns/monitor.cpp + mdns/monitor_socket.cpp + mdns/properties.cpp + mdns/resolver.cpp + mdns/service.cpp + mdns/throw_on_error.cpp + mdns/utility.cpp + ) + + target_link_libraries(aware-dnssd aware-common DNSSD::mDNSResponder) + +else() + message(FATAL_ERROR "No DNS-SD backend is found") +endif() + +target_compile_options(aware-dnssd PRIVATE ${TRIAL_AWARE_WARNING_FLAGS}) +target_include_directories(aware-dnssd BEFORE PRIVATE .) diff --git a/src/dnssd/avahi/announce_socket.cpp b/src/dnssd/avahi/announce_socket.cpp new file mode 100644 index 0000000..dd60b33 --- /dev/null +++ b/src/dnssd/avahi/announce_socket.cpp @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "avahi/announce_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +announce_socket::announce_socket(boost::asio::io_service& io) + : boost::asio::basic_io_object(io), + announcer(boost::asio::use_service(io).client()) +{ +} + +void announce_socket::async_announce(aware::contact& contact, + async_announce_handler handler) +{ + announcer.async_announce(contact, handler); +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/announce_socket.hpp b/src/dnssd/avahi/announce_socket.hpp new file mode 100644 index 0000000..0b6f3e0 --- /dev/null +++ b/src/dnssd/avahi/announce_socket.hpp @@ -0,0 +1,44 @@ +#ifndef TRIAL_AWARE_AVAHI_ANNOUNCE_SOCKET_HPP +#define TRIAL_AWARE_AVAHI_ANNOUNCE_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "avahi/service.hpp" +#include "avahi/announcer.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +class announce_socket + : public aware::announce_socket, + public boost::asio::basic_io_object +{ +public: + announce_socket(boost::asio::io_service&); + + virtual void async_announce(aware::contact&, + async_announce_handler) override; + +private: + avahi::announcer announcer; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_ANNOUNCE_SOCKET_HPP diff --git a/src/dnssd/avahi/announcer.cpp b/src/dnssd/avahi/announcer.cpp new file mode 100644 index 0000000..256abd8 --- /dev/null +++ b/src/dnssd/avahi/announcer.cpp @@ -0,0 +1,212 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "avahi/utility.hpp" +#include "avahi/error.hpp" +#include "avahi/client.hpp" +#include "avahi/announcer.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +//----------------------------------------------------------------------------- +// announcer::wrapper +//----------------------------------------------------------------------------- + +struct announcer::wrapper +{ + static void entry_group_callback(AvahiEntryGroup *group, + AvahiEntryGroupState state, + void *userdata) + { + auto self = static_cast(userdata); + + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + { + boost::system::error_code success; + self->handler(success); + } + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + case AVAHI_ENTRY_GROUP_FAILURE: + { + self->handler(avahi::make_error_code(avahi_client_errno(avahi_entry_group_get_client(group)))); + } + break; + + default: + break; + } + } +}; + +//----------------------------------------------------------------------------- +// property_list +//----------------------------------------------------------------------------- + +// RAII wrapper for AvahiStringList +class property_list +{ +public: + property_list() + : data(0) + { + } + + ~property_list() + { + avahi_string_list_free(data); + } + + property_list& operator = (const announcer::property_map& properties) + { + for (const auto& property : properties) + { + data = avahi_string_list_add_pair(data, property.first.c_str(), property.second.c_str()); + if (data == 0) + { + break; + } + } + return *this; + } + + operator AvahiStringList *() + { + return data; + } + +private: + AvahiStringList *data; +}; + +//----------------------------------------------------------------------------- +// announcer +//----------------------------------------------------------------------------- + +announcer::announcer(const avahi::client& client) + : ptr(0) +{ + ptr = avahi_entry_group_new(client, + wrapper::entry_group_callback, + this); + if (ptr == 0) + throw boost::system::system_error(avahi::make_error_code(AVAHI_ERR_DISCONNECTED)); + + assert(avahi_entry_group_get_state(ptr) == AVAHI_ENTRY_GROUP_UNCOMMITED); +} + +announcer::~announcer() +{ + if (ptr) + { + avahi_entry_group_free(ptr); + } +} + +void announcer::async_announce(aware::contact& contact, + async_announce_handler h) +{ + assert(ptr != 0); + + handler = h; + + const AvahiPublishFlags flags = AvahiPublishFlags(0); + // Use all network interfaces + const AvahiIfIndex interface_index = AVAHI_IF_UNSPEC; + // Use only a specific protocol + const AvahiProtocol protocol = + contact.address().is_v6() + ? AVAHI_PROTO_INET6 + : AVAHI_PROTO_INET; + auto name = contact.name(); + auto type = avahi::type_encode(contact.type()); + // Use .local + const char *domain = 0; + // Host name + const auto& address = contact.address(); + std::string host = address.is_unspecified() + ? "" // Use default host name + : address.to_string(); + + property_list properties; + if (!contact.properties().empty()) + { + properties = contact.properties(); + if (properties == 0) + { + handler(avahi::make_error_code(AVAHI_ERR_NO_MEMORY)); + return; + } + } + + while (true) + { + int rc = avahi_entry_group_add_service_strlst(ptr, + interface_index, + protocol, + flags, + name.c_str(), + type.c_str(), + domain, + host.empty() ? 0 : host.c_str(), + contact.port(), + properties); + if (rc == AVAHI_ERR_COLLISION) + { + char *alternative = avahi_alternative_service_name(name.c_str()); + name = alternative; + avahi_free(alternative); + } + else if (rc != AVAHI_OK) + { + handler(avahi::make_error_code(rc)); + return; + } + else + { + break; + } + } + commit(ptr); +} + +void announcer::commit(AvahiEntryGroup *group) +{ + assert(group != 0); + + if (!avahi_entry_group_is_empty(group)) + { + int rc = avahi_entry_group_commit(group); + if (rc != AVAHI_OK) + { + handler(avahi::make_error_code(rc)); + } + } +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/announcer.hpp b/src/dnssd/avahi/announcer.hpp new file mode 100644 index 0000000..fce3436 --- /dev/null +++ b/src/dnssd/avahi/announcer.hpp @@ -0,0 +1,57 @@ +#ifndef TRIAL_AWARE_AVAHI_ANNOUNCER_HPP +#define TRIAL_AWARE_AVAHI_ANNOUNCER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +struct AvahiEntryGroup; + +namespace trial +{ +namespace aware +{ +namespace avahi +{ +class client; + +// RAII wrapper for avahi_entry_group functionality +class announcer +{ +public: + using property_map = std::map; + using async_announce_handler = std::function; + + announcer(const avahi::client&); + ~announcer(); + + void async_announce(aware::contact&, + async_announce_handler); + +private: + void commit(AvahiEntryGroup *); + +private: + struct wrapper; + + AvahiEntryGroup *ptr; + async_announce_handler handler; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_ANNOUNCER_HPP diff --git a/src/dnssd/avahi/browser.cpp b/src/dnssd/avahi/browser.cpp new file mode 100644 index 0000000..c46d91e --- /dev/null +++ b/src/dnssd/avahi/browser.cpp @@ -0,0 +1,186 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "avahi/error.hpp" +#include "avahi/utility.hpp" +#include "avahi/client.hpp" +#include "avahi/browser.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +boost::asio::ip::address to_address(const AvahiAddress& addr) +{ + using namespace boost::asio; + switch (addr.proto) + { + case AVAHI_PROTO_INET: + return ip::address_v4(reinterpret_cast(addr.data.ipv4.address)); + + case AVAHI_PROTO_INET6: + return ip::address_v6(reinterpret_cast(addr.data.ipv6.address)); + + default: + assert(false); + break; + } +} + +struct browser::wrapper +{ + static void resolver_callback( + AvahiServiceResolver *resolver, + AvahiIfIndex, + AvahiProtocol, + AvahiResolverEvent event, + const char *name, + const char *full_type, + const char * /* domain */, + const char * /* host_name */, + const AvahiAddress *address, + unsigned short port, + AvahiStringList *txt, + AvahiLookupResultFlags, + void *userdata) + { + auto self = static_cast(userdata); + + if (event == AVAHI_RESOLVER_FOUND) + { + // Convert txt record to property map + aware::contact::properties_type properties; + for (; txt != 0; txt = avahi_string_list_get_next(txt)) + { + char *key; + char *value; + if (avahi_string_list_get_pair(txt, &key, &value, 0) == AVAHI_OK) + { + properties[key] = value; + avahi_free(key); + avahi_free(value); + } + } + // Notify requester + auto type = avahi::type_decode(full_type); + auto contact = aware::contact(type).name(name).address(avahi::to_address(*address)).port(port).properties(properties); + self->listener.on_appear(contact); + } + else + { + self->listener.on_failure(avahi::make_error_code(avahi_client_errno(avahi_service_resolver_get_client(resolver)))); + } + avahi_service_resolver_free(resolver); + } + + static void browser_callback( + AvahiServiceBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *full_type, + const char *domain, + AvahiLookupResultFlags, + void *userdata) + { + auto self = static_cast(userdata); + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + { + self->listener.on_failure(avahi::make_error_code(avahi_client_errno(avahi_service_browser_get_client(browser)))); + } + break; + + case AVAHI_BROWSER_NEW: + { + // Use resolver to obtain port and properties + AvahiServiceResolver *resolver = avahi_service_resolver_new( + avahi_service_browser_get_client(browser), + interface, + protocol, + name, + full_type, + domain, + AVAHI_PROTO_UNSPEC, + AvahiLookupFlags(0), + wrapper::resolver_callback, + userdata); + if (!resolver) + { + self->listener.on_failure(avahi::make_error_code(avahi_client_errno(avahi_service_browser_get_client(browser)))); + } + // resolver is freed in the callback + } + break; + + case AVAHI_BROWSER_REMOVE: + { + auto type = avahi::type_decode(full_type); + auto contact = aware::contact(type).name(name); + self->listener.on_disappear(contact); + } + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + + default: + break; + } + } +}; + +browser::browser(const avahi::client& client, + const aware::contact& contact, + listener_type& listener) + : listener(listener) +{ + const AvahiProtocol protocol = + contact.address().is_v6() + ? AVAHI_PROTO_INET6 + : AVAHI_PROTO_INET; + auto type = avahi::type_encode(contact.type()); + ptr = avahi_service_browser_new(client, + AVAHI_IF_UNSPEC, + protocol, + type.c_str(), + NULL, + AvahiLookupFlags(0), + wrapper::browser_callback, + this); + if (ptr == 0) + throw boost::system::system_error(avahi::make_error_code(AVAHI_ERR_DISCONNECTED)); +} + +browser::~browser() +{ + if (ptr) + { + avahi_service_browser_free(ptr); + } +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/browser.hpp b/src/dnssd/avahi/browser.hpp new file mode 100644 index 0000000..99b2757 --- /dev/null +++ b/src/dnssd/avahi/browser.hpp @@ -0,0 +1,57 @@ +#ifndef TRIAL_AWARE_AVAHI_BROWSER_HPP +#define TRIAL_AWARE_AVAHI_BROWSER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +struct AvahiServiceBrowser; + +namespace trial +{ +namespace aware +{ +namespace avahi +{ +class client; + +class browser +{ +public: + class listener + { + public: + virtual ~listener() {} + + virtual void on_appear(const aware::contact&) = 0; + virtual void on_disappear(const aware::contact&) = 0; + virtual void on_failure(const boost::system::error_code&) = 0; + }; + + browser(const avahi::client&, + const aware::contact& contact, + browser::listener&); + ~browser(); + +private: + using listener_type = browser::listener; + struct wrapper; + + browser::listener& listener; + AvahiServiceBrowser *ptr; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_BROWSER_HPP diff --git a/src/dnssd/avahi/client.cpp b/src/dnssd/avahi/client.cpp new file mode 100644 index 0000000..abc9662 --- /dev/null +++ b/src/dnssd/avahi/client.cpp @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "avahi/error.hpp" +#include "avahi/poller.hpp" +#include "avahi/client.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +struct client::wrapper +{ + + static void client_callback(AvahiClient *client, + AvahiClientState state, + void *userdata) + { + avahi::client *self = static_cast(userdata); + + switch (state) + { + case AVAHI_CLIENT_S_REGISTERING: + self->registering(client); + break; + case AVAHI_CLIENT_S_RUNNING: + self->running(client); + break; + case AVAHI_CLIENT_S_COLLISION: + self->collision(client); + break; + case AVAHI_CLIENT_FAILURE: + self->failure(client); + break; + case AVAHI_CLIENT_CONNECTING: + self->connecting(client); + break; + default: + break; + } + } +}; + +client::client(avahi::poller& poller) + : ptr(0) +{ + const AvahiClientFlags flags = AvahiClientFlags(0); + // This will trigger the running() callback before ptr is set here + ptr = avahi_client_new(reinterpret_cast(&poller), + flags, + wrapper::client_callback, + this, + 0); + if (ptr == 0) + throw boost::system::system_error(avahi::make_error_code(AVAHI_ERR_DISCONNECTED)); + + assert(avahi_client_get_state(ptr) == AVAHI_CLIENT_S_RUNNING); +} + +client::~client() +{ + if (ptr) + { + avahi_client_free(ptr); + } +} + +void client::registering(AvahiClient *client) +{ + if (ptr == 0) + { + // Ignore because we have been called from the constructor + } + else + { + assert(ptr == client); + } +} + +void client::connecting(AvahiClient *client) +{ + if (ptr == 0) + { + // Ignore because we have been called from the constructor + } + else + { + assert(ptr == client); + } +} + +void client::running(AvahiClient *client) +{ + if (ptr == 0) + { + // Ignore because we have been called from the constructor + } + else + { + assert(ptr == client); + } +} + +void client::collision(AvahiClient *client) +{ + if (ptr == 0) + { + // Ignore because we have been called from the constructor + } + else + { + assert(ptr == client); + } +} + +void client::failure(AvahiClient *client) +{ + if (ptr == 0) + { + // Ignore because we have been called from the constructor + } + else + { + assert(ptr == client); + } +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/client.hpp b/src/dnssd/avahi/client.hpp new file mode 100644 index 0000000..cb2511f --- /dev/null +++ b/src/dnssd/avahi/client.hpp @@ -0,0 +1,52 @@ +#ifndef TRIAL_AWARE_AVAHI_CLIENT_HPP +#define TRIAL_AWARE_AVAHI_CLIENT_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +struct AvahiClient; + +namespace trial +{ +namespace aware +{ +namespace avahi +{ +class poller; + +// RAII wrapper of avahi_client functionality +class client +{ +public: + client(avahi::poller&); + virtual ~client(); + + operator AvahiClient *() const { return ptr; } + +private: + struct wrapper; + + void registering(AvahiClient *); + void connecting(AvahiClient *); + void running(AvahiClient *); + void collision(AvahiClient *); + void failure(AvahiClient *); + +private: + AvahiClient *ptr; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_CLIENT_HPP diff --git a/src/dnssd/avahi/error.cpp b/src/dnssd/avahi/error.cpp new file mode 100644 index 0000000..6ef9e47 --- /dev/null +++ b/src/dnssd/avahi/error.cpp @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "avahi/error.hpp" + +namespace +{ + +class avahi_category + : public boost::system::error_category +{ + virtual const char *name() const noexcept override + { + return "avahi"; + } + + virtual std::string message(int value) const override + { + return avahi_strerror(value); + } + + virtual bool equivalent(int value, + const boost::system::error_condition& condition) const noexcept override + { + using namespace boost::system; + switch (value) + { + case AVAHI_OK: + return bool(condition); + + case AVAHI_ERR_NO_MEMORY: + return condition == errc::make_error_condition(errc::not_enough_memory); + + default: + return false; + } + } +}; + +} // anonymous namespace + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +const boost::system::error_category& category() +{ + static avahi_category instance; + return instance; +} + +boost::system::error_code make_error_code(int value) +{ + return boost::system::error_code(value, avahi::category()); +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/error.hpp b/src/dnssd/avahi/error.hpp new file mode 100644 index 0000000..0f4e44f --- /dev/null +++ b/src/dnssd/avahi/error.hpp @@ -0,0 +1,31 @@ +#ifndef TRIAL_AWARE_AVAHI_ERROR_HPP +#define TRIAL_AWARE_AVAHI_ERROR_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +const boost::system::error_category& category(); +boost::system::error_code make_error_code(int); + +} // namespace avahi +} // namespace aware +} // namespace trial + + +#endif // TRIAL_AWARE_AVAHI_ERROR_HPP diff --git a/src/dnssd/avahi/factory.cpp b/src/dnssd/avahi/factory.cpp new file mode 100644 index 0000000..750f702 --- /dev/null +++ b/src/dnssd/avahi/factory.cpp @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "avahi/announce_socket.hpp" +#include "avahi/monitor_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace dnssd +{ + +std::unique_ptr factory::make_announce(boost::asio::io_service& io) +{ + return std::unique_ptr(new avahi::announce_socket(io)); +} + +std::unique_ptr factory::make_monitor(boost::asio::io_service& io) +{ + return std::unique_ptr(new avahi::monitor_socket(io)); +} + +} // namespace dnssd +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/monitor_socket.cpp b/src/dnssd/avahi/monitor_socket.cpp new file mode 100644 index 0000000..aef3a84 --- /dev/null +++ b/src/dnssd/avahi/monitor_socket.cpp @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include // std::pair +#include "avahi/browser.hpp" +#include "avahi/monitor_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +namespace detail +{ + +class monitor + : public browser::listener +{ + using response_type = std::pair; + using handler_type = aware::monitor_socket::async_monitor_handler; + +public: + monitor(boost::asio::io_service& io, + aware::contact& contact) + : contact_pointer(&contact) + { + browser = std::make_shared( + boost::asio::use_service(io).client(), + contact, + std::ref(*this)); + } + + //! @pre Must be called from an io_service thread + void prepare(aware::contact& contact, handler_type handler) + { + contact_pointer = &contact; + handlers.push(handler); + perform(); + } + + //! @pre Must be called from an io_service thread + void perform() + { + if (responses.empty()) + return; // Nothing to send + if (handlers.empty()) + return; // No receiver + + const auto& response = responses.front(); + auto& handler = handlers.front(); + *contact_pointer = response.second; + handler(response.first); + contact_pointer = 0; + responses.pop(); + handlers.pop(); + } + + virtual void on_appear(const aware::contact& contact) + { + boost::system::error_code success; + responses.push(std::make_pair(success, contact)); + perform(); + } + + virtual void on_disappear(const aware::contact& contact) + { + boost::system::error_code success; + responses.push(std::make_pair(success, contact)); + perform(); + } + + virtual void on_failure(const boost::system::error_code& error) + { + aware::contact no_contact(""); + responses.push(std::make_pair(error, no_contact)); + perform(); + } + +private: + aware::contact *contact_pointer; + std::shared_ptr browser; + std::queue responses; + std::queue handlers; +}; + +} // namespace detail + +monitor_socket::monitor_socket(boost::asio::io_service& io) + : boost::asio::basic_io_object(io) +{ +} + +void monitor_socket::async_monitor(aware::contact& contact, + async_monitor_handler handler) +{ + // Perform from io_service thread because the constructor of + // detail::browser will invoke the first callback + get_io_service().post( + [this, &contact, handler] + { + const auto& key = contact.type(); + auto where = monitors.lower_bound(key); + if ((where == monitors.end()) || (monitors.key_comp()(key, where->first))) + { + where = monitors.insert( + where, + monitor_map::value_type( + key, + std::make_shared( + std::ref(get_io_service()), + std::ref(contact)))); + } + assert(where != monitors.end()); + where->second->prepare(contact, handler); + }); +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/monitor_socket.hpp b/src/dnssd/avahi/monitor_socket.hpp new file mode 100644 index 0000000..7c36516 --- /dev/null +++ b/src/dnssd/avahi/monitor_socket.hpp @@ -0,0 +1,50 @@ +#ifndef TRIAL_AWARE_AVAHI_MONITOR_SOCKET_HPP +#define TRIAL_AWARE_AVAHI_MONITOR_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "avahi/service.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ +namespace detail { class monitor; } + +class monitor_socket + : public aware::monitor_socket, + public boost::asio::basic_io_object +{ + using monitor_ptr = std::shared_ptr; + using monitor_map = std::map; + +public: + monitor_socket(boost::asio::io_service&); + + virtual void async_monitor(aware::contact&, + async_monitor_handler) override; + +private: + monitor_map monitors; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_MONITOR_SOCKET_HPP diff --git a/src/dnssd/avahi/poller.cpp b/src/dnssd/avahi/poller.cpp new file mode 100644 index 0000000..afa3ed6 --- /dev/null +++ b/src/dnssd/avahi/poller.cpp @@ -0,0 +1,331 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include // std::nothrow +#include +#include +#include +#include +#include +#include "native_socket.hpp" +#include "avahi/poller.hpp" + +using namespace trial::aware; + +//----------------------------------------------------------------------------- +// AvahiWatch +//----------------------------------------------------------------------------- + +// This struct must be in global namespace +struct AvahiWatch +{ + AvahiWatch(boost::asio::io_service& io, + int fd, + AvahiWatchEvent event, + AvahiWatchCallback callback, + void *userdata) + : socket(io, fd), + callback(callback), + userdata(userdata), + revents(AvahiWatchEvent(0)) + { + if (event & AVAHI_WATCH_IN) + { + start_read(); + } + if (event & AVAHI_WATCH_OUT) + { + start_write(); + } + } + + void update(AvahiWatchEvent /* event */) + { + // FIXME + assert(false); + } + + AvahiWatchEvent get_events() const + { + return revents; + } + + void start_read() + { + revents = (AvahiWatchEvent)(revents & ~AVAHI_WATCH_IN); + socket.async_read_event(std::bind(&AvahiWatch::process_read, + this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void process_read(const boost::system::error_code& error, + std::size_t /* bytes_transferred */) + { + if (!error) + { + revents = (AvahiWatchEvent)(revents | AVAHI_WATCH_IN); + callback(this, socket.native_handle(), AVAHI_WATCH_IN, userdata); + start_read(); + } + else if (error == boost::asio::error::operation_aborted) + { + // We are closing down + } + else + { + // FIXME + } + } + + void start_write() + { + revents = (AvahiWatchEvent)(revents & ~AVAHI_WATCH_OUT); + socket.async_write_event(std::bind(&AvahiWatch::process_write, + this, + std::placeholders::_1, + std::placeholders::_2)); + } + + void process_write(const boost::system::error_code& error, + std::size_t /* bytes_transferred */) + { + if (!error) + { + revents = (AvahiWatchEvent)(revents | AVAHI_WATCH_OUT); + callback(this, socket.native_handle(), AVAHI_WATCH_OUT, userdata); + start_write(); + } + else if (error == boost::asio::error::operation_aborted) + { + // We are closing down + } + else + { + // FIXME + } + } + + native_socket socket; + AvahiWatchCallback callback; + void *userdata; + AvahiWatchEvent revents; +}; + +extern "C" +AvahiWatch *aware_avahi_watch_new(const AvahiPoll *poll, + int fd, + AvahiWatchEvent event, + AvahiWatchCallback callback, + void *userdata) +{ + auto poller = static_cast(poll); + return new (std::nothrow) AvahiWatch(poller->get_io_service(), fd, event, callback, userdata); +} + +extern "C" +void aware_avahi_watch_free(AvahiWatch *self) +{ + delete self; +} + +extern "C" +void aware_avahi_watch_update(AvahiWatch *self, + AvahiWatchEvent event) +{ + self->update(event); +} + +extern "C" +AvahiWatchEvent aware_avahi_watch_get_events(AvahiWatch *self) +{ + return self->get_events(); +} + +//----------------------------------------------------------------------------- +// AvahiTimeout +//----------------------------------------------------------------------------- + +class avahi_timer; + +// This struct must be in global namespace +struct AvahiTimeout +{ + AvahiTimeout(std::shared_ptr); + void update(const struct timeval *); + void release(); + + // AvahiTimeout may be deleted before the timer callback is invoked, so the + // timer is kept as a shared_ptr, for which this class acts as a simple + // wrapper. + std::shared_ptr timer; +}; + +class avahi_timer : public std::enable_shared_from_this +{ +public: + static std::shared_ptr create(boost::asio::io_service& io, + const struct timeval *tv, + AvahiTimeoutCallback callback, + void *userdata) + { + auto result = std::make_shared(io, callback, userdata); + if (tv) + { + result->update(tv); + } + result->set_resource(new (std::nothrow) AvahiTimeout(result)); + return result; + } + + avahi_timer(boost::asio::io_service& io, + AvahiTimeoutCallback callback, + void *userdata) + : resource(0), + timer(io), + callback(callback), + userdata(userdata) + { + } + + ~avahi_timer() + { + delete resource; + } + + AvahiTimeout *get_resource() const + { + assert(resource); + return resource; + } + + void set_resource(AvahiTimeout *res) + { + resource = res; + } + + void cancel() + { + boost::system::error_code dummy; + timer.cancel(dummy); + } + + void update(const struct timeval *tv) + { + if (tv) + { + const AvahiUsec deadline = ((tv->tv_sec == 0) && (tv->tv_usec == 0)) ? 0 : -avahi_age(tv); + boost::system::error_code dummy; + timer.expires_from_now(std::chrono::microseconds(deadline), dummy); + timer.async_wait(std::bind(&avahi_timer::process_timeout, + shared_from_this(), + std::placeholders::_1)); + } + else + { + cancel(); + } + } + + void process_timeout(const boost::system::error_code& error) + { + if (!error) + { + callback(resource, userdata); + } + else if (error == boost::asio::error::operation_aborted) + { + // We are closing down + } + else + { + // FIXME + } + } + + AvahiTimeout *resource; + boost::asio::basic_waitable_timer timer; + AvahiTimeoutCallback callback; + void *userdata; +}; + +AvahiTimeout::AvahiTimeout(std::shared_ptr timer) + : timer(timer) +{ +} + +void AvahiTimeout::update(const struct timeval *tv) +{ + timer->update(tv); +} + +void AvahiTimeout::release() +{ + timer->cancel(); + timer.reset(); +} + +extern "C" +AvahiTimeout *aware_avahi_timeout_new(const AvahiPoll *poll, + const struct timeval *tv, + AvahiTimeoutCallback callback, + void *userdata) +{ + auto poller = static_cast(poll); + auto timer = avahi_timer::create(poller->get_io_service(), tv, callback, userdata); + return timer->get_resource(); +} + +extern "C" +void aware_avahi_timeout_free(AvahiTimeout *self) +{ + self->release(); +} + +extern "C" +void aware_avahi_timeout_update(AvahiTimeout *self, + const struct timeval *tv) +{ + self->update(tv); +} + +//----------------------------------------------------------------------------- +// poller +//----------------------------------------------------------------------------- + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +poller::poller(boost::asio::io_service& io) + : io(io) +{ + userdata = this; + watch_new = &aware_avahi_watch_new; + watch_update = &aware_avahi_watch_update; + watch_get_events = &aware_avahi_watch_get_events; + watch_free = &aware_avahi_watch_free; + timeout_new = &aware_avahi_timeout_new; + timeout_update = &aware_avahi_timeout_update; + timeout_free = &aware_avahi_timeout_free; +} + +boost::asio::io_service& poller::get_io_service() const +{ + return io; +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/poller.hpp b/src/dnssd/avahi/poller.hpp new file mode 100644 index 0000000..1fde079 --- /dev/null +++ b/src/dnssd/avahi/poller.hpp @@ -0,0 +1,40 @@ +#ifndef TRIAL_AWARE_AVAHI_POLLER_HPP +#define TRIAL_AWARE_AVAHI_POLLER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +class poller + : public ::AvahiPoll +{ +public: + poller(boost::asio::io_service&); + + boost::asio::io_service& get_io_service() const; + +private: + boost::asio::io_service& io; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_POLLER_HPP diff --git a/src/dnssd/avahi/service.cpp b/src/dnssd/avahi/service.cpp new file mode 100644 index 0000000..749aecf --- /dev/null +++ b/src/dnssd/avahi/service.cpp @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "avahi/service.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +boost::asio::io_service::id service::id; + +service::service(boost::asio::io_service& io) + : boost::asio::io_service::service(io), + current_poller(io), + current_client(current_poller) +{ +} + +} // namespace avahi +} // namespace aware +} // namespace trial diff --git a/src/dnssd/avahi/service.hpp b/src/dnssd/avahi/service.hpp new file mode 100644 index 0000000..3e4b26c --- /dev/null +++ b/src/dnssd/avahi/service.hpp @@ -0,0 +1,52 @@ +#ifndef TRIAL_AWARE_AVAHI_SERVICE_HPP +#define TRIAL_AWARE_AVAHI_SERVICE_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "avahi/poller.hpp" +#include "avahi/client.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + +class service + : public boost::asio::io_service::service +{ +public: + static boost::asio::io_service::id id; + + struct implementation_type {}; + + explicit service(boost::asio::io_service&); + + void construct(implementation_type&) {} + void destroy(implementation_type&) {} + + avahi::client& client() & { return current_client; } + +private: + virtual void shutdown_service() {} + +private: + avahi::poller current_poller; + avahi::client current_client; +}; + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_SERVICE_HPP diff --git a/src/dnssd/avahi/utility.cpp b/src/dnssd/avahi/utility.cpp new file mode 100644 index 0000000..d10c2f0 --- /dev/null +++ b/src/dnssd/avahi/utility.cpp @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "avahi/utility.hpp" + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + + +std::string type_encode(const std::string& type) +{ + return "_" + type + "._tcp"; +} + +// Extract "type" from a "_type._tcp" string. +// Returns empty string if a parse error occurred. +std::string type_decode(const char *type) +{ + const char *beginning = type; + const char *ending = type; + if (*beginning == '_') + { + ++beginning; + ending = std::strchr(beginning, '.'); + if (ending == 0) + { + ending = beginning; + } + } + return std::string(beginning, ending); +} + +} // namespace avahi +} // namespace aware +} // namespace trial + diff --git a/src/dnssd/avahi/utility.hpp b/src/dnssd/avahi/utility.hpp new file mode 100644 index 0000000..83db8ac --- /dev/null +++ b/src/dnssd/avahi/utility.hpp @@ -0,0 +1,31 @@ +#ifndef TRIAL_AWARE_AVAHI_UTILITY_HPP +#define TRIAL_AWARE_AVAHI_UTILITY_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ +namespace avahi +{ + + +std::string type_encode(const std::string&); +std::string type_decode(const char *); + +} // namespace avahi +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_AVAHI_UTILITY_HPP diff --git a/src/dnssd/mdns/announce_socket.cpp b/src/dnssd/mdns/announce_socket.cpp new file mode 100644 index 0000000..ae8b667 --- /dev/null +++ b/src/dnssd/mdns/announce_socket.cpp @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/announcer.hpp" +#include "mdns/announce_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +announce_socket::announce_socket(boost::asio::io_service& io) + : io(io), + connection(mdns::handle::with_connection), + socket(io, connection.native_handle()), + waiting(false) +{ +} + +void announce_socket::async_announce(aware::contact& contact, + async_announce_handler handler) +{ + if (connection.empty()) + { + using namespace boost::system; + auto irrecoverable = errc::make_error_code(errc::state_not_recoverable); + io.post(std::bind(&announce_socket::invoke, + this, + irrecoverable, + handler)); + return; + } + + try + { + const auto& key = contact.type(); + auto where = announcers.lower_bound(key); + if ((where == announcers.end()) || (announcers.key_comp()(key, where->first))) + { + where = announcers.insert( + where, + std::make_pair(key, + std::make_shared(std::ref(connection)))); + } + assert(where != announcers.end()); + where->second->announce(contact, handler); + + if (!waiting) + { + socket.async_read_event(std::bind(&announce_socket::process_read_event, + this, + std::placeholders::_1, + std::placeholders::_2)); + waiting = true; + } + } + catch (const boost::system::system_error& ex) + { + io.post(std::bind(&announce_socket::invoke, + this, + ex.code(), + handler)); + } + // Other exceptions are propagated outwards +} + +void announce_socket::process_read_event(const boost::system::error_code& error, + std::size_t) +{ + waiting = false; + if (!error) + { + // Execute the register callbacks + int status = ::DNSServiceProcessResult(connection.get()); + if (status == kDNSServiceErr_NoError) + { + socket.async_read_event(std::bind(&announce_socket::process_read_event, + this, + std::placeholders::_1, + std::placeholders::_2)); + waiting = true; + } + else + { + // FIXME: How to report error? + } + } + else if (error == boost::asio::error::operation_aborted) + { + // Ignore + } + else + { + // FIXME: How to report error? + } +} + +void announce_socket::invoke(const boost::system::error_code& error, + async_announce_handler handler) +{ + handler(error); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/announce_socket.hpp b/src/dnssd/mdns/announce_socket.hpp new file mode 100644 index 0000000..fbf0366 --- /dev/null +++ b/src/dnssd/mdns/announce_socket.hpp @@ -0,0 +1,57 @@ +#ifndef TRIAL_AWARE_MDNS_ANNOUNCE_SOCKET_HPP +#define TRIAL_AWARE_MDNS_ANNOUNCE_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "native_socket.hpp" +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ +class announcer; + +class announce_socket + : public aware::announce_socket +{ +public: + announce_socket(boost::asio::io_service&); + + virtual void async_announce(aware::contact& contact, + async_announce_handler) override; + +private: + void process_read_event(const boost::system::error_code&, + std::size_t); + + void invoke(const boost::system::error_code&, + async_announce_handler); + +private: + boost::asio::io_service& io; + mdns::handle connection; + aware::native_socket socket; + bool waiting; + std::map< std::string, std::shared_ptr > announcers; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_ANNOUNCE_SOCKET_HPP diff --git a/src/dnssd/mdns/announcer.cpp b/src/dnssd/mdns/announcer.cpp new file mode 100644 index 0000000..e7c962f --- /dev/null +++ b/src/dnssd/mdns/announcer.cpp @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/error.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/utility.hpp" +#include "mdns/properties.hpp" +#include "mdns/announcer.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +struct announcer::callback +{ + static void on_registered(::DNSServiceRef, + ::DNSServiceFlags flags, + ::DNSServiceErrorType error, + const char *name, + const char *regtype, + const char * /* domain */, + void *context) + { + announcer *self = static_cast(context); + assert(self); + + try + { + if (error == kDNSServiceErr_NoError) + { + if (flags & kDNSServiceFlagsAdd) + { + assert(self->current); + // Endpoint and properties have to be obtained via the monitor + self->current->contact = aware::contact(mdns::type_decode(regtype)) + .name(name); + self->current->handler(mdns::make_error_code(kDNSServiceErr_NoError)); + self->current = boost::none; + } + else + { + // Name conflicts not implemented yet + self->current->handler(mdns::make_error_code(kDNSServiceErr_NameConflict)); + self->current = boost::none; + } + } + else + { + assert(self->current); + self->current->handler(mdns::make_error_code(error)); + self->current = boost::none; + } + } + catch (const boost::system::system_error& ex) + { + self->current->handler(ex.code()); + self->current = boost::none; + } + catch (...) + { + // Ignore unknown exceptions + } + } +}; + +announcer::announcer(mdns::handle& connection) + : connection(connection) +{ +} + +void announcer::announce(aware::contact& contact, + async_announce_handler handler) +{ + if (current || !handle.empty()) + { + throw_on_error(kDNSServiceErr_AlreadyRegistered); + } + + // This operation will be completed when data is ready and process() is called + current = boost::in_place(std::ref(contact), handler); + + const ::DNSServiceFlags flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename; + const uint32_t interface_index = from_index(contact.index()); + std::string name = contact.name(); + std::string type = mdns::type_encode(contact.type()); + const char *domain = 0; // .local + const auto& address = contact.address(); + std::string host = address.is_unspecified() + ? "" // Use default host name + : address.to_string(); + const uint16_t port = boost::asio::detail::socket_ops::host_to_network_short(contact.port()); + + current->properties = std::make_shared(contact.properties()); + + ::DNSServiceRef ref = connection.get(); + // May block for seconds if service not running + ::DNSServiceErrorType error = ::DNSServiceRegister( + &ref, + flags, + interface_index, + name.c_str(), + type.c_str(), + domain, + host.empty() ? 0 : host.c_str(), + port, + current->properties->size(), + current->properties->data(), + &announcer::callback::on_registered, + this); + throw_on_error(error); + + handle.reset(ref); +} + +announcer::current_type::current_type(aware::contact& contact, + async_announce_handler handler) + : contact(contact), + handler(handler) +{ +} + +announcer::current_type::current_type(const current_type& other) + : contact(other.contact), + properties(other.properties), + handler(other.handler) +{ +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/announcer.hpp b/src/dnssd/mdns/announcer.hpp new file mode 100644 index 0000000..9e40a51 --- /dev/null +++ b/src/dnssd/mdns/announcer.hpp @@ -0,0 +1,62 @@ +#ifndef TRIAL_AWARE_MDNS_ANNOUNCER_HPP +#define TRIAL_AWARE_MDNS_ANNOUNCER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include "mdns/handle.hpp" +#include "mdns/properties.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +// Wrapper for DNSServiceRegister +class announcer +{ +public: + using error_type = int; + using async_announce_handler = typename aware::announce_socket::async_announce_handler; + + // Instantiated per announce type + announcer(mdns::handle&); + + // Called per announce request + void announce(aware::contact&, async_announce_handler); + +private: + struct callback; + + mdns::handle& connection; + mdns::handle handle; + struct current_type + { + current_type(aware::contact&, async_announce_handler); + current_type(const current_type&); + + aware::contact& contact; + std::shared_ptr properties; + async_announce_handler handler; + }; + boost::optional current; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_ANNOUNCER_HPP diff --git a/src/dnssd/mdns/browser.cpp b/src/dnssd/mdns/browser.cpp new file mode 100644 index 0000000..7d05312 --- /dev/null +++ b/src/dnssd/mdns/browser.cpp @@ -0,0 +1,106 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/utility.hpp" +#include "mdns/error.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/browser.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +//----------------------------------------------------------------------------- +// Callback +//----------------------------------------------------------------------------- + +struct browser::callback +{ + static void on_browsed(::DNSServiceRef, + ::DNSServiceFlags flags, + uint32_t interface_index, + ::DNSServiceErrorType error, + const char *name, + const char *regtype, + const char *domain, + void *context) + { + try + { + auto self = static_cast(context); + assert(self); + + if (error == kDNSServiceErr_NoError) + { + auto contact = aware::contact(mdns::type_decode(regtype)) + .name(name) + .domain(domain) + .index(to_index(interface_index)); + const bool commit = !(flags & kDNSServiceFlagsMoreComing); + if (flags & kDNSServiceFlagsAdd) + { + self->listener.on_browser_appear(contact, commit); + } + else + { + self->listener.on_browser_disappear(contact, commit); + } + } + else + { + self->listener.on_browser_failure(mdns::make_error_code(error)); + } + } + catch (...) + { + // Ignore unknown exceptions + } + } +}; + +//----------------------------------------------------------------------------- +// browser +//----------------------------------------------------------------------------- + +browser::browser(const std::string& type, + mdns::handle& connection, + typename browser::listener& listener) + : connection(connection), + listener(listener) +{ + const ::DNSServiceFlags flags = kDNSServiceFlagsShareConnection; + std::string regtype = mdns::type_encode(type); + const uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny; + const char *domain = 0; // Use .local + + auto ref = connection.get(); + ::DNSServiceErrorType error = ::DNSServiceBrowse( + &ref, + flags, + interfaceIndex, + regtype.c_str(), + domain, + &browser::callback::on_browsed, + this); + throw_on_error(error); + + handle.reset(ref); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/browser.hpp b/src/dnssd/mdns/browser.hpp new file mode 100644 index 0000000..464ab4c --- /dev/null +++ b/src/dnssd/mdns/browser.hpp @@ -0,0 +1,59 @@ +#ifndef TRIAL_AWARE_MDNS_BROWSER_HPP +#define TRIAL_AWARE_MDNS_BROWSER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +class browser +{ + using identifier_type = void *; + +public: + class listener + { + public: + virtual ~listener() = default; + + virtual void on_browser_appear(const aware::contact& contact, + bool more) = 0; + virtual void on_browser_disappear(const aware::contact& contact, + bool more) = 0; + virtual void on_browser_failure(const boost::system::error_code&) = 0; + }; + + browser(const std::string& type, + mdns::handle&, + browser::listener&); + +private: + struct callback; + + mdns::handle& connection; + browser::listener& listener; + mdns::handle handle; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_BROWSER_HPP diff --git a/src/dnssd/mdns/dns_sd.hpp b/src/dnssd/mdns/dns_sd.hpp new file mode 100644 index 0000000..7ac0abd --- /dev/null +++ b/src/dnssd/mdns/dns_sd.hpp @@ -0,0 +1,56 @@ +#ifndef TRIAL_AWARE_MDNS_DNS_SD_HPP +#define TRIAL_AWARE_MDNS_DNS_SD_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +// The Avahi Bonjour compatibility layer includes a very old dns_sd.h header. +// http://0pointer.net/blog/projects/avahi-compat.html +#if _DNS_SD_H + 0 == 0 +# error "mDNSResponder is too old" +#endif + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +inline uint32_t from_index(int value) +{ + switch (value) + { + case aware::contact::wildcard: + return kDNSServiceInterfaceIndexAny; + default: + return value; + } +} + +inline int to_index(uint32_t value) +{ + switch (value) + { + case kDNSServiceInterfaceIndexAny: + return aware::contact::wildcard; + default: + return value; + } +} + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_DNS_SD_HPP diff --git a/src/dnssd/mdns/error.cpp b/src/dnssd/mdns/error.cpp new file mode 100644 index 0000000..e71d360 --- /dev/null +++ b/src/dnssd/mdns/error.cpp @@ -0,0 +1,133 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "mdns/dns_sd.hpp" +#include "mdns/error.hpp" + +namespace +{ + +class mdns_category : public boost::system::error_category +{ +public: + const char *name() const BOOST_SYSTEM_NOEXCEPT + { + return "mDNSResponder"; + } + std::string message(int value) const + { + switch (DNSServiceErrorType(value)) + { + case kDNSServiceErr_NoError: + return "success"; + case kDNSServiceErr_Unknown: + return "unknown"; + case kDNSServiceErr_NoSuchName: + return "no such name"; + case kDNSServiceErr_NoMemory: + return "no memory"; + case kDNSServiceErr_BadParam: + return "bad parameter"; + case kDNSServiceErr_BadReference: + return "bad reference"; + case kDNSServiceErr_BadState: + return "bad state"; + case kDNSServiceErr_BadFlags: + return "bad flags"; + case kDNSServiceErr_Unsupported: + return "unsupported"; + case kDNSServiceErr_NotInitialized: + return "not initialized"; + case kDNSServiceErr_AlreadyRegistered: + return "already registered"; + case kDNSServiceErr_NameConflict: + return "name conflict"; + case kDNSServiceErr_Invalid: + return "invalid"; + case kDNSServiceErr_Incompatible: + return "incompatible"; + case kDNSServiceErr_BadInterfaceIndex: + return "bad interface index"; + case kDNSServiceErr_NoSuchRecord: + return "no such record"; + case kDNSServiceErr_NoAuth: + return "no authentication"; + case kDNSServiceErr_NoSuchKey: + return "no such key"; + case kDNSServiceErr_BadTime: + return "bad time"; + case kDNSServiceErr_BadSig: + return "bad signature"; + case kDNSServiceErr_BadKey: + return "bad key"; + case kDNSServiceErr_Transient: + return "transient"; + case kDNSServiceErr_ServiceNotRunning: + return "service not running"; + case kDNSServiceErr_NoRouter: + return "no router"; + case kDNSServiceErr_Firewall: + return "firewall"; + case kDNSServiceErr_Refused: + return "refused"; + case kDNSServiceErr_NATTraversal: + return "NAT traversal"; + case kDNSServiceErr_DoubleNAT: + return "double NAT"; + case kDNSServiceErr_NATPortMappingUnsupported: + return "NAT port mapping unsupported"; + case kDNSServiceErr_NATPortMappingDisabled: + return "NAT port mapping disabled"; + case kDNSServiceErr_PollingMode: + return "polling mode"; + } + return "unknown"; + } + + virtual bool equivalent(int value, + const boost::system::error_condition& condition) const noexcept override + { + switch (value) + { + case kDNSServiceErr_NoError: + return bool(condition); + + case kDNSServiceErr_NoMemory: + return condition == boost::system::errc::make_error_condition(boost::system::errc::not_enough_memory); + + default: + return false; + } + } +}; + +} // anonymous namespace + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +const boost::system::error_category& category() +{ + static mdns_category instance; + return instance; +} + +boost::system::error_code make_error_code(int value) +{ + return boost::system::error_code(value, mdns::category()); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/error.hpp b/src/dnssd/mdns/error.hpp new file mode 100644 index 0000000..c0f1917 --- /dev/null +++ b/src/dnssd/mdns/error.hpp @@ -0,0 +1,31 @@ +#ifndef TRIAL_AWARE_MDNS_ERROR_HPP +#define TRIAL_AWARE_MDNS_ERROR_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +const boost::system::error_category& category(); +boost::system::error_code make_error_code(int); + +} // namespace mdns +} // namespace aware +} // namespace trial + + +#endif // TRIAL_AWARE_MDNS_ERROR_HPP diff --git a/src/dnssd/mdns/factory.cpp b/src/dnssd/mdns/factory.cpp new file mode 100644 index 0000000..42c606a --- /dev/null +++ b/src/dnssd/mdns/factory.cpp @@ -0,0 +1,34 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "mdns/announce_socket.hpp" +#include "mdns/monitor_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace dnssd +{ + +std::unique_ptr factory::make_announce(boost::asio::io_service& io) +{ + return std::unique_ptr(new mdns::announce_socket(io)); +} + +std::unique_ptr factory::make_monitor(boost::asio::io_service& io) +{ + return std::unique_ptr(new mdns::monitor_socket(io)); +} + +} // namespace dnssd +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/handle.cpp b/src/dnssd/mdns/handle.cpp new file mode 100644 index 0000000..e73a958 --- /dev/null +++ b/src/dnssd/mdns/handle.cpp @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +struct handle::opaque_type +{ + opaque_type() + : ref(0) + { + } + + opaque_type(DNSServiceRef ref) + : ref(ref) + { + } + + ~opaque_type() + { + if (ref) + { + ::DNSServiceRefDeallocate(ref); + } + } + + static_assert(std::is_pointer::value, "DNSServiceRef must be a pointer"); + DNSServiceRef ref; +}; + +handle::handle() + : opaque(new opaque_type) +{ +} + +handle::handle(with_connection_tag) + : opaque(0) +{ + ::DNSServiceRef ref = 0; + ::DNSServiceErrorType error = ::DNSServiceCreateConnection(&ref); + throw_on_error(error); + opaque = new opaque_type(ref); +} + +handle::~handle() +{ + assert(opaque); + delete opaque; +} + +template <> +::DNSServiceRef handle::get() +{ + if (empty()) + throw_on_error(kDNSServiceErr_NotInitialized); + return opaque->ref; +} + +template <> +const ::DNSServiceRef& handle::get() const +{ + if (empty()) + throw_on_error(kDNSServiceErr_NotInitialized); + return opaque->ref; +} + +void handle::reset() +{ + if (!empty()) + { + delete opaque; + opaque = new opaque_type; + } +} + +template <> +void handle::reset(const DNSServiceRef& ref) +{ + if (empty()) + { + opaque->ref = ref; + } + else + { + delete opaque; + opaque = new opaque_type(ref); + } +} + +bool handle::empty() const +{ + assert(opaque); + return (opaque->ref == 0); +} + +handle::native_handle_type handle::native_handle() +{ + if (empty()) + throw_on_error(kDNSServiceErr_NotInitialized); + int result = ::DNSServiceRefSockFD(get()); + if (result == -1) + throw_on_error(kDNSServiceErr_NotInitialized); + return result; +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/handle.hpp b/src/dnssd/mdns/handle.hpp new file mode 100644 index 0000000..4c0f0f0 --- /dev/null +++ b/src/dnssd/mdns/handle.hpp @@ -0,0 +1,62 @@ +#ifndef TRIAL_AWARE_MDNS_HANDLE_HPP +#define TRIAL_AWARE_MDNS_HANDLE_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +// Type-erased RAII wrapper for DNSServiceRef +class handle +{ +public: + using native_handle_type = int; + enum with_connection_tag { with_connection }; + + handle(); + handle(with_connection_tag); + ~handle(); + + template + T get(); + + template + const T& get() const; + + void reset(); + + template + void reset(const T&); + + bool empty() const; + + native_handle_type native_handle(); + +private: + handle(const handle&) = delete; + handle(handle&&) = delete; + handle& operator= (const handle&) = delete; + handle& operator= (handle&&) = delete; + +private: + struct opaque_type; + opaque_type *opaque; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_HANDLE_HPP diff --git a/src/dnssd/mdns/monitor.cpp b/src/dnssd/mdns/monitor.cpp new file mode 100644 index 0000000..f3e5ba3 --- /dev/null +++ b/src/dnssd/mdns/monitor.cpp @@ -0,0 +1,375 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/error.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/monitor.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +//----------------------------------------------------------------------------- +// monitor +//----------------------------------------------------------------------------- + +monitor::monitor(boost::asio::io_service& io, + mdns::handle& connection) + : io(io), + connection(connection) +{ +} + +void monitor::listen(aware::contact& contact, + async_monitor_handler handler) +{ + if (permanent_error) + { + handler(permanent_error); + return; + } + + if (!browser) + { + type = contact.type(); + // Browser will continously trigger announcements via its listener + browser = boost::in_place(contact.type(), + std::ref(connection), + std::ref(*this)); + } + assert(contact.type() == type); + + auto where = resolvers.find(contact.type()); + if (where != resolvers.end()) + { + resolvers.erase(where); + } + + requests.push(request(std::ref(contact), handler)); +} + +void monitor::on_browser_appear(const aware::contact& contact, + bool commit) +{ + auto entry = scopes.lower_bound(contact.name()); + if ((entry == scopes.end()) || (scopes.key_comp()(contact.name(), entry->first))) + { + assert(contact.index() != aware::contact::wildcard); + + entry = scopes.insert( + entry, + std::make_pair(contact.name(), + std::make_shared(std::ref(io), std::ref(*this)))); + } + entry->second->submit_appear(contact); + + if (commit) + { + entry->second->commit_appear(); + for (auto it = uncommitted_appear.begin(); + it != uncommitted_appear.end(); + ++it) + { + if (*it == entry->first) + continue; + + auto where = scopes.find(*it); + if (where != scopes.end()) + { + where->second->commit_appear(); + } + } + uncommitted_appear.clear(); + } + else + { + // Keep track of uncommitted appearances so we can commit them later + uncommitted_appear.insert(contact.name()); + } +} + +void monitor::on_browser_disappear(const aware::contact& contact, + bool commit) +{ + auto entry = scopes.find(contact.name()); + if (entry != scopes.end()) + { + entry->second->submit_disappear(contact); + } + + if (commit) + { + entry->second->commit_disappear(); + for (auto it = uncommitted_disappear.begin(); + it != uncommitted_disappear.end(); + ++it) + { + if (*it == entry->first) + continue; + + auto where = scopes.find(*it); + if (where != scopes.end()) + { + where->second->commit_disappear(); + } + } + uncommitted_disappear.clear(); + } + else + { + uncommitted_disappear.insert(contact.name()); + } +} + +void monitor::on_browser_failure(const boost::system::error_code& error) +{ + if (requests.empty()) + { + permanent_error = error; + } + else + { + monitor::request request = requests.front(); + requests.pop(); + request.handler(error); + } +} + +void monitor::on_resolver_done(const aware::contact& contact) +{ + if (requests.empty()) + { + permanent_error = mdns::make_error_code(kDNSServiceErr_BadState); + assert(false); + } + else + { + auto entry = scopes.find(contact.name()); + if (entry != scopes.end()) + { + entry->second->resolved(contact); + } + } + resolvers.erase(contact); +} + +void monitor::on_resolver_failure(const boost::system::error_code& error) +{ + if (requests.empty()) + { + permanent_error = error; + } + else + { + monitor::request request = requests.front(); + requests.pop(); + request.handler(error); + } +} + +//----------------------------------------------------------------------------- +// monitor::request +//----------------------------------------------------------------------------- + +monitor::request::request(aware::contact& contact, + async_monitor_handler handler) + : contact(contact), + handler(handler) +{ +} + +//----------------------------------------------------------------------------- +// monitor::scope +//----------------------------------------------------------------------------- + +monitor::scope::scope(boost::asio::io_service& io, + monitor& self) + : self(self), + timer(io), + wildcard_count(0) +{ +} + +monitor::scope::~scope() +{ + boost::system::error_code dummy; + timer.cancel(dummy); +} + +void monitor::scope::submit_appear(const aware::contact& key) +{ + if (key.index() == aware::contact::wildcard) + return; + + auto where = removals.find(key); + if (where != removals.end()) + { + removals.erase(where); + return; + } + + additions.insert(key); +} + +void monitor::scope::submit_disappear(const aware::contact& key) +{ + if (key.index() == aware::contact::wildcard) + { + ++wildcard_count; + } + else + { + // Treat duplicated disappearances as wildcards + if (removals.find(key) != removals.end()) + { + ++wildcard_count; + } + else + { + removals.insert(key); + } + } +} + +void monitor::scope::commit_appear() +{ + // Prune announcements that have been both removed and added + std::set intersection; + std::set_intersection(additions.begin(), additions.end(), + removals.begin(), removals.end(), + std::inserter(intersection, intersection.begin())); + additions.erase(intersection.begin(), intersection.end()); + removals.erase(intersection.begin(), intersection.end()); + + execute_appear(); + execute_disappear(); +} + +void monitor::scope::commit_disappear() +{ + // Defer action in order to detect false positives + boost::system::error_code dummy; + timer.cancel(dummy); + timer.expires_from_now(std::chrono::milliseconds(3000)); + timer.async_wait(std::bind(&monitor::scope::process_disappear, + this, + std::placeholders::_1)); +} + +void monitor::scope::resolved(const aware::contact& contact) +{ + monitor::request request = self.requests.front(); + self.requests.pop(); + assert(request.contact.type() == contact.type()); + request.contact = contact; + activate(request.contact); + request.handler(boost::system::error_code()); +} + +void monitor::scope::process_disappear(const boost::system::error_code& error) +{ + if (error) + return; + + execute_appear(); + execute_disappear(); +} + +void monitor::scope::activate(const aware::contact& key) +{ + active.insert(key); +} + +void monitor::scope::deactivate(const aware::contact& key) +{ + active.erase(key); +} + +void monitor::scope::execute_appear() +{ + if (wildcard_count > 0) + { + // Remove inactive additions + std::size_t size = removals.size(); + std::set_difference(active.begin(), active.end(), + additions.begin(), additions.end(), + std::inserter(removals, removals.begin())); + wildcard_count -= removals.size() - size; + + auto it = additions.begin(); + while (it != additions.end()) + { + if (active.find(*it) != active.end()) + { + it = additions.erase(it++); + --wildcard_count; + } + else + { + ++it; + } + } + } + + for (auto it = additions.begin(); + it != additions.end(); + ++it) + { + self.resolvers.insert( + std::make_pair(*it, + std::make_shared(std::ref(self.connection), + *it, + std::ref(self)))); + // Continues in resolver listener + } + additions.clear(); +} + +void monitor::scope::execute_disappear() +{ + auto it = removals.begin(); + while (it != removals.end()) + { + if (self.requests.empty()) + { + self.permanent_error = mdns::make_error_code(kDNSServiceErr_BadState); + assert(false); + break; // for + } + else + { + if (it->index() != aware::contact::wildcard) + { + monitor::request request = self.requests.front(); + self.requests.pop(); + + assert(request.contact.type() == it->type()); + request.contact = aware::contact(it->type()).name(it->name()).domain(it->domain()).index(it->index()); + deactivate(request.contact); + request.handler(boost::system::error_code()); + } + removals.erase(it++); + } + } + wildcard_count = 0; +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/monitor.hpp b/src/dnssd/mdns/monitor.hpp new file mode 100644 index 0000000..3a72ac9 --- /dev/null +++ b/src/dnssd/mdns/monitor.hpp @@ -0,0 +1,134 @@ +#ifndef TRIAL_AWARE_MDNS_MONITOR_HPP +#define TRIAL_AWARE_MDNS_MONITOR_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdns/handle.hpp" +#include "mdns/browser.hpp" +#include "mdns/resolver.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +// mDNSResponder periodically (about once per minute) reports false positives +// where announcements are reported as disappeared only to be reported as +// appeared again shortly after (a second or two.) In reality they have never +// disappeared. Consequently, action on disappear events is defered for a +// couple of seconds. +// +// When mDNSResponder reports false disappearances they are either reported on +// their actual network interface or on the "any" network interface. In the +// latter case, we cannot know exactly which network interface to ignore, so +// we keep track of the amount of disappearances as well as the re-appeared +// annoucements. + +class monitor + : public browser::listener, + public resolver::listener +{ +public: + using async_monitor_handler = typename aware::monitor_socket::async_monitor_handler; + + monitor(boost::asio::io_service&, mdns::handle&); + + void listen(aware::contact&, async_monitor_handler); + +private: + // browser::listener + virtual void on_browser_appear(const aware::contact&, bool) override; + virtual void on_browser_disappear(const aware::contact&, bool) override; + virtual void on_browser_failure(const boost::system::error_code&) override; + // resolver::listener + virtual void on_resolver_done(const aware::contact&) override; + virtual void on_resolver_failure(const boost::system::error_code&) override; + +private: + boost::asio::io_service& io; + mdns::handle& connection; + std::string type; + boost::optional browser; + boost::system::error_code permanent_error; + + struct request + { + request(aware::contact&, async_monitor_handler); + + aware::contact& contact; + async_monitor_handler handler; + }; + std::queue requests; + + class scope + { + public: + using active_container = std::set; + using addition_container = std::set; + using removal_container = std::multiset; + + scope(boost::asio::io_service&, monitor&); + ~scope(); + + void submit_appear(const aware::contact&); + void commit_appear(); + void submit_disappear(const aware::contact&); + void commit_disappear(); + void resolved(const aware::contact&); + + void activate(const aware::contact&); + void deactivate(const aware::contact&); + + private: + scope(const scope&) = delete; + scope(scope&&) = delete; + scope& operator= (const scope&) = delete; + scope& operator= (scope&&) = delete; + + void process_disappear(const boost::system::error_code&); + void execute_appear(); + void execute_disappear(); + + private: + monitor& self; + boost::asio::basic_waitable_timer timer; + active_container active; + addition_container additions; + removal_container removals; + std::size_t wildcard_count; + }; + // Key is name (type is implicit) + std::map< std::string, std::shared_ptr > scopes; + + using name_container = std::set; + name_container uncommitted_appear; + name_container uncommitted_disappear; + + std::map< aware::contact, std::shared_ptr > resolvers; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_MONITOR_HPP diff --git a/src/dnssd/mdns/monitor_socket.cpp b/src/dnssd/mdns/monitor_socket.cpp new file mode 100644 index 0000000..c829b94 --- /dev/null +++ b/src/dnssd/mdns/monitor_socket.cpp @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "mdns/dns_sd.hpp" +#include "mdns/error.hpp" +#include "mdns/monitor.hpp" +#include "mdns/monitor_socket.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +monitor_socket::monitor_socket(boost::asio::io_service& io) + : io(io), + connection(mdns::handle::with_connection), + socket(io, connection.native_handle()), + waiting(false) +{ +} + +monitor_socket::~monitor_socket() +{ + monitors.clear(); +} + +void monitor_socket::async_monitor(aware::contact& contact, + async_monitor_handler handler) +{ + if (connection.empty()) + { + permanent_error = mdns::make_error_code(kDNSServiceErr_BadState); + } + if (permanent_error) + { + io.post(std::bind(&monitor_socket::invoke, + this, + permanent_error, + handler)); + return; + } + + try + { + const auto& key = contact.type(); + auto where = monitors.lower_bound(key); + if ((where == monitors.end()) || (monitors.key_comp()(key, where->first))) + { + where = monitors.insert( + where, + std::make_pair(key, + std::make_shared(std::ref(io), + std::ref(connection)))); + } + assert(where != monitors.end()); + where->second->listen(contact, handler); + + if (!waiting) + { + socket.async_read_event(std::bind(&monitor_socket::process_read_event, + this, + std::placeholders::_1, + std::placeholders::_2)); + } + } + catch (const boost::system::system_error& ex) + { + io.post(std::bind(&monitor_socket::invoke, + this, + ex.code(), + handler)); + } + // Other exceptions are propagated outwards +} + +void monitor_socket::process_read_event(const boost::system::error_code& error, + std::size_t) +{ + waiting = false; + if (!error) + { + // Execute the register callbacks + int status = ::DNSServiceProcessResult(connection.get()); + if (status == kDNSServiceErr_NoError) + { + socket.async_read_event(std::bind(&monitor_socket::process_read_event, + this, + std::placeholders::_1, + std::placeholders::_2)); + waiting = true; + } + else + { + permanent_error = mdns::make_error_code(status); + } + } + else if (error == boost::asio::error::operation_aborted) + { + // Ignore + } + else + { + permanent_error = error; + } +} + +void monitor_socket::invoke(const boost::system::error_code& error, + async_monitor_handler handler) +{ + handler(error); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/monitor_socket.hpp b/src/dnssd/mdns/monitor_socket.hpp new file mode 100644 index 0000000..6fb3060 --- /dev/null +++ b/src/dnssd/mdns/monitor_socket.hpp @@ -0,0 +1,60 @@ +#ifndef TRIAL_AWARE_MDNS_MONITOR_SOCKET_HPP +#define TRIAL_AWARE_MDNS_MONITOR_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "native_socket.hpp" +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ +class monitor; + +class monitor_socket + : public aware::monitor_socket +{ +public: + monitor_socket(boost::asio::io_service&); + ~monitor_socket(); + + virtual void async_monitor(aware::contact&, + async_monitor_handler) override; + +private: + void process_read_event(const boost::system::error_code&, + std::size_t); + + void invoke(const boost::system::error_code&, + async_monitor_handler); + +private: + boost::asio::io_service& io; + mdns::handle connection; + aware::native_socket socket; + bool waiting; + boost::system::error_code permanent_error; + std::map< std::string, std::shared_ptr > monitors; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_MONITOR_SOCKET_HPP diff --git a/src/dnssd/mdns/properties.cpp b/src/dnssd/mdns/properties.cpp new file mode 100644 index 0000000..44388e9 --- /dev/null +++ b/src/dnssd/mdns/properties.cpp @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "mdns/dns_sd.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/properties.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +properties::properties(const map_type& input) +{ + static_assert(sizeof(decltype(record)) >= sizeof(::TXTRecordRef), + "Increase properties::record_size"); + + ::TXTRecordCreate(reinterpret_cast(std::addressof(record)), 0, 0); + for (const auto& property : input) + { + ::DNSServiceErrorType error = ::TXTRecordSetValue(reinterpret_cast(std::addressof(record)), + property.first.data(), + property.second.size(), + property.second.data()); + throw_on_error(error); + } +} + +properties::~properties() +{ + ::TXTRecordDeallocate(reinterpret_cast(std::addressof(record))); +} + +auto properties::size() const -> size_type +{ + return ::TXTRecordGetLength(reinterpret_cast(std::addressof(record))); +} + +const void *properties::data() const +{ + return ::TXTRecordGetBytesPtr(reinterpret_cast(std::addressof(record))); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/properties.hpp b/src/dnssd/mdns/properties.hpp new file mode 100644 index 0000000..5e028ef --- /dev/null +++ b/src/dnssd/mdns/properties.hpp @@ -0,0 +1,46 @@ +#ifndef TRIAL_AWARE_MDNS_PROPERTIES_HPP +#define TRIAL_AWARE_MDNS_PROPERTIES_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +class properties +{ +public: + using size_type = std::size_t; + using map_type = std::map; + + properties(const map_type& input); + ~properties(); + + size_type size() const; + const void *data() const; + +private: + static constexpr size_type record_size = 16; + std::aligned_storage::type record; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_PROPERTIES_HPP diff --git a/src/dnssd/mdns/resolver.cpp b/src/dnssd/mdns/resolver.cpp new file mode 100644 index 0000000..b507961 --- /dev/null +++ b/src/dnssd/mdns/resolver.cpp @@ -0,0 +1,201 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include // network_to_host_short +#include "mdns/dns_sd.hpp" +#include "mdns/utility.hpp" +#include "mdns/error.hpp" +#include "mdns/throw_on_error.hpp" +#include "mdns/resolver.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +boost::asio::ip::address to_address(const sockaddr& addr) +{ + using namespace boost::asio; + switch (addr.sa_family) + { + case AF_INET: + return ip::address_v4(reinterpret_cast(reinterpret_cast(addr).sin_addr)); + + case AF_INET6: + return ip::address_v6(reinterpret_cast(reinterpret_cast(addr).sin6_addr)); + + default: + assert(false); + break; + } +} + +//----------------------------------------------------------------------------- +// Callback +//----------------------------------------------------------------------------- + +struct resolver::callback +{ + static void on_resolved(::DNSServiceRef, + ::DNSServiceFlags, + uint32_t interface_index, + ::DNSServiceErrorType error, + const char * /* fullname */, // name._type._tcp.local + const char *host, // host.local + uint16_t port, + uint16_t txt_length, + const unsigned char *txt_record, + void *context) + { + auto self = static_cast(context); + assert(self); + + try + { + if (error == kDNSServiceErr_NoError) + { + // Properties + aware::contact::properties_type properties; + const uint16_t keyLength = 256; // See dns_sd.h + char key[keyLength]; + uint8_t valueLength; + const void *value; + uint16_t count = ::TXTRecordGetCount(txt_length, txt_record); + for (uint16_t current = 0; current < count; ++current) + { + if (::TXTRecordGetItemAtIndex(txt_length, txt_record, + current, + keyLength, key, + &valueLength, &value) == kDNSServiceErr_NoError) + { + properties[key] = std::string(static_cast(value), + valueLength); + } + } + + self->contact.index(to_index(interface_index)); + self->contact.properties(properties); + // FIXME: Set port directly on contact? + self->port = boost::asio::detail::socket_ops::network_to_host_short(port); + self->on_resolved(host); + } + else + { + self->listener.on_resolver_failure(mdns::make_error_code(error)); + } + } + catch (const boost::system::system_error& ex) + { + self->listener.on_resolver_failure(ex.code()); + } + catch (...) + { + // Ignore unknown exceptions + } + } + + static void on_addrinfo(::DNSServiceRef, + ::DNSServiceFlags flags, + uint32_t /* interface_index */, + ::DNSServiceErrorType error, + const char * /* host */, + const struct sockaddr *address, + uint32_t /* ttl */, + void *context) + { + auto self = static_cast(context); + assert(self); + + try + { + if (error == kDNSServiceErr_NoError) + { + const bool more = flags & kDNSServiceFlagsMoreComing; + self->contact.address(mdns::to_address(*address)); + self->contact.port(self->port); + self->on_addrinfo(more); + } + else + { + self->listener.on_resolver_failure(mdns::make_error_code(error)); + } + } + catch (...) + { + // Ignore unknown exceptions + } + } +}; + +//----------------------------------------------------------------------------- +// resolver +//----------------------------------------------------------------------------- + +resolver::resolver(mdns::handle& connection, + const aware::contact& contact, + typename resolver::listener& listener) + : connection(connection), + listener(listener), + contact(contact), + port(0) +{ + const ::DNSServiceFlags flags = kDNSServiceFlagsShareConnection; + auto regtype = mdns::type_encode(contact.type()); + + auto ref = connection.get(); + ::DNSServiceErrorType error = ::DNSServiceResolve(&ref, + flags, + from_index(contact.index()), + contact.name().c_str(), + regtype.c_str(), + contact.domain().c_str(), + &resolver::callback::on_resolved, + this); + throw_on_error(error); + + handle.reset(ref); +} + +void resolver::on_resolved(const char *host) +{ + const ::DNSServiceFlags flags = kDNSServiceFlagsShareConnection; + const ::DNSServiceProtocol protocol = + (contact.address().is_v6()) + ? kDNSServiceProtocol_IPv6 + : kDNSServiceProtocol_IPv4; + + auto ref = connection.get(); + ::DNSServiceErrorType error = ::DNSServiceGetAddrInfo(&ref, + flags, + from_index(contact.index()), + protocol, + host, + &resolver::callback::on_addrinfo, + this); + throw_on_error(error); + + handle.reset(ref); +} + +void resolver::on_addrinfo(bool more) +{ + if (!more) + { + handle.reset(); + } + listener.on_resolver_done(contact); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/resolver.hpp b/src/dnssd/mdns/resolver.hpp new file mode 100644 index 0000000..dfc04fd --- /dev/null +++ b/src/dnssd/mdns/resolver.hpp @@ -0,0 +1,59 @@ +#ifndef TRIAL_AWARE_MDNS_RESOLVER_HPP +#define TRIAL_AWARE_MDNS_RESOLVER_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +class resolver +{ +public: + class listener + { + public: + virtual ~listener() = default; + + virtual void on_resolver_done(const aware::contact& contact) = 0; + virtual void on_resolver_failure(const boost::system::error_code&) = 0; + }; + + resolver(mdns::handle&, + const aware::contact& contact, + resolver::listener&); + +private: + void on_resolved(const char *host); + void on_addrinfo(bool more); + +private: + struct callback; + + mdns::handle& connection; + resolver::listener& listener; + mdns::handle handle; + aware::contact contact; + unsigned short port; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_RESOLVER_HPP diff --git a/src/dnssd/mdns/service.cpp b/src/dnssd/mdns/service.cpp new file mode 100644 index 0000000..e3b6537 --- /dev/null +++ b/src/dnssd/mdns/service.cpp @@ -0,0 +1,36 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "mdns/dns_sd.hpp" +#include "mdns/service.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +boost::asio::io_service::id service::id; + +service::service(boost::asio::io_service& io) + : boost::asio::io_service::service(io), + connection(mdns::handle::with_connection) +{ +} + +mdns::handle& service::get_connection() +{ + return connection; +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/service.hpp b/src/dnssd/mdns/service.hpp new file mode 100644 index 0000000..5850629 --- /dev/null +++ b/src/dnssd/mdns/service.hpp @@ -0,0 +1,53 @@ +#ifndef TRIAL_AWARE_MDNS_SERVICE_HPP +#define TRIAL_AWARE_MDNS_SERVICE_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "native_socket.hpp" +#include "mdns/handle.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +class service + : public boost::asio::io_service::service +{ +public: + static boost::asio::io_service::id id; + + struct implementation_type {}; + + explicit service(boost::asio::io_service& io); + + void construct(implementation_type&) {} + void destroy(implementation_type&) {} + + mdns::handle& get_connection(); + +private: + virtual void shutdown_service() {} + +private: + mdns::handle connection; + std::unique_ptr socket; +}; + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_SERVICE_HPP diff --git a/src/dnssd/mdns/throw_on_error.cpp b/src/dnssd/mdns/throw_on_error.cpp new file mode 100644 index 0000000..c1e3a18 --- /dev/null +++ b/src/dnssd/mdns/throw_on_error.cpp @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "mdns/error.hpp" +#include "mdns/throw_on_error.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +void throw_on_error(int error) +{ + if (error) + throw boost::system::system_error(mdns::make_error_code(error)); +} + +} // namespace mdns +} // namespace aware +} // namespace trial diff --git a/src/dnssd/mdns/throw_on_error.hpp b/src/dnssd/mdns/throw_on_error.hpp new file mode 100644 index 0000000..d36faf6 --- /dev/null +++ b/src/dnssd/mdns/throw_on_error.hpp @@ -0,0 +1,27 @@ +#ifndef TRIAL_AWARE_MDNS_THROW_ON_ERROR_HPP +#define TRIAL_AWARE_MDNS_THROW_ON_ERROR_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + +void throw_on_error(int error); + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_THROW_ON_ERROR_HPP diff --git a/src/dnssd/mdns/utility.cpp b/src/dnssd/mdns/utility.cpp new file mode 100644 index 0000000..2ba39f8 --- /dev/null +++ b/src/dnssd/mdns/utility.cpp @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "mdns/utility.hpp" + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + + +std::string type_encode(const std::string& type) +{ + return "_" + type + "._tcp"; +} + +// Extract "type" from a "_type._tcp" string. +// Returns empty string if a parse error occurred. +std::string type_decode(const char *type) +{ + const char *beginning = type; + const char *ending = type; + if (*beginning == '_') + { + ++beginning; + ending = std::strchr(beginning, '.'); + if (ending == 0) + { + ending = beginning; + } + } + return std::string(beginning, ending); +} + +} // namespace mdns +} // namespace aware +} // namespace trial + diff --git a/src/dnssd/mdns/utility.hpp b/src/dnssd/mdns/utility.hpp new file mode 100644 index 0000000..2cb66ff --- /dev/null +++ b/src/dnssd/mdns/utility.hpp @@ -0,0 +1,31 @@ +#ifndef TRIAL_AWARE_MDNS_UTILITY_HPP +#define TRIAL_AWARE_MDNS_UTILITY_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace trial +{ +namespace aware +{ +namespace mdns +{ + + +std::string type_encode(const std::string&); +std::string type_decode(const char *); + +} // namespace mdns +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_MDNS_UTILITY_HPP diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..2082b4e --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,60 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +namespace +{ + +class aware_category + : public boost::system::error_category +{ + virtual const char *name() const noexcept override + { + return "trial::aware"; + } + + virtual std::string message(int value) const override + { + using namespace trial::aware; + switch (errc(value)) + { + case success: + return "success"; + } + return "trial::aware error"; + } +}; + +} // anonymous namespace + +namespace trial +{ +namespace aware +{ + +const boost::system::error_category& category() +{ + static aware_category instance; + return instance; +} + +boost::system::error_code make_error_code(aware::errc value) +{ + return boost::system::error_code(value, aware::category()); +} + +boost::system::error_condition make_error_condition(aware::errc value) +{ + return boost::system::error_condition(value, aware::category()); +} + +} // namespace aware +} // namespace trial diff --git a/src/native_socket.cpp b/src/native_socket.cpp new file mode 100644 index 0000000..40c8625 --- /dev/null +++ b/src/native_socket.cpp @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2018 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "native_socket.hpp" + +namespace trial +{ +namespace aware +{ + +native_socket::native_socket(boost::asio::io_service& io, + native_handle_type handle) + : socket(io, handle) +{ +} + +native_socket::~native_socket() +{ + // Close the socket to make sure all asynchronous requests are cancelled. + if (socket.is_open()) + { + boost::system::error_code dummy; // Ignore errors + socket.close(dummy); + socket.release(); + } +} + +native_socket::native_handle_type native_socket::native_handle() +{ + return socket.native_handle(); +} + +} // namespace aware +} // namespace trial diff --git a/src/native_socket.hpp b/src/native_socket.hpp new file mode 100644 index 0000000..80f386a --- /dev/null +++ b/src/native_socket.hpp @@ -0,0 +1,69 @@ +#ifndef TRIAL_AWARE_NATIVE_SOCKET_HPP +#define TRIAL_AWARE_NATIVE_SOCKET_HPP + +/////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2013 Bjorn Reese +// +// 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) +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +namespace trial +{ +namespace aware +{ + +template +class non_closing_service : public Service +{ +public: + non_closing_service(boost::asio::io_service& io) + : Service(io) + {} + + void destroy(typename Service::implementation_type&) + { + // Do not close the file descriptor as we have no ownership of it + } +}; + +//! @brief Boost.Asio wrapper for a native socket +class native_socket +{ + using service_type = non_closing_service; + using socket_type = boost::asio::posix::basic_stream_descriptor; + +public: + using native_handle_type = socket_type::native_handle_type; + + native_socket(boost::asio::io_service&, native_handle_type); + ~native_socket(); + + template + void async_read_event(Handler&& handler) + { + socket.async_read_some(boost::asio::null_buffers(), std::forward(handler)); + } + + template + void async_write_event(Handler&& handler) + { + socket.async_write_some(boost::asio::null_buffers(), std::forward(handler)); + } + + native_handle_type native_handle(); + +private: + socket_type socket; +}; + +} // namespace aware +} // namespace trial + +#endif // TRIAL_AWARE_NATIVE_SOCKET_HPP