diff options
Diffstat (limited to 'mDNSResponder/mDNSMacOSX/BLE.c')
-rw-r--r-- | mDNSResponder/mDNSMacOSX/BLE.c | 1369 |
1 files changed, 1004 insertions, 365 deletions
diff --git a/mDNSResponder/mDNSMacOSX/BLE.c b/mDNSResponder/mDNSMacOSX/BLE.c index 85fb810c..4a35ff0b 100644 --- a/mDNSResponder/mDNSMacOSX/BLE.c +++ b/mDNSResponder/mDNSMacOSX/BLE.c @@ -15,23 +15,134 @@ * limitations under the License. */ +#if ENABLE_BLE_TRIGGERED_BONJOUR + #include "mDNSEmbeddedAPI.h" #include "DNSCommon.h" #include "mDNSMacOSX.h" #include "BLE.h" -#include <pthread.h> +#include "D2D.h" + +#include <dlfcn.h> #pragma mark - Browse and Registration Request Handling -// Disable use of BLE discovery APIs by default. +// When set, enables BLE triggered discovery APIs. mDNSBool EnableBLEBasedDiscovery = mDNSfalse; +// When set, the default mode is to promote all client requests made with +// kDNSServiceInterfaceIndexAny to BLE Triggered Discovery. +// Requests to promote will be filtered by either a service type whitelist or +// blacklist as noted below. +mDNSBool DefaultToBLETriggered = mDNSfalse; + +#define USE_WHITELIST 1 + +#if USE_WHITELIST + +// Current list of service types that will have BLE triggers applied by default +// when DefaultToBLETriggered is set to true. + +const char * defaultServiceWhitelist[] = { + "\x04_ssh", + "\x04_smb", + "\x04_rfb", + "\x04_ipp", + "\x05_ipps", + "\x08_printer", + 0 +}; + +// Return true if DefaultToBLETriggered is set and the operation should be +// promoted to use BLE triggered discovery by default. +bool shouldUseBLE(mDNSInterfaceID interfaceID, DNS_TypeValues rrtype, domainname *serviceType, domainname *domain) +{ + const mDNSu8 ** ptr; + + if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain)) + return mDNSfalse; + + // Address records don't have a service type to match on, but we'll trigger them + // here to support the case were the DNSServiceQueryRecord() was done using mDNSInterface_Any instead + // of the interface that the corresponding SRV record was returned over. + if ((rrtype == kDNSType_A) || (rrtype == kDNSType_AAAA)) + return mDNStrue; + + ptr = (const mDNSu8 **) defaultServiceWhitelist; + while (*ptr) + { + if (SameDomainLabel(*ptr, serviceType->c)) + return mDNStrue; + ptr++; + } + + return mDNSfalse; +} + +#else // USE_WHITELIST + +// Current list of service types that will NOT have BLE triggers applied by default +// when DefaultToBLETriggered is set to true. + +// _airplay and _airdrop discovery already employ BLE based triggering using Apple service specific +// BLE beacons. The rest of the entries here are default browses run in a standard OSX install +// that we don't want to have cluttering up the Bloom filter when using the service blacklist approach. + +const char * defaultServiceBlacklist[] = { + "\x08_airplay", + "\x08_airdrop", + "\x05_raop", + "\x08_airport", + "\x0d_apple-mobdev", + "\x06_uscan", + "\x07_uscans", + "\x08_scanner", + "\x0e_apple-mobdev2", + "\x04_ipp", + "\x05_ipps", + "\x07_ippusb", + "\x08_printer", + "\x0f_pdl-datastream", + "\x04_ptp", + 0 +}; + +// Return true if DefaultToBLETriggered is set and the operation should be +// promoted to use BLE triggered discovery by default. +bool shouldUseBLE(mDNSInterfaceID interfaceID, DNS_TypeValues rrtype, domainname *serviceType, domainname *domain) +{ + (void) rrtype; + const mDNSu8 ** ptr; + + if (!DefaultToBLETriggered || (interfaceID != mDNSInterface_Any) || !IsLocalDomain(domain)) + return mDNSfalse; + + ptr = (const mDNSu8 **) defaultServiceBlacklist; + while (*ptr) + { + if (SameDomainLabel(*ptr, serviceType->c)) + return mDNSfalse; + ptr++; + } + + return mDNStrue; +} + +#endif // USE_WHITELIST + +// Structure for linked list of BLE responses received that match +// a given client request. typedef struct matchingResponses { struct matchingResponses * next; void * response; } matchingResponses_t; +// Max size of input key generated by DNSNameCompressionBuildLHS() is MAX_DOMAIN_NAME + 3 +// where the three additional bytes are: +// two bytes for DNS_TypeValues and one byte for "compression_packet_v1", the D2D compression version number. +#define MAX_KEY_SIZE MAX_DOMAIN_NAME + 3 + // Initially used for both the browse and registration lists. typedef struct requestList { @@ -41,22 +152,17 @@ typedef struct requestList mDNSu16 type; DNSServiceFlags flags; mDNSInterfaceID InterfaceID; - -// TODO: Possibly restructure the following browse and registration specific -// members as a union to save a bit of space. + serviceHash_t browseHash; + serviceHash_t registeredHash; + matchingResponses_t * ourResponses; + bool triggeredOnAWDL; // The following fields are only used for browse requests currently - serviceHash_t browseHash; - DNSQuestion * question; - mDNSu8 key[MAX_DOMAIN_LABEL]; + mDNSu8 key[MAX_KEY_SIZE]; size_t keySize; - matchingResponses_t * ourResponses; // The following fields are only used for registration requests currently - serviceHash_t registeredHash; - ServiceRecordSet * serviceRecordSet; // service record set in the original request - AuthRecType savedARType; - bool triggeredOnAWDL; + const ResourceRecord * resourceRecord; } requestList_t; // Lists for all DNSServiceBrowse() and DNSServiceRegister() requests using @@ -64,10 +170,27 @@ typedef struct requestList static requestList_t* BLEBrowseListHead = NULL; static requestList_t* BLERegistrationListHead = NULL; -#define isAutoTriggerRequest(ptr) ((ptr->InterfaceID == kDNSServiceInterfaceIndexAny) && (ptr->flags & kDNSServiceFlagsAutoTrigger)) +// The kDNSServiceFlagsAutoTrigger should only be set for a request that would normally apply to AWDL. +#define isAutoTriggerRequest(INTERFACE_INDEX, FLAGS) ( (FLAGS & kDNSServiceFlagsAutoTrigger) \ + && ( (AWDLInterfaceID && (INTERFACE_INDEX == AWDLInterfaceID)) \ + || ((INTERFACE_INDEX == kDNSServiceInterfaceIndexAny) && (FLAGS & kDNSServiceFlagsIncludeAWDL)))) #pragma mark - Manage list of responses that match this request. +// Return true if any response matches one of our current registrations. +mDNSlocal bool responseMatchesRegistrations(void) +{ + requestList_t *ptr; + + for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next) + { + if (ptr->ourResponses) + return true; + } + return false; +} + +// Return true if the response is already in the list of responses for this client request. mDNSlocal bool inResponseListForRequest(requestList_t *request, void * response) { matchingResponses_t * rp; @@ -130,10 +253,13 @@ mDNSlocal void freeResponseListEntriesForRequest(requestList_t *request) ptr = ptr->next; free(tmp); } + request->ourResponses = 0; } #pragma mark - Manage request lists +// Return the address of the pointer to the entry, which can either be the address of "listHead" +// or the address of the prior entry on the lists "next" pointer. mDNSlocal requestList_t ** findInRequestList(requestList_t ** listHead, const domainname *const name, mDNSu16 type) { requestList_t **ptr = listHead; @@ -185,332 +311,383 @@ mDNSlocal void removeFromRequestList(requestList_t ** listHead, const domainname #pragma mark - Hashing and beacon state -// Simple string hash based on the Bernstein hash. - -#define PRIME 31 // small prime number -#define MODULO (sizeof(serviceHash_t) * 8) -#define CONVERT_TO_LOWER_CASE(x) (((x) <= 'Z' && (x) >= 'A') ? ((x) | 0x20) : (x)) - -mDNSlocal serviceHash_t BLELabelHash(const unsigned char *str, unsigned int length) +// These SipHash routines were copied from CoreUtils-500.9. +// We use these when running an mDNSRespnder root on a system that does not +// have the SipHash() routine available and exported in CoreUtils. +// TODO: This local copy should be removed once we are no longer running mDNSResponder roots +// on systems that do no include CoreUtils-500.9 or newer. + +// Start of code copied from: CoreUtils-500.9 + +/*! @group BitRotates + @abstract Rotates X COUNT bits to the left or right. +*/ +#define ROTL( X, N, SIZE ) ( ( (X) << (N) ) | ( (X) >> ( (SIZE) - N ) ) ) +#define ROTR( X, N, SIZE ) ( ( (X) >> (N) ) | ( (X) << ( (SIZE) - N ) ) ) + +#define ROTL64( X, N ) ROTL( (X), (N), 64 ) +#define ROTR64( X, N ) ROTR( (X), (N), 64 ) + + #define ReadLittle64( PTR ) \ + ( (uint64_t)( \ + ( (uint64_t)( (uint8_t *)(PTR) )[ 0 ] ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 1 ] ) << 8 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 2 ] ) << 16 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 3 ] ) << 24 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 4 ] ) << 32 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 5 ] ) << 40 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 6 ] ) << 48 ) | \ + ( ( (uint64_t)( (uint8_t *)(PTR) )[ 7 ] ) << 56 ) ) ) + +// Based on <https://131002.net/siphash/>. + +#define SipRound() \ + do \ + { \ + v0 += v1; v1 = ROTL64( v1, 13 ); v1 ^= v0; v0 = ROTL64( v0, 32 ); \ + v2 += v3; v3 = ROTL64( v3, 16 ); v3 ^= v2; \ + v0 += v3; v3 = ROTL64( v3, 21 ); v3 ^= v0; \ + v2 += v1; v1 = ROTL64( v1, 17 ); v1 ^= v2; v2 = ROTL64( v2, 32 ); \ + \ + } while( 0 ) + +mDNSlocal uint64_t local_SipHash( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen ) { - serviceHash_t hash = 0; - - for (unsigned int i = 0; i < length; i++) { - hash = PRIME * hash + CONVERT_TO_LOWER_CASE(*str); - str++; - } - - hash %= MODULO; - LogInfo("BLELabelHash: %d characters hashed to %d", length, hash); - - return ((serviceHash_t)1 << hash); + const uint8_t * src = (const uint8_t *) inSrc; + size_t const left = inLen % 8; + const uint8_t * const end = src + ( inLen - left ); + uint64_t k0, k1, v0, v1, v2, v3, tmp; + + k0 = ReadLittle64( &inKey[ 0 ] ); + k1 = ReadLittle64( &inKey[ 8 ] ); + v0 = k0 ^ UINT64_C( 0x736f6d6570736575 ); // 'somepseu' + v1 = k1 ^ UINT64_C( 0x646f72616e646f6d ); // 'dorandom' + v2 = k0 ^ UINT64_C( 0x6c7967656e657261 ); // 'lygenera' + v3 = k1 ^ UINT64_C( 0x7465646279746573 ); // 'tedbytes' + + for( ; src != end; src += 8 ) + { + tmp = ReadLittle64( src ); + v3 ^= tmp; + SipRound(); + SipRound(); + v0 ^= tmp; + } + + tmp = ( (uint64_t)( inLen & 0xFF ) ) << 56; + switch( left ) + { + case 7: tmp |= ( ( (uint64_t) src[ 6 ] ) << 48 ); + case 6: tmp |= ( ( (uint64_t) src[ 5 ] ) << 40 ); + case 5: tmp |= ( ( (uint64_t) src[ 4 ] ) << 32 ); + case 4: tmp |= ( ( (uint64_t) src[ 3 ] ) << 24 ); + case 3: tmp |= ( ( (uint64_t) src[ 2 ] ) << 16 ); + case 2: tmp |= ( ( (uint64_t) src[ 1 ] ) << 8 ); + case 1: tmp |= ( (uint64_t) src[ 0 ] ); + default: break; + } + v3 ^= tmp; + SipRound(); + SipRound(); + v0 ^= tmp; + v2 ^= 0xFF; + SipRound(); + SipRound(); + SipRound(); + SipRound(); + return( v0 ^ v1 ^ v2 ^ v3 ); } -// Hash just the service type not including the protocol or first "_" character initially. -mDNSlocal serviceHash_t BLEServiceHash(const domainname *const domain) -{ - const unsigned char *p = domain->c; - unsigned int length = (unsigned int) *p; - - p++; - if (*p != '_') - { - LogInfo("BLEServiceHash: browse type does not begin with a _"); - return 0; - } - p++; // skip the '-" - length--; +// See <https://spc.apple.com/AppleBLEInfo.html#_wifi_tds> for details. - if (length > MAX_DOMAIN_LABEL || length == 0) - { - LogInfo("BLEServiceHash: invalid browse type length: %d characters", length); - return 0; - } +#define kTDSSipHashKey ( (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" ) +#define kTDSSipHashCount 5 - return BLELabelHash(p, length); -} +#define kSizeCString ( (size_t) -1 ) -// Storage for the current Bonjour BLE beacon data; -typedef struct BLEBeacon -{ - serviceHash_t browseHash; - serviceHash_t registeredHash; -} BLEBeacon_t; +// End of code copied from: CoreUtils-500.9 -BLEBeacon_t BLEBeacon; +// Must link symbol from CoreUtils at runtime to avoid cyclic dependency cycles in the build process. +static uint64_t (*SipHash_p)( const uint8_t inKey[ 16 ], const void *inSrc, size_t inLen ) = NULL; -mDNSlocal void addServiceToBeacon(serviceHash_t browseHash, serviceHash_t registeredHash) +mDNSlocal uint64_t local_TDSBloomFilterMake( uint32_t inBloomCount, const void *inStr, size_t inLen ) { - bool beaconUpdated = false; + uint64_t bloomFilter = 0, hash; + uint8_t i; - if (BLEBeacon.browseHash & browseHash) - { - LogInfo("addServiceToBeacon: Bit 0x%x already set in browsing services hash", browseHash); - } + if( inLen == kSizeCString ) inLen = strlen( (const char *) inStr ); + if (SipHash_p) + hash = SipHash_p( kTDSSipHashKey, inStr, inLen ); else - { - BLEBeacon.browseHash |= browseHash; - beaconUpdated = true; - } + hash = local_SipHash( kTDSSipHashKey, inStr, inLen ); - if (BLEBeacon.registeredHash & registeredHash) - { - LogInfo("addServiceToBeacon: Bit 0x%x already set in advertising services hash", registeredHash); - } - else + for( i = 0; i < kTDSSipHashCount; ++i ) { - BLEBeacon.registeredHash |= registeredHash; - beaconUpdated = true; + bloomFilter |= ( UINT64_C( 1 ) << ( hash % inBloomCount ) ); + hash /= inBloomCount; } - - if (beaconUpdated) - updateBLEBeaconAndScan(BLEBeacon.browseHash, BLEBeacon.registeredHash); + return( bloomFilter ); } -// Go through all the existing browses and registrations to get the -// current hash values for the corresponding BLE beacon. -// We must do this when any hash bits are removed do accurately generate -// the correct combination of all currently set hash bits. -mDNSlocal void updateBeacon() +mDNSlocal void loadCoreUtils() { - requestList_t *ptr; + static mDNSBool runOnce = mDNSfalse; + static void *CoreUtils_p = mDNSNULL; + static const char path[] = "/System/Library/PrivateFrameworks/CoreUtils.framework/CoreUtils"; - BLEBeacon.browseHash = 0; - BLEBeacon.registeredHash = 0; - - for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next) + if (!runOnce) { - BLEBeacon.browseHash |= ptr->browseHash; - } + runOnce = mDNStrue; + if (!CoreUtils_p) + { + CoreUtils_p = dlopen(path, RTLD_LAZY | RTLD_LOCAL); + if (!CoreUtils_p) + { + LogInfo("loadCoreUtils: dlopen() failed."); + return; + } + } - for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next) - { - BLEBeacon.registeredHash |= ptr->registeredHash; + if (!SipHash_p) + { + SipHash_p = dlsym(CoreUtils_p, "SipHash"); + if (!SipHash_p) + { + LogInfo("loadCoreUtils: load of SipHash symbol failed."); + return; + } + } + LogInfo("loadCoreUtils: found SipHash symbol."); } +} + +#define HASH_SIZE 64 + +mDNSlocal serviceHash_t BLELabelHash(unsigned char *str, unsigned int length) +{ + loadCoreUtils(); - updateBLEBeaconAndScan(BLEBeacon.browseHash, BLEBeacon.registeredHash); + return local_TDSBloomFilterMake(HASH_SIZE, (const void *) str, (size_t) length); } -#pragma mark - Request start/stop -// Forward declarations for mDNSLocal functions that are called before they are defined. -mDNSlocal void checkForMatchingResponses(requestList_t *bp); -mDNSlocal void clearResponseLists(); +// Maximum number of characters in string to hash should be: +// 2 for initial "s:" or "p:" +// 16 for "_" followed by up to 15 characters of service type +// 1 for separating "." +// 4 for "_udp" or "_tcp" +// 1 for the terminating NULL byte +#define MAX_HASH_STRING (2 + 16 + 1 + 4 + 1) + +// Maximum service name length, including the initial "_" +#define MAX_SERVICE_NAME 16 -void start_BLE_browse(DNSQuestion * q, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags, mDNSu8 *key, size_t keySize) +// Convert the service name and transport protocol to a NULL terminated C string. +// stringBuf must point to least (MAX_HASH_STRING - 2) bytes of available space. +mDNSlocal bool serviceNameStringFromDomain(const domainname *const domain, mDNSu8 * stringBuf) { - requestList_t * ptr; + mDNSu8 * dst = stringBuf; + const mDNSu8 * src = domain->c; + mDNSu8 len = *src++; - if (!EnableBLEBasedDiscovery) + if (len == 0 || len > MAX_SERVICE_NAME) { - LogMsg("start_BLE_browse: EnableBLEBasedDiscovery disabled"); - return; + LogInfo("serviceNameStringFromDomain: Invalid name lenght: %d", len); + return false; } - - LogInfo("start_BLE_browse: Starting BLE browse for: %##s %s", domain->c, DNSTypeName(type)); - - ptr = addToRequestList(&BLEBrowseListHead, domain, type, flags); - - // If equivalent BLE browse is already running, just return. - if (ptr->refCount > 1) + if (*src != '_') { - LogInfo("start_BLE_browse: Dup of existing BLE browse."); - return; + LogInfo("serviceNameStringFromDomain: service name does not begin with a _"); + return false; } + // Copy the service type + while (len--) + *dst++ = *src++; - ptr->browseHash = BLEServiceHash(domain); - ptr->question = q; + *dst++ = '.'; - if (ptr->browseHash == 0) + if (!ValidTransportProtocol(src)) { - LogInfo("BLEServiceHash failed!"); - removeFromRequestList(&BLEBrowseListHead, domain, type); - return; + LogInfo("serviceNameStringFromDomain: Transport protocol name must be _udp or _tcp"); + return false; } + // copy the transport protocol + len = *src++; + while (len--) + *dst++ = *src++; - // Save these for use in D2D plugin callback logic. - memcpy(ptr->key, key, keySize); - ptr->keySize = keySize; - // Extract the interface ID for easier access in the requestList_t structure - ptr->InterfaceID = q->InterfaceID; - - addServiceToBeacon(ptr->browseHash, 0); - - checkForMatchingResponses(ptr); + *dst = 0; + return true; } -// Stop the browse. -// Return true if this is the last reference to the browse, false otherwise. -bool stop_BLE_browse(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +mDNSlocal bool setBLEServiceHash(const domainname *const domain, requestList_t * ptr) { - (void) flags; // not used initially - requestList_t * ptr; - bool lastReference = false; + // Initialize the string with the "s:" for the browser/seeker hash calculation. + mDNSu8 stringBuf[MAX_HASH_STRING] = { 's', ':', '\0' }; - if (!EnableBLEBasedDiscovery) + // Append the service name and protocol strings to the initial "s:" string. + if (!serviceNameStringFromDomain(domain, &stringBuf[2])) { - LogMsg("stop_BLE_browse: EnableBLEBasedDiscovery disabled"); - return lastReference; + LogInfo("setBLEServiceHash: serviceNameStringFromDomain() failed!"); + return false; } - LogInfo("stop_BLE_browse: Stopping BLE browse for: %##s %s", domain->c, DNSTypeName(type)); - - ptr = *(findInRequestList(&BLEBrowseListHead, domain, type)); - if (ptr == 0) - { - LogInfo("stop_BLE_browse: No matching browse found."); - return lastReference; - } - - // If this is the last reference for this browse, update advertising and browsing bits set in - // the beacon after removing this browse from the list. - if (ptr->refCount == 1) - lastReference = true; + ptr->browseHash = BLELabelHash(stringBuf, strlen((const char *)stringBuf)); + LogInfo("setBLEServiceHash: seeker string %s, hashed to 0x%lx", stringBuf, ptr->browseHash); - removeFromRequestList(&BLEBrowseListHead, domain, type); + // Update string to start with "p:" for registration/provider hash calculation. + stringBuf[0] = 'p'; - if (lastReference) - updateBeacon(); + ptr->registeredHash = BLELabelHash(stringBuf, strlen((const char *)stringBuf)); + LogInfo("setBLEServiceHash: provider string %s, hashed to 0x%lx", stringBuf, ptr->registeredHash); + if (ptr->browseHash && ptr->registeredHash) + return true; + else + return false; +} - // If there are no active browse or registration requests, BLE scanning will be disabled. - // Clear the list of responses received to remove any stale response state. - if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0) - clearResponseLists(); +// Indicates we are sending the final beacon with zeroed Bloom filter to let +// peers know we are no longer actively seeking or providing any services. +bool finalBeacon = false; - return lastReference; -} +// The last time we walked our response list looking for stale entries. +mDNSs32 lastScanForStaleResponses; -extern void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, DNSQuestion * q); -extern void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags); +// Forward declaration. +mDNSlocal void removeStaleResponses(mDNSs32 currentTime); -extern void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags); -extern void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags); +// Interval at which we scan the response lists to remove any stale entries. +#define StaleResponseScanInterval 30 -void start_BLE_advertise(ServiceRecordSet * serviceRecordSet, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +// Called from mDNS_Execute() when NextBLEServiceTime is reached. +void serviceBLE(void) { - requestList_t * ptr; - const domainname * instanceRemoved; + // Note, we can access mDNSStorage.timenow since we are called from mDNS_Execute, + // which initializes that value by calling mDNS_Lock(). + mDNSs32 currentTime = mDNSStorage.timenow; - if (!EnableBLEBasedDiscovery) - { - LogMsg("start_BLE_advertise: EnableBLEBasedDiscovery disabled"); - return; - } - - // Just process the SRV record for each service registration. The PTR - // record already has the service type at the beginning of the domain, but - // we want to filter out reverse address PTR records at this point in time, so using - // the SRV record instead. - if (type != kDNSServiceType_SRV) - return; + // Initialize if zero. + if (!lastScanForStaleResponses) + lastScanForStaleResponses = NonZeroTime(currentTime - (StaleResponseScanInterval * mDNSPlatformOneSecond)); - if (serviceRecordSet == NULL) + if (finalBeacon) { - LogInfo("start_BLE_advertise: NULL service record set for: %##s %s, returning", domain->c, DNSTypeName(type)); - return; - } - LogInfo("start_BLE_advertise: Starting BLE advertisement for: %##s %s", domain->c, DNSTypeName(type)); + // We don't expect to do the finalBeacon processing if there are active browse requests, + if (BLEBrowseListHead) + LogInfo("serviceBLE: finalBeacon set and called with active browse BLE requests ??"); - instanceRemoved = SkipLeadingLabels(domain, 1); + // or active registrations but we are not in suppress beacons state. + if (BLERegistrationListHead && !suppressBeacons) + LogInfo("serviceBLE: finalBeacon set and called with active registrations requests, but not in suppress beacons state ??"); - ptr = addToRequestList(&BLERegistrationListHead, instanceRemoved, type, flags); + finalBeacon = false; + stopBLEBeacon(); + } - // If equivalent BLE registration is already running, just return. - if (ptr->refCount > 1) + if (!BLEBrowseListHead && !BLERegistrationListHead) { - LogInfo("start_BLE_advertise: Dup of existing BLE advertisement."); - return; + LogInfo("serviceBLE: no active client requests, disabling service timer"); + mDNSStorage.NextBLEServiceTime = 0; } - - ptr->registeredHash = BLEServiceHash(instanceRemoved); - if (ptr->registeredHash == 0) + else if ((currentTime - lastScanForStaleResponses) >= (StaleResponseScanInterval * mDNSPlatformOneSecond)) { - LogInfo("BLEServiceHash failed!"); - removeFromRequestList(&BLERegistrationListHead, instanceRemoved, type); - return; + removeStaleResponses(currentTime); + lastScanForStaleResponses = currentTime; + mDNSStorage.NextBLEServiceTime = NonZeroTime(currentTime + (StaleResponseScanInterval * mDNSPlatformOneSecond)); } - ptr->serviceRecordSet = serviceRecordSet; - // Extract the interface ID for easier access in the requestList_t structure - ptr->InterfaceID = serviceRecordSet->RR_SRV.resrec.InterfaceID; - - addServiceToBeacon(0, ptr->registeredHash); } -void stop_BLE_advertise(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +// Initialize the periodic service timer if we have active requests. +// The timer is disabled in the next call to serviceBLE() when no requests are active. +mDNSlocal void updateServiceTimer() { - (void) flags; // not used initially - requestList_t * ptr; - bool lastReference = false; - const domainname * instanceRemoved; - - if (!EnableBLEBasedDiscovery) - { - LogMsg("stop_BLE_advertise: EnableBLEBasedDiscovery disabled"); - return; - } + if (!mDNSStorage.NextBLEServiceTime && (BLEBrowseListHead || BLERegistrationListHead)) + mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (StaleResponseScanInterval * mDNSPlatformOneSecond)); +} - // Just process the SRV record for each service registration. The PTR - // record already has the service type at the beginning of the domain, but - // we want to filter out reverse address PTR records at this point in time, so using - // the SRV record instead. - if (type != kDNSServiceType_SRV) - return; +// Set true when suppressing beacon transmissions for our registrations until we see +// a peer beacon indicating a browse for one of our services. +bool suppressBeacons = false; - LogInfo("stop_BLE_advertise: Stopping BLE advertisement for: %##s %s", domain->c, DNSTypeName(type)); +// Go through all the existing browses and registrations to create the +// current Bloom filter value for the BLE beacon. +// Update the current scan and beaconing state appropriately. +mDNSlocal void updateBeaconAndScanState() +{ + requestList_t *ptr; + serviceHash_t beaconBloomFilter = 0; - instanceRemoved = SkipLeadingLabels(domain, 1); + updateServiceTimer(); - // Get the request pointer from the indirect pointer returned. - ptr = *(findInRequestList(&BLERegistrationListHead, instanceRemoved, type)); - - if (ptr == 0) + for (ptr = BLEBrowseListHead; ptr; ptr = ptr->next) { - LogInfo("stop_BLE_advertise: No matching advertisement found."); - return; + beaconBloomFilter |= ptr->browseHash; } - - // If this is the last reference for this registration, update advertising and browsing bits set in - // the beacon before removing this registration from the request list. - if (ptr->refCount == 1) + + for (ptr = BLERegistrationListHead; ptr; ptr = ptr->next) { - lastReference = true; + beaconBloomFilter |= ptr->registeredHash; + } - if (isAutoTriggerRequest(ptr) && ptr->triggeredOnAWDL) + // If only advertising registered services and not browsing, we don't start the beacon transmission + // until we receive a beacon from a peer matching one of our registrations. + if (BLERegistrationListHead && !BLEBrowseListHead && !responseMatchesRegistrations()) + { + // If beacons are already suppressed, then no further action to take. + if (suppressBeacons) + LogInfo("updateBeaconAndScanState: continuing to suppressing beacons"); + else { - // And remove the corresponding advertisements from the AWDL D2D plugin. - // Do it directly here, since we do not set the kDNSServiceFlagsIncludeAWDL bit in the original client request structure - // when we trigger the registration over AWDL, we just update the record ARType field, so our caller, external_stop_browsing_for_service() - // would not call into the D2D plugin to remove the advertisements in this case. - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); + LogInfo("updateBeaconAndScanState: suppressing beacons, no peers currently seeking our services"); + suppressBeacons = true; + + // If currently beaconing, send a beacon for two seconds with a zeroed Bloom filter indicating we are + // no longer browsing for any services so that any matching auto triggered peer registrations have a + // chance to see our state change. + if (currentlyBeaconing()) + updateBLEBeacon(0); + startBLEScan(); } } - removeFromRequestList(&BLERegistrationListHead, instanceRemoved, type); - - if (lastReference) - updateBeacon(); - - // If there are no active browse or registration requests, BLE scanning will be disabled. - // Clear the list of responses received to remove any stale response state. - if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0) - clearResponseLists(); + // If beacons had been suppressed and we no longer have services to advertise, no + // need to send a beacon with a zeroed Bloom filter for two seconds, just stop + // the scan. + else if (suppressBeacons == true && beaconBloomFilter == 0) + { + suppressBeacons = false; + stopBLEScan(); + } + // Update the beacon with the current Bloom filter values. + else + { + suppressBeacons = false; + updateBLEBeacon(beaconBloomFilter); + // Scan unless the Bloom filter is zero, indicating we are not currently + // seeking or providing any services. + if (beaconBloomFilter) + startBLEScan(); + else + stopBLEScan(); + } } -#pragma mark - Response Handling +#pragma mark - Peer response handling // Structure used to track the beacons received from various peers. typedef struct responseList { struct responseList * next; - serviceHash_t browseHash; - serviceHash_t registeredHash; - mDNSEthAddr senderMAC; + serviceHash_t peerBloomFilter; + mDNSs32 recievedTime; + mDNSEthAddr peerMac; } responseList_t; #define RESPONSE_LIST_NUMBER 8 static responseList_t* BLEResponseListHeads[RESPONSE_LIST_NUMBER]; +// Return the address of the pointer to the entry, which can either be the address of the +// corresponding BLEResponseListHeads[] entry, or the address of the prior responseList_t entry +// on the lists "next" pointer. mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC) { // Use the least significant byte of the MAC address as our hash index to find the list. @@ -518,7 +695,7 @@ mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC) for ( ; *ptr; ptr = &(*ptr)->next) { - if (memcmp(&(*ptr)->senderMAC, ptrToMAC, sizeof(mDNSEthAddr)) == 0) + if (memcmp(&(*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr)) == 0) break; } @@ -526,7 +703,7 @@ mDNSlocal responseList_t ** findInResponseList(mDNSEthAddr * ptrToMAC) } -mDNSlocal responseList_t ** addToResponseList(serviceHash_t browseHash, serviceHash_t registeredHash, mDNSEthAddr * ptrToMAC) +mDNSlocal responseList_t * addToResponseList(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC) { responseList_t **ptr = findInResponseList(ptrToMAC); @@ -534,12 +711,11 @@ mDNSlocal responseList_t ** addToResponseList(serviceHash_t browseHash, serviceH { *ptr = mDNSPlatformMemAllocate(sizeof(**ptr)); mDNSPlatformMemZero(*ptr, sizeof(**ptr)); - (*ptr)->browseHash = browseHash; - (*ptr)->registeredHash = registeredHash; - memcpy(& (*ptr)->senderMAC, ptrToMAC, sizeof(mDNSEthAddr)); + (*ptr)->peerBloomFilter = peerBloomFilter; + memcpy(& (*ptr)->peerMac, ptrToMAC, sizeof(mDNSEthAddr)); } - return ptr; + return *ptr; } mDNSlocal void removeFromResponseList(mDNSEthAddr * ptrToMAC) @@ -581,25 +757,8 @@ mDNSlocal void clearResponseLists() } } -// Called from mDNS_Execute() when NextBLEServiceTime is reached -// to stop the BLE beacon a few seconds after the last request has -// been stopped. This gives peers a chance to see that this device -// is no longer browsing for or advertising any services via the -// BLE beacon. -void serviceBLE(void) -{ - mDNSStorage.NextBLEServiceTime = 0; - if (BLEBrowseListHead || BLERegistrationListHead) - { - // We don't expect to be called if there are active requests. - LogInfo("serviceBLE: called with active BLE requests ??"); - return; - } - stopBLEBeacon(); -} - -// Called from start_BLE_browse() on the mDNSResonder kqueue thread -mDNSlocal void checkForMatchingResponses(requestList_t *bp) +// Check to see if we have cached a response that matches a service for which we just started a browse or registration. +mDNSlocal void checkCachedResponses(requestList_t *browse, requestList_t *registration) { responseList_t *ptr; @@ -607,28 +766,28 @@ mDNSlocal void checkForMatchingResponses(requestList_t *bp) { for (ptr = BLEResponseListHeads[i]; ptr; ptr = ptr->next) { - if ((bp->browseHash & ptr->registeredHash) == bp->browseHash) + // For browses, we are looking for responses that have a matching registration + // and for registrations we are looking for responses that have a matching browse. + if ( (browse && (browse->registeredHash & ptr->peerBloomFilter) == browse->registeredHash) + || (registration && (registration->browseHash & ptr->peerBloomFilter) == registration->browseHash)) { - // Clear the registered services hash for the response. - // The next beacon from this peer will update the hash and our - // newly started browse will get an add event if there is a match. - ptr->registeredHash = 0; + // Clear the Bloom filter for the response. + // The next beacon from this peer will update the filter then autoTrigger + // any newly started client requests as appropriate. + ptr->peerBloomFilter = 0; } } } } // Define a fixed name to use for the instance name denoting that one or more instances -// of a service are being advetised by peers in their BLE beacons. +// of a service are being advertised by peers in their BLE beacons. // Name format is: length byte + bytes of name string + two byte pointer to the PTR record name. -// See compression_lhs definition in the D2D plugin code for backgound on 0xc027 DNS name compression pointer value. +// See compression_lhs definition in the D2D plugin code for background on 0xc027 DNS name compression pointer value. static Byte *BLEinstanceValue = (Byte *) "\x11ThresholdInstance\xc0\x27"; #define BLEValueSize strlen((const char *)BLEinstanceValue) -void xD2DAddToCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize); -void xD2DRemoveFromCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize); - -// Find each unique browse that matches the registered service hash in the BLE response. +// Find each local browse that matches the registered service hash in the BLE response. // Called on the CFRunLoop thread while handling a callback from CoreBluetooth. // Caller should hold KQueueLock(). mDNSlocal void findMatchingBrowse(responseList_t *response) @@ -638,7 +797,9 @@ mDNSlocal void findMatchingBrowse(responseList_t *response) ptr = BLEBrowseListHead; for ( ; ptr; ptr = ptr->next) { - if ((ptr->browseHash & response->registeredHash) == ptr->browseHash) + // See if we potentially match a corresponding registration in the beacon. + // thus, compare using the "registeredHash" of our browse.. + if ((ptr->registeredHash & response->peerBloomFilter) == ptr->registeredHash) { LogInfo("findMatchingBrowse: Registration in response matched browse for: %##s", ptr->name.c); @@ -655,20 +816,19 @@ mDNSlocal void findMatchingBrowse(responseList_t *response) if (ptr->ourResponses == 0) { - if (isAutoTriggerRequest(ptr)) + if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags)) { LogInfo("findMatchingBrowse: First BLE response, triggering browse for %##s on AWDL", ptr->name.c); - ptr->question->flags |= kDNSServiceFlagsIncludeAWDL; - mDNSCoreRestartQuestion(& mDNSStorage, ptr->question); // register with the AWDL D2D plugin, - internal_start_browsing_for_service(ptr->question->InterfaceID, & ptr->name, ptr->type, ptr->question->flags, ptr->question); + internal_start_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags); + ptr->triggeredOnAWDL = true; } // Browse on mDNSInterface_BLE is used to determine if there are one or more instances of the // service type discoveryed over BLE. If this is the first instance, add the psuedo instance defined by BLEinstanceValue. - if (ptr->question->InterfaceID == mDNSInterface_BLE) + if (ptr->InterfaceID == mDNSInterface_BLE) { - xD2DAddToCache(& mDNSStorage, kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize); + xD2DAddToCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize); } } addToResponseListForRequest(ptr, response); @@ -680,34 +840,37 @@ mDNSlocal void findMatchingBrowse(responseList_t *response) // list now. If this is the last matching response, remove the corresponding key from the AWDL D2D plugin if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0)) { - if (ptr->question->InterfaceID == mDNSInterface_BLE) + if (ptr->InterfaceID == mDNSInterface_BLE) { - xD2DRemoveFromCache(& mDNSStorage, kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize); + xD2DRemoveFromCache(kD2DSuccess, 0, D2DBLETransport, ptr->key, ptr->keySize, BLEinstanceValue, BLEValueSize); } - if (isAutoTriggerRequest(ptr)) + if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags)) { LogInfo("findMatchingBrowse: Last BLE response, disabling browse for %##s on AWDL", ptr->name.c); - internal_stop_browsing_for_service(ptr->question->InterfaceID, & ptr->name, ptr->type, ptr->question->flags); + internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags); + ptr->triggeredOnAWDL = false; } } } } } -// Find each local registration that matches the service browse hash in the BLE response. +// Find each local registration that matches the service browse hash BLE response Bloom filter. // Called on the CFRunLoop thread while handling a callback from CoreBluetooth. // Caller should hold KQueueLock(). mDNSlocal void findMatchingRegistration(responseList_t *response) { - requestList_t *ptr; + requestList_t *ptr; + bool matchingPeer; ptr = BLERegistrationListHead; for ( ; ptr; ptr = ptr->next) { - if ((ptr->registeredHash & response->browseHash) == ptr->registeredHash) + // See if we potentially match a corresponding browse in the beacon, + // thus, compare using the "browseHash" of our registration. + if ((ptr->browseHash & response->peerBloomFilter) == ptr->browseHash) { - LogInfo("findMatchingRegistration: Incoming browse matched registration for: %##s", ptr->name.c); if (inResponseListForRequest(ptr, response)) @@ -722,44 +885,16 @@ mDNSlocal void findMatchingRegistration(responseList_t *response) // Also pass the registration to the AWDL D2D plugin if this is the first matching peer browse for // an auto triggered local registration. - if ((ptr->ourResponses == 0) && isAutoTriggerRequest(ptr)) + if ((ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags)) { - AuthRecType newARType; - LogInfo("findMatchingRegistration: First BLE response, triggering registration for %##s on AWDL", ptr->name.c); - if (ptr->serviceRecordSet == 0) - { - LogInfo("findMatchingRegistration: serviceRecordSet pointer is NULL ??"); - continue; - } - // Modify the PTR, TXT, and SRV records so that they now apply to AWDL and restart the registration. - // RR_ADV is not passed to the D2D plugins froma internal_start_advertising_helper(), so we don't do it here either. - - if (ptr->flags & kDNSServiceFlagsIncludeAWDL) + if (ptr->resourceRecord == 0) { - LogInfo("findMatchingRegistration: registration for %##s already applies to AWDL, skipping", ptr->name.c); + LogInfo("findMatchingRegistration: resourceRecord pointer is NULL ??"); continue; } - // Save the current ARType value to restore when the promotion to use AWDL is stopped. - ptr->savedARType = ptr->serviceRecordSet->RR_PTR.ARType; - - // Preserve P2P attribute if original registration was applied to P2P. - if (ptr->serviceRecordSet->RR_PTR.ARType == AuthRecordAnyIncludeP2P) - newARType = AuthRecordAnyIncludeAWDLandP2P; - else - newARType = AuthRecordAnyIncludeAWDL; - - ptr->serviceRecordSet->RR_PTR.ARType = newARType; - ptr->serviceRecordSet->RR_SRV.ARType = newARType; - ptr->serviceRecordSet->RR_TXT.ARType = newARType; - mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_PTR, -1); - mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_SRV, -1); - mDNSCoreRestartRegistration(& mDNSStorage, & ptr->serviceRecordSet->RR_TXT, -1); - - internal_start_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_start_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_start_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); + internal_start_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); // indicate the registration has been applied to the AWDL interface ptr->triggeredOnAWDL = true; } @@ -772,51 +907,113 @@ mDNSlocal void findMatchingRegistration(responseList_t *response) // If a previous response from this peer had matched the browse, remove that response from the // list now. If this is the last matching response for a local auto triggered registration, // remove the advertised key/value pairs from the AWDL D2D plugin. - if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0) && isAutoTriggerRequest(ptr)) + if (removeFromResponseListForRequest(ptr, response) && (ptr->ourResponses == 0) && isAutoTriggerRequest(ptr->InterfaceID, ptr->flags)) { LogInfo("findMatchingRegistration: Last BLE response, disabling registration for %##s on AWDL", ptr->name.c); // Restore the saved ARType and call into the AWDL D2D plugin to stop the corresponding record advertisements over AWDL. - ptr->serviceRecordSet->RR_PTR.ARType = ptr->savedARType; - ptr->serviceRecordSet->RR_SRV.ARType = ptr->savedARType; - ptr->serviceRecordSet->RR_TXT.ARType = ptr->savedARType; - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_PTR.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_SRV.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); - internal_stop_advertising_service(& ptr->serviceRecordSet->RR_TXT.resrec, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); + internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); + ptr->triggeredOnAWDL = false; } } } + + // If beacons for our registrations had been suppressed, see if we now have a match and need to restart them. + matchingPeer = responseMatchesRegistrations(); + if (suppressBeacons && matchingPeer) + { + LogInfo("findMatchingRegistration: peer searching for our service, starting beacon transmission"); + updateBeaconAndScanState(); + + if (suppressBeacons == true) + LogInfo("findMatchingRegistration: NOTE: suppressBeacons is true after updateBeaconAndScanState() call ??"); + } + // If we have only registrations, but no matching peers, we can suppress beacons until we get a matching peer beacon. + else if (!suppressBeacons && !matchingPeer && BLERegistrationListHead && !BLEBrowseListHead) + { + LogInfo("findMatchingRegistration: no peer beacons match our registrations, suppressing beacon transmission"); + suppressBeacons = true; + stopBLEBeacon(); + } +} + + +// Time limit before a beacon is aged out of our received list. +#define MAX_RESPONSE_AGE 10 + +// If we have responses from peers that are more than MAX_RESPONSE_AGE seconds +// old, remove them since a peer with active requests should be beaconing multiple +// times per second if still within BLE range. +mDNSlocal void removeStaleResponses(mDNSs32 currentTime) +{ + responseList_t **ptr; + + for (unsigned int i = 0; i < RESPONSE_LIST_NUMBER; i++) + { + ptr = & BLEResponseListHeads[i]; + while (*ptr) + { + if ((currentTime - (*ptr)->recievedTime) > (MAX_RESPONSE_AGE * mDNSPlatformOneSecond)) + { + responseList_t * tmp; + + // Clear the Bloom filter so that it will be removed from any matching response list + // by the following calls. + (*ptr)->peerBloomFilter = 0; + + LogInfo("removeStaleResponses: clearing stale response from peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + (*ptr)->peerMac.b[0], (*ptr)->peerMac.b[1], (*ptr)->peerMac.b[2], (*ptr)->peerMac.b[3], (*ptr)->peerMac.b[4], (*ptr)->peerMac.b[5]); + + findMatchingBrowse(*ptr); + findMatchingRegistration(*ptr); + + // Unlink and free the response structure + tmp = *ptr; + *ptr = (*ptr)->next; + mDNSPlatformMemFree(tmp); + } + // Move to the next response on this linked list. + else + ptr = & (*ptr)->next; + } + } } // Called on CFRunLoop thread during CoreBluetooth beacon response processing. // Thus, must call KQueueLock() prior to calling any core mDNSResponder routines to register records, etc. -void responseReceived(serviceHash_t browseHash, serviceHash_t registeredHash, mDNSEthAddr * ptrToMAC) +void responseReceived(serviceHash_t peerBloomFilter, mDNSEthAddr * ptrToMAC) { - responseList_t ** ptr; + responseList_t * ptr; - KQueueLock(& mDNSStorage); - ptr = findInResponseList(ptrToMAC); - if (*ptr == 0) + KQueueLock(); + mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow + + ptr = *(findInResponseList(ptrToMAC)); + if (ptr == 0) { // Only add to list if peer is actively browsing or advertising. - if (browseHash || registeredHash) + if (peerBloomFilter) { LogInfo("responseReceived: First beacon of this type, adding to list"); - LogInfo("responseReceived: browseHash = 0x%x, registeredHash = 0x%x", - browseHash, registeredHash); - LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter); + LogInfo("responseReceived: peer MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]); - ptr = addToResponseList(browseHash, registeredHash, ptrToMAC); + ptr = addToResponseList(peerBloomFilter, ptrToMAC); + // Update the received time. + ptr->recievedTime = mDNSStorage.timenow; // See if we are browsing for any of the peers advertised services. - findMatchingBrowse(*ptr); + findMatchingBrowse(ptr); // See if we have a registration that matches the peer's browse. - findMatchingRegistration(*ptr); + findMatchingRegistration(ptr); } } - else // have entry from this MAC in the list + else // Have an entry from this MAC in the list. { - if (((*ptr)->browseHash == browseHash) && ((*ptr)->registeredHash == registeredHash)) + // Update the received time. + ptr->recievedTime = mDNSStorage.timenow; + + if (ptr->peerBloomFilter == peerBloomFilter) { // A duplicate of a current entry. #if VERBOSE_BLE_DEBUG @@ -828,20 +1025,17 @@ void responseReceived(serviceHash_t browseHash, serviceHash_t registeredHash, mD else { LogInfo("responseReceived: Update of previous beacon"); - LogInfo("responseReceived: browseHash = 0x%x, registeredHash = 0x%x", - browseHash, registeredHash); + LogInfo("responseReceived: peerBloomFilter = 0x%lx", peerBloomFilter); LogInfo("responseReceived: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", ptrToMAC->b[0], ptrToMAC->b[1], ptrToMAC->b[2], ptrToMAC->b[3], ptrToMAC->b[4], ptrToMAC->b[5]); - (*ptr)->browseHash = browseHash; - (*ptr)->registeredHash = registeredHash; - - findMatchingBrowse(*ptr); - findMatchingRegistration(*ptr); + ptr->peerBloomFilter = peerBloomFilter; + findMatchingBrowse(ptr); + findMatchingRegistration(ptr); } // If peer is no longer browsing or advertising, remove from list. - if ((browseHash == 0) && (registeredHash == 0)) + if (peerBloomFilter == 0) { LogInfo("responseReceived: Removing peer entry from the list"); @@ -849,5 +1043,450 @@ void responseReceived(serviceHash_t browseHash, serviceHash_t registeredHash, mD } } - KQueueUnlock(& mDNSStorage, "BLE responseReceived"); + mDNS_Unlock(& mDNSStorage); // Calling mDNS_Unlock is what gives m->NextScheduledEvent its new value + KQueueUnlock("BLE responseReceived"); +} + +#pragma mark - Client request handling + +void start_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags, mDNSu8 *key, size_t keySize) +{ + requestList_t * ptr; + const domainname *serviceType = domain; + + if (!EnableBLEBasedDiscovery) + { + LogMsg("start_BLE_browse: EnableBLEBasedDiscovery disabled"); + return; + } + + if (keySize > MAX_KEY_SIZE) + { + LogMsg("start_BLE_browse: keySize = %d, maximum allowable is %d", keySize, MAX_KEY_SIZE); + return; + } + + // Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for + // BLE threshold browsing. + if (!isAutoTriggerRequest(InterfaceID, flags) && (InterfaceID != mDNSInterface_BLE)) + { + LogMsg("start_BLE_browse: invalid request: InterfaceID = %d, flags = 0x%x", InterfaceID, flags); + return; + } + + // Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin. + if ((type == kDNSType_A) || (type == kDNSType_AAAA)) + { + LogInfo("start_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type)); + internal_start_browsing_for_service(InterfaceID, domain, type, flags); + return; + } + + // Skip the instance to get to the service type for non PTR records + if (type != kDNSType_PTR) + serviceType = SkipLeadingLabels(domain, 1); + + LogInfo("start_BLE_browse: Starting BLE service type browse for: %##s %s", domain->c, DNSTypeName(type)); + + ptr = addToRequestList(&BLEBrowseListHead, domain, type, flags); + + // If equivalent BLE browse is already running, just return. + if (ptr->refCount > 1) + { + LogInfo("start_BLE_browse: Dup of existing BLE browse."); + return; + } + + if (!setBLEServiceHash(serviceType, ptr)) + { + LogInfo("setBLEServiceHash failed!"); + removeFromRequestList(&BLEBrowseListHead, domain, type); + return; + } + + // Save these for use in D2D plugin callback logic. + memcpy(ptr->key, key, keySize); + ptr->keySize = keySize; + ptr->InterfaceID = InterfaceID; + + mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow. + updateBeaconAndScanState(); + mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent. + checkCachedResponses(ptr, NULL); +} + +// Stop the browse. +// Return true if this is the last reference to the browse, false otherwise. +bool stop_BLE_browse(mDNSInterfaceID InterfaceID, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +{ + (void) flags; // not used initially + requestList_t * ptr; + bool lastReference = false; + + if (!EnableBLEBasedDiscovery) + { + LogMsg("stop_BLE_browse: EnableBLEBasedDiscovery disabled"); + return lastReference; + } + + // Address records don't have a service type to hash and match on, so pass them directly to the D2D plugin. + if ((type == kDNSType_A) || (type == kDNSType_AAAA)) + { + LogInfo("stop_BLE_browse: Passing directly to D2D layer: %##s %s", domain->c, DNSTypeName(type)); + internal_stop_browsing_for_service(InterfaceID, domain, type, flags); + return lastReference; + } + + LogInfo("stop_BLE_browse: Stopping BLE service type browse for: %##s %s", domain->c, DNSTypeName(type)); + + ptr = *(findInRequestList(&BLEBrowseListHead, domain, type)); + if (ptr == 0) + { + LogInfo("stop_BLE_browse: No matching browse found."); + return lastReference; + } + + // If this is the last reference for this browse, and it was autoTriggered on AWDL, + // remove the request from the AWDL pluggin. + if (ptr->refCount == 1) + { + lastReference = true; + + if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL) + { + internal_stop_browsing_for_service(ptr->InterfaceID, & ptr->name, ptr->type, ptr->flags); + ptr->triggeredOnAWDL = false; + } + } + + removeFromRequestList(&BLEBrowseListHead, domain, type); + + mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow. + if (lastReference) + updateBeaconAndScanState(); + mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent. + + // If there are no active browse or registration requests, BLE scanning will be disabled. + // Clear the list of responses received to remove any stale response state. + if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0) + clearResponseLists(); + + return lastReference; +} + +void start_BLE_advertise(const ResourceRecord *const resourceRecord, const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +{ + requestList_t * ptr; + const domainname * serviceType = domain; + + if (!EnableBLEBasedDiscovery) + { + LogMsg("start_BLE_advertise: EnableBLEBasedDiscovery disabled"); + return; + } + + if (resourceRecord == NULL) + { + LogInfo("start_BLE_advertise: NULL resourceRecord for: %##s %s, returning", domain->c, DNSTypeName(type)); + return; + } + + // Verify that the request to be auto triggered applies to AWDL, or is using the pseudo interface for + // BLE threshold browsing. + if (!isAutoTriggerRequest(resourceRecord->InterfaceID, flags) && (resourceRecord->InterfaceID != mDNSInterface_BLE)) + { + LogMsg("start_BLE_advertise: invalid request: InterfaceID = %d, flags = 0x%x", resourceRecord->InterfaceID, flags); + return; + } + + LogInfo("start_BLE_advertise: Starting BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type)); + + // Skip the instance to get to the service type for non PTR records + if (type != kDNSType_PTR) + serviceType = SkipLeadingLabels(domain, 1); + + ptr = addToRequestList(&BLERegistrationListHead, domain, type, flags); + + // If equivalent BLE registration is already running, just return. + if (ptr->refCount > 1) + { + LogInfo("start_BLE_advertise: Dup of existing BLE advertisement."); + return; + } + + if (!setBLEServiceHash(serviceType, ptr)) + { + LogInfo("setBLEServiceHash failed!"); + removeFromRequestList(&BLERegistrationListHead, domain, type); + return; + } + ptr->resourceRecord = resourceRecord; + ptr->InterfaceID = resourceRecord->InterfaceID; + + mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow. + updateBeaconAndScanState(); + mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent. + checkCachedResponses(NULL, ptr); +} + +void stop_BLE_advertise(const domainname *const domain, DNS_TypeValues type, DNSServiceFlags flags) +{ + (void) flags; // not used initially + requestList_t * ptr; + bool lastReference = false; + + LogInfo("stop_BLE_advertise: Stopping BLE service type advertisement for: %##s %s", domain->c, DNSTypeName(type)); + + // Get the request pointer from the indirect pointer returned. + ptr = *(findInRequestList(&BLERegistrationListHead, domain, type)); + + if (ptr == 0) + { + LogInfo("stop_BLE_advertise: No matching advertisement found."); + return; + } + + // If this is the last reference for this registration, and it was autoTriggered on AWDL, + // remove the request from the AWDL pluggin. + if (ptr->refCount == 1) + { + lastReference = true; + + if (isAutoTriggerRequest(ptr->InterfaceID, ptr->flags) && ptr->triggeredOnAWDL) + { + // And remove the corresponding advertisements from the AWDL D2D plugin if we had previously + // passed this advertisement request to the plugin. + internal_stop_advertising_service(ptr->resourceRecord, (ptr->flags | kDNSServiceFlagsIncludeAWDL)); + ptr->triggeredOnAWDL = false; + } + } + removeFromRequestList(&BLERegistrationListHead, domain, type); + + mDNS_Lock(& mDNSStorage); // Must lock to initialize mDNSStorage.timenow. + // If this is the last reference for this registration, update advertising and browsing bits set in the beacon. + if (lastReference) + updateBeaconAndScanState(); + mDNS_Unlock(& mDNSStorage); // Updates mDNSStorage.NextScheduledEvent. + + // If there are no active browse or registration requests, BLE scanning will be disabled. + // Clear the list of responses received to remove any stale response state. + if (BLEBrowseListHead == NULL && BLERegistrationListHead == 0) + clearResponseLists(); } + +#ifdef UNIT_TEST +#pragma mark - Unit test support routines + +// These unit test support routines are called from unittests/ framework +// and are not compiled for the mDNSResponder runtime code paths. + +#define MAX_ENTRIES 42 +#define FAILED exit(1) + +mDNSlocal void BLE_requestListTests(void) +{ + const domainname *domainArray[] = { (const domainname*)"\x6" "_test0" "\x4" "_tcp" "\x5" "local", + (const domainname*)"\x6" "_test1" "\x4" "_tcp" "\x5" "local", + (const domainname*)"\x6" "_test2" "\x4" "_tcp" "\x5" "local", + (const domainname*)"\x6" "_test3" "\x4" "_tcp" "\x5" "local", + (const domainname*)"\x6" "_test4" "\x4" "_tcp" "\x5" "local", + }; + + mDNSu16 type = kDNSServiceType_PTR; + DNSServiceFlags flags = 0; + requestList_t * ptr; + void * response = 0; + int i; + int numOfdomains = sizeof(domainArray)/sizeof(domainArray[0]); + + printf("BLE_requestListTests() entry:\n"); + + // Basic request list unit tests. + for (i = 0; i < numOfdomains; i++) + { + ptr = addToRequestList(&BLEBrowseListHead, domainArray[i], type, flags); + + if (ptr == NULL) + { + printf("addToRequestList() FAILED:\n"); + FAILED; + } + } + for (i = 0; i < numOfdomains; i++) + { + // should now find the entry + if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], type)) == 0) + { + printf("findInRequestList() did not find valid entry FAILED:\n"); + FAILED; + } + // but not find an entry with the same domain, but different type + if (*(findInRequestList(&BLEBrowseListHead, domainArray[i], kDNSServiceType_NULL)) != 0) + { + printf("findInRequestList() invalid entry matched FAILED:\n"); + FAILED; + } + } + // remove all the entries + for (i = 0; i < numOfdomains; i++) + { + removeFromRequestList(&BLEBrowseListHead, domainArray[i], type); + } + // and sanity check the list is now empty + if (BLEBrowseListHead) + { + printf("BLEBrowseListHead not empty after all entries removed.\n"); + FAILED; + } + + // Identical request reference count management tests. + // Add identical requests to the list and verify the corresponding refCount is managed correctly + for (i = 0; i < MAX_ENTRIES; i++) + { + ptr = addToRequestList(&BLEBrowseListHead, domainArray[0], type, flags); + + if (ptr == NULL) + { + printf("addToRequestList() of duplicate request FAILED:\n"); + FAILED; + } + } + + if (ptr->refCount != MAX_ENTRIES) + { + printf("refCount = %d, should be %d\n", ptr->refCount, MAX_ENTRIES); + FAILED; + } + + // Remove all but one entry + for (i = 0; i < (MAX_ENTRIES - 1); i++) + { + removeFromRequestList(&BLEBrowseListHead, domainArray[0], type); + } + if (ptr->refCount != 1) + { + printf("refCount = %d, should be %d\n", ptr->refCount, 1); + FAILED; + } + + // Basic response list unit tests. + // Note that responses per request are not checked for duplicates at this level, so + // we can unit test with the same (NULL) response pointer to add multiple responses. + + // add MAX_ENTRIES responses + for (i = 0; i < MAX_ENTRIES; i++) + addToResponseListForRequest(ptr, response); + + // remove the responses, counting that MAX_ENTRIES were removed + i = 0; + while (inResponseListForRequest(ptr, response) && removeFromResponseListForRequest(ptr, response)) + { + i++; + } + if (i != MAX_ENTRIES) + { + printf("removed %d responses, should have been %d\n", i, MAX_ENTRIES); + FAILED; + } + + // response list should be empty at this point + if (ptr->ourResponses) + { + printf("response list should be empty\n"); + FAILED; + } + + // add MAX_ENTRIES responses + for (i = 0; i < MAX_ENTRIES; i++) + addToResponseListForRequest(ptr, response); + + // free them all + freeResponseListEntriesForRequest(ptr); + + if (ptr->ourResponses) + { + printf("freeResponseListEntriesForRequest() should have removed all responses\n"); + FAILED; + } +} + +mDNSlocal mDNSEthAddr etherAddress[] = { + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e } }, + { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f } }, +}; + +mDNSlocal void BLE_responseListTests(void) +{ + int numOfEtherAddresses = sizeof(etherAddress)/sizeof(etherAddress[0]); + int i; + + printf("BLE_responseListTests() entry:\n"); + + // Just use the index as to generate the peerBloomFilter value to vary it per entry. + for (i = 0; i < numOfEtherAddresses; i++) + (void)addToResponseList(1 << i, ð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 |