/* 
   NSPort.m

   Implementation of abstract superclass port for use with NSConnection

   Copyright (C) 1997, 1998 Free Software Foundation, Inc.

   Author:  Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
   Date:	July 1994
   Rewrite: Richard Frith-Macdonald <richard@brainstorm.co.u>
   Date:	August 1997
 
   NSSocketPort & Major Rewrite: H. Nikolaus Schaller <hns@computer.org>
   Date:	Dec 2005 - Sept 2006

   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/

#define USE_MESSAGE_PORT_AS_DEFAULT 1	// use NSSocketPort otherwise

#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>	// sockaddr_un
#include <fcntl.h>

#import <Foundation/NSPort.h>
#import <Foundation/NSHost.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSConnection.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSByteOrder.h>
#import <Foundation/NSData.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSPortMessage.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSPortCoder.h>			// for Coding protocol in Object category
#import <Foundation/NSPortNameServer.h>
#import <Foundation/NSStream.h>

#import "NSPrivate.h"

NSString *NSPortDidBecomeInvalidNotification = @"NSPortDidBecomeInvalidNotification";

NSString *NSObjectInaccessibleException=@"NSObjectInaccessibleException";
NSString *NSObjectNotAvailableException=@"NSObjectNotAvailableException";
NSString *NSDestinationInvalidException=@"NSDestinationInvalidException";
NSString *NSPortTimeoutException=@"NSPortTimeoutException";
NSString *NSInvalidSendPortException=@"NSInvalidSendPortException";
NSString *NSInvalidReceivePortException=@"NSInvalidReceivePortException";
NSString *NSPortSendException=@"NSPortSendException";
NSString *NSPortReceiveException=@"NSPortReceiveException";

@implementation NSPort

+ (void) initialize
{
	if(self == [NSPort class])
		{
		signal(SIGPIPE, SIG_IGN);
		// If SIGPIPE is not ignored, we will abort 
		// on any attempt to write to a pipe/socket
		// that has been closed by the other end!
		}
}

// this should create two local ports to communicate between threads

+ (NSPort *) port							{ return [[self new] autorelease]; }

+ (id) allocWithZone:(NSZone *) z;
{
	if(self == [NSPort class])
#if USE_MESSAGE_PORT_AS_DEFAULT
		return [NSMessagePort allocWithZone:z];	// should be NSMachPort but we don't have that one
#else
		return [NSSocketPort allocWithZone:z];	// should be NSMachPort but we don't have that one
#endif
	return [super allocWithZone:z];	// concrete subclass
}

- (id) copyWithZone:(NSZone *) zone			{ return [self retain]; }
- (id) delegate								{ return _delegate; }

- (BOOL) isEqual:(id) port;
{
	return self == port;
}

- (id) init
{
	if((self=[super init]))
		{
		_isValid = YES;
		}
	return self;
}

- (void) dealloc;
{
	if(_sendBuffer)
		objc_free(_sendBuffer);
	if(_recvBuffer)
		objc_free(_recvBuffer);
	[super dealloc];
}

// subclasses should override this if needed
// method and call [super invalidate]  
// in their versions of the method.

- (void) invalidate
{
	if(_isValid)
		{
#if 1
		NSLog(@"invalidated: %@", self);
#endif
		_isValid = NO;
		// we should remove any scheduling for invalid ports! 
		[[NSNotificationCenter defaultCenter] postNotificationName:NSPortDidBecomeInvalidNotification object:self];
		}
}

- (BOOL) isValid							{ return _isValid; }

- (void) release
{
	if(_isValid && [self retainCount] == 1)
		{
		NSAutoreleasePool *arp;
				// If the port is about to have a final release deallocate it
				// we must invalidate it.  Use a local autorelease pool when
				// invalidating so that we know that anything refering to this
				// port during the invalidation process is released immediately
				// Also bracket with retain/release pair to prevent recursion.
#if 1
		NSLog(@"port finally released: %@", self);
#endif
		[super retain];
		arp = [NSAutoreleasePool new];
		[self invalidate];
		[arp release];
		[super release];
		}
	[super release];
}

- (void) setDelegate:(id)anObject
{
	if(anObject && ![anObject respondsToSelector: @selector(handlePortMessage:)])
		[NSException raise:NSInvalidArgumentException format:@"does not provide handlePortMessage: %@", anObject];
	_delegate = anObject;
}

// should we really override?

- (id) replacementObjectForPortCoder:(NSPortCoder *)aCoder	{ return self; }	// never replace

- (void) encodeWithCoder:(NSCoder *) coder
{
	[(NSPortCoder *) coder encodePortObject:self];
}

- (id) initWithCoder:(NSCoder *) coder
{
	[self release];
	return [(NSPortCoder *) coder decodePortObject];
}

- (void) addConnection:(NSConnection *)connection
			 toRunLoop:(NSRunLoop *)runLoop
			   forMode:(NSString *)mode;
{
	[self setDelegate:connection];
	[self scheduleInRunLoop:runLoop forMode:mode];
}

- (void) removeConnection:(NSConnection *)connection
			  fromRunLoop:(NSRunLoop *)runLoop
				  forMode:(NSString *)mode;
{
	[self removeFromRunLoop:runLoop forMode:mode];
	[self setDelegate:nil];
}

- (void) removeFromRunLoop:(NSRunLoop *)runLoop
				   forMode:(NSString *)mode;
{
	[runLoop _removeInputWatcher:self forMode:mode];
	[runLoop _removeOutputWatcher:self forMode:mode];
}

- (void) scheduleInRunLoop:(NSRunLoop *)runLoop
				   forMode:(NSString *)mode;
{
	// shouldn't we simply remember the mode and add the output watcher only if we really have something to send?
	[runLoop _addInputWatcher:self forMode:mode];
	[runLoop _addOutputWatcher:self forMode:mode];
}

- (BOOL) sendBeforeDate:(NSDate *)limitDate
			 components:(NSMutableArray *)components
				   from:(NSPort *)receivePort
			   reserved:(unsigned)headerSpaceReserved;
{ // use next sequence number
	return [self sendBeforeDate:limitDate
						  msgid:0	// default message
					 components:components
						   from:receivePort
					   reserved:headerSpaceReserved];
}

// make us generically work as an NSPort based on a file descriptor

struct NSPortHeader
{
	char magic[8];	// "NSPort00"
	unsigned long messageLength;
};

- (unsigned) reservedSpaceLength; { return sizeof(struct NSPortHeader); }

- (BOOL) sendBeforeDate:(NSDate *)limitDate
				  msgid:(unsigned)msgid
			 components:(NSMutableArray *)components
				   from:(NSPort *)receivePort
			   reserved:(unsigned)headerSpaceReserved;
{
	struct NSPortHeader header = { "NSPort00" };
	NSRunLoop *loop=[NSRunLoop currentRunLoop];
	int fd=[self _fileDescriptor];
#if 1
	NSLog(@"NSPort sendBeforeDate:%@ msgid:%u components:%@ from:%@ reserved:%u", limitDate, msgid, components, receivePort, headerSpaceReserved);
#endif
	_sendBuffer=[NSPortMessage _machMessageWithId:msgid forSendPort:self receivePort:receivePort components:components returnMessageLength:&_sendLength];
	if(!_sendBuffer)
		{
		[NSException raise:NSPortSendException format:@"could not convert data to machMessage"];
		return NO;
		}
#if 1
	NSLog(@"sendLength=%u", _sendLength);
#endif
	_sendPos=0;
	header.messageLength=NSSwapHostLongToBig(_sendLength);
	if(write(fd, &header, sizeof(header)) != sizeof(header))	// write header
		{
		[NSException raise:NSPortSendException format:@"could not send message header on %@: %s", self, strerror(errno)];
		return NO;
		}
	[self scheduleInRunLoop:loop forMode:NSConnectionReplyMode];	// schedule us now
	[receivePort scheduleInRunLoop:loop forMode:NSConnectionReplyMode];	// schedule our receive port - which might be different from self!
#if 1
	NSLog(@"remaining interval %lf", [limitDate timeIntervalSinceNow]);
#endif
	while(_sendBuffer && [limitDate timeIntervalSinceNow] > 0)
		{
#if 1
		NSLog(@"run loop in mode %@", NSConnectionReplyMode);
#endif
		if(![loop runMode:NSConnectionReplyMode beforeDate:limitDate])
			{ // some runloop error, e.g. not scheduled in this mode
#if 1
			NSLog(@"sendBeforeDate: runloop error");
#endif
			[NSException raise:NSPortSendException format:@"sendBeforeDate: runloop error for %@", self];
			break;
			}
#if 1
		NSLog(@"remaining interval %lf", [limitDate timeIntervalSinceNow]);
#endif
		}
	[receivePort removeFromRunLoop:loop forMode:NSConnectionReplyMode];
	[self removeFromRunLoop:loop forMode:NSConnectionReplyMode];
	return _sendPos == _sendLength;		// we did send all components?
}

- (void) _readFileDescriptorReady;
{ // callback
	int fd=[self _fileDescriptor];
	NSLog(@"_readFileDescriptor:%d ready %@", fd, self);
	if(!_recvBuffer)
		{ // no buffer allocated so far - receive and check header
		struct NSPortHeader header;
		fcntl(fd, F_SETFL, O_NONBLOCK);	// don't block here or later
		if(read(fd, &header, sizeof(struct NSPortHeader)) != sizeof(struct NSPortHeader))
			{ // should we have a mechanism to resync? This appears to be not required since we assume a reliable transport socket
			[self invalidate];
			[NSException raise:NSPortReceiveException format:@"sendBeforeDate: header read error %s", strerror(errno)];
			}
		if(strncmp(header.magic, "NSPort00", 8) != 0)
			{
			[self invalidate];
			[NSException raise:NSPortReceiveException format:@"sendBeforeDate: bad header magic %8s", header.magic];
			}
		_recvLength=NSSwapBigLongToHost(header.messageLength);
#if 1
		NSLog(@"header received length=%u", _recvLength);
#endif
		_recvBuffer=objc_malloc(_recvLength);
		if(!_recvBuffer)
			{
			[self invalidate];
			[NSException raise:NSPortReceiveException format:@"sendBeforeDate: could not allocate header for message of length %u", header.messageLength];
			}
		_recvPos=0;	// start at the beginning
		return;
		}
#if 1
	NSLog(@"pos=%u length=%u", _recvPos, _recvLength);
#endif
	if(_recvPos < _recvLength)
		{ // read next fragment - as much as we can get from the missing part
		int len;
		len=_recvLength-_recvPos;	// how much we expect
		len=read(fd, _recvBuffer+_recvPos, len);
		if(len < 0)
			{
			if(errno == EWOULDBLOCK)
				return;	// ignore
			[self invalidate];
			[NSException raise:NSPortReceiveException format:@"sendBeforeDate: read error %s", strerror(errno)];
			}
#if 1
		NSLog(@"did read %u bytes", len);
#endif
		_recvPos+=len;
		if(_recvPos < _recvLength)
			return;	// incomplete
		}
#if 1
	NSLog(@"complete message received");
#endif
	if([_delegate respondsToSelector:@selector(handleMachMessage:)])
		{
		[_delegate handleMachMessage:_recvBuffer];
		objc_free(_recvBuffer);	// done
		_recvBuffer=NULL;
		}
	else
		{
		NSPortMessage *msg=[[NSPortMessage alloc] initWithMachMessage:_recvBuffer];
#if 1
		NSLog(@"handlePortMessage:%@", msg);
#endif
		[msg _setReceivePort:self];	// we did receive this message
		objc_free(_recvBuffer);	// done
		_recvBuffer=NULL;
		[_delegate handlePortMessage:msg];	// process by delegate
		[msg release];
		}
}

- (void) _writeFileDescriptorReady;
{ // callback
	if(_sendBuffer)
		{ // we have something to write
		int fd=[self _fileDescriptor];
		int len;
#if 1
		NSLog(@"_writeFileDescriptorReady %@ (pos=%u len=%u)", self, _sendPos, _sendLength);
#endif
		len=_sendLength-_sendPos;	// remaining block
		if(len == 0)
			{ // done
#if 1
			NSLog(@"all sent");
#endif
			objc_free(_sendBuffer);
			_sendBuffer=NULL;
			return;
			}
		if(len > 512)
			len=512;	// limit to avoid blocking
#if 1
		NSLog(@"write next %u bytes", len);
#endif
		
		// we could/should make the write non-blocking and remember how much was really sent
		
		if(write(fd, _sendBuffer+_sendPos, len) != len) // this might block in the kernel if the FIFO becomes filled up!
			{
			NSLog(@"send error %s", strerror(errno));
			[self invalidate];
			return;
			}
		_sendPos+=len;
		}
	// nothing to write should probably unscheldule?
}

@end /* NSPort */

