/*
 * 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_server.h"

#include "ClientRequests.h"
#include "DNSCommon.h"
#include "dnssd_xpc.h"
#include "dnssd_svcb.h"
#include "dnssd_private.h"
#include "gai_options.h"
#include "mDNSMacOSX.h"
#include "termination_reason.h"

#include <CoreUtils/CommonServices.h>
#include <CoreUtils/DebugServices.h>
#include <mach/mach_time.h>
#include <mdns/alloc.h>
#include <mdns/audit_token.h>
#include <mdns/mortality.h>
#include <mdns/resource_record.h>
#include <mdns/system.h>
#include <mdns/ticks.h>
#include <mdns/xpc.h>
#include <os/lock.h>
#include <stdatomic.h>
#include <xpc/private.h>

#include "helpers.h"

#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
#include "QuerierSupport.h"
#endif

#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
#include "mdns_trust.h"
#include <os/feature_private.h>
#endif

#if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_STATE)
#include "resolved_cache.h"
#endif

#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
#include <mdns/signed_result.h>
#endif

#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
#include "uds_daemon.h"
#include <mdns/dispatch.h>
#include <mdns/powerlog.h>
#endif

#include "mdns_strict.h"

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

#define DX_STRUCT(NAME)					struct dx_ ## NAME ## _s
#define DX_KIND_DECLARE_ABSTRACT(NAME)	typedef DX_STRUCT(NAME) *	dx_ ## NAME ## _t
#define DX_KIND_DECLARE(NAME)		\
	DX_KIND_DECLARE_ABSTRACT(NAME)

// 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 DX_BASE_CHECK(NAME, SUPER)															\
	check_compile_time(offsetof(DX_STRUCT(NAME), base) == 0);								\
	check_compile_time(sizeof_field(DX_STRUCT(NAME), base) == sizeof(DX_STRUCT(SUPER)));	\
	extern int _dx_base_type_check[sizeof(&(((dx_ ## NAME ## _t)0)->base) == ((dx_ ## SUPER ## _t)0))]

#define DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, ...)									\
	static const struct dx_kind_s _dx_ ## NAME ## _kind = {								\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()	\
		.superkind = &_dx_ ## SUPER ##_kind,											\
		__VA_ARGS__																		\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()		\
	};																					\
	DX_BASE_CHECK(NAME, SUPER)

#define DX_SUBKIND_DEFINE(NAME, SUPER, ...)												\
	DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, __VA_ARGS__);								\
																						\
	static dx_ ## NAME ## _t															\
	_dx_ ## NAME ## _new(void)															\
	{																					\
		const dx_ ## NAME ## _t obj = (dx_ ## NAME ## _t)mdns_calloc(1, sizeof(*obj));	\
		require_return_value(obj, NULL);												\
																						\
		_dx_object_init(obj, &_dx_ ## NAME ## _kind);									\
		return obj;																		\
	}																					\
	extern int _dx_dummy_variable

#define DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME, ...)	DX_SUBKIND_DEFINE_ABSTRACT(NAME, object, __VA_ARGS__)
#define DX_OBJECT_SUBKIND_DEFINE(NAME, ...)				DX_SUBKIND_DEFINE(NAME, object, __VA_ARGS__)

DX_KIND_DECLARE_ABSTRACT(object);
DX_KIND_DECLARE(session);
DX_KIND_DECLARE_ABSTRACT(request);
DX_KIND_DECLARE(gai_request);
DX_KIND_DECLARE(gai_result);

#define DX_TRANSPARENT_UNION_MEMBER(NAME)	DX_STRUCT(NAME) *	NAME

typedef union {
	DX_TRANSPARENT_UNION_MEMBER(object);
	DX_TRANSPARENT_UNION_MEMBER(session);
	DX_TRANSPARENT_UNION_MEMBER(request);
	DX_TRANSPARENT_UNION_MEMBER(gai_request);
	DX_TRANSPARENT_UNION_MEMBER(gai_result);
} dx_any_t __attribute__((__transparent_union__));

typedef void
(*dx_init_f)(dx_any_t object);

typedef void
(*dx_invalidate_f)(dx_any_t object);

typedef void
(*dx_finalize_f)(dx_any_t object);

typedef const struct dx_kind_s *	dx_kind_t;
struct dx_kind_s {
	dx_kind_t		superkind;	// This kind's superkind. All kinds have a superkind, except the base kind.
	dx_init_f		init;		// Initializes an object.
	dx_invalidate_f	invalidate;	// Stops an object's outstanding operations, if any.
	dx_finalize_f	finalize;	// Releases object's resources right before the object is freed.
};

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

struct dx_object_s {
	dx_kind_t			kind;		// The object's kind.
	_Atomic(int32_t)	ref_count;	// Reference count.
};

static void
_dx_object_init(dx_any_t object, dx_kind_t kind);

static void
_dx_retain(dx_any_t object);

static void
_dx_release(dx_any_t object);
#define _dx_forget(X)	ForgetCustom(X, _dx_release)
#define _dx_replace(PTR, OBJ)		\
	do {							\
		if (OBJ) {					\
			_dx_retain(OBJ);		\
		}							\
		if (*(PTR)) {				\
			_dx_release(*(PTR));	\
		}							\
		*(PTR) = (OBJ);				\
	} while(0)

static void
_dx_invalidate(dx_any_t object);

static const struct dx_kind_s _dx_object_kind = {
	.superkind	= NULL,
	.init		= NULL,
	.invalidate	= NULL,
	.finalize	= NULL
};

//======================================================================================================================
// MARK: - Session Kind Definition

struct dx_session_s {
	struct dx_object_s	base;						// Object base;
	dx_session_t		next;						// Next session in list.
	dx_request_t		request_list;				// List of outstanding requests.
	xpc_connection_t	connection;					// Underlying XPC connection.
	dispatch_source_t	idle_timer;					// Timer for detecting idleness.
	dispatch_source_t	keepalive_reply_timer;		// Timer for enforcing a time limit on keepalive replies.
	uint64_t			pending_send_start_ticks;	// Start time in mach ticks of the current pending send condition.
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
	mdns_audit_token_t	peer_token;					// Client's audit token.
#endif
	uid_t				client_euid;				// Client's EUID.
	pid_t				client_pid;					// Client's PID.
	uint32_t			pending_send_count;			// Count of sent messages that still haven't been processed.
	char				client_name[MAXCOMLEN];		// Client's process name.
	bool				terminated;					// True if the session was prematurely ended due to a fatal error.
	bool				log_pending_send_counts;	// True if pending send counts should be logged.
};
mdns_compile_time_max_size_check(struct dx_session_s, 104);

static void
_dx_session_invalidate(dx_session_t session);

static void
_dx_session_finalize(dx_session_t session);

DX_OBJECT_SUBKIND_DEFINE(session,
	.invalidate	= _dx_session_invalidate,
	.finalize	= _dx_session_finalize
);

typedef union {
	DX_TRANSPARENT_UNION_MEMBER(request);
	DX_TRANSPARENT_UNION_MEMBER(gai_request);
} dx_any_request_t __attribute__((__transparent_union__));

//======================================================================================================================
// MARK: - Request Kind Definition

struct dx_request_s {
	struct dx_object_s		base;				// Object base.
	dx_request_t			next;				// Next request in list.
	dx_session_t			session;			// Back pointer to parent session.
	xpc_object_t			results;			// Array of pending results.
	uint64_t				command_id;			// ID to distinquish multiple commands during a session.
	uint32_t				request_id;			// Request ID, used for logging purposes.
	DNSServiceErrorType		error;				// Pending error.
	os_unfair_lock			lock;				// Lock for pending error and results array.
	bool					sent_error;			// True if the pending error has been sent to client.
};

static void
_dx_request_init(dx_request_t request);

static void
_dx_request_finalize(dx_request_t request);

DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(request,
	.init		= _dx_request_init,
	.finalize	= _dx_request_finalize
);

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

OS_CLOSED_OPTIONS(dx_gai_state, uint8_t,
	dx_gai_state_null						= 0,         // Default null state.
	dx_gai_state_waiting_for_a				= (1U << 0), // Currently waiting for an A result. [1]
	dx_gai_state_waiting_for_aaaa			= (1U << 1), // Currently waiting for a AAAA result. [1]
	dx_gai_state_service_allowed_failover	= (1U << 2), // Got a result from a DNS service that allows failover.
	dx_gai_state_failover_mode				= (1U << 3), // Currently avoiding DNS services that allow failover.
	dx_gai_state_avoid_suppressed_a_result	= (1U << 4), // Avoiding negative results for suppressed A queries. [2]
);

#define DX_GAI_STATE_WAITING_FOR_RESULTS (dx_gai_state_waiting_for_a | dx_gai_state_waiting_for_aaaa)

MDNS_CLANG_TREAT_WARNING_AS_ERROR_BEGIN(-Wpadded)
struct dx_gai_request_s {
	struct dx_request_s				base;					// Request object base.
	mdns_dns_service_id_t			custom_service_id;		// ID for this request's custom DNS service.
#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
	uint64_t						powerlog_start_time;	// If non-zero, time when mDNS client request started.
#endif
	GetAddrInfoClientRequest *		gai;					// Underlying GAI request.
	QueryRecordClientRequest *		query;					// Underlying SVCB/HTTPS query request.
	dx_gai_result_t					results;				// List of pending results.
	char *							hostname;				// Hostname C string to be resolved for getaddrinfo request.
	mdns_domain_name_t				last_domain_name;		// Domain name of the most recent result.
	mdns_xpc_string_t				last_tracker_hostname;	// Tracker hostname of the most recent result (XPC string).
	mdns_xpc_string_t				last_tracker_owner;		// Tracker owner of the most recent result (XPC string).
	const char *					svcb_name;				// If non-NULL, name of the SVCB/HTTPS record to query for.
	uuid_t *						resolver_uuid;			// The resolver UUID to use for UUID-scoped requests.
	xpc_object_t					cnames_a;				// Hostname's canonical names for A records (XPC array).
	xpc_object_t					cnames_aaaa;			// Hostname's canonical names for AAAA records (XPC array).
	mdns_audit_token_t				delegator_token;		// The delegator's audit token.
	xpc_object_t					fallback_dns_config;	// Fallback DNS configuration.
#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
	mdns_trust_t					trust;					// Trust instance if status is mdns_trust_status_pending
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_signed_resolve_result_t	signed_resolve;			// Signed resolve result with which to sign results.
#endif
	char *							svcb_name_memory;		// Memory that was allocated for svcb_name.
	dx_gai_result_t					pending_suppresed_a;	// Pending negative result for a suppressed A query. [2]
	DNSServiceFlags					flags;					// The request's flags parameter.
	uint32_t						ifindex;				// The interface index to use for interface-scoped requests.
	DNSServiceProtocol				protocols;				// Used to specify IPv4, IPv6, or any IP address types.
	pid_t							effective_pid;			// Effective client PID.
	uint16_t						svcb_type;				// If svcb_name is non-NULL, the type for SVCB/HTTPS query.
	uuid_t							effective_uuid;			// Effective client UUID.
	dx_gai_state_t					state;					// Collection of state bits.
	mdns_gai_options_t				options;				// Additional request options.
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	dnssd_log_privacy_level_t		log_privacy_level;		// The log privacy level of this request.
#endif
	bool							cnames_a_changed;		// True if cnames_a has changed.
	bool							cnames_aaaa_changed;	// True if cnames_aaaa has changed.
	MDNS_STRUCT_PAD_64_32(1, 1);
};
MDNS_CLANG_TREAT_WARNING_AS_ERROR_END()
mdns_compile_time_max_size_check(struct dx_gai_request_s, 256);

// Notes:
// 1. If a client request specifies that DNS services that allow failover be avoided if they're unable to provide
//    at least one positive response to either the A, AAAA, or HTTPS query, then we have to wait until at least one
//    positive result is received or until all of the underlying DNSQuestions get negative results before deciding
//    whether to start providing results to the client (former case), or discarding the current list of pending
//    results and restarting the underlying DNSQuestions in failover mode (latter case).
//
// 2. There are cases where an IPv6-only network is the primary network and will inadvertently prevent the
//    resolution of IPv4-only hostnames that are in fact resolvable via a VPN's DNS service.
//
//    On IPv6-only networks it usually makes no sense to bother sending A record queries to the network's DNS
//    service because IPv4 addresses are typically unusable on such networks, or it's at least preferable to
//    connect directly to an IPv6 address instead of to an IPv4 address through an IPv4 translation mechanism, such
//    as 464XLAT. So sending A queries is wasteful. Therefore, as a matter of policy, DNS services associated with
//    IPv6-only networks are typically configured to advise against sending A queries.
//
//    As a DNSQuestion traverses the original QNAME's CNAME chain, it may end up switching over to a different DNS
//    service depending on its current QNAME and the most appropriate DNS service for that QNAME according to the
//    current DNS configuration. For example, consider a DNSQuestion that is originally for www.example.com. If
//    there's a split-tunnel corporate VPN DNS service that's responsible for resolving everything in the
//    example.com domain. If the VPN DNS service responds to a query for www.example.com with a CNAME mapping
//    www.example.com to cdn.example.net, then when the DNSQuestion is restarted for cdn.example.net, it may be
//    assigned a different DNS service, specifically if the VPN is not responsible for cdn.example.net.
//
//    Suppose that the next DNS service is for an IPv6-only network not associated with the VPN. Suppose that in
//    the public DNS there's a cdn.example.net CNAME record that points to server.example.com. Also suppose that
//    the VPN supports IPv4 and that server.example.com is an IPv4-only host. Because the IPv6-only network's DNS
//    service advises against A queries, the DNSQuestion is stuck at cdn.example.net if the DNSQuestion is
//    configured to suppress queries for unusable addresses, as is usually the case.
//
//    When a GAI request involves parallel A and AAAA DNSQuestions, the CNAME records placed in the cache as a
//    result of the unsuppressed AAAA query can help the A DNSQuestion advance to the next name in the CNAME chain
//    so that it can advance to the VPN DNS service for server.example.com for its unsuppressed A query. This is
//    only possible if the persistWhenARecordsUnusable option is set.
//
//    A caveat with the persistWhenARecordsUnusable option is that negative results that indicate that an A query
//    was suppressed will still be generated when the A DNSQuestion is parked on a DNS service that advises against
//    A queries. We want to avoid a client seeing a negative AAAA result along with a negative suppressed A result
//    as the first pair of A+AAAA results because this could lead the client to give up upon getting such a
//    negative pair when it could be the case that a more definitive A result is on the way, e.g., a positive
//    result with an IPv4 address or some other negative result, as a consequence of the A DNSQuestion advancing to
//    a DNS service for which A queries are not suppressed.

typedef xpc_object_t
(*dx_request_take_results_f)(dx_any_request_t request);

typedef void
(*dx_request_report_powerlog_progress_f)(dx_any_request_t request);

typedef const struct dx_request_kind_s * dx_request_kind_t;
struct dx_request_kind_s {
	struct dx_kind_s			base;
	dx_request_take_results_f	take_results;
};

#define DX_REQUEST_SUBKIND_DEFINE(NAME, ...)														\
	static void																						\
	_dx_ ## NAME ## _request_invalidate(dx_ ## NAME ## _request_t request);							\
																									\
	static void																						\
	_dx_ ## NAME ## _request_finalize(dx_ ## NAME ## _request_t request);							\
																									\
	static const struct dx_request_kind_s _dx_ ## NAME ## _request_kind = {							\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()				\
		.base = {																					\
			.superkind	= &_dx_request_kind,														\
			.invalidate	= _dx_ ## NAME ## _request_invalidate,										\
			.finalize	= _dx_ ## NAME ## _request_finalize											\
		},																							\
		__VA_ARGS__																					\
		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()					\
	};																								\
																									\
	static dx_ ## NAME ## _request_t																\
	_dx_ ## NAME ## _request_new(void)																\
	{																								\
		dx_ ## NAME ## _request_t obj = (dx_ ## NAME ## _request_t)mdns_calloc(1, sizeof(*obj));	\
		require_return_value(obj, NULL);															\
																									\
		_dx_object_init(obj, &_dx_ ## NAME ## _request_kind.base);									\
		return obj;																					\
	}																								\
	DX_BASE_CHECK(NAME ## _request, request)

static xpc_object_t
_dx_gai_request_take_results(dx_gai_request_t request);

DX_REQUEST_SUBKIND_DEFINE(gai,
	.take_results = _dx_gai_request_take_results,
);

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

struct dx_gai_result_s {
	struct dx_object_s				base;					// Object base.
	dx_gai_result_t					next;					// Next result in list.
	mdns_resource_record_t			record;					// Result's resource record.
	mdns_xpc_string_t				provider_name;			// The DNS service's provider name, if any.
	xpc_object_t					cname_update;			// If non-NULL, XPC array to use for a CNAME chain update.
	mdns_xpc_string_t				tracker_hostname;		// If non-NULL, tracker hostname as an XPC string.
	mdns_xpc_string_t				tracker_owner;			// If non-NULL, owner of the tracker hostname as an XPC string.
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_signed_hostname_result_t	signed_hostname;		// Signed hostname result.
#endif
	mdns_extended_dns_error_t		extended_dns_error;		// Extended DNS Error, if any.
	DNSServiceFlags					flags;					// The result's flags.
	dnssd_negative_reason_t			negative_reason;		// Reason code for negative results.
	DNSServiceErrorType				error;					// Error returned by mDNS core.
	uint32_t						ifindex;				// The interface index associated with the result.
	mdns_resolver_type_t			protocol;				// The transport protocol used to obtain the record.
	uint16_t						question_id;			// ID of DNSQuestion used to get result. Used for logging.
	unsigned						tracker_is_approved : 1;// True if the associated tracker is approved for the client.
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	unsigned						sensitive_logging : 1;	// True if hostnames and rdata should be logged sensitively.
#endif
	unsigned						tracker_can_block : 1;	// True if a request to this known tracker can be blocked.
	unsigned						__pad_bits : 5;
};
mdns_compile_time_max_size_check(struct dx_gai_result_s, 104);

static void
_dx_gai_result_finalize(dx_gai_result_t result);

DX_OBJECT_SUBKIND_DEFINE(gai_result,
	.finalize = _dx_gai_result_finalize
);

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

static dispatch_queue_t
_dx_server_queue(void);

static void
_dx_server_handle_new_connection(xpc_connection_t connection);

static void
_dx_server_register_session(dx_session_t session);

static void
_dx_server_deregister_session(dx_session_t session);

static void
_dx_server_check_sessions(void);

static dx_session_t
_dx_session_create(xpc_connection_t connection);

static void
_dx_session_activate(dx_session_t session);

static void
_dx_session_handle_message(dx_session_t session, xpc_object_t msg);

static DNSServiceErrorType
_dx_session_handle_getaddrinfo_command(dx_session_t session, xpc_object_t msg);

static DNSServiceErrorType
_dx_session_handle_stop_command(dx_session_t session, xpc_object_t msg);

static void
_dx_session_append_request(dx_session_t session, dx_any_request_t any);

static void
_dx_session_check(dx_session_t session, uint64_t now_ticks);

static void
_dx_session_send_message(dx_session_t session, xpc_object_t msg);

static void
_dx_session_terminate(dx_session_t session, mdns_termination_reason_t reason);

static void
_dx_session_reset_idle_timer(dx_session_t session);

static void
_dx_session_log_error(dx_session_t session, DNSServiceErrorType error);

static void
_dx_session_log_pending_send_count_increase(dx_session_t session);

static void
_dx_session_log_pending_send_count_decrease(dx_session_t session);

static void
_dx_session_log_termination(dx_session_t session, mdns_termination_reason_t reason);

static xpc_object_t
_dx_request_take_results(dx_request_t request);

typedef void (^dx_block_t)(void);

static void
_dx_request_locked(dx_any_request_t request, dx_block_t block);

static DNSServiceErrorType
_dx_request_get_error(dx_any_request_t request);

static bool
_dx_request_set_error(dx_any_request_t request, DNSServiceErrorType error);

static bool
_dx_request_send_pending_error(dx_any_request_t request);

static dx_gai_request_t
_dx_gai_request_create(uint64_t command_id, dx_session_t session);

static DNSServiceErrorType
_dx_gai_request_activate(dx_gai_request_t request);

static DNSServiceErrorType
_dx_gai_request_start_client_requests(dx_gai_request_t request, bool need_lock);

static DNSServiceErrorType
_dx_gai_request_parse_params(dx_gai_request_t request, xpc_object_t params);

static DNSServiceErrorType
_dx_gai_request_start_client_requests_internal(dx_gai_request_t request, GetAddrInfoClientRequestParams *gai_params,
	QueryRecordClientRequestParams *query_params, bool need_lock);

static void
_dx_gai_request_stop_client_requests(dx_gai_request_t request, bool need_lock);

static void
_dx_gai_request_restart_client_requests_in_failover_mode(dx_gai_request_t request);

static void
_dx_gai_request_append_cname(dx_gai_request_t request, int qtype, const domainname *cname, bool expired);

static xpc_object_t
_dx_gai_request_copy_cname_update(dx_gai_request_t request, int qtype);

static void
_dx_gai_request_gai_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, mDNSBool expired,
	QC_result qc_result, DNSServiceErrorType error, void *context);

static void
_dx_gai_request_query_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, mDNSBool expired,
	QC_result qc_result, DNSServiceErrorType error, void *context);

static void
_dx_gai_request_enqueue_result(dx_gai_request_t request, QC_result qc_result, const ResourceRecord *answer,
	bool answer_is_expired, const uint8_t *rdata_ptr, uint16_t rdata_len, DNSServiceErrorType error,
	const DNSQuestion *q);

#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
static const uint8_t *
_dx_gai_request_get_resolver_uuid(xpc_object_t params);
#endif

#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
static bool
_dx_gai_request_is_for_in_app_browser(xpc_object_t params);
#endif

static void
_dx_gai_request_log_start(dx_gai_request_t request, pid_t delegator_pid, const uuid_t delegator_uuid);

static void
_dx_gai_request_log_stop(dx_gai_request_t request);

static void
_dx_gai_request_log_error(dx_gai_request_t request, DNSServiceErrorType error);

static bool
_dx_gai_request_check_for_failover_restart(dx_gai_request_t request, const ResourceRecord *answer,
	bool answer_is_expired, bool answer_is_positive);

static void
_dx_gai_result_list_forget(dx_gai_result_t *list_ptr);

static xpc_object_t
_dx_gai_result_to_dictionary(dx_gai_result_t result);

static void
_dx_gai_result_log(dx_gai_result_t result, uint32_t request_id);

static void
_dx_kqueue_locked(const char *description, bool need_lock, dx_block_t block);

static void
_dx_replace_domain_name(mdns_domain_name_t *ptr, const domainname *name);

static bool
_dx_qc_result_is_add(QC_result qc_result);

static bool
_dx_qc_result_is_suppressed(QC_result qc_result);

static QueryRecordClientRequest *
_dx_query_record_client_request_start(const QueryRecordClientRequestParams *params,
	QueryRecordResultHandler handler, void *context, DNSServiceErrorType *out_error);

static void
_dx_query_record_client_request_forget(QueryRecordClientRequest **request_ptr);

static GetAddrInfoClientRequest *
_dx_get_addr_info_client_request_start(const GetAddrInfoClientRequestParams *params,
	QueryRecordResultHandler handler, void *context, DNSServiceErrorType *out_error);

static void
_dx_get_addr_info_client_request_forget(GetAddrInfoClientRequest **request_ptr);

//======================================================================================================================
// MARK: - Logging

MDNS_LOG_CATEGORY_DEFINE(server, "dnssd_server");

//======================================================================================================================
// MARK: - Globals

static dx_session_t	g_session_list = NULL;

//======================================================================================================================
// MARK: - Server Functions

mDNSexport void
dnssd_server_init(void)
{
	static dispatch_once_t s_once = 0;
	static xpc_connection_t s_listener = NULL;
#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
	static dispatch_source_t s_powerlog_progress_timer = NULL;
#endif
	dispatch_once(&s_once,
	^{
		s_listener = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dx_server_queue(),
			XPC_CONNECTION_MACH_SERVICE_LISTENER);
		xpc_connection_set_event_handler(s_listener,
		^(xpc_object_t event)
		{
			if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
				_dx_server_handle_new_connection((xpc_connection_t)event);
			}
		});
		xpc_connection_activate(s_listener);
	#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
		const uint32_t interval_ms = 30 * MDNS_MILLISECONDS_PER_MINUTE;
		s_powerlog_progress_timer = mdns_dispatch_create_periodic_monotonic_timer(interval_ms, 5, _dx_server_queue());
		if (s_powerlog_progress_timer) {
			dispatch_source_set_event_handler(s_powerlog_progress_timer,
			^{
				os_log_debug(_mdns_server_log(), "periodic powerlog report timer fired");
				_dx_kqueue_locked("dnssd_server: submitting client summary to powerlog", true,
				^{
					mdns_powerlog_submit_client_summary();
				});
			});
			dispatch_activate(s_powerlog_progress_timer);
		} else {
			os_log_fault(_mdns_server_log(), "Failed to create periodic powerlog report timer");
		}
	#endif
	});
}

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

mDNSexport void
dnssd_server_idle(void)
{
	static dispatch_once_t		s_once = 0;
	static dispatch_source_t	s_source = NULL;
	dispatch_once(&s_once,
	^{
		s_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, _dx_server_queue());
		dispatch_source_set_event_handler(s_source,
		^{
			_dx_server_check_sessions();
		});
		dispatch_activate(s_source);
	});
	dispatch_source_merge_data(s_source, 1);
}

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

uint32_t
dnssd_server_get_new_request_id(void)
{
	static _Atomic(uint32_t) s_next_id = 1;
	return atomic_fetch_add(&s_next_id, 1);
}

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

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

	dispatch_once(&once,
	^{
		const dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
			QOS_CLASS_USER_INITIATED, 0);
		queue = dispatch_queue_create("com.apple.dnssd.server", attr);
	});
	return queue;
}

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

static void
_dx_server_handle_new_connection(const xpc_connection_t connection)
{
	dx_session_t session = _dx_session_create(connection);
	if (session) {
		_dx_session_activate(session);
		_dx_server_register_session(session);
		_dx_forget(&session);
	} else {
		const pid_t client_pid = xpc_connection_get_pid(connection);
		char client_name[MAXCOMLEN];
		client_name[0] = '\0';
		mdns_system_pid_to_name(client_pid, client_name);
		os_log_fault(_mdns_server_log(),
			"Failed to create session for connection -- client pid: %d (%{public}s)", client_pid, client_name);
		xpc_connection_cancel(connection);
	}
}

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

static void
_dx_server_register_session(dx_session_t session)
{
	dx_session_t *ptr = &g_session_list;
	while (*ptr) {
		ptr = &(*ptr)->next;
	}
	session->next = NULL;
	*ptr = session;
	_dx_retain(*ptr);
}

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

static void
_dx_server_deregister_session(dx_session_t session)
{
	dx_session_t *ptr = &g_session_list;
	while (*ptr && (*ptr != session)) {
		ptr = &(*ptr)->next;
	}
	if (*ptr) {
		*ptr = session->next;
		session->next = NULL;
		_dx_forget(&session);
	}
}

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

static void
_dx_server_check_sessions(void)
{
	if (g_session_list) {
		const uint64_t now_ticks = mach_absolute_time();
		for (dx_session_t session = g_session_list; session; session = session->next) {
			_dx_session_check(session, now_ticks);
		}
	}
}

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

static void
_dx_recursive_init(dx_object_t object, dx_kind_t kind);

static void
_dx_object_init(const dx_object_t me, const dx_kind_t kind)
{
	me->kind = kind;
	atomic_store_explicit(&me->ref_count, 1, memory_order_relaxed);
	_dx_recursive_init(me, me->kind);
}

static void
_dx_recursive_init(const dx_object_t me, const dx_kind_t kind)
{
	if (kind->superkind) {
		_dx_recursive_init(me, kind->superkind);
	}
	if (kind->init) {
		kind->init(me);
	}
}

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

static void
_dx_retain(const dx_any_t any)
{
	const dx_object_t me = any.object;
	atomic_fetch_add(&me->ref_count, 1);
}

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

static void
_dx_finalize(dx_object_t object);

static void
_dx_release(const dx_any_t any)
{
	dx_object_t me = any.object;
	if (atomic_fetch_sub(&me->ref_count, 1) == 1) {
		_dx_finalize(me);
		ForgetMem(&me);
	}
}

static void
_dx_finalize(const dx_object_t me)
{
	for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) {
		if (kind->finalize) {
			kind->finalize(me);
		}
	}
}

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

static void
_dx_invalidate(const dx_any_t any)
{
	const dx_object_t me = any.object;
	for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) {
		if (kind->invalidate) {
			kind->invalidate(me);
			return;
		}
	}
}

//======================================================================================================================
// MARK: - Session Methods

static dx_session_t
_dx_session_create(const xpc_connection_t connection)
{
	dx_session_t session = NULL;
	dx_session_t obj = _dx_session_new();
	require_quiet(obj, exit);

	obj->connection = connection;
	xpc_retain(obj->connection);
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
	audit_token_t token;
	memset(&token, 0, sizeof(token));
	xpc_connection_get_audit_token(obj->connection, &token);
	obj->peer_token = mdns_audit_token_create(&token);
	require_quiet(obj->peer_token, exit);
#endif

	obj->client_pid  = xpc_connection_get_pid(obj->connection);
	obj->client_euid = xpc_connection_get_euid(obj->connection);
	mdns_system_pid_to_name(obj->client_pid, obj->client_name);
	session = obj;
	obj = NULL;

exit:
	static_analyzer_malloc_freed(obj); // Analyzer isn't aware that obj will be freed if non-NULL.
	_dx_forget(&obj);
	return session;
}

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

static void
_dx_session_activate(const dx_session_t me)
{
	_dx_retain(me);
	xpc_connection_set_target_queue(me->connection, _dx_server_queue());
	xpc_connection_set_event_handler(me->connection,
	^(const xpc_object_t event) {
		const xpc_type_t type = xpc_get_type(event);
		if (type == XPC_TYPE_DICTIONARY) {
			if (me->connection) {
				_dx_session_handle_message(me, event);
			}
		} else if (event == XPC_ERROR_CONNECTION_INVALID) {
			_dx_server_deregister_session(me);
			_dx_session_invalidate(me);
			_dx_release(me);
		} else {
			xpc_connection_forget(&me->connection);
		}
	});
	xpc_connection_activate(me->connection);
	_dx_session_reset_idle_timer(me);
}

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

static void
_dx_session_invalidate(const dx_session_t me)
{
	xpc_connection_forget(&me->connection);
	dispatch_source_forget(&me->idle_timer);
	dispatch_source_forget(&me->keepalive_reply_timer);
	dx_request_t req;
	while ((req = me->request_list) != NULL)
	{
		me->request_list = req->next;
		_dx_invalidate(req);
		_dx_release(req);
	}
}

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

static void
_dx_session_finalize(const dx_session_t me)
{
	xpc_forget(&me->connection);
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
	mdns_forget(&me->peer_token);
#endif
}

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

static void
_dx_session_handle_message(const dx_session_t me, const xpc_object_t msg)
{
	DNSServiceErrorType err;
	const char * const command = dnssd_xpc_message_get_command(msg);
	require_action_quiet(command, exit, err = kDNSServiceErr_BadParam);

	if (strcmp(command, DNSSD_COMMAND_GETADDRINFO) == 0) {
		err = _dx_session_handle_getaddrinfo_command(me, msg);
	} else if (strcmp(command, DNSSD_COMMAND_STOP) == 0) {
		err = _dx_session_handle_stop_command(me, msg);
	} else {
		err = kDNSServiceErr_BadParam;
	}

exit:
	_dx_session_reset_idle_timer(me);
	xpc_object_t reply = xpc_dictionary_create_reply(msg);
	if (likely(reply)) {
		dnssd_xpc_message_set_error(reply, err);
		_dx_session_send_message(me, reply);
		xpc_forget(&reply);
	} else {
		_dx_session_terminate(me, mdns_termination_reason_client_error);
	}
}

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

static DNSServiceErrorType
_dx_session_handle_getaddrinfo_command(const dx_session_t me, const xpc_object_t msg)
{
	dx_gai_request_t req = NULL;
	bool valid;
	DNSServiceErrorType err;
	const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid);
	require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);

	const xpc_object_t params = dnssd_xpc_message_get_parameters(msg);
	require_action_quiet(params, exit, err = kDNSServiceErr_BadParam);

	req = _dx_gai_request_create(command_id, me);
	require_action_quiet(req, exit, err = kDNSServiceErr_NoMemory);

	err = _dx_gai_request_parse_params(req, params);
	require_noerr_quiet(err, exit);

	err = _dx_gai_request_activate(req);
	require_noerr_quiet(err, exit);

	_dx_session_append_request(me, req);

exit:
	if (err) {
		if (req) {
			_dx_gai_request_log_error(req, err);
		} else {
			_dx_session_log_error(me, err);
		}
	}
	_dx_forget(&req);
	return err;
}

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

static DNSServiceErrorType
_dx_session_handle_stop_command(const dx_session_t me, const xpc_object_t msg)
{
	bool valid;
	DNSServiceErrorType err;
	const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid);
	require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);

	dx_request_t *ptr;
	dx_request_t req;
	for (ptr = &me->request_list; (req = *ptr) != NULL; ptr = &req->next) {
		if (req->command_id == command_id) {
			break;
		}
	}
	require_action_quiet(req, exit, err = kDNSServiceErr_BadReference);

	*ptr = req->next;
	req->next = NULL;
	_dx_invalidate(req);
	_dx_forget(&req);
	err = kDNSServiceErr_NoError;

exit:
	return err;
}

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

static void
_dx_session_append_request(const dx_session_t me, const dx_any_request_t any)
{
	dx_request_t *ptr = &me->request_list;
	while (*ptr) {
		ptr = &(*ptr)->next;
	}
	const dx_request_t req = any.request;
	req->next = NULL;
	*ptr = req;
	_dx_retain(*ptr);
}

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

#define DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS	5

static void
_dx_session_check(const dx_session_t me, const uint64_t now_ticks)
{
	require_return(me->connection);

	xpc_object_t results = NULL;
	mdns_termination_reason_t terminate_reason;
	if (me->pending_send_count > 0) {
		const uint64_t elapsed_secs = (now_ticks - me->pending_send_start_ticks) / mdns_mach_ticks_per_second();
		require_action_quiet(elapsed_secs < DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS, exit,
			terminate_reason = mdns_termination_reason_back_pressure);
	}
	for (dx_request_t req = me->request_list; req; req = req->next) {
		results = _dx_request_take_results(req);
		if (results) {
			xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
			require_action_quiet(msg, exit, terminate_reason = mdns_termination_reason_server_error);

			dnssd_xpc_message_set_id(msg, req->command_id);
			dnssd_xpc_message_set_error(msg, kDNSServiceErr_NoError);
			dnssd_xpc_message_set_results(msg, results);
			xpc_forget(&results);
			_dx_session_send_message(me, msg);
			xpc_forget(&msg);
		}
		const bool ok = _dx_request_send_pending_error(req);
		require_action_quiet(ok, exit, terminate_reason = mdns_termination_reason_server_error);
	}
	terminate_reason = mdns_termination_reason_none;

exit:
	if (terminate_reason != mdns_termination_reason_none) {
		_dx_session_terminate(me, terminate_reason);
	}
	xpc_forget(&results);
}

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

static void
_dx_session_send_message(const dx_session_t me, const xpc_object_t msg)
{
	require_return(me->connection);

	xpc_connection_send_message(me->connection, msg);
	++me->pending_send_count;
	if (me->pending_send_count == 1) {
		me->pending_send_start_ticks = mach_absolute_time();
	} else {
		if (me->pending_send_count == 2) {
			me->log_pending_send_counts = true;
		}
		_dx_session_log_pending_send_count_increase(me);
	}
	_dx_retain(me);
	xpc_connection_send_barrier(me->connection,
	^{
		--me->pending_send_count;
		if (me->log_pending_send_counts) {
			_dx_session_log_pending_send_count_decrease(me);
		}
		if (me->pending_send_count == 0) {
			me->log_pending_send_counts = false;
		}
		_dx_release(me);
	});
}

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

static void
_dx_session_terminate(const dx_session_t me, const mdns_termination_reason_t reason)
{
	if (!me->terminated) {
		_dx_session_log_termination(me, reason);
		xpc_connection_forget(&me->connection);
		me->terminated = true;
	}
}

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

static dispatch_source_t
_dx_create_oneshot_timer(const uint32_t interval_ms, const unsigned int leeway_percent_numerator)
{
	const dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _dx_server_queue());
	require_quiet(timer, exit);

	const unsigned int numerator = Min(leeway_percent_numerator, 100);
	const uint64_t leeway_ns = interval_ms * (numerator * (UINT64_C_safe(kNanosecondsPerMillisecond) / 100));
	dispatch_source_set_timer(timer, dispatch_time_milliseconds(interval_ms), DISPATCH_TIME_FOREVER, leeway_ns);

exit:
	return timer;
}

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

#define DX_SESSION_KEEPALIVE_TIMEOUT_MS	(5 * kMillisecondsPerSecond)

static void
_dx_session_send_keepalive_message(const dx_session_t me)
{
	require_return(me->connection && !me->keepalive_reply_timer);

	static xpc_object_t s_keepalive_msg = NULL;
	if (!s_keepalive_msg) {
		s_keepalive_msg = xpc_dictionary_create_empty();
		require_return(s_keepalive_msg);

		dnssd_xpc_message_set_command(s_keepalive_msg, DNSSD_COMMAND_KEEPALIVE);
	}
	me->keepalive_reply_timer = _dx_create_oneshot_timer(DX_SESSION_KEEPALIVE_TIMEOUT_MS, 5);
	require_return(me->keepalive_reply_timer);

	_dx_retain(me);
	xpc_connection_send_message_with_reply(me->connection, s_keepalive_msg, _dx_server_queue(),
	^(xpc_object_t reply)
	{
		if (me->connection && (xpc_get_type(reply) == XPC_TYPE_DICTIONARY)) {
			dispatch_source_forget(&me->keepalive_reply_timer);
			_dx_session_reset_idle_timer(me);
		}
		_dx_release(me);
	});
	dispatch_source_set_event_handler(me->keepalive_reply_timer,
	^{
		dispatch_source_forget(&me->keepalive_reply_timer);
		_dx_session_terminate(me, mdns_termination_reason_keepalive_timeout);
	});
	dispatch_activate(me->keepalive_reply_timer);
}

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

#define DX_SESSION_IDLE_TIMEOUT_WITH_REQUESTS_MS	(1 * kSecondsPerMinute * kMillisecondsPerSecond)
#define DX_SESSION_IDLE_TIMEOUT_WITHOUT_REQUESTS_MS	(5 * kSecondsPerMinute * kMillisecondsPerSecond)

static void
_dx_session_reset_idle_timer(const dx_session_t me)
{
	require_return(!me->keepalive_reply_timer);

	dispatch_source_forget(&me->idle_timer);
	uint32_t idle_interval_ms;
	if (me->request_list) {
		idle_interval_ms = DX_SESSION_IDLE_TIMEOUT_WITH_REQUESTS_MS;
	} else {
		idle_interval_ms = DX_SESSION_IDLE_TIMEOUT_WITHOUT_REQUESTS_MS;
	}
	me->idle_timer = _dx_create_oneshot_timer(idle_interval_ms, 5);
	require_return(me->idle_timer);

	dispatch_source_set_event_handler(me->idle_timer,
	^{
		dispatch_source_forget(&me->idle_timer);
		if (me->request_list) {
			_dx_session_send_keepalive_message(me);
		} else {
			_dx_session_terminate(me, mdns_termination_reason_idle);
		}
	});
	dispatch_activate(me->idle_timer);
}

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

static bool
_dx_session_has_entitlement(const dx_session_t me, const char * const entitlement)
{
	bool entitled = false;
	if (me->connection) {
		entitled = mdns_xpc_connection_is_entitled(me->connection, entitlement);
	}
	return entitled;
}

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

static bool
_dx_session_has_delegate_entitlement(const dx_session_t me)
{
	return _dx_session_has_entitlement(me, "com.apple.private.network.socket-delegate");
}

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

static bool
_dx_session_has_prohibit_encrypted_dns_entitlement(const dx_session_t me)
{
	return _dx_session_has_entitlement(me, "com.apple.private.dnssd.prohibit-encrypted-dns");
}

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

static void
_dx_session_log_error(const dx_session_t me, const DNSServiceErrorType error)
{
	os_log_error(_mdns_server_log(),
		"XPC session error -- error: %{mdns:err}ld, client pid: %lld (%{public}s)",
		(long)error, (long long)me->client_pid, me->client_name);
}

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

static void
_dx_session_log_pending_send_count_increase(const dx_session_t me)
{
	os_log_debug(_mdns_server_log(),
		"XPC session to client with pid %lld (%{public}s) pending send count increased to %d",
		(long long)me->client_pid, me->client_name, me->pending_send_count);
}

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

static void
_dx_session_log_pending_send_count_decrease(const dx_session_t me)
{
	os_log_debug(_mdns_server_log(),
		"XPC session to client with pid %lld (%{public}s) pending send count decreased to %d",
		(long long)me->client_pid, me->client_name, me->pending_send_count);
}

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

static void
_dx_session_log_termination(const dx_session_t me, const mdns_termination_reason_t reason)
{
	os_log_with_type(_mdns_server_log(),
		(reason == mdns_termination_reason_idle) ? OS_LOG_TYPE_INFO : OS_LOG_TYPE_DEFAULT,
		"Session terminated -- reason: %{mdns:termination_reason}d, pending send count: %u, client pid: %lld "
		"(%{public}s)",
		reason, me->pending_send_count, (long long)me->client_pid, me->client_name);
}

//======================================================================================================================
// MARK: - Request Methods

static void
_dx_request_init(const dx_request_t me)
{
	me->request_id	= dnssd_server_get_new_request_id();
	me->lock		= OS_UNFAIR_LOCK_INIT;
}

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

static void
_dx_request_finalize(const dx_request_t me)
{
	_dx_forget(&me->session);
	xpc_forget(&me->results);
}

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

static xpc_object_t
_dx_request_take_results(const dx_request_t me)
{
	const dx_request_kind_t kind = (dx_request_kind_t)me->base.kind;
	if (kind->take_results) {
		return kind->take_results(me);
	} else {
		return NULL;
	}
}

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

static void
_dx_request_locked(const dx_any_request_t any, const dx_block_t block)
{
	const dx_request_t me = any.request;
	os_unfair_lock_lock(&me->lock);
	block();
	os_unfair_lock_unlock(&me->lock);
}

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

static DNSServiceErrorType
_dx_request_get_error(const dx_any_request_t any)
{
	const dx_request_t me = any.request;
	__block DNSServiceErrorType error;
	_dx_request_locked(me,
	^{
		error = me->error;
	});
	return error;
}

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

static bool
_dx_request_set_error(const dx_any_request_t any, const DNSServiceErrorType error)
{
	__block bool did_set = false;
	if (error) {
		const dx_request_t me = any.request;
		_dx_request_locked(me,
		^{
			if (!me->error) {
				me->error = error;
				did_set = true;
			}
		});
	}
	return did_set;
}

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

static bool
_dx_request_send_pending_error(const dx_any_request_t any)
{
	bool ok = false;
	const dx_request_t me = any.request;
	const DNSServiceErrorType error = _dx_request_get_error(me);
	if (error && !me->sent_error) {
		xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
		require_quiet(msg, exit);

		dnssd_xpc_message_set_id(msg, me->command_id);
		dnssd_xpc_message_set_error(msg, error);
		_dx_session_send_message(me->session, msg);
		xpc_forget(&msg);
		me->sent_error = true;
	}
	ok = true;

exit:
	return ok;
}

//======================================================================================================================
// MARK: - GetAddrInfo Request Methods

static dx_gai_request_t
_dx_gai_request_create(const uint64_t command_id, const dx_session_t session)
{
	dx_gai_request_t obj = _dx_gai_request_new();
	require_quiet(obj, exit);

	obj->base.command_id = command_id;
	obj->base.session    = session;
	_dx_retain(obj->base.session);

exit:
	return obj;
}

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

#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
static DNSServiceErrorType
_dx_gai_request_trust_check(dx_gai_request_t request, bool *out_defer_start);
#endif

static DNSServiceErrorType
_dx_gai_request_activate(const dx_gai_request_t me)
{
	DNSServiceErrorType err;
	bool defer_start = false;
#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
	bool privacy_check_done = false;
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	privacy_check_done = (me->signed_resolve != NULL);
#endif
	if (!privacy_check_done && os_feature_enabled(mDNSResponder, bonjour_privacy)) {
		err = _dx_gai_request_trust_check(me, &defer_start);
		require_noerr_quiet(err, exit);
	}
#endif
	if (!defer_start) {
		err = _dx_gai_request_start_client_requests(me, true);
		require_noerr_quiet(err, exit);
	}
	err = kDNSServiceErr_NoError;

exit:
	return err;
}

#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
static DNSServiceErrorType
_dx_gai_request_trust_check(const dx_gai_request_t me, bool * const out_defer_start)
{
	DNSServiceErrorType err;
	bool defer_start = false;
	const dx_session_t session = me->base.session;
    const audit_token_t *const token = mdns_audit_token_get_token(session->peer_token);
	mdns_trust_flags_t flags = mdns_trust_flags_none;
	const mdns_trust_status_t status = mdns_trust_check_getaddrinfo(*token, me->hostname, &flags);
	switch (status) {
		case mdns_trust_status_granted:
			err = kDNSServiceErr_NoError;
			break;

		case mdns_trust_status_denied:
		case mdns_trust_status_pending:
			me->trust = mdns_trust_create(*token, NULL, flags);
			require_action_quiet(me->trust, exit, err = kDNSServiceErr_NoMemory);

			_dx_retain(me);
			mdns_trust_set_queue(me->trust, _dx_server_queue());
			mdns_trust_set_event_handler(me->trust,
			^(const mdns_trust_event_t event, const mdns_trust_status_t update)
			{
				if (me->trust && (event == mdns_trust_event_result)) {
					DNSServiceErrorType handler_err;
					if (update == mdns_trust_status_granted) {
						handler_err = _dx_gai_request_start_client_requests(me, true);
					} else {
						handler_err = kDNSServiceErr_PolicyDenied;
					}
					if (handler_err && _dx_request_set_error(me, handler_err)) {
						_dx_gai_request_log_error(me, handler_err);
						_dx_request_send_pending_error(me);
					}
				}
				mdns_forget(&me->trust);
				_dx_release(me);
			});
			mdns_trust_activate(me->trust);
			defer_start = true;
			err = kDNSServiceErr_NoError;
			break;

		case mdns_trust_status_no_entitlement:
			err = kDNSServiceErr_NoAuth;
			break;

		MDNS_COVERED_SWITCH_DEFAULT:
			err = kDNSServiceErr_Unknown;
			break;
	}

exit:
	if (out_defer_start) {
		*out_defer_start = defer_start;
	}
	return err;
}
#endif

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

static void
_dx_gai_request_invalidate(const dx_gai_request_t me)
{
	_dx_gai_request_log_stop(me);
	_dx_gai_request_stop_client_requests(me, true);
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
	if (me->custom_service_id != MDNS_DNS_SERVICE_INVALID_ID) {
		Querier_DeregisterCustomDNSService(me->custom_service_id);
		me->custom_service_id = MDNS_DNS_SERVICE_INVALID_ID;
	}
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
	mdns_trust_forget_with_invalidation(&me->trust);
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_forget(&me->signed_resolve);
#endif
	mdns_forget(&me->last_domain_name);
	mdns_xpc_string_forget(&me->last_tracker_hostname);
	mdns_xpc_string_forget(&me->last_tracker_owner);
}

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

static void
_dx_gai_request_finalize(const dx_gai_request_t me)
{
	_dx_gai_result_list_forget(&me->results);
	ForgetMem(&me->hostname);
	ForgetMem(&me->svcb_name_memory);
	xpc_forget(&me->cnames_a);
	xpc_forget(&me->cnames_aaaa);
	mdns_forget(&me->delegator_token);
	xpc_forget(&me->fallback_dns_config);
	ForgetMem(&me->resolver_uuid);
	_dx_forget(&me->pending_suppresed_a);
}

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

static DNSServiceErrorType
_dx_gai_request_parse_params(const dx_gai_request_t me, const xpc_object_t params)
{
	DNSServiceErrorType err;
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_signed_resolve_result_t signed_resolve = NULL;
#endif
	const char * const hostname = dnssd_xpc_parameters_get_hostname(params);
	require_action_quiet(hostname, exit, err = kDNSServiceErr_BadParam);

	me->hostname = mdns_strdup(hostname);
	require_action_quiet(me->hostname, exit, err = kDNSServiceErr_NoMemory);

	bool valid;
	me->flags = dnssd_xpc_parameters_get_flags(params, &valid);
	require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);

	me->ifindex = dnssd_xpc_parameters_get_interface_index(params, &valid);
	require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);

	me->protocols = dnssd_xpc_parameters_get_protocols(params, &valid);
	require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);

	const dx_session_t session = me->base.session;

	// Get delegator IDs.
	pid_t delegator_pid;
	const uint8_t *delegator_uuid;
	audit_token_t storage;
	const audit_token_t * const delegator_token = dnssd_xpc_parameters_get_delegate_audit_token(params, &storage);
	if (delegator_token) {
		me->delegator_token = mdns_audit_token_create(delegator_token);
		require_action_quiet(me->delegator_token, exit, err = kDNSServiceErr_NoMemory);
	}
	if (me->delegator_token) {
		delegator_pid  = mdns_audit_token_get_pid(me->delegator_token);
		delegator_uuid = NULL;
	} else {
		delegator_uuid = dnssd_xpc_parameters_get_delegate_uuid(params);
		if (delegator_uuid) {
			delegator_pid = 0;
		} else {
			delegator_pid = dnssd_xpc_parameters_get_delegate_pid(params, NULL);
		}
	}
	if (me->delegator_token || delegator_uuid || (delegator_pid != 0)) {
		const bool entitled = _dx_session_has_delegate_entitlement(session);
		require_action_quiet(entitled, exit, err = kDNSServiceErr_NoAuth);
	}

	// Determine effective IDs.
	// Note: The mDNS core requires that the effective PID be set to zero if the effective UUID is the primary ID.
	if (delegator_uuid) {
		uuid_copy(me->effective_uuid, delegator_uuid);
		me->effective_pid = 0;
	} else {
		uuid_clear(me->effective_uuid);
		me->effective_pid = (delegator_pid != 0) ? delegator_pid : session->client_pid;
	}

	// Determine if an SVCB or HTTPS query is necessary.
	const char * const service_scheme = dnssd_xpc_parameters_get_service_scheme(params);
	if (service_scheme) {
		if (strcasecmp(service_scheme, "_443._https") == 0) {
			me->svcb_name = me->hostname;
			me->svcb_type = kDNSType_HTTPS;
		} else {
			asprintf(&me->svcb_name_memory, "%s.%s", service_scheme, me->hostname);
			require_action_quiet(me->svcb_name_memory, exit, err = kDNSServiceErr_NoMemory);

			me->svcb_name = me->svcb_name_memory;
			me->svcb_type = kDNSType_SVCB;
		}
	}
	me->fallback_dns_config = dnssd_xpc_parameters_get_fallback_config(params);
	if (me->fallback_dns_config) {
		xpc_retain(me->fallback_dns_config);
	}
	const uint8_t * const resolver_uuid = _dx_gai_request_get_resolver_uuid(params);
	if (resolver_uuid) {
		me->resolver_uuid = (uuid_t *)mdns_memdup(resolver_uuid, sizeof(*me->resolver_uuid));
		require_action_quiet(me->resolver_uuid, exit, err = kDNSServiceErr_NoMemory);
	}
	if (dnssd_xpc_parameters_get_need_encrypted_query(params)) {
		me->options |= mdns_gai_option_need_encryption;
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
	if (_dx_gai_request_is_for_in_app_browser(params)) {
		me->options |= mdns_gai_option_in_app_browser;
	}
#endif
	if (dnssd_xpc_parameters_get_use_failover(params)) {
		me->options |= mdns_gai_option_use_failover;
	}
	if (me->options & mdns_gai_option_use_failover) {
		if ((me->protocols & kDNSServiceProtocol_IPv4) || !(me->protocols & kDNSServiceProtocol_IPv6)) {
			me->state |= dx_gai_state_waiting_for_a;
		}
		if ((me->protocols & kDNSServiceProtocol_IPv6) || !(me->protocols & kDNSServiceProtocol_IPv4)) {
			me->state |= dx_gai_state_waiting_for_aaaa;
		}
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	me->log_privacy_level = dnssd_xpc_parameters_get_log_privacy_level(params);
#endif
	if (dnssd_xpc_parameters_get_prohibit_encrypted_dns(params)) {
		const bool entitled = _dx_session_has_prohibit_encrypted_dns_entitlement(session);
		require_action_quiet(entitled, exit, err = kDNSServiceErr_NoAuth);

		me->options |= mdns_gai_option_prohibit_encrypted_dns;
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	size_t signed_data_len;
	const uint8_t * const signed_data = dnssd_xpc_parameters_get_validation_data(params, &signed_data_len);
	if (signed_data) {
		// Get signed_result data and validate
		OSStatus create_err;
		signed_resolve = mdns_signed_resolve_result_create_from_data(signed_data, signed_data_len, &create_err);
		require_action_quiet(signed_resolve, exit, err = kDNSServiceErr_Invalid; os_log_error(_mdns_server_log(),
			"[R%u] Failed to create signed resolve result from data: %{mdns:err}ld",
			me->base.request_id, (long)create_err));

		// Use signed result to verify params otherwise don't set the signed result instance to fallback to trust
		if (mdns_signed_resolve_result_contains(signed_resolve, me->hostname, me->ifindex)) {
			const bool allowed = mdns_system_is_signed_result_uuid_valid(mdns_signed_result_get_uuid(signed_resolve));
			require_action_quiet(allowed, exit, err = kDNSServiceErr_PolicyDenied; os_log_error(_mdns_server_log(),
				"[R%u] Signed result UUID revoked.", me->base.request_id));

			os_log_debug(_mdns_server_log(), "[R%u] Allowing signed result", me->base.request_id);
			me->signed_resolve = signed_resolve;
			signed_resolve = NULL;
		} else {
			os_log_error(_mdns_server_log(),
				"[R%u] Signed resolve result does not cover request -- hostname: %{private,mask.hash}s, ifindex: %u",
				me->base.request_id, me->hostname, me->ifindex);
		}
	}
#endif
	_dx_gai_request_log_start(me, delegator_pid, delegator_uuid);
	err = kDNSServiceErr_NoError;

exit:
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_forget(&signed_resolve);
#endif
	return err;
}

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

static dx_gai_result_t
_dx_gai_request_take_expired_results(dx_gai_request_t request);

static xpc_object_t
_dx_gai_request_take_results(const dx_gai_request_t me)
{
	DNSServiceErrorType err;
	xpc_object_t result_array = NULL;
	__block dx_gai_result_t result_list = NULL;
	_dx_request_locked(me,
	^{
		if (me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) {
			result_list = _dx_gai_request_take_expired_results(me);
		} else {
			result_list = me->results;
			me->results = NULL;
		}
	});
	if (result_list) {
		result_array = xpc_array_create(NULL, 0);
		require_action_quiet(result_array, exit, err = kDNSServiceErr_NoMemory);

		dx_gai_result_t result;
		while ((result = result_list) != NULL) {
			xpc_object_t result_dict = _dx_gai_result_to_dictionary(result);
			require_action_quiet(result_dict, exit, err = kDNSServiceErr_NoMemory);

			_dx_gai_result_log(result, me->base.request_id);
			result_list = result->next;
			_dx_forget(&result);

			xpc_array_append_value(result_array, result_dict);
			xpc_forget(&result_dict);
		}
	}
	err = kDNSServiceErr_NoError;

exit:
	_dx_gai_result_list_forget(&result_list);
	if (err) {
		_dx_request_set_error(me, err);
	}
	return result_array;
}

static dx_gai_result_t
_dx_gai_request_take_expired_results(const dx_gai_request_t me)
{
	dx_gai_result_t expired_results = NULL;
	dx_gai_result_t *expired_ptr = &expired_results;
	dx_gai_result_t result;
	dx_gai_result_t *ptr = &me->results;
	while ((result = *ptr) != NULL) {
		if (result->flags & kDNSServiceFlagsExpiredAnswer) {
			*ptr = result->next;
			result->next = NULL;
			*expired_ptr = result;
			expired_ptr = &result->next;
		} else {
			ptr = &result->next;
		}
	}
	return expired_results;
}

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

static DNSServiceErrorType
_dx_gai_request_start_client_requests_internal(const dx_gai_request_t me,
	GetAddrInfoClientRequestParams * const gai_params, QueryRecordClientRequestParams * const query_params,
	const bool need_lock)
{
	__block DNSServiceErrorType err = kDNSServiceErr_NoError;
	_dx_kqueue_locked("dx_gai_request: starting client requests", need_lock,
	^{
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
		if (me->resolver_uuid && !uuid_is_null(*me->resolver_uuid)) {
			Querier_RegisterPathResolver(*me->resolver_uuid);
		}
		if ((me->custom_service_id == MDNS_DNS_SERVICE_INVALID_ID) && me->fallback_dns_config) {
			me->custom_service_id = Querier_RegisterCustomDNSService(me->fallback_dns_config);
		}
		if (gai_params) {
			gai_params->resolverUUID	= *me->resolver_uuid;
			gai_params->customID		= me->custom_service_id;
		}
		if (query_params) {
			query_params->resolverUUID	= *me->resolver_uuid;
			query_params->customID		= me->custom_service_id;
		}
#endif
		// If present, run the query for SVCB/HTTPS first, in case the ALPN and address hints come back first.
		if (query_params && !me->query) {
			me->query = _dx_query_record_client_request_start(query_params, _dx_gai_request_query_result_handler, me,
				&err);
			require_noerr_return(err);
		}
		// Run the A/AAAA lookup.
		if (gai_params && !me->gai) {
			me->gai = _dx_get_addr_info_client_request_start(gai_params, _dx_gai_request_gai_result_handler, me, &err);
			require_noerr_return(err);
		#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
			if (me->gai) {
				const domainname *const qname = GetAddrInfoClientRequestGetQName(me->gai);
				if ((me->ifindex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(qname)) {
					const char * const client_name = me->base.session->client_name;
					const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags);
					me->powerlog_start_time = mdns_powerlog_getaddrinfo_start(client_name, uses_awdl);
				}
			}
		#endif
		}
	});
	if (err) {
		_dx_gai_request_stop_client_requests(me, need_lock);
	}
	return err;
}

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

static void
_dx_gai_request_stop_client_requests(const dx_gai_request_t me, const bool need_lock)
{
	_dx_kqueue_locked("dx_gai_request: stopping client requests", need_lock,
	^{
		_dx_get_addr_info_client_request_forget(&me->gai);
	#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
		if (me->powerlog_start_time != 0) {
			const char * const client_name = me->base.session->client_name;
			const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags);
			mdns_powerlog_getaddrinfo_stop(client_name, me->powerlog_start_time, uses_awdl);
			me->powerlog_start_time = 0;
		}
	#endif
		_dx_query_record_client_request_forget(&me->query);
	});
}

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

static void
_dx_gai_request_restart_client_requests_in_failover_mode(const dx_gai_request_t me)
{
	if (!(me->state & dx_gai_state_failover_mode)) {
		_dx_gai_request_stop_client_requests(me, false);
		os_log(_mdns_server_log(), "[R%u] getaddrinfo failover restart", me->base.request_id);
		me->state |= dx_gai_state_failover_mode;
		_dx_gai_request_start_client_requests(me, false);
	}
}

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

static xpc_object_t *
_dx_gai_request_get_cnames_ptr(const dx_gai_request_t me, const int qtype, bool ** const out_changed_ptr)
{
	xpc_object_t *cnames_ptr;
	bool *changed_ptr;
	switch (qtype) {
		case kDNSServiceType_A:
			cnames_ptr		= &me->cnames_a;
			changed_ptr		= &me->cnames_a_changed;
			break;

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

		default:
			cnames_ptr		= NULL;
			changed_ptr		= NULL;
			break;
	}
	if (out_changed_ptr) {
		*out_changed_ptr = changed_ptr;
	}
	return cnames_ptr;
}

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

static void
_dx_gai_request_reset_cnames(const dx_gai_request_t me, const int qtype)
{
	bool *changed_ptr;
	xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr);
	require_quiet(cnames_ptr, exit);

	xpc_forget(cnames_ptr);
	*cnames_ptr = xpc_array_create_empty();
	*changed_ptr = true;

exit:
	return;
}

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

static void
_dx_gai_request_append_cname(const dx_gai_request_t me, const int qtype, const domainname * const cname,
	const bool expired)
{
	bool *changed_ptr;
	xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr);
	require_quiet(cnames_ptr, exit);

	const char *cname_str = NULL;
	char cname_buf[MAX_ESCAPED_DOMAIN_NAME];
	if (cname) {
		if (!ConvertDomainNameToCString(cname, cname_buf)) {
			cname_buf[0] = '\0';
		}
		cname_str = cname_buf;
	}
	_dx_request_locked(me,
	^{
		if (cname_str) {
			xpc_object_t cnames = *cnames_ptr;
			if (!cnames) {
				cnames = xpc_array_create(NULL, 0);
				*cnames_ptr = cnames;
			}
			if (cnames) {
				xpc_array_set_string(cnames, XPC_ARRAY_APPEND, cname_str);
				*changed_ptr = true;
			}
		}
	});
	if ((me->state & dx_gai_state_avoid_suppressed_a_result) && !expired) {
		// An intermediate CNAME result for the A DNSQuestion means that the last pending negative result is no
		// longer relevant, so it can be dropped.
		if (cname && (qtype == kDNSType_A)) {
			_dx_forget(&me->pending_suppresed_a);
		}
	}

exit:
	return;
}

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

static xpc_object_t
_dx_gai_request_copy_cname_update(const dx_gai_request_t me, const int qtype)
{
	__block xpc_object_t result = NULL;
	bool *changed_ptr;
	xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr);
	require_quiet(cnames_ptr, exit);

	_dx_request_locked(me,
	^{
		if (*changed_ptr) {
			const xpc_object_t cnames = *cnames_ptr;
			if (cnames) {
				result = xpc_copy(cnames);
			}
			*changed_ptr = false;
		}
	});

exit:
	return result;
}

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

static void
_dx_gai_request_failover_check_gai_answer(dx_gai_request_t request, const ResourceRecord *answer);

static void
_dx_gai_request_gai_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer,
	const mDNSBool expired, const QC_result qc_result, const DNSServiceErrorType error, void * const context)
{
	(void)m;
	bool failover_restart = false;
	const dx_gai_request_t me = (dx_gai_request_t)context;
	if (!error || (error == kDNSServiceErr_NoSuchRecord)) {
		_dx_gai_request_failover_check_gai_answer(me, answer);
		if (q->CNAMEReferrals == 0) {
			_dx_gai_request_reset_cnames(me, q->qtype);
		}
		if (answer->rrtype == kDNSServiceType_CNAME) {
			require_quiet(!error, exit);

			_dx_gai_request_append_cname(me, q->qtype, &answer->rdata->u.name, expired);
		}
		require_quiet((answer->rrtype == kDNSServiceType_A) || (answer->rrtype == kDNSServiceType_AAAA), exit);

		const uint8_t *rdata_ptr;
		uint16_t rdata_len;
		if (!error) {
			if (answer->rrtype == kDNSServiceType_A) {
				rdata_ptr = answer->rdata->u.ipv4.b;
				rdata_len = 4;
			} else {
				rdata_ptr = answer->rdata->u.ipv6.b;
				rdata_len = 16;
			}
		} else {
			rdata_ptr = NULL;
			rdata_len = 0;
		}
		failover_restart = _dx_gai_request_check_for_failover_restart(me, answer, expired, rdata_len > 0);
		if (!failover_restart) {
			_dx_gai_request_enqueue_result(me, qc_result, answer, expired, rdata_ptr, rdata_len, error, q);
		}
	} else {
		_dx_request_set_error(me, error);
	}

exit:
	if (failover_restart) {
		_dx_gai_request_restart_client_requests_in_failover_mode(me);
	}
}

static void
_dx_gai_request_failover_check_gai_answer(const dx_gai_request_t me, const ResourceRecord * const answer)
{
	if ((me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) && !(me->state & dx_gai_state_service_allowed_failover)) {
		const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata);
		if (dnsservice && mdns_dns_service_allows_failover(dnsservice)) {
			me->state |= dx_gai_state_service_allowed_failover;
		}
	}
}

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

static void
_dx_gai_request_query_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer,
    const mDNSBool expired, const QC_result qc_result, const DNSServiceErrorType error, void * const context)
{
	(void)m;
	bool failover_restart = false;
	const dx_gai_request_t me = (dx_gai_request_t)context;
	if (!error || (error == kDNSServiceErr_NoSuchRecord)) {
		require_quiet((answer->rrtype == kDNSServiceType_SVCB) || (answer->rrtype == kDNSServiceType_HTTPS), exit);

		const uint8_t *rdata_ptr;
		uint16_t rdata_len;
		if (!error) {
			rdata_ptr = answer->rdata->u.data;
			rdata_len = answer->rdlength;
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
			char *svcb_doh_uri = dnssd_svcb_copy_doh_uri(rdata_ptr, rdata_len);
			// Check for a valid DoH URI.
			if (svcb_doh_uri) {
				// Pass the domain to map if the record is DNSSEC signed.
				char *svcb_domain = NULL;
#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
				char svcb_domain_buffer[MAX_ESCAPED_DOMAIN_NAME] = "";
				if (resource_record_get_validation_result(answer) == dnssec_secure) {
					if (ConvertDomainNameToCString(answer->name, svcb_domain_buffer)) {
						svcb_domain = svcb_domain_buffer;
					}
				}
#endif
				Querier_RegisterDoHURI(svcb_doh_uri, svcb_domain);
				ForgetMem(&svcb_doh_uri);
			}
#endif
		} else {
			rdata_ptr = NULL;
			rdata_len = 0;
		}
		failover_restart = _dx_gai_request_check_for_failover_restart(me, answer, expired, rdata_len > 0);
		if (!failover_restart) {
			_dx_gai_request_enqueue_result(me, qc_result, answer, expired, rdata_ptr, rdata_len, error, q);
		}
	} else {
		_dx_request_set_error(me, error);
	}

exit:
	if (failover_restart) {
		_dx_gai_request_restart_client_requests_in_failover_mode(me);
	}
}

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

static bool
_dx_gai_request_needs_sensitive_logging(const dx_gai_request_t me)
{
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	return (me->log_privacy_level == dnssd_log_privacy_level_private);
#else
	return false;
#endif
}

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

static void
_dx_gai_request_append_result(const dx_gai_request_t me, const dx_gai_result_t result)
{
	result->cname_update = _dx_gai_request_copy_cname_update(me, mdns_resource_record_get_type(result->record));
	_dx_request_locked(me,
	^{
		dx_gai_result_t *ptr = &me->results;
		while (*ptr) {
			ptr = &(*ptr)->next;
		}
		*ptr = result;
		_dx_retain(*ptr);
	});
}

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

static dnssd_negative_reason_t
_dx_get_negative_answer_reason(const ResourceRecord *answer, QC_result qc_result);

static void
_dx_gai_request_enqueue_result(const dx_gai_request_t me, const QC_result qc_result,
	const ResourceRecord * const answer, const bool answer_is_expired, const uint8_t * const rdata_ptr,
	const uint16_t rdata_len, const DNSServiceErrorType result_error, const DNSQuestion * const q)
{
	DNSServiceErrorType err;
	dx_gai_result_t result = NULL;
	require_action_quiet(!answer_is_expired || (rdata_len > 0), exit, err = kDNSServiceErr_NoError);

	result = _dx_gai_result_new();
	require_action_quiet(result, exit, err = kDNSServiceErr_NoMemory);

	_dx_replace_domain_name(&me->last_domain_name, answer->name);
	require_action_quiet(me->last_domain_name, exit, err = kDNSServiceErr_NoMemory);

	result->record = mdns_resource_record_create(me->last_domain_name, answer->rrtype, answer->rrclass, 0,
		rdata_ptr, rdata_len, NULL);
	require_action_quiet(result->record, exit, err = kDNSServiceErr_NoMemory);

	DNSServiceFlags flags = 0;
	const bool is_add = _dx_qc_result_is_add(qc_result);
	if (is_add) {
		flags |= kDNSServiceFlagsAdd;
		if (!q->InitialCacheMiss) {
			flags |= kDNSServiceFlagAnsweredFromCache;
		}
		if (rdata_len <= 0) {
			result->negative_reason = _dx_get_negative_answer_reason(answer, qc_result);
		}
		result->extended_dns_error = mdns_cache_metadata_get_extended_dns_error(answer->metadata);
		mdns_retain_null_safe(result->extended_dns_error);
	}
	if (answer_is_expired) {
		flags |= kDNSServiceFlagsExpiredAnswer;
	}
	extern mDNS mDNSStorage;
	result->flags       = flags;
	result->error       = result_error;
	result->ifindex     = mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, answer->InterfaceID, mDNStrue);
	result->protocol    = mdns_cache_metadata_get_protocol(answer->metadata);
	result->question_id = mDNSVal16(q->TargetQID);
	const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata);
	if (dnsservice) {
		result->provider_name = mdns_dns_service_copy_provider_name(dnsservice);
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	result->sensitive_logging = _dx_gai_request_needs_sensitive_logging(me);
#endif
	const uint16_t record_type = mdns_resource_record_get_type(result->record);
	if (is_add && !result_error) {
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
		if (me->signed_resolve && ((record_type == kDNSType_A) || (record_type == kDNSType_AAAA))) {
			OSStatus create_err;
			mdns_signed_hostname_result_t signed_hostname;
			if (record_type == kDNSType_A) {
				signed_hostname = mdns_signed_hostname_result_create_ipv4(me->signed_resolve, rdata_ptr, &create_err);
			} else {
				signed_hostname = mdns_signed_hostname_result_create_ipv6(me->signed_resolve, rdata_ptr,
					result->ifindex, &create_err);
			}
			result->signed_hostname = signed_hostname;
			if (!result->signed_hostname) {
				os_log_error(_mdns_server_log(),
					"[R%u] Failed to create IPv%d signed hostname result: %{mdns:err}ld",
					me->base.request_id, (record_type == kDNSType_A) ? 4 : 6, (long)create_err);
			}
		}
#endif
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_STATE)
	if (resolved_cache_is_enabled() && is_add) {
		const char *hostname = NULL;
		const char *owner = NULL;
		bool approved_domain = false;
		bool can_block_request = false;
		const tracker_state_t tracker_state = resolved_cache_get_tracker_state(q, &hostname, &owner, &approved_domain,
			&can_block_request);
		if ((tracker_state == tracker_state_known_tracker) && hostname) {
			mdns_xpc_string_recreate(&me->last_tracker_hostname, hostname);
			require_action_quiet(me->last_tracker_hostname, exit, err = kDNSServiceErr_NoMemory);

			result->tracker_hostname = me->last_tracker_hostname;
			xpc_retain(result->tracker_hostname);
			if (owner) {
				mdns_xpc_string_recreate(&me->last_tracker_owner, owner);
				require_action_quiet(me->last_tracker_owner, exit, err = kDNSServiceErr_NoMemory);

				result->tracker_owner = me->last_tracker_owner;
				mdns_xpc_string_retain(result->tracker_owner);
			}
			if (approved_domain) {
				result->tracker_is_approved = true;
			}
			if (can_block_request) {
				result->tracker_can_block = true;
			}
		}
	}
#endif
	if ((me->state & dx_gai_state_avoid_suppressed_a_result) && !answer_is_expired) {
		switch (record_type) {
			case kDNSType_A:
				if (result->negative_reason == dnssd_negative_reason_query_suppressed) {
					// Put a negative suppressed A result on hold.
					_dx_replace(&me->pending_suppresed_a, result);
					err = kDNSServiceErr_NoError;
					goto exit;
				} else {
					// Any other type of A result means that if there's a pending negative suppressed A result,
					// then it can be dropped. It also means that we no longer care about avoiding negative
					// suppressed A results.
					_dx_forget(&me->pending_suppresed_a);
					SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, false);
				}
				break;

			case kDNSType_AAAA:
				// A AAAA result means that if there's a pending negative suppressed A result, then it should no
				// longer be kept from the client. It also means that we no longer care about avoiding negative
				// suppressed A results because if the A DNSQuestion was stuck at a DNS service that suppresses A
				// queries, then it should have advanced past it by now if there was a CNAME chain to traverse
				// because the AAAA DNSQuestion just did.
				if (me->pending_suppresed_a) {
					_dx_gai_request_append_result(me, me->pending_suppresed_a);
					_dx_forget(&me->pending_suppresed_a);
				}
				SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, false);
				break;

			default:
				break;
		}
	}
	_dx_gai_request_append_result(me, result);
	err = kDNSServiceErr_NoError;

exit:
	static_analyzer_malloc_freed(result); // Analyzer isn't aware that result will be freed if non-NULL.
	_dx_forget(&result);
	if (err) {
		_dx_request_set_error(me, err);
	}
}

static dnssd_negative_reason_t
_dx_get_negative_answer_reason(const ResourceRecord * const answer, const QC_result qc_result)
{
	const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(answer->metadata);
	if (dnsservice) {
		if (_dx_qc_result_is_suppressed(qc_result)) {
			return dnssd_negative_reason_query_suppressed;
		} else {
			switch (answer->rcode) {
				case kDNSFlag1_RC_NoErr:
					return dnssd_negative_reason_no_data;

				case kDNSFlag1_RC_NXDomain:
					return dnssd_negative_reason_nxdomain;

				default:
					return dnssd_negative_reason_server_error;
			}
		}
	} else {
		if (answer->InterfaceID) {
			// A non-zero InterfaceID means that an mDNS NSEC record asserted that there's no data for the record.
			return dnssd_negative_reason_no_data;
		} else {
			return dnssd_negative_reason_no_dns_service;
		}
	}
}

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

#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
static const uint8_t *
_dx_gai_request_get_resolver_uuid(const xpc_object_t params)
{
	const xpc_object_t resolver_uuids = dnssd_xpc_parameters_get_resolver_uuid_array(params);
	if (resolver_uuids && (xpc_array_get_count(resolver_uuids) > 0)) {
		return xpc_array_get_uuid(resolver_uuids, 0);
	} else {
		return NULL;
	}
}
#endif

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

#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
static bool
_dx_gai_request_is_for_in_app_browser(const xpc_object_t params)
{
	const char * const account_id = dnssd_xpc_parameters_get_account_id(params);
	if (account_id && (strcmp(account_id, "com.apple.WebKit.InAppBrowser") == 0)) {
		return true;
	} else {
		return false;
	}
}
#endif

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

#define _dx_log_private_string_with_formatted_bookends(SENSITIVE, PREFIX_FMT, SUFFIX_FMT, ...)			\
	do {																								\
		if (SENSITIVE) {																				\
			os_log(_mdns_server_log(), PREFIX_FMT "%{sensitive,mask.hash}s" SUFFIX_FMT, __VA_ARGS__);	\
		} else {																						\
			os_log(_mdns_server_log(), PREFIX_FMT "%{private,mask.hash}s" SUFFIX_FMT, __VA_ARGS__);		\
		}																								\
	} while (0)

static void
_dx_gai_request_log_start(const dx_gai_request_t me, const pid_t delegator_pid, const uuid_t delegator_uuid)
{
	const dx_session_t session = me->base.session;
	const bool sensitive_logging = _dx_gai_request_needs_sensitive_logging(me);
	if (delegator_uuid) {
		_dx_log_private_string_with_formatted_bookends(sensitive_logging,
			"[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */
			", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s), delegator uuid: %{public,uuid_t}.16P",
			me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname,
			me->options, (long long)session->client_pid, session->client_name, delegator_uuid);
	} else if (delegator_pid != 0) {
		char delegator_name[MAXCOMLEN];
		delegator_name[0] = '\0';
		mdns_system_pid_to_name(delegator_pid, delegator_name);
		_dx_log_private_string_with_formatted_bookends(sensitive_logging,
			"[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */
			", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s), delegator pid: %lld (%{public}s)",
			me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname,
			me->options, (long long)session->client_pid, session->client_name, (long long)delegator_pid,
			delegator_name);
	} else {
		_dx_log_private_string_with_formatted_bookends(sensitive_logging,
			"[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: ", /* hostname */
			", options: %{mdns:gaiopts}X, client pid: %lld (%{public}s)",
			me->base.request_id, me->flags, (int32_t)me->ifindex, me->protocols, me->hostname,
			me->options, (long long)session->client_pid, session->client_name);
	}
}

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

