/* Implementation for NSNetServices for GNUstep
   Copyright (C) 2006 Free Software Foundation, Inc.

   Written by:  Chris B. Vetter
   Date: 2006
   
   This file is part of the GNUstep Base Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02111 USA.
   */ 

#import "Foundation/NSNetServices.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSEnumerator.h"
#import "Foundation/NSData.h"
#import "Foundation/NSDebug.h"
#import "Foundation/NSNull.h"
#import "Foundation/NSRunLoop.h"
#import "Foundation/NSStream.h"
#import "Foundation/NSTimer.h"
#import "Foundation/NSValue.h"
#if defined(_REENTRANT)
#import "GNUstepBase/GSLock.h"
#endif

#import <dns_sd.h>		// Apple's DNS Service Discovery

#import <sys/types.h>
#import <sys/socket.h>		// AF_INET / AF_INET6

#import <netinet/in.h>		// struct sockaddr_in / sockaddr_in6
#import <arpa/inet.h>		// inet_pton(3)

//
// Define
//

#if ! defined(INET6_ADDRSTRLEN)
#  define INET6_ADDRSTRLEN	46
#endif

// trigger runloop timer every INTERVAL seconds
#define INTERVAL		0.3
#define SHORTTIMEOUT		0.25

// debugging stuff and laziness on my part
#if defined(VERBOSE)
#  define INTERNALTRACE	NSDebugLLog(@"Trace", @"%s", __PRETTY_FUNCTION__)
#  define LOG(f, args...)	NSDebugLLog(@"NSNetServices", f, ##args)
#else
#  define INTERNALTRACE
#  define LOG(f, args...)
#endif /* VERBOSE */

#if ! defined(VERSION)
#  define VERSION (((GNUSTEP_BASE_MAJOR_VERSION * 100)			\
		  + GNUSTEP_BASE_MINOR_VERSION) * 100)			\
		  + GNUSTEP_BASE_SUBMINOR_VERSION
#endif

#define SETVERSION(aClass)						\
        do {								\
          if (self == [aClass class]) { [self setVersion: VERSION]; }	\
          else { [self doesNotRecognizeSelector: _cmd]; }		\
        } while(0);

#if defined(_REENTRANT)
#  define THE_LOCK		GSLazyRecursiveLock	*lock
#  define CREATELOCK(x)		x->lock = [GSLazyRecursiveLock new]
#  define LOCK(x)		[x->lock lock]
#  define UNLOCK(x)		[x->lock unlock]
#  define DESTROYLOCK(x)	DESTROY(x->lock)
#else
#  define THE_LOCK		/* nothing */
#  define CREATELOCK(x)		/* nothing */
#  define LOCK(x)		/* nothing */
#  define UNLOCK(x)		/* nothing */
#  define DESTROYLOCK(x)	/* nothing */
#endif

//
// Typedef
//

typedef struct _Browser		// The actual NSNetServiceBrowser
{
  THE_LOCK;
  
  NSRunLoop		*runloop;
  NSString		*runloopmode;
  NSTimer		*timer;			// to control the runloop
  
  NSMutableDictionary	*services;
    // List of found services.
    // Key is <_name_type_domain> and value is an initialized NSNetService.
  
  int			 interfaceIndex;
} Browser;

typedef struct _Service		// The actual NSNetService
{
  THE_LOCK;
  
  NSRunLoop		*runloop;
  NSString		*runloopmode;
  NSTimer		*timer,			// to control the runloop
                        *timeout;		// to time-out the resolve
  
  NSMutableDictionary	*info;
    // The service's information, keys are
    // - Domain (string)
    // - Name (string)
    // - Type (string)
    // - Host (string)
    // - Addresses (mutable array)
    // - TXT (data)
  
  NSMutableArray	*foundAddresses;	// array of char*
  
  int			 interfaceIndex,	// should also be in 'info'
                         port;			// ditto
  
  id			 monitor;		// NSNetServiceMonitor
  
  BOOL			 isPublishing,		// true if publishing service
                         isMonitoring;		// true if monitoring
} Service;

typedef struct _Monitor		// The actual NSNetServiceMonitor
{
  THE_LOCK;
  
  NSRunLoop		*runloop;
  NSString		*runloopmode;
  NSTimer		*timer;			// to control the runloop
} Monitor;

//
// Public
//

/**
 * This key identifies the most recent error.
 */
NSString * const NSNetServicesErrorCode = @"NSNetServicesErrorCode";

/**
 * This key identifies the originator of the error.
 */
NSString * const NSNetServicesErrorDomain = @"NSNetServicesErrorDomain";

//
// Private
//

//
// Private Interface
//

@interface NSNetServiceMonitor : NSObject
{
  @private
  void		* _netServiceMonitor;
  id		  _delegate;
  void		* _reserved;
}

- (id) initWithDelegate: (id) delegate;

- (void) removeFromRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode;
- (void) scheduleInRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode;

- (void) start;
- (void) stop;

@end

//
// Prototype
//

static NSDictionary *CreateError(id sender, int errorCode);

static int ConvertError(int errorCode);

static void DNSSD_API
  // used by NSNetServiceBrowser
  EnumerationCallback(DNSServiceRef		 sdRef,
                      DNSServiceFlags		 flags,
                      uint32_t			 interfaceIndex,
                      DNSServiceErrorType	 errorCode,
                      const char		*replyDomain,
                      void			*context);

static void DNSSD_API
  BrowserCallback(DNSServiceRef			 sdRef,
                  DNSServiceFlags		 flags,
                  uint32_t			 interfaceIndex,
                  DNSServiceErrorType		 errorCode,
                  const char			*replyName,
                  const char			*replyType,
                  const char			*replyDomain,
                  void				*context);

static void DNSSD_API
  // used by NSNetService
  ResolverCallback(DNSServiceRef		 sdRef,
                   DNSServiceFlags		 flags,
                   uint32_t			 interfaceIndex,
                   DNSServiceErrorType		 errorCode,
                   const char			*fullname,
                   const char			*hosttarget,
                   uint16_t			 port,
                   uint16_t			 txtLen,
                   const char			*txtRecord,
                   void				*context);

static void DNSSD_API
  RegistrationCallback(DNSServiceRef		 sdRef,
                       DNSServiceFlags		 flags,
                       DNSServiceErrorType	 errorCode,
                       const char		*name,
                       const char		*regtype,
                       const char		*domain,
                       void			*context);

static void DNSSD_API
  // used by NSNetService and NSNetServiceMonitor
  QueryCallback(DNSServiceRef			 sdRef,
                DNSServiceFlags			 flags,
                uint32_t			 interfaceIndex,
                DNSServiceErrorType		 errorCode,
                const char			*fullname,
                uint16_t			 rrtype,
                uint16_t			 rrclass,
                uint16_t			 rdlen,
                const void			*rdata,
                uint32_t			 ttl,
                void				*context);

/***************************************************************************
**
** Implementation
**
*/

@implementation NSNetServiceBrowser

/**
 * <em>Description forthcoming</em>
 *
 *
 */

+ (void) initialize
{
  INTERNALTRACE;
  
  SETVERSION(NSNetServiceBrowser);
  {
#ifndef _REENTRANT
    LOG(@"%@ may NOT be thread-safe!", [self class]);
#endif
  }
}

