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