Imported Upstream version 878.70.2
[platform/upstream/mdnsresponder.git] / mDNSMacOSX / BLE.c
index 85fb810..4a35ff0 100644 (file)
  * limitations under the License.
  */
 
+#if ENABLE_BLE_TRIGGERED_BONJOUR
+
 #include "mDNSEmbeddedAPI.h"
 #include "DNSCommon.h"
 #include "mDNSMacOSX.h"
 #include "BLE.h"
-#include <pthread.h>
+#include "D2D.h"
+
+#include <dlfcn.h>
 
 #pragma mark - Browse and Registration Request Handling
 
-// Disable use of BLE discovery APIs by default.
+// 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
 {
@@ -41,22 +152,17 @@ typedef struct requestList
     mDNSu16             type;
     DNSServiceFlags     flags;
     mDNSInterfaceID     InterfaceID;
-
-// TODO: Possibly restructure the following browse and registration specific 
-// members as a union to save a bit of space.
+    serviceHash_t       browseHash;
+    serviceHash_t       registeredHash;
+    matchingResponses_t * ourResponses;
+    bool                triggeredOnAWDL;
 
     // The following fields are only used for browse requests currently
-    serviceHash_t       browseHash;
-    DNSQuestion         * question;
-    mDNSu8              key[MAX_DOMAIN_LABEL];
+    mDNSu8              key[MAX_KEY_SIZE];
     size_t              keySize;
-    matchingResponses_t * ourResponses;
 
     // The following fields are only used for registration requests currently
-    serviceHash_t       registeredHash;
-    ServiceRecordSet    * serviceRecordSet; // service record set in the original request
-    AuthRecType         savedARType;
-    bool                triggeredOnAWDL;
+    const ResourceRecord    * resourceRecord;
 } requestList_t;
 
 // Lists for all DNSServiceBrowse() and DNSServiceRegister() requests using 
@@ -64,10 +170,27 @@ typedef struct requestList
 static requestList_t* BLEBrowseListHead = NULL;
 static requestList_t* BLERegistrationListHead = NULL;
 
-#define isAutoTriggerRequest(ptr) ((ptr->InterfaceID == kDNSServiceInterfaceIndexAny) && (ptr->flags & kDNSServiceFlagsAutoTrigger))
+// 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;
@@ -130,10 +253,13 @@ mDNSlocal void freeResponseListEntriesForRequest(requestList_t *request)
         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;
@@ -185,332 +311,383 @@ mDNSlocal void removeFromRequestList(requestList_t ** listHead, const domainname
 
 #pragma mark - Hashing and beacon state 
 
-// Simple string hash based on the Bernstein hash.
-
-#define PRIME   31  // small prime number
-#define MODULO  (sizeof(serviceHash_t) * 8)
-#define CONVERT_TO_LOWER_CASE(x) (((x) <= 'Z' && (x) >= 'A') ? ((x) | 0x20) : (x))
-
-mDNSlocal serviceHash_t BLELabelHash(const unsigned char *str, unsigned int length)
+// 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 )
 {
-    serviceHash_t hash = 0;
-
-    for (unsigned int i = 0; i < length; i++) {
-        hash = PRIME * hash + CONVERT_TO_LOWER_CASE(*str);
-        str++;
-    }
-
-    hash %= MODULO;
-    LogInfo("BLELabelHash: %d characters hashed to %d", length, hash);
-
-    return ((serviceHash_t)1 << hash);
+       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 );
 }
 
-// Hash just the service type not including the protocol or first "_" character initially.
-mDNSlocal serviceHash_t BLEServiceHash(const domainname *const domain)
-{
-    const unsigned char *p = domain->c;
-    unsigned int length = (unsigned int) *p;
-
-    p++;
-    if (*p != '_')
-    {
-        LogInfo("BLEServiceHash: browse type does not begin with a _");
-        return 0;
-    }
-    p++;  // skip the '-"
-    length--;
+// See <https://spc.apple.com/AppleBLEInfo.html#_wifi_tds> for details.
 
-    if (length > MAX_DOMAIN_LABEL || length == 0)
-    {
-        LogInfo("BLEServiceHash: invalid browse type length: %d characters", length);
-        return 0;
-    }
+#define kTDSSipHashKey          ( (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )
+#define kTDSSipHashCount        5
 
-    return BLELabelHash(p, length);
-}
+#define kSizeCString        ( (size_t) -1 )
 
