[wpiutil] Add mDNS resolver and announcer (#3733)

This commit is contained in:
Thad House
2021-11-25 22:08:26 -08:00
committed by GitHub
parent 4b1defc8d8
commit 82066946e5
28 changed files with 2562 additions and 0 deletions

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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'
}
}
}
}
}

View 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.
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);
}
}

View File

@@ -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);
}
}

View 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;
}

View File

@@ -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);
}

View 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;
}

View 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

View 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"

View 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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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.
*

View 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);
}

View 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

View 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;
}
}

View 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;
}
}

View 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;
}

View 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;
}

View 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;
}

View 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

View 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;
}

View 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

View 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(&registerRequest, 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(&registerRequest, nullptr) ==
DNS_REQUEST_PENDING) {
WaitForSingleObject(pImpl->event, INFINITE);
}
pImpl->dynamicDns.DnsServiceFreeInstancePtr(pImpl->serviceInstance);
pImpl->serviceInstance = nullptr;
CloseHandle(pImpl->event);
pImpl->event = nullptr;
}

View 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;
}