// FIXME: modify so that a NSMessagePort is a socket in UNIX domain, i.e. a file name

@implementation NSMessagePort

- (NSSocketNativeHandle) _fileDescriptor; { return _fd; } // the fd to watch

- (NSString *) description;
{
	return [NSString stringWithFormat:@"%@ file=%@ fd=%d", NSStringFromClass(isa), _name, _fd];
}

- (const char *) _fileSystemRepresentation;
{
	return [_name fileSystemRepresentation];
}

- (id) _initWithSocket:(NSSocketNativeHandle) fd
{
#if 1
	NSLog(@"NSMessagePort _initWithSocket:%d", fd);
#endif
	if(_fd < 0)
		{
		NSLog(@"NSMessagePort: could not initWithSocket:%d", fd);
		[self release];
		return nil;
		}
	if((self=[super init]))
		{
		_name=@"no name";
		_fd=fd;
		}
	return self;
}

- (id) _initWithFileName:(NSString *) name create:(BOOL) flag
{
#if 1
	NSLog(@"NSMessagePort _initWithFileName:%@", name);
#endif
	if((self=[super init]))
		{ // open named socket
		struct sockaddr_un saddr;
		const char *fname=[name fileSystemRepresentation];
		_name=[name retain];
#if 1
		if(flag)
			NSLog(@"create socket(%s)", fname);
		else
			NSLog(@"connect socket(%s)", fname);
#endif
		saddr.sun_family = AF_UNIX;
		strncpy(saddr.sun_path, fname, sizeof(saddr.sun_path));
		_fd=socket(saddr.sun_family, SOCK_STREAM, PF_UNSPEC);
		if(_fd < 0)
			{
			NSLog(@"NSMessagePort: could not create socket due to %s", strerror(errno));
			[self release];
			return nil;
			}
		if(!flag)
			{ // connect to existing socket
			if(connect(_fd, (struct sockaddr *) &saddr, SUN_LEN(&saddr)))
				{
				NSLog(@"NSMessagePort: could not connect NSMessagePort %@ due to %s", name, strerror(errno));
				[self release];
				return nil;
				}
			NSLog(@"connected");
			}
		else
			{ // create receiving socket - note that we can't listen()/accept() on AF_UNIX sockets
			if(bind(_fd, (struct sockaddr *) &saddr, SUN_LEN(&saddr)))
				{
				NSLog(@"NSMessagePort: could not bind NSMessagePort %@ due to %s", name, strerror(errno));
				[self release];
				return nil;
				}
			NSLog(@"bound");
#if 1
			if(listen(_fd, 10))
				{
				NSLog(@"NSMessagePort: could not listen on NSMessagePort %@ due to %s", name, strerror(errno));
				[self release];
				return nil;
				}
			NSLog(@"listening");
#endif
			_listening=YES;
			_owner=YES;
			}
		}
	return self;
}

