/* 
   NSPortNameServer.m

   Interface to the port registration service used by the DO system.

   Copyright (C) 1998 Free Software Foundation, Inc.

   Author:	Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Date:	October 1998

   Author:	H. N. Schaller <hns@computer.org>
   Date:	June 2006
			reworked to be based on NSStream, NSMessagePort etc.
 
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSByteOrder.h>
#import <Foundation/NSException.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSData.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSNotificationQueue.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSTask.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSTimer.h>
#import <Foundation/NSPort.h>
#import <Foundation/NSPortNameServer.h>
#import <Foundation/NSHost.h>
#import <Foundation/NSBundle.h>

#import "NSPrivate.h"

@implementation NSPortNameServer

static NSPortNameServer *__systemDefaultPortNameServer;

+ (NSPortNameServer *) systemDefaultPortNameServer;
{
	if(!__systemDefaultPortNameServer)
		__systemDefaultPortNameServer=[self new];
	return __systemDefaultPortNameServer;
}

- (NSPort *) portForName:(NSString *) portName; { return [self portForName:portName host:nil]; }

- (NSPort *) portForName:(NSString *) portName host:(NSString *) hostName;
{
	if([hostName length] != 0)
		return [[NSSocketPortNameServer sharedInstance] portForName:portName host:hostName];
	return [[NSMessagePortNameServer sharedInstance] portForName:portName host:nil];
}

- (BOOL) registerPort:(NSPort *) port name:(NSString *) name;
{ // register locally and remote
	return [[NSMessagePortNameServer sharedInstance] registerPort:port name:name] | // don't use || because it would not try to register both
		   [[NSSocketPortNameServer sharedInstance] registerPort:port name:name];
}

- (BOOL) removePortForName:(NSString *) name;
{
	return [[NSSocketPortNameServer sharedInstance] removePortForName:name];	// only socketport can unregister explicitly
}

@end

@implementation NSSocketPortNameServer

static NSSocketPortNameServer *defaultServer;

// implementation should be based on NSNetServices!

+ (id) sharedInstance
{
	if(!defaultServer)
		defaultServer=[self new];
	return defaultServer;
}

- (id) init;
{
	if((self=[super init]))
		{
		_publishedSocketPorts=[NSMutableDictionary new];
		}
	return self;
}

- (void) dealloc
{
	[NSException raise: NSGenericException
				 format: @"attempt to deallocate default port name server"]; 
	[_publishedSocketPorts release];
	[super dealloc];	// makes gcc not complain
}

- (unsigned short) defaultNameServerPortNumber; { return defaultNameServerPortNumber; }
- (void) setDefaultNameServerPortNumber:(unsigned short) portNumber; { defaultNameServerPortNumber=portNumber; }

- (NSPort*) portForName:(NSString*)name
{
	return [self portForName: name host: nil];
}

- (NSPort*) portForName:(NSString*)name host:(NSString*)host
{
	return [self portForName:name host:host nameServerPortNumber:0];
}

- (NSPort*) portForName:(NSString*)name host:(NSString*)host nameServerPortNumber:(unsigned short) portNumber;
{
#if 1
	NSLog(@"NSSocketPortNameServer portForName:%@ host:%@ nameServerPortNumber:%u", name, host, portNumber);
#endif	  
	// somewhere calls [NSNetService resolve] and connects to peer
	return NIMP;
}

- (BOOL) registerPort:(NSPort*)port name:(NSString*)name;
{
	return [self registerPort:port name:name nameServerPortNumber:0];
}

- (BOOL) registerPort:(NSPort*)port name:(NSString*)name nameServerPortNumber:(unsigned short) portNumber;
{
	NSNetService *s=[_publishedSocketPorts objectForKey:name];
#if 1
	NSLog(@"NSSocketPortNameServer registerPort:%@ name:%@ nameServerPortNumber:%u", port, name, portNumber);
#endif
	if(s)
		return NO;	// already known to be published
	s=[[NSNetService alloc] initWithDomain:@"local." type:@"_nssocketport._tcp." name:name port:portNumber];
	// [s setDelegate:self];
	if(!s)
		return NO;
	[s publish];		// try to publish
	[_publishedSocketPorts setObject:s forKey:name];	// so that we can delete...
	// FIXME: listen() and accept() for any responses???
	return YES;	// appears to be always successful
}

- (BOOL) removePortForName:(NSString*)name
{
	NSNetService *s=[_publishedSocketPorts objectForKey:name];
	if(!s)
		return NO;	// wasn't published before
	[s stop];	// stop publishing
	[_publishedSocketPorts removeObjectForKey:name];	// will release
	return YES;
		return YES;
}

- (void) _removePort:(NSPort*)port
{ // Remove all names for a particular port.  Called when a port is invalidated.
	NIMP;
}

@end /* NSPortNameServer (Private) */

@implementation NSMessagePortNameServer

static NSMessagePortNameServer *__sharedNSMessagePortNameServer;

+ (id) sharedInstance;
{
	if(!__sharedNSMessagePortNameServer)
		__sharedNSMessagePortNameServer=[self new];
	return __sharedNSMessagePortNameServer;
}

- (NSPort *) portForName:(NSString *) portName; { return [self portForName:portName host:nil]; }

- (NSString *) _fileNameForPortName:(NSString *) name;
{
	NSMutableString *n=[NSMutableString stringWithString:name];	// make autoreleased mutable copy
	NSString *finalName;
	[n replaceOccurrencesOfString:@"%" withString:@"%%" options:0 range:NSMakeRange(0, [name length])];
	[n replaceOccurrencesOfString:@"/" withString:@"%-" options:0 range:NSMakeRange(0, [name length])];	// prevent using / to create or overwrite other files
	finalName=[[NSMessagePort _portSocketDirectory] stringByAppendingPathComponent:n];
#if 1
	NSLog(@"port name %@ -> %@", name, finalName);
#endif
	return finalName;
}

- (NSPort *) portForName:(NSString *) portName host:(NSString *) hostName;
{ // get named socket through alias
#if 1
	NSLog(@"NSMessagePortNameServer portForName:%@ host:%@", portName, hostName);
#endif	  
	if([hostName length] != 0)
		return nil; // host name must be nil or empty!
	// int readlink(const char *path, char *buf, size_t bufsiz);  // if we can read, replace
	return [[NSMessagePort alloc] _initWithFileName:[self _fileNameForPortName:portName] create:NO];	// connect to (existing) file
}

- (BOOL) registerPort:(NSPort *) port name:(NSString *) name;
{ // make a named alias for the port (named FIFO)
	const char *fname;
	if(![port isKindOfClass:[NSMessagePort class]])
		return NO;	// not a message port
	// check if referenced port (still) exists
	// what if the name already exists?
	// how can we remove this when the process dies?
	fname=[[self _fileNameForPortName:name] fileSystemRepresentation];
	/* HACK: */ unlink(fname);	// delete any other registration if it still exists
	if(symlink([(NSMessagePort *) port _fileSystemRepresentation], fname) != 0)
		{
		NSLog(@"NSMessagePortNameServer: can't register port %@ with name %@: %s", port, name, strerror(errno));
		return NO;
		}
	// add notification if port is becoming invalid?
	return YES;
}

- (BOOL) removePortForName:(NSString *) name;
{ // remove named alias
	return unlink([[self _fileNameForPortName:name] fileSystemRepresentation]) == 0;
}

@end