/***************************************************************************
**
** Private Methods
**
*/

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) cleanup
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    if (browser->runloop)
      {
	[self removeFromRunLoop: browser->runloop
			forMode: browser->runloopmode];
      }
    
    if (browser->timer)
      {
	[browser->timer invalidate];
	DESTROY(browser->timer);
      }
    
    if (_netServiceBrowser)
      {
	DNSServiceRefDeallocate(_netServiceBrowser);
	_netServiceBrowser = NULL;
      }
    
    [browser->services removeAllObjects];
  }
  UNLOCK(browser);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) executeWithError: (DNSServiceErrorType) err
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    if (kDNSServiceErr_NoError == err)
      {
	[self netServiceBrowserWillSearch: self];
	
	if (! browser->runloop)
	  {
	    [self scheduleInRunLoop: [NSRunLoop currentRunLoop]
			    forMode: NSDefaultRunLoopMode];
	  }
	
	[browser->runloop addTimer: browser->timer
			   forMode: browser->runloopmode];
	
	[browser->timer fire];
      }
    else // notify the delegate of the error
      {
	[self netServiceBrowser: self
		   didNotSearch: CreateError(self, err)];
      }
  }
  UNLOCK(browser);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) searchForDomain: (int) aFlag
{
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  Browser		*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    do
      {
	if (! _delegate)
	  {
	    err = NSNetServicesInvalidError;
	    break;
	  }
	
	if (browser->timer)
	  {
	    err = NSNetServicesActivityInProgress;
	    break;
	  }
	
	err = DNSServiceEnumerateDomains((DNSServiceRef *)&_netServiceBrowser,
	  aFlag,
	  browser->interfaceIndex,
	  EnumerationCallback,
	  self);
      }
    while(0);
  }
  UNLOCK(browser);
  
  [self executeWithError: err];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) enumCallback: (DNSServiceRef) sdRef
                flags: (DNSServiceFlags) flags
            interface: (uint32_t) interfaceIndex
                error: (DNSServiceErrorType) errorCode
               domain: (const char *) replyDomain
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  
  if (_netServiceBrowser)
    {
      if (errorCode)
	{
	  [self cleanup];
	  
	  [self netServiceBrowser: self
		     didNotSearch: CreateError(self, errorCode)];
	}
      else
	{  
	  BOOL	more = NO;
	  
	  if (replyDomain)
	    {
	      NSString	*domain;

	      more = flags & kDNSServiceFlagsMoreComing;
	      
	      browser->interfaceIndex = interfaceIndex;
	      
	      domain = [NSString stringWithUTF8String: replyDomain];

	      if (flags & kDNSServiceFlagsAdd)
		{
		  LOG(@"Found domain <%s>", replyDomain);
		  
		  [self netServiceBrowser: self
			    didFindDomain: domain
			       moreComing: more];
		}
	      else // kDNSServiceFlagsRemove
		{
		  LOG(@"Removed domain <%s>", replyDomain);
		  
		  [self netServiceBrowser: self
			  didRemoveDomain: domain
			       moreComing: more];
		}
	    }
	}
    }
  UNLOCK(browser);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) browseCallback: (DNSServiceRef) sdRef
                  flags: (DNSServiceFlags) flags
              interface: (uint32_t) interfaceIndex
                  error: (DNSServiceErrorType) errorCode
                   name: (const char *) replyName
                   type: (const char *) replyType
                 domain: (const char *) replyDomain
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  
  if (_netServiceBrowser)
    {
      if (errorCode)
	{
	  [self cleanup];
		
	  [self netServiceBrowser: self
		     didNotSearch: CreateError(self, errorCode)];
	}
      else
	{
	  NSNetService	*service = nil;
	  NSString	*domain = nil;
	  NSString	*type = nil;
	  NSString	*name = nil;
	  NSString	*key = nil;
	  BOOL		more = (flags & kDNSServiceFlagsMoreComing);
	  
	  browser->interfaceIndex = interfaceIndex;
	  
	  if (nil == browser->services)
	    {
	      browser->services
		= [[NSMutableDictionary alloc] initWithCapacity: 1];
	    }
	
	  domain = [NSString stringWithUTF8String: replyDomain];
	  type = [NSString stringWithUTF8String: replyType];
	  name = [NSString stringWithUTF8String: replyName];
	  
	  key = [NSString stringWithFormat: @"%@%@%@", name, type, domain];
	  
	  if (flags & kDNSServiceFlagsAdd)
	    {
	      service = [[NSNetService alloc] initWithDomain: domain
							type: type
							name: name];
	      
	      if (service)
		{
		  LOG(@"Found service <%s>", replyName);
		  
		  [self netServiceBrowser: self
			   didFindService: service
			       moreComing: more];
		  
		  [browser->services setObject: service
					forKey: key];
		  
		  [service autorelease];
		}
	      else
		{
		  LOG(@"WARNING: Could not create an NSNetService for <%s>",
		    replyName);
		}
	    }
	  else // kDNSServiceFlagsRemove
	    {
	      service = [browser->services objectForKey: key];
	      
	      if (service)
		{
		  LOG(@"Removed service <%@>", [service name]);
		  
		  [self netServiceBrowser: self
			 didRemoveService: service
			       moreComing: more];
		}
	      else
		{
		  LOG(@"WARNING: Could not find <%@> in list", key);
		}
	    }
	}
    }
  UNLOCK(browser);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) loop: (id) sender
{
  int			sock = 0;
  struct timeval	tout = { 0 };
  fd_set		set;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  
  sock = DNSServiceRefSockFD(_netServiceBrowser);
  
  if (-1 != sock)
    {
      FD_ZERO(&set);
      FD_SET(sock, &set);
      
      if (1 == select(sock + 1, &set, (fd_set *) NULL, (fd_set *) NULL, &tout))
	{
	  err = DNSServiceProcessResult(_netServiceBrowser);
	}
    }
  
  if (kDNSServiceErr_NoError != err)
    {
      [self netServiceBrowser: self
		 didNotSearch: CreateError(self, err)];
    }
}

/**
 * Removes the receiver from the specified runloop.
 *
 *
 */
- (void) removeFromRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    if (browser->timer)
      {
	[browser->timer setFireDate: [NSDate date]];
	[browser->timer invalidate];
	browser->timer = nil;
      }
    
    // Do not release the runloop!
    browser->runloop = nil;
    
    DESTROY(browser->runloopmode);
  }
  UNLOCK(browser);
}

/**
 * Adds the receiver to the specified runloop.
 *
 *
 */

- (void) scheduleInRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    if (browser->timer)
      {
	[browser->timer setFireDate: [NSDate date]];
	[browser->timer invalidate];
	browser->timer = nil;
      }
    
    browser->timer = [NSTimer timerWithTimeInterval: INTERVAL
                                             target: self
                                           selector: @selector(loop:)
                                           userInfo: nil
                                            repeats: YES];
    
    browser->runloop = aRunLoop;
    browser->runloopmode = mode;
    
    [browser->timer retain];
  }
  UNLOCK(browser);
}

/**
 * Search for all visible domains. This method is deprecated.
 *
 *
 */

- (void) searchForAllDomains
{
  DNSServiceFlags	flags = 0;
  
  INTERNALTRACE;
  
  flags = kDNSServiceFlagsBrowseDomains|kDNSServiceFlagsRegistrationDomains;
  [self searchForDomain: flags];
}

/**
 * Search for all browsable domains.
 *
 *
 */

- (void) searchForBrowsableDomains
{
  INTERNALTRACE;
  
  [self searchForDomain: kDNSServiceFlagsBrowseDomains];
}

/**
 * Search for all registration domains. These domains can be used to register
 * a service.
 *
 */

- (void) searchForRegistrationDomains
{
  INTERNALTRACE;
  
  [self searchForDomain: kDNSServiceFlagsRegistrationDomains];
}

/**
 * Search for a particular service within a given domain.
 *
 *
 */

- (void) searchForServicesOfType: (NSString *) serviceType
                        inDomain: (NSString *) domainName
{
  Browser		*browser;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  DNSServiceFlags	flags = 0;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    do
      {
	if (! _delegate)
	  {
	    err = NSNetServicesInvalidError;
	    break;
	  }
	
	if (browser->timer)
	  {
	    err = NSNetServicesActivityInProgress;
	    break;
	  }
	
	err = DNSServiceBrowse((DNSServiceRef *) &_netServiceBrowser,
	  flags,
	  browser->interfaceIndex,
	  [serviceType UTF8String],
	  [domainName UTF8String],
	  BrowserCallback,
	  self);
      }
    while(0);
  }
  UNLOCK(browser);
  
  [self executeWithError: err];
}