-// Storage for the current Bonjour BLE beacon data;
-typedef struct BLEBeacon
-{
-    serviceHash_t browseHash;
-    serviceHash_t registeredHash;
-} BLEBeacon_t;
+// End of code copied from: CoreUtils-500.9
 
-BLEBeacon_t BLEBeacon;
+// 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 void addServiceToBeacon(serviceHash_t browseHash, serviceHash_t registeredHash)
+mDNSlocal uint64_t local_TDSBloomFilterMake( uint32_t inBloomCount, const void *inStr, size_t inLen )
 {
-    bool beaconUpdated = false;
+    uint64_t        bloomFilter = 0, hash;
+    uint8_t         i;
 
-    if (BLEBeacon.browseHash & browseHash)
-    {
-        LogInfo("addServiceToBeacon: Bit 0x%x already set in browsing services hash", browseHash);
-    }
+    if( inLen == kSizeCString ) inLen = strlen( (const char *) inStr );
+    if (SipHash_p)
+        hash = SipHash_p( kTDSSipHashKey, inStr, inLen );
     else
-    {
-        BLEBeacon.browseHash |= browseHash;
-        beaconUpdated = true;
-    }
+        hash = local_SipHash( kTDSSipHashKey, inStr, inLen );
 
-    if (BLEBeacon.registeredHash & registeredHash)
-    {
-        LogInfo("addServiceToBeacon: Bit 0x%x already set in advertising services hash", registeredHash);
-    }
-    else
+    for( i = 0; i < kTDSSipHashCount; ++i )
     {
-        BLEBeacon.registeredHash |= registeredHash;
-        beaconUpdated = true;
+        bloomFilter |= ( UINT64_C( 1 ) << ( hash % inBloomCount ) );
+        hash /= inBloomCount;
     }
-
-    if (beaconUpdated)
-        updateBLEBeaconAndScan(BLEBeacon.browseHash, BLEBeacon.registeredHash);
+    return( bloomFilter );
 }
 
-// Go through all the existing browses and registrations to get the
-// current hash values for the corresponding BLE beacon.
-// We must do this when any hash bits are removed do accurately generate
-// the correct combination of all currently set hash bits.
-mDNSlocal void updateBeacon()
+mDNSlocal void loadCoreUtils()
 {
-    requestList_t *ptr;
+    static mDNSBool runOnce = mDNSfalse;
+    static void *CoreUtils_p = mDNSNULL;
+    static const char path[] = "/System/Library/PrivateFrameworks/CoreUtils.framework/CoreUtils";
 
-    BLEBeacon.browseHash = 0;
-    BLEBeacon.registeredHash = 0;
-
-    for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next)
+    if (!runOnce)
     {
-        BLEBeacon.browseHash |= ptr->browseHash;
-    }
+        runOnce = mDNStrue;
+        if (!CoreUtils_p)
+        {
+            CoreUtils_p = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
+            if (!CoreUtils_p)
+            {
+                LogInfo("loadCoreUtils: dlopen() failed.");
+                return;
+            }
+        }
 
-    for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
-    {
-        BLEBeacon.registeredHash |= ptr->registeredHash;
+        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();
 
-    updateBLEBeaconAndScan(BLEBeacon.browseHash, BLEBeacon.registeredHash);
+    return local_TDSBloomFilterMake(HASH_SIZE, (const void *) str, (size_t) length);
 }
 
-#pragma mark - Request start/stop
 
-// Forward declarations for mDNSLocal functions that are called before they are defined.
-mDNSlocal void checkForMatchingResponses(requestList_t *bp);
-mDNSlocal void clearResponseLists();
+// 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
 
