diff --git a/external_libs/link/.appveyor.yml b/external_libs/link/.appveyor.yml index aff8de9..b64fa6b 100644 --- a/external_libs/link/.appveyor.yml +++ b/external_libs/link/.appveyor.yml @@ -6,21 +6,23 @@ branches: environment: matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave - CONFIGURATION: Release - XCODE_VERSION: 9.4.1 - - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina - CONFIGURATION: Release - XCODE_VERSION: 11.7 - - APPVEYOR_BUILD_WORKER_IMAGE: macos-bigsur - CONFIGURATION: Debug - XCODE_VERSION: 12.5.1 - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey CONFIGURATION: Release XCODE_VERSION: 13.4.1 - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey + CONFIGURATION: Debug + XCODE_VERSION: 14.2.0 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-ventura CONFIGURATION: Release - XCODE_VERSION: 14.1 + XCODE_VERSION: 14.3.0 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma + CONFIGURATION: Debug + XCODE_VERSION: 15.2.0 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Alsa + CONFIGURATION: Release + GENERATOR: Ninja + CXX: clang++-12 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 AUDIO_DRIVER: Jack CONFIGURATION: Debug @@ -36,6 +38,16 @@ environment: CONFIGURATION: Debug GENERATOR: Ninja CXX: clang++-9 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204 + AUDIO_DRIVER: Alsa + CONFIGURATION: Release + GENERATOR: Ninja + CXX: g++-11 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2204 + AUDIO_DRIVER: Jack + CONFIGURATION: Debug + GENERATOR: Ninja + CXX: g++-10 - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 AUDIO_DRIVER: Alsa CONFIGURATION: Release @@ -88,10 +100,9 @@ install: for: - matrix: only: - - APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave - - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina - - APPVEYOR_BUILD_WORKER_IMAGE: macos-bigsur - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey + - APPVEYOR_BUILD_WORKER_IMAGE: macos-ventura + - APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma build_script: - sudo xcode-select -s /Applications/Xcode-$XCODE_VERSION.app - python3 ci/configure.py --generator Xcode diff --git a/external_libs/link/include/ableton/discovery/PeerGateway.hpp b/external_libs/link/include/ableton/discovery/PeerGateway.hpp index 6b4db27..3a04a6b 100644 --- a/external_libs/link/include/ableton/discovery/PeerGateway.hpp +++ b/external_libs/link/include/ableton/discovery/PeerGateway.hpp @@ -213,23 +213,21 @@ PeerGateway makePeerGateway( return {std::move(messenger), std::move(observer), std::move(io)}; } -// IpV4 gateway types template -using IpV4Messenger = UdpMessenger< +using Messenger = UdpMessenger< IpInterface::type&, v1::kMaxMessageSize>, StateQuery, IoContext>; template -using IpV4Gateway = - PeerGateway::type&>, +using Gateway = + PeerGateway::type&>, PeerObserver, IoContext>; // Factory function to bind a PeerGateway to an IpInterface with the given address. template -IpV4Gateway makeIpV4Gateway( - util::Injected io, +Gateway makeGateway(util::Injected io, const IpAddress& addr, util::Injected observer, NodeState state) diff --git a/external_libs/link/include/ableton/discovery/Service.hpp b/external_libs/link/include/ableton/discovery/Service.hpp index 678811d..394208f 100644 --- a/external_libs/link/include/ableton/discovery/Service.hpp +++ b/external_libs/link/include/ableton/discovery/Service.hpp @@ -33,16 +33,23 @@ class Service using ServicePeerGateways = PeerGateways; Service(NodeState state, GatewayFactory factory, util::Injected io) - : mGateways( + : mEnabled(false) + , mGateways( std::chrono::seconds(5), std::move(state), std::move(factory), std::move(io)) { } void enable(const bool bEnable) { + mEnabled = bEnable; mGateways.enable(bEnable); } + bool isEnabled() const + { + return mEnabled; + } + // Asynchronously operate on the current set of peer gateways. The // handler will be invoked in the service's io context. template @@ -65,6 +72,7 @@ class Service } private: + bool mEnabled; ServicePeerGateways mGateways; }; diff --git a/external_libs/link/include/ableton/discovery/UdpMessenger.hpp b/external_libs/link/include/ableton/discovery/UdpMessenger.hpp index cca36ae..2ba1bc4 100644 --- a/external_libs/link/include/ableton/discovery/UdpMessenger.hpp +++ b/external_libs/link/include/ableton/discovery/UdpMessenger.hpp @@ -270,24 +270,42 @@ class UdpMessenger // Ignore messages from self and other groups if (header.ident != mState.ident() && header.groupId == 0) { - debug(mIo->log()) << "Received message type " - << static_cast(header.messageType) << " from peer " - << header.ident; + // On Linux multicast messages are sent to all sockets registered to the multicast + // group. To avoid duplicate message handling and invalid response messages we + // check if the message is coming from an endpoint that is in the same subnet as + // the interface. + auto ignoreIpV4Message = false; + if (from.address().is_v4() && mInterface->endpoint().address().is_v4()) + { + const auto subnet = LINK_ASIO_NAMESPACE::ip::make_network_v4( + mInterface->endpoint().address().to_v4(), 24); + const auto fromAddr = + LINK_ASIO_NAMESPACE::ip::make_network_v4(from.address().to_v4(), 32); + ignoreIpV4Message = !fromAddr.is_subnet_of(subnet); + } - switch (header.messageType) + if (!ignoreIpV4Message) { - case v1::kAlive: - sendResponse(from); - receivePeerState(std::move(result.first), result.second, messageEnd); - break; - case v1::kResponse: - receivePeerState(std::move(result.first), result.second, messageEnd); - break; - case v1::kByeBye: - receiveByeBye(std::move(result.first.ident)); - break; - default: - info(mIo->log()) << "Unknown message received of type: " << header.messageType; + debug(mIo->log()) << "Received message type " + << static_cast(header.messageType) << " from peer " + << header.ident; + + switch (header.messageType) + { + case v1::kAlive: + sendResponse(from); + receivePeerState(std::move(result.first), result.second, messageEnd); + break; + case v1::kResponse: + receivePeerState(std::move(result.first), result.second, messageEnd); + break; + case v1::kByeBye: + receiveByeBye(std::move(result.first.ident)); + break; + default: + info(mIo->log()) << "Unknown message received of type: " + << header.messageType; + } } } listen(tag); diff --git a/external_libs/link/include/ableton/discovery/test/Interface.hpp b/external_libs/link/include/ableton/discovery/test/Interface.hpp index 9f11b75..b5643f5 100644 --- a/external_libs/link/include/ableton/discovery/test/Interface.hpp +++ b/external_libs/link/include/ableton/discovery/test/Interface.hpp @@ -31,6 +31,13 @@ namespace test class Interface { public: + Interface() = default; + + Interface(UdpEndpoint endpoint) + : mEndpoint(std::move(endpoint)) + { + } + void send( const uint8_t* const bytes, const size_t numBytes, const UdpEndpoint& endpoint) { @@ -56,7 +63,7 @@ class Interface UdpEndpoint endpoint() const { - return UdpEndpoint({}, 0); + return mEndpoint; } using SentMessage = std::pair, UdpEndpoint>; @@ -66,6 +73,7 @@ class Interface using ReceiveCallback = std::function&)>; ReceiveCallback mCallback; + UdpEndpoint mEndpoint; }; } // namespace test diff --git a/external_libs/link/include/ableton/link/Controller.hpp b/external_libs/link/include/ableton/link/Controller.hpp index a0a3c14..07117e8 100644 --- a/external_libs/link/include/ableton/link/Controller.hpp +++ b/external_libs/link/include/ableton/link/Controller.hpp @@ -176,7 +176,8 @@ class Controller auto stopped = false; mIo->async([this, &mutex, &condition, &stopped]() { - enable(false); + mEnabled = false; + mDiscovery.enable(false); std::unique_lock lock(mutex); stopped = true; condition.notify_one(); @@ -193,18 +194,7 @@ class Controller const bool bWasEnabled = mEnabled.exchange(bEnable); if (bWasEnabled != bEnable) { - mIo->async([this, bEnable] { - if (bEnable) - { - // Process the pending client states to make sure we don't push one after we - // have joined a running session - mRtClientStateSetter.processPendingClientStates(); - // Always reset when first enabling to avoid hijacking - // tempo in existing sessions - resetState(); - } - mDiscovery.enable(bEnable); - }); + mRtClientStateSetter.invoke(); } } @@ -570,11 +560,23 @@ class Controller RtClientStateSetter(Controller& controller) : mController(controller) , mCallbackDispatcher( - [this] { mController.mIo->async([this]() { processPendingClientStates(); }); }, + [this] { + mController.mIo->async([this]() { + // Process the pending client states first to make sure we don't push one + // after we have joined a running session when enabling + processPendingClientStates(); + updateEnabled(); + }); + }, detail::kRtHandlerFallbackPeriod) { } + void invoke() + { + mCallbackDispatcher.invoke(); + } + void push(const IncomingClientState clientState) { if (clientState.timeline) @@ -600,6 +602,20 @@ class Controller mController.handleRtClientState(clientState); } + void updateEnabled() + { + if (mController.mEnabled && !mController.mDiscovery.isEnabled()) + { + // Always reset when first enabling to avoid hijacking tempo in existing sessions + mController.resetState(); + mController.mDiscovery.enable(true); + } + else if (!mController.mEnabled && mController.mDiscovery.isEnabled()) + { + mController.mDiscovery.enable(false); + } + } + private: IncomingClientState buildMergedPendingClientState() { diff --git a/external_libs/link/include/ableton/link/Gateway.hpp b/external_libs/link/include/ableton/link/Gateway.hpp index 6352272..1a2bcb4 100644 --- a/external_libs/link/include/ableton/link/Gateway.hpp +++ b/external_libs/link/include/ableton/link/Gateway.hpp @@ -44,7 +44,7 @@ class Gateway std::move(ghostXForm), std::move(clock), util::injectRef(*mIo)) - , mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo), + , mPeerGateway(discovery::makeGateway(util::injectRef(*mIo), std::move(addr), std::move(observer), PeerState{std::move(nodeState), mMeasurement.endpoint()})) @@ -84,9 +84,8 @@ class Gateway private: util::Injected mIo; MeasurementService::type&> mMeasurement; - discovery:: - IpV4Gateway::type&> - mPeerGateway; + discovery::Gateway::type&> + mPeerGateway; }; } // namespace link diff --git a/external_libs/link/src/ableton/discovery/tst_UdpMessenger.cpp b/external_libs/link/src/ableton/discovery/tst_UdpMessenger.cpp index c30a335..908576e 100644 --- a/external_libs/link/src/ableton/discovery/tst_UdpMessenger.cpp +++ b/external_libs/link/src/ableton/discovery/tst_UdpMessenger.cpp @@ -99,7 +99,8 @@ TEST_CASE("UdpMessenger") const auto state2 = TestNodeState{3, 10}; const auto peerEndpoint = UdpEndpoint{IpAddress::from_string("123.123.234.234"), 1900}; ::ableton::test::serial_io::Fixture io; - auto iface = test::Interface{}; + auto iface = + test::Interface(UdpEndpoint{IpAddress::from_string("123.123.234.42"), 1234}); SECTION("BroadcastsStateOnConstruction") { @@ -214,6 +215,25 @@ TEST_CASE("UdpMessenger") // We should have an initial Alive and then a single ByeBye CHECK(2 == iface.sentMessages.size()); } + + SECTION("DropMessageFromUnreachableNetwork") + { + auto tmpMessenger = makeUdpMessenger( + util::injectRef(iface), TestNodeState{}, util::injectVal(io.makeIoContext()), 1, 1); + auto messenger = std::move(tmpMessenger); + auto handler = TestHandler{}; + messenger.receive(std::ref(handler)); + + // Simulate state broadcast from peer, leaving out details like payload + v1::MessageBuffer buffer; + const auto messageEnd = + v1::aliveMessage(state1.ident(), 0, makePayload(), begin(buffer)); + iface.incomingMessage( + UdpEndpoint{IpAddress::from_string("1.2.3.4"), 5678}, begin(buffer), messageEnd); + + // Received message should not be handled + CHECK(0 == handler.peerStates.size()); + } } } // namespace discovery