static void
_dx_gai_request_log_stop(const dx_gai_request_t me)
{
	const dx_session_t session = me->base.session;
	const bool sensitive_logging = _dx_gai_request_needs_sensitive_logging(me);
	if (session->terminated) {
		_dx_log_private_string_with_formatted_bookends(sensitive_logging,
			"[R%u] getaddrinfo stop (forced) -- hostname: ", /* hostname */ ", client pid: %lld (%{public}s)",
			me->base.request_id, me->hostname, (long long)session->client_pid, session->client_name);
	} else {
		_dx_log_private_string_with_formatted_bookends(sensitive_logging,
			"[R%u] getaddrinfo stop -- hostname: ", /* hostname */ ", client pid: %lld (%{public}s)",
			me->base.request_id, me->hostname, (long long)session->client_pid, session->client_name);
	}
}

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

static void
_dx_gai_request_log_error(const dx_gai_request_t me, const DNSServiceErrorType error)
{
	const dx_session_t session = me->base.session;
	os_log_error(_mdns_server_log(),
		"[R%u] getaddrinfo error -- hostname: %{private,mask.hash}s, error: %{mdns:err}ld"", "
		"client pid: %lld (%{public}s)",
		me->base.request_id, me->hostname, (long)error, (long long)session->client_pid, session->client_name);
}

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