+ (NSString *) _portSocketDirectory;
{ // this is a system constant...
	return [NSTemporaryDirectory() stringByAppendingPathComponent:@".QuantumSTEP"];
}

- (id) init
{ // initialize for a local port with default name
	static NSString *dir;
	if(!dir)
		{
		dir=[[isa _portSocketDirectory] retain];
		mkdir([dir fileSystemRepresentation], 0770);	// create socket temp directory - ignore errors
		}
	return [self _initWithFileName:[dir stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] create:YES];
}

- (void) invalidate
{
	// unschedule
	[super invalidate];
}

- (void) dealloc;
{
#if 1
	NSLog(@"NSMessagePort dealloc:%@", self);
#endif
	close(_fd);	// if open
	if(_owner)
		unlink([_name fileSystemRepresentation]);	// if we are the owner, remove from file system
	// we should also unlink a named registration if available
	[_name release];
	[super dealloc];
}

- (void) _readFileDescriptorReady;
{ // callback
	if(_listening)
		{
		NSMessagePort *newPort;
		NSConnection *c;
		struct sockaddr_un saddr;
		socklen_t saddrlen=SUN_LEN(&saddr);
		int fd=accept([self _fileDescriptor], (struct sockaddr *) &saddr, &saddrlen);
		if(fd < 0)
			{
			NSLog(@"NSMessagePort: could not accept on NSMessagePort %@ due to %s", self, strerror(errno));
			return;
			}
		NSLog(@"accepted fd:%d from %@", fd, self);
		NSLog(@"saddr=%s", saddr.sun_path, saddrlen);
		newPort=[[isa alloc] _initWithSocket:fd];
		c=[NSConnection connectionWithReceivePort:newPort sendPort:newPort];	// create new connection object
#if 1
		NSLog(@"new connection: %@", c);
#endif
		}
	else
		[super _readFileDescriptorReady];
}

