blob: 1a7af394369752216102b1983061cb141445963a [file] [log] [blame]
// 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;
}
}