static bool
_dx_gai_request_involves_parallel_a_and_aaaa_questions(const dx_gai_request_t me)
{
	switch (me->protocols & (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6)) {
		case 0:
			return true;

		case (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6):
			return true;

		default:
			return false;
	}
}

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

static DNSServiceErrorType
_dx_gai_request_start_client_requests(const dx_gai_request_t me, const bool need_lock)
{
	const dx_session_t session = me->base.session;

	// Set up GetAddrInfo parameters.
	GetAddrInfoClientRequestParams gai_params;
	GetAddrInfoClientRequestParamsInit(&gai_params);
	gai_params.hostnameStr				= me->hostname;
	gai_params.requestID				= me->base.request_id;
	gai_params.interfaceIndex			= me->ifindex;
	gai_params.flags					= me->flags;
	gai_params.protocols				= me->protocols;
	gai_params.effectivePID				= me->effective_pid;
	gai_params.effectiveUUID			= me->effective_uuid;
	gai_params.peerUID					= session->client_euid;
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
	gai_params.needEncryption			= (me->options & mdns_gai_option_need_encryption) != 0;
	gai_params.failoverMode				= (me->state & dx_gai_state_failover_mode) != 0;
	gai_params.prohibitEncryptedDNS		= (me->options & mdns_gai_option_prohibit_encrypted_dns) != 0;
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
	gai_params.peerToken				= session->peer_token;
	gai_params.delegatorToken			= me->delegator_token;
	gai_params.isInAppBrowserRequest	= (me->options & mdns_gai_option_in_app_browser) != 0;
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	gai_params.logPrivacyLevel				= me->log_privacy_level;
#endif
	const bool will_have_parallel_a_and_aaaa = _dx_gai_request_involves_parallel_a_and_aaaa_questions(me);
	SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, will_have_parallel_a_and_aaaa);
	_dx_forget(&me->pending_suppresed_a);
	gai_params.persistWhenARecordsUnusable  = will_have_parallel_a_and_aaaa;

	// Set up QueryRecord parameters.
	QueryRecordClientRequestParams query_params;
	QueryRecordClientRequestParams *query_params_ptr = NULL;
	if (me->svcb_name) {
		QueryRecordClientRequestParamsInit(&query_params);
		query_params.requestID				= me->base.request_id;
		query_params.qnameStr				= me->svcb_name;
		query_params.interfaceIndex			= me->ifindex;
		query_params.flags					= me->flags;
		query_params.qtype					= me->svcb_type;
		query_params.qclass					= kDNSServiceClass_IN;
		query_params.effectivePID			= me->effective_pid;
		query_params.effectiveUUID			= me->effective_uuid;
		query_params.peerUID				= session->client_euid;
#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
		query_params.needEncryption			= (me->options & mdns_gai_option_need_encryption) != 0;
		query_params.failoverMode			= (me->state & dx_gai_state_failover_mode) != 0;
		query_params.prohibitEncryptedDNS	= (me->options & mdns_gai_option_prohibit_encrypted_dns) != 0;
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
		query_params.peerToken				= session->peer_token;
		query_params.delegatorToken			= me->delegator_token;
		query_params.isInAppBrowserRequest	= (me->options & mdns_gai_option_in_app_browser) != 0;
#endif
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
		query_params.logPrivacyLevel		= me->log_privacy_level;
#endif
		query_params_ptr = &query_params;
	}
	return _dx_gai_request_start_client_requests_internal(me, &gai_params, query_params_ptr, need_lock);
}

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

