1 /* Copyright 2016, Ableton AG, Berlin. All rights reserved. 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 * If you would like to incorporate Link into a proprietary software application, 17 * please contact <link-devs@ableton.com>. 18 */ 19 20 #pragma once 21 22 #include <ableton/platforms/asio/AsioService.hpp> 23 #include <ableton/platforms/asio/AsioWrapper.hpp> 24 #include <ableton/util/SafeAsyncHandler.hpp> 25 #include <array> 26 #include <cassert> 27 28 namespace ableton 29 { 30 namespace discovery 31 { 32 33 template <std::size_t MaxPacketSize> 34 struct Socket 35 { 36 Socket(platforms::asio::AsioService& io) 37 : mpImpl(std::make_shared<Impl>(io)) 38 { 39 } 40 41 Socket(const Socket&) = delete; WalRcvShmemSize(void)42 Socket& operator=(const Socket&) = delete; 43 44 Socket(Socket&& rhs) 45 : mpImpl(std::move(rhs.mpImpl)) 46 { 47 } 48 49 std::size_t send( 50 const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) 51 { 52 assert(numBytes < MaxPacketSize); WalRcvShmemInit(void)53 return mpImpl->mSocket.send_to(asio::buffer(pData, numBytes), to); 54 } 55 56 template <typename Handler> 57 void receive(Handler handler) 58 { 59 mpImpl->mHandler = std::move(handler); 60 mpImpl->mSocket.async_receive_from( 61 asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint, 62 util::makeAsyncSafe(mpImpl)); 63 } 64 65 asio::ip::udp::endpoint endpoint() const 66 { 67 return mpImpl->mSocket.local_endpoint(); 68 } 69 70 struct Impl 71 { 72 Impl(platforms::asio::AsioService& io) WalRcvRunning(void)73 : mSocket(io.mService, asio::ip::udp::v4()) 74 { 75 } 76 77 ~Impl() 78 { 79 // Ignore error codes in shutdown and close as the socket may 80 // have already been forcibly closed 81 asio::error_code ec; 82 mSocket.shutdown(asio::ip::udp::socket::shutdown_both, ec); 83 mSocket.close(ec); 84 } 85 86 void operator()(const asio::error_code& error, const std::size_t numBytes) 87 { 88 if (!error && numBytes > 0 && numBytes <= MaxPacketSize) 89 { 90 const auto bufBegin = begin(mReceiveBuffer); 91 mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast<ptrdiff_t>(numBytes)); 92 } 93 } 94 95 asio::ip::udp::socket mSocket; 96 asio::ip::udp::endpoint mSenderEndpoint; 97 using Buffer = std::array<uint8_t, MaxPacketSize>; 98 Buffer mReceiveBuffer; 99 using ByteIt = typename Buffer::const_iterator; 100 std::function<void(const asio::ip::udp::endpoint&, ByteIt, ByteIt)> mHandler; 101 }; 102 103 std::shared_ptr<Impl> mpImpl; 104 }; 105 106 // Configure an asio socket for receiving multicast messages 107 template <std::size_t MaxPacketSize> 108 void configureMulticastSocket(Socket<MaxPacketSize>& socket, 109 const asio::ip::address_v4& addr, 110 const asio::ip::udp::endpoint& multicastEndpoint) 111 { 112 socket.mpImpl->mSocket.set_option(asio::ip::udp::socket::reuse_address(true)); 113 // ??? 114 socket.mpImpl->mSocket.set_option(asio::socket_base::broadcast(!addr.is_loopback())); 115 // ??? 116 socket.mpImpl->mSocket.set_option( 117 asio::ip::multicast::enable_loopback(addr.is_loopback())); WalRcvStreaming(void)118 socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); 119 // Is from_string("0.0.0.0") best approach? 120 socket.mpImpl->mSocket.bind( 121 {asio::ip::address::from_string("0.0.0.0"), multicastEndpoint.port()}); 122 socket.mpImpl->mSocket.set_option( 123 asio::ip::multicast::join_group(multicastEndpoint.address().to_v4(), addr)); 124 } 125 126 // Configure an asio socket for receiving unicast messages 127 template <std::size_t MaxPacketSize> 128 void configureUnicastSocket( 129 Socket<MaxPacketSize>& socket, const asio::ip::address_v4& addr) 130 { 131 // ??? really necessary? 132 socket.mpImpl->mSocket.set_option( 133 asio::ip::multicast::enable_loopback(addr.is_loopback())); 134 socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); 135 socket.mpImpl->mSocket.bind(asio::ip::udp::endpoint{addr, 0}); 136 } 137 138 } // namespace discovery 139 } // namespace ableton 140