* MYBonjourBrowser: Added delegate (no methods for it yet, just for client use.)
* MYBonjourRegistration: Added +canonicalFormOfTXTRecordDictionary:.
* MYBonjourService: Added back-reference to browser.
* IPAddress: Added conversions to/from struct sockaddr.
1.1 --- a/Bonjour/MYBonjourBrowser.h Fri Jul 03 17:50:28 2009 -0700
1.2 +++ b/Bonjour/MYBonjourBrowser.h Mon Jul 20 13:26:29 2009 -0700
1.3 @@ -20,6 +20,7 @@
1.4 NSMutableSet *_services, *_addServices, *_rmvServices;
1.5 BOOL _pendingUpdate;
1.6 MYBonjourRegistration *_myRegistration;
1.7 + id _delegate;
1.8 }
1.9
1.10 /** Initializes a new MYBonjourBrowser.
1.11 @@ -27,6 +28,8 @@
1.12 @param serviceType The name of the service type to look for, e.g. "_http._tcp". */
1.13 - (id) initWithServiceType: (NSString*)serviceType;
1.14
1.15 +@property (assign) id delegate;
1.16 +
1.17 /** Is the browser currently browsing? */
1.18 @property (readonly) BOOL browsing;
1.19
2.1 --- a/Bonjour/MYBonjourBrowser.m Fri Jul 03 17:50:28 2009 -0700
2.2 +++ b/Bonjour/MYBonjourBrowser.m Mon Jul 20 13:26:29 2009 -0700
2.3 @@ -62,7 +62,7 @@
2.4 }
2.5
2.6
2.7 -@synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
2.8 +@synthesize delegate=_delegate, browsing=_browsing, services=_services, serviceClass=_serviceClass;
2.9
2.10
2.11 - (NSString*) description
2.12 @@ -92,10 +92,11 @@
2.13 flags: (DNSServiceFlags)flags
2.14 {
2.15 // Create (or reuse existing) MYBonjourService object:
2.16 - MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
2.17 - type: regtype
2.18 - domain: domain
2.19 - interface: interfaceIndex];
2.20 + MYBonjourService *service = [[_serviceClass alloc] initWithBrowser: self
2.21 + name: serviceName
2.22 + type: regtype
2.23 + domain: domain
2.24 + interface: interfaceIndex];
2.25 if ([_myRegistration isSameAsService: service]) {
2.26 // This is an echo of my own registration, so ignore it
2.27 LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
3.1 --- a/Bonjour/MYBonjourRegistration.h Fri Jul 03 17:50:28 2009 -0700
3.2 +++ b/Bonjour/MYBonjourRegistration.h Mon Jul 20 13:26:29 2009 -0700
3.3 @@ -45,9 +45,11 @@
3.4 /** The registration's full name -- the name, type and domain concatenated together. */
3.5 @property (readonly) NSString *fullName;
3.6
3.7 +
3.8 /** Is the registration currently active? */
3.9 @property (readonly) BOOL registered;
3.10
3.11 +
3.12 /** The service's metadata dictionary, stored in its DNS TXT record */
3.13 @property (copy) NSDictionary *txtRecord;
3.14
3.15 @@ -76,6 +78,10 @@
3.16 /** Is this browsed service an echo of this local registration? (Compares fullNames.) */
3.17 - (BOOL) isSameAsService: (MYBonjourService*)service;
3.18
3.19 +/** Converts a TXT record dictionary to data in a consistent way.
3.20 + This is used when signing (and verifying signatures of) TXT records. */
3.21 ++ (NSData*) canonicalFormOfTXTRecordDictionary: (NSDictionary*)txtDict;
3.22 +
3.23 //@}
3.24
3.25 @end
4.1 --- a/Bonjour/MYBonjourRegistration.m Fri Jul 03 17:50:28 2009 -0700
4.2 +++ b/Bonjour/MYBonjourRegistration.m Mon Jul 20 13:26:29 2009 -0700
4.3 @@ -165,6 +165,65 @@
4.4 }
4.5
4.6
4.7 +static int compareData (id data1, id data2, void *context) {
4.8 + size_t length1 = [data1 length], length2 = [data2 length];
4.9 + int result = memcmp([data1 bytes], [data2 bytes], MIN(length1,length2));
4.10 + if (result==0) {
4.11 + if (length1>length2)
4.12 + result = 1;
4.13 + else if (length1<length2)
4.14 + result = -1;
4.15 + }
4.16 + return result;
4.17 +}
4.18 +
4.19 ++ (NSData*) canonicalFormOfTXTRecordDictionary: (NSDictionary*)txtDict
4.20 +{
4.21 + if (!txtDict)
4.22 + return nil;
4.23 +
4.24 + // First convert keys and values to NSData:
4.25 + NSMutableDictionary *dataDict = $mdict();
4.26 + for (NSString *key in txtDict) {
4.27 + if (![key hasPrefix: @"("]) { // ignore parenthesized keys
4.28 + if (![key isKindOfClass: [NSString class]]) {
4.29 + Warn(@"TXT dictionary cannot have %@ as key", [key class]);
4.30 + return nil;
4.31 + }
4.32 + NSData *keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
4.33 + if (keyData.length > 255) {
4.34 + Warn(@"TXT dictionary key too long: %@", key);
4.35 + return nil;
4.36 + }
4.37 + id value = [txtDict objectForKey: key];
4.38 + if (![value isKindOfClass: [NSData class]]) {
4.39 + value = [[value description] dataUsingEncoding: NSUTF8StringEncoding];
4.40 + }
4.41 + if ([value length] > 255) {
4.42 + Warn(@"TXT dictionary value too long: %@", value);
4.43 + return nil;
4.44 + }
4.45 + [dataDict setObject: value forKey: keyData];
4.46 + }
4.47 + }
4.48 +
4.49 + // Add key/value pairs, sorted by increasing key:
4.50 + NSMutableData *canonical = [NSMutableData dataWithCapacity: 1000];
4.51 + for (NSData *key in [[dataDict allKeys] sortedArrayUsingFunction: compareData context: NULL]) {
4.52 + // Append key prefixed with length:
4.53 + UInt8 length = [key length];
4.54 + [canonical appendBytes: &length length: sizeof(length)];
4.55 + [canonical appendData: key];
4.56 + // Append value prefixed with length:
4.57 + NSData *value = [dataDict objectForKey: key];
4.58 + length = [value length];
4.59 + [canonical appendBytes: &length length: sizeof(length)];
4.60 + [canonical appendData: value];
4.61 + }
4.62 + return canonical;
4.63 +}
4.64 +
4.65 +
4.66 - (void) updateTxtRecord {
4.67 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(updateTxtRecord) object: nil];
4.68 if (self.serviceRef) {
4.69 @@ -179,7 +238,7 @@
4.70 if (err)
4.71 Warn(@"%@ failed to update TXT (err=%i)", self,err);
4.72 else
4.73 - LogTo(Bonjour,@"%@ updated TXT to %@", self,data);
4.74 + LogTo(Bonjour,@"%@ updated TXT to %u bytes: %@", self,data.length,data);
4.75 }
4.76 }
4.77
5.1 --- a/Bonjour/MYBonjourService.h Fri Jul 03 17:50:28 2009 -0700
5.2 +++ b/Bonjour/MYBonjourService.h Mon Jul 20 13:26:29 2009 -0700
5.3 @@ -7,13 +7,14 @@
5.4 //
5.5
5.6 #import "MYDNSService.h"
5.7 -@class MYBonjourQuery, MYAddressLookup;
5.8 +@class MYBonjourBrowser, MYBonjourQuery, MYAddressLookup;
5.9
5.10
5.11 /** Represents a Bonjour service discovered by a MYBonjourBrowser. */
5.12 @interface MYBonjourService : MYDNSService
5.13 {
5.14 @private
5.15 + MYBonjourBrowser *_bonjourBrowser;
5.16 NSString *_name, *_fullName, *_type, *_domain, *_hostname;
5.17 uint32_t _interfaceIndex;
5.18 BOOL _startedResolve;
5.19 @@ -23,6 +24,9 @@
5.20 MYAddressLookup *_addressLookup;
5.21 }
5.22
5.23 +/** The browser I belong to. */
5.24 +@property (readonly) MYBonjourBrowser *bonjourBrowser;
5.25 +
5.26 /** The service's name. */
5.27 @property (readonly) NSString *name;
5.28
5.29 @@ -81,10 +85,11 @@
5.30
5.31 /** Designated initializer. You probably don't want to create MYBonjourService instances yourself,
5.32 but if you subclass you might need to override this initializer. */
5.33 -- (id) initWithName: (NSString*)serviceName
5.34 - type: (NSString*)type
5.35 - domain: (NSString*)domain
5.36 - interface: (UInt32)interfaceIndex;
5.37 +- (id) initWithBrowser: (MYBonjourBrowser*)browser
5.38 + name: (NSString*)serviceName
5.39 + type: (NSString*)type
5.40 + domain: (NSString*)domain
5.41 + interface: (UInt32)interfaceIndex;
5.42
5.43 /** Called when this service is officially added to its browser's service set.
5.44 You can override this, but be sure to call the superclass method. */
6.1 --- a/Bonjour/MYBonjourService.m Fri Jul 03 17:50:28 2009 -0700
6.2 +++ b/Bonjour/MYBonjourService.m Mon Jul 20 13:26:29 2009 -0700
6.3 @@ -29,16 +29,18 @@
6.4 @implementation MYBonjourService
6.5
6.6
6.7 -- (id) initWithName: (NSString*)serviceName
6.8 - type: (NSString*)type
6.9 - domain: (NSString*)domain
6.10 - interface: (UInt32)interfaceIndex
6.11 +- (id) initWithBrowser: (MYBonjourBrowser*)browser
6.12 + name: (NSString*)serviceName
6.13 + type: (NSString*)type
6.14 + domain: (NSString*)domain
6.15 + interface: (UInt32)interfaceIndex
6.16 {
6.17 Assert(serviceName);
6.18 Assert(type);
6.19 Assert(domain);
6.20 self = [super init];
6.21 if (self != nil) {
6.22 + _bonjourBrowser = browser;
6.23 _name = [serviceName copy];
6.24 _type = [type copy];
6.25 _domain = [domain copy];
6.26 @@ -62,8 +64,8 @@
6.27 }
6.28
6.29
6.30 -@synthesize name=_name, type=_type, domain=_domain, fullName=_fullName,
6.31 - hostname=_hostname, port=_port, interfaceIndex=_interfaceIndex;
6.32 +@synthesize bonjourBrowser=_bonjourBrowser, name=_name, type=_type, domain=_domain,
6.33 + fullName=_fullName, hostname=_hostname, port=_port, interfaceIndex=_interfaceIndex;
6.34
6.35
6.36 - (NSString*) description {
7.1 --- a/IPAddress.h Fri Jul 03 17:50:28 2009 -0700
7.2 +++ b/IPAddress.h Mon Jul 20 13:26:29 2009 -0700
7.3 @@ -42,6 +42,9 @@
7.4 /** Initializes an IPAddress from a BSD struct sockaddr. */
7.5 - (id) initWithSockAddr: (const struct sockaddr*)sockaddr;
7.6
7.7 +/** Initializes an IPAddress from NSData containing a BSD struct sockaddr. */
7.8 +- (id) initWithData: (NSData*)data;
7.9 +
7.10 /** Returns the IP address of this host (plus the specified port number).
7.11 If multiple network interfaces are active, the main one's address is returned. */
7.12 + (IPAddress*) localAddressWithPort: (UInt16)port;
7.13 @@ -71,6 +74,9 @@
7.14 /** The port number, or zero if none was specified, in native byte order. */
7.15 @property (readonly) UInt16 port;
7.16
7.17 +/** The address as an NSData object containing a struct sockaddr. */
7.18 +@property (readonly) NSData* asData;
7.19 +
7.20 /** Is this IP address in a designated private/local address range, such as 10.0.1.X?
7.21 If so, the address is not globally meaningful outside of the local subnet. */
7.22 @property (readonly) BOOL isPrivate; // In a private/local addr range like 10.0.1.X?
8.1 --- a/IPAddress.m Fri Jul 03 17:50:28 2009 -0700
8.2 +++ b/IPAddress.m Mon Jul 20 13:26:29 2009 -0700
8.3 @@ -97,6 +97,15 @@
8.4 return self;
8.5 }
8.6
8.7 +- (id) initWithData: (NSData*)data
8.8 +{
8.9 + const struct sockaddr* addr = data.bytes;
8.10 + if (data.length < sizeof(struct sockaddr_in))
8.11 + addr = nil;
8.12 + return [self initWithSockAddr: addr];
8.13 +}
8.14 +
8.15 +
8.16 + (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket
8.17 {
8.18 uint8_t name[SOCK_MAXADDRLEN];
8.19 @@ -165,6 +174,16 @@
8.20 return [self ipv4name];
8.21 }
8.22
8.23 +- (NSData*) asData
8.24 +{
8.25 + struct sockaddr_in addr = {
8.26 + .sin_len = sizeof(struct sockaddr_in),
8.27 + .sin_family = AF_INET,
8.28 + .sin_port = htons(_port),
8.29 + .sin_addr = {htonl(_ipv4)} };
8.30 + return [NSData dataWithBytes: &addr length: sizeof(addr)];
8.31 +}
8.32 +
8.33 - (NSString*) description
8.34 {
8.35 NSString *name = self.hostname ?: @"0.0.0.0";