/*
 * Copyright (c) 2002-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 "D2D.h"
#include "mDNSEmbeddedAPI.h"        // Defines the interface provided to the client layer above
#include "DNSCommon.h"
#include "mDNSMacOSX.h"             // Defines the specific types needed to run mDNS on this platform
#include "dns_sd.h"                 // For mDNSInterface_LocalOnly etc.
#include "dns_sd_internal.h"
#include "misc_utilities.h"
#include "uds_daemon.h"
#include <mdns/powerlog.h>
#include "mdns_strict.h"

#define D2DIsTransportLikeAWDL(X) ((X) == D2DAWDLTransport || (X) == D2DWiFiAwareTransport)

D2DStatus D2DInitialize(CFRunLoopRef runLoop, D2DServiceCallback serviceCallback, void* userData) __attribute__((weak_import));
D2DStatus D2DRetain(D2DServiceInstance instanceHandle, D2DTransportType transportType) __attribute__((weak_import));
D2DStatus D2DStopAdvertisingPairOnTransport(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize, D2DTransportType transport) __attribute__((weak_import));
D2DStatus D2DRelease(D2DServiceInstance instanceHandle, D2DTransportType transportType) __attribute__((weak_import));
D2DStatus D2DStartAdvertisingPairOnTransport(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize, D2DTransportType transport) __attribute__((weak_import));
D2DStatus D2DStartBrowsingForKeyOnTransport(const Byte *key, const size_t keySize, D2DTransportType transport) __attribute__((weak_import));
D2DStatus D2DStopBrowsingForKeyOnTransport(const Byte *key, const size_t keySize, D2DTransportType transport) __attribute__((weak_import));
void D2DStartResolvingPairOnTransport(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize, D2DTransportType transport) __attribute__((weak_import));
void D2DStopResolvingPairOnTransport(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize, D2DTransportType transport) __attribute__((weak_import));
D2DStatus D2DTerminate(void) __attribute__((weak_import));

#pragma mark - D2D Support

mDNSexport void D2D_start_advertising_interface(NetworkInterfaceInfo *interface)
{
    // AWDL wants the address and reverse address PTR record communicated
    // via the D2D interface layer.
    if (interface->InterfaceID == AWDLInterfaceID || interface->InterfaceID == WiFiAwareInterfaceID)
    {
        // only log if we have a valid record to start advertising
        if (interface->RR_A.resrec.RecordType || interface->RR_PTR.resrec.RecordType)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2D_start_advertising_interface - ifname: " PUB_S,
                interface->ifname);
        }

        if (interface->RR_A.resrec.RecordType)
            external_start_advertising_service(&interface->RR_A.resrec, 0, 0);
        if (interface->RR_PTR.resrec.RecordType)
            external_start_advertising_service(&interface->RR_PTR.resrec, 0, 0);
    }
}

mDNSexport void D2D_stop_advertising_interface(NetworkInterfaceInfo *interface)
{
    if (interface->InterfaceID == AWDLInterfaceID || interface->InterfaceID == WiFiAwareInterfaceID)
    {
        // only log if we have a valid record to stop advertising
        if (interface->RR_A.resrec.RecordType || interface->RR_PTR.resrec.RecordType)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2D_stop_advertising_interface - ifname: " PUB_S,
                interface->ifname);
        }

        if (interface->RR_A.resrec.RecordType)
            external_stop_advertising_service(&interface->RR_A.resrec, 0, 0);
        if (interface->RR_PTR.resrec.RecordType)
            external_stop_advertising_service(&interface->RR_PTR.resrec, 0, 0);
    }
}

// If record would have been advertised to the D2D plugin layer, stop that advertisement.
mDNSexport void D2D_stop_advertising_record(AuthRecord *ar)
{
    DNSServiceFlags flags = deriveD2DFlagsFromAuthRecType(ar->ARType);
    if (callExternalHelpers(ar->resrec.InterfaceID, ar->resrec.name, flags))
    {
        external_stop_advertising_service(&ar->resrec, flags, 0);
    }
}

// If record should be advertised to the D2D plugin layer, start that advertisement.
mDNSexport void D2D_start_advertising_record(AuthRecord *ar)
{
    DNSServiceFlags flags = deriveD2DFlagsFromAuthRecType(ar->ARType);
    if (callExternalHelpers(ar->resrec.InterfaceID, ar->resrec.name, flags))
    {
        external_start_advertising_service(&ar->resrec, flags, 0);
    }
}

// Name compression items for fake packet version number 1
static const mDNSu8 compression_packet_v1 = 0x01;

static DNSMessage compression_base_msg = { { {{0}}, {{0}}, 2, 1, 0, 0 }, "\x04_tcp\x05local\x00\x00\x0C\x00\x01\x04_udp\xC0\x11\x00\x0C\x00\x01" };
static mDNSu8 *const compression_limit = (mDNSu8 *) &compression_base_msg + sizeof(DNSMessage);
static mDNSu8 *const compression_lhs = (mDNSu8 *const) compression_base_msg.data + 27;

mDNSlocal void FreeD2DARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result);

typedef struct D2DRecordListElem
{
    struct D2DRecordListElem *next;
    D2DServiceInstance       instanceHandle;
    D2DTransportType         transportType;
    AuthRecord               ar;    // must be last in the structure to accomodate extra space
                                    // allocated for large records.
} D2DRecordListElem;

static D2DRecordListElem *D2DRecords = NULL; // List of records returned with D2DServiceFound events

typedef struct D2DBrowseListElem
{
    struct D2DBrowseListElem *next;
    domainname name;
    mDNSu16 type;
    unsigned int refCount;
} D2DBrowseListElem;

D2DBrowseListElem* D2DBrowseList = NULL;

mDNSlocal mDNSu8 *putVal16(mDNSu8 *ptr, mDNSu16 val)
{
    ptr[0] = (mDNSu8)((val >> 8 ) & 0xFF);
    ptr[1] = (mDNSu8)((val      ) & 0xFF);
    return ptr + sizeof(mDNSu16);
}

mDNSlocal mDNSu8 *putVal32(mDNSu8 *ptr, mDNSu32 val)
{
    ptr[0] = (mDNSu8)((val >> 24) & 0xFF);
    ptr[1] = (mDNSu8)((val >> 16) & 0xFF);
    ptr[2] = (mDNSu8)((val >>  8) & 0xFF);
    ptr[3] = (mDNSu8)((val      ) & 0xFF);
    return ptr + sizeof(mDNSu32);
}

mDNSlocal void DomainnameToLower(const domainname * const in, domainname * const out)
{
    const mDNSu8 * const start = (const mDNSu8 * const)in;
    const mDNSu8 *ptr = (const mDNSu8*)start;
    while(*ptr)
    {
        mDNSu8 c = *ptr;
        out->c[ptr-start] = *ptr;
        ptr++;
        for (; c; c--,ptr++) out->c[ptr-start] = mDNSIsUpperCase(*ptr) ? (*ptr - 'A' + 'a') : *ptr;
    }
    out->c[ptr-start] = *ptr;
}

mDNSlocal mDNSu8 * DNSNameCompressionBuildLHS(const domainname* typeDomain, mDNSu16 qtype)
{
    mDNSu8 *ptr = putDomainNameAsLabels(&compression_base_msg, compression_lhs, compression_limit, typeDomain);
    if (!ptr) return ptr;
    *ptr = (qtype >> 8) & 0xff;
    ptr += 1;
    *ptr = qtype & 0xff;
    ptr += 1;
    *ptr = compression_packet_v1;
    return ptr + 1;
}

mDNSlocal mDNSu8 * DNSNameCompressionBuildRHS(mDNSu8 *start, const ResourceRecord *const resourceRecord)
{
    return putRData(&compression_base_msg, start, compression_limit, resourceRecord);
}

mDNSlocal void PrintHelper(const char *const tag, const mDNSu8 *lhs, mDNSu16 lhs_len, const mDNSu8 *rhs, mDNSu16 rhs_len)
{
    if (mDNS_LoggingEnabled)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, PUB_S ": LHS: (%d bytes) " PRI_HEX, tag, lhs_len, HEX_PARAM(lhs, lhs_len));
        if (rhs)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, PUB_S ": RHS: (%d bytes) " PRI_HEX, tag, rhs_len, HEX_PARAM(rhs, rhs_len));
        }
    }
}

mDNSlocal void FreeD2DARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
{
    (void)m;  // unused
    if (result == mStatus_MemFree)
    {
        D2DRecordListElem **ptr = &D2DRecords;
        D2DRecordListElem *tmp;
        while (*ptr && &(*ptr)->ar != rr) ptr = &(*ptr)->next;
        if (!*ptr)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "FreeD2DARElemCallback: Could not find in D2DRecords: " PRI_S, ARDisplayString(m, rr));
            return;
        }
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "FreeD2DARElemCallback: Found in D2DRecords: " PRI_S, ARDisplayString(m, rr));
        tmp = *ptr;
        *ptr = (*ptr)->next;
        // Just because we stoppped browsing, doesn't mean we should tear down the PAN connection.
        mDNSPlatformMemFree(tmp);
    }
}

mDNSexport void external_connection_release(const domainname *instance)
{
    (void) instance;
    D2DRecordListElem *ptr = D2DRecords;

    for ( ; ptr ; ptr = ptr->next)
    {
        if ((ptr->ar.resrec.rrtype == kDNSServiceType_PTR) &&
             SameDomainName(&ptr->ar.rdatastorage.u.name, instance))
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "external_connection_release: Calling D2DRelease - "
                "instanceHandle: %p, transportType: %d", ptr->instanceHandle, ptr->transportType);
            if (D2DRelease) D2DRelease(ptr->instanceHandle, ptr->transportType);
        }
    }
}

mDNSlocal void xD2DClearCache(const domainname *regType, mDNSu16 qtype)
{
    D2DRecordListElem *ptr = D2DRecords;
    while (ptr)
    {
        D2DRecordListElem *tmp = ptr;
        ptr = ptr->next;
        if ((tmp->ar.resrec.rrtype == qtype) && SameDomainName(&tmp->ar.namestorage, regType))
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT,
                "xD2DClearCache: Clearing and deregistering cache record - "
                "name: " PRI_DM_NAME ", rrtype: " PUB_DNS_TYPE ", auth record: " PRI_S, DM_NAME_PARAM(regType),
                DNS_TYPE_PARAM(qtype), ARDisplayString(&mDNSStorage, &tmp->ar));

            mDNS_Deregister(&mDNSStorage, &tmp->ar);
            // Memory will be freed and element removed in FreeD2DARElemCallback
        }
    }
}

mDNSlocal D2DBrowseListElem ** D2DFindInBrowseList(const domainname *const name, mDNSu16 type)
{
    D2DBrowseListElem **ptr = &D2DBrowseList;

    for ( ; *ptr; ptr = &(*ptr)->next)
        if ((*ptr)->type == type && SameDomainName(&(*ptr)->name, name))
            break;

    return ptr;
}

mDNSlocal unsigned int D2DBrowseListRefCount(const domainname *const name, mDNSu16 type)
{
    D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);
    return *ptr ? (*ptr)->refCount : 0;
}

mDNSlocal void D2DBrowseListRetain(const domainname *const name, mDNSu16 type)
{
    D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);

    if (!*ptr)
    {
        *ptr = (D2DBrowseListElem *) mDNSPlatformMemAllocateClear(sizeof(**ptr));
        (*ptr)->type = type;
        AssignDomainName(&(*ptr)->name, name);
    }
    (*ptr)->refCount += 1;

    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRetain - "
        "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", ref count: %u", DM_NAME_PARAM(&(*ptr)->name),
        DNS_TYPE_PARAM((*ptr)->type), (*ptr)->refCount);
}

// Returns true if found in list, false otherwise
mDNSlocal bool D2DBrowseListRelease(const domainname *const name, mDNSu16 type)
{
    D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);

    if (!*ptr)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRelease item not found in the list - "
            "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE, DM_NAME_PARAM(name), DNS_TYPE_PARAM(type));
        return false;
    }

    (*ptr)->refCount -= 1;

    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRelease - "
        "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", ref count: %u", DM_NAME_PARAM(&(*ptr)->name),
        DNS_TYPE_PARAM((*ptr)->type), (*ptr)->refCount);

    if (!(*ptr)->refCount)
    {
        D2DBrowseListElem *tmp = *ptr;
        *ptr = (*ptr)->next;
        mDNSPlatformMemFree(tmp);
    }
    return true;
}

mDNSlocal mDNSBool LabelPairIsForService(const mDNSu8 *const firstLabel)
{
    // See <https://datatracker.ietf.org/doc/html/rfc6763#section-7> for more info.
    const int firstLabelLen = *firstLabel;
    if ((firstLabelLen > 0) && (firstLabel[1] == '_'))
    {
        const mDNSu8 *const secondLabel = firstLabel + 1 + firstLabelLen;
        const mDNSu8 *const protoLabelTCP = (const mDNSu8 *)"\x4" "_tcp";
        const mDNSu8 *const protoLabelUDP = (const mDNSu8 *)"\x4" "_udp";
        if (SameDomainLabel(secondLabel, protoLabelTCP) || SameDomainLabel(secondLabel, protoLabelUDP))
        {
            return mDNStrue;
        }
    }
    return mDNSfalse;
}

mDNSlocal mDNSBool DomainNameIsForBrowsing(const domainname *const name)
{
    // Check if the domain name is used for DNS-SD browsing. Specifically, check if the domain name is used for Service
    // Instance Enumeration, i.e., of the form <service>.<parent domain>, or for Selective Instance Enumeration, i.e.,
    // of the form <subtype>._sub.<service>.<parent domain>. Note that <subtype> is a single label and <service> is a
    // pair of labels starting with a service name label, followed by a protocol label, either "_tcp" or "_udp". See
    // <https://datatracker.ietf.org/doc/html/rfc6763#section-7>.
    int labelLen;
    const mDNSu8 *label3 = mDNSNULL;
    const mDNSu8 *label2 = mDNSNULL;
    for (const mDNSu8 *label1 = name->c; (labelLen = *label1) != 0; label1 += (1 + labelLen))
    {
        if (!label3 && !label2 && label1)
        {
            if (LabelPairIsForService(label1))
            {
                return mDNStrue;
            }
        }
        else if (label3 && label2 && label1)
        {
            const mDNSu8 *const subLabel  = (const mDNSu8 *)"\x4" "_sub";
            if (SameDomainLabel(label2, subLabel) && LabelPairIsForService(label1))
            {
                return mDNStrue;
            }
            break;
        }
        label3 = label2;
        label2 = label1;
    }
    return mDNSfalse;
}

mDNSlocal mStatus xD2DParseCompressedPacket(const mDNSu8 * const lhs, const mDNSu16 lhs_len, const mDNSu8 * const rhs, const mDNSu16 rhs_len, const mDNSu32 ttl, mDNSu8 ** const out_ptr)
{
    // Sanity check that key array (lhs) has one domain name, followed by the record type and single byte D2D
    // plugin protocol version number.
    // Note, we don't have a DNSMessage pointer at this point, so just pass in the lhs value as the lower bound
    // of the input bytes we are processing.  skipDomainName() does not try to follow name compression pointers,
    // so it is safe to pass it the key byte array since it will stop parsing the DNS name and return a pointer
    // to the byte after the first name compression pointer it encounters.
    const mDNSu8 *keyp = skipDomainName((const DNSMessage *const) lhs, lhs, lhs + lhs_len);

    // There should be 3 bytes remaining in a valid key,
    // two for the DNS record type, and one for the D2D protocol version number.
    if (keyp == NULL || (keyp + 3 != (lhs + lhs_len)))
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DParseCompressedPacket: Could not parse DNS name in key");
        return mStatus_Incompatible;
    }
    const mDNSu16 recordType = ((mDNSu16)(keyp[0] << 8)) | keyp[1];
    keyp += 2;   // point to D2D compression packet format version byte
    if (*keyp != compression_packet_v1)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DParseCompressedPacket: Invalid D2D packet version - "
            "version: %d", *keyp);
        return mStatus_Incompatible;
    }

    mDNSu8 *ptr = compression_lhs; // pointer to the end of our fake packet

    // Check to make sure we're not going to go past the end of the DNSMessage data
    // 7 = 2 for CLASS (-1 for our version) + 4 for TTL + 2 for RDLENGTH
    if (ptr + lhs_len - 7 + rhs_len >= compression_limit) return mStatus_NoMemoryErr;

    // Copy the LHS onto our fake wire packet
    const mDNSu8 *const recordNamePtr = ptr;
    mDNSPlatformMemCopy(ptr, lhs, lhs_len);
    ptr += lhs_len - 1;

    // Check the 'fake packet' version number, to ensure that we know how to decompress this data
    if (*ptr != compression_packet_v1) return mStatus_Incompatible;

    // two bytes of CLASS
    mDNSu8 *const recordClassPtr = ptr;
    ptr = putVal16(ptr, kDNSClass_IN | kDNSClass_UniqueRRSet);

    // four bytes of TTL
    ptr = putVal32(ptr, ttl);

    // Copy the RHS length into the RDLENGTH of our fake wire packet
    ptr = putVal16(ptr, rhs_len);

    // Copy the RHS onto our fake wire packet
    mDNSPlatformMemCopy(ptr, rhs, rhs_len);
    ptr += rhs_len;
    *out_ptr = ptr;

    domainname recordName;
    const mDNSu8 *const recordNameNextPtr = getDomainName(&compression_base_msg, recordNamePtr, compression_limit,
        &recordName);
    if (!recordNameNextPtr)
    {
        AssignConstStringDomainName(&recordName, "");
    }

    // If the record answers a DNS-SD PTR browsing query, then clear the cache-flush bit. Such records are part of a
    // shared resource record set since PTR records for instances of a given service type can come from more than one
    // mDNS responder. From <https://datatracker.ietf.org/doc/html/rfc6762#section-10.2>:
    //
    //    The cache-flush bit MUST NOT ever be set in any shared resource
    //    record.  To do so would cause all the other shared versions of this
    //    resource record with different rdata from different responders to be
    //    immediately deleted from all the caches on the network.
    if (recordType == kDNSServiceType_PTR && DomainNameIsForBrowsing(&recordName))
    {
        putVal16(recordClassPtr, kDNSClass_IN);
    }
    if (mDNS_LoggingEnabled)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "xD2DParseCompressedPacket: Our Bytes - name: " PRI_DM_NAME
            ", type: " PUB_DNS_TYPE ", TTL: %u, rdata length: %u", DM_NAME_PARAM(&recordName),
            DNS_TYPE_PARAM(recordType), ttl, rhs_len);
    }
    return mStatus_NoError;
}

mDNSlocal mStatus xD2DParse(const mDNSu8 * const lhs, const mDNSu16 lhs_len, const mDNSu8 * const rhs, const mDNSu16 rhs_len, D2DRecordListElem **D2DListp)
{
    mDNS *const m = &mDNSStorage;
    mStatus err;
    mDNSu8 *ptr;
    err = xD2DParseCompressedPacket(lhs, lhs_len, rhs, rhs_len, 120, &ptr);
    if (err != mStatus_NoError)  return err;

    const mDNSu8 *const next = GetLargeResourceRecord(m, &compression_base_msg, compression_lhs, ptr, mDNSInterface_Any, kDNSRecordTypePacketAns, &m->rec);
    if (!next || m->rec.r.resrec.RecordType == kDNSRecordTypePacketNegative)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DParse: failed to get large RR");
        mDNSCoreResetRecord(m);
        return mStatus_UnknownErr;
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DParse got record - "
            "name: " PRI_DM_NAME ", rrtype: " PUB_DNS_TYPE ", rdata: " PRI_S, DM_NAME_PARAM(m->rec.r.resrec.name),
            DNS_TYPE_PARAM(m->rec.r.resrec.rrtype), CRDisplayString(m, &m->rec.r));
    }

    *D2DListp = (D2DRecordListElem *) mDNSPlatformMemAllocateClear(sizeof(D2DRecordListElem) + (m->rec.r.resrec.rdlength <= sizeof(RDataBody) ? 0 : m->rec.r.resrec.rdlength - sizeof(RDataBody)));
    if (!*D2DListp) return mStatus_NoMemoryErr;

    AuthRecord *rr = &(*D2DListp)->ar;
    mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSInterface_P2P, m->rec.r.resrec.rrtype, 7200, kDNSRecordTypeShared, AuthRecordP2P, FreeD2DARElemCallback, NULL);
    AssignDomainName(&rr->namestorage, &m->rec.namestorage);
    rr->resrec.rdlength = m->rec.r.resrec.rdlength;
    rr->resrec.rdata->MaxRDLength = m->rec.r.resrec.rdlength;
    mDNSPlatformMemCopy(rr->resrec.rdata->u.data, m->rec.r.resrec.rdata->u.data, m->rec.r.resrec.rdlength);
    rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
    SetNewRData(&rr->resrec, mDNSNULL, 0);  // Sets rr->rdatahash for us

    mDNSCoreResetRecord(m);

    return mStatus_NoError;
}

mDNSlocal void xD2DReceiveResponse(const DNSMessage *const response, const mDNSu8 *end, D2DTransportType transportType)
{
    mDNS *const m = &mDNSStorage;
    mDNS_Lock(m);
    mDNSCoreReceiveD2DResponse(m, response, end, mDNSNULL, MulticastDNSPort, &AllDNSLinkGroup_v6, MulticastDNSPort,
        (transportType == D2DAWDLTransport) ? AWDLInterfaceID : WiFiAwareInterfaceID);
    mDNS_Unlock(m);
}

mDNSexport void xD2DAddToCache(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
{
    mDNS *const m = &mDNSStorage;
    if (result == kD2DSuccess)
    {
        if ( key == NULL || value == NULL || keySize == 0 || valueSize == 0)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DAddToCache: NULL Byte * passed in or length == 0");
            return;
        }

        mStatus err;
        if (D2DIsTransportLikeAWDL(transportType))
        {
            mDNSu8 *end_ptr; // pointer to the end of our fake packet
            err = xD2DParseCompressedPacket(key, (mDNSu16)keySize, value, (mDNSu16)valueSize, kStandardTTL, &end_ptr);
            if (err != mStatus_NoError)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DAddToCache: xD2DParseCompressedPacket failed - "
                    "error: %d", err);
                return;
            }
            xD2DReceiveResponse(&compression_base_msg, end_ptr, transportType);
        }
        else
        {
            D2DRecordListElem *ptr = NULL;

            err = xD2DParse((const mDNSu8 * const)key, (const mDNSu16)keySize, (const mDNSu8 * const)value, (const mDNSu16)valueSize, &ptr);
            if (err)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DAddToCache: xD2DParse failed - "
                    "error: %d", err);
                PrintHelper(__func__, key, (mDNSu16)keySize, value, (mDNSu16)valueSize);
                if (ptr)
                    mDNSPlatformMemFree(ptr);
                return;
            }
            err = mDNS_Register(m, &ptr->ar);
            if (err)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DAddToCache: mDNS_Register failed - "
                    "error: %d, name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", auth record: " PRI_S, err,
                    DM_NAME_PARAM(ptr->ar.resrec.name), DNS_TYPE_PARAM(ptr->ar.resrec.rrtype),
                    ARDisplayString(m, &ptr->ar));
                mDNSPlatformMemFree(ptr);
                return;
            }

            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DAddToCache: mDNS_Register succeeded - "
                "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", Interface ID: %p, auth record: " PRI_S,
                DM_NAME_PARAM(ptr->ar.resrec.name), DNS_TYPE_PARAM(ptr->ar.resrec.rrtype), ptr->ar.resrec.InterfaceID,
                ARDisplayString(m, &ptr->ar));

            ptr->instanceHandle = instanceHandle;
            ptr->transportType = transportType;
            ptr->next = D2DRecords;
            D2DRecords = ptr;
       }
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DAddToCache: Unexpected result - result: %d", result);
    }
}

mDNSlocal D2DRecordListElem * xD2DFindInList(const Byte *const key, const size_t keySize, const Byte *const value, const size_t valueSize)
{
    D2DRecordListElem *ptr = D2DRecords;
    D2DRecordListElem *arptr = NULL;

    if ( key == NULL || value == NULL || keySize == 0 || valueSize == 0)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DFindInList: NULL Byte * passed in or length == 0");
        return NULL;
    }

    mStatus err = xD2DParse((const mDNSu8 *const)key, (const mDNSu16)keySize, (const mDNSu8 *const)value, (const mDNSu16)valueSize, &arptr);
    if (err)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DFindInList: xD2DParse failed - error: %d", err);
        PrintHelper(__func__, key, (mDNSu16)keySize, value, (mDNSu16)valueSize);
        if (arptr)
            mDNSPlatformMemFree(arptr);
        return NULL;
    }

    while (ptr)
    {
        if (IdenticalResourceRecord(&arptr->ar.resrec, &ptr->ar.resrec)) break;
        ptr = ptr->next;
    }

    if (!ptr)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DFindInList: Could not find in D2DRecords - "
            "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", auth record: " PRI_S, DM_NAME_PARAM(arptr->ar.resrec.name),
            DNS_TYPE_PARAM(arptr->ar.resrec.rrtype), ARDisplayString(&mDNSStorage, &arptr->ar));
    }
    mDNSPlatformMemFree(arptr);
    return ptr;
}

mDNSexport void xD2DRemoveFromCache(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
{
    (void)instanceHandle; // We don't care about this, yet.

    if (result == kD2DSuccess)
    {
        if (D2DIsTransportLikeAWDL(transportType))
        {
            mDNSu8 *end_ptr; // pointer to the end of our fake packet
            mStatus err = xD2DParseCompressedPacket(key, (mDNSu16)keySize, value, (mDNSu16)valueSize, 0, &end_ptr);
            if (err != mStatus_NoError)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DRemoveFromCache: xD2DParseCompressedPacket failed"
                    " - error: %d", err);
                return;
            }
            xD2DReceiveResponse(&compression_base_msg, end_ptr, transportType);
        }
        else
        {
            D2DRecordListElem *ptr = xD2DFindInList(key, keySize, value, valueSize);
            if (ptr)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DRemoveFromCache: removing record from cache - "
                    "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", auth record: " PRI_S,
                    DM_NAME_PARAM(ptr->ar.resrec.name), DNS_TYPE_PARAM(ptr->ar.resrec.rrtype),
                    ARDisplayString(&mDNSStorage, &ptr->ar));
                mDNS_Deregister(&mDNSStorage, &ptr->ar);
            }
        }
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DRemoveFromCache: Unexpected result - result: %d", result);
    }
}

mDNSlocal void xD2DServiceResolved(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
{
    (void)key;
    (void)keySize;
    (void)value;
    (void)valueSize;

    if (result == kD2DSuccess)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DServiceResolved: Starting up PAN connection - "
            "instanceHandle: %p", instanceHandle);

        if (D2DRetain) D2DRetain(instanceHandle, transportType);
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DServiceResolved: Unexpected result - result: %d", result);
    }
}

mDNSlocal void xD2DRetainHappened(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
{
    (void)instanceHandle;
    (void)transportType;
    (void)key;
    (void)keySize;
    (void)value;
    (void)valueSize;

    if (result == kD2DSuccess)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DRetainHappened: Opening up PAN connection - "
            "instanceHandle: %p", instanceHandle);
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DRetainHappened: Unexpected result - result: %d", result);
    }
}

mDNSlocal void xD2DReleaseHappened(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
{
    (void)instanceHandle;
    (void)transportType;
    (void)key;
    (void)keySize;
    (void)value;
    (void)valueSize;

    if (result == kD2DSuccess)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DReleaseHappened: Closing PAN connection - "
            "instanceHandle: %p", instanceHandle);
    }
    else
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DReleaseHappened: Unexpected result - result: %d", result);
    }
}

mDNSlocal void removeCachedPeerRecords(const mDNSInterfaceID interface, const mDNSAddr *const peerAddr)
{
    mDNS *const m = &mDNSStorage;
    mDNS_Lock(m);
    mDNSu32 slot;
    const CacheGroup *cg;
    CacheRecord *cr;
    FORALL_CACHERECORDS(slot, cg, cr)
    {
        const ResourceRecord *const rr = &cr->resrec;
        if ((rr->InterfaceID == interface) && mDNSSameAddress(&cr->sourceAddress, peerAddr))
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_INFO,
                "Removing cached peer record -- peer address: " PRI_IP_ADDR ", name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE,
                peerAddr, DM_NAME_PARAM(rr->name), DNS_TYPE_PARAM(rr->rrtype));
            mDNS_PurgeCacheResourceRecord(m, cr);
        }
    }
    mDNS_Unlock(m);
}

mDNSlocal void xD2DPeerLostHappened(const D2DStatus result, __unused const D2DServiceInstance instanceHandle,
    const D2DTransportType transportType, __unused const Byte *const key, __unused const size_t keySize,
    const Byte *const value, const size_t valueSize)
{
    require_quiet(result == kD2DSuccess, exit);

    // Deduce the interface based on the transport type. Currently, only AWDL and WiFiAware transports are supported.
    mDNSInterfaceID interface;
    switch (transportType)
    {
    case D2DAWDLTransport:
        interface = AWDLInterfaceID;
        break;
    case D2DWiFiAwareTransport:
        interface = WiFiAwareInterfaceID;
        break;
    case D2DBluetoothTransport:
    case D2DWifiPeerToPeerTransport:
    default:
        goto exit;
    }

    // The peer's IPv6 address is passed in value byte array as an in6_addr_t. See <rdar://98848070>.
    in6_addr_t ipv6Addr;
    require_quiet(valueSize == sizeof(ipv6Addr), exit);

    memcpy(&ipv6Addr, value, sizeof(ipv6Addr));
    const mDNSAddr peerAddr = mDNSAddr_from_in6_addr(&ipv6Addr);
    removeCachedPeerRecords(interface, &peerAddr);

exit:
    return;
}

mDNSlocal void xD2DServiceCallback(D2DServiceEvent event, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize, void * __unused userData)
{
    KQueueLock();

    if (keySize   > 0xFFFF)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DServiceCallback: keySize too large - "
            "key size: %zu", keySize);
    }
    if (valueSize > 0xFFFF)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "xD2DServiceCallback: valueSize too large - "
            "value size: %zu", valueSize);
    }

    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DServiceCallback -- event: " PUB_D2D_SRV_EVENT, event);
    PrintHelper(__func__, key, (mDNSu16)keySize, value, (mDNSu16)valueSize);

    switch (event)
    {
    case D2DServiceFound:
        xD2DAddToCache(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    case D2DServiceLost:
        xD2DRemoveFromCache(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    case D2DServiceResolved:
        xD2DServiceResolved(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    case D2DServiceRetained:
        xD2DRetainHappened(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    case D2DServiceReleased:
        xD2DReleaseHappened(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    case D2DServicePeerLost:
        xD2DPeerLostHappened(result, instanceHandle, transportType, key, keySize, value, valueSize);
        break;
    default:
        break;
    }

    // Need to tickle the main kqueue loop to potentially handle records we removed or added.
    KQueueUnlock("xD2DServiceCallback");
}

// Map interface index and flags to a specific D2D transport type or D2DTransportMax if all installed plugins should be called.
// When D2DTransportMax is returned, if a specific transport should not be called, *excludedTransportType 
// will be set to the excluded transport value, otherwise, it will be set to D2DTransportMax to include all transports.
// If the return value is not D2DTransportMax, *excludedTransportType is undefined.
//
// The following D2D plugin types are currently defined in the DeviceToDeviceManager project.
//
//  D2DBluetoothTransport:      Legacy Bluetooth Person Area Network (PAN)
//  D2DWifiPeerToPeerTransport: Deprecated transport used for first generation of AirDrop on macOS. This plugin is no
//                              longer installed and thus the DeviceToDeviceManager will simply return if called with this value.
//  D2DAWDLTransport:           AWDL transport
//  D2DWiFiAwareTransport:      NAN transport
//
// See the logic below for when a given plugin call is enabled.
// If additional D2DTransportType values are defined in the future, this routine should be refactored to just
// return a set of D2DTransportType values to simplify the logic in the call sites and support the newly defined D2DTransportType values.

mDNSlocal D2DTransportType xD2DMapToTransportType(mDNSInterfaceID InterfaceID, DNSServiceFlags flags, D2DTransportType * excludedTransportType)
{
    // Set default to deprecated plugin value.
    *excludedTransportType = D2DWifiPeerToPeerTransport;

    // Call all D2D plugins when both kDNSServiceFlagsIncludeP2P and kDNSServiceFlagsIncludeAWDL are set.
    if ((flags & kDNSServiceFlagsIncludeP2P) && (flags & kDNSServiceFlagsIncludeAWDL))
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: call all active plugins since both "
            "kDNSServiceFlagsIncludeP2P and kDNSServiceFlagsIncludeAWDL are set");
        return D2DTransportMax;
    } 
    // Call D2DBluetoothTransport plugin when only kDNSServiceFlagsIncludeP2P is set.
    else if (flags & kDNSServiceFlagsIncludeP2P)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: returning D2DBluetoothTransport since only "
            "kDNSServiceFlagsIncludeP2P is set");
        return D2DBluetoothTransport;
    }
    // Call both D2DAWDLTransport and D2DWiFiAwareTransport plugins when only kDNSServiceFlagsIncludeAWDL is set.
    else if (flags & kDNSServiceFlagsIncludeAWDL)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: call AWDL and NAN plugins since kDNSServiceFlagsIncludeAWDL is set");
        *excludedTransportType = D2DBluetoothTransport;
        return D2DTransportMax;
    }

    // Call only D2DBluetoothTransport plugin when psuedo interface mDNSInterface_P2P is used.
    if (InterfaceID == mDNSInterface_P2P)
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: returning D2DBluetoothTransport for interface index "
            "mDNSInterface_P2P");
        return D2DBluetoothTransport;
    }

    // Compare to cached AWDL interface ID.
    if (AWDLInterfaceID && (InterfaceID == AWDLInterfaceID))
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: returning D2DAWDLTransport for interface index %p", InterfaceID);
        return D2DAWDLTransport;
    }
    if (WiFiAwareInterfaceID && (InterfaceID == WiFiAwareInterfaceID))
    {
        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: returning D2DWiFiAwareTransport for interface index %p", InterfaceID);
        return D2DWiFiAwareTransport;
    }

    // Return the deprecated and no longer installed plugin value for no matches to this point since it will result
    // in no plugins being called.
    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DMapToTransportType: no matching plugins for interface index %p", InterfaceID);
    return D2DWifiPeerToPeerTransport;
}

mDNSexport void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, mDNSu16 qtype, DNSServiceFlags flags, pid_t clientPID)
{
    internal_start_browsing_for_service(InterfaceID, typeDomain, qtype, flags, clientPID);
}

mDNSexport void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, mDNSu16 qtype, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;

    DomainnameToLower(typeDomain, &lower);

    if (!D2DBrowseListRefCount(&lower, qtype))
    {
        D2DTransportType transportType, excludedTransport;

        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "internal_start_browsing_for_service: starting browse - "
            "qname: " PRI_DM_NAME ", qtype: " PUB_DNS_TYPE, DM_NAME_PARAM(&lower), DNS_TYPE_PARAM(qtype));

        mDNSu8 *const end = DNSNameCompressionBuildLHS(&lower, qtype);
        const size_t keyLen = (size_t)(end - compression_lhs);
        PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, mDNSNULL, 0);

        transportType = xD2DMapToTransportType(InterfaceID, flags, & excludedTransport);
        if (transportType == D2DTransportMax)
        {
            D2DTransportType i;
            for (i = 0; i < D2DTransportMax; i++)
            {
                if (i == excludedTransport) continue;
                if (D2DStartBrowsingForKeyOnTransport)
                {
                    if (D2DIsTransportLikeAWDL(i))
                    {
                        mdns_powerlog_awdl_browse_start(typeDomain->c, qtype, clientPID);
                    }
                    D2DStartBrowsingForKeyOnTransport(compression_lhs, keyLen, i);
                }
            }
        }
        else
        {
            if (D2DStartBrowsingForKeyOnTransport)
            {
                if (D2DIsTransportLikeAWDL(transportType))
                {
                    mdns_powerlog_awdl_browse_start(typeDomain->c, qtype, clientPID);
                }
                D2DStartBrowsingForKeyOnTransport(compression_lhs, keyLen, transportType);
            }
        }
    }
    D2DBrowseListRetain(&lower, qtype);
}

mDNSexport void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, mDNSu16 qtype, DNSServiceFlags flags, pid_t clientPID)
{
    internal_stop_browsing_for_service(InterfaceID, typeDomain, qtype, flags, clientPID);
}

mDNSexport void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, mDNSu16 qtype, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;

    DomainnameToLower(typeDomain, &lower);

    // If found in list and this is the last reference to this browse, remove the key from the D2D plugins.
    if (D2DBrowseListRelease(&lower, qtype) && !D2DBrowseListRefCount(&lower, qtype))
    {
        D2DTransportType transportType, excludedTransport;

        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "internal_stop_browsing_for_service: stopping browse - "
            "qname: " PRI_DM_NAME ", qtype: " PUB_DNS_TYPE, DM_NAME_PARAM(&lower), DNS_TYPE_PARAM(qtype));

        mDNSu8 *const end = DNSNameCompressionBuildLHS(&lower, qtype);
        const size_t keyLen = (size_t)(end - compression_lhs);
        PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, mDNSNULL, 0);

        transportType = xD2DMapToTransportType(InterfaceID, flags, & excludedTransport);
        if (transportType == D2DTransportMax)
        {
            D2DTransportType i;
            for (i = 0; i < D2DTransportMax; i++)
            {
                if (i == excludedTransport) continue;
                if (D2DStopBrowsingForKeyOnTransport)
                {
                    D2DStopBrowsingForKeyOnTransport(compression_lhs, keyLen, i);
                    if (D2DIsTransportLikeAWDL(i))
                    {
                        mdns_powerlog_awdl_browse_stop(typeDomain->c, qtype, clientPID);
                    }
                }
            }
        }
        else
        {
            if (D2DStopBrowsingForKeyOnTransport)
            {
                D2DStopBrowsingForKeyOnTransport(compression_lhs, keyLen, transportType);
                if (D2DIsTransportLikeAWDL(transportType))
                {
                    mdns_powerlog_awdl_browse_stop(typeDomain->c, qtype, clientPID);
                }
            }
        }

        // The D2D driver may not generate the D2DServiceLost event for this key after
        // the D2DStopBrowsingForKey*() call above.  So, we flush the key from the D2D 
        // record cache now.
        xD2DClearCache(&lower, qtype);
    }
}

mDNSexport void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
{
    internal_start_advertising_service(resourceRecord, flags, clientPID);
}

mDNSexport void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;
    D2DTransportType transportType, excludedTransport;
    DomainnameToLower(resourceRecord->name, &lower);

    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "internal_start_advertising_service - "
        "name: " PRI_DM_NAME ", rrtype: " PUB_DNS_TYPE, DM_NAME_PARAM(resourceRecord->name),
        DNS_TYPE_PARAM(resourceRecord->rrtype));

    mDNSu8 *const rhs = DNSNameCompressionBuildLHS(&lower, resourceRecord->rrtype);
    const mDNSu8 *const end = DNSNameCompressionBuildRHS(rhs, resourceRecord);
    const size_t keyLen = (size_t)(rhs - compression_lhs);
    const size_t valueLen = (size_t)(end - rhs);
    PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, rhs, (mDNSu16)valueLen);

    transportType = xD2DMapToTransportType(resourceRecord->InterfaceID, flags, & excludedTransport);
    if (transportType == D2DTransportMax)
    {
        D2DTransportType i;
        for (i = 0; i < D2DTransportMax; i++)
        {
            if (i == excludedTransport) continue;
            if (D2DStartAdvertisingPairOnTransport)
            {
                if (D2DIsTransportLikeAWDL(i))
                {
                    mdns_powerlog_awdl_advertise_start(lower.c, resourceRecord->rrtype, clientPID);
                }
                D2DStartAdvertisingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, i);
            }
        }
    }
    else
    {
        if (D2DStartAdvertisingPairOnTransport)
        {
            if (D2DIsTransportLikeAWDL(transportType))
            {
                mdns_powerlog_awdl_advertise_start(lower.c, resourceRecord->rrtype, clientPID);
            }
            D2DStartAdvertisingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, transportType);
        }
    }
}

mDNSexport void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
{
    internal_stop_advertising_service(resourceRecord, flags, clientPID);
}

mDNSexport void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;
    D2DTransportType transportType, excludedTransport;
    DomainnameToLower(resourceRecord->name, &lower);

    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "internal_stop_advertising_service: " PRI_S,
        RRDisplayString(&mDNSStorage, resourceRecord));

    mDNSu8 *const rhs = DNSNameCompressionBuildLHS(&lower, resourceRecord->rrtype);
    const mDNSu8 *const end = DNSNameCompressionBuildRHS(rhs, resourceRecord);
    const size_t keyLen = (size_t)(rhs - compression_lhs);
    const size_t valueLen = (size_t)(end - rhs);
    PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, rhs, (mDNSu16)valueLen);

    transportType = xD2DMapToTransportType(resourceRecord->InterfaceID, flags, & excludedTransport);
    if (transportType == D2DTransportMax)
    {
        D2DTransportType i;
        for (i = 0; i < D2DTransportMax; i++)
        {
            if (i == excludedTransport) continue;
            if (D2DStopAdvertisingPairOnTransport)
            {
                D2DStopAdvertisingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, i);
                if (D2DIsTransportLikeAWDL(i))
                {
                    mdns_powerlog_awdl_advertise_stop(lower.c, resourceRecord->rrtype, clientPID);
                }
            }
        }
    }
    else
    {
        if (D2DStopAdvertisingPairOnTransport)
        {
            D2DStopAdvertisingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, transportType);
            if (D2DIsTransportLikeAWDL(transportType))
            {
                mdns_powerlog_awdl_advertise_stop(lower.c, resourceRecord->rrtype, clientPID);
            }
        }
    }
}

mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;
    D2DTransportType transportType, excludedTransport;
    DomainnameToLower(SkipLeadingLabels(fqdn, 1), &lower);

    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "external_start_resolving_service - "
        "fqdn: " PRI_DM_NAME, DM_NAME_PARAM(fqdn));

    mDNSu8 *const rhs = DNSNameCompressionBuildLHS(&lower, kDNSType_PTR);
    const mDNSu8 *const end = putDomainNameAsLabels(&compression_base_msg, rhs, compression_limit, fqdn);
    const size_t keyLen = (size_t)(rhs - compression_lhs);
    const size_t valueLen = (size_t)(end - rhs);
    PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, rhs, (mDNSu16)valueLen);

    transportType = xD2DMapToTransportType(InterfaceID, flags, & excludedTransport);
    if (transportType == D2DTransportMax)
    {
        // Resolving over all the transports, except for excludedTransport if set.
        D2DTransportType i;
        for (i = 0; i < D2DTransportMax; i++)
        {
            if (i == excludedTransport) continue;
            if (D2DStartResolvingPairOnTransport)
            {
                if (D2DIsTransportLikeAWDL(i))
                {
                    mdns_powerlog_awdl_resolve_start(lower.c, kDNSType_PTR, clientPID);
                }
                D2DStartResolvingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, i);
            }
        }
    }
    else
    {
        // Resolving over one specific transport.
        if (D2DStartResolvingPairOnTransport)
        {
            if (D2DIsTransportLikeAWDL(transportType))
            {
                mdns_powerlog_awdl_resolve_start(lower.c, kDNSType_PTR, clientPID);
            }
            D2DStartResolvingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, transportType);
        }
    }

}

mDNSexport void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID)
{
    domainname lower;
    D2DTransportType transportType, excludedTransport;
    DomainnameToLower(SkipLeadingLabels(fqdn, 1), &lower);

    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "external_stop_resolving_service - "
        "fqdn: " PRI_DM_NAME, DM_NAME_PARAM(fqdn));

    mDNSu8 *const rhs = DNSNameCompressionBuildLHS(&lower, kDNSType_PTR);
    const mDNSu8 *const end = putDomainNameAsLabels(&compression_base_msg, rhs, compression_limit, fqdn);
    const size_t keyLen = (size_t)(rhs - compression_lhs);
    const size_t valueLen = (size_t)(end - rhs);
    PrintHelper(__func__, compression_lhs, (mDNSu16)keyLen, rhs, (mDNSu16)valueLen);

    transportType = xD2DMapToTransportType(InterfaceID, flags, & excludedTransport);
    if (transportType == D2DTransportMax)
    {
        D2DTransportType i;
        for (i = 0; i < D2DTransportMax; i++)
        {
            if (i == excludedTransport) continue;
            if (D2DStopResolvingPairOnTransport)
            {
                D2DStopResolvingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, i);
                if (D2DIsTransportLikeAWDL(i))
                {
                    mdns_powerlog_awdl_resolve_stop(lower.c, kDNSType_PTR, clientPID);
                }
            }
        }
    }
    else
    {
        if (D2DStopResolvingPairOnTransport)
        {
            D2DStopResolvingPairOnTransport(compression_lhs, keyLen, rhs, valueLen, transportType);
            if (D2DIsTransportLikeAWDL(transportType))
            {
                mdns_powerlog_awdl_resolve_stop(lower.c, kDNSType_PTR, clientPID);
            }
        }
    }
}

mDNSexport mDNSBool callExternalHelpers(mDNSInterfaceID InterfaceID, const domainname *const domain, DNSServiceFlags flags)
{
    // Only call D2D layer routines if request applies to a D2D interface and the domain is "local".
    if (    (((InterfaceID == mDNSInterface_Any) && (flags & (kDNSServiceFlagsIncludeP2P | kDNSServiceFlagsIncludeAWDL | kDNSServiceFlagsAutoTrigger)))
            || mDNSPlatformInterfaceIsD2D(InterfaceID) || (InterfaceID == mDNSInterface_BLE))
        && IsLocalDomain(domain))
    {
        return mDNStrue;
    }
    else
        return mDNSfalse;
}

// Used to derive the original D2D specific flags specified by the client in the registration
// when we don't have access to the original flag (kDNSServiceFlags*) values.
mDNSexport mDNSu32 deriveD2DFlagsFromAuthRecType(AuthRecType authRecType)
{
    mDNSu32 flags = 0;
    if ((authRecType == AuthRecordAnyIncludeP2P) || (authRecType == AuthRecordAnyIncludeAWDLandP2P))
        flags |= kDNSServiceFlagsIncludeP2P;
    else if ((authRecType == AuthRecordAnyIncludeAWDL) || (authRecType == AuthRecordAnyIncludeAWDLandP2P))
        flags |= kDNSServiceFlagsIncludeAWDL;
    return flags;
}

void initializeD2DPlugins(mDNS *const m)
{
        // We only initialize if mDNSCore successfully initialized.
        if (D2DInitialize)
        {
            D2DStatus ds = D2DInitialize(CFRunLoopGetMain(), xD2DServiceCallback, m);
            if (ds != kD2DSuccess)
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "D2DInitialiize failed: %d", ds);
            }
            else
            {
                LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2DInitialize succeeded");
            }
        }
}

void terminateD2DPlugins(void)
{
    if (D2DTerminate)
    {
        D2DStatus ds = D2DTerminate();
        if (ds != kD2DSuccess)
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_ERROR, "D2DTerminate failed: %d", ds);
        }
        else
        {
            LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2DTerminate succeeded");
        }

    }
}

#ifdef UNIT_TEST
#pragma mark - Unit test support routines

// These unit test support routines are called from unittests/ framework
// and are not compiled for the mDNSResponder runtime code paths.

void D2D_unitTest(void)
{
}

#endif  //  UNIT_TEST
