/* -*- Mode: C; tab-width: 4 -*-
*
* Copyright (c) 2015-2016 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if ENABLE_BLE_TRIGGERED_BONJOUR
#include "mDNSEmbeddedAPI.h"
#include "DNSCommon.h"
#include "mDNSMacOSX.h"
#include "BLE.h"
#include "D2D.h"
#include <dlfcn.h>
#pragma mark - Browse and Registration Request Handling
// When set, enables BLE triggered discovery APIs.
mDNSBool EnableBLEBasedDiscovery = mDNSfalse;
// When set, the default mode is to promote all client requests made with
// kDNSServiceInterfaceIndexAny to BLE Triggered Discovery.
// Requests to promote will be filtered by either a service type whitelist or
// blacklist as noted below.
mDNSBool DefaultToBLETriggered = mDNSfalse;
#define USE_WHITELIST 1
#if USE_WHITELIST
// Current list of service types that will have BLE triggers applied by default
// when DefaultToBLETriggered is set to true.
const char * defaultServiceWhitelist[] = {
"\x04_ssh",
"\x04_smb",
"\x04_rfb",
"\x04_ipp",
"\x05_ipps",
"\x08_printer",
0
};
// Return true if DefaultToBLETriggered is set and the operation should be
// promoted to use BLE triggered discovery by default.
bool shouldUseBLE(mDNSInterfaceID interfaceID, DNS_TypeValues rrtype, domainname *serviceType, domainname *domain)
{
const mDNSu8 ** ptr;
if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain))
return mDNSfalse;
// Address records don't have a service type to match on, but we'll trigger them
// here to support the case were the DNSServiceQueryRecord() was done using mDNSInterface_Any instead
// of the interface that the corresponding SRV record was returned over.
if ((rrtype == kDNSType_A) || (rrtype == kDNSType_AAAA))
return mDNStrue;
ptr = (const mDNSu8 **) defaultServiceWhitelist;
while (*ptr)
{
if (SameDomainLabel(*ptr, serviceType->c))
return mDNStrue;
ptr++;
}
return mDNSfalse;
}
#else // USE_WHITELIST
// Current list of service types that will NOT have BLE triggers applied by default
// when DefaultToBLETriggered is set to true.
// _airplay and _airdrop discovery already employ BLE based triggering using Apple service specific
// BLE beacons. The rest of the entries here are default browses run in a standard OSX install
// that we don't want to have cluttering up the Bloom filter when using the service blacklist approach.
const char * defaultServiceBlacklist[] = {
"\x08_airplay",
"\x08_airdrop",
"\x05_raop",
"\x08_airport",
"\x0d_apple-mobdev",
"\x06_uscan",
"\x07_uscans",
"\x08_scanner",
"\x0e_apple-mobdev2",
"\x04_ipp",
"\x05_ipps",
"\x07_ippusb",
"\x08_printer",
"\x0f_pdl-datastream",
"\x04_ptp",
0
};
// Return true if DefaultToBLETriggered is set and the operation should be
// promoted to use BLE triggered discovery by default.
bool shouldUseBLE(mDNSInterfaceID interfaceID, DNS_TypeValues rrtype, domainname *serviceType, domainname *domain)
{
(void) rrtype;
const mDNSu8 ** ptr;
if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain))
return mDNSfalse;
ptr = (const mDNSu8 **) defaultServiceBlacklist;
while (*ptr)
{
if (SameDomainLabel(*ptr, serviceType->c))
return mDNSfalse;
ptr++;
}
return mDNStrue;
}
#endif // USE_WHITELIST
// Structure for linked list of BLE responses received that match
// a given client request.
typedef struct matchingResponses
{
struct matchingResponses * next;
void * response;
} matchingResponses_t;
// Max size of input key generated by DNSNameCompressionBuildLHS() is MAX_DOMAIN_NAME + 3
// where the three additional bytes are:
// two bytes for DNS_TypeValues and one byte for "compression_packet_v1", the D2D compression version number.
#define MAX_KEY_SIZE MAX_DOMAIN_NAME + 3
// Initially used for both the browse and registration lists.
typedef struct requestList
{
struct requestList * next;
unsigned int refCount;
domainname name;
mDNSu16 type;
DNSServiceFlags flags;
mDNSInterfaceID InterfaceID;
serviceHash_t browseHash;
serviceHash_t registeredHash;
matchingResponses_t * ourResponses;
bool triggeredOnAWDL;
// The following fields are only used for browse requests currently
mDNSu8 key[MAX_KEY_SIZE];
size_t keySize;
// The following fields are only used for registration requests currently
const ResourceRecord * resourceRecord;
} requestList_t;
// Lists for all DNSServiceBrowse() and DNSServiceRegister() requests using
// BLE beacon based triggering.
static requestList_t* BLEBrowseListHead = NULL;
static requestList_t* BLERegistrationListHead = NULL;
// The kDNSServiceFlagsAutoTrigger should only be set for a request that would normally apply to AWDL.
#define isAutoTriggerRequest(INTERFACE_INDEX, FLAGS) ( (FLAGS & kDNSServiceFlagsAutoTrigger) \
&& ( (AWDLInterfaceID && (INTERFACE_INDEX == AWDLInterfaceID)) \
|| ((INTERFACE_INDEX == kDNSServiceInterfaceIndexAny) && (FLAGS & kDNSServiceFlagsIncludeAWDL))))
#pragma mark - Manage list of responses that match this request.
// Return true if any response matches one of our current registrations.
mDNSlocal bool responseMatchesRegistrations(void)
{
requestList_t *ptr;
for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
{
if (ptr->ourResponses)
return true;
}
return false;
}
// Return true if the response is already in the list of responses for this client request.
mDNSlocal bool inResponseListForRequest(requestList_t *request, void * response)
{
matchingResponses_t * rp;
for (rp = request->ourResponses; rp; rp = rp->next)
if (rp->response == response)
break;
return (rp != 0);
}
mDNSlocal void addToResponseListForRequest(requestList_t *request, void * response)
{
matchingResponses_t *matchingResponse = calloc(1, sizeof(matchingResponses_t));
if (matchingResponse == NULL)
{
LogMsg("addToResponseListForRequest: calloc() failed!");
return;
}
matchingResponse->response = response;
matchingResponse->next = request->ourResponses;
request->ourResponses = matchingResponse;
}
// If response is currently in the list of responses, remove it and return true.
// Othewise, return false.
mDNSlocal bool removeFromResponseListForRequest(requestList_t *request, void * response)
{
matchingResponses_t ** nextp;
bool responseRemoved = false;
for (nextp = & request->ourResponses; *nextp; nextp = & (*nextp)->next)
if ((*nextp)->response == response)
break;
if (*nextp)
{
LogInfo("removeFromResponseListForRequest: response no longer matches for %##s %s ", request->name.c, DNSTypeName(request->type));
responseRemoved = true;
matchingResponses_t *tmp = *nextp;
*nextp = (*nextp)->next;
free(tmp);
}
return responseRemoved;
}
// Free all current entries on the response list for this request.
mDNSlocal void freeResponseListEntriesForRequest(requestList_t *request)
{
matchingResponses_t * ptr;
ptr = request->ourResponses;
while (ptr)
{
matchingResponses_t * tmp;
tmp = ptr;
ptr = ptr->next;
free(tmp);
}
request->ourResponses = 0;
}
#pragma mark - Manage request lists
// Return the address of the pointer to the entry, which can either be the address of "listHead"
// or the address of the prior entry on the lists "next" pointer.
mDNSlocal requestList_t ** findInRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type)
{
requestList_t **ptr = listHead;
for ( ; *ptr; ptr = &(*ptr)->next)
if ((*ptr)->type == type && SameDomainName(&(*ptr)->name, name))
break;
return ptr;
}
mDNSlocal requestList_t * addToRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type, DNSServiceFlags flags)
{
requestList_t **ptr = findInRequestList(listHead, name, type);
if (!*ptr)
{
*ptr = mDNSPlatformMemAllocate(sizeof(**ptr));
mDNSPlatformMemZero(*ptr, sizeof(**ptr));
(*ptr)->type = type;
(*ptr)->flags = flags;
AssignDomainName(&(*ptr)->name, name);
}
(*ptr)->refCount += 1;
LogInfo("addToRequestList: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);
return *ptr;
}
mDNSlocal void removeFromRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type)
{
requestList_t **ptr = findInRequestList(listHead, name, type);
if (!*ptr) { LogMsg("removeFromRequestList: Didn't find %##s %s in list", name->c, DNSTypeName(type)); return; }
(*ptr)->refCount -= 1;
LogInfo("removeFromRequestList: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);
if (!(*ptr)->refCount)
{
requestList_t *tmp = *ptr;
*ptr = (*ptr)->next;
freeResponseListEntriesForRequest(tmp);
mDNSPlatformMemFree(tmp);
}
}
#pragma mark - Hashing and beacon state
// These SipHash routines were copied from CoreUtils-500.9.
// We use these when running an mDNSRespnder root on a system that does not
// have the SipHash() routine available and exported in CoreUtils.
// TODO: This local copy should be removed once we are no longer running mDNSResponder roots
// on systems that do no include CoreUtils-500.9 or newer.
// Start of code copied from: CoreUtils-500.9
/*! @group BitRotates
@abstract Rotates X COUNT bits to the left or right.
*/
#define ROTL( X, N, SIZE ) ( ( (X) << (N) ) | ( (X) >> ( (SIZE) - N ) ) )
#define ROTR( X, N, SIZE ) ( ( (X) >> (N) ) | ( (X) << ( (SIZE) - N ) ) )
#define ROTL64( X, N ) ROTL( (X), (N), 64 )
#define ROTR64( X, N ) ROTR( (X), (N), 64 )
#define ReadLittle64( PTR ) \
( (uint64_t)( \
( (uint64_t)( (uint8_t *)(PTR) )[ 0 ] ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 1 ] ) << 8 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 2 ] ) << 16 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 3 ] ) << 24 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 4 ] ) << 32 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 5 ] ) << 40 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 6 ] ) << 48 ) | \
( ( (uint64_t)( (uint8_t *)(PTR) )[ 7 ] ) << 56 ) ) )
// Based on <https://131002.net/siphash/>.
#define SipRound() \
do \
{ \
v0 += v1; v1 = ROTL64( v1, 13 ); v1 ^= v0; v0 = ROTL64( v0, 32 ); \
v2 += v3; v3 = ROTL64( v3, 16 ); v3 ^= v2; \
v0 += v3; v3 = ROTL64( v3, 21 ); v3 ^= v0; \
v2 += v1; v1 = ROTL64( v1, 17 ); v1 ^= v2; v2 = ROTL64( v2, 32 ); \
\
} while( 0 )
mDNSlocal uint64_t local_SipHash( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen )
{
const uint8_t * src = (const uint8_t *) inSrc;
size_t const left = inLen % 8;
const uint8_t * const end = src + ( inLen - left );
uint64_t k0, k1, v0, v1, v2, v3, tmp;
k0 = ReadLittle64( &inKey[ 0 ] );
k1 = ReadLittle64( &inKey[ 8 ] );
v0 = k0 ^ UINT64_C( 0x736f6d6570736575 ); // 'somepseu'
v1 = k1 ^ UINT64_C( 0x646f72616e646f6d ); // 'dorandom'
v2 = k0 ^ UINT64_C( 0x6c7967656e657261 ); // 'lygenera'
v3 = k1 ^ UINT64_C( 0x7465646279746573 ); // 'tedbytes'
for( ; src != end; src += 8 )
{
tmp = ReadLittle64( src );
v3 ^= tmp;
SipRound();
SipRound();
v0 ^= tmp;
}
tmp = ( (uint64_t)( inLen & 0xFF ) ) << 56;
switch( left )
{
case 7: tmp |= ( ( (uint64_t) src[ 6 ] ) << 48 );
case 6: tmp |= ( ( (uint64_t) src[ 5 ] ) << 40 );
case 5: tmp |= ( ( (uint64_t) src[ 4 ] ) << 32 );
case 4: tmp |= ( ( (uint64_t) src[ 3 ] ) << 24 );
case 3: tmp |= ( ( (uint64_t) src[ 2 ] ) << 16 );
case 2: tmp |= ( ( (uint64_t) src[ 1 ] ) << 8 );
case 1: tmp |= ( (uint64_t) src[ 0 ] );
default: break;
}
v3 ^= tmp;
SipRound();
SipRound();
v0 ^= tmp;
v2 ^= 0xFF;
SipRound();
SipRound();
SipRound();
SipRound();
return( v0 ^ v1 ^ v2 ^ v3 );
}
// See <https://spc.apple.com/AppleBLEInfo.html#_wifi_tds> for details.
#define kTDSSipHashKey ( (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )
#define kTDSSipHashCount 5
#define kSizeCString ( (size_t) -1 )
// End of code copied from: CoreUtils-500.9
// Must link symbol from CoreUtils at runtime to avoid cyclic dependency cycles in the build process.
static uint64_t (*SipHash_p)( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen ) = NULL;
mDNSlocal uint64_t local_TDSBloomFilterMake( uint32_t inBloomCount, const void *inStr, size_t inLen )
{
uint64_t bloomFilter = 0, hash;
uint8_t i;
if( inLen == kSizeCString ) inLen = strlen( (const char *) inStr );
if (SipHash_p)
hash = SipHash_p( kTDSSipHashKey, inStr, inLen );
else
hash = local_SipHash( kTDSSipHashKey, inStr, inLen );
for( i = 0; i < kTDSSipHashCount; ++i )
{
bloomFilter |= ( UINT64_C( 1 ) << ( hash % inBloomCount ) );
hash /= inBloomCount;
}
return( bloomFilter );
}
mDNSlocal void loadCoreUtils()
{
static mDNSBool runOnce = mDNSfalse;
static void *CoreUtils_p = mDNSNULL;
static const char path[] = "/System/Library/PrivateFrameworks/CoreUtils.framework/CoreUtils";
if (!runOnce)
{
runOnce = mDNStrue;
if (!CoreUtils_p)
{
CoreUtils_p = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
if (!CoreUtils_p)
{
LogInfo("loadCoreUtils: dlopen() failed.");
return;
}
}
if (!SipHash_p)
{
SipHash_p = dlsym(CoreUtils_p, "SipHash");
if (!SipHash_p)
{
LogInfo("loadCoreUtils: load of SipHash symbol failed.");
return;
}
}
LogInfo("loadCoreUtils: found SipHash symbol.");
}
}
#define HASH_SIZE 64
mDNSlocal serviceHash_t BLELabelHash(unsigned char *str, unsigned int length)
{
loadCoreUtils();
return local_TDSBloomFilterMake(HASH_SIZE, (const void *) str, (size_t) length);
}
// Maximum number of characters in string to hash should be:
// 2 for initial "s:" or "p:"
// 16 for "_" followed by up to 15 characters of service type
// 1 for separating "."
// 4 for "_udp" or "_tcp"
// 1 for the terminating NULL byte
#define MAX_HASH_STRING (2 + 16 + 1 + 4 + 1)
// Maximum service name length, including the initial "_"
#define MAX_SERVICE_NAME 16
// Convert the service name and transport protocol to a NULL terminated C string.
// stringBuf must point to least (MAX_HASH_STRING - 2) bytes of available space.
mDNSlocal bool serviceNameStringFromDomain(const domainname *const domain, mDNSu8 * stringBuf)
{
mDNSu8 * dst = stringBuf;
const mDNSu8 * src = domain->c;
mDNSu8 len = *src++;
if (len == 0 || len > MAX_SERVICE_NAME)
{
LogInfo("serviceNameStringFromDomain: Invalid name lenght: %d", len);
return false;
}
if (*src != '_')
{
LogInfo("serviceNameStringFromDomain: service name does not begin with a _");
return false;
}
// Copy the service type
while (len--)
*dst++ = *src++;
*dst++ = '.';
if (!ValidTransportProtocol(src))
{
LogInfo("serviceNameStringFromDomain: Transport protocol name must be _udp or _tcp");
return false;
}
// copy the transport protocol
len = *src++;
while (len--)
*dst++ = *src++;
*dst = 0;
return true;
}
mDNSlocal bool setBLEServiceHash(const domainname *const domain, requestList_t * ptr)
{
// Initialize the string with the "s:" for the browser/seeker hash calculation.
mDNSu8 stringBuf[MAX_HASH_STRING] = { 's', ':', '\0' };
// Append the service name and protocol strings to the initial "s:" string.
if (!serviceNameStringFromDomain(domain, &stringBuf[2]))
{
LogInfo("setBLEServiceHash: serviceNameStringFromDomain() failed!");
return false;
}
ptr->browseHash = BLELabelHash(stringBuf, strlen((const char *)stringBuf));
LogInfo("setBLEServiceHash: seeker string %s, hashed to 0x%lx", stringBuf, ptr->browseHash);
// Update string to start with "p:" for registration/provider hash calculation.
stringBuf[0] = 'p';
ptr->registeredHash = BLELabelHash(stringBuf, strlen((const char *)stringBuf));
LogInfo("setBLEServiceHash: provider string %s, hashed to 0x%lx", stringBuf, ptr->registeredHash);
if (ptr->browseHash && ptr->registeredHash)
return true;
else
return false;
}
// Indicates we are sending the final beacon with zeroed Bloom filter to let
// peers know we are no longer actively seeking or providing any services.
bool finalBeacon = false;
// The last time we walked our response list looking for stale entries.
mDNSs32 lastScanForStaleResponses;
// Forward declaration.
mDNSlocal void removeStaleResponses(mDNSs32 currentTime);
// Interval at which we scan the response lists to remove any stale entries.
#define StaleResponseScanInterval 30
// Called from mDNS_Execute() when NextBLEServiceTime is reached.
void serviceBLE(void)
{
// Note, we can access mDNSStorage.timenow since we are called from mDNS_Execute,
// which initializes that value by calling mDNS_Lock().
mDNSs32 currentTime = mDNSStorage.timenow;
// Initialize if zero.
if (!lastScanForStaleResponses)
lastScanForStaleResponses = NonZeroTime(currentTime - (StaleResponseScanInterval * mDNSPlatformOneSecond));
if (finalBeacon)
{
// We don't expect to do the finalBeacon processing if there are active browse requests,
if (BLEBrowseListHead)
LogInfo("serviceBLE: finalBeacon set and called with active browse BLE requests ??");
// or active registrations but we are not in suppress beacons state.
if (BLERegistrationListHead && !suppressBeacons)
LogInfo("serviceBLE: finalBeacon set and called with active registrations requests, but not in suppress beacons state ??");
finalBeacon = false;
stopBLEBeacon();
}
if (!BLEBrowseListHead && !BLERegistrationListHead)
{
LogInfo("serviceBLE: no active client requests, disabling service timer");
mDNSStorage.NextBLEServiceTime = 0;
}
else if ((currentTime - lastScanForStaleResponses) >= (StaleResponseScanInterval * mDNSPlatformOneSecond))
{
removeStaleResponses(currentTime);
lastScanForStaleResponses = currentTime;
mDNSStorage.NextBLEServiceTime = NonZeroTime(currentTime + (StaleResponseScanInterval * mDNSPlatformOneSecond));
}
}
// Initialize the periodic service timer if we have active requests.
// The timer is disabled in the next call to serviceBLE() when no requests are active.
mDNSlocal void updateServiceTimer()
{
if (!mDNSStorage.NextBLEServiceTime && (BLEBrowseListHead || BLERegistrationListHead))
mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (StaleResponseScanInterval * mDNSPlatformOneSecond));
}
// Set true when suppressing beacon transmissions for our registrations until we see
// a peer beacon indicating a browse for one of our services.
bool suppressBeacons = false;
// Go through all the existing browses and registrations to create the
// current Bloom filter value for the BLE beacon.
// Update the current scan and beaconing state appropriately.
mDNSlocal void updateBeaconAndScanState()
{
requestList_t *ptr;
serviceHash_t beaconBloomFilter = 0;
updateServiceTimer();
for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next)
{
beaconBloomFilter |= ptr->browseHash;
}
for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next)
{
beaconBloomFilter |= ptr->registeredHash;
}
// If only advertising registered services and not browsing, we don't start the beacon transmission
// until we receive a beacon from a peer matching one of our registrations.
if (BLERegistrationListHead && !BLEBrowseListHead && !responseMatchesRegistrations())
{
// If beacons are already suppressed, then no further action to take.
if (suppressBeacons)
LogInfo("updateBeaconAndScanState: continuing to suppressing beacons");
else
{
LogInfo("updateBeaconAndScanState: suppressing beacons, no peers currently seeking our services");
suppressBeacons = true;
// If currently beaconing, send a beacon for two seconds with a zeroed Bloom filter indicating we are
// no longer browsing for any services so that any matching auto triggered peer registrations have a
// chance to see our state change.
if (currentlyBeaconing())
updateBLEBeacon(0);
startBLEScan();
}
}
// If beacons had been suppressed and we no longer have services to advertise, no
// need to send a beacon with a zeroed Bloom filter for two seconds, just stop
// the scan.
else if (suppressBeacons == true && beaconBloomFilter == 0)
{
suppressBeacons = false;
stopBLEScan();
}
// Update the beacon with the current Bloom filter values.
else
{
suppressBeacons = false;
updateBLEBeacon(beaconBloomFilter);
// Scan unless the Bloom filter is zero, indicating we are not currently
// seeking or providing any services.
if (beaconBloomFilter)
startBLEScan();
else
stopBLEScan();
}
}
#pragma mark - Peer response handling
// Structure used to track the beacons received from various peers.
typedef struct responseList
{
struct responseList * next;
serviceHash_t peerBloomFilter;
mDNSs32 recievedTime;
mDNSEthAddr peerMac;
} responseList_t;
#define RESPONSE_LIST_NUMBER 8
static responseList_t* BLEResponseListHeads[RESPONSE_LIST_NUMBER];
// Return the address of the pointer to the entry, which can either be the address of the
// corresponding BLEResponseListHeads[] entry, or the address of the prior responseList_t entry
// on the lists "next" pointer.
mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC)
{
// Use the least significant byte of the MAC address as our hash index to find the list.
responseList_t **ptr = & BLEResponseListHeads[ptrToMAC->b[5] % RESPONSE_LIST_NUMBER];
for ( ; *ptr; ptr = &(*ptr)->next)
{
if (memcmp(&(*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr)) == 0)
break;
}
return ptr;
}
mDNSlocal responseList_t * addToResponseList(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
{
responseList_t **ptr = findInResponseList(ptrToMAC);
if (!*ptr)
{
*ptr = mDNSPlatformMemAllocate(sizeof(**ptr));
mDNSPlatformMemZero(*ptr, sizeof(**ptr));
(*ptr)->peerBloomFilter = peerBloomFilter;
memcpy(& (*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr));
}
return *ptr;
}
mDNSlocal void removeFromResponseList(mDNSEthAddr * ptrToMAC)
{
responseList_t **ptr = findInResponseList(ptrToMAC);
if (!*ptr)
{
LogMsg("removeFromResponseList: did not find entry for MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
return;
}
LogInfo("removeFromResponseList: removing entry for MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
responseList_t *tmp = *ptr;
*ptr = (*ptr)->next;
mDNSPlatformMemFree(tmp);
}
// Free all current entries on the BLE response lists, removing all pointers
// to freed structures from the lists.
mDNSlocal void clearResponseLists()
{
responseList_t **ptr;
for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
{
ptr = & BLEResponseListHeads[i];
while (*ptr)
{
responseList_t * tmp;
tmp = *ptr;
*ptr = (*ptr)->next;
mDNSPlatformMemFree(tmp);
}
}
}
// Check to see if we have cached a response that matches a service for which we just started a browse or registration.
mDNSlocal void checkCachedResponses(requestList_t *browse, requestList_t *registration)
{
responseList_t *ptr;
for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
{
for (ptr = BLEResponseListHeads[i]; ptr; ptr = ptr->next)
{
// For browses, we are looking for responses that have a matching registration
// and for registrations we are looking for responses that have a matching browse.
if ( (browse && (browse->registeredHash & ptr->peerBloomFilter) == browse->registeredHash)
|| (registration && (registration->browseHash & ptr->peerBloomFilter) == registration->browseHash))
{
// Clear the Bloom filter for the response.
// The next beacon from this peer will update the filter then autoTrigger
// any newly started client requests as appropriate.
ptr->peerBloomFilter = 0;
}
}
}
}
// Define a fixed name to use for the instance name denoting that one or more instances
// of a service are being advertised by peers in their BLE beacons.
// Name format is: length byte + bytes of name string + two byte pointer to the PTR record name.
// See compression_lhs definition in the D2D plugin code for background on 0xc027 DNS name compression pointer value.
static Byte *BLEinstanceValue = (Byte *) "\x11ThresholdInstance\xc0\x27";
#define BLEValueSize strlen((const char *)BLEinstanceValue)
// Find each local browse that matches the registered service hash in the BLE response.
// Called on the CFRunLoop thread while handling a callback from CoreBluetooth.
// Caller should hold KQueueLock().
mDNSlocal void findMatchingBrowse(responseList_t *response)
{
requestList_t *ptr;
ptr = BLEBrowseListHead;
for ( ; ptr; ptr = ptr->next)
{
// See if we potentially match a corresponding registration in the beacon.
// thus, compare using the "registeredHash" of our browse..
if ((ptr->registeredHash & response->peerBloomFilter) == ptr->registeredHash)
{
LogInfo("findMatchingBrowse: Registration in response matched browse for: %##s", ptr->name.c);
if (inResponseListForRequest(ptr, response))
{
LogInfo("findMatchingBrowse: Already on response list for browse: %##s", ptr->name.c);
continue;
}
else
{
LogInfo("findMatchingBrowse: Adding to response list for browse: %##s", ptr->name.c);
if (ptr->ourResponses == 0)
{
if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
{
LogInfo("findMatchingBrowse: First BLE response, triggering browse for %##s on AWDL", ptr->name.c);
// register with the AWDL D2D plugin,
internal_start_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
ptr->triggeredOnAWDL = true;
}
// Browse on mDNSInterface_BLE is used to determine if there are one or more instances of the
// service type discoveryed over BLE. If this is the first instance, add the psuedo instance defined by BLEinstanceValue.
if (ptr->InterfaceID == mDNSInterface_BLE)
{
xD2DAddToCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
}
}
addToResponseListForRequest(ptr, response);
}
}
else
{
// If a previous response from this peer had matched the browse, remove that response from the
// list now. If this is the last matching response, remove the corresponding key from the AWDL D2D plugin
if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0))
{
if (ptr->InterfaceID == mDNSInterface_BLE)
{
xD2DRemoveFromCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize);
}
if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
{
LogInfo("findMatchingBrowse: Last BLE response, disabling browse for %##s on AWDL", ptr->name.c);
internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
ptr->triggeredOnAWDL = false;
}
}
}
}
}
// Find each local registration that matches the service browse hash BLE response Bloom filter.
// Called on the CFRunLoop thread while handling a callback from CoreBluetooth.
// Caller should hold KQueueLock().
mDNSlocal void findMatchingRegistration(responseList_t *response)
{
requestList_t *ptr;
bool matchingPeer;
ptr = BLERegistrationListHead;
for ( ; ptr; ptr = ptr->next)
{
// See if we potentially match a corresponding browse in the beacon,
// thus, compare using the "browseHash" of our registration.
if ((ptr->browseHash & response->peerBloomFilter) == ptr->browseHash)
{
LogInfo("findMatchingRegistration: Incoming browse matched registration for: %##s", ptr->name.c);
if (inResponseListForRequest(ptr, response))
{
LogInfo("findMatchingRegistration: Already on response list for registration: %##s", ptr->name.c);
continue;
}
else
{
LogInfo("findMatchingRegistration: Adding to response list for registration: %##s", ptr->name.c);
// Also pass the registration to the AWDL D2D plugin if this is the first matching peer browse for
// an auto triggered local registration.
if ((ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
{
LogInfo("findMatchingRegistration: First BLE response, triggering registration for %##s on AWDL", ptr->name.c);
if (ptr->resourceRecord == 0)
{
LogInfo("findMatchingRegistration: resourceRecord pointer is NULL ??");
continue;
}
internal_start_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
// indicate the registration has been applied to the AWDL interface
ptr->triggeredOnAWDL = true;
}
addToResponseListForRequest(ptr, response);
}
}
else
{
// If a previous response from this peer had matched the browse, remove that response from the
// list now. If this is the last matching response for a local auto triggered registration,
// remove the advertised key/value pairs from the AWDL D2D plugin.
if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags))
{
LogInfo("findMatchingRegistration: Last BLE response, disabling registration for %##s on AWDL", ptr->name.c);
// Restore the saved ARType and call into the AWDL D2D plugin to stop the corresponding record advertisements over AWDL.
internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
ptr->triggeredOnAWDL = false;
}
}
}
// If beacons for our registrations had been suppressed, see if we now have a match and need to restart them.
matchingPeer = responseMatchesRegistrations();
if (suppressBeacons && matchingPeer)
{
LogInfo("findMatchingRegistration: peer searching for our service, starting beacon transmission");
updateBeaconAndScanState();
if (suppressBeacons == true)
LogInfo("findMatchingRegistration: NOTE: suppressBeacons is true after updateBeaconAndScanState() call ??");
}
// If we have only registrations, but no matching peers, we can suppress beacons until we get a matching peer beacon.
else if (!suppressBeacons && !matchingPeer && BLERegistrationListHead && !BLEBrowseListHead)
{
LogInfo("findMatchingRegistration: no peer beacons match our registrations, suppressing beacon transmission");
suppressBeacons = true;
stopBLEBeacon();
}
}
// Time limit before a beacon is aged out of our received list.
#define MAX_RESPONSE_AGE 10
// If we have responses from peers that are more than MAX_RESPONSE_AGE seconds
// old, remove them since a peer with active requests should be beaconing multiple
// times per second if still within BLE range.
mDNSlocal void removeStaleResponses(mDNSs32 currentTime)
{
responseList_t **ptr;
for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++)
{
ptr = & BLEResponseListHeads[i];
while (*ptr)
{
if ((currentTime - (*ptr)->recievedTime) > (MAX_RESPONSE_AGE * mDNSPlatformOneSecond))
{
responseList_t * tmp;
// Clear the Bloom filter so that it will be removed from any matching response list
// by the following calls.
(*ptr)->peerBloomFilter = 0;
LogInfo("removeStaleResponses: clearing stale response from peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
(*ptr)->peerMac.b[0], (*ptr)->peerMac.b[1], (*ptr)->peerMac.b[2], (*ptr)->peerMac.b[3], (*ptr)->peerMac.b[4], (*ptr)->peerMac.b[5]);
findMatchingBrowse(*ptr);
findMatchingRegistration(*ptr);
// Unlink and free the response structure
tmp = *ptr;
*ptr = (*ptr)->next;
mDNSPlatformMemFree(tmp);
}
// Move to the next response on this linked list.
else
ptr = & (*ptr)->next;
}
}
}
// Called on CFRunLoop thread during CoreBluetooth beacon response processing.
// Thus, must call KQueueLock() prior to calling any core mDNSResponder routines to register records, etc.
void responseReceived(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC)
{
responseList_t * ptr;
KQueueLock();
mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow
ptr = *(findInResponseList(ptrToMAC));
if (ptr == 0)
{
// Only add to list if peer is actively browsing or advertising.
if (peerBloomFilter)
{
LogInfo("responseReceived: First beacon of this type, adding to list");
LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter);
LogInfo("responseReceived: peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
ptr = addToResponseList(peerBloomFilter, ptrToMAC);
// Update the received time.
ptr->recievedTime = mDNSStorage.timenow;
// See if we are browsing for any of the peers advertised services.
findMatchingBrowse(ptr);
// See if we have a registration that matches the peer's browse.
findMatchingRegistration(ptr);
}
}
else // Have an entry from this MAC in the list.
{
// Update the received time.
ptr->recievedTime = mDNSStorage.timenow;
if (ptr->peerBloomFilter == peerBloomFilter)
{
// A duplicate of a current entry.
#if VERBOSE_BLE_DEBUG
LogInfo("responseReceived: Duplicate of previous beacon, ignoring");
LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
#endif // VERBOSE_BLE_DEBUG
}
else
{
LogInfo("responseReceived: Update of previous beacon");
LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter);
LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]);
ptr->peerBloomFilter = peerBloomFilter;
findMatchingBrowse(ptr);
findMatchingRegistration(ptr);
}
// If peer is no longer browsing or advertising, remove from list.
if (peerBloomFilter == 0)
{
LogInfo("responseReceived: Removing peer entry from the list");
removeFromResponseList(ptrToMAC);
}
}
mDNS_Unlock(& mDNSStorage); // Calling mDNS_Unlock is what gives m->NextScheduledEvent its new value
KQueueUnlock("BLE responseReceived");
}
#pragma mark - Client request handling
void start_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags, mDNSu8 *key, size_t keySize)
{
requestList_t * ptr;
const domainname *serviceType = domain;
if (!EnableBLEBasedDiscovery)
{
LogMsg("start_BLE_browse: EnableBLEBasedDiscovery disabled");
return;
}
if (keySize > MAX_KEY_SIZE)
{
LogMsg("start_BLE_browse: keySize = %d, maximum allowable is %d", keySize, MAX_KEY_SIZE);
return;
}
// Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for
// BLE threshold browsing.
if (!isAutoTriggerRequest(InterfaceID, flags) && (InterfaceID != mDNSInterface_BLE))
{
LogMsg("start_BLE_browse: invalid request: InterfaceID = %d, flags = 0x%x", InterfaceID, flags);
return;
}
// Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin.
if ((type == kDNSType_A) || (type == kDNSType_AAAA))
{
LogInfo("start_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type));
internal_start_browsing_for_service(InterfaceID, domain, type, flags);
return;
}
// Skip the instance to get to the service type for non PTR records
if (type != kDNSType_PTR)
serviceType = SkipLeadingLabels(domain, 1);
LogInfo("start_BLE_browse: Starting BLE service type browse for: %##s %s", domain->c, DNSTypeName(type));
ptr = addToRequestList(&BLEBrowseListHead, domain, type, flags);
// If equivalent BLE browse is already running, just return.
if (ptr->refCount > 1)
{
LogInfo("start_BLE_browse: Dup of existing BLE browse.");
return;
}
if (!setBLEServiceHash(serviceType, ptr))
{
LogInfo("setBLEServiceHash failed!");
removeFromRequestList(&BLEBrowseListHead, domain, type);
return;
}
// Save these for use in D2D plugin callback logic.
memcpy(ptr->key, key, keySize);
ptr->keySize = keySize;
ptr->InterfaceID = InterfaceID;
mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow.
updateBeaconAndScanState();
mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
checkCachedResponses(ptr, NULL);
}
// Stop the browse.
// Return true if this is the last reference to the browse, false otherwise.
bool stop_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
(void) flags; // not used initially
requestList_t * ptr;
bool lastReference = false;
if (!EnableBLEBasedDiscovery)
{
LogMsg("stop_BLE_browse: EnableBLEBasedDiscovery disabled");
return lastReference;
}
// Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin.
if ((type == kDNSType_A) || (type == kDNSType_AAAA))
{
LogInfo("stop_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type));
internal_stop_browsing_for_service(InterfaceID, domain, type, flags);
return lastReference;
}
LogInfo("stop_BLE_browse: Stopping BLE service type browse for: %##s %s", domain->c, DNSTypeName(type));
ptr = *(findInRequestList(&BLEBrowseListHead, domain, type));
if (ptr == 0)
{
LogInfo("stop_BLE_browse: No matching browse found.");
return lastReference;
}
// If this is the last reference for this browse, and it was autoTriggered on AWDL,
// remove the request from the AWDL pluggin.
if (ptr->refCount == 1)
{
lastReference = true;
if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL)
{
internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags);
ptr->triggeredOnAWDL = false;
}
}
removeFromRequestList(&BLEBrowseListHead, domain, type);
mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow.
if (lastReference)
updateBeaconAndScanState();
mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
// If there are no active browse or registration requests, BLE scanning will be disabled.
// Clear the list of responses received to remove any stale response state.
if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0)
clearResponseLists();
return lastReference;
}
void start_BLE_advertise(const ResourceRecord *const resourceRecord, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
requestList_t * ptr;
const domainname * serviceType = domain;
if (!EnableBLEBasedDiscovery)
{
LogMsg("start_BLE_advertise: EnableBLEBasedDiscovery disabled");
return;
}
if (resourceRecord == NULL)
{
LogInfo("start_BLE_advertise: NULL resourceRecord for: %##s %s, returning", domain->c, DNSTypeName(type));
return;
}
// Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for
// BLE threshold browsing.
if (!isAutoTriggerRequest(resourceRecord->InterfaceID, flags) && (resourceRecord->InterfaceID != mDNSInterface_BLE))
{
LogMsg("start_BLE_advertise: invalid request: InterfaceID = %d, flags = 0x%x", resourceRecord->InterfaceID, flags);
return;
}
LogInfo("start_BLE_advertise: Starting BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type));
// Skip the instance to get to the service type for non PTR records
if (type != kDNSType_PTR)
serviceType = SkipLeadingLabels(domain, 1);
ptr = addToRequestList(&BLERegistrationListHead, domain, type, flags);
// If equivalent BLE registration is already running, just return.
if (ptr->refCount > 1)
{
LogInfo("start_BLE_advertise: Dup of existing BLE advertisement.");
return;
}
if (!setBLEServiceHash(serviceType, ptr))
{
LogInfo("setBLEServiceHash failed!");
removeFromRequestList(&BLERegistrationListHead, domain, type);
return;
}
ptr->resourceRecord = resourceRecord;
ptr->InterfaceID = resourceRecord->InterfaceID;
mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow.
updateBeaconAndScanState();
mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
checkCachedResponses(NULL, ptr);
}
void stop_BLE_advertise(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags)
{
(void) flags; // not used initially
requestList_t * ptr;
bool lastReference = false;
LogInfo("stop_BLE_advertise: Stopping BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type));
// Get the request pointer from the indirect pointer returned.
ptr = *(findInRequestList(&BLERegistrationListHead, domain, type));
if (ptr == 0)
{
LogInfo("stop_BLE_advertise: No matching advertisement found.");
return;
}
// If this is the last reference for this registration, and it was autoTriggered on AWDL,
// remove the request from the AWDL pluggin.
if (ptr->refCount == 1)
{
lastReference = true;
if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL)
{
// And remove the corresponding advertisements from the AWDL D2D plugin if we had previously
// passed this advertisement request to the plugin.
internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL));
ptr->triggeredOnAWDL = false;
}
}
removeFromRequestList(&BLERegistrationListHead, domain, type);
mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow.
// If this is the last reference for this registration, update advertising and browsing bits set in the beacon.
if (lastReference)
updateBeaconAndScanState();
mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent.
// If there are no active browse or registration requests, BLE scanning will be disabled.
// Clear the list of responses received to remove any stale response state.
if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0)
clearResponseLists();
}
#ifdef UNIT_TEST
#pragma mark - Unit test support routines
// These unit test support routines are called from unittests/ framework
// and are not compiled for the mDNSResponder runtime code paths.
#define MAX_ENTRIES 42
#define FAILED exit(1)
mDNSlocal void BLE_requestListTests(void)
{
const domainname *domainArray[] = { (const domainname*)"\x6" "_test0" "\x4" "_tcp" "\x5" "local",
(const domainname*)"\x6" "_test1" "\x4" "_tcp" "\x5" "local",
(const domainname*)"\x6" "_test2" "\x4" "_tcp" "\x5" "local",
(const domainname*)"\x6" "_test3" "\x4" "_tcp" "\x5" "local",
(const domainname*)"\x6" "_test4" "\x4" "_tcp" "\x5" "local",
};
mDNSu16 type = kDNSServiceType_PTR;
DNSServiceFlags flags = 0;
requestList_t * ptr;
void * response = 0;
int i;
int numOfdomains = sizeof(domainArray)/sizeof(domainArray[0]);
printf("BLE_requestListTests() entry:\n");
// Basic request list unit tests.
for (i = 0; i < numOfdomains; i++)
{
ptr = addToRequestList(&BLEBrowseListHead, domainArray[i], type, flags);
if (ptr == NULL)
{
printf("addToRequestList() FAILED:\n");
FAILED;
}
}
for (i = 0; i < numOfdomains; i++)
{
// should now find the entry
if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], type)) == 0)
{
printf("findInRequestList() did not find valid entry FAILED:\n");
FAILED;
}
// but not find an entry with the same domain, but different type
if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], kDNSServiceType_NULL)) != 0)
{
printf("findInRequestList() invalid entry matched FAILED:\n");
FAILED;
}
}
// remove all the entries
for (i = 0; i < numOfdomains; i++)
{
removeFromRequestList(&BLEBrowseListHead, domainArray[i], type);
}
// and sanity check the list is now empty
if (BLEBrowseListHead)
{
printf("BLEBrowseListHead not empty after all entries removed.\n");
FAILED;
}
// Identical request reference count management tests.
// Add identical requests to the list and verify the corresponding refCount is managed correctly
for (i = 0; i < MAX_ENTRIES; i++)
{
ptr = addToRequestList(&BLEBrowseListHead, domainArray[0], type, flags);
if (ptr == NULL)
{
printf("addToRequestList() of duplicate request FAILED:\n");
FAILED;
}
}
if (ptr->refCount != MAX_ENTRIES)
{
printf("refCount = %d, should be %d\n", ptr->refCount, MAX_ENTRIES);
FAILED;
}
// Remove all but one entry
for (i = 0; i < (MAX_ENTRIES - 1); i++)
{
removeFromRequestList(&BLEBrowseListHead, domainArray[0], type);
}
if (ptr->refCount != 1)
{
printf("refCount = %d, should be %d\n", ptr->refCount, 1);
FAILED;
}
// Basic response list unit tests.
// Note that responses per request are not checked for duplicates at this level, so
// we can unit test with the same (NULL) response pointer to add multiple responses.
// add MAX_ENTRIES responses
for (i = 0; i < MAX_ENTRIES; i++)
addToResponseListForRequest(ptr, response);
// remove the responses, counting that MAX_ENTRIES were removed
i = 0;
while (inResponseListForRequest(ptr, response) && removeFromResponseListForRequest(ptr, response))
{
i++;
}
if (i != MAX_ENTRIES)
{
printf("removed %d responses, should have been %d\n", i, MAX_ENTRIES);
FAILED;
}
// response list should be empty at this point
if (ptr->ourResponses)
{
printf("response list should be empty\n");
FAILED;
}
// add MAX_ENTRIES responses
for (i = 0; i < MAX_ENTRIES; i++)
addToResponseListForRequest(ptr, response);
// free them all
freeResponseListEntriesForRequest(ptr);
if (ptr->ourResponses)
{
printf("freeResponseListEntriesForRequest() should have removed all responses\n");
FAILED;
}
}
mDNSlocal mDNSEthAddr etherAddress[] = {
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e } },
{ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f } },
};
mDNSlocal void BLE_responseListTests(void)
{
int numOfEtherAddresses = sizeof(etherAddress)/sizeof(etherAddress[0]);
int i;
printf("BLE_responseListTests() entry:\n");
// Just use the index as to generate the peerBloomFilter value to vary it per entry.
for (i = 0; i < numOfEtherAddresses; i++)
(void)addToResponseList(1 << i, ð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