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