-void start_BLE_browse(DNSQuestion * q, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags, mDNSu8 *key, size_t keySize)
+// 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)
 {
-    requestList_t * ptr; 
+    mDNSu8       * dst = stringBuf;
+    const mDNSu8 * src = domain->c;
+    mDNSu8         len = *src++;
 
-    if (!EnableBLEBasedDiscovery)
+    if (len == 0 || len > MAX_SERVICE_NAME)
     {
-        LogMsg("start_BLE_browse: EnableBLEBasedDiscovery disabled");
-        return;
+        LogInfo("serviceNameStringFromDomain: Invalid name lenght: %d", len);
+        return false;
     }
-
-    LogInfo("start_BLE_browse: Starting BLE 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)
+    if (*src != '_')
     {
-        LogInfo("start_BLE_browse: Dup of existing BLE browse.");
-        return;
+        LogInfo("serviceNameStringFromDomain: service name does not begin with a _");
+        return false;
     }
+    // Copy the service type
+    while (len--)
+        *dst++ = *src++;
 
-    ptr->browseHash = BLEServiceHash(domain);
-    ptr->question = q;
+    *dst++ = '.';
 
-    if (ptr->browseHash == 0)
+    if (!ValidTransportProtocol(src))
     {
-        LogInfo("BLEServiceHash failed!");
-        removeFromRequestList(&BLEBrowseListHead, domain, type);
-        return;
+        LogInfo("serviceNameStringFromDomain: Transport protocol name must be _udp or _tcp");
+        return false;
     }
+    // copy the transport protocol
+    len = *src++;
+    while (len--)
+        *dst++ = *src++;
 
-    // Save these for use in D2D plugin callback logic.
-    memcpy(ptr->key, key, keySize);
-    ptr->keySize = keySize;
-    // Extract the interface ID for easier access in the requestList_t structure
-    ptr->InterfaceID = q->InterfaceID;
-
-    addServiceToBeacon(ptr->browseHash, 0);
-
-    checkForMatchingResponses(ptr);
+    *dst = 0;
+    return true;
 }
 
-// Stop the browse.
-// Return true if this is the last reference to the browse, false otherwise.
-bool stop_BLE_browse(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
+mDNSlocal bool setBLEServiceHash(const domainname *const domain, requestList_t * ptr)
 {
-    (void)  flags;   // not used initially
-    requestList_t * ptr;
-    bool    lastReference = false;
+    // Initialize the string with the "s:" for the browser/seeker hash calculation.
+    mDNSu8 stringBuf[MAX_HASH_STRING] = { 's', ':', '\0' };
 
-    if (!EnableBLEBasedDiscovery)
+    // Append the service name and protocol strings to the initial "s:" string.
+    if (!serviceNameStringFromDomain(domain, &stringBuf[2]))
     {
-        LogMsg("stop_BLE_browse: EnableBLEBasedDiscovery disabled");
-        return lastReference;
+        LogInfo("setBLEServiceHash: serviceNameStringFromDomain() failed!");
+        return false;
     }
 
-    LogInfo("stop_BLE_browse: Stopping BLE 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, update advertising and browsing bits set in
-    // the beacon after removing this browse from the list.
-    if (ptr->refCount == 1)
-        lastReference = true;
+    ptr->browseHash =  BLELabelHash(stringBuf, strlen((const char *)stringBuf));
+    LogInfo("setBLEServiceHash: seeker string %s, hashed to 0x%lx", stringBuf, ptr->browseHash);
 
-    removeFromRequestList(&BLEBrowseListHead, domain, type);
+    // Update string to start with "p:" for registration/provider hash calculation.
+    stringBuf[0] = 'p';
 
-    if (lastReference)
-        updateBeacon();
+    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;
+}
 
-    // 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();
+// 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;
 
-    return lastReference;
-}
+// The last time we walked our response list looking for stale entries.
+mDNSs32 lastScanForStaleResponses;
 
-extern void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, DNSQuestion * q);
-extern void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags);
+// Forward declaration.
+mDNSlocal void removeStaleResponses(mDNSs32 currentTime);
 
-extern void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
-extern void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
+// Interval at which we scan the response lists to remove any stale entries.
+#define StaleResponseScanInterval 30
 