/**
 * Halts all currently running searches.
 *
 *
 */

- (void) stop
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  
  LOCK(browser);
  {
    [self cleanup];
    
    [self netServiceBrowserDidStopSearch: self];
  }
  UNLOCK(browser);
}

/**
 * Returns the receiver's delegate.
 *
 *
 */

- (id) delegate
{
  INTERNALTRACE;
  
  return [[_delegate retain] autorelease];
}

/**
 * Sets the receiver's delegate.
 *
 *
 */

- (void) setDelegate: (id) delegate
{
  INTERNALTRACE;
  
  ASSIGN(_delegate, delegate);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowserWillSearch: (NSNetServiceBrowser *) aBrowser
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceBrowserWillSearch:)])
    {
      [_delegate netServiceBrowserWillSearch: aBrowser];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowser: (NSNetServiceBrowser *) aBrowser
              didNotSearch: (NSDictionary *) errorDict
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowser:didNotSearch:)])
    {
      [_delegate netServiceBrowser: aBrowser
		      didNotSearch: errorDict];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowserDidStopSearch: (NSNetServiceBrowser *) aBrowser
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowserDidStopSearch:)])
    {
      [_delegate netServiceBrowserDidStopSearch: aBrowser];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowser: (NSNetServiceBrowser *) aBrowser
             didFindDomain: (NSString *) domainString
                moreComing: (BOOL) moreComing
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowser:didFindDomain:moreComing:)])
    {
      [_delegate netServiceBrowser: aBrowser
		     didFindDomain: domainString
			moreComing: moreComing];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowser: (NSNetServiceBrowser *) aBrowser
           didRemoveDomain: (NSString *) domainString
                moreComing: (BOOL) moreComing
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowser:didRemoveDomain:moreComing:)])
    {
      [_delegate netServiceBrowser: aBrowser
		   didRemoveDomain: domainString
			moreComing: moreComing];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowser: (NSNetServiceBrowser *) aBrowser
            didFindService: (NSNetService *) aService
                moreComing: (BOOL) moreComing
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowser:didFindService:moreComing:)])
    {
      [_delegate netServiceBrowser: aBrowser
		    didFindService: aService
			moreComing: moreComing];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceBrowser: (NSNetServiceBrowser *) aBrowser
          didRemoveService: (NSNetService *) aService
                moreComing: (BOOL) moreComing
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netServiceBrowser:didRemoveService:moreComing:)])
    {
      [_delegate netServiceBrowser: aBrowser
		  didRemoveService: aService
			moreComing: moreComing];
    }
}

/** <init />
 * Initializes the receiver.
 *
 *
 */

- (id) init
{
  INTERNALTRACE;
  
  if ((self = [super init]))
    {
      Browser	*browser;
      
      browser = malloc(sizeof (struct _Browser));
      memset(browser, 0, sizeof browser);
      
      CREATELOCK(browser);
      
      browser->runloop = nil;
      browser->runloopmode = nil;
      browser->timer = nil;
      
      browser->services = [[NSMutableDictionary alloc] initWithCapacity: 1];
      
      browser->interfaceIndex = 0;
      
      _netServiceBrowser = NULL;
      _delegate = nil;
      _reserved = browser;
    }
  return self;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) dealloc
{
  Browser	*browser;
  
  INTERNALTRACE;
  
  browser = (Browser *) _reserved;
  {
    LOCK(browser);
    {
      [self cleanup];
      
      DESTROY(browser->services);
      
      _delegate = nil;
    }
    UNLOCK(browser);
    
    DESTROYLOCK(browser);
    
    free(browser);
  }
  [super dealloc];
}

@end

@implementation NSNetService

/**
 * <em>Description forthcoming</em>
 *
 *
 */

+ (void) initialize
{
  INTERNALTRACE;
  
  SETVERSION(NSNetService);
  {
#ifndef _REENTRANT
    LOG(@"%@ may NOT be thread-safe!", [self class]);
#endif
  }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) executeWithError: (DNSServiceErrorType) err
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    if (kDNSServiceErr_NoError == err)
      {
	if (YES == service->isPublishing)
	  {
	    [self netServiceWillPublish: self];
	  }
	else
	  {
	    [self netServiceWillResolve: self];
	  }
	
	if (! service->runloop)
	  {
	    [self scheduleInRunLoop: [NSRunLoop currentRunLoop]
			    forMode: NSDefaultRunLoopMode];
	  }
	
	[service->runloop addTimer: service->timer
			   forMode: service->runloopmode];
	
	[service->timer fire];
      }
    else // notify the delegate of the error
      {
	if (YES == service->isPublishing)
	  {
	    [self netService: self
	       didNotPublish: CreateError(self, err)];
	  }
	else
	  {
	    [self netService: self
	       didNotResolve: CreateError(self, err)];
	  }
      }
  }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) cleanup
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    if (service->runloop)
      {
	[self removeFromRunLoop: service->runloop
			forMode: service->runloopmode];
      }
    
    if (service->timer)
      {
	[service->timer invalidate];
	DESTROY(service->timer);
      }
    
    if (_netService)
      {
	DNSServiceRefDeallocate(_netService);
	_netService = NULL;
      }
    
    [service->info removeAllObjects];
    [service->foundAddresses removeAllObjects];
  }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) stopResolving: (id) sender
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    [service->timeout invalidate];
    [service->timer invalidate];
    
    [self netService: self
       didNotResolve: CreateError(self, NSNetServicesTimeoutError)];
  }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) resolverCallback: (DNSServiceRef) sdRef
                    flags: (DNSServiceFlags) flags
                interface: (uint32_t) interfaceIndex
                    error: (DNSServiceErrorType) errorCode
                 fullname: (const char *) fullname
                   target: (const char *) hosttarget
                     port: (uint16_t) port
                   length: (uint16_t) txtLen
                   record: (const char *) txtRecord
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  
  if (_netService)
    {
      if (errorCode)
	{
	  [self cleanup];
	  
	  [self netService: self
	     didNotResolve: CreateError(self, errorCode)];
	}
      else
	{
	  NSData	*txt = nil;
	  NSString	*target = nil;
	  
	  // Add the TXT record
	  txt = txtRecord
	    ? [[NSData alloc] initWithBytes: txtRecord length: txtLen]
	    : nil;
	  
	  // Get the host
	  target = hosttarget
	    ? [[NSString alloc] initWithUTF8String: hosttarget]
	    : nil;
	  
	  // Add the port
	  service->port = ntohs(port);
	  
	  // Remove the old TXT entry
	  [service->info removeObjectForKey: @"TXT"];
	  
	  if (txt)
	    {
	      [service->info setObject: txt forKey: @"TXT"];
	      [txt release];
	    }
	  
	  // Remove the old host entry
	  [service->info removeObjectForKey: @"Host"];
	  
	  // Add the host if there is one
	  if (target)
	    {
	      [service->info setObject: target forKey: @"Host"];
	      [target release];
	    }
	  
	  /* Add the interface so all subsequent
	   * queries are on the same interface
	   */
	  service->interfaceIndex = interfaceIndex;
	  
	  service->timer = nil;
	  
	  // Prepare query for A and/or AAAA record
	  errorCode = DNSServiceQueryRecord((DNSServiceRef *) &_netService,
	    flags,
	    interfaceIndex,
	    hosttarget,
	    kDNSServiceType_ANY,
	    kDNSServiceClass_IN,
	    QueryCallback,
	    self);
	  
	  // No error? Then create a new timer
	  if (kDNSServiceErr_NoError == errorCode)
	    {
	      service->timer = [NSTimer timerWithTimeInterval: INTERVAL
						       target: self
						     selector: @selector(loop:)
						     userInfo: nil
						      repeats: YES];
	      [service->timer fire];
	    }
	}
    }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (BOOL) addAddress: (char *) addressString
{
  Service	*service;
  NSString	*string;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  if (nil == service->foundAddresses)
    {
      service->foundAddresses = [[NSMutableArray alloc] init];
    }
  
  string = [NSString stringWithCString: addressString];
  if ([service->foundAddresses containsObject: string])
    {
      // duplicate, didn't add it
      return NO;
    }
  
  [service->foundAddresses addObject: string];

  return YES;
}