static bool
_dx_gai_request_check_for_failover_restart(const dx_gai_request_t me, const ResourceRecord * const answer,
	const bool answer_is_expired, const bool answer_is_positive)
{
	__block bool restart = false;
	__block dx_gai_result_t free_list = NULL;
	if ((me->state & DX_GAI_STATE_WAITING_FOR_RESULTS) && !answer_is_expired) {
		_dx_request_locked(me,
		^{
			if (answer_is_positive) {
				switch (answer->rrtype) {
					case kDNSType_A:
					case kDNSType_AAAA:
					case kDNSType_HTTPS:
						me->state &= ~DX_GAI_STATE_WAITING_FOR_RESULTS;
						break;

					default:
						break;
				}
			} else {
				switch (answer->rrtype) {
					case kDNSServiceType_A:
						me->state &= ~dx_gai_state_waiting_for_a;
						break;

					case kDNSServiceType_AAAA:
						me->state &= ~dx_gai_state_waiting_for_aaaa;
						break;

					default:
						break;
				}
				const dx_gai_state_t state = me->state;
				if (!(state & DX_GAI_STATE_WAITING_FOR_RESULTS) && (state & dx_gai_state_service_allowed_failover)) {
					restart = true;
					free_list = me->results;
					me->results = NULL;
				}
			}
		});
	}
	_dx_gai_result_list_forget(&free_list);
	return restart;
}

