/* 
   NSPortCoder.m

   Implementation of NSPortCoder object for remote messaging

   Copyright (C) 1994, 1995, 1996, 1997 Free Software Foundation, Inc.

   Author:	Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
   Date:	July 1994
   Rewrite:	Richard Frith-Macdonald <richard@brainstorm.co.uk>
   Date:	August 1997

   Complete rewrite:
   Dr. H. Nikolaus Schaller <hns@computer.org>
   Date: Jan-Sept 2006
   Some implementation expertise comes from from Crashlogs found on the Internet: Google e.g. for "NSPortCoder sendBeforeTime:"

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

#import <Foundation/NSPortCoder.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSDistantObject.h>
#import <Foundation/NSInvocation.h>
#import <Foundation/NSMethodSignature.h>
#import <Foundation/NSRunLoop.h>

#import "NSPrivate.h"

@implementation NSPortCoder

+ (NSPortCoder *) portCoderWithReceivePort:(NSPort *) recv
								  sendPort:(NSPort *) send
								components:(NSArray *) cmp;
{
	return [[[self alloc] initWithReceivePort:recv sendPort:send components:cmp] autorelease];
}

- (void) sendBeforeTime:(NSTimeInterval) time sendReplyPort:(NSPort *) port;
{ // this method is not documented but exists!
	NSPortMessage *pm=[[NSPortMessage alloc] initWithSendPort:_send
												  receivePort:_recv
												   components:_components];
	NSDate *due=[NSDate dateWithTimeIntervalSinceNow:time];
	BOOL r;
	[pm setMsgid:_msgid];
#if 1
	NSLog(@"sendBeforeTime %@ reply %@ msgid=%d", due, port, _msgid);
#endif
	r=[pm sendBeforeDate:due];
	[pm release];
	if(!r)
		[NSException raise:NSPortTimeoutException format:@"could not send request within %.0lf seconds", time];
}

- (void) dispatch;
{ // handle components either passed during initialization or received while sending
	[_connection handlePortCoder:self];	// forward to connection
}

- (NSConnection *) connection; { return _connection; }

- (NSArray *) _components; { return _components; }
- (void) _setConnection:(NSConnection *) connection; { _connection=connection; }	// not retained!
- (void) _setMsgid:(unsigned) msgid; { _msgid=msgid; }
- (unsigned) _msgid; { return _msgid; }

- (id) initWithReceivePort:(NSPort *) recv sendPort:(NSPort *) send components:(NSArray *) cmp;
{
	if((self=[super init]))
		{
		_recv=[recv retain];
		_send=[send retain];
		_components=[cmp retain];	// for sending, pass in an empty NSMutableArray
		}
	return self;
}

- (void) dealloc;
{
	// [_connection release];	// not retained
	[_recv release];
	[_send release];
	[_components release];
	[super dealloc];
}

- (BOOL) isBycopy; { return _isBycopy; }
- (BOOL) isByref; { return _isByref; }

// core encoding

- (void) encodePortObject:(NSPort *) port;
{
	[(NSMutableArray *) _components addObject:port];
}

// CHECKME:
// shouldn't we be able to handle conditional objects and two passes? why? when?
// Well, if we want to send byCopy a dictionary or NSView tree with self/parent-references.
// But sending byRef shouldn't be a problem.

- (void) encodeObject:(id) obj
{
	Class class;
#if 0
	NSLog(@"NSPortCoder encodeObject%@%@ %p", _isBycopy?@" bycopy":@"", _isByref?@" byref":@"", obj);
	NSLog(@"  obj %@", obj);
#endif
	if(!obj)
		{
		NSLog(@"trying to encode nil object");
		class=Nil;	// encode nil object as Nil class
		}
	else
		{
		class=[obj classForPortCoder];
// FIXME: should also be looked up in class translation table!
		}
#if 0
	NSLog(@"  classForPortCoder %@", NSStringFromClass(class));
#endif
	[self encodeValueOfObjCType:@encode(Class) at:&class];
#if 0
	NSLog(@"  replacement %@", [obj replacementObjectForPortCoder:self]);
#endif
	[[obj replacementObjectForPortCoder:self] encodeWithCoder:self];	// translate and encode
	_isBycopy=_isByref=NO;	// reset flags for next encoder call
}

- (void) encodeBycopyObject:(id) obj
{
	if([obj isKindOfClass:[NSDistantObject class]])
		{ // we should encode a copy of the local object and NOT the proxy!
		obj=[(NSDistantObject *) obj _localObject];
		}
	_isBycopy=YES;
	[self encodeObject:obj];
}

- (void) encodeByrefObject:(id) obj
{
	if(![obj isKindOfClass:[NSDistantObject class]])
		obj=[NSDistantObject proxyWithLocal:obj connection:_connection];	// replace by a proxy reference
	_isByref=YES;
	[self encodeObject:obj];
}

- (void) encodeBytes:(const void *)address length:(unsigned)numBytes;
{
	[self encodeDataObject:[NSData dataWithBytes:address length:numBytes]];
}

- (void) encodeDataObject:(NSData *)data
{
	[(NSMutableArray *) _components addObject:data];	// as it is...
}

- (void) encodeValueOfObjCType:(const char*)type at:(const void*)address
{ // must encode in network byte order (i.e. bigendian)
#if 0
	NSLog(@"NSPortCoder encodeValueOfObjCType:%s", type);
#endif
	switch(*type)
		{
		default:
			NSLog(@"%@ can't encodeValueOfObjCType:%s", self, type);
			return;
		case _C_ID:
			{
				[self encodeObject:*((id *)address)];
				break;
			}
		case _C_CLASS:
			{
				Class c=*((Class *)address);
				if(!c)
					{
					NSLog(@"trying to encode Nil class");
					[self encodeDataObject:[@"Nil" dataUsingEncoding:NSUTF8StringEncoding]];
					}
				else
					[self encodeDataObject:[NSStringFromClass(c) dataUsingEncoding:NSUTF8StringEncoding]];		// encode class name as String
				break;
			}
		case _C_SEL:
			{
				SEL sel=*((SEL *)address);
				if(!sel)
					[self encodeDataObject:[@"NULL" dataUsingEncoding:NSUTF8StringEncoding]];
				else
					[self encodeDataObject:[NSStringFromSelector(sel) dataUsingEncoding:NSUTF8StringEncoding]];	// encode selector name
				break;
			}
		case _C_CHR:
		case _C_UCHR:
			{
				[self encodeBytes:address length:sizeof(char)];
				break;
			}
		case _C_SHT:
		case _C_USHT:
			{
				short val=NSSwapHostShortToBig(*(short *)address);
				[self encodeBytes:&val length:sizeof(short)];
				break;
			}
		case _C_INT:
		case _C_UINT:
			{
				int val=NSSwapHostIntToBig(*(int *)address);
				[self encodeBytes:&val length:sizeof(int)];
				break;
			}
		case _C_LNG:
		case _C_ULNG:
			{
				long val=NSSwapHostLongToBig(*(long *)address);
				[self encodeBytes:&val length:sizeof(long)];
				break;
			}
		case _C_LNG_LNG:
		case _C_ULNG_LNG:
			{
				long long val=NSSwapHostLongLongToBig(*(long long *)address);
				[self encodeBytes:&val length:sizeof(long long)];
				break;
			}
		case _C_FLT:
			{
				float val=NSSwapHostFloatToBig(*(float *)address);
				[self encodeBytes:&val length:sizeof(float)];
				break;
			}
		case _C_DBL:
			{
				double val=NSSwapHostDoubleToBig(*(double *)address);
				[self encodeBytes:&val length:sizeof(double)];
				break;
			}
//		case _C_PTR:	FIXME: can we really encode a C_PTR object as a string???
		case _C_ATOM:
		case _C_CHARPTR:
			{
				char *str=*((char **)address);
				[self encodeBytes:str length:strlen(str)+1];	// include 0-byte
				break;
			}
		case _C_ARY_B:
		case _C_STRUCT_B:
		case _C_UNION_B:
			{
				// FIXME: is that correct???
				int len=atoi(type+1);
				[self encodeBytes:address length:len];
				break;
			}
		case _C_VOID:
			break;
		}
}

// core decoding

- (NSPort *) decodePortObject;
{
	// decode a port
	return NIMP;
}

- (id) decodeObject
{
	Class class;
	[self decodeValueOfObjCType:@encode(Class) at:&class];
#if 1
	NSLog(@"NSPortCoder decodeObject of class %@", NSStringFromClass(class));
#endif
	if(class == Nil)
		return nil;	// was a nil object
	// should also look up in class translation table!
	return [[class alloc] initWithCoder:self];	// decode
}

- (void *) decodeBytesWithReturnedLength:(unsigned *)numBytes;
{
	NSData *d=[self decodeDataObject];
	*numBytes=[d length];
	return (void *) [d bytes];
}

- (NSData *) decodeDataObject;
{ // get next object as it is
	return [_components objectAtIndex:_nextComponent++];	// will raise exception if we decode beyond end
}

- (void) decodeValueOfObjCType:(const char *) type at:(void *) address
{ // must encode in network byte order (i.e. bigendian)
#if 1
	NSLog(@"NSPortCoder decodeValueOfObjCType:%s", type);
#endif
	switch(*type)
		{
		default:
			NSLog(@"%@ can't decodeValueOfObjCType:%s", self, type);
			return;
		case _C_ID:
			{
				*((id *)address)=[self decodeObject];
				return;
			}
		case _C_CLASS:
			{
				NSString *class=[[NSString alloc] initWithData:[self decodeDataObject] encoding:NSUTF8StringEncoding];
				if(!class)
					{
					NSLog(@"could not decode Class");
					*((Class *)address)=Nil;
					}
				else
					{
					if([class isEqualToString:@"Nil"])
						*((Class *)address)=Nil;		// Nil class was encoded
					else
						*((Class *)address)=NSClassFromString(class);		// decode class by name
					[class release];
					}
				return;
			}
		case _C_SEL:
			{
				NSString *selector=[[NSString alloc] initWithData:[self decodeDataObject] encoding:NSUTF8StringEncoding];
				if(!selector)
					{
					NSLog(@"could not decode SEL");
					*((SEL *)address)=NULL;
					}
				else
					{
					if([selector isEqualToString:@"NULL"])
						*((SEL *)address)=NULL;	// NULL selector (e.g. an [target action])
					else
						*((SEL *)address)=NSSelectorFromString(selector);		// decode selector by name
					[selector release];
					}
				return;
			}
		case _C_CHR:
		case _C_UCHR:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(char)
				*((char *) address) = *(char *) addr;
				break;
			}
		case _C_SHT:
		case _C_USHT:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(short)
				*((short *) address) = NSSwapBigShortToHost(*(short *) addr);
				break;
			}
		case _C_INT:
		case _C_UINT:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(int)
				*((int *) address) = NSSwapBigIntToHost(*(int *) addr);
				break;
			}
		case _C_LNG:
		case _C_ULNG:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(short)
				*((long *) address) = NSSwapBigLongToHost(*(long *) addr);
				break;
			}
		case _C_LNG_LNG:
		case _C_ULNG_LNG:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(long long)
				*((long long *) address) = NSSwapBigLongLongToHost(*(long long *) addr);
				break;
			}
		case _C_FLT:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(float)
				*((float *) address) = NSSwapBigFloatToHost(*(float *) addr);
				break;
			}
		case _C_DBL:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				// check for numBytes == sizeof(double)
				*((double *) address) = NSSwapBigShortToHost(*(double *) addr);
				break;
			}
//		case _C_PTR:	FIXME: can we really encode a C_PTR object as a string???
		case _C_ATOM:
		case _C_CHARPTR:
			{
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				NSLog(@"decoded %u bytes string", numBytes);
				*((char **) address) = addr;	// store address (storage object is an autoreleased NSData!)
				break;
			}
#if 0
		case _C_ARY_B:
		case _C_STRUCT_B:
		case _C_UNION_B:
			{
				// FIXME: is that correct???
				int len=atoi(type+1);
				unsigned numBytes;
				void *addr=[self decodeBytesWithReturnedLength:&numBytes];
				NSLog(@"decoded %u bytes string", numBytes);
				// make a copy somehow autoreleased
				*((char **) address) = "bytes";
				break;
			}
#endif
		case _C_VOID:
			break;
		}
}

@end

@implementation NSObject (NSPortCoder)

// default implementations

- (Class) classForPortCoder;
{
	return [self classForCoder];
}

- (id) replacementObjectForPortCoder:(NSPortCoder*)coder
{ // default is to encode a proxy
	return [NSDistantObject proxyWithLocal:self connection:[coder connection]];
}

@end

@implementation NSPortMessage

struct NSPortMessageHeader
{
	unsigned msgId;			// message id
	unsigned components;	// number of components
	unsigned length;		// total size of the message so that we can verify the message integrity
};

struct NSPortMessageComponent
{
	unsigned long length;
	char data[1];
};

/*  Mach defines:
	port_t				NSPort object
	MSG_TYPE_BYTE		NSData object
	MSG_TYPE_CHAR	
	MSG_TYPE_INTEGER_32	
 */