/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) addAddress: (const void *) rdata
             length: (uint16_t) rdlen
               type: (uint16_t) rrtype
          interface: (uint32_t) interfaceIndex
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    NSData		*data = nil;
    NSMutableArray	*addresses = nil;
    struct sockaddr	*address = { 0 };
    size_t		length = 0;
    const unsigned char	*rd = rdata;
    char		rdb[INET6_ADDRSTRLEN];
    
    memset(rdb, 0, sizeof rdb);
    
    addresses = [service->info objectForKey: @"Addresses"];
    
    if (nil == addresses)
      {
	addresses = [[NSMutableArray alloc] initWithCapacity: 1];
      }
    
    switch(rrtype)
      {
	case kDNSServiceType_A:		// AF_INET
	  {
	    struct sockaddr_in	ip4;
	    
	    // oogly
	    sprintf(rdb, "%d.%d.%d.%d", rd[0], rd[1], rd[2], rd[3]);
	    LOG(@"Found IPv4 <%s> on port %d", rdb, service->port);
	    
	    length = sizeof (struct sockaddr_in);
	    memset(&ip4, 0, length);
	    
	    inet_pton(AF_INET, rdb, &ip4.sin_addr);
	    ip4.sin_family = AF_INET;
	    ip4.sin_port = htons(service->port);
	    
	    address = (struct sockaddr *) &ip4;
	  }
	  break;
	
  #if defined(AF_INET6)
	case kDNSServiceType_AAAA:	// AF_INET6
	case kDNSServiceType_A6:		// deprecates AAAA
	  {
	    struct sockaddr_in6	ip6;
	    
	    // Even more oogly
	    sprintf(rdb, "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x",
			 rd[0], rd[1], rd[2], rd[3],
			 rd[4], rd[5], rd[6], rd[7],
			 rd[8], rd[9], rd[10], rd[11],
			 rd[12], rd[13], rd[14], rd[15]);
	    LOG(@"Found IPv6 <%s> on port %d", rdb, service->port);
	    
	    length = sizeof (struct sockaddr_in6);
	    memset(&ip6, 0, length);
	    
	    inet_pton(AF_INET6, rdb, &ip6.sin6_addr);
#if defined(HAVE_SA_LEN)
	    ip6.sin6_len = sizeof ip6;
#endif
	    ip6.sin6_family = AF_INET6;
	    ip6.sin6_port = htons(service->port);
	    ip6.sin6_flowinfo = 0;
	    ip6.sin6_scope_id = interfaceIndex;
	    
	    address = (struct sockaddr *) &ip6;
	  }
	  break;
#endif /* AF_INET6 */      
	
	default:
	  LOG(@"Unkown type of length <%d>", rdlen);
	  break;
      }
    
    // check for duplicate entries
    if ([self addAddress: rdb])
      {
	// add it
	data = [NSData dataWithBytes: address
			      length: length];
	
	[addresses addObject: data];
	[service->info setObject: [addresses retain]
			  forKey: @"Addresses"];
	
	// notify the delegate
	[self netServiceDidResolveAddress: self];
	
	[addresses release];
	
	// got it, so invalidate the timeout
	[service->timeout invalidate];
	service->timeout = nil;
      }
  }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) queryCallback: (DNSServiceRef) sdRef
                 flags: (DNSServiceFlags) flags
             interface: (uint32_t) interfaceIndex
                 error: (DNSServiceErrorType) errorCode
              fullname: (const char *) fullname
                  type: (uint16_t) rrtype
                 class: (uint16_t) rrclass
                length: (uint16_t) rdlen
                  data: (const void *) rdata
                   ttl: (uint32_t) ttl
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  
  if (_netService)
    {
      if (errorCode)
	{
	  [self cleanup];
	  
	  [self netService: self
	     didNotResolve: CreateError(self, errorCode)];
	  
	  UNLOCK(service);
	  
	  return;
	}
      
      switch(rrtype)
	{
	  case kDNSServiceType_A:		// 1 -- AF_INET
	    [self addAddress: rdata
		      length: rdlen
			type: rrtype
		   interface: interfaceIndex];
	    break;
	  
	  case kDNSServiceType_NS:
	  case kDNSServiceType_MD:
	  case kDNSServiceType_MF:
	  case kDNSServiceType_CNAME:	// 5
	  case kDNSServiceType_SOA:
	  case kDNSServiceType_MB:
	  case kDNSServiceType_MG:
	  case kDNSServiceType_MR:
	  case kDNSServiceType_NULL:	// 10
	  case kDNSServiceType_WKS:
	  case kDNSServiceType_PTR:
	  case kDNSServiceType_HINFO:
	  case kDNSServiceType_MINFO:
	  case kDNSServiceType_MX:		// 15
	    // not handled (yet)
	    break;
	  
	  case kDNSServiceType_TXT:
	    {
	      NSData
		*data = nil;
	      
	      data = [NSData dataWithBytes: rdata
				    length: rdlen];
	      
	      [service->info removeObjectForKey: @"TXT"];
	      [service->info setObject: data
				forKey: @"TXT"];
	      
	      [self        netService: self
	       didUpdateTXTRecordData: data];
	    
	    }
	    break;
	  
	  case kDNSServiceType_RP:
	  case kDNSServiceType_AFSDB:
	  case kDNSServiceType_X25:
	  case kDNSServiceType_ISDN:	// 20
	  case kDNSServiceType_RT:
	  case kDNSServiceType_NSAP:
	  case kDNSServiceType_NSAP_PTR:
	  case kDNSServiceType_SIG:
	  case kDNSServiceType_KEY:		// 25
	  case kDNSServiceType_PX:
	  case kDNSServiceType_GPOS:
	    // not handled (yet)
	    break;
	  
	  case kDNSServiceType_AAAA:	// 28 -- AF_INET6
	    [self addAddress: rdata
		      length: rdlen
			type: rrtype
		   interface: interfaceIndex];
	    break;
	  
	  case kDNSServiceType_LOC:
	  case kDNSServiceType_NXT:		// 30
	  case kDNSServiceType_EID:
	  case kDNSServiceType_NIMLOC:
	  case kDNSServiceType_SRV:
	  case kDNSServiceType_ATMA:
	  case kDNSServiceType_NAPTR:	// 35
	  case kDNSServiceType_KX:
	  case kDNSServiceType_CERT:
	    // not handled (yet)
	    break;
	  
	  case kDNSServiceType_A6:	// 38 -- AF_INET6, deprecates AAAA
	    [self addAddress: rdata
		      length: rdlen
			type: rrtype
		   interface: interfaceIndex];
	    break;
	  
	  case kDNSServiceType_DNAME:
	  case kDNSServiceType_SINK:	// 40
	  case kDNSServiceType_OPT:
	    // not handled (yet)
	    break;
	  
	  case kDNSServiceType_TKEY:	// 249
	  case kDNSServiceType_TSIG:	// 250
	  case kDNSServiceType_IXFR:
	  case kDNSServiceType_AXFR:
	  case kDNSServiceType_MAILB:
	  case kDNSServiceType_MAILA:
	    // not handled (yet)
	    break;
	  
	  case kDNSServiceType_ANY:
	    LOG(@"Oops, got the wildcard match...");
	    break;
	  
	  default:
	    LOG(@"Don't know how to handle rrtype <%d>", rrtype);
	    break;
	}
    }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) registerCallback: (DNSServiceRef) sdRef
                    flags: (DNSServiceFlags) flags
                    error: (DNSServiceErrorType) errorCode
                     name: (const char *) name
                     type: (const char *) regtype
                   domain: (const char *) domain
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  
  if (_netService)
    {
      if (errorCode)
	{
	  [self cleanup];
	  
	  [self netService: self
	     didNotPublish: CreateError(self, errorCode)];
	}
      else
	{
	  [self netServiceDidPublish: self];
	}
    }
  UNLOCK(service);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) loop: (id) sender
{
  int			sock = 0;
  struct timeval	tout = { 0 };
  fd_set		set;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  
  sock = DNSServiceRefSockFD(_netService);
  
  if (-1 != sock)
    {
      FD_ZERO(&set);
      FD_SET(sock, &set);
      
      if (1 == select(sock + 1, &set, (fd_set *) NULL, (fd_set *) NULL, &tout))
	{
	  err = DNSServiceProcessResult(_netService);
	}
    }
  
  if (kDNSServiceErr_NoError != err)
    {
      Service	*service;
      
      service = (Service *) _reserved;
      
      if (YES == service->isPublishing)
	{
	  [self netService: self
	     didNotPublish: CreateError(self, err)];
	}
      else
	{
	  [self netService: self
	     didNotResolve: CreateError(self, err)];
	}
    }
}