@end

@implementation NSSocketPort

// FIXME: handle connect(), listen() and accept()

- (NSSocketNativeHandle) _fileDescriptor; { return _fd; } // the fd to watch

- (id) init;
{ // initialize for a local port assigned by the system
	return [self initWithTCPPort:0];
}

- (id) initRemoteWithProtocolFamily:(int) family socketType:(int) type protocol:(int) protocol address:(NSData *) address;
{
	self=[self initWithProtocolFamily:family socketType:type protocol:protocol address:address];
	if(self)
		{
		_remote=YES;
		// does not connect until data is sent!
		}
	return self;
}

- (id) initRemoteWithTCPPort:(unsigned short) port host:(NSString *) host;
{
	NSHost *h;
	h=[NSHost hostWithName:host];	// use NSHost to do the address resolution
	if(!h)
		h=[NSHost hostWithAddress:host];	// try dotted notation
	if(!h)
		{ // could not resolve
		[self release];
		return nil;
		}
	self=[self initRemoteWithProtocolFamily:AF_INET
								 socketType:SOCK_STREAM
								   protocol:IPPROTO_TCP 
									address:nil];	// no address
	if(self)
		{ // insert address of remote system
		inet_aton([[h address] cString], &_addr.sin_addr);
		_addr.sin_port = htons(port);
		}
	return self;
}