//======================================================================================================================
// MARK: - GAI Result Methods

static void
_dx_gai_result_finalize(const dx_gai_result_t me)
{
	mdns_forget(&me->record);
	mdns_xpc_string_forget(&me->provider_name);
	xpc_forget(&me->cname_update);
	mdns_xpc_string_forget(&me->tracker_hostname);
	mdns_xpc_string_forget(&me->tracker_owner);
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	mdns_forget(&me->signed_hostname);
#endif
	mdns_forget(&me->extended_dns_error);
}

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

static void
_dx_gai_result_list_forget(dx_gai_result_t * const list_ptr)
{
	dx_gai_result_t list;
	if ((list = *list_ptr) != NULL) {
		*list_ptr = NULL;
		dx_gai_result_t result;
		while ((result = list) != NULL) {
			list = result->next;
			_dx_forget(&result);
		}
	}
}

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

static xpc_object_t
_dx_gai_result_to_dictionary(const dx_gai_result_t me)
{
	xpc_object_t result = xpc_dictionary_create(NULL, NULL, 0);
	require_return_value(result, NULL);

	dnssd_xpc_result_set_error(result, me->error);
	dnssd_xpc_result_set_flags(result, me->flags);
	dnssd_xpc_result_set_interface_index(result, me->ifindex);
	const mdns_domain_name_t name = mdns_resource_record_get_name(me->record);
	dnssd_xpc_result_set_record_name(result, mdns_domain_name_get_presentation(name));
	dnssd_xpc_result_set_record_type(result, mdns_resource_record_get_type(me->record));
	dnssd_xpc_result_set_record_protocol(result, (uint16_t)me->protocol);
	dnssd_xpc_result_set_record_class(result, mdns_resource_record_get_class(me->record));
	dnssd_xpc_result_set_record_data(result, mdns_resource_record_get_rdata_bytes_ptr(me->record),
		mdns_resource_record_get_rdata_length(me->record));
	if (me->negative_reason != dnssd_negative_reason_none) {
		dnssd_xpc_result_set_negative_reason(result, me->negative_reason);
	}
	if (me->provider_name) {
		dnssd_xpc_result_set_provider_name(result, me->provider_name);
	}
	if (me->cname_update) {
		dnssd_xpc_result_set_cname_update(result, me->cname_update);
	}
	if (me->tracker_hostname) {
		dnssd_xpc_result_set_tracker_hostname(result, me->tracker_hostname);
		if (me->tracker_owner) {
			dnssd_xpc_result_set_tracker_owner(result, me->tracker_owner);
		}
		dnssd_xpc_result_set_tracker_is_approved(result, me->tracker_is_approved);
		dnssd_xpc_result_set_tracker_can_block_request(result, me->tracker_can_block);
	}
#if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
	if (me->signed_hostname) {
		size_t data_len;
		const uint8_t * const data = mdns_signed_result_get_data(me->signed_hostname, &data_len);
		if (data) {
			dnssd_xpc_result_set_validation_data(result, data, data_len);
		}
	}
#endif
	const mdns_extended_dns_error_t ede = me->extended_dns_error;
	if (ede) {
		const uint16_t code = mdns_extended_dns_error_get_code(ede);
		const mdns_xpc_string_t text = mdns_extended_dns_error_get_extra_text(ede);
		dnssd_xpc_result_set_extended_dns_error(result, code, text);
	}
	return result;
}

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