/**
 * Converts txtDictionary into a TXT data.
 *
 *
 */

+ (NSData *) dataFromTXTRecordDictionary: (NSDictionary *) txtDictionary
{
  NSMutableData	*result = nil;
  NSArray	*keys = nil;
  NSArray	*values = nil;
  int		count = 0;
  
  INTERNALTRACE;
  
  count = [txtDictionary count];
  
  if (count)
    {
      keys = [txtDictionary allKeys];
      values = [txtDictionary allValues];
      
      if (keys && values)
	{
	  TXTRecordRef	txt;
	  int		i = 0;
	  char		key[256];
	  
	  TXTRecordCreate(&txt, 0, NULL);
	  
	  for(; i < count; i++)
	    {
	      int			length = 0;
	      int			used = 0;
	      DNSServiceErrorType err = kDNSServiceErr_Unknown;
	      
	      if (! [[keys objectAtIndex: i] isKindOfClass: [NSString class]])
		{
		  LOG(@"%@ is not a string", [keys objectAtIndex: i]);
		  break;
		}
	      
	      length = [[keys objectAtIndex: i] length];
	      [[keys objectAtIndex: i] getCString: key
					maxLength: sizeof key];
	      used = strlen(key);
	      
	      if (! length || (used >= sizeof key))
		{
		  LOG(@"incorrect length %d - %d - %d",
		    length, used, sizeof key);
		  break;
		}
	      
	      strcat(key, "\0");
	      
	      if ([[values objectAtIndex: i] isKindOfClass: [NSString class]])
		{
		  char	value[256];
		  
		  length = [[values objectAtIndex: i] length];
		  [[values objectAtIndex: i] getCString: value
					      maxLength: sizeof value];
		  used = strlen(value);
		  
		  if (used >= sizeof value)
		    {
		      LOG(@"incorrect length %d - %d - %d",
			length, used, sizeof value);
		      break;
		    }
		  
		  err = TXTRecordSetValue(&txt,
		    (const char *) key,
		    used,
		    value);
		}
	      else if ([[values objectAtIndex: i] isKindOfClass: [NSData class]]
		&& [[values objectAtIndex: i] length] < 256
		&& [[values objectAtIndex: i] length] >= 0)
		{
		  err = TXTRecordSetValue(&txt,
		    (const char *) key,
		    [[values objectAtIndex: i] length],
		    [[values objectAtIndex: i] bytes]);
		}
	      else if ([values objectAtIndex: i] == [NSNull null])
		{
		  err = TXTRecordSetValue(&txt,
		    (const char *) key,
		    0,
		    NULL);
		}
	      else
		{
		  LOG(@"unknown value type");
		  break;
		}
	      
	      if (err != kDNSServiceErr_NoError)
		{
		  LOG(@"error creating data type");
		  break;
		}
	    }
	  
	  if (i == count)
	    {
	      result = [NSData dataWithBytes: TXTRecordGetBytesPtr(&txt)
				      length: TXTRecordGetLength(&txt)];
	    }
	  
	  TXTRecordDeallocate(&txt);
	}
      else
	{
	  LOG(@"No keys or values");
	}
      
      // both are autorelease'd
      keys = nil;
      values = nil;
    }
  else
    {
      LOG(@"Dictionary seems empty");
    }
  return result;
}

/**
 * Converts the TXT data txtData into a dictionary.
 *
 *
 */

+ (NSDictionary *) dictionaryFromTXTRecordData: (NSData *) txtData
{
  NSMutableDictionary	*result = nil;
  int			len = 0;
  const void		*txt = 0;
  
  INTERNALTRACE;
  
  len = [txtData length];
  txt = [txtData bytes];
  
  //
  // A TXT record cannot exceed 65535 bytes, see Chapter 6.1 of
  // http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
  //
  if ((len > 0) && (len < 65536))
    {
      uint16_t	i = 0;
      uint16_t	count = 0;
      
      // get number of keys
      count = TXTRecordGetCount(len, txt);
      result = [NSMutableDictionary dictionaryWithCapacity: 1];
      
      if (result)
	{
	  // go through all keys
	  for(; i < count; i++)
	    {
	      char			key[256];
	      uint8_t			valLen = 0;
	      const void		*value = NULL;
	      DNSServiceErrorType	err = kDNSServiceErr_NoError;
	      
	      err = TXTRecordGetItemAtIndex(len, txt, i,
		sizeof key, key,
		&valLen, &value);
	      
	      // only if we can get the key and value...
	      if (kDNSServiceErr_NoError == err)
		{
		  NSData	*data = nil;
		  NSString	*str = nil;
		  
		  str = [NSString stringWithUTF8String: key];
		  
		  if (value)
		    {
		      data = [NSData dataWithBytes: value
					    length: valLen];
		    }
		  
		  if (data && str && [str length]
		    && ! [result objectForKey: str])
		    {
		      /* only add if key and value were created
		       * and key doesn't exist yet
		       */
		      [result setValue: data
				forKey: str];
		    }
		  else
		    {
		      /* I'm not exactly sure what to do if there
		       * is a key WITHOUT a value
		       * Theoretically '<6>foobar' should be identical
		       * to '<7>foobar=' i.e. the value would be [NSNull null]
		       */
		      [result setValue: [NSNull null]
				forKey: str];
		    }
		  
		  // both are autorelease'd
		  data = nil;
		  str = nil;
		}
	      else
		{
		  LOG(@"Couldn't get TXTRecord item");
		}
	    }
	}
      else
	{
	  LOG(@"Couldn't create dictionary");
	}
    }
  else
    {
      LOG(@"TXT record has incorrect length: <%d>", len);
    }
  return result;
}

/**
 * Initializes the receiver for service resolution. Use this method to create
 * an object if you intend to -resolve a service.
 *
 */

- (id) initWithDomain: (NSString *) domain
                 type: (NSString *) type
                 name: (NSString *) name
{
  INTERNALTRACE;
  
  return [self initWithDomain: domain
                         type: type
                         name: name
                         port: -1]; // -1 to indicate resolution, not publish
}

/** <init />
 * Initializes the receiver for service publication. Use this method to create
 * an object if you intend to -publish a service.
 *
 */

- (id) initWithDomain: (NSString *) domain
                 type: (NSString *) type
                 name: (NSString *) name
                 port: (int) port
{
  INTERNALTRACE;
  
  if ((self = [super init]))
    {
      Service	*service;
      
      service = malloc(sizeof (struct _Service));
      memset(service, 0, sizeof service);
      
      CREATELOCK(service);
      
      service->runloop = nil;
      service->runloopmode = nil;
      service->timer = nil;
      service->timeout = nil;
      
      service->info = [[NSMutableDictionary alloc] initWithCapacity: 1];
      [service->info setObject: [domain retain]
			forKey: @"Domain"];
      [service->info setObject: [name retain]
			forKey: @"Name"];
      [service->info setObject: [type retain]
			forKey: @"Type"];
      
      service->foundAddresses = nil;
      
      service->interfaceIndex = 0;
      service->port = htons(port);
      
      service->monitor = nil;
      
      service->isPublishing = (-1 == port) ? NO : YES;
      service->isMonitoring = NO;
      
      _netService = NULL;
      _delegate = nil;
      _reserved = service;
      
      return self;
    }
  
  return nil;
}