+ (void *) _machMessageWithId:(unsigned) msgid forSendPort:(NSPort *)sendPort receivePort:(NSPort *)receivePort components:(NSArray *)components returnMessageLength:(unsigned long *)messageLength;
{ // encode as a binary message - return objc_alloc'ed object (NOT autoreleased!)
	unsigned size=sizeof(struct NSPortMessageHeader);
	NSEnumerator *e=[components objectEnumerator];
	char *buffer;
	char *bp;
	id c;
	while((c=[e nextObject]))
		{ // first pass - determine size
		if([c isKindOfClass:[NSData class]])
			size+=sizeof(unsigned long)+[c length];	// component size plus data length
		else if([c isKindOfClass:[NSMessagePort class]])
			size+=sizeof(unsigned long)+strlen([c _fileSystemRepresentation])+1;	// header + file name length plus 1
		else
			return NULL;	// should be an NSMessagePort!
		}
	if(messageLength)
		*messageLength=size;
	buffer=objc_malloc(size);
	if(buffer)
		{
		((struct NSPortMessageHeader *)buffer)->msgId=NSSwapHostLongToBig(msgid);
		((struct NSPortMessageHeader *)buffer)->components=NSSwapHostLongToBig([components count]);
		((struct NSPortMessageHeader *)buffer)->length=NSSwapHostLongToBig(size);
		bp=buffer+sizeof(struct NSPortMessageHeader);
		e=[components objectEnumerator];
		while((c=[e nextObject]))
			{ // second pass - serialize
			if([c isKindOfClass:[NSData class]])
				{
				unsigned long len=[c length];
				unsigned long l=NSSwapHostLongToBig(len+1);	// encode component length (+1 to use 0 for NSPorts)
				memcpy(bp, &l, sizeof(l));
				[c getBytes:bp+sizeof(l)];	// get bytes of object
				bp+=sizeof(l)+len;
				}
			else
				{ // serialize an NSPort
				static unsigned long l=0;	// encode component length
				const char *filename=[(NSMessagePort *) c _fileSystemRepresentation];
				memcpy(bp, &l, sizeof(l));	// encode length 0
				memcpy(bp+sizeof(l), filename, strlen(filename)+1);	// append bytes of filename including trailing 0
				bp+=sizeof(l)+strlen(filename)+1;
				}
			}
		}
	return buffer;
}

