blob: d04711fa57c9b2a138c1c7ef1fee9a723cfc972b [file] [log] [blame]
James Kuszmaulcf324122023-01-14 14:07:17 -08001// 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
14using namespace wpi;
15
16struct 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
37template <typename T>
38MulticastServiceAnnouncer::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
67static void RegisterService(AvahiClient* client,
68 MulticastServiceAnnouncer::Impl* impl);
69
70static 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
84static 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
121static 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
136MulticastServiceAnnouncer::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
142MulticastServiceAnnouncer::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
148MulticastServiceAnnouncer::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
154MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
155 Stop();
156}
157
158bool MulticastServiceAnnouncer::HasImplementation() const {
159 return pImpl->table.IsValid();
160}
161
162void 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
175void 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}