/* -*- 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 #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 . #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 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, ðerAddress[i]); // Verify all entries are found. for (i = 0; i < numOfEtherAddresses; i++) { if (*(findInResponseList(ðerAddress[i])) == 0) { printf("findInResponseList() did not find entry in list\n"); FAILED; } } // Remove all entries. for (i = 0; i < numOfEtherAddresses; i++) removeFromResponseList(ðerAddress[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, ðerAddress[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