diff --git a/wpinet/src/main/java/edu/wpi/first/net/MulticastServiceAnnouncer.java b/wpinet/src/main/java/edu/wpi/first/net/MulticastServiceAnnouncer.java index 7420a75b88..6dff4541b2 100644 --- a/wpinet/src/main/java/edu/wpi/first/net/MulticastServiceAnnouncer.java +++ b/wpinet/src/main/java/edu/wpi/first/net/MulticastServiceAnnouncer.java @@ -26,6 +26,18 @@ public class MulticastServiceAnnouncer implements AutoCloseable { WPINetJNI.createMulticastServiceAnnouncer(serviceName, serviceType, port, keys, values); } + /** + * Creates a MulticastServiceAnnouncer. + * + * @param serviceName service name + * @param serviceType service type + * @param port port + */ + public MulticastServiceAnnouncer(String serviceName, String serviceType, int port) { + m_handle = + WPINetJNI.createMulticastServiceAnnouncer(serviceName, serviceType, port, null, null); + } + @Override public void close() { WPINetJNI.freeMulticastServiceAnnouncer(m_handle); diff --git a/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp b/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp index ca5b6a6466..ef42b78baa 100644 --- a/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp +++ b/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp @@ -91,17 +91,21 @@ Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceAnnouncer JStringRef serviceNameRef{env, serviceName}; JStringRef serviceTypeRef{env, serviceType}; - size_t keysLen = env->GetArrayLength(keys); wpi::SmallVector, 8> txtVec; - txtVec.reserve(keysLen); - for (size_t i = 0; i < keysLen; i++) { - JLocal key{ - env, static_cast(env->GetObjectArrayElement(keys, i))}; - JLocal value{ - env, static_cast(env->GetObjectArrayElement(values, i))}; - txtVec.emplace_back(std::pair{ - JStringRef{env, key}.str(), JStringRef{env, value}.str()}); + if (keys != nullptr && values != nullptr) { + size_t keysLen = env->GetArrayLength(keys); + + txtVec.reserve(keysLen); + for (size_t i = 0; i < keysLen; i++) { + JLocal key{ + env, static_cast(env->GetObjectArrayElement(keys, i))}; + JLocal value{ + env, static_cast(env->GetObjectArrayElement(values, i))}; + + txtVec.emplace_back(std::pair{ + JStringRef{env, key}.str(), JStringRef{env, value}.str()}); + } } auto announcer = std::make_unique( diff --git a/wpinet/src/main/native/include/wpinet/MulticastServiceAnnouncer.h b/wpinet/src/main/native/include/wpinet/MulticastServiceAnnouncer.h index 29da3fa9cb..68bcb672a0 100644 --- a/wpinet/src/main/native/include/wpinet/MulticastServiceAnnouncer.h +++ b/wpinet/src/main/native/include/wpinet/MulticastServiceAnnouncer.h @@ -22,6 +22,8 @@ class MulticastServiceAnnouncer { MulticastServiceAnnouncer( std::string_view serviceName, std::string_view serviceType, int port, wpi::span> txt); + MulticastServiceAnnouncer(std::string_view serviceName, + std::string_view serviceType, int port); ~MulticastServiceAnnouncer() noexcept; void Start(); void Stop(); diff --git a/wpinet/src/main/native/linux/AvahiClient.cpp b/wpinet/src/main/native/linux/AvahiClient.cpp index 6a17ea0876..7a5f56092c 100644 --- a/wpinet/src/main/native/linux/AvahiClient.cpp +++ b/wpinet/src/main/native/linux/AvahiClient.cpp @@ -58,6 +58,7 @@ AvahiFunctionTable::AvahiFunctionTable() { AvahiFunctionLoad(service_resolver_free); AvahiFunctionLoad(entry_group_new); AvahiFunctionLoad(entry_group_free); + AvahiFunctionLoad(entry_group_add_service); AvahiFunctionLoad(entry_group_add_service_strlst); AvahiFunctionLoad(entry_group_reset); AvahiFunctionLoad(entry_group_is_empty); diff --git a/wpinet/src/main/native/linux/AvahiClient.h b/wpinet/src/main/native/linux/AvahiClient.h index ca679bb41f..1ed93a8b58 100644 --- a/wpinet/src/main/native/linux/AvahiClient.h +++ b/wpinet/src/main/native/linux/AvahiClient.h @@ -263,6 +263,12 @@ class AvahiFunctionTable { (AvahiClient*, AvahiEntryGroupCallback, void*)); AvahiFunction(entry_group_free, int, (AvahiEntryGroup*)); + AvahiFunction(entry_group_add_service, int, + (AvahiEntryGroup * group, AvahiIfIndex interface, + AvahiProtocol protocol, AvahiPublishFlags flags, + const char* name, const char* type, const char* domain, + const char* host, uint16_t port, ...)); + AvahiFunction(entry_group_add_service_strlst, int, (AvahiEntryGroup * group, AvahiIfIndex interface, AvahiProtocol protocol, AvahiPublishFlags flags, diff --git a/wpinet/src/main/native/linux/MulticastServiceAnnouncer.cpp b/wpinet/src/main/native/linux/MulticastServiceAnnouncer.cpp index 29e8fcf90d..c6f493288e 100644 --- a/wpinet/src/main/native/linux/MulticastServiceAnnouncer.cpp +++ b/wpinet/src/main/native/linux/MulticastServiceAnnouncer.cpp @@ -16,7 +16,7 @@ using namespace wpi; struct MulticastServiceAnnouncer::Impl { AvahiFunctionTable& table = AvahiFunctionTable::Get(); std::shared_ptr thread = AvahiThread::Get(); - AvahiClient* client; + AvahiClient* client = nullptr; AvahiEntryGroup* group = nullptr; std::string serviceName; std::string serviceType; @@ -46,18 +46,22 @@ MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName, this->serviceType = serviceType; this->port = port; - std::vector txts; - for (auto&& i : txt) { - txts.push_back(fmt::format("{}={}", i.first, i.second)); - } + if (txt.empty()) { + this->stringList = nullptr; + } else { + std::vector txts; + for (auto&& i : txt) { + txts.push_back(fmt::format("{}={}", i.first, i.second)); + } - std::vector txtArr; - for (auto&& i : txts) { - txtArr.push_back(i.c_str()); - } + std::vector txtArr; + for (auto&& i : txts) { + txtArr.push_back(i.c_str()); + } - this->stringList = - this->table.string_list_new_from_array(txtArr.data(), txtArr.size()); + this->stringList = + this->table.string_list_new_from_array(txtArr.data(), txtArr.size()); + } } static void RegisterService(AvahiClient* client, @@ -85,11 +89,19 @@ static void RegisterService(AvahiClient* client, while (true) { if (impl->table.entry_group_is_empty(impl->group)) { - auto ret = impl->table.entry_group_add_service_strlst( - impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(), - impl->serviceType.c_str(), "local", nullptr, impl->port, - impl->stringList); + int ret = 0; + if (impl->stringList == nullptr) { + ret = impl->table.entry_group_add_service( + impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(), + impl->serviceType.c_str(), "local", nullptr, impl->port, nullptr); + } else { + ret = impl->table.entry_group_add_service_strlst( + impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(), + impl->serviceType.c_str(), "local", nullptr, impl->port, + impl->stringList); + } if (ret == AVAHI_ERR_COLLISION) { // Local collision char* newName = @@ -121,6 +133,12 @@ static void ClientCallback(AvahiClient* client, AvahiClientState state, } } +MulticastServiceAnnouncer::MulticastServiceAnnouncer( + std::string_view serviceName, std::string_view serviceType, int port) { + wpi::span> txt; + pImpl = std::make_unique(serviceName, serviceType, port, txt); +} + MulticastServiceAnnouncer::MulticastServiceAnnouncer( std::string_view serviceName, std::string_view serviceType, int port, wpi::span> txt) { diff --git a/wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp b/wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp index a87da88d4c..361d54270b 100644 --- a/wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp +++ b/wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp @@ -22,6 +22,14 @@ struct MulticastServiceAnnouncer::Impl { ~Impl() noexcept { TXTRecordDeallocate(&txtRecord); } }; +MulticastServiceAnnouncer::MulticastServiceAnnouncer( + std::string_view serviceName, std::string_view serviceType, int port) { + pImpl = std::make_unique(); + pImpl->serviceName = serviceName; + pImpl->serviceType = serviceType; + pImpl->port = port; +} + MulticastServiceAnnouncer::MulticastServiceAnnouncer( std::string_view serviceName, std::string_view serviceType, int port, wpi::span> txt) { diff --git a/wpinet/src/main/native/windows/MulticastServiceAnnouncer.cpp b/wpinet/src/main/native/windows/MulticastServiceAnnouncer.cpp index 5290b0f0d7..8f5daefd0f 100644 --- a/wpinet/src/main/native/windows/MulticastServiceAnnouncer.cpp +++ b/wpinet/src/main/native/windows/MulticastServiceAnnouncer.cpp @@ -103,6 +103,12 @@ MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName, std::wstring{wideStorage.data(), wideStorage.size()}; } +MulticastServiceAnnouncer::MulticastServiceAnnouncer( + std::string_view serviceName, std::string_view serviceType, int port) { + wpi::span> txt; + pImpl = std::make_unique(serviceName, serviceType, port, txt); +} + MulticastServiceAnnouncer::MulticastServiceAnnouncer( std::string_view serviceName, std::string_view serviceType, int port, wpi::span> txt) { diff --git a/wpinet/src/test/native/cpp/MulticastTest.cpp b/wpinet/src/test/native/cpp/MulticastTest.cpp new file mode 100644 index 0000000000..64f360f683 --- /dev/null +++ b/wpinet/src/test/native/cpp/MulticastTest.cpp @@ -0,0 +1,47 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include +#include + +#include + +#include + +#include "gtest/gtest.h" + +TEST(MulticastServiceAnnouncerTest, EmptyText) { + const std::string_view serviceName = "TestServiceNoText"; + const std::string_view serviceType = "_wpinotxt"; + const int port = std::rand(); + wpi::MulticastServiceAnnouncer announcer(serviceName, serviceType, port); + wpi::MulticastServiceResolver resolver(serviceType); + + if (announcer.HasImplementation() && resolver.HasImplementation()) { + announcer.Start(); + resolver.Start(); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + resolver.Stop(); + announcer.Stop(); + } +} + +TEST(MulticastServiceAnnouncerTest, SingleText) { + const std::string_view serviceName = "TestServiceSingle"; + const std::string_view serviceType = "_wpitxt"; + const int port = std::rand(); + std::array, 1> txt = { + std::make_pair("hello", "world")}; + wpi::MulticastServiceAnnouncer announcer(serviceName, serviceType, port, txt); + wpi::MulticastServiceResolver resolver(serviceType); + + if (announcer.HasImplementation() && resolver.HasImplementation()) { + announcer.Start(); + resolver.Start(); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +}