dnssd_clientshim.c   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2003 Apple Computer, 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
 * 
 *     http://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.

 * This file defines a simple shim layer between a client calling the "/usr/include/dns_sd.h" APIs
 * and an implementation of mDNSCore ("mDNSEmbeddedAPI.h" APIs) in the same address space.
 * When the client calls a dns_sd.h function, the shim calls the corresponding mDNSEmbeddedAPI.h
 * function, and when mDNSCore calls the shim's callback, we call through to the client's callback.
 * The shim is responsible for two main things:
 * - converting string parameters between C string format and native DNS format,
 * - and for allocating and freeing memory.

	Change History (most recent first):

$Log: dnssd_clientshim.c,v $
Revision 1.16  2007/11/30 20:12:24  cheshire
Removed unused "badparam:" label

Revision 1.15  2007/07/27 19:30:41  cheshire
Changed mDNSQuestionCallback parameter from mDNSBool to QC_result,
to properly reflect tri-state nature of the possible responses

Revision 1.14  2007/07/17 19:15:26  cheshire
<rdar://problem/5297410> Crash in DNSServiceRegister() in dnssd_clientshim.c

Revision 1.13  2007/01/04 20:57:49  cheshire
Rename ReturnCNAME to ReturnIntermed (for ReturnIntermediates)

Revision 1.12  2006/12/19 22:43:55  cheshire
Fix compiler warnings

Revision 1.11  2006/10/27 01:30:23  cheshire
Need explicitly to set ReturnIntermed = mDNSfalse

Revision 1.10  2006/08/14 23:24:56  cheshire
Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0

Revision 1.9  2006/07/24 23:45:55  cheshire
<rdar://problem/4605276> DNSServiceReconfirmRecord() should return error code

Revision 1.8  2004/12/16 20:47:34  cheshire
<rdar://problem/3324626> Cache memory management improvements

Revision 1.7  2004/12/10 04:08:43  cheshire
Added comments about autoname and autorename

Revision 1.6  2004/10/19 21:33:22  cheshire
<rdar://problem/3844991> Cannot resolve non-local registrations using the mach API
Added flag 'kDNSServiceFlagsForceMulticast'. Passing through an interface id for a unicast name
doesn't force multicast unless you set this flag to indicate explicitly that this is what you want

Revision 1.5  2004/09/21 23:29:51  cheshire
<rdar://problem/3680045> DNSServiceResolve should delay sending packets

Revision 1.4  2004/09/17 01:08:55  cheshire
Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
  The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
  declared in that file are ONLY appropriate to single-address-space embedded applications.
  For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.

Revision 1.3  2004/05/27 06:26:31  cheshire
Add shim for DNSServiceQueryRecord()

Revision 1.2  2004/05/20 18:41:24  cheshire
Fix build broken by removal of 'kDNSServiceFlagsRemove' from dns_sd.h

Revision 1.1  2004/03/12 21:30:29  cheshire
Build a System-Context Shared Library from mDNSCore, for the benefit of developers
like Muse Research who want to be able to use mDNS/DNS-SD from GPL-licensed code.

 */

#include "dns_sd.h"				// Defines the interface to the client layer above
#include "mDNSEmbeddedAPI.h"		// The interface we're building on top of
extern mDNS mDNSStorage;		// We need to pass the address of this storage to the lower-layer functions

#if MDNS_BUILDINGSHAREDLIBRARY || MDNS_BUILDINGSTUBLIBRARY
#pragma export on
#endif

//*************************************************************************************************************
// General Utility Functions

// All mDNS_DirectOP structures start with the pointer to the type-specific disposal function.
// Optional type-specific data follows these three fields
// When the client starts an operation, we return the address of the corresponding mDNS_DirectOP
// as the DNSServiceRef for the operation
// We stash the value in core context fields so we can get it back to recover our state in our callbacks,
// and pass it though to the client for it to recover its state

typedef struct mDNS_DirectOP_struct mDNS_DirectOP;
typedef void mDNS_DirectOP_Dispose(mDNS_DirectOP *op);
struct mDNS_DirectOP_struct
	{
	mDNS_DirectOP_Dispose  *disposefn;
	};

typedef struct
	{
	mDNS_DirectOP_Dispose  *disposefn;
	DNSServiceRegisterReply callback;
	void                   *context;
	mDNSBool                autoname;		// Set if this name is tied to the Computer Name
	mDNSBool                autorename;		// Set if we just got a name conflict and now need to automatically pick a new name
	domainlabel             name;
	domainname              host;
	ServiceRecordSet        s;
	} mDNS_DirectOP_Register;

typedef struct
	{
	mDNS_DirectOP_Dispose  *disposefn;
	DNSServiceBrowseReply   callback;
	void                   *context;
	DNSQuestion             q;
	} mDNS_DirectOP_Browse;

typedef struct
	{
	mDNS_DirectOP_Dispose  *disposefn;
	DNSServiceResolveReply  callback;
	void                   *context;
	const ResourceRecord   *SRV;
	const ResourceRecord   *TXT;
	DNSQuestion             qSRV;
	DNSQuestion             qTXT;
	} mDNS_DirectOP_Resolve;

typedef struct
	{
	mDNS_DirectOP_Dispose      *disposefn;
	DNSServiceQueryRecordReply  callback;
	void                       *context;
	DNSQuestion                 q;
	} mDNS_DirectOP_QueryRecord;

int DNSServiceRefSockFD(DNSServiceRef sdRef)
	{
	(void)sdRef;	// Unused
	return(0);
	}

DNSServiceErrorType DNSServiceProcessResult(DNSServiceRef sdRef)
	{
	(void)sdRef;	// Unused
	return(kDNSServiceErr_NoError);
	}

void DNSServiceRefDeallocate(DNSServiceRef sdRef)
	{
	mDNS_DirectOP *op = (mDNS_DirectOP *)sdRef;
	//LogMsg("DNSServiceRefDeallocate");
	op->disposefn(op);
	}

//*************************************************************************************************************
// Domain Enumeration

// Not yet implemented, so don't include in stub library
// We DO include it in the actual Extension, so that if a later client compiled to use this
// is run against this Extension, it will get a reasonable error code instead of just
// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link)
#if !MDNS_BUILDINGSTUBLIBRARY
DNSServiceErrorType DNSServiceEnumerateDomains
	(
	DNSServiceRef                       *sdRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	DNSServiceDomainEnumReply           callback,
	void                                *context  /* may be NULL */
	)
	{
	(void)sdRef;			// Unused
	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused
	(void)callback;			// Unused
	(void)context;			// Unused
	return(kDNSServiceErr_Unsupported);
	}
#endif

//*************************************************************************************************************
// Register Service

mDNSlocal void FreeDNSServiceRegistration(mDNS_DirectOP_Register *x)
	{
	while (x->s.Extras)
		{
		ExtraResourceRecord *extras = x->s.Extras;
		x->s.Extras = x->s.Extras->next;
		if (extras->r.resrec.rdata != &extras->r.rdatastorage)
			mDNSPlatformMemFree(extras->r.resrec.rdata);
		mDNSPlatformMemFree(extras);
		}

	if (x->s.RR_TXT.resrec.rdata != &x->s.RR_TXT.rdatastorage)
			mDNSPlatformMemFree(x->s.RR_TXT.resrec.rdata);

	if (x->s.SubTypes) mDNSPlatformMemFree(x->s.SubTypes);
	
	mDNSPlatformMemFree(x);
	}

static void DNSServiceRegisterDispose(mDNS_DirectOP *op)
	{
	mDNS_DirectOP_Register *x = (mDNS_DirectOP_Register*)op;
	x->autorename = mDNSfalse;
	// If mDNS_DeregisterService() returns mStatus_NoError, that means that the service was found in the list,
	// is sending its goodbye packet, and we'll get an mStatus_MemFree message when we can free the memory.
	// If mDNS_DeregisterService() returns an error, it means that the service had already been removed from
	// the list, so we should go ahead and free the memory right now
	if (mDNS_DeregisterService(&mDNSStorage, &x->s) != mStatus_NoError)
		FreeDNSServiceRegistration(x);
	}

mDNSlocal void RegCallback(mDNS *const m, ServiceRecordSet *const sr, mStatus result)
	{
	mDNS_DirectOP_Register *x = (mDNS_DirectOP_Register*)sr->ServiceContext;

    domainlabel name;
    domainname type, dom;
	char namestr[MAX_DOMAIN_LABEL+1];		// Unescaped name: up to 63 bytes plus C-string terminating NULL.
	char typestr[MAX_ESCAPED_DOMAIN_NAME];
	char domstr [MAX_ESCAPED_DOMAIN_NAME];
    if (!DeconstructServiceName(sr->RR_SRV.resrec.name, &name, &type, &dom)) return;
    if (!ConvertDomainLabelToCString_unescaped(&name, namestr)) return;
    if (!ConvertDomainNameToCString(&type, typestr)) return;
    if (!ConvertDomainNameToCString(&dom, domstr)) return;

	if (result == mStatus_NoError)
		{
		if (x->callback)
			x->callback((DNSServiceRef)x, 0, result, namestr, typestr, domstr, x->context);
		}
	else if (result == mStatus_NameConflict)
		{
		if (x->autoname) mDNS_RenameAndReregisterService(m, sr, mDNSNULL);
		else if (x->callback)
				x->callback((DNSServiceRef)x, 0, result, namestr, typestr, domstr, x->context);
		}
	else if (result == mStatus_MemFree)
		{
		if (x->autorename)
			{
			x->autorename = mDNSfalse;
			x->name = mDNSStorage.nicelabel;
			mDNS_RenameAndReregisterService(m, &x->s, &x->name);
			}
		else
			FreeDNSServiceRegistration(x);
		}
	}

DNSServiceErrorType DNSServiceRegister
	(
	DNSServiceRef                       *sdRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	const char                          *name,         /* may be NULL */
	const char                          *regtype,
	const char                          *domain,       /* may be NULL */
	const char                          *host,         /* may be NULL */
	uint16_t                            notAnIntPort,
	uint16_t                            txtLen,
	const void                          *txtRecord,    /* may be NULL */
	DNSServiceRegisterReply             callback,      /* may be NULL */
	void                                *context       /* may be NULL */
	)
	{
	mStatus err = mStatus_NoError;
	const char *errormsg = "Unknown";
	domainlabel n;
	domainname t, d, h, srv;
	mDNSIPPort port;
	unsigned int size = sizeof(RDataBody);
	AuthRecord *SubTypes = mDNSNULL;
	mDNSu32 NumSubTypes = 0;
	mDNS_DirectOP_Register *x;
	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused

	// Check parameters
	if (!name) name = "";
	if (!name[0]) n = mDNSStorage.nicelabel;
	else if (!MakeDomainLabelFromLiteralString(&n, name))                              { errormsg = "Bad Instance Name"; goto badparam; }
	if (!regtype || !*regtype || !MakeDomainNameFromDNSNameString(&t, regtype))        { errormsg = "Bad Service Type";  goto badparam; }
	if (!MakeDomainNameFromDNSNameString(&d, (domain && *domain) ? domain : "local.")) { errormsg = "Bad Domain";        goto badparam; }
	if (!MakeDomainNameFromDNSNameString(&h, (host   && *host  ) ? host   : ""))       { errormsg = "Bad Target Host";   goto badparam; }
	if (!ConstructServiceName(&srv, &n, &t, &d))                                       { errormsg = "Bad Name";          goto badparam; }
	port.NotAnInteger = notAnIntPort;

	// Allocate memory, and handle failure
	if (size < txtLen)
		size = txtLen;
	x = (mDNS_DirectOP_Register *)mDNSPlatformMemAllocate(sizeof(*x) - sizeof(RDataBody) + size);
	if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; }

	// Set up object
	x->disposefn = DNSServiceRegisterDispose;
	x->callback  = callback;
	x->context   = context;
	x->autoname = (!name[0]);
	x->autorename = mDNSfalse;
	x->name = n;
	x->host = h;

	// Do the operation
	err = mDNS_RegisterService(&mDNSStorage, &x->s,
		&x->name, &t, &d,		// Name, type, domain
		&x->host, port,			// Host and port
		txtRecord, txtLen,		// TXT data, length
		SubTypes, NumSubTypes,	// Subtypes
		mDNSInterface_Any,		// Interface ID
		RegCallback, x);		// Callback and context
	if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_RegisterService"; goto fail; }

	// Succeeded: Wrap up and return
	*sdRef = (DNSServiceRef)x;
	return(mStatus_NoError);

badparam:
	err = mStatus_BadParamErr;
fail:
	LogMsg("DNSServiceBrowse(\"%s\", \"%s\") failed: %s (%ld)", regtype, domain, errormsg, err);
	return(err);
	}

//*************************************************************************************************************
// Add / Update / Remove records from existing Registration

// Not yet implemented, so don't include in stub library
// We DO include it in the actual Extension, so that if a later client compiled to use this
// is run against this Extension, it will get a reasonable error code instead of just
// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link)
#if !MDNS_BUILDINGSTUBLIBRARY
DNSServiceErrorType DNSServiceAddRecord
	(
	DNSServiceRef                       sdRef,
	DNSRecordRef                        *RecordRef,
	DNSServiceFlags                     flags,
	uint16_t                            rrtype,
	uint16_t                            rdlen,
	const void                          *rdata,
	uint32_t                            ttl
	)
	{
	(void)sdRef;		// Unused
	(void)RecordRef;	// Unused
	(void)flags;		// Unused
	(void)rrtype;		// Unused
	(void)rdlen;		// Unused
	(void)rdata;		// Unused
	(void)ttl;			// Unused
	return(kDNSServiceErr_Unsupported);
	}

DNSServiceErrorType DNSServiceUpdateRecord
	(
	DNSServiceRef                       sdRef,
	DNSRecordRef                        RecordRef,     /* may be NULL */
	DNSServiceFlags                     flags,
	uint16_t                            rdlen,
	const void                          *rdata,
	uint32_t                            ttl
	)
	{
	(void)sdRef;		// Unused
	(void)RecordRef;	// Unused
	(void)flags;		// Unused
	(void)rdlen;		// Unused
	(void)rdata;		// Unused
	(void)ttl;			// Unused
	return(kDNSServiceErr_Unsupported);
	}

DNSServiceErrorType DNSServiceRemoveRecord
	(
	DNSServiceRef                 sdRef,
	DNSRecordRef                  RecordRef,
	DNSServiceFlags               flags
	)
	{
	(void)sdRef;		// Unused
	(void)RecordRef;	// Unused
	(void)flags;		// Unused
	return(kDNSServiceErr_Unsupported);
	}
#endif

//*************************************************************************************************************
// Browse for services

static void DNSServiceBrowseDispose(mDNS_DirectOP *op)
	{
	mDNS_DirectOP_Browse *x = (mDNS_DirectOP_Browse*)op;
	//LogMsg("DNSServiceBrowseDispose");
	mDNS_StopBrowse(&mDNSStorage, &x->q);
	mDNSPlatformMemFree(x);
	}

mDNSlocal void FoundInstance(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
	{
	DNSServiceFlags flags = AddRecord ? kDNSServiceFlagsAdd : (DNSServiceFlags)0;
	domainlabel name;
	domainname type, domain;
	char cname[MAX_DOMAIN_LABEL+1];			// Unescaped name: up to 63 bytes plus C-string terminating NULL.
	char ctype[MAX_ESCAPED_DOMAIN_NAME];
	char cdom [MAX_ESCAPED_DOMAIN_NAME];
	mDNS_DirectOP_Browse *x = (mDNS_DirectOP_Browse*)question->QuestionContext;
	(void)m;		// Unused
	
	if (answer->rrtype != kDNSType_PTR)
		{ LogMsg("FoundInstance: Should not be called with rrtype %d (not a PTR record)", answer->rrtype); return; }
	
	if (!DeconstructServiceName(&answer->rdata->u.name, &name, &type, &domain))
		{
		LogMsg("FoundInstance: %##s PTR %##s received from network is not valid DNS-SD service pointer",
			answer->name->c, answer->rdata->u.name.c);
		return;
		}

	ConvertDomainLabelToCString_unescaped(&name, cname);
	ConvertDomainNameToCString(&type, ctype);
	ConvertDomainNameToCString(&domain, cdom);
	if (x->callback)
		x->callback((DNSServiceRef)x, flags, 0, 0, cname, ctype, cdom, x->context);
	}

DNSServiceErrorType DNSServiceBrowse
	(
	DNSServiceRef                       *sdRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	const char                          *regtype,
	const char                          *domain,    /* may be NULL */
	DNSServiceBrowseReply               callback,
	void                                *context    /* may be NULL */
	)
	{
	mStatus err = mStatus_NoError;
	const char *errormsg = "Unknown";
	domainname t, d;
	mDNS_DirectOP_Browse *x;
	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused

	// Check parameters
	if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype))      { errormsg = "Illegal regtype"; goto badparam; }
	if (!MakeDomainNameFromDNSNameString(&d, *domain ? domain : "local.")) { errormsg = "Illegal domain";  goto badparam; }

	// Allocate memory, and handle failure
	x = (mDNS_DirectOP_Browse *)mDNSPlatformMemAllocate(sizeof(*x));
	if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; }

	// Set up object
	x->disposefn = DNSServiceBrowseDispose;
	x->callback  = callback;
	x->context   = context;
	x->q.QuestionContext = x;

	// Do the operation
	err = mDNS_StartBrowse(&mDNSStorage, &x->q, &t, &d, mDNSInterface_Any, (flags & kDNSServiceFlagsForceMulticast) != 0, FoundInstance, x);
	if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_StartBrowse"; goto fail; }

	// Succeeded: Wrap up and return
	*sdRef = (DNSServiceRef)x;
	return(mStatus_NoError);

badparam:
	err = mStatus_BadParamErr;
fail:
	LogMsg("DNSServiceBrowse(\"%s\", \"%s\") failed: %s (%ld)", regtype, domain, errormsg, err);
	return(err);
	}

//*************************************************************************************************************
// Resolve Service Info

static void DNSServiceResolveDispose(mDNS_DirectOP *op)
	{
	mDNS_DirectOP_Resolve *x = (mDNS_DirectOP_Resolve*)op;
	if (x->qSRV.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->qSRV);
	if (x->qTXT.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->qTXT);
	mDNSPlatformMemFree(x);
	}

mDNSlocal void FoundServiceInfo(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
	{
	mDNS_DirectOP_Resolve *x = (mDNS_DirectOP_Resolve*)question->QuestionContext;
	(void)m;	// Unused
	if (!AddRecord)
		{
		if (answer->rrtype == kDNSType_SRV && x->SRV == answer) x->SRV = mDNSNULL;
		if (answer->rrtype == kDNSType_TXT && x->TXT == answer) x->TXT = mDNSNULL;
		}
	else
		{
		if (answer->rrtype == kDNSType_SRV) x->SRV = answer;
		if (answer->rrtype == kDNSType_TXT) x->TXT = answer;
		if (x->SRV && x->TXT && x->callback)
			{
			char fullname[MAX_ESCAPED_DOMAIN_NAME], targethost[MAX_ESCAPED_DOMAIN_NAME];
		    ConvertDomainNameToCString(answer->name, fullname);
		    ConvertDomainNameToCString(&x->SRV->rdata->u.srv.target, targethost);
			x->callback((DNSServiceRef)x, 0, 0, kDNSServiceErr_NoError, fullname, targethost,
				x->SRV->rdata->u.srv.port.NotAnInteger, x->TXT->rdlength, (unsigned char*)x->TXT->rdata->u.txt.c, x->context);
			}
		}
	}

DNSServiceErrorType DNSServiceResolve
	(
	DNSServiceRef                       *sdRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	const char                          *name,
	const char                          *regtype,
	const char                          *domain,
	DNSServiceResolveReply              callback,
	void                                *context  /* may be NULL */
	)
	{
	mStatus err = mStatus_NoError;
	const char *errormsg = "Unknown";
	domainlabel n;
	domainname t, d, srv;
	mDNS_DirectOP_Resolve *x;

	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused

	// Check parameters
	if (!name[0]    || !MakeDomainLabelFromLiteralString(&n, name  )) { errormsg = "Bad Instance Name"; goto badparam; }
	if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Bad Service Type";  goto badparam; }
	if (!domain[0]  || !MakeDomainNameFromDNSNameString(&d, domain )) { errormsg = "Bad Domain";        goto badparam; }
	if (!ConstructServiceName(&srv, &n, &t, &d))                      { errormsg = "Bad Name";          goto badparam; }

	// Allocate memory, and handle failure
	x = (mDNS_DirectOP_Resolve *)mDNSPlatformMemAllocate(sizeof(*x));
	if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; }

	// Set up object
	x->disposefn = DNSServiceResolveDispose;
	x->callback  = callback;
	x->context   = context;
	x->SRV       = mDNSNULL;
	x->TXT       = mDNSNULL;

	x->qSRV.ThisQInterval       = -1;		// So that DNSServiceResolveDispose() knows whether to cancel this question
	x->qSRV.InterfaceID         = mDNSInterface_Any;
	x->qSRV.Target              = zeroAddr;
	AssignDomainName(&x->qSRV.qname, &srv);
	x->qSRV.qtype               = kDNSType_SRV;
	x->qSRV.qclass              = kDNSClass_IN;
	x->qSRV.LongLived           = mDNSfalse;
	x->qSRV.ExpectUnique        = mDNStrue;
	x->qSRV.ForceMCast          = mDNSfalse;
	x->qSRV.ReturnIntermed      = mDNSfalse;
	x->qSRV.QuestionCallback    = FoundServiceInfo;
	x->qSRV.QuestionContext     = x;

	x->qTXT.ThisQInterval       = -1;		// So that DNSServiceResolveDispose() knows whether to cancel this question
	x->qTXT.InterfaceID         = mDNSInterface_Any;
	x->qTXT.Target              = zeroAddr;
	AssignDomainName(&x->qTXT.qname, &srv);
	x->qTXT.qtype               = kDNSType_TXT;
	x->qTXT.qclass              = kDNSClass_IN;
	x->qTXT.LongLived           = mDNSfalse;
	x->qTXT.ExpectUnique        = mDNStrue;
	x->qTXT.ForceMCast          = mDNSfalse;
	x->qTXT.ReturnIntermed      = mDNSfalse;
	x->qTXT.QuestionCallback    = FoundServiceInfo;
	x->qTXT.QuestionContext     = x;

	err = mDNS_StartQuery(&mDNSStorage, &x->qSRV);
	if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery qSRV"; goto fail; }
	err = mDNS_StartQuery(&mDNSStorage, &x->qTXT);
	if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery qTXT"; goto fail; }

	// Succeeded: Wrap up and return
	*sdRef = (DNSServiceRef)x;
	return(mStatus_NoError);

badparam:
	err = mStatus_BadParamErr;
fail:
	LogMsg("DNSServiceResolve(\"%s\", \"%s\", \"%s\") failed: %s (%ld)", name, regtype, domain, errormsg, err);
	return(err);
	}

//*************************************************************************************************************
// Connection-oriented calls

// Not yet implemented, so don't include in stub library
// We DO include it in the actual Extension, so that if a later client compiled to use this
// is run against this Extension, it will get a reasonable error code instead of just
// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link)
#if !MDNS_BUILDINGSTUBLIBRARY
DNSServiceErrorType DNSServiceCreateConnection(DNSServiceRef *sdRef)
	{
	(void)sdRef;	// Unused
	return(kDNSServiceErr_Unsupported);
	}

DNSServiceErrorType DNSServiceRegisterRecord
	(
	DNSServiceRef                       sdRef,
	DNSRecordRef                        *RecordRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	const char                          *fullname,
	uint16_t                            rrtype,
	uint16_t                            rrclass,
	uint16_t                            rdlen,
	const void                          *rdata,
	uint32_t                            ttl,
	DNSServiceRegisterRecordReply       callback,
	void                                *context    /* may be NULL */
	)
	{
	(void)sdRef;			// Unused
	(void)RecordRef;		// Unused
	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused
	(void)fullname;			// Unused
	(void)rrtype;			// Unused
	(void)rrclass;			// Unused
	(void)rdlen;			// Unused
	(void)rdata;			// Unused
	(void)ttl;				// Unused
	(void)callback;			// Unused
	(void)context;			// Unused
	return(kDNSServiceErr_Unsupported);
	}
#endif

//*************************************************************************************************************
// DNSServiceQueryRecord

static void DNSServiceQueryRecordDispose(mDNS_DirectOP *op)
	{
	mDNS_DirectOP_QueryRecord *x = (mDNS_DirectOP_QueryRecord*)op;
	if (x->q.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->q);
	mDNSPlatformMemFree(x);
	}

mDNSlocal void DNSServiceQueryRecordResponse(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
	{
	mDNS_DirectOP_QueryRecord *x = (mDNS_DirectOP_QueryRecord*)question->QuestionContext;
	char fullname[MAX_ESCAPED_DOMAIN_NAME];
	(void)m;	// Unused
	ConvertDomainNameToCString(answer->name, fullname);
	x->callback((DNSServiceRef)x, AddRecord ? kDNSServiceFlagsAdd : (DNSServiceFlags)0, 0, kDNSServiceErr_NoError,
		fullname, answer->rrtype, answer->rrclass, answer->rdlength, answer->rdata->u.data, answer->rroriginalttl, x->context);
	}

DNSServiceErrorType DNSServiceQueryRecord
	(
	DNSServiceRef                       *sdRef,
	DNSServiceFlags                     flags,
	uint32_t                            interfaceIndex,
	const char                          *fullname,
	uint16_t                            rrtype,
	uint16_t                            rrclass,
	DNSServiceQueryRecordReply          callback,
	void                                *context  /* may be NULL */
	)
	{
	mStatus err = mStatus_NoError;
	const char *errormsg = "Unknown";
	mDNS_DirectOP_QueryRecord *x;

	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused

	// Allocate memory, and handle failure
	x = (mDNS_DirectOP_QueryRecord *)mDNSPlatformMemAllocate(sizeof(*x));
	if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; }

	// Set up object
	x->disposefn = DNSServiceQueryRecordDispose;
	x->callback  = callback;
	x->context   = context;

	x->q.ThisQInterval       = -1;		// So that DNSServiceResolveDispose() knows whether to cancel this question
	x->q.InterfaceID         = mDNSInterface_Any;
	x->q.Target              = zeroAddr;
	MakeDomainNameFromDNSNameString(&x->q.qname, fullname);
	x->q.qtype               = rrtype;
	x->q.qclass              = rrclass;
	x->q.LongLived           = (flags & kDNSServiceFlagsLongLivedQuery) != 0;
	x->q.ExpectUnique        = mDNSfalse;
	x->q.ForceMCast          = (flags & kDNSServiceFlagsForceMulticast) != 0;
	x->q.ReturnIntermed      = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
	x->q.QuestionCallback    = DNSServiceQueryRecordResponse;
	x->q.QuestionContext     = x;

	err = mDNS_StartQuery(&mDNSStorage, &x->q);
	if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery"; goto fail; }

	// Succeeded: Wrap up and return
	*sdRef = (DNSServiceRef)x;
	return(mStatus_NoError);

fail:
	LogMsg("DNSServiceQueryRecord(\"%s\", %d, %d) failed: %s (%ld)", fullname, rrtype, rrclass, errormsg, err);
	return(err);
	}

//*************************************************************************************************************
// DNSServiceReconfirmRecord

// Not yet implemented, so don't include in stub library
// We DO include it in the actual Extension, so that if a later client compiled to use this
// is run against this Extension, it will get a reasonable error code instead of just
// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link)
#if !MDNS_BUILDINGSTUBLIBRARY
DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord
	(
	DNSServiceFlags                    flags,
	uint32_t                           interfaceIndex,
	const char                         *fullname,
	uint16_t                           rrtype,
	uint16_t                           rrclass,
	uint16_t                           rdlen,
	const void                         *rdata
	)
	{
	(void)flags;			// Unused
	(void)interfaceIndex;	// Unused
	(void)fullname;			// Unused
	(void)rrtype;			// Unused
	(void)rrclass;			// Unused
	(void)rdlen;			// Unused
	(void)rdata;			// Unused
	return(kDNSServiceErr_Unsupported);
	}
#endif