static bool
_dx_gai_result_needs_sensitive_logging(const dx_gai_result_t me)
{
#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
	return me->sensitive_logging;
#else
	return false;
#endif
}

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

#define _dx_log_name_and_record_with_formatted_bookends(NAME, RECORD, SENSITIVE, PREFIX_FMT, MID_FMT, SUFFIX_FMT,	\
	REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, RRTYPE, EXPIRED)														\
	do {																											\
		const bool _sensitive = SENSITIVE;																			\
		const mdns_domain_name_t _name = NAME;																		\
		char *_sensitive_name_desc = _sensitive ? mdns_copy_private_description(_name) : NULL;						\
		const mdns_resource_record_t _record = RECORD;																\
		char *_sensitive_record_desc = _sensitive ? mdns_copy_private_description(_record) : NULL;					\
		if (_sensitive_name_desc && _sensitive_record_desc) {														\
			os_log(_mdns_server_log(), PREFIX_FMT "%{public}s" MID_FMT "%{public}s" SUFFIX_FMT,						\
				REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _sensitive_name_desc, RRTYPE, _sensitive_record_desc,		\
				EXPIRED);																							\
		} else {																									\
			os_log(_mdns_server_log(), PREFIX_FMT "%@" MID_FMT "%@" SUFFIX_FMT,										\
				REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _name, RRTYPE, _record, EXPIRED);							\
		}																											\
		ForgetMem(&_sensitive_name_desc);																			\
		ForgetMem(&_sensitive_record_desc);																			\
	} while (0)