-void start_BLE_advertise(ServiceRecordSet * serviceRecordSet, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
+// Called from mDNS_Execute() when NextBLEServiceTime is reached.
+void serviceBLE(void)
 {
-    requestList_t * ptr; 
-    const domainname * instanceRemoved;
+    // 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;
 
-    if (!EnableBLEBasedDiscovery)
-    {
-        LogMsg("start_BLE_advertise: EnableBLEBasedDiscovery disabled");
-        return;
-    }
-
-    // Just process the SRV record for each service registration.  The PTR
-    // record already has the service type at the beginning of the domain, but 
-    // we want to filter out reverse address PTR records at this point in time, so using
-    // the SRV record instead.
-    if (type != kDNSServiceType_SRV)
-        return;
+    // Initialize if zero.
+    if (!lastScanForStaleResponses)
+        lastScanForStaleResponses = NonZeroTime(currentTime - (StaleResponseScanInterval * mDNSPlatformOneSecond));
 
-    if (serviceRecordSet == NULL)
+    if (finalBeacon)
     {
-        LogInfo("start_BLE_advertise: NULL service record set for: %##s %s, returning", domain->c, DNSTypeName(type));
-        return;
-    }
-    LogInfo("start_BLE_advertise: Starting BLE advertisement for: %##s %s", domain->c, DNSTypeName(type));
+        // 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 ??");
 
-    instanceRemoved = SkipLeadingLabels(domain, 1);
+        // 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 ??");
 
-    ptr = addToRequestList(&BLERegistrationListHead, instanceRemoved, type, flags);
+        finalBeacon = false;
+        stopBLEBeacon();
+    }
 
-    // If equivalent BLE registration is already running, just return.
-    if (ptr->refCount > 1)
+    if (!BLEBrowseListHead && !BLERegistrationListHead)
     {
-        LogInfo("start_BLE_advertise: Dup of existing BLE advertisement.");
-        return;
+        LogInfo("serviceBLE: no active client requests, disabling service timer");
+        mDNSStorage.NextBLEServiceTime = 0;
     }
-
-    ptr->registeredHash = BLEServiceHash(instanceRemoved);
-    if (ptr->registeredHash == 0)
+    else if ((currentTime - lastScanForStaleResponses) >= (StaleResponseScanInterval * mDNSPlatformOneSecond))
     {
-        LogInfo("BLEServiceHash failed!");
-        removeFromRequestList(&BLERegistrationListHead, instanceRemoved, type);
-        return;
+        removeStaleResponses(currentTime);
+        lastScanForStaleResponses = currentTime;
+        mDNSStorage.NextBLEServiceTime = NonZeroTime(currentTime + (StaleResponseScanInterval * mDNSPlatformOneSecond));
     }
-    ptr->serviceRecordSet = serviceRecordSet;
-    // Extract the interface ID for easier access in the requestList_t structure
-    ptr->InterfaceID = serviceRecordSet->RR_SRV.resrec.InterfaceID;
-
-    addServiceToBeacon(0, ptr->registeredHash);
 }
 
-void stop_BLE_advertise(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
+// 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()
 {
-    (void)  flags;   // not used initially
-    requestList_t       * ptr;
-    bool                lastReference = false;
-    const domainname    * instanceRemoved;
-
-    if (!EnableBLEBasedDiscovery)
-    {
-        LogMsg("stop_BLE_advertise: EnableBLEBasedDiscovery disabled");
-        return;
-    }
+    if (!mDNSStorage.NextBLEServiceTime && (BLEBrowseListHead || BLERegistrationListHead))
+        mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (StaleResponseScanInterval * mDNSPlatformOneSecond));
+}
 
-    // Just process the SRV record for each service registration.  The PTR
-    // record already has the service type at the beginning of the domain, but 
-    // we want to filter out reverse address PTR records at this point in time, so using
-    // the SRV record instead.
-    if (type != kDNSServiceType_SRV)
-        return;
+// 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;
 
-    LogInfo("stop_BLE_advertise: Stopping BLE advertisement for: %##s %s", domain->c, DNSTypeName(type));
+// 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;
 
-    instanceRemoved = SkipLeadingLabels(domain, 1);
+    updateServiceTimer();
 
