PageRenderTime 27ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/XMPP_Demo/Utilities/XMPPSRVResolver.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 706 lines | 446 code | 171 blank | 89 comment | 61 complexity | 0768bb81d5b65add458d1bbe63cad4ff MD5 | raw file
  1. //
  2. // XMPPSRVResolver.m
  3. //
  4. // Originally created by Eric Chamberlain on 6/15/10.
  5. // Based on SRVResolver by Apple, Inc.
  6. //
  7. #import "XMPPSRVResolver.h"
  8. #import "XMPPLogging.h"
  9. #include <dns_util.h>
  10. #include <stdlib.h>
  11. #if ! __has_feature(objc_arc)
  12. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  13. #endif
  14. /**
  15. * Does ARC support support GCD objects?
  16. * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
  17. **/
  18. #if TARGET_OS_IPHONE
  19. // Compiling for iOS
  20. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
  21. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  22. #else // iOS 5.X or earlier
  23. #define NEEDS_DISPATCH_RETAIN_RELEASE 1
  24. #endif
  25. #else
  26. // Compiling for Mac OS X
  27. #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
  28. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  29. #else
  30. #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
  31. #endif
  32. #endif
  33. NSString *const XMPPSRVResolverErrorDomain = @"XMPPSRVResolverErrorDomain";
  34. // Log levels: off, error, warn, info, verbose
  35. #if DEBUG
  36. static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
  37. #else
  38. static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
  39. #endif
  40. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  41. #pragma mark -
  42. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  43. @interface XMPPSRVRecord ()
  44. @property(nonatomic, assign) NSUInteger srvResultsIndex;
  45. @property(nonatomic, assign) NSUInteger sum;
  46. - (NSComparisonResult)compareByPriority:(XMPPSRVRecord *)aRecord;
  47. @end
  48. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  49. #pragma mark -
  50. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  51. @implementation XMPPSRVResolver
  52. - (id)initWithdDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq resolverQueue:(dispatch_queue_t)rq
  53. {
  54. NSParameterAssert(aDelegate != nil);
  55. NSParameterAssert(dq != NULL);
  56. if ((self = [super init]))
  57. {
  58. XMPPLogTrace();
  59. delegate = aDelegate;
  60. delegateQueue = dq;
  61. #if NEEDS_DISPATCH_RETAIN_RELEASE
  62. dispatch_retain(delegateQueue);
  63. #endif
  64. if (rq)
  65. {
  66. resolverQueue = rq;
  67. #if NEEDS_DISPATCH_RETAIN_RELEASE
  68. dispatch_retain(resolverQueue);
  69. #endif
  70. }
  71. else
  72. {
  73. resolverQueue = dispatch_queue_create("XMPPSRVResolver", NULL);
  74. }
  75. results = [[NSMutableArray alloc] initWithCapacity:2];
  76. }
  77. return self;
  78. }
  79. - (void)dealloc
  80. {
  81. XMPPLogTrace();
  82. [self stop];
  83. #if NEEDS_DISPATCH_RETAIN_RELEASE
  84. if (resolverQueue)
  85. dispatch_release(resolverQueue);
  86. #endif
  87. }
  88. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  89. #pragma mark Properties
  90. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  91. @dynamic srvName;
  92. @dynamic timeout;
  93. - (NSString *)srvName
  94. {
  95. __block NSString *result = nil;
  96. dispatch_block_t block = ^{
  97. result = [srvName copy];
  98. };
  99. if (dispatch_get_current_queue() == resolverQueue)
  100. block();
  101. else
  102. dispatch_sync(resolverQueue, block);
  103. return result;
  104. }
  105. - (NSTimeInterval)timeout
  106. {
  107. __block NSTimeInterval result = 0.0;
  108. dispatch_block_t block = ^{
  109. result = timeout;
  110. };
  111. if (dispatch_get_current_queue() == resolverQueue)
  112. block();
  113. else
  114. dispatch_sync(resolverQueue, block);
  115. return result;
  116. }
  117. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  118. #pragma mark Private Methods
  119. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  120. - (void)sortResults
  121. {
  122. NSAssert(dispatch_get_current_queue() == resolverQueue, @"Invoked on incorrect queue");
  123. XMPPLogTrace();
  124. // Sort results
  125. NSMutableArray *sortedResults = [NSMutableArray arrayWithCapacity:[results count]];
  126. // Sort the list by priority (lowest number first)
  127. [results sortUsingSelector:@selector(compareByPriority:)];
  128. /* From RFC 2782
  129. *
  130. * For each distinct priority level
  131. * While there are still elements left at this priority level
  132. *
  133. * Select an element as specified above, in the
  134. * description of Weight in "The format of the SRV
  135. * RR" Section, and move it to the tail of the new
  136. * list.
  137. *
  138. * The following algorithm SHOULD be used to order
  139. * the SRV RRs of the same priority:
  140. */
  141. NSUInteger srvResultsCount;
  142. while ([results count] > 0)
  143. {
  144. srvResultsCount = [results count];
  145. if (srvResultsCount == 1)
  146. {
  147. XMPPSRVRecord *srvRecord = [results objectAtIndex:0];
  148. [sortedResults addObject:srvRecord];
  149. [results removeObjectAtIndex:0];
  150. }
  151. else // (srvResultsCount > 1)
  152. {
  153. // more than two records so we need to sort
  154. /* To select a target to be contacted next, arrange all SRV RRs
  155. * (that have not been ordered yet) in any order, except that all
  156. * those with weight 0 are placed at the beginning of the list.
  157. *
  158. * Compute the sum of the weights of those RRs, and with each RR
  159. * associate the running sum in the selected order.
  160. */
  161. NSUInteger runningSum = 0;
  162. NSMutableArray *samePriorityRecords = [NSMutableArray arrayWithCapacity:srvResultsCount];
  163. XMPPSRVRecord *srvRecord = [results objectAtIndex:0];
  164. NSUInteger initialPriority = srvRecord.priority;
  165. NSUInteger index = 0;
  166. do
  167. {
  168. if (srvRecord.weight == 0)
  169. {
  170. // add to front of array
  171. [samePriorityRecords insertObject:srvRecord atIndex:0];
  172. srvRecord.srvResultsIndex = index;
  173. srvRecord.sum = 0;
  174. }
  175. else
  176. {
  177. // add to end of array and update the running sum
  178. [samePriorityRecords addObject:srvRecord];
  179. runningSum += srvRecord.weight;
  180. srvRecord.srvResultsIndex = index;
  181. srvRecord.sum = runningSum;
  182. }
  183. if (++index < srvResultsCount)
  184. {
  185. srvRecord = [results objectAtIndex:index];
  186. }
  187. else
  188. {
  189. srvRecord = nil;
  190. }
  191. } while(srvRecord && (srvRecord.priority == initialPriority));
  192. /* Then choose a uniform random number between 0 and the sum computed
  193. * (inclusive), and select the RR whose running sum value is the
  194. * first in the selected order which is greater than or equal to
  195. * the random number selected.
  196. */
  197. NSUInteger randomIndex = arc4random() % (runningSum + 1);
  198. for (srvRecord in samePriorityRecords)
  199. {
  200. if (srvRecord.sum >= randomIndex)
  201. {
  202. /* The target host specified in the
  203. * selected SRV RR is the next one to be contacted by the client.
  204. * Remove this SRV RR from the set of the unordered SRV RRs and
  205. * apply the described algorithm to the unordered SRV RRs to select
  206. * the next target host. Continue the ordering process until there
  207. * are no unordered SRV RRs. This process is repeated for each
  208. * Priority.
  209. */
  210. [sortedResults addObject:srvRecord];
  211. [results removeObjectAtIndex:srvRecord.srvResultsIndex];
  212. break;
  213. }
  214. }
  215. }
  216. }
  217. results = sortedResults;
  218. XMPPLogVerbose(@"%@: Sorted results:\n%@", THIS_FILE, results);
  219. }
  220. - (void)succeed
  221. {
  222. NSAssert(dispatch_get_current_queue() == resolverQueue, @"Invoked on incorrect queue");
  223. XMPPLogTrace();
  224. [self sortResults];
  225. id theDelegate = delegate;
  226. NSArray *records = [results copy];
  227. dispatch_async(delegateQueue, ^{ @autoreleasepool {
  228. SEL selector = @selector(srvResolver:didResolveRecords:);
  229. if ([theDelegate respondsToSelector:selector])
  230. {
  231. [theDelegate srvResolver:self didResolveRecords:records];
  232. }
  233. else
  234. {
  235. XMPPLogWarn(@"%@: delegate doesn't implement %@", THIS_FILE, NSStringFromSelector(selector));
  236. }
  237. }});
  238. [self stop];
  239. }
  240. - (void)failWithError:(NSError *)error
  241. {
  242. NSAssert(dispatch_get_current_queue() == resolverQueue, @"Invoked on incorrect queue");
  243. XMPPLogTrace2(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
  244. id theDelegate = delegate;
  245. if (delegateQueue != NULL)
  246. {
  247. dispatch_async(delegateQueue, ^{ @autoreleasepool {
  248. SEL selector = @selector(srvResolver:didNotResolveDueToError:);
  249. if ([theDelegate respondsToSelector:selector])
  250. {
  251. [theDelegate srvResolver:self didNotResolveDueToError:error];
  252. }
  253. else
  254. {
  255. XMPPLogWarn(@"%@: delegate doesn't implement %@", THIS_FILE, NSStringFromSelector(selector));
  256. }
  257. }});
  258. }
  259. [self stop];
  260. }
  261. - (void)failWithDNSError:(DNSServiceErrorType)sdErr
  262. {
  263. XMPPLogTrace2(@"%@: %@ %i", THIS_FILE, THIS_METHOD, (int)sdErr);
  264. [self failWithError:[NSError errorWithDomain:XMPPSRVResolverErrorDomain code:sdErr userInfo:nil]];
  265. }
  266. - (XMPPSRVRecord *)processRecord:(const void *)rdata length:(uint16_t)rdlen
  267. {
  268. XMPPLogTrace();
  269. // Note: This method is almost entirely from Apple's sample code.
  270. //
  271. // Otherwise there would be a lot more comments and explanation...
  272. if (rdata == NULL)
  273. {
  274. XMPPLogWarn(@"%@: %@ - rdata == NULL", THIS_FILE, THIS_METHOD);
  275. return nil;
  276. }
  277. // Rather than write a whole bunch of icky parsing code, I just synthesise
  278. // a resource record and use <dns_util.h>.
  279. XMPPSRVRecord *result = nil;
  280. NSMutableData * rrData;
  281. dns_resource_record_t * rr;
  282. uint8_t u8; // 1 byte
  283. uint16_t u16; // 2 bytes
  284. uint32_t u32; // 4 bytes
  285. rrData = [NSMutableData dataWithCapacity:(1 + 2 + 2 + 4 + 2 + rdlen)];
  286. u8 = 0;
  287. [rrData appendBytes:&u8 length:sizeof(u8)];
  288. u16 = htons(kDNSServiceType_SRV);
  289. [rrData appendBytes:&u16 length:sizeof(u16)];
  290. u16 = htons(kDNSServiceClass_IN);
  291. [rrData appendBytes:&u16 length:sizeof(u16)];
  292. u32 = htonl(666);
  293. [rrData appendBytes:&u32 length:sizeof(u32)];
  294. u16 = htons(rdlen);
  295. [rrData appendBytes:&u16 length:sizeof(u16)];
  296. [rrData appendBytes:rdata length:rdlen];
  297. // Parse the record.
  298. rr = dns_parse_resource_record([rrData bytes], (uint32_t) [rrData length]);
  299. if (rr != NULL)
  300. {
  301. NSString *target;
  302. target = [NSString stringWithCString:rr->data.SRV->target encoding:NSASCIIStringEncoding];
  303. if (target != nil)
  304. {
  305. UInt16 priority = rr->data.SRV->priority;
  306. UInt16 weight = rr->data.SRV->weight;
  307. UInt16 port = rr->data.SRV->port;
  308. result = [XMPPSRVRecord recordWithPriority:priority weight:weight port:port target:target];
  309. }
  310. dns_free_resource_record(rr);
  311. }
  312. return result;
  313. }
  314. static void QueryRecordCallback(DNSServiceRef sdRef,
  315. DNSServiceFlags flags,
  316. uint32_t interfaceIndex,
  317. DNSServiceErrorType errorCode,
  318. const char * fullname,
  319. uint16_t rrtype,
  320. uint16_t rrclass,
  321. uint16_t rdlen,
  322. const void * rdata,
  323. uint32_t ttl,
  324. void * context)
  325. {
  326. // Called when we get a response to our query.
  327. // It does some preliminary work, but the bulk of the interesting stuff
  328. // is done in the processRecord:length: method.
  329. XMPPSRVResolver *resolver = (__bridge XMPPSRVResolver *)context;
  330. NSCAssert(dispatch_get_current_queue() == resolver->resolverQueue, @"Invoked on incorrect queue");
  331. XMPPLogCTrace();
  332. if (!(flags & kDNSServiceFlagsAdd))
  333. {
  334. // If the kDNSServiceFlagsAdd flag is not set, the domain information is not valid.
  335. return;
  336. }
  337. if (errorCode == kDNSServiceErr_NoError &&
  338. rrtype == kDNSServiceType_SRV)
  339. {
  340. XMPPSRVRecord *record = [resolver processRecord:rdata length:rdlen];
  341. if (record)
  342. {
  343. [resolver->results addObject:record];
  344. }
  345. if ( ! (flags & kDNSServiceFlagsMoreComing) )
  346. {
  347. [resolver succeed];
  348. }
  349. }
  350. else
  351. {
  352. [resolver failWithDNSError:errorCode];
  353. }
  354. }
  355. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  356. #pragma mark Public Methods
  357. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  358. - (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout
  359. {
  360. dispatch_block_t block = ^{ @autoreleasepool {
  361. if (resolveInProgress)
  362. {
  363. return;
  364. }
  365. XMPPLogTrace2(@"%@: startWithSRVName:%@ timeout:%f", THIS_FILE, aSRVName, aTimeout);
  366. // Save parameters
  367. srvName = [aSRVName copy];
  368. timeout = aTimeout;
  369. // Check parameters
  370. const char *srvNameCStr = [srvName cStringUsingEncoding:NSASCIIStringEncoding];
  371. if (srvNameCStr == NULL)
  372. {
  373. [self failWithDNSError:kDNSServiceErr_BadParam];
  374. return;
  375. }
  376. // Create DNS Service
  377. DNSServiceErrorType sdErr;
  378. sdErr = DNSServiceQueryRecord(&sdRef, // Pointer to unitialized DNSServiceRef
  379. kDNSServiceFlagsReturnIntermediates, // Flags
  380. kDNSServiceInterfaceIndexAny, // Interface index
  381. srvNameCStr, // Full domain name
  382. kDNSServiceType_SRV, // rrtype
  383. kDNSServiceClass_IN, // rrclass
  384. QueryRecordCallback, // Callback method
  385. (__bridge void *)self); // Context pointer
  386. if (sdErr != kDNSServiceErr_NoError)
  387. {
  388. [self failWithDNSError:sdErr];
  389. return;
  390. }
  391. // Extract unix socket (so we can poll for events)
  392. sdFd = DNSServiceRefSockFD(sdRef);
  393. if (sdFd < 0)
  394. {
  395. // Todo...
  396. }
  397. // Create GCD read source for sd file descriptor
  398. sdReadSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, sdFd, 0, resolverQueue);
  399. dispatch_source_set_event_handler(sdReadSource, ^{ @autoreleasepool {
  400. XMPPLogVerbose(@"%@: sdReadSource_eventHandler", THIS_FILE);
  401. // There is data to be read on the socket (or an error occurred).
  402. //
  403. // Invoking DNSServiceProcessResult will invoke our QueryRecordCallback,
  404. // the callback we set when we created the sdRef.
  405. DNSServiceErrorType dnsErr = DNSServiceProcessResult(sdRef);
  406. if (dnsErr != kDNSServiceErr_NoError)
  407. {
  408. [self failWithDNSError:dnsErr];
  409. }
  410. }});
  411. #if NEEDS_DISPATCH_RETAIN_RELEASE
  412. dispatch_source_t theSdReadSource = sdReadSource;
  413. #endif
  414. DNSServiceRef theSdRef = sdRef;
  415. dispatch_source_set_cancel_handler(sdReadSource, ^{ @autoreleasepool {
  416. XMPPLogVerbose(@"%@: sdReadSource_cancelHandler", THIS_FILE);
  417. #if NEEDS_DISPATCH_RETAIN_RELEASE
  418. dispatch_release(theSdReadSource);
  419. #endif
  420. DNSServiceRefDeallocate(theSdRef);
  421. }});
  422. dispatch_resume(sdReadSource);
  423. // Create timer (if requested timeout > 0)
  424. if (timeout > 0.0)
  425. {
  426. timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, resolverQueue);
  427. dispatch_source_set_event_handler(timeoutTimer, ^{ @autoreleasepool {
  428. NSString *errMsg = @"Operation timed out";
  429. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
  430. NSError *err = [NSError errorWithDomain:XMPPSRVResolverErrorDomain code:0 userInfo:userInfo];
  431. [self failWithError:err];
  432. }});
  433. dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
  434. dispatch_source_set_timer(timeoutTimer, tt, DISPATCH_TIME_FOREVER, 0);
  435. dispatch_resume(timeoutTimer);
  436. }
  437. resolveInProgress = YES;
  438. }};
  439. if (dispatch_get_current_queue() == resolverQueue)
  440. block();
  441. else
  442. dispatch_async(resolverQueue, block);
  443. }
  444. - (void)stop
  445. {
  446. dispatch_block_t block = ^{ @autoreleasepool {
  447. XMPPLogTrace();
  448. delegate = nil;
  449. if (delegateQueue)
  450. {
  451. #if NEEDS_DISPATCH_RETAIN_RELEASE
  452. dispatch_release(delegateQueue);
  453. #endif
  454. delegateQueue = NULL;
  455. }
  456. [results removeAllObjects];
  457. if (sdReadSource)
  458. {
  459. // Cancel the readSource.
  460. // It will be released from within the cancel handler.
  461. dispatch_source_cancel(sdReadSource);
  462. sdReadSource = NULL;
  463. sdFd = -1;
  464. // The sdRef will be deallocated from within the cancel handler too.
  465. sdRef = NULL;
  466. }
  467. if (timeoutTimer)
  468. {
  469. dispatch_source_cancel(timeoutTimer);
  470. #if NEEDS_DISPATCH_RETAIN_RELEASE
  471. dispatch_release(timeoutTimer);
  472. #endif
  473. timeoutTimer = NULL;
  474. }
  475. resolveInProgress = NO;
  476. }};
  477. if (dispatch_get_current_queue() == resolverQueue)
  478. block();
  479. else
  480. dispatch_sync(resolverQueue, block);
  481. }
  482. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  483. #pragma mark Utility Methods
  484. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  485. + (NSString *)srvNameFromXMPPDomain:(NSString *)xmppDomain
  486. {
  487. if (xmppDomain == nil)
  488. return nil;
  489. else
  490. return [NSString stringWithFormat:@"_xmpp-client._tcp.%@", xmppDomain];
  491. }
  492. @end
  493. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  494. #pragma mark -
  495. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  496. @implementation XMPPSRVRecord
  497. @synthesize priority;
  498. @synthesize weight;
  499. @synthesize port;
  500. @synthesize target;
  501. @synthesize sum;
  502. @synthesize srvResultsIndex;
  503. + (XMPPSRVRecord *)recordWithPriority:(UInt16)p1 weight:(UInt16)w port:(UInt16)p2 target:(NSString *)t
  504. {
  505. return [[XMPPSRVRecord alloc] initWithPriority:p1 weight:w port:p2 target:t];
  506. }
  507. - (id)initWithPriority:(UInt16)p1 weight:(UInt16)w port:(UInt16)p2 target:(NSString *)t
  508. {
  509. if ((self = [super init]))
  510. {
  511. priority = p1;
  512. weight = w;
  513. port = p2;
  514. target = [t copy];
  515. sum = 0;
  516. srvResultsIndex = 0;
  517. }
  518. return self;
  519. }
  520. - (NSString *)description
  521. {
  522. return [NSString stringWithFormat:@"<%@:%p target(%@) port(%hu) priority(%hu) weight(%hu)>",
  523. NSStringFromClass([self class]), self, target, port, priority, weight];
  524. }
  525. - (NSComparisonResult)compareByPriority:(XMPPSRVRecord *)aRecord
  526. {
  527. UInt16 mPriority = self.priority;
  528. UInt16 aPriority = aRecord.priority;
  529. if (mPriority < aPriority)
  530. return NSOrderedAscending;
  531. if (mPriority > aPriority)
  532. return NSOrderedDescending;
  533. return NSOrderedSame;
  534. }
  535. @end