- (id) initWithMachMessage:(void *) buffer;		// NOTE: does not initialize _recv and _send!
{ // decode a binary encoded message - for some details see e.g. http://objc.toodarkpark.net/Foundation/Classes/NSPortMessage.htm
	if((self=[super init]))
		{
		unsigned long i, count, length;
		char *bp;
		_msgid=NSSwapBigLongToHost(((struct NSPortMessageHeader *) buffer)->msgId);
		count=NSSwapBigLongToHost(((struct NSPortMessageHeader *) buffer)->components);
		length=NSSwapBigLongToHost(((struct NSPortMessageHeader *) buffer)->length);
#if 1
		NSLog(@"msglength=%u msgid=%u components=%u", length, _msgid, count);
#endif
		_components=[[NSMutableArray alloc] initWithCapacity:count];
		bp=(char *) buffer+sizeof(struct NSPortMessageHeader);		
		for(i=0; i<count; i++)
			{
			unsigned long len;
			unsigned long l;
			memcpy(&l, bp, sizeof(l));
			if(l != 0)
				{ // NSData
				len=NSSwapBigLongToHost(l)-1;				// decode component length
#if 0
				NSLog(@"decode component %u at offset %u with length %u", i, bp-(char *) buffer, len); 
#endif
				if((bp-(char *) buffer)+len > length)
					{
					NSLog(@"invalid length (%u) of component %u (max. %u available to end of message)", len, i, length-(bp-(char *) buffer));
					[self release];
					return nil;
					}
				[_components addObject:[NSData dataWithBytes:bp+sizeof(l) length:len]];	// cut out a copy of the data fragment
				bp+=sizeof(l)+len;
				}
			else
				{ // NSMessagePort
				len=strlen(bp+sizeof(l));	// file name length
				NSLog(@"decode NSPort %s", bp+sizeof(l));
				bp+=len+1;	// skip trailing 0
				}
			}
		}
	return self;
}

- (NSArray*) components; { return _components; }

- (id) initWithSendPort: (NSPort*)aPort
			receivePort: (NSPort*)anotherPort
			 components: (NSArray*)items;
{
	if((self=[super init]))
		{
		_recv=[anotherPort retain];
		_send=[aPort retain];
		_components=[items retain];
		}
	return self;
}

- (void) dealloc;
{
	[_recv release];
	[_send release];
	[_components release];
	[super dealloc];
}

- (unsigned) msgid; { return _msgid; }
- (NSPort *) receivePort; { return _recv; }
- (void) _setReceivePort:(NSPort *) p; { ASSIGN(_recv, p); }

- (BOOL) sendBeforeDate:(NSDate*) when;
{
	if(!_send)
		[NSException raise:NSInvalidSendPortException format:@"no send port for message %@", self];
	if(!_recv)
		[NSException raise:NSInvalidReceivePortException format:@"no send port for message %@", self];
#if 0
	NSLog(@"send NSPortMessage: %@ on %@", _components, _send);
#endif
	return [_send sendBeforeDate:when msgid:_msgid components:_components from:_recv reserved:[_send reservedSpaceLength]];
}

- (NSPort *) sendPort; { return _send; }
- (void) _setSendPort:(NSPort *) p; { ASSIGN(_send, p); }
- (void) setMsgid: (unsigned)anId; { _msgid=anId; }

@end
