mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
[wpinet] Move network portions of wpiutil into new wpinet library (#4077)
This commit is contained in:
86
wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp
Normal file
86
wpinet/src/main/native/macOS/MulticastServiceAnnouncer.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 "wpinet/MulticastServiceAnnouncer.h"
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "dns_sd.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
struct MulticastServiceAnnouncer::Impl {
|
||||
std::string serviceName;
|
||||
std::string serviceType;
|
||||
int port;
|
||||
DNSServiceRef serviceRef{nullptr};
|
||||
TXTRecordRef txtRecord;
|
||||
|
||||
Impl() { TXTRecordCreate(&txtRecord, 0, nullptr); }
|
||||
|
||||
~Impl() noexcept { TXTRecordDeallocate(&txtRecord); }
|
||||
};
|
||||
|
||||
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>();
|
||||
pImpl->serviceName = serviceName;
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->port = port;
|
||||
|
||||
for (auto&& i : txt) {
|
||||
TXTRecordSetValue(&pImpl->txtRecord, i.first.c_str(), i.second.length(),
|
||||
i.second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
pImpl->serviceName = serviceName;
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->port = port;
|
||||
|
||||
wpi::SmallString<64> key;
|
||||
|
||||
for (auto&& i : txt) {
|
||||
key.clear();
|
||||
key.append(i.first);
|
||||
key.emplace_back('\0');
|
||||
|
||||
TXTRecordSetValue(&pImpl->txtRecord, key.data(), i.second.length(),
|
||||
i.second.data());
|
||||
}
|
||||
}
|
||||
|
||||
MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool MulticastServiceAnnouncer::HasImplementation() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MulticastServiceAnnouncer::Start() {
|
||||
if (pImpl->serviceRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t len = TXTRecordGetLength(&pImpl->txtRecord);
|
||||
const void* ptr = TXTRecordGetBytesPtr(&pImpl->txtRecord);
|
||||
|
||||
(void)DNSServiceRegister(&pImpl->serviceRef, 0, 0, pImpl->serviceName.c_str(),
|
||||
pImpl->serviceType.c_str(), "local", nullptr,
|
||||
htons(pImpl->port), len, ptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void MulticastServiceAnnouncer::Stop() {
|
||||
if (!pImpl->serviceRef) {
|
||||
return;
|
||||
}
|
||||
DNSServiceRefDeallocate(pImpl->serviceRef);
|
||||
pImpl->serviceRef = nullptr;
|
||||
}
|
||||
214
wpinet/src/main/native/macOS/MulticastServiceResolver.cpp
Normal file
214
wpinet/src/main/native/macOS/MulticastServiceResolver.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
// 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 "wpinet/MulticastServiceResolver.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "ResolverThread.h"
|
||||
#include "dns_sd.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
struct DnsResolveState {
|
||||
DnsResolveState(MulticastServiceResolver::Impl* impl,
|
||||
std::string_view serviceNameView)
|
||||
: pImpl{impl} {
|
||||
data.serviceName = serviceNameView;
|
||||
}
|
||||
|
||||
DNSServiceRef ResolveRef = nullptr;
|
||||
MulticastServiceResolver::Impl* pImpl;
|
||||
|
||||
MulticastServiceResolver::ServiceData data;
|
||||
};
|
||||
|
||||
struct MulticastServiceResolver::Impl {
|
||||
std::string serviceType;
|
||||
MulticastServiceResolver* resolver;
|
||||
std::shared_ptr<ResolverThread> thread = ResolverThread::Get();
|
||||
std::vector<std::unique_ptr<DnsResolveState>> ResolveStates;
|
||||
DNSServiceRef serviceRef = nullptr;
|
||||
|
||||
void onFound(ServiceData&& data) {
|
||||
resolver->PushData(std::forward<ServiceData>(data));
|
||||
}
|
||||
};
|
||||
|
||||
MulticastServiceResolver::MulticastServiceResolver(
|
||||
std::string_view serviceType) {
|
||||
pImpl = std::make_unique<Impl>();
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->resolver = this;
|
||||
}
|
||||
|
||||
MulticastServiceResolver::~MulticastServiceResolver() noexcept {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ServiceGetAddrInfoReply(DNSServiceRef sdRef, DNSServiceFlags flags,
|
||||
uint32_t interfaceIndex,
|
||||
DNSServiceErrorType errorCode,
|
||||
const char* hostname,
|
||||
const struct sockaddr* address, uint32_t ttl,
|
||||
void* context) {
|
||||
if (errorCode != kDNSServiceErr_NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
DnsResolveState* resolveState = static_cast<DnsResolveState*>(context);
|
||||
|
||||
resolveState->data.hostName = hostname;
|
||||
resolveState->data.ipv4Address =
|
||||
reinterpret_cast<const struct sockaddr_in*>(address)->sin_addr.s_addr;
|
||||
|
||||
resolveState->pImpl->onFound(std::move(resolveState->data));
|
||||
|
||||
resolveState->pImpl->thread->RemoveServiceRefInThread(
|
||||
resolveState->ResolveRef);
|
||||
|
||||
resolveState->pImpl->ResolveStates.erase(std::find_if(
|
||||
resolveState->pImpl->ResolveStates.begin(),
|
||||
resolveState->pImpl->ResolveStates.end(),
|
||||
[resolveState](auto& a) { return a.get() == resolveState; }));
|
||||
}
|
||||
|
||||
void ServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags,
|
||||
uint32_t interfaceIndex, DNSServiceErrorType errorCode,
|
||||
const char* fullname, const char* hosttarget,
|
||||
uint16_t port, /* In network byte order */
|
||||
uint16_t txtLen, const unsigned char* txtRecord,
|
||||
void* context) {
|
||||
if (errorCode != kDNSServiceErr_NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
DnsResolveState* resolveState = static_cast<DnsResolveState*>(context);
|
||||
resolveState->pImpl->thread->RemoveServiceRefInThread(
|
||||
resolveState->ResolveRef);
|
||||
DNSServiceRefDeallocate(resolveState->ResolveRef);
|
||||
resolveState->ResolveRef = nullptr;
|
||||
resolveState->data.port = ntohs(port);
|
||||
|
||||
int txtCount = TXTRecordGetCount(txtLen, txtRecord);
|
||||
char keyBuf[256];
|
||||
uint8_t valueLen;
|
||||
const void* value;
|
||||
|
||||
for (int i = 0; i < txtCount; i++) {
|
||||
errorCode = TXTRecordGetItemAtIndex(txtLen, txtRecord, i, sizeof(keyBuf),
|
||||
keyBuf, &valueLen, &value);
|
||||
if (errorCode == kDNSServiceErr_NoError) {
|
||||
if (valueLen == 0) {
|
||||
// No value
|
||||
resolveState->data.txt.emplace_back(
|
||||
std::pair<std::string, std::string>{std::string{keyBuf}, {}});
|
||||
} else {
|
||||
resolveState->data.txt.emplace_back(std::pair<std::string, std::string>{
|
||||
std::string{keyBuf},
|
||||
std::string{reinterpret_cast<const char*>(value), valueLen}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorCode = DNSServiceGetAddrInfo(
|
||||
&resolveState->ResolveRef, flags, interfaceIndex,
|
||||
kDNSServiceProtocol_IPv4, hosttarget, ServiceGetAddrInfoReply, context);
|
||||
|
||||
if (errorCode == kDNSServiceErr_NoError) {
|
||||
dnssd_sock_t socket = DNSServiceRefSockFD(resolveState->ResolveRef);
|
||||
resolveState->pImpl->thread->AddServiceRef(resolveState->ResolveRef,
|
||||
socket);
|
||||
} else {
|
||||
resolveState->pImpl->thread->RemoveServiceRefInThread(
|
||||
resolveState->ResolveRef);
|
||||
resolveState->pImpl->ResolveStates.erase(std::find_if(
|
||||
resolveState->pImpl->ResolveStates.begin(),
|
||||
resolveState->pImpl->ResolveStates.end(),
|
||||
[resolveState](auto& a) { return a.get() == resolveState; }));
|
||||
}
|
||||
}
|
||||
|
||||
static void DnsCompletion(DNSServiceRef sdRef, DNSServiceFlags flags,
|
||||
uint32_t interfaceIndex,
|
||||
DNSServiceErrorType errorCode,
|
||||
const char* serviceName, const char* regtype,
|
||||
const char* replyDomain, void* context) {
|
||||
if (errorCode != kDNSServiceErr_NoError) {
|
||||
return;
|
||||
}
|
||||
if (!(flags & kDNSServiceFlagsAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MulticastServiceResolver::Impl* impl =
|
||||
static_cast<MulticastServiceResolver::Impl*>(context);
|
||||
auto& resolveState = impl->ResolveStates.emplace_back(
|
||||
std::make_unique<DnsResolveState>(impl, serviceName));
|
||||
|
||||
errorCode = DNSServiceResolve(&resolveState->ResolveRef, 0, interfaceIndex,
|
||||
serviceName, regtype, replyDomain,
|
||||
ServiceResolveReply, resolveState.get());
|
||||
|
||||
if (errorCode == kDNSServiceErr_NoError) {
|
||||
dnssd_sock_t socket = DNSServiceRefSockFD(resolveState->ResolveRef);
|
||||
resolveState->pImpl->thread->AddServiceRef(resolveState->ResolveRef,
|
||||
socket);
|
||||
} else {
|
||||
resolveState->pImpl->ResolveStates.erase(std::find_if(
|
||||
resolveState->pImpl->ResolveStates.begin(),
|
||||
resolveState->pImpl->ResolveStates.end(),
|
||||
[r = resolveState.get()](auto& a) { return a.get() == r; }));
|
||||
}
|
||||
}
|
||||
|
||||
bool MulticastServiceResolver::HasImplementation() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MulticastServiceResolver::Start() {
|
||||
if (pImpl->serviceRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
DNSServiceErrorType status =
|
||||
DNSServiceBrowse(&pImpl->serviceRef, 0, 0, pImpl->serviceType.c_str(),
|
||||
"local", DnsCompletion, pImpl.get());
|
||||
if (status == kDNSServiceErr_NoError) {
|
||||
dnssd_sock_t socket = DNSServiceRefSockFD(pImpl->serviceRef);
|
||||
pImpl->thread->AddServiceRef(pImpl->serviceRef, socket);
|
||||
}
|
||||
}
|
||||
|
||||
void MulticastServiceResolver::Stop() {
|
||||
if (!pImpl->serviceRef) {
|
||||
return;
|
||||
}
|
||||
wpi::SmallVector<WPI_EventHandle, 8> cleanupEvents;
|
||||
for (auto&& i : pImpl->ResolveStates) {
|
||||
cleanupEvents.push_back(
|
||||
pImpl->thread->RemoveServiceRefOutsideThread(i->ResolveRef));
|
||||
}
|
||||
cleanupEvents.push_back(
|
||||
pImpl->thread->RemoveServiceRefOutsideThread(pImpl->serviceRef));
|
||||
wpi::SmallVector<WPI_Handle, 8> signaledBuf;
|
||||
signaledBuf.resize(cleanupEvents.size());
|
||||
while (!cleanupEvents.empty()) {
|
||||
auto signaled = wpi::WaitForObjects(cleanupEvents, signaledBuf);
|
||||
for (auto&& s : signaled) {
|
||||
cleanupEvents.erase(
|
||||
std::find(cleanupEvents.begin(), cleanupEvents.end(), s));
|
||||
}
|
||||
}
|
||||
|
||||
pImpl->ResolveStates.clear();
|
||||
pImpl->serviceRef = nullptr;
|
||||
}
|
||||
109
wpinet/src/main/native/macOS/ResolverThread.cpp
Normal file
109
wpinet/src/main/native/macOS/ResolverThread.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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 "ResolverThread.h"
|
||||
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
ResolverThread::ResolverThread(const private_init&) {}
|
||||
|
||||
ResolverThread::~ResolverThread() noexcept {
|
||||
running = false;
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ResolverThread::AddServiceRef(DNSServiceRef serviceRef,
|
||||
dnssd_sock_t socket) {
|
||||
std::scoped_lock lock{serviceRefMutex};
|
||||
serviceRefs.emplace_back(
|
||||
std::pair<DNSServiceRef, dnssd_sock_t>{serviceRef, socket});
|
||||
if (serviceRefs.size() == 1) {
|
||||
running = false;
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
running = true;
|
||||
thread = std::thread([=] { ThreadMain(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ResolverThread::RemoveServiceRefInThread(DNSServiceRef serviceRef) {
|
||||
std::scoped_lock lock{serviceRefMutex};
|
||||
serviceRefs.erase(
|
||||
std::find_if(serviceRefs.begin(), serviceRefs.end(),
|
||||
[=](auto& a) { return a.first == serviceRef; }));
|
||||
DNSServiceRefDeallocate(serviceRef);
|
||||
}
|
||||
|
||||
WPI_EventHandle ResolverThread::RemoveServiceRefOutsideThread(
|
||||
DNSServiceRef serviceRef) {
|
||||
std::scoped_lock lock{serviceRefMutex};
|
||||
WPI_EventHandle handle = CreateEvent(true);
|
||||
serviceRefsToRemove.push_back({serviceRef, handle});
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool ResolverThread::CleanupRefs() {
|
||||
std::scoped_lock lock{serviceRefMutex};
|
||||
for (auto&& r : serviceRefsToRemove) {
|
||||
serviceRefs.erase(
|
||||
std::find_if(serviceRefs.begin(), serviceRefs.end(),
|
||||
[=](auto& a) { return a.first == r.first; }));
|
||||
DNSServiceRefDeallocate(r.first);
|
||||
wpi::SetEvent(r.second);
|
||||
}
|
||||
serviceRefsToRemove.clear();
|
||||
return serviceRefs.empty();
|
||||
}
|
||||
|
||||
void ResolverThread::ThreadMain() {
|
||||
std::vector<pollfd> readSockets;
|
||||
std::vector<DNSServiceRef> serviceRefs;
|
||||
|
||||
while (running) {
|
||||
readSockets.clear();
|
||||
serviceRefs.clear();
|
||||
|
||||
for (auto&& i : this->serviceRefs) {
|
||||
readSockets.emplace_back(pollfd{i.second, POLLIN, 0});
|
||||
serviceRefs.emplace_back(i.first);
|
||||
}
|
||||
|
||||
int res = poll(readSockets.begin().base(), readSockets.size(), 100);
|
||||
|
||||
if (res > 0) {
|
||||
for (size_t i = 0; i < readSockets.size(); i++) {
|
||||
if (readSockets[i].revents == POLLIN) {
|
||||
DNSServiceProcessResult(serviceRefs[i]);
|
||||
}
|
||||
}
|
||||
} else if (res == 0) {
|
||||
if (!running) {
|
||||
CleanupRefs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (CleanupRefs()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static wpi::mutex ThreadLoopLock;
|
||||
static std::weak_ptr<ResolverThread> ThreadLoop;
|
||||
|
||||
std::shared_ptr<ResolverThread> ResolverThread::Get() {
|
||||
std::scoped_lock lock{ThreadLoopLock};
|
||||
auto locked = ThreadLoop.lock();
|
||||
if (!locked) {
|
||||
locked = std::make_unique<ResolverThread>(private_init{});
|
||||
ThreadLoop = locked;
|
||||
}
|
||||
return locked;
|
||||
}
|
||||
46
wpinet/src/main/native/macOS/ResolverThread.h
Normal file
46
wpinet/src/main/native/macOS/ResolverThread.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/Synchronization.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "dns_sd.h"
|
||||
|
||||
namespace wpi {
|
||||
class ResolverThread {
|
||||
private:
|
||||
struct private_init {};
|
||||
|
||||
public:
|
||||
explicit ResolverThread(const private_init&);
|
||||
~ResolverThread() noexcept;
|
||||
|
||||
void AddServiceRef(DNSServiceRef serviceRef, dnssd_sock_t socket);
|
||||
void RemoveServiceRefInThread(DNSServiceRef serviceRef);
|
||||
WPI_EventHandle RemoveServiceRefOutsideThread(DNSServiceRef serviceRef);
|
||||
|
||||
static std::shared_ptr<ResolverThread> Get();
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
bool CleanupRefs();
|
||||
|
||||
wpi::mutex serviceRefMutex;
|
||||
std::vector<std::pair<DNSServiceRef, WPI_EventHandle>> serviceRefsToRemove;
|
||||
std::vector<std::pair<DNSServiceRef, dnssd_sock_t>> serviceRefs;
|
||||
std::thread thread;
|
||||
std::atomic_bool running;
|
||||
};
|
||||
} // namespace wpi
|
||||
Reference in New Issue
Block a user