mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-06-12 18:23:46 +08:00

The sources can be obtained via: https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-878.1.1.tar.gz Update #3522.
1493 lines
53 KiB
C
1493 lines
53 KiB
C
/* -*- 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
|