/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2015-2016 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
 *
 *     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.
 */

#if ENABLE_BLE_TRIGGERED_BONJOUR

#include "mDNSEmbeddedAPI.h"
#include "DNSCommon.h"
#include "mDNSMacOSX.h"
#include "BLE.h"
#include "D2D.h"

#include <dlfcn.h>

#pragma mark - Browse and Registration Request Handling

// When set, enables BLE triggered discovery APIs.
mDNSBool EnableBLEBasedDiscovery = mDNSfalse;

// When set, the default mode is to promote all client requests made with 
// kDNSServiceInterfaceIndexAny to BLE Triggered Discovery.
// Requests to promote will be filtered by either a service type whitelist or
// blacklist as noted below.
mDNSBool DefaultToBLETriggered = mDNSfalse;

#define USE_WHITELIST 1

#if USE_WHITELIST

// Current list of service types that will have BLE triggers applied by default
// when DefaultToBLETriggered is set to true.

const char * defaultServiceWhitelist[] = {
    "\x04_ssh",
    "\x04_smb",
    "\x04_rfb",
    "\x04_ipp",
    "\x05_ipps",
    "\x08_printer",
    0
};

// Return true if DefaultToBLETriggered is set and the operation should be
// promoted to use BLE triggered discovery by default.
bool shouldUseBLE(mDNSInterfaceID interfaceID, DNS_TypeValues rrtype, domainname *serviceType, domainname *domain)
{
    const mDNSu8 ** ptr;

    if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain))
        return mDNSfalse;

    // Address records don't have a service type to match on, but we'll trigger them
    // here to support the case were the DNSServiceQueryRecord() was done using mDNSInterface_Any instead
    // of the interface that the corresponding SRV record was returned over.
    if ((rrtype == kDNSType_A) || (rrtype == kDNSType_AAAA))
            return mDNStrue;

    ptr = (const mDNSu8 **) defaultServiceWhitelist;
    while (*ptr)
    {
        if (SameDomainLabel(*ptr, serviceType->c))
            return mDNStrue;
        ptr++;
    }

    return mDNSfalse;
}

#else // USE_WHITELIST

// Current list of service types that will NOT have BLE triggers applied by default
// when DefaultToBLETriggered is set to true.

// _airplay and _airdrop discovery already employ BLE based triggering using Apple service specific
// BLE beacons.  The rest of the entries here are default browses run in a standard OSX install
// that we don't want to have cluttering up the Bloom filter when using the service blacklist approach.

const char * defaultServiceBlacklist[] = {
    "\x08_airplay",
    "\x08_airdrop",
    "\x05_raop",
    "\x08_airport",
    "\x0d_apple-mobdev",
    "\x06_uscan",
    "\x07_uscans",
    "\x08_scanner",
    "\x0e_apple-mobdev2",
    "\x04_ipp",
    "\x05_ipps",
    "\x07_ippusb",
    "\x08_printer",
    "\x0f_pdl-datastream",
    "\x04_ptp",
    0
};

// Return true if DefaultToBLETriggered is set and the operation should be
// promoted to use BLE triggered discovery by default.
bool shouldUseBLE(mDNSInterfaceID interfaceID,  DNS_TypeValues rrtype, domainname *serviceType, domainname *domain)
{
    (void) rrtype;
    const mDNSu8 ** ptr;

    if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain))
        return mDNSfalse;

    ptr = (const mDNSu8 **) defaultServiceBlacklist;
    while (*ptr)
    {
        if (SameDomainLabel(*ptr, serviceType->c))
            return mDNSfalse;
        ptr++;
    }

    return mDNStrue;
}

#endif // USE_WHITELIST

// Structure for linked list of BLE responses received that match
// a given client request.
typedef struct matchingResponses 
{
    struct matchingResponses * next;
    void * response;
} matchingResponses_t;

// Max size of input key generated by DNSNameCompressionBuildLHS() is MAX_DOMAIN_NAME + 3
// where the three additional bytes are:
// two bytes for DNS_TypeValues and one byte for "compression_packet_v1", the D2D compression version number.
#define MAX_KEY_SIZE    MAX_DOMAIN_NAME + 3

// Initially used for both the browse and registration lists.
typedef struct requestList
{
    struct requestList  * next;
    unsigned int        refCount;
    domainname          name;
    mDNSu16             type;
    DNSServiceFlags     flags;
    mDNSInterfaceID     InterfaceID;
    serviceHash_t       browseHash;
    serviceHash_t       registeredHash;
    matchingResponses_t * ourResponses;
    bool                triggeredOnAWDL;

    // The following fields are only used for browse requests currently
    mDNSu8              key[MAX_KEY_SIZE];
    size_t              keySize;

    // The following fields are only used for registration requests currently
    const ResourceRecord    * resourceRecord;
} requestList_t;

// Lists for all DNSServiceBrowse() and DNSServiceRegister() requests using 
// BLE beacon based triggering.
static requestList_t* BLEBrowseListHead = NULL;
static requestList_t* BLERegistrationListHead = NULL;

// The kDNSServiceFlagsAutoTrigger should only be set for a request that would normally apply to AWDL.
#define isAutoTriggerRequest(INTERFACE_INDEX, FLAGS) (    (FLAGS & kDNSServiceFlagsAutoTrigger) \
                                                       && (   (AWDLInterfaceID && (INTERFACE_INDEX == AWDLInterfaceID)) \
                                                           || ((INTERFACE_INDEX == kDNSServiceInterfaceIndexAny) && (FLAGS & kDNSServiceFlagsIncludeAWDL))))

#pragma mark - Manage list of responses that match this request.

// Return true if any response matches one of our current registrations.
mDNSlocal bool responseMatchesRegistrations(void)
{
    requestList_t   *ptr;

    for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
    {
        if (ptr->ourResponses)
            return true;
    }
    return false;
}

// Return true if the response is already in the list of responses for this client request.
mDNSlocal bool inResponseListForRequest(requestList_t *request, void * response)
{
    matchingResponses_t * rp;

    for (rp = request->ourResponses; rp; rp = rp->next)
        if (rp->response == response)
            break;
    
    return (rp != 0);
}

