Index: ObjXMPP.xcodeproj/project.pbxproj ================================================================== --- ObjXMPP.xcodeproj/project.pbxproj +++ ObjXMPP.xcodeproj/project.pbxproj @@ -121,17 +121,25 @@ 4B1295EE1337BD5F00154B25 /* test.m */, ); path = ObjXMPPTests; sourceTree = ""; }; - 4BC559851337A65400E345C7 = { + 4B484502138BD3A400EB48A5 /* Libraries */ = { isa = PBXGroup; children = ( 4B484500138BBEEB00EB48A5 /* libresolv.dylib */, + ); + name = Libraries; + sourceTree = ""; + }; + 4BC559851337A65400E345C7 = { + isa = PBXGroup; + children = ( 4BC5599A1337A65400E345C7 /* ObjXMPP */, 4B1295E41337BD2D00154B25 /* ObjXMPPTests */, 4BC559931337A65400E345C7 /* Frameworks */, + 4B484502138BD3A400EB48A5 /* Libraries */, 4BC559921337A65400E345C7 /* Products */, ); sourceTree = ""; }; 4BC559921337A65400E345C7 /* Products */ = { Index: src/XMPPConnection.m ================================================================== --- src/XMPPConnection.m +++ src/XMPPConnection.m @@ -198,10 +198,12 @@ { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; XMPPSRVEnumerator *SRVEnumerator = [XMPPSRVEnumerator enumeratorWithDomain: server]; XMPPSRVEntry *candidate; + + [SRVEnumerator lookUpEntries]; while ((candidate = [SRVEnumerator nextObject]) != nil) { @try { [sock connectToHost: [candidate target] onPort: [candidate port]]; Index: src/XMPPSRVEnumerator.h ================================================================== --- src/XMPPSRVEnumerator.h +++ src/XMPPSRVEnumerator.h @@ -19,51 +19,63 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include +#include + #import @interface XMPPSRVEntry: OFObject { uint16_t priority; - uint16_t weight; + uint16_t weight, accumulatedWeight; uint16_t port; OFString *target; } + #ifdef OF_HAVE_PROPERTIES -@property uint16_t priority; -@property uint16_t weight; -@property uint16_t port; -@property (copy) OFString *target; +@property (readonly, assign) uint16_t priority; +@property (readonly, assign) uint16_t weight; +@property (assign) uint16_t accumulatedWeight; +@property (readonly, assign) uint16_t port; +@property (readonly, copy) OFString *target; #endif -- (void) setPriority: (uint16_t)priority_; -- (uint16_t) priority; -- (void) setWeight: (uint16_t)weight_; -- (uint16_t) weight; -- (void) setPort: (uint16_t)port_; -- (uint16_t) port; -- (void) setTarget: (OFString*)target_; -- (OFString*) target; ++ entryWithPriority: (uint16_t)priority + weight: (uint16_t)weight + port: (uint16_t)port + target: (OFString*)target; ++ entryWithResourceRecord: (ns_rr)resourceRecord + handle: (ns_msg)handle; +- initWithPriority: (uint16_t)priority + weight: (uint16_t)weight + port: (uint16_t)port + target: (OFString*)target; +- initWithResourceRecord: (ns_rr)resourceRecord + handle: (ns_msg)handle; +- (uint16_t)priority; +- (uint16_t)weight; +- (uint16_t)port; +- (OFString*)target; @end -@interface XMPPSRVEnumerator: OFEnumerator +@interface XMPPSRVEnumerator: OFEnumerator { OFString *domain; - OFList *priorityList; + struct __res_state resState; + OFList *list; + of_list_object_t *listIter; + OFList *subListCopy; + BOOL done; } + #ifdef OF_HAVE_PROPERTIES -@property (copy) OFString *domain; +@property (readonly, copy) OFString *domain; #endif + enumeratorWithDomain: (OFString*)domain; - - initWithDomain: (OFString*)domain; -- (void) setDomain: (OFString*)domain; -- (OFString*) domain; -- (void)XMPP_parseSRVRRWithHandle: (const ns_msg)handle - RR: (const ns_rr)rr - result: (XMPPSRVEntry*)result; -- (void)XMPP_addSRVEntry: (XMPPSRVEntry*)item - toSortedPriorityList: (OFList*)list; +- (OFString*)domain; +- (void)lookUpEntries; +- (void)XMPP_addEntry: (XMPPSRVEntry*)item; @end Index: src/XMPPSRVEnumerator.m ================================================================== --- src/XMPPSRVEnumerator.m +++ src/XMPPSRVEnumerator.m @@ -1,6 +1,7 @@ /* + * Copyright (c) 2011, Jonathan Schleifer * Copyright (c) 2011, Florian Zeitz * * https://webkeks.org/hg/objxmpp/ * * Permission to use, copy, modify, and/or distribute this software for any @@ -17,76 +18,140 @@ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ + +#include + #include #include #include #include #include #include -#include #import "XMPPSRVEnumerator.h" @implementation XMPPSRVEntry ++ entryWithPriority: (uint16_t)priority + weight: (uint16_t)weight + port: (uint16_t)port + target: (OFString*)target +{ + return [[[self alloc] initWithPriority: priority + weight: weight + port: port + target: target] autorelease]; +} + ++ entryWithResourceRecord: (ns_rr)resourceRecord + handle: (ns_msg)handle +{ + return [[[self alloc] initWithResourceRecord: resourceRecord + handle: handle] autorelease]; +} + +- init +{ + Class c = isa; + [self release]; + @throw [OFNotImplementedException newWithClass: c + selector: _cmd]; +} + +- initWithPriority: (uint16_t)priority_ + weight: (uint16_t)weight_ + port: (uint16_t)port_ + target: (OFString*)target_ +{ + self = [super init]; + + @try { + priority = priority_; + weight = weight_; + port = port_; + target = [target_ copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- initWithResourceRecord: (ns_rr)resourceRecord + handle: (ns_msg)handle +{ + self = [super init]; + + @try { + const uint16_t *rdata; + char buffer[NS_MAXDNAME]; + + rdata = (const uint16_t*)(void*)ns_rr_rdata(resourceRecord); + priority = ntohs(rdata[0]); + weight = ntohs(rdata[1]); + port = ntohs(rdata[2]); + + if (dn_expand(ns_msg_base(handle), ns_msg_end(handle), + (uint8_t*)&rdata[3], buffer, NS_MAXDNAME) < 1) + @throw [OFInitializationFailedException + newWithClass: isa]; + + target = [[OFString alloc] initWithCString: buffer]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + - (void)dealloc { [target release]; [super dealloc]; } - (OFString*)description { - return [OFString stringWithFormat: @"priority: %" PRIu16 - @" weight: %" PRIu16 - @" target: %@:%" PRIu16, priority, weight, - target, port]; -} - -- (void)setPriority: (uint16_t)priority_ -{ - priority = priority_; + return [OFString stringWithFormat: @"<%@ priority: %" PRIu16 + @", weight: %" PRIu16 + @", target: %@:%" PRIu16 @">", + isa, priority, weight, target, port]; } - (uint16_t)priority { return priority; } -- (void)setWeight: (uint16_t)weight_ -{ - weight = weight_; -} - - (uint16_t)weight { return weight; } -- (void)setPort: (uint16_t)port_ +- (void)setAccumulatedWeight: (uint16_t)accumulatedWeight_ +{ + accumulatedWeight = accumulatedWeight_; +} + +- (uint16_t)accumulatedWeight { - port = port_; + return accumulatedWeight; } - (uint16_t)port { return port; } -- (void)setTarget: (OFString*)target_ -{ - OFString *old = target; - target = [target_ copy]; - [old release]; -} - - (OFString*)target { - return [[target copy] autorelease]; + OF_GETTER(target, YES) } @end @implementation XMPPSRVEnumerator + enumeratorWithDomain: (OFString*)domain_ @@ -95,166 +160,225 @@ } - initWithDomain: (OFString*)domain_ { self = [super init]; - priorityList = [[OFList alloc] init]; - [self setDomain: domain_]; + + @try { + list = [[OFList alloc] init]; + domain = [domain_ copy]; + } @catch (id e) { + [self release]; + @throw e; + } return self; } - (void)dealloc { - [priorityList release]; + [list release]; [domain release]; + [subListCopy release]; [super dealloc]; } -- (void)setDomain: (OFString*)domain_ -{ - OFString *old = domain; - domain = [domain_ copy]; - [old release]; - [self reset]; -} - - (OFString*)domain; { - return [[domain copy] autorelease]; -} - -- (void)XMPP_parseSRVRRWithHandle: (const ns_msg)handle - RR: (const ns_rr)rr - result: (XMPPSRVEntry*)result -{ - const uint16_t *rdata = (uint16_t*) ns_rr_rdata(rr); - char target[NS_MAXDNAME]; - [result setPriority: ntohs(rdata[0])]; - [result setWeight: ntohs(rdata[1])]; - [result setPort: ntohs(rdata[2])]; - dn_expand(ns_msg_base(handle), ns_msg_end(handle), - (uint8_t*) &rdata[3], target, NS_MAXDNAME); - [result setTarget: [OFString stringWithCString: target]]; + OF_GETTER(domain, YES) +} + +- (void)lookUpEntries +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + unsigned char *answer = NULL; + OFString *request; + + request = [OFString stringWithFormat: @"_xmpp-client._tcp.%@", domain]; + + @try { + int answerLen, resourceRecordCount, i; + ns_rr resourceRecord; + ns_msg handle; + + if (res_ninit(&resState)) + @throw [OFAddressTranslationFailedException + newWithClass: isa + socket: nil + host: domain]; + + answer = [self allocMemoryWithSize: of_pagesize]; + answerLen = res_nsearch(&resState, [request cString], ns_c_in, + ns_t_srv, answer, (int)of_pagesize); + + if (answerLen < 1 || answerLen > of_pagesize) + @throw [OFAddressTranslationFailedException + newWithClass: isa + socket: nil + host: domain]; + + if (ns_initparse(answer, answerLen, &handle)) + @throw [OFAddressTranslationFailedException + newWithClass: isa + socket: nil + host: domain]; + + resourceRecordCount = ns_msg_count(handle, ns_s_an); + for (i = 0; i < resourceRecordCount; i++) { + if (ns_parserr(&handle, ns_s_an, i, &resourceRecord)) + continue; + + if (ns_rr_type(resourceRecord) != ns_t_srv || + ns_rr_class(resourceRecord) != ns_c_in) + continue; + + [self XMPP_addEntry: [XMPPSRVEntry + entryWithResourceRecord: resourceRecord + handle: handle]]; + } + } @finally { + [self freeMemory: answer]; + res_ndestroy(&resState); + } + + [pool release]; +} + +- (void)XMPP_addEntry: (XMPPSRVEntry*)entry +{ + OFAutoreleasePool *pool; + OFList *subList; + of_list_object_t *iter; + + /* Look if there already is a list with the priority */ + for (iter = [list firstListObject]; iter != NULL; iter = iter->next) { + if ([[iter->object firstObject] priority] == [entry priority]) { + /* + * RFC 2782 says those with weight 0 should be at the + * beginning of the list. + */ + if ([entry weight] > 0) + [iter->object appendObject: entry]; + else + [iter->object prependObject: entry]; + + return; + } + + /* We can't have one if the priority is already bigger */ + if ([[iter->object firstObject] priority] > [entry priority]) + break; + } + + /* No list with the priority -> create one at the correct place */ + for (iter = [list firstListObject]; iter != NULL; iter = iter->next) { + if ([[iter->object firstObject] priority] > [entry priority]) { + OFAutoreleasePool *pool; + + pool = [[OFAutoreleasePool alloc] init]; + + subList = [OFList list]; + + /* + * RFC 2782 says those with weight 0 should be at the + * beginning of the list. + */ + if ([entry weight] > 0) + [subList appendObject: entry]; + else + [subList prependObject: entry]; + + [list insertObject: subList + beforeListObject: iter]; + + [pool release]; + + return; + } + } + + /* There is no list with a bigger priority -> append */ + pool = [[OFAutoreleasePool alloc] init]; + + subList = [OFList list]; + + /* + * RFC 2782 says those with weight 0 should be at the beginning of the + * list. + */ + if ([entry weight] > 0) + [subList appendObject: entry]; + else + [subList prependObject: entry]; + + [list appendObject: subList]; + + [pool release]; } - (id)nextObject { - if ([priorityList firstListObject]) { - uint16_t weight = 0; - of_list_object_t *iter; - XMPPSRVEntry *ret; - OFList *weightList = [priorityList firstObject]; - uint16_t maximumWeight = [[weightList lastObject] weight]; - - if (maximumWeight) { - RAND_pseudo_bytes((unsigned char *)&weight, 2); - weight %= maximumWeight; - } - - iter = [weightList firstListObject]; - while (iter) { - if (weight <= [iter->object weight]) { - ret = [iter->object retain]; - [weightList removeListObject: iter]; - if (![weightList firstListObject]) - [priorityList removeListObject: - [priorityList firstListObject]]; - return [ret autorelease]; - } - iter = iter->next; - } - assert(0); - } - - return nil; -} - -- (int)countByEnumeratingWithState: (of_fast_enumeration_state_t*)state - objects: (id*)objects - count: (int)count -{ - int len = 0; - XMPPSRVEntry *entry = [self nextObject]; - state->itemsPtr = objects; - while ((len < count) && entry) { - state->mutationsPtr = (unsigned long *)self; - objects[len++] = entry; - entry = [self nextObject]; - } - return len; + XMPPSRVEntry *ret; + of_list_object_t *iter; + uint32_t totalWeight = 0; + BOOL found = NO; + + if (done) + return nil; + + if (listIter == NULL) + listIter = [list lastListObject]; + + if (listIter == NULL) + return nil; + + if (subListCopy == nil) + subListCopy = [listIter->object copy]; + + /* FIXME: Handle empty subListCopy */ + for (iter = [subListCopy firstListObject]; iter != NULL; + iter = iter->next) { + totalWeight += [iter->object weight]; + [iter->object setAccumulatedWeight: totalWeight]; + } + + while (!found) { + uint32_t randomWeight; + + RAND_pseudo_bytes((uint8_t*)&randomWeight, sizeof(uint32_t)); + randomWeight %= (totalWeight + 1); + + for (iter = [subListCopy firstListObject]; iter != NULL; + iter = iter->next) { + if ([iter->object accumulatedWeight] >= randomWeight) { + ret = [[iter->object retain] autorelease]; + + [subListCopy removeListObject: iter]; + + found = YES; + break; + } + } + } + + if ([subListCopy count] == 0) { + [subListCopy release]; + subListCopy = nil; + + listIter = listIter->previous; + + if (listIter == NULL) + done = YES; + } + + return ret; } - (void)reset { - int i, rrCount; - unsigned char *answer; - OFString *request; - OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; - - request = [OFString stringWithFormat: @"_xmpp-client._tcp.%@", domain]; - answer = [self allocMemoryWithSize: NS_MAXMSG]; - - res_ninit(&_res); - if (!(res_nsearch(&_res, [request cString], ns_c_in, ns_t_srv, answer, - NS_MAXMSG) < 0)) { - ns_rr rr; - ns_msg handle; - - ns_initparse(answer, NS_MAXMSG, &handle); - rrCount = ns_msg_count(handle, ns_s_an); - for (i = 0; i < rrCount ; i++) { - XMPPSRVEntry *result = [[XMPPSRVEntry alloc] init]; - ns_parserr(&handle, ns_s_an, i, &rr); - if ((ns_rr_type(rr) != ns_t_srv) - || ns_rr_class(rr) != ns_c_in) - @throw [OFInvalidServerReplyException - newWithClass: isa]; - - [self XMPP_parseSRVRRWithHandle: handle - RR: rr - result: result]; - - [self XMPP_addSRVEntry: result - toSortedPriorityList: priorityList]; - } - } - [self freeMemory: answer]; - [pool release]; - - -} - -- (void)XMPP_addSRVEntry: (XMPPSRVEntry*)item - toSortedPriorityList: (OFList*)list -{ - of_list_object_t *priorityIter = - [list firstListObject]; - while (1) { - if (priorityIter == NULL || - [[priorityIter->object firstObject] - priority] > [item priority]) { - OFList *newList = [OFList list]; - [newList appendObject: item]; - if (priorityIter) - [list insertObject: newList - beforeListObject: priorityIter]; - else - [list appendObject: newList]; - break; - } - if ([[priorityIter->object firstObject] priority] - == [item priority]) { - if ([item weight] == 0) - [priorityIter->object prependObject: item]; - else { - [item setWeight: [item weight] + - [[priorityIter->object lastObject] weight]]; - [priorityIter->object appendObject: item]; - } - break; - } - priorityIter = priorityIter->next; - } + listIter = NULL; + [subListCopy release]; + subListCopy = nil; + done = NO; } @end