#define _dx_log_name_with_formatted_bookends(NAME, SENSITIVE, PREFIX_FMT, SUFFIX_FMT, REQUEST_ID, QUESTION_ID,	\
	IS_ADD,	IFINDEX, RRTYPE, NEGATIVE_REASON)																	\
	do {																										\
		const mdns_domain_name_t _name = NAME;																	\
		char *_sensitive_name_desc = (SENSITIVE) ? mdns_copy_private_description(_name) : NULL;					\
		if (_sensitive_name_desc) {																				\
			os_log(_mdns_server_log(), PREFIX_FMT "%{public}s" SUFFIX_FMT,										\
				REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _sensitive_name_desc, RRTYPE, NEGATIVE_REASON);		\
			ForgetMem(&_sensitive_name_desc);																	\
		} else {																								\
			os_log(_mdns_server_log(), PREFIX_FMT "%@" SUFFIX_FMT,												\
				REQUEST_ID, QUESTION_ID, IS_ADD, IFINDEX, _name, RRTYPE, NEGATIVE_REASON);						\
		}																										\
	} while (0)

static void
_dx_gai_result_log(const dx_gai_result_t me, const uint32_t request_id)
{
	const bool is_add = (me->flags & kDNSServiceFlagsAdd) ? true : false;
	const mdns_domain_name_t name = me->record ? mdns_resource_record_get_name(me->record) : NULL;
	const int type = me->record ? mdns_resource_record_get_type(me->record) : 0;
	const bool sensitive_logging = _dx_gai_result_needs_sensitive_logging(me);
	if (me->record && (mdns_resource_record_get_rdata_length(me->record) > 0)) {
		const bool expired = (me->flags & kDNSServiceFlagsExpiredAnswer) != 0;
		_dx_log_name_and_record_with_formatted_bookends(name, me->record, sensitive_logging,
			"[R%u->Q%u] getaddrinfo result -- event: %{mdns:addrmv}d, ifindex: %d, name: ", /* name */
			", type: %{mdns:rrtype}d, rdata: ", /* rdata */ ", expired: %{mdns:yesno}d",
			request_id, me->question_id, is_add, me->ifindex, type, expired);
	} else {
		_dx_log_name_with_formatted_bookends(name, sensitive_logging,
			"[R%u->Q%u] getaddrinfo result -- event: %{mdns:addrmv}d, ifindex: %d, name: ", /* name */
			", type: %{mdns:rrtype}d, rdata: <none>, reason: %{mdns:nreason}d",
			request_id, me->question_id, is_add, me->ifindex, type, me->negative_reason);
	}
}