mDNSlocal void addToResponseListForRequest(requestList_t *request, void * response)
{
    matchingResponses_t *matchingResponse = calloc(1, sizeof(matchingResponses_t));

    if (matchingResponse == NULL)
    {
        LogMsg("addToResponseListForRequest: calloc() failed!");
        return;
    }
    matchingResponse->response = response;
    matchingResponse->next = request->ourResponses;
    request->ourResponses = matchingResponse;
}

// If response is currently in the list of responses, remove it and return true.
// Othewise, return false.
mDNSlocal bool removeFromResponseListForRequest(requestList_t *request, void * response)
{
    matchingResponses_t ** nextp;
    bool responseRemoved = false;

    for (nextp = & request->ourResponses; *nextp; nextp = & (*nextp)->next)
        if ((*nextp)->response == response)
            break;

    if (*nextp)
    {
        LogInfo("removeFromResponseListForRequest: response no longer matches for  %##s %s ", request->name.c, DNSTypeName(request->type));

        responseRemoved = true;
        matchingResponses_t *tmp = *nextp;
        *nextp = (*nextp)->next;
        free(tmp);
    }
    return responseRemoved;
}

// Free all current entries on the response list for this request.
mDNSlocal void freeResponseListEntriesForRequest(requestList_t *request)
{
    matchingResponses_t * ptr;

    ptr = request->ourResponses; 
    while (ptr)
    {
        matchingResponses_t * tmp;

        tmp = ptr;
        ptr = ptr->next;
        free(tmp);
    }
    request->ourResponses = 0;
}

#pragma mark - Manage request lists

// Return the address of the pointer to the entry, which can either be the address of "listHead"
// or the address of the prior entry on the lists "next" pointer.
mDNSlocal requestList_t ** findInRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type)
{
    requestList_t **ptr = listHead;

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

    return ptr;
}

mDNSlocal requestList_t * addToRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type, DNSServiceFlags flags)
{
    requestList_t **ptr = findInRequestList(listHead, name, type);

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

    LogInfo("addToRequestList: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);

    return *ptr;
}

mDNSlocal void removeFromRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type)
{
    requestList_t **ptr = findInRequestList(listHead, name, type);

    if (!*ptr) { LogMsg("removeFromRequestList: Didn't find %##s %s in list", name->c, DNSTypeName(type)); return; }

    (*ptr)->refCount -= 1;

    LogInfo("removeFromRequestList: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);

    if (!(*ptr)->refCount)
    {
        requestList_t *tmp = *ptr;
        *ptr = (*ptr)->next;
        freeResponseListEntriesForRequest(tmp);
        mDNSPlatformMemFree(tmp);
    }
}

#pragma mark - Hashing and beacon state 

// These SipHash routines were copied from CoreUtils-500.9.
// We use these when running an mDNSRespnder root on a system that does not
// have the SipHash() routine available and exported in CoreUtils.
// TODO:  This local copy should be removed once we are no longer running mDNSResponder roots
// on systems that do no include CoreUtils-500.9 or newer.

// Start of code copied from: CoreUtils-500.9

/*! @group      BitRotates
    @abstract   Rotates X COUNT bits to the left or right.
*/
#define ROTL( X, N, SIZE )          ( ( (X) << (N) ) | ( (X) >> ( (SIZE) - N ) ) )
#define ROTR( X, N, SIZE )          ( ( (X) >> (N) ) | ( (X) << ( (SIZE) - N ) ) )

#define ROTL64( X, N )              ROTL( (X), (N), 64 )
#define ROTR64( X, N )              ROTR( (X), (N), 64 )

    #define ReadLittle64( PTR ) \
        ( (uint64_t)( \
              ( (uint64_t)( (uint8_t *)(PTR) )[ 0 ] )           | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 1 ] ) <<  8 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 2 ] ) << 16 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 3 ] ) << 24 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 4 ] ) << 32 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 5 ] ) << 40 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 6 ] ) << 48 )   | \
            ( ( (uint64_t)( (uint8_t *)(PTR) )[ 7 ] ) << 56 ) ) )

// Based on <https://131002.net/siphash/>.

#define SipRound() \
	do \
	{ \
		v0 += v1; v1 = ROTL64( v1, 13 ); v1 ^= v0; v0 = ROTL64( v0, 32 ); \
		v2 += v3; v3 = ROTL64( v3, 16 ); v3 ^= v2; \
		v0 += v3; v3 = ROTL64( v3, 21 ); v3 ^= v0; \
		v2 += v1; v1 = ROTL64( v1, 17 ); v1 ^= v2; v2 = ROTL64( v2, 32 ); \
		\
	}	while( 0 )

mDNSlocal uint64_t	local_SipHash( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen )
{
	const uint8_t *				src  = (const uint8_t *) inSrc;
	size_t const				left = inLen % 8;
	const uint8_t * const		end  = src + ( inLen - left );
	uint64_t					k0, k1, v0, v1, v2, v3, tmp;
	
	k0 = ReadLittle64( &inKey[ 0 ] );
	k1 = ReadLittle64( &inKey[ 8 ] );
	v0 = k0 ^ UINT64_C( 0x736f6d6570736575 ); // 'somepseu'
	v1 = k1 ^ UINT64_C( 0x646f72616e646f6d ); // 'dorandom'
	v2 = k0 ^ UINT64_C( 0x6c7967656e657261 ); // 'lygenera'
	v3 = k1 ^ UINT64_C( 0x7465646279746573 ); // 'tedbytes'
	
	for( ; src != end; src += 8 )
	{
		tmp = ReadLittle64( src );
		v3 ^= tmp;
		SipRound();
		SipRound();
		v0 ^= tmp;
	}
	
	tmp = ( (uint64_t)( inLen & 0xFF ) ) << 56;
	switch( left )
	{
		case 7: tmp |= ( ( (uint64_t) src[ 6 ] ) << 48 );
		case 6: tmp |= ( ( (uint64_t) src[ 5 ] ) << 40 );
		case 5: tmp |= ( ( (uint64_t) src[ 4 ] ) << 32 );
		case 4: tmp |= ( ( (uint64_t) src[ 3 ] ) << 24 );
		case 3: tmp |= ( ( (uint64_t) src[ 2 ] ) << 16 );
		case 2: tmp |= ( ( (uint64_t) src[ 1 ] ) <<  8 );
		case 1: tmp |=   ( (uint64_t) src[ 0 ] );
		default: break;
	}
	v3 ^= tmp;
	SipRound();
	SipRound();
	v0 ^= tmp;
	v2 ^= 0xFF;
	SipRound();
	SipRound();
	SipRound();
	SipRound();
	return( v0 ^ v1 ^ v2 ^ v3 );
}

