/*
 * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dnssd_private.h"

#include "dnssd_object.h"
#include "dnssd_xpc.h"
#include "dnssd_svcb.h"

#include <CoreUtils/CoreUtils.h>
#include <os/object_private.h>
#include <xpc/private.h>
#include "mdns_strict.h"

//======================================================================================================================
// MARK: - Kind Declarations

#define DNSSD_STRUCT(NAME)	struct dnssd_ ## NAME ## _s
#define DNSSD_TYPE(NAME)	dnssd_ ## NAME ## _t

#define DNSSD_KIND_DECLARE(NAME)																\
	static DNSSD_TYPE(NAME)																		\
	_dnssd_ ## NAME ## _alloc(void);															\
																								\
	static char *																				\
	_dnssd_ ## NAME ## _copy_description(DNSSD_TYPE(NAME) object, bool debug, bool privacy);	\
																								\
	static void																					\
	_dnssd_ ## NAME ## _finalize(DNSSD_TYPE(NAME) object)

// Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer
// comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as
// the warning hasn't been disabled.

#define DNSSD_BASE_CHECK(NAME, SUPER)																	\
	check_compile_time(offsetof(DNSSD_STRUCT(NAME), base) == 0);										\
	check_compile_time(sizeof_field(DNSSD_STRUCT(NAME), base) == sizeof(DNSSD_STRUCT(SUPER)));			\
	extern int _dnssd_base_type_check[sizeof(&(((DNSSD_TYPE(NAME))0)->base) == ((DNSSD_TYPE(SUPER))0))]

#define DNSSD_KIND_DEFINE(NAME, SUPER) 													\
	static const struct dnssd_kind_s _dnssd_ ## NAME ## _kind = {						\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()	\
		&_dnssd_ ## SUPER ## _kind,														\
		# NAME,																			\
		_dnssd_ ## NAME ## _copy_description,											\
		_dnssd_ ## NAME ## _finalize,													\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()		\
	};																					\
																						\
	static DNSSD_TYPE(NAME)																\
	_dnssd_ ## NAME ## _alloc(void)														\
	{																					\
		DNSSD_TYPE(NAME) obj = dnssd_object_ ## NAME ## _alloc(sizeof(*obj));			\
		require_quiet(obj, exit);														\
																						\
		const dnssd_object_t base = (dnssd_object_t)obj;								\
		base->kind = &_dnssd_ ## NAME ## _kind;											\
																						\
	exit:																				\
		return obj;																		\
	}																					\
	DNSSD_BASE_CHECK(NAME, SUPER)

DNSSD_KIND_DECLARE(getaddrinfo);
DNSSD_KIND_DECLARE(getaddrinfo_result);
DNSSD_KIND_DECLARE(cname_array);

typedef char *	(*dnssd_copy_description_f)(dnssd_any_t object, bool debug, bool privacy);
typedef void	(*dnssd_finalize_f)(dnssd_any_t object);

typedef const struct dnssd_kind_s *	dnssd_kind_t;
struct dnssd_kind_s {
	dnssd_kind_t				superkind;			// This kind's superkind.
	const char *				name;				// Name of this kind.
	dnssd_copy_description_f	copy_description;	// Creates a textual description of object.
	dnssd_finalize_f			finalize;			// Releases object's resources right before the object is freed.
};

//======================================================================================================================
// MARK: - Object Kind Definition

struct dnssd_object_s {
	_OS_OBJECT_HEADER(const void * __ptrauth_objc_isa_pointer _os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt);
	dnssd_kind_t	kind;	// Pointer to an object's kind.
};

static const struct dnssd_kind_s _dnssd_object_kind = {
	NULL,		// No superkind.
	"object",
	NULL,		// No copy_description method.
	NULL,		// No finalize method.
};

//======================================================================================================================
// MARK: - GetAddrInfo Kind Definition

typedef enum {
	dnssd_getaddrinfo_state_nascent		= 0,
	dnssd_getaddrinfo_state_starting	= 1,
	dnssd_getaddrinfo_state_started		= 2,
	dnssd_getaddrinfo_state_failed		= 3,
	dnssd_getaddrinfo_state_invalidated	= 4,
} dnssd_getaddrinfo_state_t;

struct dnssd_getaddrinfo_s {
	struct dnssd_object_s				base;				// Object base.
	dnssd_getaddrinfo_t					next;				// Next getaddrinfo object in list.
	uint64_t							command_id;			// Command ID.
	dispatch_queue_t					user_queue;			// User's dispatch queue for result and event handlers.
	dispatch_queue_t					mutex_queue;		// Mutex for accessing result_list from different queues.
	xpc_object_t						params;				// Parameters dictionary for getaddrinfo command.
	xpc_object_t						hostname;			// Reference to hostname from parameters dictionary.
	dnssd_cname_array_t					cnames_a;			// Array of hostname's canonical names for A results.
	dnssd_cname_array_t					cnames_aaaa;		// Array of hostname's canonical names for AAAA results.
	dispatch_source_t					event_source;		// Data source for triggering result and event handlers.
	dnssd_getaddrinfo_result_t			result_list;		// List of getaddrinfo results.
	dnssd_getaddrinfo_result_handler_t	result_handler;		// User's result handler.
	dnssd_event_handler_t				event_handler;		// User's event handler.
	mdns_xpc_string_t					tracker_hostname;	// The last result's tracker hostname. Used for deduping.
	mdns_xpc_string_t					tracker_owner;		// The last result's tracker owner. Used for deduping.
	dnssd_getaddrinfo_state_t			state;				// Internal state.
	OSStatus							error;				// Pending error.
	bool								user_activated;		// True if the object has been activated by user.
};

DNSSD_KIND_DEFINE(getaddrinfo, object);

//======================================================================================================================
// MARK: - GetAddrInfo Result Kind Definition

struct dnssd_getaddrinfo_result_s {
	struct dnssd_object_s				base;				// Object base.
	dnssd_getaddrinfo_result_t			next;				// Next getaddrinfo result in list.
	sockaddr_ip							addr;				// IPv4 or IPv6 address of hostname.
	xpc_object_t						hostname;			// Requested hostname to resolve.
	xpc_object_t						actual_hostname;	// The actual/canonical hostname of the requested hostname.
	dnssd_cname_array_t					cnames;				// Array of hostname's canonical names.
	xpc_object_t						provider_name;		// Provider name.
	xpc_object_t						ech_config;			// SVCB ECH config.
	xpc_object_t						address_hints;		// SVCB address hints.
	xpc_object_t						doh_uri;			// SVCB DoH URI.
	xpc_object_t						doh_path;			// SVCB DoH Path.
	xpc_object_t						odoh_config;		// SVCB Oblivious DoH config.
	xpc_object_t						alpn_values;		// SVCB ALPN values.
	xpc_object_t						service_name;		// SVCB name.
	mdns_xpc_string_t					tracker_hostname;	// Tracker hostname [1].
	mdns_xpc_string_t					tracker_owner;		// Tracker owner [2].
	xpc_object_t						validation_data;	// Validation data.
	mdns_xpc_string_t					ede_text;			// Extended DNS Error extra text for negative results.
	uint16_t							port;				// SVCB port.
	uint16_t							priority;			// SVCB priority.
	uint32_t							if_index;			// Interface index to which the result pertains.
	dnssd_getaddrinfo_result_type_t		type;				// Type of getaddrinfo result.
	dnssd_getaddrinfo_result_protocol_t	protocol;			// Protocol used for getaddrinfo result.
	dnssd_negative_reason_t				negative_reason;	// The reason code for a negative result.
	uint16_t							ede_code;			// Extended DNS Error code for negative results.
	bool								is_from_cache;		// True if the result was an answer from the cache.
	bool								valid_svcb;			// True if SVCB info is valid.
	bool								tracker_approved;	// True if the tracker is an approved domain for the app.
	bool 								tracker_can_block;	// True if a request to this known tracker can be blocked.
	bool								ede_valid;			// True if Extended DNS Error is valid for negative result.
};

// Notes:
// 1. If non-NULL, identifies the known tracker domain name, if any, that appears earliest in the requested hostname's
//    CNAME chain.
// 2. If non-NULL, identifies the entity (usually a company) that owns the tracker hostname.

DNSSD_KIND_DEFINE(getaddrinfo_result, object);

//======================================================================================================================
// MARK: - CName Array Kind Definition

struct dnssd_cname_array_s {
	struct dnssd_object_s	base;		// Object base.
	xpc_object_t			xpc_array;	// Underlying array of cnames as strings. Important: Must not be modified.
};

DNSSD_KIND_DEFINE(cname_array, object);

//======================================================================================================================
// MARK: - Constants

#define DNSSD_EVENT_HAVE_RESULTS	(1U << 0)	// Results are available.
#define DNSSD_EVENT_REMOVE_ALL		(1U << 1)	// Previously delivered results are no longer valid.
#define DNSSD_EVENT_ERROR			(1U << 2)	// An error was encountered.

// Strings for redacted description items.

#define DNSSD_REDACTED_HOSTNAME_STR		"<redacted hostname>"
#define DNSSD_REDACTED_IPv4_ADDRESS_STR	"<redacted IPv4 address>"
#define DNSSD_REDACTED_IPv6_ADDRESS_STR	"<redacted IPv6 address>"

//======================================================================================================================
// MARK: - Local Prototypes

static dispatch_queue_t
_dnssd_client_queue(void);

static xpc_connection_t
_dnssd_client_connection(void);

static uint64_t
_dnssd_client_get_new_id(void);

static void
_dnssd_client_activate_getaddrinfo_async(dnssd_getaddrinfo_t gai);

static void
_dnssd_client_register_getaddrinfo(dnssd_getaddrinfo_t gai);

static void
_dnssd_client_deregister_getaddrinfo(dnssd_getaddrinfo_t gai);

static OSStatus
_dnssd_client_send_getaddrinfo_command(dnssd_getaddrinfo_t gai);

static void
_dnssd_client_fail_getaddrinfo(dnssd_getaddrinfo_t gai, OSStatus error);

static void
_dnssd_getaddrinfo_append_results(dnssd_getaddrinfo_t gai, dnssd_getaddrinfo_result_t result_list);

static void
_dnssd_getaddrinfo_remove_all_results(dnssd_getaddrinfo_t gai);

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_take_results(dnssd_getaddrinfo_t gai);

static void
_dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t gai, OSStatus error);

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t gai, xpc_object_t result_dict,
	OSStatus *out_error);

static dnssd_cname_array_t
_dnssd_cname_array_create(xpc_object_t xpc_array, OSStatus *out_error);

static dnssd_cname_array_t
_dnssd_get_empty_cname_array(void);

static DNSServiceErrorType
_dnssd_osstatus_to_dns_service_error(OSStatus status);

static int
_dnssd_snprintf(char **dst, const char *end, const char *format, ...);

#if !defined(dnssd_release_null_safe)
	#define dnssd_release_null_safe(X)	\
		do {							\
			if (X) {					\
				dnssd_release(X);		\
			}							\
		} while(0)
#endif

#if !defined(dnssd_forget)
	#define dnssd_forget(X)	ForgetCustom(X, dnssd_release)
#endif

//======================================================================================================================
// MARK: - Object Public Methods

void
dnssd_retain(const dnssd_any_t any)
{
	os_retain(any.object);
}

//======================================================================================================================

void
dnssd_release(const dnssd_any_t any)
{
	os_release(any.object);
}

//======================================================================================================================

char *
dnssd_copy_description(dnssd_any_t object)
{
	return dnssd_object_copy_description(object, false, false);
}

//======================================================================================================================
// MARK: - Object Private Methods

char *
dnssd_object_copy_description(const dnssd_any_t any, const bool debug, const bool privacy)
{
	const dnssd_object_t me = any.object;
	for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) {
		if (kind->copy_description) {
			char *desc = kind->copy_description(me, debug, privacy);
			return desc;
		}
	}
	return NULL;
}

//======================================================================================================================

void
dnssd_object_finalize(const dnssd_any_t any)
{
	const dnssd_object_t me = any.object;
	for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) {
		if (kind->finalize) {
			kind->finalize(me);
		}
	}
}

//======================================================================================================================
// MARK: - GetAddrInfo Public Methods

dnssd_getaddrinfo_t
dnssd_getaddrinfo_create(void)
{
	dnssd_getaddrinfo_t	gai = NULL;
	dnssd_getaddrinfo_t	obj = _dnssd_getaddrinfo_alloc();
	require_quiet(obj, exit);

	obj->params = xpc_dictionary_create(NULL, NULL, 0);
	require_quiet(obj->params, exit);

	obj->mutex_queue = dispatch_queue_create("com.apple.dnssd.getaddrinfo.mutex", DISPATCH_QUEUE_SERIAL);
	require_quiet(obj->mutex_queue, exit);

	gai = obj;
	obj = NULL;

exit:
	dnssd_release_null_safe(obj);
	return gai;
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_queue(dnssd_getaddrinfo_t me, dispatch_queue_t queue)
{
	if (!me->user_activated) {
		dispatch_retain(queue);
		dispatch_release_null_safe(me->user_queue);
		me->user_queue = queue;
	} else if (!me->user_queue) {
		me->user_queue = queue;
		dispatch_retain(me->user_queue);
		_dnssd_client_activate_getaddrinfo_async(me);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_flags(dnssd_getaddrinfo_t me, DNSServiceFlags flags)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_flags(me->params, flags);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_account_id(dnssd_getaddrinfo_t me, const char * account_id)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_account_id(me->params, account_id);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_hostname(dnssd_getaddrinfo_t me, const char *hostname)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_hostname(me->params, hostname);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_interface_index(dnssd_getaddrinfo_t me, uint32_t interface_index)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_interface_index(me->params, interface_index);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_protocols(dnssd_getaddrinfo_t me, DNSServiceProtocol protocols)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_protocols(me->params, protocols);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_service_scheme(dnssd_getaddrinfo_t me, const char *service_scheme)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_service_scheme(me->params, service_scheme);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_delegate_pid(dnssd_getaddrinfo_t me, pid_t pid)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_delegate_pid(me->params, pid);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_delegate_uuid(dnssd_getaddrinfo_t me, uuid_t uuid)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_delegate_uuid(me->params, uuid);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_delegate_audit_token(dnssd_getaddrinfo_t me, audit_token_t audit_token)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_delegate_audit_token(me->params, &audit_token);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_result_handler(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_result_handler_t handler)
{
	dnssd_getaddrinfo_result_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
	BlockForget(&me->result_handler);
	me->result_handler = new_handler;
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_event_handler(dnssd_getaddrinfo_t me, dnssd_event_handler_t handler)
{
	dnssd_event_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
	BlockForget(&me->event_handler);
	me->event_handler = new_handler;
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_need_encrypted_query(dnssd_getaddrinfo_t me, bool need, _Nullable xpc_object_t fallback_config)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_set_need_encrypted_query(me->params, need, fallback_config);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_use_failover(const dnssd_getaddrinfo_t me, const bool use_failover)
{
	require_return(!me->user_activated);
	dnssd_xpc_parameters_set_use_failover(me->params, use_failover);
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_log_privacy_level(const dnssd_getaddrinfo_t me, const dnssd_log_privacy_level_t level)
{
	require_return(!me->user_activated);
	dnssd_xpc_parameters_set_log_privacy_level(me->params, level);
}

//======================================================================================================================

void
dnssd_getaddrinfo_add_resolver_uuid(dnssd_getaddrinfo_t me, uuid_t _Nonnull uuid)
{
	if (!me->user_activated) {
		dnssd_xpc_parameters_add_resolver_uuid(me->params, uuid);
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_set_validation_data(const dnssd_getaddrinfo_t me, const uint8_t * const data_ptr,
	const size_t data_len)
{
	require_return(!me->user_activated);
	dnssd_xpc_parameters_set_validation_data(me->params, data_ptr, data_len);
}

//======================================================================================================================

void
dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t me)
{
	if (!me->user_activated) {
		if (me->user_queue) {
			_dnssd_client_activate_getaddrinfo_async(me);
		}
		me->user_activated = true;
	}
}

//======================================================================================================================

static void
_dnssd_client_invalidate_getaddrinfo(dnssd_getaddrinfo_t gai);

static void
_dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me);

void
dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me)
{
	dnssd_retain(me);
	dispatch_async(_dnssd_client_queue(),
	^{
		_dnssd_client_invalidate_getaddrinfo(me);
		dnssd_release(me);
	});
}

static void
_dnssd_client_invalidate_getaddrinfo(dnssd_getaddrinfo_t gai)
{
	require_quiet(gai->state != dnssd_getaddrinfo_state_invalidated, exit);

	_dnssd_client_deregister_getaddrinfo(gai);
	if ((gai->state == dnssd_getaddrinfo_state_starting) || (gai->state == dnssd_getaddrinfo_state_started)) {
		xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
		if (msg) {
			dnssd_xpc_message_set_id(msg, gai->command_id);
			dnssd_xpc_message_set_command(msg, DNSSD_COMMAND_STOP);
			xpc_connection_send_message_with_reply(_dnssd_client_connection(), msg, _dnssd_client_queue(),
			^(xpc_object_t reply)
			{
				(void)reply;
			});
			xpc_forget(&msg);
		}
	}
	_dnssd_getaddrinfo_invalidate(gai);
	gai->state = dnssd_getaddrinfo_state_invalidated;

exit:
	return;
}

static void
_dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me)
{
	dispatch_source_forget(&me->event_source);
	_dnssd_getaddrinfo_remove_all_results(me);

	if (me->user_queue) {
		dnssd_retain(me);
		dispatch_async(me->user_queue,
		^{
			if (me->event_handler) {
				me->event_handler(dnssd_event_invalidated, kDNSServiceErr_NoError);
			}
			dnssd_release(me);
		});
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_prohibit_encrypted_dns(const dnssd_getaddrinfo_t me, const bool prohibit)
{
	require_return(!me->user_activated);
	dnssd_xpc_parameters_set_prohibit_encrypted_dns(me->params, prohibit);
}

//======================================================================================================================
// MARK: - GetAddrInfo Private Methods

static char *
_dnssd_getaddrinfo_copy_description(dnssd_getaddrinfo_t me, const bool debug, const bool privacy)
{
	const char *hostname_str;
	if (me->hostname) {
		hostname_str = xpc_string_get_string_ptr(me->hostname);
		if (privacy && hostname_str) {
			hostname_str = DNSSD_REDACTED_HOSTNAME_STR;
		}
	} else {
		hostname_str = NULL;
	}
	char *desc = NULL;
	char *buf_ptr = NULL;
	size_t buf_len = 0;
	for (;;)
	{
		int n;
		char * dst = buf_ptr;
		char * const end = &buf_ptr[buf_len];
		size_t desc_len = 0;
		if (debug) {
			n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
			require_quiet(n >= 0, exit);
			desc_len += (size_t)n;
		}
		n = _dnssd_snprintf(&dst, end, "hostname: %s", hostname_str ? hostname_str : "<NO HOSTNAME>");
		require_quiet(n >= 0, exit);
		desc_len += (size_t)n;

		if (!buf_ptr) {
			buf_len = desc_len + 1;
			buf_ptr = (char *)mdns_malloc(buf_len);
			require_quiet(buf_ptr, exit);
			buf_ptr[0] = '\0';
		} else {
			break;
		}
	}
	desc = buf_ptr;
	buf_ptr = NULL;

exit:
	FreeNullSafe(buf_ptr);
	return desc;
}

//======================================================================================================================

static void
_dnssd_getaddrinfo_finalize(dnssd_getaddrinfo_t me)
{
	dispatch_forget(&me->user_queue);
	dispatch_forget(&me->mutex_queue);
	xpc_forget(&me->params);
	xpc_forget(&me->hostname);
	dnssd_forget(&me->cnames_a);
	dnssd_forget(&me->cnames_aaaa);
	BlockForget(&me->result_handler);
	BlockForget(&me->event_handler);
	mdns_xpc_string_forget(&me->tracker_hostname);
	mdns_xpc_string_forget(&me->tracker_owner);
}

//======================================================================================================================

static void
_dnssd_getaddrinfo_append_results(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_result_t result_list)
{
	dispatch_sync(me->mutex_queue,
	^{
		dnssd_getaddrinfo_result_t *ptr = &me->result_list;
		while (*ptr) {
			ptr = &(*ptr)->next;
		}
		*ptr = result_list;
	});
	dispatch_source_merge_data(me->event_source, DNSSD_EVENT_HAVE_RESULTS);
}

//======================================================================================================================

static void
_dnssd_getaddrinfo_remove_all_results(dnssd_getaddrinfo_t me)
{
	dnssd_forget(&me->cnames_a);
	dnssd_forget(&me->cnames_aaaa);
	dnssd_getaddrinfo_result_t result_list = _dnssd_getaddrinfo_take_results(me);
	if (me->event_source) {
		dispatch_source_merge_data(me->event_source, DNSSD_EVENT_REMOVE_ALL);
	}

	dnssd_getaddrinfo_result_t result;
	while ((result = result_list) != NULL) {
		result_list = result->next;
		dnssd_release(result);
	}
}

//======================================================================================================================

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_take_results(dnssd_getaddrinfo_t me)
{
	__block dnssd_getaddrinfo_result_t list;
	dispatch_sync(me->mutex_queue,
	^{
		list = me->result_list;
		me->result_list = NULL;
	});
	return list;
}

//======================================================================================================================

static void
_dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t me, OSStatus error)
{
	dispatch_sync(me->mutex_queue,
	^{
		me->error = error;
	});
	dispatch_source_merge_data(me->event_source, DNSSD_EVENT_ERROR);
}

//======================================================================================================================
// MARK: - GetAddrInfo Result Public Methods

dnssd_getaddrinfo_result_type_t
dnssd_getaddrinfo_result_get_type(dnssd_getaddrinfo_result_t me)
{
	return me->type;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_actual_hostname(dnssd_getaddrinfo_result_t me)
{
	const char * const tmp = xpc_string_get_string_ptr(me->actual_hostname);
	return tmp;
}

//======================================================================================================================

const struct sockaddr *
dnssd_getaddrinfo_result_get_address(dnssd_getaddrinfo_result_t me)
{
	return &me->addr.sa;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_hostname(dnssd_getaddrinfo_result_t me)
{
	const char * const tmp = xpc_string_get_string_ptr(me->hostname);
	return tmp;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_doh_uri(dnssd_getaddrinfo_result_t me)
{
	return xpc_string_get_string_ptr(me->doh_uri);
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_doh_path(dnssd_getaddrinfo_result_t me)
{
	return xpc_string_get_string_ptr(me->doh_path);
}

//======================================================================================================================

uint16_t
dnssd_getaddrinfo_result_get_service_port(dnssd_getaddrinfo_result_t me)
{
	return me->port;
}

//======================================================================================================================

uint16_t
dnssd_getaddrinfo_result_get_service_priority(dnssd_getaddrinfo_result_t me)
{
	return me->priority;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_service_name(dnssd_getaddrinfo_result_t me)
{
	return xpc_string_get_string_ptr(me->service_name);
}

//======================================================================================================================

bool
dnssd_getaddrinfo_result_service_is_valid(dnssd_getaddrinfo_result_t me)
{
	return me->valid_svcb;
}

//======================================================================================================================

void
dnssd_getaddrinfo_result_enumerate_alpn_values(dnssd_getaddrinfo_result_t me,
											   DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_alpn_values_block_t enumerator)
{
	if (me->alpn_values != NULL) {
		xpc_array_apply(me->alpn_values, ^bool(__unused size_t index, xpc_object_t _Nonnull value) {
			const char *string = xpc_string_get_string_ptr(value);
			return enumerator(string);
		});
	}
}

//======================================================================================================================

void
dnssd_getaddrinfo_result_enumerate_service_address_hints(dnssd_getaddrinfo_result_t me,
														 DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_addresses_block_t enumerator)
{
	if (me->address_hints != NULL) {
		xpc_array_apply(me->address_hints, ^bool(__unused size_t index, xpc_object_t _Nonnull value) {
			const void *bytes = xpc_data_get_bytes_ptr(value);
			return enumerator((const struct sockaddr *)bytes);
		});
	}
}

//======================================================================================================================

const void *
dnssd_getaddrinfo_result_get_ech_config(dnssd_getaddrinfo_result_t me, size_t *out_length)
{
	const void *	ech_ptr;
	size_t			ech_len;

	if (me->ech_config) {
		ech_ptr = xpc_data_get_bytes_ptr(me->ech_config);
		ech_len = xpc_data_get_length(me->ech_config);
	} else {
		ech_ptr = NULL;
		ech_len = 0;
	}
	if (out_length) {
		*out_length = ech_len;
	}
	return ech_ptr;
}

//======================================================================================================================

const void *
dnssd_getaddrinfo_result_get_odoh_config(dnssd_getaddrinfo_result_t me, size_t *out_length)
{
	const void *	odoh_ptr;
	size_t			odoh_len;

	if (me->odoh_config) {
		odoh_ptr = xpc_data_get_bytes_ptr(me->odoh_config);
		odoh_len = xpc_data_get_length(me->odoh_config);
	} else {
		odoh_ptr = NULL;
		odoh_len = 0;
	}
	if (out_length) {
		*out_length = odoh_len;
	}
	return odoh_ptr;
}

//======================================================================================================================

uint32_t
dnssd_getaddrinfo_result_get_interface_index(dnssd_getaddrinfo_result_t me)
{
	return me->if_index;
}

//======================================================================================================================

dnssd_getaddrinfo_result_protocol_t
dnssd_getaddrinfo_result_get_protocol(dnssd_getaddrinfo_result_t me)
{
	return me->protocol;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_provider_name(dnssd_getaddrinfo_result_t me)
{
	return xpc_string_get_string_ptr(me->provider_name);
}

//======================================================================================================================

dnssd_cname_array_t
dnssd_getaddrinfo_result_get_cnames(const dnssd_getaddrinfo_result_t me)
{
	return (me->cnames ? me->cnames : _dnssd_get_empty_cname_array());
}

//======================================================================================================================

bool
dnssd_getaddrinfo_result_is_from_cache(const dnssd_getaddrinfo_result_t me)
{
	return me->is_from_cache;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_tracker_hostname(const dnssd_getaddrinfo_result_t me)
{
	return mdns_xpc_string_get_string_ptr(me->tracker_hostname);
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_tracker_owner(const dnssd_getaddrinfo_result_t me)
{
	return mdns_xpc_string_get_string_ptr(me->tracker_owner);
}

//======================================================================================================================

bool
dnssd_getaddrinfo_result_get_tracker_is_approved(const dnssd_getaddrinfo_result_t me)
{
	return me->tracker_approved;
}

//======================================================================================================================

bool
dnssd_getaddrinfo_result_get_tracker_can_block_request(const dnssd_getaddrinfo_result_t me)
{
	return me->tracker_can_block;
}

//======================================================================================================================

dnssd_negative_reason_t
dnssd_getaddrinfo_result_get_negative_reason(const dnssd_getaddrinfo_result_t me)
{
	return me->negative_reason;
}

//======================================================================================================================

const uint8_t *
dnssd_getaddrinfo_result_get_validation_data(const dnssd_getaddrinfo_result_t me, size_t * const out_length)
{
	const uint8_t *data_ptr;
	size_t data_len;
	if (me->validation_data) {
		data_ptr = xpc_data_get_bytes_ptr(me->validation_data);
		data_len = xpc_data_get_length(me->validation_data);
	} else {
		data_ptr = NULL;
		data_len = 0;
	}
	if (out_length) {
		*out_length = data_len;
	}
	return data_ptr;
}

//======================================================================================================================

bool
dnssd_getaddrinfo_result_has_extended_dns_error(const dnssd_getaddrinfo_result_t me)
{
	return me->ede_valid;
}

//======================================================================================================================

uint16_t
dnssd_getaddrinfo_result_get_extended_dns_error_code(const dnssd_getaddrinfo_result_t me)
{
	return me->ede_code;
}

//======================================================================================================================

const char *
dnssd_getaddrinfo_result_get_extended_dns_error_text(const dnssd_getaddrinfo_result_t me)
{
	const char *text = mdns_xpc_string_get_string_ptr(me->ede_text);
	return (text ? text : "");
}

//======================================================================================================================
// MARK: - GetAddrInfo Result Private Methods

static char *
_dnssd_getaddrinfo_result_copy_description(dnssd_getaddrinfo_result_t me, const bool debug, const bool privacy)
{
	const char *hostname;
	if (me->hostname) {
		hostname = xpc_string_get_string_ptr(me->hostname);
		if (privacy && hostname) {
			hostname = DNSSD_REDACTED_HOSTNAME_STR;
		}
	} else {
		hostname = NULL;
	}
	char addr_buf[INET6_ADDRSTRLEN + 1 + Max(IF_NAMESIZE, 10) + 1];
	const char *addr_str;
	if (me->addr.sa.sa_family == AF_INET) {
		if (privacy) {
			addr_str = DNSSD_REDACTED_IPv4_ADDRESS_STR;
		} else {
			mdns_compile_time_check_local(sizeof(addr_buf) >= INET_ADDRSTRLEN);
			addr_str = inet_ntop(AF_INET, &me->addr.v4.sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf));
		}
	} else if (me->addr.sa.sa_family == AF_INET6) {
		if (privacy) {
			addr_str = DNSSD_REDACTED_IPv6_ADDRESS_STR;
		} else {
			const struct sockaddr_in6 * const sin6 = &me->addr.v6;
			mdns_compile_time_check_local(sizeof(addr_buf) >= INET6_ADDRSTRLEN);
			addr_str = inet_ntop(AF_INET6, sin6->sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
			if (addr_str && (sin6->sin6_scope_id > 0)) {
				char * const		dst = &addr_buf[strlen(addr_buf)];
				const char * const	end = &addr_buf[countof(addr_buf)];
				char ifname[IF_NAMESIZE + 1];
				if (if_indextoname(sin6->sin6_scope_id, ifname)) {
					snprintf(dst, (size_t)(end - dst), "%%%s", ifname);
				} else {
					snprintf(dst, (size_t)(end - dst), "%%%u", sin6->sin6_scope_id);
				}
			}
		}
	} else {
		addr_str = NULL;
	}
	char *desc = NULL;
	char *buf_ptr = NULL;
	size_t buf_len = 0;
	for (;;)
	{
		char *dst = buf_ptr;
		char * const end = &buf_ptr[buf_len];
		size_t desc_len = 0;
		int n;
		if (debug) {
			n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
			require_quiet(n >= 0, exit);
			desc_len += (size_t)n;
		}
		n = _dnssd_snprintf(&dst, end, "hostname: %s, address: %s, type: %s, ifindex: %lu",
			hostname ? hostname : "<NO HOSTNAME>", addr_str ? addr_str : "<NO ADDR>",
			dnssd_getaddrinfo_result_type_to_string(me->type), (unsigned long)me->if_index);
		require_quiet(n >= 0, exit);
		desc_len += (size_t)n;
		if (!buf_ptr) {
			buf_len = desc_len + 1;
			buf_ptr = (char *)mdns_malloc(buf_len);
			require_quiet(buf_ptr, exit);
			buf_ptr[0] = '\0';
		} else {
			break;
		}
	}
	desc = buf_ptr;
	buf_ptr	= NULL;

exit:
	FreeNullSafe(buf_ptr);
	return desc;
}

//======================================================================================================================

void
_dnssd_getaddrinfo_result_finalize(dnssd_getaddrinfo_result_t me)
{
	xpc_forget(&me->hostname);
	xpc_forget(&me->actual_hostname);
	dnssd_forget(&me->cnames);
	xpc_forget(&me->provider_name);
	xpc_forget(&me->doh_uri);
	xpc_forget(&me->doh_path);
	xpc_forget(&me->odoh_config);
	xpc_forget(&me->alpn_values);
	xpc_forget(&me->service_name);
	mdns_xpc_string_forget(&me->tracker_hostname);
	mdns_xpc_string_forget(&me->tracker_owner);
	xpc_forget(&me->ech_config);
	xpc_forget(&me->address_hints);
	xpc_forget(&me->validation_data);
	mdns_xpc_string_forget(&me->ede_text);
}

//======================================================================================================================

static OSStatus
_dnssd_getaddrinfo_set_cnames(const dnssd_getaddrinfo_t me, const int record_type, const xpc_object_t xpc_cname_array)
{
	dnssd_cname_array_t *cnames_ptr;
	switch (record_type) {
		case kDNSServiceType_A:
			cnames_ptr = &me->cnames_a;
			break;

		case kDNSServiceType_AAAA:
			cnames_ptr = &me->cnames_aaaa;
			break;

		default:
			cnames_ptr = NULL;
			break;
	}
	OSStatus err;
	if (cnames_ptr) {
		dnssd_forget(cnames_ptr);
		*cnames_ptr = _dnssd_cname_array_create(xpc_cname_array, &err);
		require_noerr_quiet(err, exit);
	}
	err = kNoErr;

exit:
	return err;
}

//======================================================================================================================

static xpc_object_t
_dnssd_getaddrinfo_get_cname_array(const dnssd_getaddrinfo_t me, const int type)
{
	switch (type) {
		case kDNSServiceType_A:
			return me->cnames_a;

		case kDNSServiceType_AAAA:
			return me->cnames_aaaa;

		default:
			return NULL;
	}
}

//======================================================================================================================
// MARK: - dnssd_cname_array Public Methods

size_t
dnssd_cname_array_get_count(const dnssd_cname_array_t me)
{
	return (me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0);
}

//======================================================================================================================

const char *
dnssd_cname_array_get_cname(const dnssd_cname_array_t me, const size_t index)
{
	return (me->xpc_array ? xpc_array_get_string(me->xpc_array, index) : NULL);
}

//======================================================================================================================
// MARK: - dnssd_cname_array Private Methods

static dnssd_cname_array_t
_dnssd_cname_array_create(const xpc_object_t xpc_array, OSStatus * const out_error)
{
	OSStatus err;
	dnssd_cname_array_t array = NULL;
	dnssd_cname_array_t obj = _dnssd_cname_array_alloc();
	require_action_quiet(obj, exit, err = kNoMemoryErr);

	if (xpc_array) {
		obj->xpc_array = xpc_copy(xpc_array);
		require_action_quiet(obj->xpc_array, exit, err = kNoResourcesErr);
	}
	array = obj;
	obj = NULL;
	err = kNoErr;

exit:
	if (out_error) {
		*out_error = err;
	}
	dnssd_release_null_safe(obj);
	return array;
}

//======================================================================================================================

static char *
_dnssd_cname_array_copy_description(const dnssd_cname_array_t me, const bool debug, const bool privacy)
{
	char *desc = NULL;
	char *buf_ptr = NULL;
	size_t buf_len = 0;
	for (;;)
	{
		__block int n;
		__block char *dst = buf_ptr;
		const char * const end = &buf_ptr[buf_len];
		__block size_t desc_len = 0;
		if (debug) {
			n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
			require_quiet(n >= 0, exit);
			desc_len += (size_t)n;
		}
		n = _dnssd_snprintf(&dst, end, "[");
		require_quiet(n >= 0, exit);
		desc_len += (size_t)n;

		if (privacy) {
			n = _dnssd_snprintf(&dst, end, "<%zu redacted cnames>",
				me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0);
			require_quiet(n >= 0, exit);
			desc_len += (size_t)n;
		} else if (me->xpc_array) {
			const bool ok = xpc_array_apply(me->xpc_array,
			^ bool (const size_t index, const xpc_object_t _Nonnull cname)
			{
				const char *cname_str = xpc_string_get_string_ptr(cname);
				if (!cname_str) {
					cname_str = "";
				}
				n = _dnssd_snprintf(&dst, end, "%s%s", (index == 0) ? "" : ", ", cname_str);
				if (likely(n >= 0)) {
					desc_len += (size_t)n;
					return true;
				} else {
					return false;
				}
			});
			require_quiet(ok, exit);
		}
		n = _dnssd_snprintf(&dst, end, "]");
		require_quiet(n >= 0, exit);
		desc_len += (size_t)n;

		if (!buf_ptr) {
			buf_len = desc_len + 1;
			buf_ptr = (char *)mdns_malloc(buf_len);
			require_quiet(buf_ptr, exit);
			buf_ptr[0] = '\0';
		} else {
			break;
		}
	}
	desc = buf_ptr;
	buf_ptr = NULL;

exit:
	FreeNullSafe(buf_ptr);
	return desc;
}

//======================================================================================================================

void
_dnssd_cname_array_finalize(dnssd_cname_array_t me)
{
	xpc_forget(&me->xpc_array);
}

//======================================================================================================================
// MARK: - dnssd Client

static dnssd_getaddrinfo_t g_gai_list = NULL;

static dispatch_queue_t
_dnssd_client_queue(void)
{
	static dispatch_once_t	once	= 0;
	static dispatch_queue_t	queue	= NULL;

	dispatch_once(&once,
	^{
		queue = dispatch_queue_create("com.apple.dnssd.client", DISPATCH_QUEUE_SERIAL);
	});
	return queue;
}

//======================================================================================================================

static void
_dnssd_client_handle_message(xpc_object_t msg);
static void
_dnssd_client_handle_interruption(void);

static xpc_connection_t
_dnssd_client_connection(void)
{
	static dispatch_once_t	once		= 0;
	static xpc_connection_t	connection	= NULL;

	dispatch_once(&once,
	^{
		connection = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dnssd_client_queue(),
			XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
		xpc_connection_set_event_handler(connection,
		^(xpc_object_t event)
		{
			if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
				_dnssd_client_handle_message(event);
			} else if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
				_dnssd_client_handle_interruption();
			}
		});
		xpc_connection_activate(connection);
	});
	return connection;
}

static void
_dnssd_client_handle_message(xpc_object_t msg)
{
	// Check if the server sent a keepalive command, which requires an immediate reply.
	const char * const command = dnssd_xpc_message_get_command(msg);
	if (command && (strcmp(command, DNSSD_COMMAND_KEEPALIVE) == 0)) {
		xpc_object_t reply = xpc_dictionary_create_reply(msg);
		if (reply) {
			xpc_dictionary_send_reply(reply);
			xpc_forget(&reply);
		}
		goto exit;
	}
	const uint64_t command_id = dnssd_xpc_message_get_id(msg, NULL);
	dnssd_getaddrinfo_t gai;
	for (gai = g_gai_list; gai; gai = gai->next) {
		if (gai->command_id == command_id) {
			break;
		}
	}
	require_quiet(gai, exit);

	const OSStatus error = dnssd_xpc_message_get_error(msg, NULL);
	if (!error) {
		xpc_object_t const result_array = dnssd_xpc_message_get_results(msg);
		require_quiet(result_array, exit);

		dnssd_getaddrinfo_result_t				result_list	= NULL;
		__block dnssd_getaddrinfo_result_t *	result_ptr	= &result_list;
		xpc_array_apply(result_array,
		^ bool (__unused size_t index, xpc_object_t _Nonnull result_dict)
		{
			const dnssd_getaddrinfo_result_t result = _dnssd_getaddrinfo_create_result_from_dictionary(gai, result_dict,
				NULL);
			if (result) {
				*result_ptr	= result;
				result_ptr	= &result->next;
			}
			return true;
		});
		require_quiet(result_list, exit);

		_dnssd_getaddrinfo_append_results(gai, result_list);
		result_list = NULL;
	} else {
		_dnssd_client_fail_getaddrinfo(gai, error);
	}

exit:
	return;
}

static void
_dnssd_client_handle_interruption(void)
{
	dnssd_getaddrinfo_t next_gai;
	for (dnssd_getaddrinfo_t gai = g_gai_list; gai; gai = next_gai) {
		next_gai = gai->next;
		gai->state = dnssd_getaddrinfo_state_starting;
		const OSStatus err = _dnssd_client_send_getaddrinfo_command(gai);
		if (!err) {
			_dnssd_getaddrinfo_remove_all_results(gai);
		} else {
			_dnssd_client_fail_getaddrinfo(gai, err);
		}
	}
}

//======================================================================================================================

static uint64_t
_dnssd_client_get_new_id(void)
{
	static uint64_t last_id = 0;
	return ++last_id;
}

//======================================================================================================================

static void
_dnssd_client_activate_getaddrinfo(dnssd_getaddrinfo_t gai);

static OSStatus
_dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t gai);

static void
_dnssd_client_activate_getaddrinfo_async(dnssd_getaddrinfo_t gai)
{
	dnssd_retain(gai);
	dispatch_async(_dnssd_client_queue(),
	^{
		_dnssd_client_activate_getaddrinfo(gai);
		dnssd_release(gai);
	});
}

static void
_dnssd_client_activate_getaddrinfo(dnssd_getaddrinfo_t gai)
{
	OSStatus err;
	require_action_quiet(gai->state == dnssd_getaddrinfo_state_nascent, exit, err = kNoErr);

	err = _dnssd_getaddrinfo_activate(gai);
	if (err) {
		gai->state = dnssd_getaddrinfo_state_failed;
		goto exit;
	}

	gai->command_id = _dnssd_client_get_new_id();
	gai->state = dnssd_getaddrinfo_state_starting;

	_dnssd_client_register_getaddrinfo(gai);

	err = _dnssd_client_send_getaddrinfo_command(gai);
	if (err) {
		_dnssd_client_fail_getaddrinfo(gai, err);
	}

exit:
	return;
}

static void
_dnssd_getaddrinfo_process_events(dnssd_getaddrinfo_t gai, unsigned long events);

static OSStatus
_dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t me)
{
	OSStatus err;
	xpc_object_t const hostname = dnssd_xpc_parameters_get_hostname_object(me->params);
	require_action_quiet(hostname, exit, err = kParamErr);

	me->hostname = xpc_copy(hostname);
	require_action_quiet(me->hostname, exit, err = kNoResourcesErr);

	me->event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, me->user_queue);
	require_action_quiet(me->event_source, exit, err = kNoResourcesErr);

	dnssd_retain(me);
	dispatch_source_t const event_source = me->event_source;
	dispatch_source_set_event_handler(me->event_source,
	^{
		_dnssd_getaddrinfo_process_events(me, dispatch_source_get_data(event_source));
	});
	dispatch_source_set_cancel_handler(me->event_source,
	^{
		dnssd_release(me);
	});
	dispatch_activate(me->event_source);
	err = kNoErr;

exit:
	if (err) {
		dnssd_retain(me);
		dispatch_async(me->user_queue,
		^{
			if (me->event_handler) {
				me->event_handler(dnssd_event_error, _dnssd_osstatus_to_dns_service_error(err));
			}
			dnssd_release(me);
		});
	}
	return err;
}

static void
_dnssd_getaddrinfo_process_events(dnssd_getaddrinfo_t me, unsigned long events)
{
	if (events & DNSSD_EVENT_REMOVE_ALL) {
		if (me->event_handler) {
			me->event_handler(dnssd_event_remove_all, kDNSServiceErr_NoError);
		}
	}

	if (events & DNSSD_EVENT_HAVE_RESULTS) {
		dnssd_getaddrinfo_result_t result;
		dnssd_getaddrinfo_result_t result_array[32];
		dnssd_getaddrinfo_result_t result_list = _dnssd_getaddrinfo_take_results(me);

		size_t result_count = 0;
		while ((result = result_list) != NULL) {
			result_list		= result->next;
			result->next	= NULL;
			result_array[result_count++] = result;

			if ((result_count == countof(result_array)) || !result_list) {
				if (me->result_handler) {
					me->result_handler(result_array, result_count);
				}
				for (size_t i = 0; i < result_count; ++i) {
					dnssd_release(result_array[i]);
				}
				result_count = 0;
			}
		}
	}

	if (events & DNSSD_EVENT_ERROR) {
		__block OSStatus error;
		dispatch_sync(me->mutex_queue,
		^{
			error = me->error;
			me->error = kNoErr;
		});
		if (me->event_handler && error) {
			me->event_handler(dnssd_event_error, _dnssd_osstatus_to_dns_service_error(error));
		}
	}
}

//======================================================================================================================

static void
_dnssd_client_register_getaddrinfo(dnssd_getaddrinfo_t gai)
{
	gai->next	= g_gai_list;
	g_gai_list	= gai;
	dnssd_retain(gai);
}

//======================================================================================================================

static void
_dnssd_client_deregister_getaddrinfo(dnssd_getaddrinfo_t gai)
{
	dnssd_getaddrinfo_t *ptr;
	for (ptr = &g_gai_list; *ptr; ptr = &(*ptr)->next)
	{
		if (*ptr == gai) {
			break;
		}
	}
	if (*ptr) {
		*ptr = gai->next;
		gai->next = NULL;
		dnssd_release(gai);
	}
}

//======================================================================================================================

static void
_dnssd_client_handle_getaddrinfo_reply(dnssd_getaddrinfo_t gai, xpc_object_t reply);

static OSStatus
_dnssd_client_send_getaddrinfo_command(dnssd_getaddrinfo_t gai)
{
	OSStatus err;
	xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
	require_action_quiet(msg, exit, err = kNoResourcesErr);

	dnssd_xpc_message_set_id(msg, gai->command_id);
	dnssd_xpc_message_set_command(msg, DNSSD_COMMAND_GETADDRINFO);
	dnssd_xpc_message_set_parameters(msg, gai->params);

	dnssd_retain(gai);
	xpc_connection_send_message_with_reply(_dnssd_client_connection(), msg, _dnssd_client_queue(),
	^(xpc_object_t reply)
	{
		_dnssd_client_handle_getaddrinfo_reply(gai, reply);
		dnssd_release(gai);
	});
	xpc_forget(&msg);
	err = kNoErr;

exit:
	return err;
}

static void
_dnssd_client_handle_getaddrinfo_reply(dnssd_getaddrinfo_t gai, xpc_object_t reply)
{
	require_quiet(gai->state == dnssd_getaddrinfo_state_starting, exit);

	if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
		const OSStatus error = dnssd_xpc_message_get_error(reply, NULL);
		if (error) {
			_dnssd_client_fail_getaddrinfo(gai, error);
		} else {
			gai->state = dnssd_getaddrinfo_state_started;
		}
	} else if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) {
		OSStatus error;
		if (reply == XPC_ERROR_CONNECTION_INVALID) {
			error = kDNSServiceErr_ServiceNotRunning;
		} else {
			error = kDNSServiceErr_Unknown;
		}
		_dnssd_client_fail_getaddrinfo(gai, error);
	}

exit:
	return;
}

//======================================================================================================================

static void
_dnssd_client_fail_getaddrinfo(dnssd_getaddrinfo_t gai, OSStatus error)
{
	_dnssd_client_deregister_getaddrinfo(gai);
	gai->state = dnssd_getaddrinfo_state_failed;
	_dnssd_getaddrinfo_post_error_event(gai, error);
}

//======================================================================================================================

static bool
_dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error,
	DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class,
	xpc_object_t *out_rdata, dnssd_getaddrinfo_result_protocol_t *out_protocol, xpc_object_t *out_provider_name,
	mdns_xpc_string_t *out_tracker_hostname, mdns_xpc_string_t *out_tracker_owner, bool *out_tracker_approved,
	bool *out_tracker_can_block_request, dnssd_negative_reason_t *out_negative_reason,
	xpc_object_t *out_validation_data, mdns_xpc_dictionary_t *out_extended_dns_error);

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_object_t hostname,
	xpc_object_t actual_hostname, dnssd_cname_array_t cname_array, int addr_family, const void *addr_data,
	uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol, xpc_object_t provider_name,
	mdns_xpc_string_t tracker_hostname, mdns_xpc_string_t tracker_owner, bool tracker_approved,
	bool tracker_can_block_request, dnssd_negative_reason_t negative_reason, xpc_object_t validation_data,
	mdns_xpc_dictionary_t extended_dns_error, OSStatus *out_error);

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data,
	size_t svcb_length, uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol,
	xpc_object_t provider_name, mdns_xpc_string_t tracker_hostname, mdns_xpc_string_t tracker_owner,
	bool tracker_approved, bool tracker_can_block_request, dnssd_negative_reason_t negative_reason,
	xpc_object_t validation_data, OSStatus *out_error);

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t me, xpc_object_t result_dict, OSStatus *out_error)
{
	OSStatus					err;
	xpc_object_t				actual_hostname, rdata, provider_name;
	mdns_xpc_string_t			tracker_hostname, tracker_owner;
	DNSServiceErrorType			error;
	DNSServiceFlags				flags;
	uint32_t					if_index;
	uint16_t					rtype;
	bool						tracker_approved;
	bool						tracker_can_block_request;
	dnssd_getaddrinfo_result_protocol_t protocol;
	dnssd_negative_reason_t		negative_reason;
	xpc_object_t				validation_data;
	mdns_xpc_dictionary_t		extended_dns_error;

	dnssd_getaddrinfo_result_t result = NULL;
	const bool ok = _dnssd_extract_result_dict_values(result_dict, &actual_hostname, &error, &flags, &if_index,
		&rtype, NULL, &rdata, &protocol, &provider_name, &tracker_hostname, &tracker_owner, &tracker_approved,
		&tracker_can_block_request, &negative_reason, &validation_data, &extended_dns_error);
	require_action_quiet(ok, exit, err = kMalformedErr);
	require_action_quiet((error == kDNSServiceErr_NoError) || (error == kDNSServiceErr_NoSuchRecord), exit,
		err = kUnexpectedErr);

	// Dedupe the tracker hostname and owner strings, which will very likely be the same across multiple results.
	if (tracker_hostname) {
		mdns_xpc_string_replace_if_different(&me->tracker_hostname, tracker_hostname);
		tracker_hostname = me->tracker_hostname;
	}
	if (tracker_owner) {
		mdns_xpc_string_replace_if_different(&me->tracker_owner, tracker_owner);
		tracker_owner = me->tracker_owner;
	}
	switch(rtype) {
		case kDNSServiceType_A:
		case kDNSServiceType_AAAA: {
			const xpc_object_t cname_update = dnssd_xpc_result_get_cname_update(result_dict);
			if (cname_update) {
				_dnssd_getaddrinfo_set_cnames(me, rtype, cname_update);
			}
			dnssd_getaddrinfo_result_type_t result_type;
			if (error == kDNSServiceErr_NoSuchRecord) {
				result_type = dnssd_getaddrinfo_result_type_no_address;
			} else {
				if (flags & kDNSServiceFlagsAdd) {
					if (flags & kDNSServiceFlagsExpiredAnswer) {
						result_type = dnssd_getaddrinfo_result_type_expired;
					} else {
						result_type = dnssd_getaddrinfo_result_type_add;
					}
				} else {
					result_type = dnssd_getaddrinfo_result_type_remove;
				}
				if (rtype == kDNSServiceType_A) {
					require_action_quiet(xpc_data_get_length(rdata) == 4, exit, err = kMalformedErr);
				} else {
					require_action_quiet(xpc_data_get_length(rdata) == 16, exit, err = kMalformedErr);
				}
			}
			const int addr_family = (rtype == kDNSServiceType_A) ? AF_INET : AF_INET6;
			result = _dnssd_getaddrinfo_result_create(result_type, me->hostname, actual_hostname,
				_dnssd_getaddrinfo_get_cname_array(me, rtype), addr_family, xpc_data_get_bytes_ptr(rdata), if_index,
				protocol, provider_name, tracker_hostname, tracker_owner, tracker_approved, tracker_can_block_request,
				negative_reason, validation_data, extended_dns_error, &err);
			require_noerr_quiet(err, exit);
			break;
		}
		case kDNSServiceType_SVCB:
		case kDNSServiceType_HTTPS: {
			if (error != kDNSServiceErr_NoSuchRecord) {
				require_action_quiet(xpc_data_get_length(rdata) > 0, exit, err = kMalformedErr);
			}

			// SVCB type answer
			result = _dnssd_getaddrinfo_result_create_svcb(me->hostname, actual_hostname,
				xpc_data_get_bytes_ptr(rdata), xpc_data_get_length(rdata), if_index, protocol, provider_name,
				tracker_hostname, tracker_owner, tracker_approved, tracker_can_block_request, negative_reason,
				validation_data, &err);
			require_noerr_quiet(err, exit);
			break;
		}
		default:
			err = kTypeErr;
			goto exit;
	}
	if ((flags & kDNSServiceFlagsAdd) && (flags & kDNSServiceFlagAnsweredFromCache)) {
		result->is_from_cache = true;
	}

exit:
	if (err) {
		dnssd_forget(&result);
	}
	if (out_error) {
		*out_error = err;
	}
	return result;
}

static bool
_dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error,
	DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class,
	xpc_object_t *out_rdata, dnssd_getaddrinfo_result_protocol_t *out_protocol, xpc_object_t *out_provider_name,
	mdns_xpc_string_t *out_tracker_hostname, mdns_xpc_string_t *out_tracker_owner, bool *out_tracker_approved,
	bool *out_tracker_can_block_request, dnssd_negative_reason_t *out_negative_reason,
	xpc_object_t *out_validation_data, mdns_xpc_dictionary_t *out_extended_dns_error)
{
	bool result_is_valid = false;
	xpc_object_t const hostname = dnssd_xpc_result_get_record_name_object(result);
	require_quiet(hostname, exit);

	xpc_object_t const rdata = dnssd_xpc_result_get_record_data_object(result);
	require_quiet(rdata, exit);

	if (out_hostname) {
		*out_hostname = hostname;
	}
	if (out_error) {
		*out_error = dnssd_xpc_result_get_error(result, NULL);
	}
	if (out_flags) {
		*out_flags = dnssd_xpc_result_get_flags(result, NULL);
	}
	if (out_interface_index) {
		*out_interface_index = dnssd_xpc_result_get_interface_index(result, NULL);
	}
	if (out_type) {
		*out_type = dnssd_xpc_result_get_record_type(result, NULL);
	}
	if (out_class) {
		*out_class = dnssd_xpc_result_get_record_class(result, NULL);
	}
	if (out_rdata) {
		*out_rdata = rdata;
	}
	if (out_protocol) {
		*out_protocol = dnssd_xpc_result_get_record_protocol(result, NULL);
	}
	if (out_provider_name) {
		*out_provider_name = dnssd_xpc_result_get_provider_name_object(result);
	}
	if (out_tracker_hostname) {
		*out_tracker_hostname = dnssd_xpc_result_get_tracker_hostname(result);
	}
	if (out_tracker_owner) {
		*out_tracker_owner = dnssd_xpc_result_get_tracker_owner(result);
	}
	if (out_tracker_approved) {
		*out_tracker_approved = dnssd_xpc_result_get_tracker_is_approved(result);
	}
	if (out_tracker_can_block_request) {
		*out_tracker_can_block_request = dnssd_xpc_result_get_tracker_can_block_request(result);
	}
	if (out_negative_reason) {
		*out_negative_reason = dnssd_xpc_result_get_negative_reason(result);
	}
	if (out_validation_data) {
		*out_validation_data = dnssd_xpc_result_get_validation_data_object(result);
	}
	if (out_extended_dns_error) {
		*out_extended_dns_error = dnssd_xpc_result_get_extended_dns_error(result);
	}
	result_is_valid = true;

exit:
	return result_is_valid;
}

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_result_create(const dnssd_getaddrinfo_result_type_t type, const xpc_object_t hostname,
	const xpc_object_t actual_hostname, const dnssd_cname_array_t cnames, const int addr_family,
	const void * const addr_data, const uint32_t if_index, const dnssd_getaddrinfo_result_protocol_t protocol,
	const xpc_object_t provider_name, const mdns_xpc_string_t tracker_hostname, const mdns_xpc_string_t tracker_owner,
	const bool tracker_approved, const bool tracker_can_block_request, const dnssd_negative_reason_t negative_reason,
	const xpc_object_t validation_data, const mdns_xpc_dictionary_t extended_dns_error, OSStatus * const out_error)
{
	OSStatus err;
	dnssd_getaddrinfo_result_t result = NULL;
	dnssd_getaddrinfo_result_t obj = _dnssd_getaddrinfo_result_alloc();
	require_action_quiet(obj, exit, err = kNoMemoryErr);

	switch (type) {
		case dnssd_getaddrinfo_result_type_add:
		case dnssd_getaddrinfo_result_type_remove:
		case dnssd_getaddrinfo_result_type_no_address:
		case dnssd_getaddrinfo_result_type_expired:
			break;

		case dnssd_getaddrinfo_result_type_service_binding:
		MDNS_COVERED_SWITCH_DEFAULT:
			err = kTypeErr;
			goto exit;
	}
	obj->type				= type;
	obj->if_index			= if_index;
	obj->protocol			= protocol;
	obj->negative_reason	= negative_reason;

	require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);

	obj->hostname = xpc_copy(hostname);
	require_action_quiet(obj->hostname, exit, err = kNoResourcesErr);

	require_action_quiet(xpc_get_type(actual_hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);

	obj->actual_hostname = xpc_copy(actual_hostname);
	require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr);

	obj->cnames = cnames ? cnames : _dnssd_get_empty_cname_array();
	dnssd_retain(obj->cnames);
	require_action_quiet((addr_family == AF_INET) || (addr_family == AF_INET6), exit, err = kTypeErr);

	if (addr_family == AF_INET) {
		obj->addr.sa.sa_family	= AF_INET;
		obj->addr.v4.sin_len	= sizeof(struct sockaddr_in);
		if (obj->type != dnssd_getaddrinfo_result_type_no_address) {
			memcpy(&obj->addr.v4.sin_addr.s_addr, addr_data, 4);
		}
	} else if (addr_family == AF_INET6) {
		struct sockaddr_in6 * const sin6 = &obj->addr.v6;
		sin6->sin6_family	= AF_INET6;
		sin6->sin6_len		= sizeof(struct sockaddr_in6);
		if (obj->type != dnssd_getaddrinfo_result_type_no_address) {
			memcpy(&sin6->sin6_addr.s6_addr, addr_data, 16);
			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
				sin6->sin6_scope_id = obj->if_index;
			}
		}
	}
	if (provider_name) {
		require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr);

		obj->provider_name = xpc_copy(provider_name);
		require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr);
	}
	if (tracker_hostname) {
		obj->tracker_hostname = mdns_xpc_string_retain(tracker_hostname);
		if (tracker_owner) {
			obj->tracker_owner = mdns_xpc_string_retain(tracker_owner);
		}
		obj->tracker_approved = tracker_approved;
		obj->tracker_can_block = tracker_can_block_request;
	}
	if (validation_data) {
		require_action_quiet(xpc_get_type(validation_data) == XPC_TYPE_DATA, exit, err = kTypeErr);

		obj->validation_data = xpc_copy(validation_data);
		require_action_quiet(obj->validation_data, exit, err = kNoResourcesErr);
	}
	if (extended_dns_error) {
		obj->ede_code = dnssd_xpc_extended_dns_error_get_code(extended_dns_error, NULL);
		obj->ede_text = dnssd_xpc_extended_dns_error_get_text(extended_dns_error);
		if (obj->ede_text) {
			mdns_xpc_string_retain(obj->ede_text);
		}
		obj->ede_valid = true;
	}
	result	= obj;
	obj		= NULL;
	err = kNoErr;

exit:
	if (out_error) {
		*out_error = err;
	}
	dnssd_release_null_safe(obj);
	return result;
}

static dnssd_getaddrinfo_result_t
_dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data,
	size_t svcb_length, uint32_t interface_index, dnssd_getaddrinfo_result_protocol_t protocol,
	xpc_object_t provider_name, const mdns_xpc_string_t tracker_hostname, const mdns_xpc_string_t tracker_owner,
	const bool tracker_approved, const bool tracker_can_block_request, const dnssd_negative_reason_t negative_reason,
	xpc_object_t validation_data, OSStatus *out_error)
{
	OSStatus err;
	dnssd_getaddrinfo_result_t result = NULL;
	dnssd_getaddrinfo_result_t obj = _dnssd_getaddrinfo_result_alloc();
	require_action_quiet(obj, exit, err = kNoMemoryErr);

	obj->type				= dnssd_getaddrinfo_result_type_service_binding;
	obj->if_index			= interface_index;
	obj->protocol			= protocol;
	obj->negative_reason	= negative_reason;

	require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);
	obj->hostname = xpc_copy(hostname);

	require_action_quiet(xpc_get_type(actual_hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);

	obj->actual_hostname = xpc_copy(actual_hostname);
	require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr);

	if (svcb_data != NULL && svcb_length > 0) {
		obj->valid_svcb = dnssd_svcb_is_valid(svcb_data, svcb_length);
		obj->priority = dnssd_svcb_get_priority(svcb_data, svcb_length);
		obj->port = dnssd_svcb_get_port(svcb_data, svcb_length);

		char *service_name = dnssd_svcb_copy_service_name_string(svcb_data, svcb_length);
		if (service_name != NULL) {
			if (dnssd_svcb_service_name_is_empty(svcb_data, svcb_length)) {
				// The empty name is an placeholder for the name for the record
				obj->service_name = xpc_copy(obj->hostname);
			} else {
				obj->service_name = xpc_string_create(service_name);
			}
			ForgetMem(&service_name);
			require_action_quiet(obj->service_name, exit, err = kNoResourcesErr);
		}

		char *doh_uri = dnssd_svcb_copy_doh_uri(svcb_data, svcb_length);
		if (doh_uri != NULL) {
			obj->doh_uri = xpc_string_create(doh_uri);
			ForgetMem(&doh_uri);
			require_action_quiet(obj->doh_uri, exit, err = kNoResourcesErr);
		}

		char *doh_path = dnssd_svcb_copy_doh_path(svcb_data, svcb_length);
		if (doh_path != NULL) {
			obj->doh_path = xpc_string_create(doh_path);
			ForgetMem(&doh_path);
			require_action_quiet(obj->doh_path, exit, err = kNoResourcesErr);
		}

		size_t ech_config_length = 0;
		uint8_t *ech_config = dnssd_svcb_copy_ech_config(svcb_data, svcb_length, &ech_config_length);
		if (ech_config != NULL) {
			obj->ech_config = xpc_data_create(ech_config, ech_config_length);
			ForgetMem(&ech_config);
			require_action_quiet(obj->ech_config, exit, err = kNoResourcesErr);
		}

		size_t odoh_config_length = 0;
		uint8_t *odoh_config = dnssd_svcb_copy_odoh_config(svcb_data, svcb_length, &odoh_config_length);
		if (odoh_config != NULL) {
			obj->odoh_config = xpc_data_create(odoh_config, odoh_config_length);
			ForgetMem(&odoh_config);
			require_action_quiet(obj->odoh_config, exit, err = kNoResourcesErr);
		}

		dnssd_svcb_access_alpn_values(svcb_data, svcb_length, ^bool(const char *alpn) {
			xpc_object_t alpn_string = xpc_string_create(alpn);
			if (obj->alpn_values == NULL) {
				obj->alpn_values = xpc_array_create(NULL, 0);
			}
			xpc_array_append_value(obj->alpn_values, alpn_string);
			xpc_forget(&alpn_string);
			return true;
		});

		dnssd_svcb_access_address_hints(svcb_data, svcb_length, ^bool(const struct sockaddr *address) {
			xpc_object_t address_hint = xpc_data_create(address, address->sa_len);
			if (obj->address_hints == NULL) {
				obj->address_hints = xpc_array_create(NULL, 0);
			}
			xpc_array_append_value(obj->address_hints, address_hint);
			xpc_forget(&address_hint);
			return true;
		});
	} else {
		obj->valid_svcb = false;
	}

	if (provider_name) {
		require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr);

		obj->provider_name = xpc_copy(provider_name);
		require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr);
	}

	if (tracker_hostname) {
		obj->tracker_hostname = mdns_xpc_string_retain(tracker_hostname);
		if (tracker_owner) {
			obj->tracker_owner = mdns_xpc_string_retain(tracker_owner);
		}
		obj->tracker_approved = tracker_approved;
		obj->tracker_can_block = tracker_can_block_request;
	}
	if (validation_data) {
		require_action_quiet(xpc_get_type(validation_data) == XPC_TYPE_DATA, exit, err = kTypeErr);

		obj->validation_data = xpc_copy(validation_data);
		require_action_quiet(obj->validation_data, exit, err = kNoResourcesErr);
	}
	result	= obj;
	obj		= NULL;
	err = kNoErr;

exit:
	if (out_error) {
		*out_error = err;
	}
	dnssd_release_null_safe(obj);
	return result;
}

//======================================================================================================================
// MARK: - Misc. Helpers

static dnssd_cname_array_t
_dnssd_get_empty_cname_array(void)
{
	static dispatch_once_t		s_once = 0;
	static dnssd_cname_array_t	s_empty_cname_array = NULL;
	dispatch_once(&s_once,
	^{
		s_empty_cname_array = _dnssd_cname_array_create(NULL, NULL);
		s_empty_cname_array->base._os_obj_refcnt	= _OS_OBJECT_GLOBAL_REFCNT;
		s_empty_cname_array->base._os_obj_xref_cnt	= _OS_OBJECT_GLOBAL_REFCNT;
	});
	return s_empty_cname_array;
}

//======================================================================================================================

static DNSServiceErrorType
_dnssd_osstatus_to_dns_service_error(OSStatus error)
{
	switch (error) {
		case kNoMemoryErr:
		case kNoResourcesErr:
			error = kDNSServiceErr_NoMemory;
			break;

		case kParamErr:
			error = kDNSServiceErr_BadParam;
			break;

		default:
			if ((error >= kGenericErrorBase) && (error <= kGenericErrorEnd)) {
				error = kDNSServiceErr_Unknown;
			}
			break;
	}
	return error;
}

//======================================================================================================================

static int
_dnssd_snprintf(char ** const dst, const char * const end, const char * const format, ...)
{
	char * const ptr = *dst;
	const size_t len = (size_t)(end - ptr);
	va_list args;
	va_start(args, format);
	CUClangWarningIgnoreBegin(-Wformat-nonliteral);
	const int n = vsnprintf(ptr, len, format, args);
	CUClangWarningIgnoreEnd();
	va_end(args);
	if (n >= 0) {
		*dst = ptr + Min((size_t)n, len);
	}
	return n;
}