/**
 * Removes the service from the specified run loop.
 *
 *
 */

- (void) removeFromRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    if (service->timer)
      {
	[service->timer setFireDate: [NSDate date]];
	[service->timer invalidate];
	
	// Do not release the timer!
	service->timer = nil;
      }
    
    // Do not release the runloop!
    service->runloop = nil;
    
    DESTROY(service->runloopmode);
  }
  UNLOCK(service);
}

/**
 * Adds the service to the specified run loop.
 *
 *
 */

- (void) scheduleInRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    if (service->timer)
      {
	[service->timer setFireDate: [NSDate date]];
	[service->timer invalidate];
	service->timer = nil;
      }
    
    service->timer = [NSTimer timerWithTimeInterval: INTERVAL
                                             target: self
                                           selector: @selector(loop:)
                                           userInfo: nil
                                            repeats: YES];
    
    service->runloop = aRunLoop;
    service->runloopmode = mode;
    
    [service->timer retain];
  }
  UNLOCK(service);
}

/**
 * Attempts to publish a service on the network.
 *
 *
 */

- (void) publish
{
  Service		*service;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  DNSServiceFlags	flags = 0;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    do
      {
	// cannot -publish on a service that's init'd for resolving
	if (NO == service->isPublishing)
	  {
	    err = NSNetServicesBadArgumentError;
	    break;
	  }
	
	if (! _delegate)
	  {
	    err = NSNetServicesInvalidError;
	    break;
	  }
	
	if (service->timer)
	  {
	    err = NSNetServicesActivityInProgress;
	    break;
	  }
	
	if (service->timeout)
	  {
	    [service->timeout setFireDate: [NSDate date]];
	    [service->timeout invalidate];
	    service->timeout = nil;
	  }
	
	err = DNSServiceRegister((DNSServiceRef *) &_netService,
	  flags, service->interfaceIndex,
	  [[service->info objectForKey: @"Name"] UTF8String],
	  [[service->info objectForKey: @"Type"] UTF8String],
	  [[service->info objectForKey: @"Domain"] UTF8String],
	  NULL, service->port, 0, NULL,
	  RegistrationCallback, self);
      }
    while(0);
  }
  UNLOCK(service);
  
  [self executeWithError: err];
}

/**
 * This method is deprecated. Use -resolveWithTimeout: instead.
 *
 *
 */

- (void) resolve
{
  INTERNALTRACE;
  
  [self resolveWithTimeout: 5];
}

/**
 * Starts a service resolution for a limited duration.
 *
 *
 */

- (void) resolveWithTimeout: (NSTimeInterval) timeout
{
  Service		*service;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  DNSServiceFlags	flags = 0;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    do
      {
	// cannot -resolve on a service that's init'd for publishing
	if (YES == service->isPublishing)
	  {
	    err = NSNetServicesBadArgumentError;
	    break;
	  }
	
	if (! _delegate)
	  {
	    err = NSNetServicesInvalidError;
	    break;
	  }
	
	if (service->timer)
	  {
	    err = NSNetServicesActivityInProgress;
	    break;
	  }
	
	if (service->timeout)
	  {
	    [service->timeout setFireDate: [NSDate date]];
	    [service->timeout invalidate];
	    service->timeout = nil;
	  }
	
	service->timeout = [NSTimer alloc];
	{
	  NSDate	*date = nil;
	  
	  date = [NSDate dateWithTimeIntervalSinceNow: timeout + SHORTTIMEOUT];
	  
	  [service->timeout initWithFireDate: date
				    interval: INTERVAL
				      target: self
				    selector: @selector(stopResolving:)
				    userInfo: nil
				     repeats: NO];
	}
	
	err = DNSServiceResolve((DNSServiceRef *) &_netService,
	  flags,
	  service->interfaceIndex,
	  [[service->info objectForKey: @"Name"] UTF8String],
	  [[service->info objectForKey: @"Type"] UTF8String],
	  [[service->info objectForKey: @"Domain"] UTF8String],
	  ResolverCallback,
	  self);
      }
    while(0);
  }
  UNLOCK(service);
  
  [self executeWithError: err];
}

/**
 * Stops the current attempt to publish or resolve a service.
 *
 *
 */

- (void) stop
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    [self cleanup];
    
    [self netServiceDidStop: self];
  }
  UNLOCK(service);
}

/**
 * Starts monitoring of TXT record updates.
 *
 *
 */

- (void) startMonitoring
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    // Obviously this will only work on a resolver
    if (! service->isPublishing)
      {
	if (! service->isMonitoring)
	  {
	    service->monitor
	      = [[NSNetServiceMonitor alloc] initWithDelegate: self];
	    
	    [service->monitor scheduleInRunLoop: service->runloop
					forMode: service->runloopmode];
	    [service->monitor start];
	    
	    service->isMonitoring = YES;
	  }
      }
  }
  UNLOCK(service);
}

/**
 * Stops monitoring of TXT record updates.
 *
 *
 */

- (void) stopMonitoring
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    if (! service->isPublishing)
      {
	if (service->isMonitoring)
	  {
	    [service->monitor stop];
	    
	    // Probably don't need it anymore, so release it
	    DESTROY(service->monitor);
	    service->isMonitoring = NO;
	  }
      }
  }
  UNLOCK(service);
}

/**
 * Returns the receiver's delegate.
 *
 *
 */

- (id) delegate
{
  INTERNALTRACE;
  
  return [[_delegate retain] autorelease];
}

/**
 * Sets the receiver's delegate.
 *
 *
 */

- (void) setDelegate: (id) delegate
{
  INTERNALTRACE;
  
  ASSIGN(_delegate, delegate);
}

/**
 * Returns an array of NSData objects that each contain the socket address of
 * the service.
 *
 */

- (NSArray *) addresses
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"Addresses"];
}

/**
 * Returns the domain name of the service.
 *
 *
 */

- (NSString *) domain
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"Domain"];
}

/**
 * Returns the host name of the computer publishing the service.
 *
 *
 */

- (NSString *) hostName
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"Host"];
}

/**
 * Returns the name of the service.
 *
 *
 */

- (NSString *) name
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"Name"];
}

/**
 * Returns the type of the service.
 *
 *
 */

- (NSString *) type
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"Type"];
}

/**
 * This method is deprecated. Use -TXTRecordData instead.
 *
 *
 */

- (NSString *) protocolSpecificInformation
{
  NSMutableArray	*array = nil;
  Service		*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  //
  // I must admit, the following may not be entirely correct...
  //
  LOCK(service);
  {
    NSDictionary	*dictionary = nil;
    
    dictionary = [NSNetService dictionaryFromTXTRecordData:
      [self TXTRecordData]];
    
    if (dictionary)
      {
	NSEnumerator	*keys = nil;
	NSString	*key = nil;
	
	array = [NSMutableArray arrayWithCapacity: [dictionary count]];
	keys = [dictionary keyEnumerator];
	
	while((key = [keys nextObject]))
	  {
	    NSData	*value = nil;
	    
	    value = [dictionary objectForKey: key];
	    
	    if (value != (NSData *) [NSNull null])
	      {
	        NSData		*str;
		NSString	*pair;

		// FIXME ... should this be UTF8?
		str = [[NSString alloc]
		  initWithBytes: [value bytes]
		  length: [value length]
		  encoding: NSUTF8StringEncoding];
		pair = [NSString stringWithFormat: @"%@=%@", key, str];
		RELEASE(str);

		[array addObject: pair];
	      }
	    else if ([key length])
	      {
		[array addObject: [NSString stringWithFormat: @"%@", key]];
	      }
	  }
      }
  }
  UNLOCK(service);
  
  return ([array count] ? [array componentsJoinedByString: @"\001"]
    : (NSString *) nil);
}