// See <https://spc.apple.com/AppleBLEInfo.html#_wifi_tds> for details.

#define kTDSSipHashKey          ( (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )
#define kTDSSipHashCount        5

#define kSizeCString        ( (size_t) -1 )

// End of code copied from: CoreUtils-500.9

// Must link symbol from CoreUtils at runtime to avoid cyclic dependency cycles in the build process.
static uint64_t (*SipHash_p)( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen ) = NULL;

mDNSlocal uint64_t local_TDSBloomFilterMake( uint32_t inBloomCount, const void *inStr, size_t inLen )
{
    uint64_t        bloomFilter = 0, hash;
    uint8_t         i;

    if( inLen == kSizeCString ) inLen = strlen( (const char *) inStr );
    if (SipHash_p)
        hash = SipHash_p( kTDSSipHashKey, inStr, inLen );
    else
        hash = local_SipHash( kTDSSipHashKey, inStr, inLen );

    for( i = 0; i < kTDSSipHashCount; ++i )
    {
        bloomFilter |= ( UINT64_C( 1 ) << ( hash % inBloomCount ) );
        hash /= inBloomCount;
    }
    return( bloomFilter );
}

mDNSlocal void loadCoreUtils()
{
    static mDNSBool runOnce = mDNSfalse;
    static void *CoreUtils_p = mDNSNULL;
    static const char path[] = "/System/Library/PrivateFrameworks/CoreUtils.framework/CoreUtils";

    if (!runOnce)
    {
        runOnce = mDNStrue;
        if (!CoreUtils_p)
        {
            CoreUtils_p = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
            if (!CoreUtils_p)
            {
                LogInfo("loadCoreUtils: dlopen() failed.");
                return;
            }
        }

        if (!SipHash_p)
        {
            SipHash_p = dlsym(CoreUtils_p, "SipHash");
            if (!SipHash_p)
            {
                LogInfo("loadCoreUtils: load of SipHash symbol failed.");
                return;
            }
        }
        LogInfo("loadCoreUtils: found SipHash symbol.");
    }
}

#define HASH_SIZE    64

mDNSlocal serviceHash_t BLELabelHash(unsigned char *str, unsigned int length)
{
    loadCoreUtils();

    return local_TDSBloomFilterMake(HASH_SIZE, (const void *) str, (size_t) length);
}


// Maximum number of characters in string to hash should be:
//  2 for initial "s:" or "p:"
// 16 for "_" followed by up to 15 characters of service type
//  1 for separating "."
//  4 for "_udp" or "_tcp"
//  1 for the terminating NULL byte
#define MAX_HASH_STRING   (2 + 16 + 1 + 4 + 1)

// Maximum service name length, including the initial "_"
#define MAX_SERVICE_NAME 16

// Convert the service name and transport protocol to a NULL terminated C string.
// stringBuf must point to least (MAX_HASH_STRING - 2) bytes of available space.
mDNSlocal bool serviceNameStringFromDomain(const domainname *const domain, mDNSu8 * stringBuf)
{
    mDNSu8       * dst = stringBuf;
    const mDNSu8 * src = domain->c;
    mDNSu8         len = *src++;

    if (len == 0 || len > MAX_SERVICE_NAME)
    {
        LogInfo("serviceNameStringFromDomain: Invalid name lenght: %d", len);
        return false;
    }
    if (*src != '_')
    {
        LogInfo("serviceNameStringFromDomain: service name does not begin with a _");
        return false;
    }
    // Copy the service type
    while (len--)
        *dst++ = *src++;

    *dst++ = '.';

    if (!ValidTransportProtocol(src))
    {
        LogInfo("serviceNameStringFromDomain: Transport protocol name must be _udp or _tcp");
        return false;
    }
    // copy the transport protocol
    len = *src++;
    while (len--)
        *dst++ = *src++;

    *dst = 0;
    return true;
}

mDNSlocal bool setBLEServiceHash(const domainname *const domain, requestList_t * ptr)
{
    // Initialize the string with the "s:" for the browser/seeker hash calculation.
    mDNSu8 stringBuf[MAX_HASH_STRING] = { 's', ':', '\0' };

    // Append the service name and protocol strings to the initial "s:" string.
    if (!serviceNameStringFromDomain(domain, &stringBuf[2]))
    {
        LogInfo("setBLEServiceHash: serviceNameStringFromDomain() failed!");
        return false;
    }

    ptr->browseHash =  BLELabelHash(stringBuf, strlen((const char *)stringBuf));
    LogInfo("setBLEServiceHash: seeker string %s, hashed to 0x%lx", stringBuf, ptr->browseHash);

    // Update string to start with "p:" for registration/provider hash calculation.
    stringBuf[0] = 'p';

    ptr->registeredHash =  BLELabelHash(stringBuf, strlen((const char *)stringBuf));
    LogInfo("setBLEServiceHash: provider string %s, hashed to 0x%lx", stringBuf, ptr->registeredHash);
    if (ptr->browseHash && ptr->registeredHash)
        return true;
    else
        return false;
}

// Indicates we are sending the final beacon with zeroed Bloom filter to let
// peers know we are no longer actively seeking or providing any services.
bool finalBeacon = false;

// The last time we walked our response list looking for stale entries.
mDNSs32 lastScanForStaleResponses;