- (id) initWithProtocolFamily:(int) family socketType:(int) type protocol:(int) protocol address:(NSData *) address;
{
	self=[self initWithProtocolFamily:family
						   socketType:type
							 protocol:protocol
							   socket:socket(family, type, protocol)];	// allocate new socket first
	if(self)
		{
		if(address)
			[address getBytes:&_addr length:sizeof(_addr)];	// initialize
		}
	return self;
}

- (id) initWithProtocolFamily:(int) family socketType:(int) type protocol:(int) protocol socket:(NSSocketNativeHandle) sock;
{ // this is the core initializer
	if(sock < 0)
		{
		[self release];
		return nil;	// was not able to create socket
		}
	self=[super init];
	if(self)
		{
#if 0
		int	r = 1;		// Set the re-use socket option so that we don't get this socket hanging around after we close it (or die)
		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&r,sizeof(r));
#endif
		_addr.sin_family=family;
		_type=type;
		_protocol=protocol;
		_fd=sock;
		}
	return self;
}

- (id) initWithTCPPort:(unsigned short) port;
{
	self=[self initWithProtocolFamily:AF_INET
						   socketType:SOCK_STREAM
							 protocol:IPPROTO_TCP 
							  address:nil];	// no address
	if(self)
		{ // set local port (or 0)
		_addr.sin_port = port;
		}
	return self;
}

- (void) invalidate
{
	// unschedule
	[super invalidate];
}

- (void) dealloc;
{
#if 1
	NSLog(@"NSSocketPort dealloc:%@", self);
#endif
	[super dealloc];
}

- (NSData *) address; { return [NSData dataWithBytesNoCopy:&_addr length:sizeof(_addr) freeWhenDone:NO]; }
- (int) protocol; { return _protocol; }
- (int) protocolFamily; { return _addr.sin_family; }
- (NSSocketNativeHandle) socket; { return _fd; }
- (int) socketType; { return _type; }

@end