* MYBonjourBrowser: Added delegate (no methods for it yet, just for client use.)
authorJens Alfke <jens@mooseyard.com>
Mon Jul 20 13:26:29 2009 -0700 (2009-07-20)
changeset 5946c7844cb592
parent 58 6577813acf12
child 60 dd637bdd214e
* 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.
Bonjour/MYBonjourBrowser.h
Bonjour/MYBonjourBrowser.m
Bonjour/MYBonjourRegistration.h
Bonjour/MYBonjourRegistration.m
Bonjour/MYBonjourService.h
Bonjour/MYBonjourService.m
IPAddress.h
IPAddress.m
     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";