// Forward declaration.
mDNSlocal void removeStaleResponses(mDNSs32 currentTime);

// Interval at which we scan the response lists to remove any stale entries.
#define StaleResponseScanInterval 30

// Called from mDNS_Execute() when NextBLEServiceTime is reached.
void serviceBLE(void)
{
    // Note, we can access mDNSStorage.timenow since we are called from mDNS_Execute, 
    // which initializes that value by calling mDNS_Lock().
    mDNSs32 currentTime = mDNSStorage.timenow;

    // Initialize if zero.
    if (!lastScanForStaleResponses)
        lastScanForStaleResponses = NonZeroTime(currentTime - (StaleResponseScanInterval * mDNSPlatformOneSecond));

    if (finalBeacon)
    {
        // We don't expect to do the finalBeacon processing if there are active browse requests,
        if (BLEBrowseListHead)
            LogInfo("serviceBLE: finalBeacon set and called with active browse BLE requests ??");

        // or active registrations but we are not in suppress beacons state.
        if (BLERegistrationListHead && !suppressBeacons)
            LogInfo("serviceBLE: finalBeacon set and called with active registrations requests, but not in suppress beacons state ??");

        finalBeacon = false;
        stopBLEBeacon();
    }

    if (!BLEBrowseListHead && !BLERegistrationListHead)
    {
        LogInfo("serviceBLE: no active client requests, disabling service timer");
        mDNSStorage.NextBLEServiceTime = 0;
    }
    else if ((currentTime - lastScanForStaleResponses) >= (StaleResponseScanInterval * mDNSPlatformOneSecond))
    {
        removeStaleResponses(currentTime);
        lastScanForStaleResponses = currentTime;
        mDNSStorage.NextBLEServiceTime = NonZeroTime(currentTime + (StaleResponseScanInterval * mDNSPlatformOneSecond));
    }
}

// Initialize the periodic service timer if we have active requests.
// The timer is disabled in the next call to serviceBLE() when no requests are active.
mDNSlocal void updateServiceTimer()
{
    if (!mDNSStorage.NextBLEServiceTime && (BLEBrowseListHead || BLERegistrationListHead))
        mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (StaleResponseScanInterval * mDNSPlatformOneSecond));
}

// Set true when suppressing beacon transmissions for our registrations until we see
// a peer beacon indicating a browse for one of our services.
bool suppressBeacons = false;

// Go through all the existing browses and registrations to create the
// current Bloom filter value for the BLE beacon.
// Update the current scan and beaconing state appropriately.
mDNSlocal void updateBeaconAndScanState()
{
    requestList_t   *ptr;
    serviceHash_t   beaconBloomFilter = 0;

    updateServiceTimer();

    for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next)
    {
        beaconBloomFilter |= ptr->browseHash;
    }

    for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
    {
        beaconBloomFilter |= ptr->registeredHash;
    }

    // If only advertising registered services and not browsing, we don't start the beacon transmission 
    // until we receive a beacon from a peer matching one of our registrations.
    if (BLERegistrationListHead && !BLEBrowseListHead && !responseMatchesRegistrations())
    {
        // If beacons are already suppressed, then no further action to take.
        if (suppressBeacons)
            LogInfo("updateBeaconAndScanState: continuing to suppressing beacons");
        else
        {
            LogInfo("updateBeaconAndScanState: suppressing beacons, no peers currently seeking our services");
            suppressBeacons = true;

            // If currently beaconing, send a beacon for two seconds with a zeroed Bloom filter indicating we are 
            // no longer browsing  for any services so that any matching auto triggered peer registrations have a 
            // chance to see our state change.
            if (currentlyBeaconing())
                updateBLEBeacon(0);
            startBLEScan();
        }
    }
    // If beacons had been suppressed and we no longer have services to advertise, no
    // need to send a beacon with a zeroed Bloom filter for two seconds, just stop
    // the scan.
    else if (suppressBeacons == true && beaconBloomFilter == 0)
    {
        suppressBeacons = false;
        stopBLEScan();
    }
    // Update the beacon with the current Bloom filter values.
    else
    {
        suppressBeacons = false;
        updateBLEBeacon(beaconBloomFilter);
        // Scan unless the Bloom filter is zero, indicating we are not currently
        // seeking or providing any services.
        if (beaconBloomFilter)
            startBLEScan();
        else
            stopBLEScan();
    }
}

#pragma mark - Peer response handling

// Structure used to track the beacons received from various peers.
typedef struct responseList
{
    struct responseList * next;
    serviceHash_t       peerBloomFilter;
    mDNSs32             recievedTime;
    mDNSEthAddr         peerMac;
} responseList_t;

#define RESPONSE_LIST_NUMBER 8
static responseList_t* BLEResponseListHeads[RESPONSE_LIST_NUMBER];

// Return the address of the pointer to the entry, which can either be the address of the
// corresponding BLEResponseListHeads[] entry, or the address of the prior responseList_t entry
// on the lists "next" pointer.
mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC)
{
    // Use the least significant byte of the MAC address as our hash index to find the list.
    responseList_t **ptr = & BLEResponseListHeads[ptrToMAC->b[5] % RESPONSE_LIST_NUMBER];

    for ( ; *ptr; ptr = &(*ptr)->next)
    {
        if (memcmp(&(*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr)) == 0)
            break;
    }

    return ptr;
}


