| // 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 "wpi/MulticastServiceAnnouncer.h" |
| |
| #include <vector> |
| |
| #include "AvahiClient.h" |
| #include "fmt/format.h" |
| #include "wpi/mutex.h" |
| |
| using namespace wpi; |
| |
| struct MulticastServiceAnnouncer::Impl { |
| AvahiFunctionTable& table = AvahiFunctionTable::Get(); |
| std::shared_ptr<AvahiThread> thread = AvahiThread::Get(); |
| AvahiClient* client; |
| AvahiEntryGroup* group = nullptr; |
| std::string serviceName; |
| std::string serviceType; |
| int port; |
| AvahiStringList* stringList = nullptr; |
| |
| ~Impl() noexcept { |
| if (stringList != nullptr && table.IsValid()) { |
| table.string_list_free(stringList); |
| } |
| } |
| |
| template <typename T> |
| Impl(std::string_view serviceName, std::string_view serviceType, int port, |
| wpi::span<const std::pair<T, T>> txt); |
| }; |
| |
| template <typename T> |
| MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName, |
| std::string_view serviceType, int port, |
| wpi::span<const std::pair<T, T>> txt) { |
| if (!this->table.IsValid()) { |
| return; |
| } |
| |
| this->serviceName = serviceName; |
| this->serviceType = serviceType; |
| this->port = port; |
| |
| std::vector<std::string> txts; |
| for (auto&& i : txt) { |
| txts.push_back(fmt::format("{}={}", i.first, i.second)); |
| } |
| |
| std::vector<const char*> txtArr; |
| for (auto&& i : txts) { |
| txtArr.push_back(i.c_str()); |
| } |
| |
| this->stringList = |
| this->table.string_list_new_from_array(txtArr.data(), txtArr.size()); |
| } |
| |
| static void RegisterService(AvahiClient* client, |
| MulticastServiceAnnouncer::Impl* impl); |
| |
| static void EntryGroupCallback(AvahiEntryGroup* group, |
| AvahiEntryGroupState state, void* userdata) { |
| if (state == AVAHI_ENTRY_GROUP_COLLISION) { |
| // Remote collision |
| MulticastServiceAnnouncer::Impl* impl = |
| reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata); |
| char* newName = |
| impl->table.alternative_service_name(impl->serviceName.c_str()); |
| impl->serviceName = newName; |
| impl->table.free(newName); |
| RegisterService(impl->table.entry_group_get_client(group), impl); |
| } |
| } |
| |
| static void RegisterService(AvahiClient* client, |
| MulticastServiceAnnouncer::Impl* impl) { |
| if (impl->group == nullptr) { |
| impl->group = impl->table.entry_group_new(client, EntryGroupCallback, impl); |
| } |
| |
| 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); |
| if (ret == AVAHI_ERR_COLLISION) { |
| // Local collision |
| char* newName = |
| impl->table.alternative_service_name(impl->serviceName.c_str()); |
| impl->serviceName = newName; |
| impl->table.free(newName); |
| continue; |
| } else if (ret != AVAHI_OK) { |
| break; |
| } |
| impl->table.entry_group_commit(impl->group); |
| break; |
| } |
| } |
| } |
| |
| static void ClientCallback(AvahiClient* client, AvahiClientState state, |
| void* userdata) { |
| MulticastServiceAnnouncer::Impl* impl = |
| reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata); |
| |
| if (state == AVAHI_CLIENT_S_RUNNING) { |
| RegisterService(client, impl); |
| } else if (state == AVAHI_CLIENT_S_COLLISION || |
| state == AVAHI_CLIENT_S_REGISTERING) { |
| if (impl->group) { |
| impl->table.entry_group_reset(impl->group); |
| } |
| } |
| } |
| |
| MulticastServiceAnnouncer::MulticastServiceAnnouncer( |
| std::string_view serviceName, std::string_view serviceType, int port, |
| wpi::span<const std::pair<std::string, std::string>> txt) { |
| pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt); |
| } |
| |
| MulticastServiceAnnouncer::MulticastServiceAnnouncer( |
| std::string_view serviceName, std::string_view serviceType, int port, |
| wpi::span<const std::pair<std::string_view, std::string_view>> txt) { |
| pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt); |
| } |
| |
| MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept { |
| Stop(); |
| } |
| |
| bool MulticastServiceAnnouncer::HasImplementation() const { |
| return pImpl->table.IsValid(); |
| } |
| |
| void MulticastServiceAnnouncer::Start() { |
| if (!pImpl->table.IsValid()) { |
| return; |
| } |
| std::scoped_lock lock{*pImpl->thread}; |
| if (pImpl->client) { |
| return; |
| } |
| pImpl->client = |
| pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL, |
| ClientCallback, pImpl.get(), nullptr); |
| } |
| |
| void MulticastServiceAnnouncer::Stop() { |
| if (!pImpl->table.IsValid()) { |
| return; |
| } |
| std::scoped_lock lock{*pImpl->thread}; |
| if (pImpl->client) { |
| if (pImpl->group) { |
| pImpl->table.entry_group_free(pImpl->group); |
| pImpl->group = nullptr; |
| } |
| pImpl->table.client_free(pImpl->client); |
| pImpl->client = nullptr; |
| } |
| } |