mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpiutil] Add mDNS resolver and announcer (#3733)
This commit is contained in:
@@ -6,11 +6,17 @@ FATAL: In-source builds are not allowed.
|
||||
")
|
||||
endif()
|
||||
|
||||
if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
|
||||
set(CMAKE_SYSTEM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
|
||||
set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
|
||||
endif()
|
||||
|
||||
project(allwpilib)
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
message(STATUS "Platform version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
|
||||
|
||||
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
INCLUDE(CPack)
|
||||
|
||||
@@ -77,6 +77,7 @@ generatedFileExclude {
|
||||
src/main/native/resources/
|
||||
src/test/native/cpp/llvm/
|
||||
src/main/native/windows/StackWalker
|
||||
src/main/native/linux/AvahiClient
|
||||
}
|
||||
|
||||
licenseUpdateExclude {
|
||||
|
||||
@@ -88,6 +88,8 @@ GENERATE_RESOURCES(src/main/native/resources generated/main/cpp WPI wpi wpiutil_
|
||||
file(GLOB_RECURSE wpiutil_native_src src/main/native/cpp/*.cpp)
|
||||
list(REMOVE_ITEM wpiutil_native_src ${wpiutil_jni_src})
|
||||
file(GLOB_RECURSE wpiutil_unix_src src/main/native/unix/*.cpp)
|
||||
file(GLOB_RECURSE wpiutil_linux_src src/main/native/linux/*.cpp)
|
||||
file(GLOB_RECURSE wpiutil_macos_src src/main/native/macOS/*.cpp)
|
||||
file(GLOB_RECURSE wpiutil_windows_src src/main/native/windows/*.cpp)
|
||||
|
||||
file(GLOB fmtlib_native_src src/main/native/fmtlib/src/*.cpp)
|
||||
@@ -189,6 +191,11 @@ if (MSVC)
|
||||
target_sources(wpiutil PRIVATE ${wpiutil_windows_src})
|
||||
else ()
|
||||
target_sources(wpiutil PRIVATE ${wpiutil_unix_src})
|
||||
if (APPLE)
|
||||
target_sources(wpiutil PRIVATE ${wpiutil_macos_src})
|
||||
else()
|
||||
target_sources(wpiutil PRIVATE ${wpiutil_linux_src})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(wpiutil PUBLIC
|
||||
|
||||
@@ -129,6 +129,16 @@ ext {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/libuv/include', 'src/main/native/libuv/src'
|
||||
}
|
||||
}
|
||||
wpiutilmacOSCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/macOS'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it.sources {
|
||||
@@ -148,6 +158,16 @@ ext {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/libuv/include', 'src/main/native/libuv/src'
|
||||
}
|
||||
}
|
||||
wpiutilLinuxCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/linux'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include', 'src/main/native/cpp', 'src/main/native/fmtlib/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** Class to announce over mDNS that a service is available. */
|
||||
public class MulticastServiceAnnouncer implements AutoCloseable {
|
||||
private final int m_handle;
|
||||
|
||||
/**
|
||||
* Creates a MulticastServiceAnnouncer.
|
||||
*
|
||||
* @param serviceName service name
|
||||
* @param serviceType service type
|
||||
* @param port port
|
||||
* @param txt txt
|
||||
*/
|
||||
public MulticastServiceAnnouncer(
|
||||
String serviceName, String serviceType, int port, Map<String, String> txt) {
|
||||
String[] keys = txt.keySet().toArray(String[]::new);
|
||||
String[] values = txt.values().toArray(String[]::new);
|
||||
m_handle =
|
||||
WPIUtilJNI.createMulticastServiceAnnouncer(serviceName, serviceType, port, keys, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
WPIUtilJNI.freeMulticastServiceAnnouncer(m_handle);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
WPIUtilJNI.startMulticastServiceAnnouncer(m_handle);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
WPIUtilJNI.stopMulticastServiceAnnouncer(m_handle);
|
||||
}
|
||||
|
||||
public boolean hasImplementation() {
|
||||
return WPIUtilJNI.getMulticastServiceAnnouncerHasImplementation(m_handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
/** Class to resolve a service over mDNS. */
|
||||
public class MulticastServiceResolver implements AutoCloseable {
|
||||
private final int m_handle;
|
||||
|
||||
/**
|
||||
* Creates a MulticastServiceResolver.
|
||||
*
|
||||
* @param serviceType service type to look for
|
||||
*/
|
||||
public MulticastServiceResolver(String serviceType) {
|
||||
m_handle = WPIUtilJNI.createMulticastServiceResolver(serviceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
WPIUtilJNI.freeMulticastServiceResolver(m_handle);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
WPIUtilJNI.startMulticastServiceResolver(m_handle);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
WPIUtilJNI.stopMulticastServiceResolver(m_handle);
|
||||
}
|
||||
|
||||
public boolean hasImplementation() {
|
||||
return WPIUtilJNI.getMulticastServiceResolverHasImplementation(m_handle);
|
||||
}
|
||||
|
||||
public int getEventHandle() {
|
||||
return WPIUtilJNI.getMulticastServiceResolverEventHandle(m_handle);
|
||||
}
|
||||
|
||||
public ServiceData getData() {
|
||||
return WPIUtilJNI.getMulticastServiceResolverData(m_handle);
|
||||
}
|
||||
}
|
||||
66
wpiutil/src/main/java/edu/wpi/first/util/ServiceData.java
Normal file
66
wpiutil/src/main/java/edu/wpi/first/util/ServiceData.java
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Service data for MulticastServiceResolver. */
|
||||
public class ServiceData {
|
||||
/**
|
||||
* Constructs a ServiceData.
|
||||
*
|
||||
* @param ipv4Address ipv4Address
|
||||
* @param port port
|
||||
* @param serviceName found service name
|
||||
* @param hostName found host name
|
||||
* @param keys txt keys
|
||||
* @param values txt values
|
||||
*/
|
||||
public ServiceData(
|
||||
long ipv4Address,
|
||||
int port,
|
||||
String serviceName,
|
||||
String hostName,
|
||||
String[] keys,
|
||||
String[] values) {
|
||||
this.m_serviceName = serviceName;
|
||||
this.m_hostName = hostName;
|
||||
this.m_port = port;
|
||||
this.m_ipv4Address = ipv4Address;
|
||||
|
||||
m_txt = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
m_txt.put(keys[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getTxt() {
|
||||
return m_txt;
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
return m_hostName;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return m_serviceName;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return m_port;
|
||||
}
|
||||
|
||||
public long getIpv4Address() {
|
||||
return m_ipv4Address;
|
||||
}
|
||||
|
||||
private final Map<String, String> m_txt;
|
||||
private final long m_ipv4Address;
|
||||
private final int m_port;
|
||||
private final String m_serviceName;
|
||||
private final String m_hostName;
|
||||
}
|
||||
@@ -122,4 +122,29 @@ public final class WPIUtilJNI {
|
||||
*/
|
||||
public static native int[] waitForObjectsTimeout(int[] handles, double timeout)
|
||||
throws InterruptedException;
|
||||
|
||||
public static native int createMulticastServiceAnnouncer(
|
||||
String serviceName, String serviceType, int port, String[] keys, String[] values);
|
||||
|
||||
public static native void freeMulticastServiceAnnouncer(int handle);
|
||||
|
||||
public static native void startMulticastServiceAnnouncer(int handle);
|
||||
|
||||
public static native void stopMulticastServiceAnnouncer(int handle);
|
||||
|
||||
public static native boolean getMulticastServiceAnnouncerHasImplementation(int handle);
|
||||
|
||||
public static native int createMulticastServiceResolver(String serviceType);
|
||||
|
||||
public static native void freeMulticastServiceResolver(int handle);
|
||||
|
||||
public static native void startMulticastServiceResolver(int handle);
|
||||
|
||||
public static native void stopMulticastServiceResolver(int handle);
|
||||
|
||||
public static native boolean getMulticastServiceResolverHasImplementation(int handle);
|
||||
|
||||
public static native int getMulticastServiceResolverEventHandle(int handle);
|
||||
|
||||
public static native ServiceData getMulticastServiceResolverData(int handle);
|
||||
}
|
||||
|
||||
12
wpiutil/src/main/native/cpp/MulticastHandleManager.cpp
Normal file
12
wpiutil/src/main/native/cpp/MulticastHandleManager.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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 "MulticastHandleManager.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
MulticastHandleManager& wpi::GetMulticastManager() {
|
||||
static MulticastHandleManager manager;
|
||||
return manager;
|
||||
}
|
||||
25
wpiutil/src/main/native/cpp/MulticastHandleManager.h
Normal file
25
wpiutil/src/main/native/cpp/MulticastHandleManager.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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 <memory>
|
||||
|
||||
#include "wpi/DenseMap.h"
|
||||
#include "wpi/MulticastServiceAnnouncer.h"
|
||||
#include "wpi/MulticastServiceResolver.h"
|
||||
#include "wpi/UidVector.h"
|
||||
|
||||
namespace wpi {
|
||||
struct MulticastHandleManager {
|
||||
wpi::mutex mutex;
|
||||
wpi::UidVector<int, 8> handleIds;
|
||||
wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceResolver>>
|
||||
resolvers;
|
||||
wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceAnnouncer>>
|
||||
announcers;
|
||||
};
|
||||
|
||||
MulticastHandleManager& GetMulticastManager();
|
||||
} // namespace wpi
|
||||
67
wpiutil/src/main/native/cpp/MulticastServiceAnnouncer.cpp
Normal file
67
wpiutil/src/main/native/cpp/MulticastServiceAnnouncer.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 <wpi/SmallVector.h>
|
||||
|
||||
#include "MulticastHandleManager.h"
|
||||
|
||||
extern "C" {
|
||||
WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
|
||||
const char* serviceName, const char* serviceType, int32_t port,
|
||||
int32_t txtCount, const char** keys, const char** values)
|
||||
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
|
||||
wpi::SmallVector<std::pair<std::string_view, std::string_view>, 8> txts;
|
||||
|
||||
for (int32_t i = 0; i < txtCount; i++) {
|
||||
txts.emplace_back(
|
||||
std::pair<std::string_view, std::string_view>{keys[i], values[i]});
|
||||
}
|
||||
|
||||
auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
|
||||
serviceName, serviceType, port, txts);
|
||||
|
||||
size_t index = manager.handleIds.emplace_back(3);
|
||||
manager.announcers[index] = std::move(announcer);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void WPI_FreeMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
manager.announcers[handle] = nullptr;
|
||||
manager.handleIds.erase(handle);
|
||||
}
|
||||
|
||||
void WPI_StartMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
announcer->Start();
|
||||
}
|
||||
|
||||
void WPI_StopMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
announcer->Stop();
|
||||
}
|
||||
|
||||
int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
|
||||
WPI_MulticastServiceAnnouncerHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
return announcer->HasImplementation();
|
||||
}
|
||||
} // extern "C"
|
||||
134
wpiutil/src/main/native/cpp/MulticastServiceResolver.cpp
Normal file
134
wpiutil/src/main/native/cpp/MulticastServiceResolver.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
// 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/MulticastServiceResolver.h"
|
||||
|
||||
#include "MulticastHandleManager.h"
|
||||
#include "wpi/MemAlloc.h"
|
||||
|
||||
extern "C" {
|
||||
WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
|
||||
const char* serviceType)
|
||||
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
|
||||
auto resolver = std::make_unique<wpi::MulticastServiceResolver>(serviceType);
|
||||
|
||||
size_t index = manager.handleIds.emplace_back(2);
|
||||
manager.resolvers[index] = std::move(resolver);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void WPI_FreeMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
manager.resolvers[handle] = nullptr;
|
||||
manager.handleIds.erase(handle);
|
||||
}
|
||||
|
||||
void WPI_StartMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
resolver->Start();
|
||||
}
|
||||
|
||||
void WPI_StopMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
resolver->Stop();
|
||||
}
|
||||
|
||||
int32_t WPI_GetMulticastServiceResolverHasImplementation(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
return resolver->HasImplementation();
|
||||
}
|
||||
|
||||
WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
return resolver->GetEventHandle();
|
||||
}
|
||||
|
||||
WPI_ServiceData* WPI_GetMulticastServiceResolverData(
|
||||
WPI_MulticastServiceResolverHandle handle) {
|
||||
wpi::MulticastServiceResolver::ServiceData data;
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
data = resolver->GetData();
|
||||
}
|
||||
size_t allocSize = sizeof(WPI_ServiceData);
|
||||
// Include space for hostName and serviceType (+ terminators)
|
||||
allocSize += data.hostName.size() + data.serviceName.size() + 2;
|
||||
|
||||
size_t keysTotalLength = 0;
|
||||
size_t valuesTotalLength = 0;
|
||||
// Include space for all keys and values, and pointer array
|
||||
for (auto&& t : data.txt) {
|
||||
allocSize += sizeof(const char*);
|
||||
keysTotalLength += (t.first.size() + 1);
|
||||
allocSize += sizeof(const char*);
|
||||
valuesTotalLength += (t.second.size() + 1);
|
||||
}
|
||||
allocSize += keysTotalLength;
|
||||
allocSize += valuesTotalLength;
|
||||
uint8_t* cDataRaw = reinterpret_cast<uint8_t*>(wpi::safe_malloc(allocSize));
|
||||
if (!cDataRaw) {
|
||||
return nullptr;
|
||||
}
|
||||
WPI_ServiceData* cData = reinterpret_cast<WPI_ServiceData*>(cDataRaw);
|
||||
cData->ipv4Address = data.ipv4Address;
|
||||
cData->port = data.port;
|
||||
cData->txtCount = data.txt.size();
|
||||
cDataRaw += sizeof(WPI_ServiceData);
|
||||
|
||||
std::memcpy(cDataRaw, data.hostName.c_str(), data.hostName.size() + 1);
|
||||
cData->hostName = reinterpret_cast<const char*>(cDataRaw);
|
||||
cDataRaw += data.hostName.size() + 1;
|
||||
|
||||
std::memcpy(cDataRaw, data.serviceName.c_str(), data.serviceName.size() + 1);
|
||||
cData->serviceName = reinterpret_cast<const char*>(cDataRaw);
|
||||
cDataRaw += data.serviceName.size() + 1;
|
||||
|
||||
char** valuesPtrArr = reinterpret_cast<char**>(cDataRaw);
|
||||
cDataRaw += (sizeof(char**) * data.txt.size());
|
||||
char** keysPtrArr = reinterpret_cast<char**>(cDataRaw);
|
||||
cDataRaw += (sizeof(char**) * data.txt.size());
|
||||
|
||||
cData->txtKeys = const_cast<const char**>(keysPtrArr);
|
||||
cData->txtValues = const_cast<const char**>(valuesPtrArr);
|
||||
|
||||
for (size_t i = 0; i < data.txt.size(); i++) {
|
||||
keysPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
|
||||
std::memcpy(keysPtrArr[i], data.txt[i].first.c_str(),
|
||||
data.txt[i].first.size() + 1);
|
||||
cDataRaw += (data.txt[i].first.size() + 1);
|
||||
|
||||
valuesPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
|
||||
std::memcpy(valuesPtrArr[i], data.txt[i].second.c_str(),
|
||||
data.txt[i].second.size() + 1);
|
||||
cDataRaw += (data.txt[i].second.size() + 1);
|
||||
}
|
||||
|
||||
return cData;
|
||||
}
|
||||
|
||||
void WPI_FreeServiceData(WPI_ServiceData* serviceData) {
|
||||
std::free(serviceData);
|
||||
}
|
||||
} // extern "C"
|
||||
@@ -4,9 +4,14 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "../MulticastHandleManager.h"
|
||||
#include "edu_wpi_first_util_WPIUtilJNI.h"
|
||||
#include "wpi/DenseMap.h"
|
||||
#include "wpi/MulticastServiceAnnouncer.h"
|
||||
#include "wpi/MulticastServiceResolver.h"
|
||||
#include "wpi/PortForwarder.h"
|
||||
#include "wpi/Synchronization.h"
|
||||
#include "wpi/UidVector.h"
|
||||
#include "wpi/jni_util.h"
|
||||
#include "wpi/timestamp.h"
|
||||
|
||||
@@ -16,6 +21,7 @@ static bool mockTimeEnabled = false;
|
||||
static uint64_t mockNow = 0;
|
||||
|
||||
static JException interruptedEx;
|
||||
static JClass serviceDataCls;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -30,6 +36,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
serviceDataCls = JClass{env, "edu/wpi/first/util/ServiceData"};
|
||||
if (!serviceDataCls) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
@@ -39,6 +50,7 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||
return;
|
||||
}
|
||||
interruptedEx.free(env);
|
||||
serviceDataCls.free(env);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -273,4 +285,238 @@ Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectsTimeout
|
||||
return MakeJIntArray(env, signaled);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: createMulticastServiceAnnouncer
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;[Ljava/lang/Object;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceAnnouncer
|
||||
(JNIEnv* env, jclass, jstring serviceName, jstring serviceType, jint port,
|
||||
jobjectArray keys, jobjectArray values)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
|
||||
JStringRef serviceNameRef{env, serviceName};
|
||||
JStringRef serviceTypeRef{env, serviceType};
|
||||
|
||||
size_t keysLen = env->GetArrayLength(keys);
|
||||
wpi::SmallVector<std::pair<std::string, std::string>, 8> txtVec;
|
||||
txtVec.reserve(keysLen);
|
||||
for (size_t i = 0; i < keysLen; i++) {
|
||||
JLocal<jstring> key{
|
||||
env, static_cast<jstring>(env->GetObjectArrayElement(keys, i))};
|
||||
JLocal<jstring> value{
|
||||
env, static_cast<jstring>(env->GetObjectArrayElement(values, i))};
|
||||
|
||||
txtVec.emplace_back(std::pair<std::string, std::string>{
|
||||
JStringRef{env, key}.str(), JStringRef{env, value}.str()});
|
||||
}
|
||||
|
||||
auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
|
||||
serviceNameRef.str(), serviceTypeRef.str(), port, txtVec);
|
||||
|
||||
size_t index = manager.handleIds.emplace_back(1);
|
||||
|
||||
manager.announcers[index] = std::move(announcer);
|
||||
|
||||
return static_cast<jint>(index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: freeMulticastServiceAnnouncer
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceAnnouncer
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
manager.announcers[handle] = nullptr;
|
||||
manager.handleIds.erase(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: startMulticastServiceAnnouncer
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceAnnouncer
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
announcer->Start();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: stopMulticastServiceAnnouncer
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceAnnouncer
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
announcer->Stop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: getMulticastServiceAnnouncerHasImplementation
|
||||
* Signature: (I)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceAnnouncerHasImplementation
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& announcer = manager.announcers[handle];
|
||||
return announcer->HasImplementation();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: createMulticastServiceResolver
|
||||
* Signature: (Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceResolver
|
||||
(JNIEnv* env, jclass, jstring serviceType)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
JStringRef serviceTypeRef{env, serviceType};
|
||||
|
||||
auto resolver =
|
||||
std::make_unique<wpi::MulticastServiceResolver>(serviceTypeRef.str());
|
||||
|
||||
size_t index = manager.handleIds.emplace_back(2);
|
||||
|
||||
manager.resolvers[index] = std::move(resolver);
|
||||
|
||||
return static_cast<jint>(index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: freeMulticastServiceResolver
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceResolver
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
manager.resolvers[handle] = nullptr;
|
||||
manager.handleIds.erase(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: startMulticastServiceResolver
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceResolver
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
resolver->Start();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: stopMulticastServiceResolver
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceResolver
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
resolver->Stop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: getMulticastServiceResolverHasImplementation
|
||||
* Signature: (I)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverHasImplementation
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
return resolver->HasImplementation();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: getMulticastServiceResolverEventHandle
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverEventHandle
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
return resolver->GetEventHandle();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: getMulticastServiceResolverData
|
||||
* Signature: (I)Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverData
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(serviceDataCls, "<init>",
|
||||
"(JILjava/lang/String;Ljava/lang/String;[Ljava/lang/"
|
||||
"String;[Ljava/lang/String;)V");
|
||||
auto& manager = wpi::GetMulticastManager();
|
||||
std::scoped_lock lock{manager.mutex};
|
||||
auto& resolver = manager.resolvers[handle];
|
||||
auto data = resolver->GetData();
|
||||
JLocal<jstring> serviceName{env, MakeJString(env, data.serviceName)};
|
||||
JLocal<jstring> hostName{env, MakeJString(env, data.hostName)};
|
||||
|
||||
wpi::SmallVector<std::string_view, 8> keysRef;
|
||||
wpi::SmallVector<std::string_view, 8> valuesRef;
|
||||
|
||||
for (auto&& txt : data.txt) {
|
||||
keysRef.emplace_back(txt.first);
|
||||
valuesRef.emplace_back(txt.second);
|
||||
}
|
||||
|
||||
JLocal<jobjectArray> keys{env, MakeJStringArray(env, keysRef)};
|
||||
JLocal<jobjectArray> values{env, MakeJStringArray(env, valuesRef)};
|
||||
|
||||
return env->NewObject(serviceDataCls, constructor,
|
||||
static_cast<jlong>(data.ipv4Address),
|
||||
static_cast<jint>(data.port), serviceName.obj(),
|
||||
hostName.obj(), keys.obj(), values.obj());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/span.h"
|
||||
namespace wpi {
|
||||
class MulticastServiceAnnouncer {
|
||||
public:
|
||||
MulticastServiceAnnouncer(
|
||||
std::string_view serviceName, std::string_view serviceType, int port,
|
||||
wpi::span<const std::pair<std::string, std::string>> txt);
|
||||
MulticastServiceAnnouncer(
|
||||
std::string_view serviceName, std::string_view serviceType, int port,
|
||||
wpi::span<const std::pair<std::string_view, std::string_view>> txt);
|
||||
~MulticastServiceAnnouncer() noexcept;
|
||||
void Start();
|
||||
void Stop();
|
||||
bool HasImplementation() const;
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Impl> pImpl;
|
||||
};
|
||||
} // namespace wpi
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef unsigned int WPI_MulticastServiceAnnouncerHandle; // NOLINT
|
||||
|
||||
WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
|
||||
const char* serviceName, const char* serviceType, int32_t port,
|
||||
int32_t txtCount, const char** keys, const char** values);
|
||||
|
||||
void WPI_FreeMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle);
|
||||
|
||||
void WPI_StartMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle);
|
||||
|
||||
void WPI_StopMulticastServiceAnnouncer(
|
||||
WPI_MulticastServiceAnnouncerHandle handle);
|
||||
|
||||
int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
|
||||
WPI_MulticastServiceAnnouncerHandle handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
// 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 "wpi/Synchronization.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/ConcurrentQueue.h"
|
||||
#include "wpi/span.h"
|
||||
namespace wpi {
|
||||
class MulticastServiceResolver {
|
||||
public:
|
||||
explicit MulticastServiceResolver(std::string_view serviceType);
|
||||
~MulticastServiceResolver() noexcept;
|
||||
struct ServiceData {
|
||||
unsigned int ipv4Address;
|
||||
int port;
|
||||
std::string serviceName;
|
||||
std::string hostName;
|
||||
std::vector<std::pair<std::string, std::string>> txt;
|
||||
};
|
||||
void Start();
|
||||
void Stop();
|
||||
WPI_EventHandle GetEventHandle() { return event.GetHandle(); }
|
||||
ServiceData GetData() { return eventQueue.pop(); }
|
||||
bool HasImplementation() const;
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
wpi::Event event;
|
||||
wpi::ConcurrentQueue<ServiceData> eventQueue;
|
||||
std::unique_ptr<Impl> pImpl;
|
||||
};
|
||||
} // namespace wpi
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef unsigned int WPI_MulticastServiceResolverHandle; // NOLINT
|
||||
|
||||
WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
|
||||
const char* serviceType);
|
||||
|
||||
void WPI_FreeMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
void WPI_StartMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
void WPI_StopMulticastServiceResolver(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
int32_t WPI_GetMulticastServiceResolverHasImplementation(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
typedef struct WPI_ServiceData { // NOLINT
|
||||
uint32_t ipv4Address;
|
||||
int32_t port;
|
||||
const char* serviceName;
|
||||
const char* hostName;
|
||||
int32_t txtCount;
|
||||
const char** txtKeys;
|
||||
const char** txtValues;
|
||||
} WPI_ServiceData;
|
||||
|
||||
WPI_ServiceData* WPI_GetMulticastServiceResolverData(
|
||||
WPI_MulticastServiceResolverHandle handle);
|
||||
|
||||
void WPI_FreeServiceData(WPI_ServiceData* serviceData);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
@@ -566,6 +566,28 @@ inline jobjectArray MakeJStringArray(JNIEnv* env, span<const std::string> arr) {
|
||||
return jarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of std::string into a jarray of jstring.
|
||||
*
|
||||
* @param env JRE environment.
|
||||
* @param arr Array to convert.
|
||||
*/
|
||||
inline jobjectArray MakeJStringArray(JNIEnv* env, span<std::string_view> arr) {
|
||||
static JClass stringCls{env, "java/lang/String"};
|
||||
if (!stringCls) {
|
||||
return nullptr;
|
||||
}
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), stringCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
JLocal<jstring> elem{env, MakeJString(env, arr[i])};
|
||||
env->SetObjectArrayElement(jarr, i, elem.obj());
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic callback thread implementation.
|
||||
*
|
||||
|
||||
115
wpiutil/src/main/native/linux/AvahiClient.cpp
Normal file
115
wpiutil/src/main/native/linux/AvahiClient.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 "AvahiClient.h"
|
||||
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "dlfcn.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
#define AvahiFunctionLoad(snake_name) \
|
||||
do { \
|
||||
snake_name = \
|
||||
reinterpret_cast<snake_name##_func>(dlsym(lib, "avahi_" #snake_name)); \
|
||||
if (!snake_name) { \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
AvahiFunctionTable::AvahiFunctionTable() {
|
||||
void* lib = dlopen("libavahi-common.so.3", RTLD_LAZY);
|
||||
|
||||
valid = false;
|
||||
|
||||
if (lib == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvahiFunctionLoad(threaded_poll_new);
|
||||
AvahiFunctionLoad(threaded_poll_free);
|
||||
AvahiFunctionLoad(threaded_poll_get);
|
||||
AvahiFunctionLoad(threaded_poll_start);
|
||||
AvahiFunctionLoad(threaded_poll_stop);
|
||||
AvahiFunctionLoad(threaded_poll_lock);
|
||||
AvahiFunctionLoad(threaded_poll_unlock);
|
||||
AvahiFunctionLoad(string_list_new_from_array);
|
||||
AvahiFunctionLoad(string_list_free);
|
||||
AvahiFunctionLoad(unescape_label);
|
||||
|
||||
lib = dlopen("libavahi-client.so.3", RTLD_LAZY);
|
||||
|
||||
if (lib == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AvahiFunctionLoad(client_new);
|
||||
AvahiFunctionLoad(client_free);
|
||||
AvahiFunctionLoad(service_browser_new);
|
||||
AvahiFunctionLoad(service_browser_get_client);
|
||||
AvahiFunctionLoad(service_browser_free);
|
||||
AvahiFunctionLoad(service_resolver_new);
|
||||
AvahiFunctionLoad(service_resolver_free);
|
||||
AvahiFunctionLoad(entry_group_new);
|
||||
AvahiFunctionLoad(entry_group_free);
|
||||
AvahiFunctionLoad(entry_group_add_service_strlst);
|
||||
AvahiFunctionLoad(entry_group_reset);
|
||||
AvahiFunctionLoad(entry_group_is_empty);
|
||||
AvahiFunctionLoad(entry_group_commit);
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
AvahiFunctionTable& AvahiFunctionTable::Get() {
|
||||
static AvahiFunctionTable table;
|
||||
return table;
|
||||
}
|
||||
|
||||
static wpi::mutex ThreadLoopLock;
|
||||
static std::weak_ptr<AvahiThread> ThreadLoop;
|
||||
|
||||
std::shared_ptr<AvahiThread> AvahiThread::Get() {
|
||||
std::scoped_lock lock{ThreadLoopLock};
|
||||
auto locked = ThreadLoop.lock();
|
||||
if (!locked) {
|
||||
locked = std::make_unique<AvahiThread>(private_init{});
|
||||
ThreadLoop = locked;
|
||||
}
|
||||
return locked;
|
||||
}
|
||||
|
||||
AvahiThread::AvahiThread(const private_init&) {
|
||||
if (!table.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
threadedPoll = table.threaded_poll_new();
|
||||
table.threaded_poll_start(threadedPoll);
|
||||
}
|
||||
|
||||
AvahiThread::~AvahiThread() noexcept {
|
||||
if (!table.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadedPoll) {
|
||||
table.threaded_poll_stop(threadedPoll);
|
||||
table.threaded_poll_free(threadedPoll);
|
||||
}
|
||||
}
|
||||
|
||||
void AvahiThread::lock() {
|
||||
table.threaded_poll_lock(threadedPoll);
|
||||
}
|
||||
|
||||
void AvahiThread::unlock() {
|
||||
table.threaded_poll_unlock(threadedPoll);
|
||||
}
|
||||
|
||||
const AvahiPoll* AvahiThread::GetPoll() const {
|
||||
return table.threaded_poll_get(threadedPoll);
|
||||
}
|
||||
249
wpiutil/src/main/native/linux/AvahiClient.h
Normal file
249
wpiutil/src/main/native/linux/AvahiClient.h
Normal file
@@ -0,0 +1,249 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/***
|
||||
This file is part of avahi.
|
||||
avahi is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation; either version 2.1 of the
|
||||
License, or (at your option) any later version.
|
||||
avahi is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
|
||||
Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with avahi; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
USA.
|
||||
***/
|
||||
|
||||
typedef struct AvahiPoll AvahiPoll;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_SERVER_INVALID,
|
||||
AVAHI_SERVER_REGISTERING,
|
||||
AVAHI_SERVER_RUNNING,
|
||||
AVAHI_SERVER_COLLISION,
|
||||
AVAHI_SERVER_FAILURE
|
||||
} AvahiServerState;
|
||||
|
||||
typedef struct AvahiClient AvahiClient;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_CLIENT_S_REGISTERING = AVAHI_SERVER_REGISTERING,
|
||||
AVAHI_CLIENT_S_RUNNING = AVAHI_SERVER_RUNNING,
|
||||
AVAHI_CLIENT_S_COLLISION = AVAHI_SERVER_COLLISION,
|
||||
AVAHI_CLIENT_FAILURE = 100,
|
||||
AVAHI_CLIENT_CONNECTING = 101
|
||||
} AvahiClientState;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_CLIENT_IGNORE_USER_CONFIG = 1,
|
||||
AVAHI_CLIENT_NO_FAIL = 2
|
||||
} AvahiClientFlags;
|
||||
|
||||
typedef void (*AvahiClientCallback)(AvahiClient* s, AvahiClientState state,
|
||||
void* userdata);
|
||||
|
||||
typedef struct AvahiServiceBrowser AvahiServiceBrowser;
|
||||
|
||||
typedef int AvahiProtocol;
|
||||
|
||||
typedef int AvahiIfIndex;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_BROWSER_NEW,
|
||||
AVAHI_BROWSER_REMOVE,
|
||||
AVAHI_BROWSER_CACHE_EXHAUSTED,
|
||||
AVAHI_BROWSER_ALL_FOR_NOW,
|
||||
AVAHI_BROWSER_FAILURE
|
||||
} AvahiBrowserEvent;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_LOOKUP_RESULT_CACHED = 1,
|
||||
AVAHI_LOOKUP_RESULT_WIDE_AREA = 2,
|
||||
AVAHI_LOOKUP_RESULT_MULTICAST = 4,
|
||||
AVAHI_LOOKUP_RESULT_LOCAL = 8,
|
||||
AVAHI_LOOKUP_RESULT_OUR_OWN = 16,
|
||||
AVAHI_LOOKUP_RESULT_STATIC = 32
|
||||
} AvahiLookupResultFlags;
|
||||
|
||||
typedef void (*AvahiServiceBrowserCallback)(
|
||||
AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol,
|
||||
AvahiBrowserEvent event, const char* name, const char* type,
|
||||
const char* domain, AvahiLookupResultFlags flags, void* userdata);
|
||||
|
||||
typedef enum {
|
||||
AVAHI_LOOKUP_USE_WIDE_AREA = 1,
|
||||
AVAHI_LOOKUP_USE_MULTICAST = 2,
|
||||
|
||||
AVAHI_LOOKUP_NO_TXT = 4,
|
||||
AVAHI_LOOKUP_NO_ADDRESS = 8
|
||||
} AvahiLookupFlags;
|
||||
|
||||
typedef struct AvahiServiceResolver AvahiServiceResolver;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_RESOLVER_FOUND,
|
||||
AVAHI_RESOLVER_FAILURE
|
||||
} AvahiResolverEvent;
|
||||
|
||||
typedef struct AvahiIPv4Address {
|
||||
uint32_t address;
|
||||
} AvahiIPv4Address;
|
||||
|
||||
typedef struct AvahiIPv6Address {
|
||||
uint8_t address[16];
|
||||
} AvahiIPv6Address;
|
||||
|
||||
typedef struct AvahiAddress {
|
||||
AvahiProtocol proto;
|
||||
|
||||
union {
|
||||
AvahiIPv6Address ipv6;
|
||||
AvahiIPv4Address ipv4;
|
||||
uint8_t data[1];
|
||||
} data;
|
||||
} AvahiAddress;
|
||||
|
||||
typedef struct AvahiStringList {
|
||||
struct AvahiStringList* next;
|
||||
size_t size;
|
||||
uint8_t text[1];
|
||||
} AvahiStringList;
|
||||
|
||||
typedef void (*AvahiServiceResolverCallback)(
|
||||
AvahiServiceResolver* r, AvahiIfIndex interface, AvahiProtocol protocol,
|
||||
AvahiResolverEvent event, const char* name, const char* type,
|
||||
const char* domain, const char* host_name, const AvahiAddress* a,
|
||||
uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
|
||||
void* userdata);
|
||||
|
||||
typedef struct AvahiThreadedPoll AvahiThreadedPoll;
|
||||
|
||||
typedef struct AvahiEntryGroup AvahiEntryGroup;
|
||||
|
||||
typedef enum {
|
||||
AVAHI_ENTRY_GROUP_UNCOMMITED,
|
||||
AVAHI_ENTRY_GROUP_REGISTERING,
|
||||
AVAHI_ENTRY_GROUP_ESTABLISHED,
|
||||
AVAHI_ENTRY_GROUP_COLLISION,
|
||||
AVAHI_ENTRY_GROUP_FAILURE
|
||||
} AvahiEntryGroupState;
|
||||
|
||||
typedef void (*AvahiEntryGroupCallback)(AvahiEntryGroup* g,
|
||||
AvahiEntryGroupState state,
|
||||
void* userdata);
|
||||
|
||||
typedef enum {
|
||||
AVAHI_PUBLISH_UNIQUE = 1,
|
||||
AVAHI_PUBLISH_NO_PROBE = 2,
|
||||
AVAHI_PUBLISH_NO_ANNOUNCE = 4,
|
||||
AVAHI_PUBLISH_ALLOW_MULTIPLE = 8,
|
||||
|
||||
AVAHI_PUBLISH_NO_REVERSE = 16,
|
||||
AVAHI_PUBLISH_NO_COOKIE = 32,
|
||||
AVAHI_PUBLISH_UPDATE = 64,
|
||||
AVAHI_PUBLISH_USE_WIDE_AREA = 128,
|
||||
AVAHI_PUBLISH_USE_MULTICAST = 256
|
||||
} AvahiPublishFlags;
|
||||
|
||||
enum { AVAHI_IF_UNSPEC = -1 };
|
||||
|
||||
enum { AVAHI_PROTO_INET = 0, AVAHI_PROTO_INET6 = 1, AVAHI_PROTO_UNSPEC = -1 };
|
||||
|
||||
namespace wpi {
|
||||
class AvahiFunctionTable {
|
||||
public:
|
||||
#define AvahiFunction(CapName, RetType, Parameters) \
|
||||
using CapName##_func = RetType(*) Parameters; \
|
||||
CapName##_func CapName = nullptr
|
||||
|
||||
AvahiFunction(threaded_poll_new, AvahiThreadedPoll*, (void));
|
||||
AvahiFunction(threaded_poll_free, void, (AvahiThreadedPoll*));
|
||||
AvahiFunction(threaded_poll_get, const AvahiPoll*, (AvahiThreadedPoll*));
|
||||
AvahiFunction(threaded_poll_start, int, (AvahiThreadedPoll*));
|
||||
AvahiFunction(threaded_poll_stop, int, (AvahiThreadedPoll*));
|
||||
AvahiFunction(threaded_poll_lock, int, (AvahiThreadedPoll*));
|
||||
AvahiFunction(threaded_poll_unlock, int, (AvahiThreadedPoll*));
|
||||
|
||||
AvahiFunction(client_new, AvahiClient*,
|
||||
(const AvahiPoll* poll_api, AvahiClientFlags flags,
|
||||
AvahiClientCallback callback, void* userdata, int* error));
|
||||
AvahiFunction(client_free, void, (AvahiClient*));
|
||||
|
||||
AvahiFunction(service_browser_new, AvahiServiceBrowser*,
|
||||
(AvahiClient * client, AvahiIfIndex interface,
|
||||
AvahiProtocol protocol, const char* type, const char* domain,
|
||||
AvahiLookupFlags flags, AvahiServiceBrowserCallback callback,
|
||||
void* userdata));
|
||||
|
||||
AvahiFunction(service_browser_free, int, (AvahiServiceBrowser*));
|
||||
|
||||
AvahiFunction(service_resolver_new, AvahiServiceResolver*,
|
||||
(AvahiClient * client, AvahiIfIndex interface,
|
||||
AvahiProtocol protocol, const char* name, const char* type,
|
||||
const char* domain, AvahiProtocol aprotocol,
|
||||
AvahiLookupFlags flags, AvahiServiceResolverCallback callback,
|
||||
void* userdata));
|
||||
AvahiFunction(service_resolver_free, int, (AvahiServiceResolver*));
|
||||
|
||||
AvahiFunction(entry_group_new, AvahiEntryGroup*,
|
||||
(AvahiClient*, AvahiEntryGroupCallback, void*));
|
||||
AvahiFunction(entry_group_free, int, (AvahiEntryGroup*));
|
||||
|
||||
AvahiFunction(entry_group_add_service_strlst, int,
|
||||
(AvahiEntryGroup * group, AvahiIfIndex interface,
|
||||
AvahiProtocol protocol, AvahiPublishFlags flags,
|
||||
const char* name, const char* type, const char* domain,
|
||||
const char* host, uint16_t port, AvahiStringList*));
|
||||
|
||||
AvahiFunction(entry_group_reset, int, (AvahiEntryGroup*));
|
||||
AvahiFunction(entry_group_is_empty, int, (AvahiEntryGroup*));
|
||||
AvahiFunction(entry_group_commit, int, (AvahiEntryGroup*));
|
||||
|
||||
AvahiFunction(string_list_new_from_array, AvahiStringList*,
|
||||
(const char** array, int len));
|
||||
AvahiFunction(string_list_free, void, (AvahiStringList*));
|
||||
|
||||
AvahiFunction(service_browser_get_client, AvahiClient*,
|
||||
(AvahiServiceBrowser*));
|
||||
|
||||
AvahiFunction(unescape_label, char*, (const char**, char*, size_t));
|
||||
|
||||
bool IsValid() const { return valid; }
|
||||
|
||||
static AvahiFunctionTable& Get();
|
||||
|
||||
private:
|
||||
AvahiFunctionTable();
|
||||
bool valid;
|
||||
};
|
||||
|
||||
class AvahiThread {
|
||||
private:
|
||||
struct private_init {};
|
||||
|
||||
public:
|
||||
explicit AvahiThread(const private_init&);
|
||||
~AvahiThread() noexcept;
|
||||
|
||||
void lock();
|
||||
const AvahiPoll* GetPoll() const;
|
||||
void unlock();
|
||||
|
||||
static std::shared_ptr<AvahiThread> Get();
|
||||
|
||||
private:
|
||||
AvahiThreadedPoll* threadedPoll;
|
||||
AvahiFunctionTable& table = AvahiFunctionTable::Get();
|
||||
};
|
||||
|
||||
} // namespace wpi
|
||||
154
wpiutil/src/main/native/linux/MulticastServiceAnnouncer.cpp
Normal file
154
wpiutil/src/main/native/linux/MulticastServiceAnnouncer.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void EntryGroupCallback(AvahiEntryGroup*, AvahiEntryGroupState, void*) {}
|
||||
|
||||
static void RegisterService(AvahiClient* client,
|
||||
MulticastServiceAnnouncer::Impl* impl) {
|
||||
if (impl->group == nullptr) {
|
||||
impl->group =
|
||||
impl->table.entry_group_new(client, EntryGroupCallback, nullptr);
|
||||
}
|
||||
|
||||
if (impl->table.entry_group_is_empty(impl->group)) {
|
||||
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);
|
||||
impl->table.entry_group_commit(impl->group);
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
if (!pImpl->table.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->serviceName = serviceName;
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->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());
|
||||
}
|
||||
|
||||
pImpl->stringList =
|
||||
pImpl->table.string_list_new_from_array(txtArr.data(), txtArr.size());
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
if (!pImpl->table.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->serviceName = serviceName;
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->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());
|
||||
}
|
||||
|
||||
pImpl->stringList =
|
||||
pImpl->table.string_list_new_from_array(txtArr.data(), txtArr.size());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
148
wpiutil/src/main/native/linux/MulticastServiceResolver.cpp
Normal file
148
wpiutil/src/main/native/linux/MulticastServiceResolver.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
// 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/MulticastServiceResolver.h"
|
||||
|
||||
#include "AvahiClient.h"
|
||||
#include "wpi/SmallString.h"
|
||||
#include "wpi/mutex.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
struct MulticastServiceResolver::Impl {
|
||||
AvahiFunctionTable& table = AvahiFunctionTable::Get();
|
||||
std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
|
||||
AvahiClient* client;
|
||||
AvahiServiceBrowser* browser;
|
||||
std::string serviceType;
|
||||
MulticastServiceResolver* resolver;
|
||||
|
||||
void onFound(ServiceData&& data) {
|
||||
resolver->eventQueue.push(std::move(data));
|
||||
resolver->event.Set();
|
||||
}
|
||||
};
|
||||
|
||||
MulticastServiceResolver::MulticastServiceResolver(
|
||||
std::string_view serviceType) {
|
||||
pImpl = std::make_unique<Impl>();
|
||||
pImpl->serviceType = serviceType;
|
||||
pImpl->resolver = this;
|
||||
}
|
||||
|
||||
MulticastServiceResolver::~MulticastServiceResolver() noexcept {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool MulticastServiceResolver::HasImplementation() const {
|
||||
return pImpl->table.IsValid();
|
||||
}
|
||||
|
||||
static void ResolveCallback(AvahiServiceResolver* r, AvahiIfIndex interface,
|
||||
AvahiProtocol protocol, AvahiResolverEvent event,
|
||||
const char* name, const char* type,
|
||||
const char* domain, const char* host_name,
|
||||
const AvahiAddress* address, uint16_t port,
|
||||
AvahiStringList* txt, AvahiLookupResultFlags flags,
|
||||
void* userdata) {
|
||||
MulticastServiceResolver::Impl* impl =
|
||||
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
|
||||
|
||||
if (event == AVAHI_RESOLVER_FOUND) {
|
||||
if (address->proto == AVAHI_PROTO_INET) {
|
||||
AvahiStringList* strLst = txt;
|
||||
MulticastServiceResolver::ServiceData data;
|
||||
while (strLst != nullptr) {
|
||||
std::string_view value{reinterpret_cast<const char*>(strLst->text),
|
||||
strLst->size};
|
||||
strLst = strLst->next;
|
||||
size_t splitIndex = value.find('=');
|
||||
if (splitIndex == value.npos) {
|
||||
// Todo make this just do key
|
||||
continue;
|
||||
}
|
||||
std::string_view key = value.substr(0, splitIndex);
|
||||
value = value.substr(splitIndex + 1, value.size() - splitIndex - 1);
|
||||
data.txt.emplace_back(std::pair<std::string, std::string>{key, value});
|
||||
}
|
||||
wpi::SmallString<256> outputHostName;
|
||||
char label[256];
|
||||
do {
|
||||
impl->table.unescape_label(&host_name, label, sizeof(label));
|
||||
if (label[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
outputHostName.append(label);
|
||||
outputHostName.append(".");
|
||||
} while (true);
|
||||
|
||||
data.ipv4Address = address->data.ipv4.address;
|
||||
data.port = port;
|
||||
data.serviceName = name;
|
||||
data.hostName = outputHostName.string();
|
||||
|
||||
impl->onFound(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
impl->table.service_resolver_free(r);
|
||||
}
|
||||
|
||||
static void BrowseCallback(AvahiServiceBrowser* b, AvahiIfIndex interface,
|
||||
AvahiProtocol protocol, AvahiBrowserEvent event,
|
||||
const char* name, const char* type,
|
||||
const char* domain, AvahiLookupResultFlags flags,
|
||||
void* userdata) {
|
||||
MulticastServiceResolver::Impl* impl =
|
||||
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
|
||||
|
||||
if (event == AVAHI_BROWSER_NEW) {
|
||||
impl->table.service_resolver_new(
|
||||
impl->table.service_browser_get_client(b), interface, protocol, name,
|
||||
type, domain, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST,
|
||||
ResolveCallback, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
static void ClientCallback(AvahiClient* client, AvahiClientState state,
|
||||
void* userdata) {
|
||||
MulticastServiceResolver::Impl* impl =
|
||||
reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
|
||||
|
||||
if (state == AVAHI_CLIENT_S_RUNNING) {
|
||||
impl->browser = impl->table.service_browser_new(
|
||||
client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, impl->serviceType.c_str(),
|
||||
"local", AvahiLookupFlags::AVAHI_LOOKUP_USE_MULTICAST, BrowseCallback,
|
||||
userdata);
|
||||
}
|
||||
}
|
||||
|
||||
void MulticastServiceResolver::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 MulticastServiceResolver::Stop() {
|
||||
if (!pImpl->table.IsValid()) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{*pImpl->thread};
|
||||
if (pImpl->client) {
|
||||
if (pImpl->browser) {
|
||||
pImpl->table.service_browser_free(pImpl->browser);
|
||||
pImpl->browser = nullptr;
|
||||
}
|
||||
pImpl->table.client_free(pImpl->client);
|
||||
pImpl->client = nullptr;
|
||||
}
|
||||
}
|
||||
86
wpiutil/src/main/native/macOS/MulticastServiceAnnouncer.cpp
Normal file
86
wpiutil/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 "wpi/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
wpiutil/src/main/native/macOS/MulticastServiceResolver.cpp
Normal file
214
wpiutil/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 "wpi/MulticastServiceResolver.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "ResolverThread.h"
|
||||
#include "dns_sd.h"
|
||||
#include "wpi/SmallVector.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->eventQueue.push(std::move(data));
|
||||
resolver->event.Set();
|
||||
}
|
||||
};
|
||||
|
||||
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
wpiutil/src/main/native/macOS/ResolverThread.cpp
Normal file
109
wpiutil/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;
|
||||
}
|
||||
45
wpiutil/src/main/native/macOS/ResolverThread.h
Normal file
45
wpiutil/src/main/native/macOS/ResolverThread.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 "dns_sd.h"
|
||||
#include "wpi/Synchronization.h"
|
||||
#include "wpi/mutex.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
|
||||
41
wpiutil/src/main/native/windows/DynamicDns.cpp
Normal file
41
wpiutil/src/main/native/windows/DynamicDns.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 "DynamicDns.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
DynamicDns& DynamicDns::GetDynamicDns() {
|
||||
static DynamicDns dns;
|
||||
return dns;
|
||||
}
|
||||
|
||||
DynamicDns::DynamicDns() {
|
||||
HMODULE library = LoadLibraryW(L"dnsapi");
|
||||
|
||||
if (library == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
DnsServiceFreeInstancePtr = (DnsServiceFreeInstanceFunc)GetProcAddress(
|
||||
library, "DnsServiceFreeInstance");
|
||||
DnsServiceConstructInstancePtr =
|
||||
(DnsServiceConstructInstanceFunc)GetProcAddress(
|
||||
library, "DnsServiceConstructInstance");
|
||||
DnsServiceRegisterPtr =
|
||||
(DnsServiceRegisterFunc)GetProcAddress(library, "DnsServiceRegister");
|
||||
DnsServiceDeRegisterPtr =
|
||||
(DnsServiceDeRegisterFunc)GetProcAddress(library, "DnsServiceDeRegister");
|
||||
|
||||
CanDnsAnnounce = DnsServiceFreeInstancePtr &&
|
||||
DnsServiceConstructInstancePtr && DnsServiceRegisterPtr &&
|
||||
DnsServiceDeRegisterPtr;
|
||||
|
||||
DnsServiceBrowsePtr =
|
||||
(DnsServiceBrowseFunc)GetProcAddress(library, "DnsServiceBrowse");
|
||||
DnsServiceBrowseCancelPtr = (DnsServiceBrowseCancelFunc)GetProcAddress(
|
||||
library, "DnsServiceBrowseCancel");
|
||||
|
||||
CanDnsResolve = DnsServiceBrowsePtr && DnsServiceBrowseCancelPtr;
|
||||
}
|
||||
59
wpiutil/src/main/native/windows/DynamicDns.h
Normal file
59
wpiutil/src/main/native/windows/DynamicDns.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <WinDNS.h>
|
||||
|
||||
namespace wpi {
|
||||
class DynamicDns {
|
||||
public:
|
||||
using DnsServiceFreeInstanceFunc =
|
||||
VOID(WINAPI*)(_In_ PDNS_SERVICE_INSTANCE pInstance);
|
||||
|
||||
using DnsServiceConstructInstanceFunc = PDNS_SERVICE_INSTANCE(WINAPI*)(
|
||||
_In_ PCWSTR pServiceName, _In_ PCWSTR pHostName,
|
||||
_In_opt_ PIP4_ADDRESS pIp4, _In_opt_ PIP6_ADDRESS pIp6, _In_ WORD wPort,
|
||||
_In_ WORD wPriority, _In_ WORD wWeight, _In_ DWORD dwPropertiesCount,
|
||||
_In_reads_(dwPropertiesCount) PCWSTR* keys,
|
||||
_In_reads_(dwPropertiesCount) PCWSTR* values);
|
||||
|
||||
using DnsServiceRegisterFunc =
|
||||
DWORD(WINAPI*)(_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest,
|
||||
_Inout_opt_ PDNS_SERVICE_CANCEL pCancel);
|
||||
|
||||
using DnsServiceDeRegisterFunc =
|
||||
DWORD(WINAPI*)(_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest,
|
||||
_Inout_opt_ PDNS_SERVICE_CANCEL pCancel);
|
||||
|
||||
using DnsServiceBrowseFunc =
|
||||
DNS_STATUS(WINAPI*)(_In_ PDNS_SERVICE_BROWSE_REQUEST pRequest,
|
||||
_Inout_ PDNS_SERVICE_CANCEL pCancel);
|
||||
|
||||
using DnsServiceBrowseCancelFunc =
|
||||
DNS_STATUS(WINAPI*)(_In_ PDNS_SERVICE_CANCEL pCancelHandle);
|
||||
|
||||
DnsServiceBrowseFunc DnsServiceBrowsePtr{nullptr};
|
||||
DnsServiceBrowseCancelFunc DnsServiceBrowseCancelPtr{nullptr};
|
||||
|
||||
DnsServiceFreeInstanceFunc DnsServiceFreeInstancePtr{nullptr};
|
||||
DnsServiceConstructInstanceFunc DnsServiceConstructInstancePtr{nullptr};
|
||||
DnsServiceRegisterFunc DnsServiceRegisterPtr{nullptr};
|
||||
DnsServiceDeRegisterFunc DnsServiceDeRegisterPtr{nullptr};
|
||||
|
||||
bool CanDnsAnnounce{false};
|
||||
bool CanDnsResolve{false};
|
||||
|
||||
static DynamicDns& GetDynamicDns();
|
||||
|
||||
private:
|
||||
DynamicDns();
|
||||
};
|
||||
} // namespace wpi
|
||||
267
wpiutil/src/main/native/windows/MulticastServiceAnnouncer.cpp
Normal file
267
wpiutil/src/main/native/windows/MulticastServiceAnnouncer.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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.
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include "wpi/MulticastServiceAnnouncer.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "DynamicDns.h"
|
||||
#include "wpi/ConvertUTF.h"
|
||||
#include "wpi/SmallString.h"
|
||||
#include "wpi/SmallVector.h"
|
||||
#include "wpi/StringExtras.h"
|
||||
#include "wpi/hostname.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
struct ImplBase {
|
||||
wpi::DynamicDns& dynamicDns = wpi::DynamicDns::GetDynamicDns();
|
||||
PDNS_SERVICE_INSTANCE serviceInstance = nullptr;
|
||||
HANDLE event = nullptr;
|
||||
};
|
||||
|
||||
struct MulticastServiceAnnouncer::Impl : ImplBase {
|
||||
std::wstring serviceType;
|
||||
std::wstring serviceInstanceName;
|
||||
std::wstring hostName;
|
||||
int port;
|
||||
std::vector<std::wstring> keys;
|
||||
std::vector<PCWSTR> keyPtrs;
|
||||
std::vector<std::wstring> values;
|
||||
std::vector<PCWSTR> valuePtrs;
|
||||
};
|
||||
|
||||
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>();
|
||||
|
||||
if (!pImpl->dynamicDns.CanDnsAnnounce) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->port = port;
|
||||
|
||||
wpi::SmallVector<wpi::UTF16, 128> wideStorage;
|
||||
std::string hostName = wpi::GetHostname() + ".local";
|
||||
|
||||
for (auto&& i : txt) {
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(i.first, wideStorage);
|
||||
pImpl->keys.emplace_back(
|
||||
std::wstring{reinterpret_cast<const wchar_t*>(wideStorage.data()),
|
||||
wideStorage.size()});
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(i.second, wideStorage);
|
||||
pImpl->values.emplace_back(
|
||||
std::wstring{reinterpret_cast<const wchar_t*>(wideStorage.data()),
|
||||
wideStorage.size()});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pImpl->keys.size(); i++) {
|
||||
pImpl->keyPtrs.emplace_back(pImpl->keys[i].c_str());
|
||||
pImpl->valuePtrs.emplace_back(pImpl->values[i].c_str());
|
||||
}
|
||||
|
||||
wpi::SmallString<128> storage;
|
||||
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(hostName, wideStorage);
|
||||
|
||||
pImpl->hostName = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
|
||||
wideStorage.clear();
|
||||
if (wpi::ends_with_lower(serviceType, ".local")) {
|
||||
wpi::convertUTF8ToUTF16String(serviceType, wideStorage);
|
||||
} else {
|
||||
storage.clear();
|
||||
storage.append(serviceType);
|
||||
storage.append(".local");
|
||||
wpi::convertUTF8ToUTF16String(storage.str(), wideStorage);
|
||||
}
|
||||
pImpl->serviceType = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
|
||||
wideStorage.clear();
|
||||
storage.clear();
|
||||
storage.append(serviceName);
|
||||
storage.append(".");
|
||||
storage.append(serviceType);
|
||||
if (!wpi::ends_with_lower(serviceType, ".local")) {
|
||||
storage.append(".local");
|
||||
}
|
||||
|
||||
wpi::convertUTF8ToUTF16String(storage.str(), wideStorage);
|
||||
pImpl->serviceInstanceName = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
if (!pImpl->dynamicDns.CanDnsAnnounce) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->port = port;
|
||||
|
||||
wpi::SmallVector<wpi::UTF16, 128> wideStorage;
|
||||
std::string hostName = wpi::GetHostname() + ".local";
|
||||
|
||||
for (auto&& i : txt) {
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(i.first, wideStorage);
|
||||
pImpl->keys.emplace_back(
|
||||
std::wstring{reinterpret_cast<const wchar_t*>(wideStorage.data()),
|
||||
wideStorage.size()});
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(i.second, wideStorage);
|
||||
pImpl->values.emplace_back(
|
||||
std::wstring{reinterpret_cast<const wchar_t*>(wideStorage.data()),
|
||||
wideStorage.size()});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pImpl->keys.size(); i++) {
|
||||
pImpl->keyPtrs.emplace_back(pImpl->keys[i].c_str());
|
||||
pImpl->valuePtrs.emplace_back(pImpl->values[i].c_str());
|
||||
}
|
||||
|
||||
wpi::SmallString<128> storage;
|
||||
|
||||
wideStorage.clear();
|
||||
wpi::convertUTF8ToUTF16String(hostName, wideStorage);
|
||||
|
||||
pImpl->hostName = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
|
||||
wideStorage.clear();
|
||||
if (wpi::ends_with_lower(serviceType, ".local")) {
|
||||
wpi::convertUTF8ToUTF16String(serviceType, wideStorage);
|
||||
} else {
|
||||
storage.clear();
|
||||
storage.append(serviceType);
|
||||
storage.append(".local");
|
||||
wpi::convertUTF8ToUTF16String(storage.str(), wideStorage);
|
||||
}
|
||||
pImpl->serviceType = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
|
||||
wideStorage.clear();
|
||||
storage.clear();
|
||||
storage.append(serviceName);
|
||||
storage.append(".");
|
||||
storage.append(serviceType);
|
||||
if (!wpi::ends_with_lower(serviceType, ".local")) {
|
||||
storage.append(".local");
|
||||
}
|
||||
|
||||
wpi::convertUTF8ToUTF16String(storage.str(), wideStorage);
|
||||
pImpl->serviceInstanceName = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
}
|
||||
|
||||
MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool MulticastServiceAnnouncer::HasImplementation() const {
|
||||
return pImpl->dynamicDns.CanDnsAnnounce;
|
||||
}
|
||||
|
||||
static void WINAPI DnsServiceRegisterCallback(DWORD /*Status*/,
|
||||
PVOID pQueryContext,
|
||||
PDNS_SERVICE_INSTANCE pInstance) {
|
||||
ImplBase* impl = reinterpret_cast<ImplBase*>(pQueryContext);
|
||||
|
||||
impl->serviceInstance = pInstance;
|
||||
|
||||
SetEvent(impl->event);
|
||||
}
|
||||
|
||||
void MulticastServiceAnnouncer::Start() {
|
||||
if (pImpl->serviceInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pImpl->dynamicDns.CanDnsAnnounce) {
|
||||
return;
|
||||
}
|
||||
|
||||
PDNS_SERVICE_INSTANCE serviceInst =
|
||||
pImpl->dynamicDns.DnsServiceConstructInstancePtr(
|
||||
pImpl->serviceInstanceName.c_str(), pImpl->hostName.c_str(), nullptr,
|
||||
nullptr, pImpl->port, 0, 0, static_cast<DWORD>(pImpl->keyPtrs.size()),
|
||||
pImpl->keyPtrs.data(), pImpl->valuePtrs.data());
|
||||
if (serviceInst == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
|
||||
registerRequest.pQueryContext = static_cast<ImplBase*>(pImpl.get());
|
||||
registerRequest.pRegisterCompletionCallback = DnsServiceRegisterCallback;
|
||||
registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
|
||||
registerRequest.unicastEnabled = false;
|
||||
registerRequest.pServiceInstance = serviceInst;
|
||||
registerRequest.InterfaceIndex = 0;
|
||||
|
||||
pImpl->event = CreateEvent(NULL, true, false, NULL);
|
||||
|
||||
if (pImpl->dynamicDns.DnsServiceRegisterPtr(®isterRequest, nullptr) ==
|
||||
DNS_REQUEST_PENDING) {
|
||||
WaitForSingleObject(pImpl->event, INFINITE);
|
||||
}
|
||||
|
||||
pImpl->dynamicDns.DnsServiceFreeInstancePtr(serviceInst);
|
||||
CloseHandle(pImpl->event);
|
||||
pImpl->event = nullptr;
|
||||
}
|
||||
|
||||
static void WINAPI DnsServiceDeRegisterCallback(
|
||||
DWORD /*Status*/, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
|
||||
ImplBase* impl = reinterpret_cast<ImplBase*>(pQueryContext);
|
||||
|
||||
if (pInstance != nullptr) {
|
||||
impl->dynamicDns.DnsServiceFreeInstancePtr(pInstance);
|
||||
pInstance = nullptr;
|
||||
}
|
||||
|
||||
SetEvent(impl->event);
|
||||
}
|
||||
|
||||
void MulticastServiceAnnouncer::Stop() {
|
||||
if (!pImpl->dynamicDns.CanDnsAnnounce) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pImpl->serviceInstance == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->event = CreateEvent(NULL, true, false, NULL);
|
||||
DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
|
||||
registerRequest.pQueryContext = static_cast<ImplBase*>(pImpl.get());
|
||||
registerRequest.pRegisterCompletionCallback = DnsServiceDeRegisterCallback;
|
||||
registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
|
||||
registerRequest.unicastEnabled = false;
|
||||
registerRequest.pServiceInstance = pImpl->serviceInstance;
|
||||
registerRequest.InterfaceIndex = 0;
|
||||
|
||||
if (pImpl->dynamicDns.DnsServiceDeRegisterPtr(®isterRequest, nullptr) ==
|
||||
DNS_REQUEST_PENDING) {
|
||||
WaitForSingleObject(pImpl->event, INFINITE);
|
||||
}
|
||||
|
||||
pImpl->dynamicDns.DnsServiceFreeInstancePtr(pImpl->serviceInstance);
|
||||
pImpl->serviceInstance = nullptr;
|
||||
CloseHandle(pImpl->event);
|
||||
pImpl->event = nullptr;
|
||||
}
|
||||
207
wpiutil/src/main/native/windows/MulticastServiceResolver.cpp
Normal file
207
wpiutil/src/main/native/windows/MulticastServiceResolver.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
// 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.
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include "wpi/MulticastServiceResolver.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "DynamicDns.h"
|
||||
#include "wpi/ConvertUTF.h"
|
||||
#include "wpi/SmallString.h"
|
||||
#include "wpi/SmallVector.h"
|
||||
#include "wpi/StringExtras.h"
|
||||
|
||||
#pragma comment(lib, "dnsapi")
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
struct MulticastServiceResolver::Impl {
|
||||
wpi::DynamicDns& dynamicDns = wpi::DynamicDns::GetDynamicDns();
|
||||
std::wstring serviceType;
|
||||
DNS_SERVICE_CANCEL serviceCancel{nullptr};
|
||||
|
||||
MulticastServiceResolver* resolver;
|
||||
|
||||
void onFound(ServiceData&& data) {
|
||||
resolver->eventQueue.push(std::move(data));
|
||||
resolver->event.Set();
|
||||
}
|
||||
};
|
||||
|
||||
MulticastServiceResolver::MulticastServiceResolver(
|
||||
std::string_view serviceType) {
|
||||
pImpl = std::make_unique<Impl>();
|
||||
pImpl->resolver = this;
|
||||
|
||||
if (!pImpl->dynamicDns.CanDnsResolve) {
|
||||
return;
|
||||
}
|
||||
|
||||
wpi::SmallVector<wpi::UTF16, 128> wideStorage;
|
||||
|
||||
if (wpi::ends_with_lower(serviceType, ".local")) {
|
||||
wpi::convertUTF8ToUTF16String(serviceType, wideStorage);
|
||||
} else {
|
||||
wpi::SmallString<128> storage;
|
||||
storage.append(serviceType);
|
||||
storage.append(".local");
|
||||
wpi::convertUTF8ToUTF16String(storage.str(), wideStorage);
|
||||
}
|
||||
pImpl->serviceType = std::wstring{
|
||||
reinterpret_cast<const wchar_t*>(wideStorage.data()), wideStorage.size()};
|
||||
}
|
||||
|
||||
MulticastServiceResolver::~MulticastServiceResolver() noexcept {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool MulticastServiceResolver::HasImplementation() const {
|
||||
return pImpl->dynamicDns.CanDnsResolve;
|
||||
}
|
||||
|
||||
static _Function_class_(DNS_QUERY_COMPLETION_ROUTINE) VOID WINAPI
|
||||
DnsCompletion(_In_ PVOID pQueryContext,
|
||||
_Inout_ PDNS_QUERY_RESULT pQueryResults) {
|
||||
MulticastServiceResolver::Impl* impl =
|
||||
reinterpret_cast<MulticastServiceResolver::Impl*>(pQueryContext);
|
||||
|
||||
wpi::SmallVector<DNS_RECORDW*, 4> PtrRecords;
|
||||
wpi::SmallVector<DNS_RECORDW*, 4> SrvRecords;
|
||||
wpi::SmallVector<DNS_RECORDW*, 4> TxtRecords;
|
||||
wpi::SmallVector<DNS_RECORDW*, 4> ARecords;
|
||||
|
||||
{
|
||||
DNS_RECORDW* current = pQueryResults->pQueryRecords;
|
||||
while (current != nullptr) {
|
||||
switch (current->wType) {
|
||||
case DNS_TYPE_PTR:
|
||||
PtrRecords.push_back(current);
|
||||
break;
|
||||
case DNS_TYPE_SRV:
|
||||
SrvRecords.push_back(current);
|
||||
break;
|
||||
case DNS_TYPE_TEXT:
|
||||
TxtRecords.push_back(current);
|
||||
break;
|
||||
case DNS_TYPE_A:
|
||||
ARecords.push_back(current);
|
||||
break;
|
||||
}
|
||||
current = current->pNext;
|
||||
}
|
||||
}
|
||||
|
||||
for (DNS_RECORDW* Ptr : PtrRecords) {
|
||||
if (std::wstring_view{Ptr->pName} != impl->serviceType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring_view nameHost = Ptr->Data.Ptr.pNameHost;
|
||||
DNS_RECORDW* foundSrv = nullptr;
|
||||
for (DNS_RECORDW* Srv : SrvRecords) {
|
||||
if (std::wstring_view{Srv->pName} == nameHost) {
|
||||
foundSrv = Srv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSrv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (DNS_RECORDW* A : ARecords) {
|
||||
if (std::wstring_view{A->pName} ==
|
||||
std::wstring_view{foundSrv->Data.Srv.pNameTarget}) {
|
||||
MulticastServiceResolver::ServiceData data;
|
||||
wpi::SmallString<128> storage;
|
||||
for (DNS_RECORDW* Txt : TxtRecords) {
|
||||
if (std::wstring_view{Txt->pName} == nameHost) {
|
||||
for (DWORD i = 0; i < Txt->Data.Txt.dwStringCount; i++) {
|
||||
std::wstring_view wideView = Txt->Data.TXT.pStringArray[i];
|
||||
size_t splitIndex = wideView.find(L'=');
|
||||
if (splitIndex == wideView.npos) {
|
||||
// Todo make this just do key
|
||||
continue;
|
||||
}
|
||||
storage.clear();
|
||||
wpi::span<const wpi::UTF16> wideStr{
|
||||
reinterpret_cast<const wpi::UTF16*>(wideView.data()),
|
||||
splitIndex};
|
||||
wpi::convertUTF16ToUTF8String(wideStr, storage);
|
||||
auto& pair = data.txt.emplace_back(
|
||||
std::pair<std::string, std::string>{storage.string(), {}});
|
||||
storage.clear();
|
||||
wideStr = wpi::span<const wpi::UTF16>{
|
||||
reinterpret_cast<const wpi::UTF16*>(wideView.data() +
|
||||
splitIndex + 1),
|
||||
wideView.size() - splitIndex - 1};
|
||||
wpi::convertUTF16ToUTF8String(wideStr, storage);
|
||||
pair.second = storage.string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
storage.clear();
|
||||
wpi::span<const wpi::UTF16> wideHostName{
|
||||
reinterpret_cast<const wpi::UTF16*>(A->pName), wcslen(A->pName)};
|
||||
wpi::convertUTF16ToUTF8String(wideHostName, storage);
|
||||
storage.append(".");
|
||||
|
||||
data.hostName = storage.string();
|
||||
storage.clear();
|
||||
|
||||
int len = nameHost.find(impl->serviceType.c_str());
|
||||
wpi::span<const wpi::UTF16> wideServiceName{
|
||||
reinterpret_cast<const wpi::UTF16*>(nameHost.data()),
|
||||
nameHost.size()};
|
||||
if (len != nameHost.npos) {
|
||||
wideServiceName = wideServiceName.subspan(0, len - 1);
|
||||
}
|
||||
wpi::convertUTF16ToUTF8String(wideServiceName, storage);
|
||||
|
||||
data.serviceName = storage.string();
|
||||
data.port = foundSrv->Data.Srv.wPort;
|
||||
data.ipv4Address = A->Data.A.IpAddress;
|
||||
|
||||
impl->onFound(std::move(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
DnsFree(pQueryResults->pQueryRecords, DNS_FREE_TYPE::DnsFreeRecordList);
|
||||
}
|
||||
|
||||
void MulticastServiceResolver::Start() {
|
||||
if (pImpl->serviceCancel.reserved != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pImpl->dynamicDns.CanDnsResolve) {
|
||||
return;
|
||||
}
|
||||
|
||||
DNS_SERVICE_BROWSE_REQUEST request = {};
|
||||
request.InterfaceIndex = 0;
|
||||
request.pQueryContext = pImpl.get();
|
||||
request.QueryName = pImpl->serviceType.c_str();
|
||||
request.Version = 2;
|
||||
request.pBrowseCallbackV2 = DnsCompletion;
|
||||
pImpl->dynamicDns.DnsServiceBrowsePtr(&request, &pImpl->serviceCancel);
|
||||
}
|
||||
|
||||
void MulticastServiceResolver::Stop() {
|
||||
if (!pImpl->dynamicDns.CanDnsResolve) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pImpl->serviceCancel.reserved == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
pImpl->dynamicDns.DnsServiceBrowseCancelPtr(&pImpl->serviceCancel);
|
||||
pImpl->serviceCancel.reserved = nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user