//======================================================================================================================
// MARK: - Helper Functions

static void
_dx_kqueue_locked(const char * const description, const bool need_lock, const dx_block_t block)
{
	if (need_lock) {
		KQueueLock();
		block();
		KQueueUnlock(description);
	} else {
		block();
	}
}

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

static void
_dx_replace_domain_name(mdns_domain_name_t * const ptr, const domainname * const name)
{
	const mdns_domain_name_t original = *ptr;
	if (!original || !SameDomainNameBytes(mdns_domain_name_get_labels(original), name->c)) {
		mdns_forget(ptr);
		*ptr = mdns_domain_name_create_with_labels(name->c, NULL);
	}
}

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

static bool
_dx_qc_result_is_add(const QC_result qc_result)
{
	switch (qc_result) {
		case QC_rmv:
			return false;

		case QC_add:
		case QC_addnocache:
		case QC_forceresponse:
		case QC_suppressed:
		MDNS_COVERED_SWITCH_DEFAULT:
			return true;
	}
}

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

static bool
_dx_qc_result_is_suppressed(const QC_result qc_result)
{
	switch (qc_result) {
		case QC_suppressed:
			return true;

		case QC_rmv:
		case QC_add:
		case QC_addnocache:
		case QC_forceresponse:
		MDNS_COVERED_SWITCH_DEFAULT:
			return false;
	}
}

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

static QueryRecordClientRequest *
_dx_query_record_client_request_start(const QueryRecordClientRequestParams * const params,
	const QueryRecordResultHandler handler, void * const context, DNSServiceErrorType * const out_error)
{
	DNSServiceErrorType err;
	QueryRecordClientRequest *query = (QueryRecordClientRequest *)mdns_calloc(1, sizeof(*query));
	mdns_require_action_quiet(query, exit, err = kDNSServiceErr_NoMemory);

	err = QueryRecordClientRequestStart(query, params, handler, context);
	if (err) {
		ForgetMem(&query);
	}

exit:
	mdns_assign(out_error, err);
	return query;
}

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

static void
_dx_query_record_client_request_forget(QueryRecordClientRequest ** const request_ptr)
{
	QueryRecordClientRequest * const query = *request_ptr;
	if (query) {
		QueryRecordClientRequestStop(query);
		ForgetMem(request_ptr);
	}
}

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

static GetAddrInfoClientRequest *
_dx_get_addr_info_client_request_start(const GetAddrInfoClientRequestParams * const params,
	const QueryRecordResultHandler handler, void * const context, DNSServiceErrorType * const out_error)
{
	DNSServiceErrorType err;
	GetAddrInfoClientRequest *gai = (GetAddrInfoClientRequest *)mdns_calloc(1, sizeof(*gai));
	mdns_require_action_quiet(gai, exit, err = kDNSServiceErr_NoMemory);

	err = GetAddrInfoClientRequestStart(gai, params, handler, context);
	if (err) {
		ForgetMem(&gai);
	}

exit:
	mdns_assign(out_error, err);
	return gai;
}

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

static void
_dx_get_addr_info_client_request_forget(GetAddrInfoClientRequest ** const request_ptr)
{
	GetAddrInfoClientRequest * const gai = *request_ptr;
	if (gai) {
		GetAddrInfoClientRequestStop(gai);
		ForgetMem(request_ptr);
	}
}