mDNSlocal responseList_t * addToResponseList(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
{
    responseList_t **ptr = findInResponseList(ptrToMAC);

    if (!*ptr)
    {
        *ptr = mDNSPlatformMemAllocate(sizeof(**ptr));
        mDNSPlatformMemZero(*ptr, sizeof(**ptr));
        (*ptr)->peerBloomFilter = peerBloomFilter;
        memcpy(& (*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr));
    }

    return *ptr;
}

mDNSlocal void removeFromResponseList(mDNSEthAddr * ptrToMAC)
{
    responseList_t **ptr = findInResponseList(ptrToMAC);

    if (!*ptr)
    {
        LogMsg("removeFromResponseList: did not find entry for MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
        return;
    }

    LogInfo("removeFromResponseList: removing entry for MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);

    responseList_t *tmp = *ptr;
    *ptr = (*ptr)->next;
    mDNSPlatformMemFree(tmp);
}

// Free all current entries on the BLE response lists, removing all pointers
// to freed structures from the lists.
mDNSlocal void clearResponseLists()
{
    responseList_t **ptr;

    for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
    {
        ptr = & BLEResponseListHeads[i];
        while (*ptr)
        {
            responseList_t * tmp;
    
            tmp = *ptr;
            *ptr = (*ptr)->next;
            mDNSPlatformMemFree(tmp);
        }
    }
}

// Check to see if we have cached a response that matches a service for which we just started a browse or registration.
mDNSlocal void checkCachedResponses(requestList_t *browse, requestList_t *registration)
{
    responseList_t *ptr;

    for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
    {
        for (ptr = BLEResponseListHeads[i]; ptr; ptr = ptr->next)
        {
            // For browses, we are looking for responses that have a matching registration
            // and for registrations we are looking for responses that have a matching browse.
            if (    (browse && (browse->registeredHash & ptr->peerBloomFilter) == browse->registeredHash)
                ||  (registration && (registration->browseHash & ptr->peerBloomFilter) == registration->browseHash))
            {
                // Clear the Bloom filter for the response.
                // The next beacon from this peer will update the filter then autoTrigger
                // any newly started client requests as appropriate.
                ptr->peerBloomFilter = 0;
            }
        }
    }
}

// Define a fixed name to use for the instance name denoting that one or more instances
// of a service are being advertised by peers in their BLE beacons.
// Name format is: length byte + bytes of name string + two byte pointer to the PTR record name.
// See compression_lhs definition in the D2D plugin code for background on 0xc027 DNS name compression pointer value.
static Byte  *BLEinstanceValue = (Byte *) "\x11ThresholdInstance\xc0\x27";
#define BLEValueSize  strlen((const char *)BLEinstanceValue)

// Find each local browse that matches the registered service hash in the BLE response.
// Called on the CFRunLoop thread while handling a callback from CoreBluetooth.
// Caller should hold  KQueueLock().
mDNSlocal void findMatchingBrowse(responseList_t *response)
{
    requestList_t *ptr;

    ptr = BLEBrowseListHead;
    for ( ; ptr; ptr = ptr->next)
    {
        // See if we potentially match a corresponding registration in the beacon.
        // thus, compare using the "registeredHash" of our browse..
        if ((ptr->registeredHash & response->peerBloomFilter) == ptr->registeredHash) 
        {

            LogInfo("findMatchingBrowse: Registration in response matched browse for: %##s", ptr->name.c);

            if (inResponseListForRequest(ptr, response))
            {
                LogInfo("findMatchingBrowse: Already on response list for browse: %##s", ptr->name.c);

                continue;
            }
            else
            {
                LogInfo("findMatchingBrowse: Adding to response list for browse: %##s", ptr->name.c);

                if (ptr->ourResponses == 0)
                {
                    if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
                    {
	                    LogInfo("findMatchingBrowse: First BLE response, triggering browse for %##s on AWDL", ptr->name.c);
	                    // register with the AWDL D2D plugin, 
	                    internal_start_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
                        ptr->triggeredOnAWDL = true;
                    }

	                // Browse on mDNSInterface_BLE is used to determine if there are one or more instances of the
	                // service type discoveryed over BLE.  If this is the first instance, add the psuedo instance defined by BLEinstanceValue.
	                if (ptr->InterfaceID == mDNSInterface_BLE)
	                {
	                    xD2DAddToCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
	                }
                }
                addToResponseListForRequest(ptr, response);
            }
        }
        else
        {
            // If a previous response from this peer had matched the browse, remove that response from the
            // list now.  If this is the last matching response, remove the corresponding key from the AWDL D2D plugin
            if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0))
            {
                if (ptr->InterfaceID == mDNSInterface_BLE)
                {
                    xD2DRemoveFromCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
                }

                if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
                {
                    LogInfo("findMatchingBrowse: Last BLE response, disabling browse for %##s on AWDL", ptr->name.c);
                    internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
                    ptr->triggeredOnAWDL = false;
                }
            }
        }
    }
}

// Find each local registration that matches the service browse hash BLE response Bloom filter.
// Called on the CFRunLoop thread while handling a callback from CoreBluetooth.
// Caller should hold  KQueueLock().
mDNSlocal void findMatchingRegistration(responseList_t *response)
{
    requestList_t   *ptr;
    bool            matchingPeer;

    ptr = BLERegistrationListHead;
    for ( ; ptr; ptr = ptr->next)
    {
        // See if we potentially match a corresponding browse in the beacon,
        // thus, compare using the "browseHash" of our registration.
        if ((ptr->browseHash & response->peerBloomFilter) == ptr->browseHash)
        {
            LogInfo("findMatchingRegistration: Incoming browse matched registration for: %##s", ptr->name.c);

            if (inResponseListForRequest(ptr, response))
            {
                LogInfo("findMatchingRegistration: Already on response list for registration: %##s", ptr->name.c);

                continue;
            }
            else
            {
                LogInfo("findMatchingRegistration: Adding to response list for registration: %##s", ptr->name.c);

                // Also pass the registration to the AWDL D2D plugin if this is the first matching peer browse for
                // an auto triggered local registration.
                if ((ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
                {
                    LogInfo("findMatchingRegistration: First BLE response, triggering registration for %##s on AWDL", ptr->name.c);
                    if (ptr->resourceRecord == 0)
                    {
                        LogInfo("findMatchingRegistration: resourceRecord pointer is NULL ??");
                        continue;
                    }

                    internal_start_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
                    // indicate the registration has been applied to the AWDL interface
                    ptr->triggeredOnAWDL = true;
                }

                addToResponseListForRequest(ptr, response);
            }
        }
        else
        {
            // If a previous response from this peer had matched the browse, remove that response from the
            // list now.  If this is the last matching response for a local auto triggered registration, 
            // remove the advertised key/value pairs from the AWDL D2D plugin.
            if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
            {
                LogInfo("findMatchingRegistration: Last BLE response, disabling registration for %##s on AWDL", ptr->name.c);

                // Restore the saved ARType and call into the AWDL D2D plugin to stop the corresponding record advertisements over AWDL.
                internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
                ptr->triggeredOnAWDL = false;
            }
        }
    }

    // If beacons for our registrations had been suppressed, see if we now have a match and need to restart them.
    matchingPeer = responseMatchesRegistrations();
    if (suppressBeacons && matchingPeer)
    {
        LogInfo("findMatchingRegistration: peer searching for our service, starting beacon transmission");
        updateBeaconAndScanState();

        if (suppressBeacons == true)
            LogInfo("findMatchingRegistration: NOTE: suppressBeacons is true after updateBeaconAndScanState() call ??");
    }
    // If we have only registrations, but no matching peers, we can suppress beacons until we get a matching peer beacon.
    else if (!suppressBeacons && !matchingPeer && BLERegistrationListHead && !BLEBrowseListHead)
    {
        LogInfo("findMatchingRegistration: no peer beacons match our registrations, suppressing beacon transmission");
        suppressBeacons = true;
        stopBLEBeacon();
    }
}


// Time limit before a beacon is aged out of our received list.
#define MAX_RESPONSE_AGE 10

// If we have responses from peers that are more than MAX_RESPONSE_AGE seconds
// old, remove them since a peer with active requests should be beaconing multiple
// times per second if still within BLE range.
mDNSlocal void removeStaleResponses(mDNSs32 currentTime)
{
    responseList_t **ptr;

    for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
    {
        ptr = & BLEResponseListHeads[i];
        while (*ptr)
        {
            if ((currentTime - (*ptr)->recievedTime) > (MAX_RESPONSE_AGE * mDNSPlatformOneSecond))
            {
                responseList_t * tmp;

                // Clear the Bloom filter so that it will be removed from any matching response list
                // by the following calls.
                (*ptr)->peerBloomFilter = 0;

                LogInfo("removeStaleResponses: clearing stale response from peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                    (*ptr)->peerMac.b[0], (*ptr)->peerMac.b[1], (*ptr)->peerMac.b[2], (*ptr)->peerMac.b[3], (*ptr)->peerMac.b[4], (*ptr)->peerMac.b[5]);

                findMatchingBrowse(*ptr);
                findMatchingRegistration(*ptr);
    
                // Unlink and free the response structure
                tmp = *ptr;
                *ptr = (*ptr)->next;
                mDNSPlatformMemFree(tmp);
            }
            // Move to the next response on this linked list.
            else
                ptr = & (*ptr)->next;
        }
    }
}

// Called on CFRunLoop thread during CoreBluetooth beacon response processing.
// Thus, must call KQueueLock() prior to calling any core mDNSResponder routines to register records, etc.
void responseReceived(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
{
    responseList_t * ptr;

    KQueueLock();
    mDNS_Lock(& mDNSStorage);   // Must lock to initialize mDNSStorage.timenow
    
    ptr = *(findInResponseList(ptrToMAC));
    if (ptr == 0)
    {
        // Only add to list if peer is actively browsing or advertising.
        if (peerBloomFilter)
        {
            LogInfo("responseReceived: First beacon of this type, adding to list");
            LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter);
            LogInfo("responseReceived: peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                    ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);

            ptr = addToResponseList(peerBloomFilter, ptrToMAC);       
            // Update the received time.
            ptr->recievedTime =  mDNSStorage.timenow;
            // See if we are browsing for any of the peers advertised services.
            findMatchingBrowse(ptr);
            // See if we have a registration that matches the peer's browse.
            findMatchingRegistration(ptr);
        }
    }
    else    // Have an entry from this MAC in the list.
    {
        // Update the received time.
        ptr->recievedTime =  mDNSStorage.timenow;

        if (ptr->peerBloomFilter == peerBloomFilter)
        {
            // A duplicate of a current entry.
#if VERBOSE_BLE_DEBUG
            LogInfo("responseReceived: Duplicate of previous beacon, ignoring");
            LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                    ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
#endif // VERBOSE_BLE_DEBUG
        }
        else
        {
            LogInfo("responseReceived: Update of previous beacon");
            LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter);
            LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
                    ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);

            ptr->peerBloomFilter = peerBloomFilter;
            findMatchingBrowse(ptr);
            findMatchingRegistration(ptr);
        }

        // If peer is no longer browsing or advertising, remove from list.
        if (peerBloomFilter == 0)
        {
            LogInfo("responseReceived: Removing peer entry from the list");

            removeFromResponseList(ptrToMAC);
        }
    }

    mDNS_Unlock(& mDNSStorage);   // Calling mDNS_Unlock is what gives m->NextScheduledEvent its new value
    KQueueUnlock("BLE responseReceived");
}

#pragma mark - Client request handling

void start_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags, mDNSu8 *key, size_t keySize)
{
    requestList_t * ptr; 
    const domainname *serviceType = domain;

    if (!EnableBLEBasedDiscovery)
    {
        LogMsg("start_BLE_browse: EnableBLEBasedDiscovery disabled");
        return;
    }

    if (keySize > MAX_KEY_SIZE)
    {
        LogMsg("start_BLE_browse: keySize = %d, maximum allowable is %d", keySize, MAX_KEY_SIZE);
        return;
    }

    // Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for
    // BLE threshold browsing.
    if (!isAutoTriggerRequest(InterfaceID, flags) && (InterfaceID != mDNSInterface_BLE))
    {
        LogMsg("start_BLE_browse: invalid request: InterfaceID = %d, flags = 0x%x", InterfaceID, flags);
        return;
    }

    // Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin.
    if ((type == kDNSType_A) || (type == kDNSType_AAAA))
    {
        LogInfo("start_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type));
        internal_start_browsing_for_service(InterfaceID, domain, type, flags);
        return;
    }

    // Skip the instance to get to the service type for non PTR records
    if (type != kDNSType_PTR)
        serviceType = SkipLeadingLabels(domain, 1);

    LogInfo("start_BLE_browse: Starting BLE service type browse for: %##s %s", domain->c, DNSTypeName(type));

    ptr = addToRequestList(&BLEBrowseListHead, domain, type, flags);

    // If equivalent BLE browse is already running, just return.
    if (ptr->refCount > 1)
    {
        LogInfo("start_BLE_browse: Dup of existing BLE browse.");
        return;
    }

    if (!setBLEServiceHash(serviceType, ptr))
    {
        LogInfo("setBLEServiceHash failed!");
        removeFromRequestList(&BLEBrowseListHead, domain, type);
        return;
    }

    // Save these for use in D2D plugin callback logic.
    memcpy(ptr->key, key, keySize);
    ptr->keySize = keySize;
    ptr->InterfaceID = InterfaceID;

    mDNS_Lock(& mDNSStorage);   // Must lock to initialize mDNSStorage.timenow.
    updateBeaconAndScanState();
    mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
    checkCachedResponses(ptr, NULL);
}

// Stop the browse.
// Return true if this is the last reference to the browse, false otherwise.
bool stop_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
    (void)  flags;   // not used initially
    requestList_t * ptr;
    bool    lastReference = false;

    if (!EnableBLEBasedDiscovery)
    {
        LogMsg("stop_BLE_browse: EnableBLEBasedDiscovery disabled");
        return lastReference;
    }

    // Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin.
    if ((type == kDNSType_A) || (type == kDNSType_AAAA))
    {
        LogInfo("stop_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type));
        internal_stop_browsing_for_service(InterfaceID, domain, type, flags);
        return lastReference;
    }

    LogInfo("stop_BLE_browse: Stopping BLE service type browse for: %##s %s", domain->c, DNSTypeName(type));

    ptr = *(findInRequestList(&BLEBrowseListHead, domain, type));
    if (ptr == 0)
    {
        LogInfo("stop_BLE_browse: No matching browse found.");
        return lastReference;
    }
    
    // If this is the last reference for this browse, and it was autoTriggered on AWDL,
    // remove the request from the AWDL pluggin.
    if (ptr->refCount == 1)
    {
        lastReference = true;

        if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL)
        {
            internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
            ptr->triggeredOnAWDL = false;
        }
    }

    removeFromRequestList(&BLEBrowseListHead, domain, type);

    mDNS_Lock(& mDNSStorage);   // Must lock to initialize mDNSStorage.timenow.
    if (lastReference)
        updateBeaconAndScanState();
    mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.

    // If there are no active browse or registration requests, BLE scanning will be disabled.
    // Clear the list of responses received to remove any stale response state.
    if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0)
        clearResponseLists();

    return lastReference;
}

void start_BLE_advertise(const ResourceRecord *const resourceRecord, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
    requestList_t * ptr; 
    const domainname * serviceType = domain;

    if (!EnableBLEBasedDiscovery)
    {
        LogMsg("start_BLE_advertise: EnableBLEBasedDiscovery disabled");
        return;
    }

    if (resourceRecord == NULL)
    {
        LogInfo("start_BLE_advertise: NULL resourceRecord for: %##s %s, returning", domain->c, DNSTypeName(type));
        return;
    }

    // Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for
    // BLE threshold browsing.
    if (!isAutoTriggerRequest(resourceRecord->InterfaceID, flags) && (resourceRecord->InterfaceID != mDNSInterface_BLE))
    {
        LogMsg("start_BLE_advertise: invalid request: InterfaceID = %d, flags = 0x%x", resourceRecord->InterfaceID, flags);
        return;
    }

    LogInfo("start_BLE_advertise: Starting BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type));

    // Skip the instance to get to the service type for non PTR records
    if (type != kDNSType_PTR)
        serviceType = SkipLeadingLabels(domain, 1);

    ptr = addToRequestList(&BLERegistrationListHead, domain, type, flags);

    // If equivalent BLE registration is already running, just return.
    if (ptr->refCount > 1)
    {
        LogInfo("start_BLE_advertise: Dup of existing BLE advertisement.");
        return;
    }

    if (!setBLEServiceHash(serviceType, ptr))
    {
        LogInfo("setBLEServiceHash failed!");
        removeFromRequestList(&BLERegistrationListHead, domain, type);
        return;
    }
    ptr->resourceRecord = resourceRecord;
    ptr->InterfaceID = resourceRecord->InterfaceID;

    mDNS_Lock(& mDNSStorage);   // Must lock to initialize mDNSStorage.timenow.
    updateBeaconAndScanState();
    mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
    checkCachedResponses(NULL, ptr);
}

void stop_BLE_advertise(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
    (void)  flags;   // not used initially
    requestList_t       * ptr;
    bool                lastReference = false;

    LogInfo("stop_BLE_advertise: Stopping BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type));

    // Get the request pointer from the indirect pointer returned.
    ptr =  *(findInRequestList(&BLERegistrationListHead, domain, type));

    if (ptr == 0)
    {
        LogInfo("stop_BLE_advertise: No matching advertisement found.");
        return;
    }
    
    // If this is the last reference for this registration, and it was autoTriggered on AWDL,
    // remove the request from the AWDL pluggin.
    if (ptr->refCount == 1)
    {
        lastReference = true;

        if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL)
        {
            // And remove the corresponding advertisements from the AWDL D2D plugin if we had previously
            // passed this advertisement request to the plugin.
            internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
            ptr->triggeredOnAWDL = false;
        }
    }
    removeFromRequestList(&BLERegistrationListHead, domain, type);

    mDNS_Lock(& mDNSStorage);   // Must lock to initialize mDNSStorage.timenow.
    // If this is the last reference for this registration, update advertising and browsing bits set in the beacon.
    if (lastReference)
        updateBeaconAndScanState();
    mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.

    // If there are no active browse or registration requests, BLE scanning will be disabled.
    // Clear the list of responses received to remove any stale response state.
    if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0)
        clearResponseLists();
}

#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.

#define MAX_ENTRIES 42
#define FAILED      exit(1)

mDNSlocal void BLE_requestListTests(void)
{
    const domainname *domainArray[] = { (const domainname*)"\x6" "_test0" "\x4" "_tcp" "\x5" "local",
                                        (const domainname*)"\x6" "_test1" "\x4" "_tcp" "\x5" "local",
                                        (const domainname*)"\x6" "_test2" "\x4" "_tcp" "\x5" "local",
                                        (const domainname*)"\x6" "_test3" "\x4" "_tcp" "\x5" "local",
                                        (const domainname*)"\x6" "_test4" "\x4" "_tcp" "\x5" "local",
                                      };

    mDNSu16         type = kDNSServiceType_PTR;
    DNSServiceFlags flags = 0;
    requestList_t   * ptr;
    void            * response = 0;
    int             i;
    int             numOfdomains = sizeof(domainArray)/sizeof(domainArray[0]);

    printf("BLE_requestListTests() entry:\n");

    // Basic request list unit tests.
    for (i = 0; i < numOfdomains; i++)
    {
        ptr = addToRequestList(&BLEBrowseListHead, domainArray[i], type, flags);

        if (ptr == NULL)
        {
            printf("addToRequestList() FAILED:\n");
            FAILED;
        }
    }
    for (i = 0; i < numOfdomains; i++)
    {
        // should now find the entry
        if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], type)) == 0)
        {
            printf("findInRequestList() did not find valid entry FAILED:\n");
            FAILED;
        }
        // but not find an entry with the same domain, but different type
        if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], kDNSServiceType_NULL)) != 0)
        {
            printf("findInRequestList() invalid entry matched FAILED:\n");
            FAILED;
        }
    }
    // remove all the entries
    for (i = 0; i < numOfdomains; i++)
    {
        removeFromRequestList(&BLEBrowseListHead, domainArray[i], type);
    }
    // and sanity check the list is now empty
    if (BLEBrowseListHead)
    {
            printf("BLEBrowseListHead not empty after all entries removed.\n");
            FAILED;
    }

    // Identical request reference count management tests.
    // Add identical requests to the list and verify the corresponding refCount is managed correctly
    for (i = 0; i < MAX_ENTRIES; i++)
    {
        ptr = addToRequestList(&BLEBrowseListHead, domainArray[0], type, flags);

        if (ptr == NULL)
        {
            printf("addToRequestList() of duplicate request FAILED:\n");
            FAILED;
        }
    }

    if (ptr->refCount != MAX_ENTRIES)
    {
        printf("refCount = %d, should be %d\n", ptr->refCount, MAX_ENTRIES);
        FAILED;
    }

    // Remove all but one entry
    for (i = 0; i < (MAX_ENTRIES - 1); i++)
    {
        removeFromRequestList(&BLEBrowseListHead, domainArray[0], type);
    }
    if (ptr->refCount != 1)
    {
        printf("refCount = %d, should be %d\n", ptr->refCount, 1);
        FAILED;
    }

    // Basic response list unit tests.
    // Note that responses per request are not checked for duplicates at this level, so
    // we can unit test with the same (NULL) response pointer to add multiple responses.

    // add MAX_ENTRIES responses
    for (i = 0; i < MAX_ENTRIES; i++)
        addToResponseListForRequest(ptr, response);

    // remove the responses, counting that MAX_ENTRIES were removed
    i = 0;
    while (inResponseListForRequest(ptr, response) && removeFromResponseListForRequest(ptr, response))
    {
        i++;
    }
    if (i != MAX_ENTRIES)
    {
        printf("removed %d responses, should have been %d\n", i, MAX_ENTRIES);
        FAILED;
    }

    // response list should be empty at this point
    if (ptr->ourResponses)
    {
        printf("response list should be empty\n");
        FAILED;
    }

    // add MAX_ENTRIES responses
    for (i = 0; i < MAX_ENTRIES; i++)
        addToResponseListForRequest(ptr, response);

    // free them all
    freeResponseListEntriesForRequest(ptr);

    if (ptr->ourResponses)
    {
        printf("freeResponseListEntriesForRequest() should have removed all responses\n");
        FAILED;
    }
}