-    // Get the request pointer from the indirect pointer returned.
-    ptr =  *(findInRequestList(&BLERegistrationListHead, instanceRemoved, type));
-
-    if (ptr == 0)
+    for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next)
     {
-        LogInfo("stop_BLE_advertise: No matching advertisement found.");
-        return;
+        beaconBloomFilter |= ptr->browseHash;
     }
-    
-    // If this is the last reference for this registration, update advertising and browsing bits set in
-    // the beacon before removing this registration from the request list.
-    if (ptr->refCount == 1)
+
+    for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
     {
-        lastReference = true;
+        beaconBloomFilter |= ptr->registeredHash;
+    }
 
-        if (isAutoTriggerRequest(ptr) && ptr->triggeredOnAWDL)
+    // 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
         {
-            // And remove the corresponding advertisements from the AWDL D2D plugin.
-            // Do it directly here, since we do not set the kDNSServiceFlagsIncludeAWDL bit in the original client request structure
-            // when we trigger the registration over AWDL, we just update the record ARType field, so our caller, external_stop_browsing_for_service()
-            // would not call into the D2D plugin to remove the advertisements in this case.
-            internal_stop_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-            internal_stop_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-            internal_stop_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
+            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();
         }
     }
-    removeFromRequestList(&BLERegistrationListHead, instanceRemoved, type);
-
-    if (lastReference)
-        updateBeacon();
-
-    // 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();
+    // 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 - Response Handling
+#pragma mark - Peer response handling
 
 // Structure used to track the beacons received from various peers.
 typedef struct responseList
 {
     struct responseList * next;
-    serviceHash_t       browseHash;
-    serviceHash_t       registeredHash;
-    mDNSEthAddr         senderMAC;
+    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.
@@ -518,7 +695,7 @@ mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC)
 
     for ( ; *ptr; ptr = &(*ptr)->next)
     {
-        if (memcmp(&(*ptr)->senderMAC, ptrToMAC, sizeof(mDNSEthAddr)) == 0)
+        if (memcmp(&(*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr)) == 0)
             break;
     }
 
@@ -526,7 +703,7 @@ mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC)
 }
 
 
-mDNSlocal responseList_t ** addToResponseList(serviceHash_t browseHash, serviceHash_t registeredHash, mDNSEthAddr * ptrToMAC)
+mDNSlocal responseList_t * addToResponseList(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
 {
     responseList_t **ptr = findInResponseList(ptrToMAC);
 
@@ -534,12 +711,11 @@ mDNSlocal responseList_t ** addToResponseList(serviceHash_t browseHash, serviceH
     {
         *ptr = mDNSPlatformMemAllocate(sizeof(**ptr));
         mDNSPlatformMemZero(*ptr, sizeof(**ptr));
-        (*ptr)->browseHash = browseHash;
-        (*ptr)->registeredHash = registeredHash;
-        memcpy(& (*ptr)->senderMAC, ptrToMAC, sizeof(mDNSEthAddr));
+        (*ptr)->peerBloomFilter = peerBloomFilter;
+        memcpy(& (*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr));
     }
 
-    return ptr;
+    return *ptr;
 }
 
 mDNSlocal void removeFromResponseList(mDNSEthAddr * ptrToMAC)
@@ -581,25 +757,8 @@ mDNSlocal void clearResponseLists()
     }
 }
 
-// Called from mDNS_Execute() when NextBLEServiceTime is reached
-// to stop the BLE beacon a few seconds after the last request has
-// been stopped.  This gives peers a chance to see that this device
-// is no longer browsing for or advertising any services via the
-// BLE beacon.  
-void serviceBLE(void)
-{
-    mDNSStorage.NextBLEServiceTime = 0;
-    if (BLEBrowseListHead || BLERegistrationListHead)
-    {
-        // We don't expect to be called if there are active requests.
-        LogInfo("serviceBLE: called with active BLE requests ??");
-        return;
-    }
-    stopBLEBeacon();
-}
-
-// Called from start_BLE_browse() on the mDNSResonder kqueue thread
-mDNSlocal void checkForMatchingResponses(requestList_t *bp)
+// 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;
 
