blob: 6585443e4b81334a1d2a8dd77241dd934a801193 [file] [log] [blame]
Austin Schuh75263e32022-02-22 18:05:32 -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 "wpi/MulticastServiceResolver.h"
6
7#include "AvahiClient.h"
8#include "wpi/SmallString.h"
9#include "wpi/StringExtras.h"
10#include "wpi/mutex.h"
11
12using namespace wpi;
13
14struct MulticastServiceResolver::Impl {
15 AvahiFunctionTable& table = AvahiFunctionTable::Get();
16 std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
17 AvahiClient* client;
18 AvahiServiceBrowser* browser;
19 std::string serviceType;
20 MulticastServiceResolver* resolver;
21
22 void onFound(ServiceData&& data) {
23 resolver->PushData(std::forward<ServiceData>(data));
24 }
25};
26
27MulticastServiceResolver::MulticastServiceResolver(
28 std::string_view serviceType) {
29 pImpl = std::make_unique<Impl>();
30 pImpl->serviceType = serviceType;
31 pImpl->resolver = this;
32}
33
34MulticastServiceResolver::~MulticastServiceResolver() noexcept {
35 Stop();
36}
37
38bool MulticastServiceResolver::HasImplementation() const {
39 return pImpl->table.IsValid();
40}
41
42static void ResolveCallback(AvahiServiceResolver* r, AvahiIfIndex interface,
43 AvahiProtocol protocol, AvahiResolverEvent event,
44 const char* name, const char* type,
45 const char* domain, const char* host_name,
46 const AvahiAddress* address, uint16_t port,
47 AvahiStringList* txt, AvahiLookupResultFlags flags,
48 void* userdata) {
49 MulticastServiceResolver::Impl* impl =
50 reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
51
52 if (event == AVAHI_RESOLVER_FOUND) {
53 if (address->proto == AVAHI_PROTO_INET) {
54 AvahiStringList* strLst = txt;
55 MulticastServiceResolver::ServiceData data;
56 while (strLst != nullptr) {
57 std::string_view value{reinterpret_cast<const char*>(strLst->text),
58 strLst->size};
59 strLst = strLst->next;
60 size_t splitIndex = value.find('=');
61 if (splitIndex == value.npos) {
62 // Todo make this just do key
63 continue;
64 }
65 std::string_view key = wpi::substr(value, 0, splitIndex);
66 value =
67 wpi::substr(value, splitIndex + 1, value.size() - splitIndex - 1);
68 data.txt.emplace_back(std::pair<std::string, std::string>{key, value});
69 }
70 wpi::SmallString<256> outputHostName;
71 char label[256];
72 do {
73 impl->table.unescape_label(&host_name, label, sizeof(label));
74 if (label[0] == '\0') {
75 break;
76 }
77 outputHostName.append(label);
78 outputHostName.append(".");
79 } while (true);
80
81 data.ipv4Address = address->data.ipv4.address;
82 data.port = port;
83 data.serviceName = name;
84 data.hostName = outputHostName.string();
85
86 impl->onFound(std::move(data));
87 }
88 }
89
90 impl->table.service_resolver_free(r);
91}
92
93static void BrowseCallback(AvahiServiceBrowser* b, AvahiIfIndex interface,
94 AvahiProtocol protocol, AvahiBrowserEvent event,
95 const char* name, const char* type,
96 const char* domain, AvahiLookupResultFlags flags,
97 void* userdata) {
98 MulticastServiceResolver::Impl* impl =
99 reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
100
101 if (event == AVAHI_BROWSER_NEW) {
102 impl->table.service_resolver_new(
103 impl->table.service_browser_get_client(b), interface, protocol, name,
104 type, domain, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST,
105 ResolveCallback, userdata);
106 }
107}
108
109static void ClientCallback(AvahiClient* client, AvahiClientState state,
110 void* userdata) {
111 MulticastServiceResolver::Impl* impl =
112 reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
113
114 if (state == AVAHI_CLIENT_S_RUNNING) {
115 impl->browser = impl->table.service_browser_new(
116 client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, impl->serviceType.c_str(),
117 "local", AvahiLookupFlags::AVAHI_LOOKUP_USE_MULTICAST, BrowseCallback,
118 userdata);
119 }
120}
121
122void MulticastServiceResolver::Start() {
123 if (!pImpl->table.IsValid()) {
124 return;
125 }
126 std::scoped_lock lock{*pImpl->thread};
127 if (pImpl->client) {
128 return;
129 }
130
131 pImpl->client =
132 pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL,
133 ClientCallback, pImpl.get(), nullptr);
134}
135
136void MulticastServiceResolver::Stop() {
137 if (!pImpl->table.IsValid()) {
138 return;
139 }
140 std::scoped_lock lock{*pImpl->thread};
141 if (pImpl->client) {
142 if (pImpl->browser) {
143 pImpl->table.service_browser_free(pImpl->browser);
144 pImpl->browser = nullptr;
145 }
146 pImpl->table.client_free(pImpl->client);
147 pImpl->client = nullptr;
148 }
149}