mDNSlocal mDNSEthAddr etherAddress[] = {
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e } },
    { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f } },
};

mDNSlocal void BLE_responseListTests(void)
{
    int             numOfEtherAddresses = sizeof(etherAddress)/sizeof(etherAddress[0]);
    int             i;

    printf("BLE_responseListTests() entry:\n");

    // Just use the index as to generate the peerBloomFilter value to vary it per entry.
    for (i = 0; i < numOfEtherAddresses; i++)
        (void)addToResponseList(1 << i, &etherAddress[i]);

    // Verify all entries are found.
    for (i = 0; i < numOfEtherAddresses; i++)
    {
        if (*(findInResponseList(&etherAddress[i])) == 0)
        {
            printf("findInResponseList() did not find entry in list\n");
            FAILED;
        }
    }

    // Remove all entries.
    for (i = 0; i < numOfEtherAddresses; i++)
        removeFromResponseList(&etherAddress[i]);
        
    // Sanity check that all response lists are empty
    for (i = 0; i < RESPONSE_LIST_NUMBER; i++)
    {
        if (BLEResponseListHeads[i])
        {
            printf("BLEResponseListHeads[%d] not empty after removeFromResponseList() calls \n", i);
            FAILED;
        }
    }

    // Add them back again.
    for (i = 0; i < numOfEtherAddresses; i++)
        (void)addToResponseList(1 << i, &etherAddress[i]);

    // And verify that clearResponseLists() clears all entries.
    clearResponseLists();
    for (i = 0; i < RESPONSE_LIST_NUMBER; i++)
    {
        if (BLEResponseListHeads[i])
        {
            printf("BLEResponseListHeads[%d] not empty after clearResponseLists() call\n", i);
            FAILED;
        }
    }
}

void BLE_unitTest(void)
{
    BLE_requestListTests();
    BLE_responseListTests();
    printf("All BLE.c unit tests PASSED.\n");
}

#endif  //  UNIT_TEST

#endif  // ENABLE_BLE_TRIGGERED_BONJOUR