@@ -607,28 +766,28 @@ mDNSlocal void checkForMatchingResponses(requestList_t *bp)
     {
         for (ptr = BLEResponseListHeads[i]; ptr; ptr = ptr->next)
         {
-            if ((bp->browseHash & ptr->registeredHash) == bp->browseHash)
+            // 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 registered services hash for the response.
-                // The next beacon from this peer will update the hash and our 
-                // newly started browse will get an add event if there is a match.
-                ptr->registeredHash = 0;
+                // 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 advetised by peers in their BLE beacons.
+// 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 backgound on 0xc027 DNS name compression pointer value.
+// 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)
 
-void xD2DAddToCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize);
-void xD2DRemoveFromCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize);
-
-// Find each unique browse that matches the registered service hash in the BLE response.
+// 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)
@@ -638,7 +797,9 @@ mDNSlocal void findMatchingBrowse(responseList_t *response)
     ptr = BLEBrowseListHead;
     for ( ; ptr; ptr = ptr->next)
     {
-        if ((ptr->browseHash & response->registeredHash) == ptr->browseHash) 
+        // 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);
@@ -655,20 +816,19 @@ mDNSlocal void findMatchingBrowse(responseList_t *response)
 
                 if (ptr->ourResponses == 0)
                 {
-                    if (isAutoTriggerRequest(ptr))
+                    if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
                     {
                            LogInfo("findMatchingBrowse: First BLE response, triggering browse for %##s on AWDL", ptr->name.c);
-                           ptr->question->flags |= kDNSServiceFlagsIncludeAWDL;
-                           mDNSCoreRestartQuestion(& mDNSStorage, ptr->question);
                            // register with the AWDL D2D plugin, 
-                           internal_start_browsing_for_service(ptr->question->InterfaceID, & ptr->name, ptr->type, ptr->question->flags, ptr->question);
+                           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->question->InterfaceID == mDNSInterface_BLE)
+                       if (ptr->InterfaceID == mDNSInterface_BLE)
                        {
-                           xD2DAddToCache(& mDNSStorage, kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
+                           xD2DAddToCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
                        }
                 }
                 addToResponseListForRequest(ptr, response);
@@ -680,34 +840,37 @@ mDNSlocal void findMatchingBrowse(responseList_t *response)
             // 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->question->InterfaceID == mDNSInterface_BLE)
+                if (ptr->InterfaceID == mDNSInterface_BLE)
                 {
-                    xD2DRemoveFromCache(& mDNSStorage, kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
+                    xD2DRemoveFromCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
                 }
 
-                if (isAutoTriggerRequest(ptr))
+                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->question->InterfaceID, & ptr->name, ptr->type, ptr->question->flags);
+                    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 in the BLE response.
+// 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;
+    requestList_t   *ptr;
+    bool            matchingPeer;
 
     ptr = BLERegistrationListHead;
     for ( ; ptr; ptr = ptr->next)
     {
-        if ((ptr->registeredHash & response->browseHash) == ptr->registeredHash) 
+        // 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))
@@ -722,44 +885,16 @@ mDNSlocal void findMatchingRegistration(responseList_t *response)
 
                 // 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))
+                if ((ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
                 {
-                    AuthRecType newARType;
-
                     LogInfo("findMatchingRegistration: First BLE response, triggering registration for %##s on AWDL", ptr->name.c);
-                    if (ptr->serviceRecordSet == 0)
-                    {
-                        LogInfo("findMatchingRegistration: serviceRecordSet pointer is NULL ??");
-                        continue;
-                    }
-                    // Modify the PTR, TXT, and SRV records so that they now apply to AWDL and restart the registration.
-                    // RR_ADV is not passed to the D2D plugins froma internal_start_advertising_helper(), so we don't do it here either.
-
-                    if (ptr->flags & kDNSServiceFlagsIncludeAWDL)
+                    if (ptr->resourceRecord == 0)
                     {
-                        LogInfo("findMatchingRegistration: registration for %##s already applies to AWDL, skipping", ptr->name.c);
+                        LogInfo("findMatchingRegistration: resourceRecord pointer is NULL ??");
                         continue;
                     }
 
-                    // Save the current ARType value to restore when the promotion to use AWDL is stopped.
-                    ptr->savedARType = ptr->serviceRecordSet->RR_PTR.ARType;
-
-                    // Preserve P2P attribute if original registration was applied to P2P.
-                    if (ptr->serviceRecordSet->RR_PTR.ARType == AuthRecordAnyIncludeP2P)
-                        newARType = AuthRecordAnyIncludeAWDLandP2P;
-                    else
-                        newARType = AuthRecordAnyIncludeAWDL;
-
-                    ptr->serviceRecordSet->RR_PTR.ARType = newARType;
-                    ptr->serviceRecordSet->RR_SRV.ARType = newARType;
-                    ptr->serviceRecordSet->RR_TXT.ARType = newARType;
-                    mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_PTR, -1);
-                    mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_SRV, -1);
-                    mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_TXT, -1);
-
-                    internal_start_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-                    internal_start_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-                    internal_start_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
+                    internal_start_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
                     // indicate the registration has been applied to the AWDL interface
                     ptr->triggeredOnAWDL = true;
                 }
@@ -772,51 +907,113 @@ mDNSlocal void findMatchingRegistration(responseList_t *response)
             // 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))
+            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.
-                ptr->serviceRecordSet->RR_PTR.ARType = ptr->savedARType;
-                ptr->serviceRecordSet->RR_SRV.ARType = ptr->savedARType;
-                ptr->serviceRecordSet->RR_TXT.ARType = ptr->savedARType;
-                internal_stop_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-                internal_stop_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
-                internal_stop_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
+                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 browseHash, serviceHash_t registeredHash, mDNSEthAddr * ptrToMAC)
+void responseReceived(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
 {
-    responseList_t ** ptr;
+    responseList_t * ptr;
 
-    KQueueLock(& mDNSStorage);
-    ptr = findInResponseList(ptrToMAC);
-    if (*ptr == 0)
+    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 (browseHash || registeredHash)
+        if (peerBloomFilter)
         {
             LogInfo("responseReceived: First beacon of this type, adding to list");
-            LogInfo("responseReceived: browseHash = 0x%x, registeredHash = 0x%x",
-                    browseHash, registeredHash);
-            LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
+            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(browseHash, registeredHash, ptrToMAC);       
+            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);
+            findMatchingBrowse(ptr);
             // See if we have a registration that matches the peer's browse.
-            findMatchingRegistration(*ptr);
+            findMatchingRegistration(ptr);
         }
     }
-    else    // have entry from this MAC in the list
+    else    // Have an entry from this MAC in the list.
     {
-        if (((*ptr)->browseHash == browseHash) && ((*ptr)->registeredHash == registeredHash))
+        // Update the received time.
+        ptr->recievedTime =  mDNSStorage.timenow;
+
+        if (ptr->peerBloomFilter == peerBloomFilter)
         {
             // A duplicate of a current entry.
 #if VERBOSE_BLE_DEBUG
@@ -828,20 +1025,17 @@ void responseReceived(serviceHash_t browseHash, serviceHash_t registeredHash, mD
         else
         {
             LogInfo("responseReceived: Update of previous beacon");
-            LogInfo("responseReceived: browseHash = 0x%x, registeredHash = 0x%x",
-                    browseHash, registeredHash);
+            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)->browseHash = browseHash;
-            (*ptr)->registeredHash = registeredHash;
-
-            findMatchingBrowse(*ptr);
-            findMatchingRegistration(*ptr);
+            ptr->peerBloomFilter = peerBloomFilter;
+            findMatchingBrowse(ptr);
+            findMatchingRegistration(ptr);
         }
 
         // If peer is no longer browsing or advertising, remove from list.
-        if ((browseHash == 0) && (registeredHash == 0))
+        if (peerBloomFilter == 0)
         {
             LogInfo("responseReceived: Removing peer entry from the list");
 
@@ -849,5 +1043,450 @@ void responseReceived(serviceHash_t browseHash, serviceHash_t registeredHash, mD
         }
     }
 
-    KQueueUnlock(& mDNSStorage, "BLE responseReceived");
+    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