/**
 * This method is deprecated. Use -setTXTRecordData: instead.
 *
 *
 */

- (void) setProtocolSpecificInformation: (NSString *) specificInformation
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  //
  // Again, the following may not be entirely correct...
  //
  LOCK(service);
  {
    NSArray	*array = nil;
    
    array = [specificInformation componentsSeparatedByString: @"\001"];
    
    if (array)
      {
	NSMutableDictionary	*dictionary = nil;
	NSEnumerator		*enumerator = nil;
	NSString		*item = nil;
	
	dictionary
	  = [NSMutableDictionary dictionaryWithCapacity: [array count]];
	enumerator = [array objectEnumerator];
	
	while((item = [enumerator nextObject]))
	  {
	    NSArray	*parts = nil;
	    NSData	*value;
	    
	    parts = [item componentsSeparatedByString:@"="];
	    
	    value = [[parts objectAtIndex: 1]
	      dataUsingEncoding: NSUTF8StringEncoding];
	    [dictionary setObject: value
			   forKey: [parts objectAtIndex: 0]];
	  }
	
	[self setTXTRecordData:
	  [NSNetService dataFromTXTRecordDictionary: dictionary]];
      }
  }
  UNLOCK(service);
}

/**
 * Returns the TXT record.
 *
 *
 */

- (NSData *) TXTRecordData
{
  INTERNALTRACE;
  
  return [((Service*)_reserved)->info objectForKey: @"TXT"];
}

/**
 * Sets the TXT record.
 *
 *
 */

- (BOOL) setTXTRecordData: (NSData *) recordData
{
  Service	*service;
  BOOL		result = NO;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    // Not allowed on a resolver...
    if (service->isPublishing)
      {
	DNSServiceErrorType
	  err = kDNSServiceErr_NoError;
	
	// Set the value, or remove it if empty
	if (recordData)
	  {
	    [service->info setObject: recordData
			      forKey: @"TXT"];
	  }
	else
	  {
	    [service->info removeObjectForKey: @"TXT"];
	  }
	
	// Assume it worked
	result = YES;
	
	// Now update the record so others can pick it up
	err = DNSServiceUpdateRecord(_netService,
	  NULL,
	  0,
	  recordData ? [recordData length] : 0,
	  recordData ? [recordData bytes] : NULL,
	  0);
	if (err)
	  {
	    result = NO;
	  }
      }
  }
  UNLOCK(service);
  
  return result;
}

/**
 * Retrieves the input and output stream for the receiver.
 *
 *
 */

