summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/mDNSMacOSX/BLE.c
diff options
context:
space:
mode:
Diffstat (limited to 'mDNSResponder/mDNSMacOSX/BLE.c')
-rw-r--r--mDNSResponder/mDNSMacOSX/BLE.c1369
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, &etherAddress[i]);
+
+ // Verify all entries are found.
+ for (i = 0; i < numOfEtherAddresses; i++)
+ {
+ if (*(findInResponseList(&etherAddress[i])) == 0)
+ {
+ printf("findInResponseList() did not find entry in list\n");
+ FAILED;
+ }
+ }
+
+ // Remove all entries.
+ for (i = 0; i < numOfEtherAddresses; i++)
+ removeFromResponseList(&etherAddress[i]);
+
+ // Sanity check that all response lists are empty
+ for (i = 0; i < RESPONSE_LIST_NUMBER; i++)
+ {
+ if (BLEResponseListHeads[i])
+ {
+ printf("BLEResponseListHeads[%d] not empty after removeFromResponseList() calls \n", i);
+ FAILED;
+ }
+ }
+
+ // Add them back again.
+ for (i = 0; i < numOfEtherAddresses; i++)
+ (void)addToResponseList(1 << i, &etherAddress[i]);
+
+ // And verify that clearResponseLists() clears all entries.
+ clearResponseLists();
+ for (i = 0; i < RESPONSE_LIST_NUMBER; i++)
+ {
+ if (BLEResponseListHeads[i])
+ {
+ printf("BLEResponseListHeads[%d] not empty after clearResponseLists() call\n", i);
+ FAILED;
+ }
+ }
+}
+
+void BLE_unitTest(void)
+{
+ BLE_requestListTests();
+ BLE_responseListTests();
+ printf("All BLE.c unit tests PASSED.\n");
+}
+
+#endif // UNIT_TEST
+
+#endif // ENABLE_BLE_TRIGGERED_BONJOUR