James Kuszmaul | cf32412 | 2023-01-14 14:07:17 -0800 | [diff] [blame^] | 1 | // Copyright (c) FIRST and other WPILib contributors. |
| 2 | // Open Source Software; you can modify and/or share it under the terms of |
| 3 | // the WPILib BSD license file in the root directory of this project. |
| 4 | |
| 5 | #include "wpinet/MulticastServiceAnnouncer.h" |
| 6 | |
| 7 | #include <vector> |
| 8 | |
| 9 | #include <fmt/format.h> |
| 10 | #include <wpi/mutex.h> |
| 11 | |
| 12 | #include "AvahiClient.h" |
| 13 | |
| 14 | using namespace wpi; |
| 15 | |
| 16 | struct MulticastServiceAnnouncer::Impl { |
| 17 | AvahiFunctionTable& table = AvahiFunctionTable::Get(); |
| 18 | std::shared_ptr<AvahiThread> thread = AvahiThread::Get(); |
| 19 | AvahiClient* client = nullptr; |
| 20 | AvahiEntryGroup* group = nullptr; |
| 21 | std::string serviceName; |
| 22 | std::string serviceType; |
| 23 | int port; |
| 24 | AvahiStringList* stringList = nullptr; |
| 25 | |
| 26 | ~Impl() noexcept { |
| 27 | if (stringList != nullptr && table.IsValid()) { |
| 28 | table.string_list_free(stringList); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | template <typename T> |
| 33 | Impl(std::string_view serviceName, std::string_view serviceType, int port, |
| 34 | std::span<const std::pair<T, T>> txt); |
| 35 | }; |
| 36 | |
| 37 | template <typename T> |
| 38 | MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName, |
| 39 | std::string_view serviceType, int port, |
| 40 | std::span<const std::pair<T, T>> txt) { |
| 41 | if (!this->table.IsValid()) { |
| 42 | return; |
| 43 | } |
| 44 | |
| 45 | this->serviceName = serviceName; |
| 46 | this->serviceType = serviceType; |
| 47 | this->port = port; |
| 48 | |
| 49 | if (txt.empty()) { |
| 50 | this->stringList = nullptr; |
| 51 | } else { |
| 52 | std::vector<std::string> txts; |
| 53 | for (auto&& i : txt) { |
| 54 | txts.push_back(fmt::format("{}={}", i.first, i.second)); |
| 55 | } |
| 56 | |
| 57 | std::vector<const char*> txtArr; |
| 58 | for (auto&& i : txts) { |
| 59 | txtArr.push_back(i.c_str()); |
| 60 | } |
| 61 | |
| 62 | this->stringList = |
| 63 | this->table.string_list_new_from_array(txtArr.data(), txtArr.size()); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | static void RegisterService(AvahiClient* client, |
| 68 | MulticastServiceAnnouncer::Impl* impl); |
| 69 | |
| 70 | static void EntryGroupCallback(AvahiEntryGroup* group, |
| 71 | AvahiEntryGroupState state, void* userdata) { |
| 72 | if (state == AVAHI_ENTRY_GROUP_COLLISION) { |
| 73 | // Remote collision |
| 74 | MulticastServiceAnnouncer::Impl* impl = |
| 75 | reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata); |
| 76 | char* newName = |
| 77 | impl->table.alternative_service_name(impl->serviceName.c_str()); |
| 78 | impl->serviceName = newName; |
| 79 | impl->table.free(newName); |
| 80 | RegisterService(impl->table.entry_group_get_client(group), impl); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | static void RegisterService(AvahiClient* client, |
| 85 | MulticastServiceAnnouncer::Impl* impl) { |
| 86 | if (impl->group == nullptr) { |
| 87 | impl->group = impl->table.entry_group_new(client, EntryGroupCallback, impl); |
| 88 | } |
| 89 | |
| 90 | while (true) { |
| 91 | if (impl->table.entry_group_is_empty(impl->group)) { |
| 92 | int ret = 0; |
| 93 | if (impl->stringList == nullptr) { |
| 94 | ret = impl->table.entry_group_add_service( |
| 95 | impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, |
| 96 | AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(), |
| 97 | impl->serviceType.c_str(), "local", nullptr, impl->port, nullptr); |
| 98 | } else { |
| 99 | ret = impl->table.entry_group_add_service_strlst( |
| 100 | impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, |
| 101 | AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(), |
| 102 | impl->serviceType.c_str(), "local", nullptr, impl->port, |
| 103 | impl->stringList); |
| 104 | } |
| 105 | if (ret == AVAHI_ERR_COLLISION) { |
| 106 | // Local collision |
| 107 | char* newName = |
| 108 | impl->table.alternative_service_name(impl->serviceName.c_str()); |
| 109 | impl->serviceName = newName; |
| 110 | impl->table.free(newName); |
| 111 | continue; |
| 112 | } else if (ret != AVAHI_OK) { |
| 113 | break; |
| 114 | } |
| 115 | impl->table.entry_group_commit(impl->group); |
| 116 | break; |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | static void ClientCallback(AvahiClient* client, AvahiClientState state, |
| 122 | void* userdata) { |
| 123 | MulticastServiceAnnouncer::Impl* impl = |
| 124 | reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata); |
| 125 | |
| 126 | if (state == AVAHI_CLIENT_S_RUNNING) { |
| 127 | RegisterService(client, impl); |
| 128 | } else if (state == AVAHI_CLIENT_S_COLLISION || |
| 129 | state == AVAHI_CLIENT_S_REGISTERING) { |
| 130 | if (impl->group) { |
| 131 | impl->table.entry_group_reset(impl->group); |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | MulticastServiceAnnouncer::MulticastServiceAnnouncer( |
| 137 | std::string_view serviceName, std::string_view serviceType, int port) { |
| 138 | std::span<const std::pair<std::string_view, std::string_view>> txt; |
| 139 | pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt); |
| 140 | } |
| 141 | |
| 142 | MulticastServiceAnnouncer::MulticastServiceAnnouncer( |
| 143 | std::string_view serviceName, std::string_view serviceType, int port, |
| 144 | std::span<const std::pair<std::string, std::string>> txt) { |
| 145 | pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt); |
| 146 | } |
| 147 | |
| 148 | MulticastServiceAnnouncer::MulticastServiceAnnouncer( |
| 149 | std::string_view serviceName, std::string_view serviceType, int port, |
| 150 | std::span<const std::pair<std::string_view, std::string_view>> txt) { |
| 151 | pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt); |
| 152 | } |
| 153 | |
| 154 | MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept { |
| 155 | Stop(); |
| 156 | } |
| 157 | |
| 158 | bool MulticastServiceAnnouncer::HasImplementation() const { |
| 159 | return pImpl->table.IsValid(); |
| 160 | } |
| 161 | |
| 162 | void MulticastServiceAnnouncer::Start() { |
| 163 | if (!pImpl->table.IsValid()) { |
| 164 | return; |
| 165 | } |
| 166 | std::scoped_lock lock{*pImpl->thread}; |
| 167 | if (pImpl->client) { |
| 168 | return; |
| 169 | } |
| 170 | pImpl->client = |
| 171 | pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL, |
| 172 | ClientCallback, pImpl.get(), nullptr); |
| 173 | } |
| 174 | |
| 175 | void MulticastServiceAnnouncer::Stop() { |
| 176 | if (!pImpl->table.IsValid()) { |
| 177 | return; |
| 178 | } |
| 179 | std::scoped_lock lock{*pImpl->thread}; |
| 180 | if (pImpl->client) { |
| 181 | if (pImpl->group) { |
| 182 | pImpl->table.entry_group_free(pImpl->group); |
| 183 | pImpl->group = nullptr; |
| 184 | } |
| 185 | pImpl->table.client_free(pImpl->client); |
| 186 | pImpl->client = nullptr; |
| 187 | } |
| 188 | } |