- (BOOL) getInputStream: (NSInputStream **) inputStream
           outputStream: (NSOutputStream **) outputStream
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  
  LOCK(service);
  {
    [NSStream getStreamsToHost: [service->info objectForKey: @"Host"]
                          port: ntohs(service->port)
                   inputStream: inputStream
                  outputStream: outputStream];
  }
  UNLOCK(service);
  
  return inputStream && outputStream;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceWillPublish: (NSNetService *) sender
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceWillPublish:)])
    {
      [_delegate netServiceWillPublish: sender];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceDidPublish: (NSNetService *) sender
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceDidPublish:)])
    {
      [_delegate netServiceDidPublish: sender];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netService: (NSNetService *) sender
      didNotPublish: (NSDictionary *) errorDict
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netService:didNotPublish:)])
    {
      [_delegate netService: sender
	      didNotPublish: errorDict];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceWillResolve: (NSNetService *) sender
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceWillResolve:)])
    {
      [_delegate netServiceWillResolve: sender];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceDidResolveAddress: (NSNetService *) sender
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceDidResolveAddress:)])
    {
      [_delegate netServiceDidResolveAddress: sender];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netService: (NSNetService *) sender
      didNotResolve: (NSDictionary *) errorDict
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netService:didNotResolve:)])
    {
      [_delegate netService: sender
	      didNotResolve: errorDict];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netServiceDidStop: (NSNetService *) sender
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector: @selector(netServiceDidStop:)])
    {
      [_delegate netServiceDidStop: sender];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void)      netService: (NSNetService *) sender
  didUpdateTXTRecordData: (NSData *) data
{
  INTERNALTRACE;
  
  if ([_delegate respondsToSelector:
    @selector(netService:didUpdateTXTRecordData:)])
    {
      [_delegate   netService: sender
       didUpdateTXTRecordData: data];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) netService: (NSNetService *) sender
      didNotMonitor: (NSDictionary *) errorDict
{
  INTERNALTRACE;
  
  // This method is kind of a misnomer. It's called whenever NSNetMonitor
  // encounters an error while monitoring.
  // All we do is stop monitoring -- which we COULD do from NSNetMonitor
  // directly, but this seems to be much cleaner.
  
  [self stopMonitoring];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (id) init
{
  DESTROY(self);
  return self;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) dealloc
{
  Service	*service;
  
  INTERNALTRACE;
  
  service = (Service *) _reserved;
  {
    LOCK(service);
    {
      [self stopMonitoring];
      [self cleanup];
      
      DESTROY(service->info);
      DESTROY(service->foundAddresses);
      DESTROY(_delegate);
    }
    UNLOCK(service);
    
    DESTROYLOCK(service);
    
    free(service);
  }
  [super dealloc];
}

@end

@implementation NSNetServiceMonitor

/**
 * <em>Description forthcoming</em>
 *
 *
 */

+ (void) initialize
{
  INTERNALTRACE;
  
  SETVERSION(NSNetServiceMonitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) loop: (id) sender
{
  int			sock = 0;
  struct timeval	tout = { 0 };
  fd_set		set;
  DNSServiceErrorType	err = kDNSServiceErr_NoError;
  
  sock = DNSServiceRefSockFD(_netServiceMonitor);
  
  if (-1 != sock)
    {
      FD_ZERO(&set);
      FD_SET(sock, &set);
      
      if (1 == select(sock + 1, &set, (fd_set *) NULL, (fd_set *) NULL, &tout))
	{
	  err = DNSServiceProcessResult(_netServiceMonitor);
	}
    }
  
  if (kDNSServiceErr_NoError != err)
    {
      LOG(@"Error <%d> while monitoring", err);
      
      [_delegate netService: _delegate
	      didNotMonitor: CreateError(self, err)];
    }
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) queryCallback: (DNSServiceRef) sdRef
                 flags: (DNSServiceFlags) flags
             interface: (uint32_t) interfaceIndex
                 error: (DNSServiceErrorType) errorCode
              fullname: (const char *) fullname
                  type: (uint16_t) rrtype
                 class: (uint16_t) rrclass
                length: (uint16_t) rdlen
                  data: (const void *) rdata
                   ttl: (uint32_t) ttl
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  
  LOCK(monitor);
  
  if (_delegate)
    {
      // we are 'monitoring' kDNSServiceType_TXT
      // this is already handled by the delegate's method of the same name
      // so we simply pass this through
      [_delegate queryCallback: sdRef
			 flags: flags
		     interface: interfaceIndex
			 error: errorCode
		      fullname: fullname
			  type: rrtype
			 class: rrclass
			length: rdlen
			  data: rdata
			   ttl: ttl];
    }
  UNLOCK(monitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (id) initWithDelegate: (id) delegate
{
  INTERNALTRACE;
  
  if ((self = [super init]) != nil)
    {
      Monitor	*monitor;
      
      monitor = malloc(sizeof (struct _Monitor));
      memset(monitor, 0, sizeof monitor);
      
      CREATELOCK(monitor);
      
      monitor->runloop = nil;
      monitor->runloopmode = nil;
      monitor->timer = nil;
      
      _netServiceMonitor = NULL;
      _delegate = [delegate retain];
      _reserved = monitor;
    }
  return self;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) removeFromRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  
  LOCK(monitor);
  {
    if (monitor->timer)
      {
	[monitor->timer setFireDate: [NSDate date]];
	[monitor->timer invalidate];
	monitor->timer = nil;
      }
    
    // Do not release the runloop!
    monitor->runloop = nil;
    
    // [monitor->runloopmode release];
    monitor->runloopmode = nil;
  }
  UNLOCK(monitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) scheduleInRunLoop: (NSRunLoop *) aRunLoop
                   forMode: (NSString *) mode
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  
  LOCK(monitor);
  {
    if (monitor->timer)
      {
	[monitor->timer setFireDate: [NSDate date]];
	[monitor->timer invalidate];
	monitor->timer = nil;
      }
    
    monitor->runloop = aRunLoop;
    monitor->runloopmode = mode;
  }
  UNLOCK(monitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) start
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  
  LOCK(monitor);
  {
    DNSServiceErrorType	err = kDNSServiceErr_NoError;
    DNSServiceFlags	flags = kDNSServiceFlagsLongLivedQuery;
    NSString		*fullname = nil;
    
    do
      {
	if (! _delegate)
	  {
	    err = NSNetServicesInvalidError;
	    break;
	  }
	
	if (monitor->timer)
	  {
	    err = NSNetServicesActivityInProgress;
	    break;
	  }
	
	fullname = [NSString stringWithFormat: @"%@.%@%@",
	  [_delegate name], [_delegate type], [_delegate domain]];
	
	err = DNSServiceQueryRecord((DNSServiceRef *) &_netServiceMonitor,
	  flags,
	  0,
	  [fullname UTF8String],
	  kDNSServiceType_TXT,
	  kDNSServiceClass_IN,
	  QueryCallback,
	  self);
	
	if (kDNSServiceErr_NoError == err)
	  {
	    monitor->timer = [NSTimer timerWithTimeInterval: INTERVAL
						     target: self
						   selector: @selector(loop:)
						   userInfo: nil
						    repeats: YES];

	    [monitor->runloop addTimer: monitor->timer
			       forMode: monitor->runloopmode];
	    
	    [monitor->timer fire];
	  }
      }
    while(0);
  }
  UNLOCK(monitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) stop
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  
  LOCK(monitor);
  {
    if (monitor->runloop)
      {
	[self removeFromRunLoop: monitor->runloop
			forMode: monitor->runloopmode];
      }
    
    if (monitor->timer)
      {
	[monitor->timer invalidate];
	monitor->timer = nil;
      }
    
    if (_netServiceMonitor)
      {
	DNSServiceRefDeallocate(_netServiceMonitor);
	_netServiceMonitor = NULL;
      }
  }
  UNLOCK(monitor);
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (id) init
{
  DESTROY(self);
  return nil;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

- (void) dealloc
{
  Monitor	*monitor;
  
  INTERNALTRACE;
  
  monitor = (Monitor *) _reserved;
  {
    LOCK(monitor);
    {
      [self stop];
      
      _delegate = nil;
    }
    UNLOCK(monitor);
    
    DESTROYLOCK(monitor);
    
    free(monitor);
  }
  [super dealloc];
}

@end

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static NSDictionary *
CreateError(id sender, int errorCode)
{
  NSMutableDictionary	*dictionary = nil;
  int			error = 0;
  
  INTERNALTRACE;
  
  dictionary = [NSMutableDictionary dictionary];
  error = ConvertError(errorCode);
  
  LOG(@"%@ says error <%d> - <%d>", [sender description], errorCode, error);
  
  [dictionary setObject: [NSNumber numberWithInt: error]
                 forKey: NSNetServicesErrorCode];
  [dictionary setObject: sender
                 forKey: NSNetServicesErrorDomain];
  
  return dictionary; // autorelease'd
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static int
ConvertError(int errorCode)
{
  INTERNALTRACE;
  
  switch(errorCode)
    {
      case kDNSServiceErr_Unknown:
	return NSNetServicesUnknownError;
      
      case kDNSServiceErr_NoSuchName:
	return NSNetServicesNotFoundError;
      
      case kDNSServiceErr_NoMemory:
	return NSNetServicesUnknownError;
      
      case kDNSServiceErr_BadParam:
      case kDNSServiceErr_BadReference:
      case kDNSServiceErr_BadState:
      case kDNSServiceErr_BadFlags:
	return NSNetServicesBadArgumentError;
      
      case kDNSServiceErr_Unsupported:
	return NSNetServicesUnknownError;
      
      case kDNSServiceErr_NotInitialized:
	return NSNetServicesInvalidError;
      
      case kDNSServiceErr_AlreadyRegistered:
      case kDNSServiceErr_NameConflict:
	return NSNetServicesCollisionError;
      
      case kDNSServiceErr_Invalid:
	return NSNetServicesInvalidError;
      
      case kDNSServiceErr_Firewall:
	return NSNetServicesUnknownError;
      
      case kDNSServiceErr_Incompatible:
	// The client library is incompatible with the daemon
	return NSNetServicesInvalidError;
      
      case kDNSServiceErr_BadInterfaceIndex:
      case kDNSServiceErr_Refused:
	return NSNetServicesUnknownError;
      
      case kDNSServiceErr_NoSuchRecord:
      case kDNSServiceErr_NoAuth:
      case kDNSServiceErr_NoSuchKey:
	return NSNetServicesNotFoundError;
      
      case kDNSServiceErr_NATTraversal:
      case kDNSServiceErr_DoubleNAT:
      case kDNSServiceErr_BadTime:
	return NSNetServicesUnknownError;
    }
  
  return errorCode;
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static void
EnumerationCallback(DNSServiceRef sdRef,
                      DNSServiceFlags flags,
                      uint32_t interfaceIndex,
                      DNSServiceErrorType  errorCode,
                      const char *replyDomain,
                      void *context)
{
  // NSNetServiceBrowser
  [(id) context enumCallback: sdRef
                       flags: flags
                   interface: interfaceIndex
                       error: errorCode
                      domain: replyDomain];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static void
BrowserCallback(DNSServiceRef sdRef,
                  DNSServiceFlags flags,
                  uint32_t interfaceIndex,
                  DNSServiceErrorType errorCode,
                  const char *replyName,
                  const char *replyType,
                  const char *replyDomain,
                  void *context)
{
  // NSNetServiceBrowser
  [(id) context browseCallback: sdRef
                         flags: flags
                     interface: interfaceIndex
                         error: errorCode
                          name: replyName
                          type: replyType
                        domain: replyDomain];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static void
ResolverCallback(DNSServiceRef sdRef,
                   DNSServiceFlags flags,
                   uint32_t interfaceIndex,
                   DNSServiceErrorType errorCode,
                   const char *fullname,
                   const char *hosttarget,
                   uint16_t port,
                   uint16_t txtLen,
                   const char *txtRecord,
                   void *context)
{
  // NSNetService
  [(id) context resolverCallback: sdRef
                           flags: flags
                       interface: interfaceIndex
                           error: errorCode
                        fullname: fullname
                          target: hosttarget
                            port: port
                          length: txtLen
                          record: txtRecord];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static void
RegistrationCallback(DNSServiceRef sdRef,
                       DNSServiceFlags flags,
                       DNSServiceErrorType errorCode,
                       const char *name,
                       const char *regtype,
                       const char *domain,
                       void *context)
{
  // NSNetService
  [(id) context registerCallback: sdRef
                           flags: flags
                           error: errorCode
                            name: name
                            type: regtype
                          domain: domain];
}

/**
 * <em>Description forthcoming</em>
 *
 *
 */

static void
QueryCallback(DNSServiceRef sdRef,
                DNSServiceFlags flags,
                uint32_t interfaceIndex,
                DNSServiceErrorType errorCode,
                const char *fullname,
                uint16_t rrtype,
                uint16_t rrclass,
                uint16_t rdlen,
                const void *rdata,
                uint32_t ttl,
                void *context)
{
  // NSNetService, NSNetServiceMonitor
  [(id) context queryCallback: sdRef
                        flags: flags
                    interface: interfaceIndex
                        error: errorCode
                     fullname: fullname
                         type: rrtype
                        class: rrclass
                       length: rdlen
                         data: rdata
                          ttl: ttl];
}

