/*************************************************************************** NetTCP.m ------------------- begin : Fri Nov 2 01:19:16 UTC 2001 copyright : (C) 2005 by Andrew Ruder email : aeruder@ksu.edu ***************************************************************************/ /*************************************************************************** * * * This program 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 2.1 of the * * License or (at your option) any later version. * * * ***************************************************************************/ /** * NetTCP reference * * * * * Revision 1 * November 8, 2003 * Andrew Ruder */ #include "config.h" #import "NetTCP.h" #import #import #import #import #import #import #import #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif NSString *NetclassesErrorTimeout = @"Connection timed out"; NSString *NetclassesErrorBadAddress = @"Bad address"; NSString *NetclassesErrorAborted = @"Connection aborted"; static TCPSystem *default_system = nil; @interface TCPSystem (InternalTCPSystem) - (int)openPort: (uint16_t)portNumber; - (int)openPort: (uint16_t)portNumber onHost: (NSHost *)aHost; - (int)connectToHost: (NSHost *)aHost onPort: (uint16_t)portNumber withTimeout: (int)timeout inBackground: (BOOL)background; - setErrorString: (NSString *)anError withErrno: (int)aErrno; @end @interface TCPConnecting (InternalTCPConnecting) - initWithNetObject: (id )netObject withTimeout: (int)aTimeout; - connectingFailed: (NSString *)error; - connectingSucceeded; - timeoutReceived: (NSTimer *)aTimer; @end @interface TCPConnectingTransport : NSObject < NetTransport > { BOOL connected; int desc; NSHost *remoteHost; NSHost *localHost; NSMutableData *writeBuffer; TCPConnecting *owner; } - (NSMutableData *)writeBuffer; - initWithDesc: (int)aDesc withRemoteHost: (NSHost *)theAddress withOwner: (TCPConnecting *)anObject; - (void)close; - (NSData *)readData: (int)maxDataSize; - (BOOL)isDoneWriting; - writeData: (NSData *)data; - (NSHost *)remoteHost; - (NSHost *)localHost; - (int)desc; @end @implementation TCPConnectingTransport - (NSMutableData *)writeBuffer { return writeBuffer; } - initWithDesc: (int)aDesc withRemoteHost: (NSHost *)theAddress withOwner: (TCPConnecting *)anObject { struct sockaddr_in x; socklen_t address_length = sizeof(x); if (!(self = [super init])) return nil; desc = aDesc; writeBuffer = [NSMutableData new]; remoteHost = RETAIN(theAddress); owner = anObject; if (getsockname(desc, (struct sockaddr *)&x, &address_length) != 0) { [[TCPSystem sharedInstance] setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; [self release]; return nil; } connected = YES; localHost = RETAIN([[TCPSystem sharedInstance] hostFromNetworkOrderInteger: x.sin_addr.s_addr]); return self; } - (void)dealloc { [self close]; RELEASE(writeBuffer); RELEASE(remoteHost); RELEASE(localHost); [super dealloc]; } - (NSData *)readData: (int)maxDataSize { return nil; } - (BOOL)isDoneWriting { return YES; } - writeData: (NSData *)data { char buffer[1]; if (data) { [writeBuffer appendData: data]; return self; } if (recv(desc, buffer, sizeof(buffer), MSG_PEEK) == -1) { if (errno != EAGAIN) { [owner connectingFailed: [NSString stringWithFormat: @"%s", strerror(errno)]]; return self; } } [owner connectingSucceeded]; return self; } - (NSHost *)remoteHost { return remoteHost; } - (NSHost *)localHost { return localHost; } - (int)desc { return desc; } - (void)close { if (!connected) return; close(desc); connected = NO; } @end @implementation TCPConnecting (InternalTCPConnecting) - initWithNetObject: (id )aNetObject withTimeout: (int)aTimeout { if (!(self = [super init])) return nil; netObject = RETAIN(aNetObject); if (aTimeout > 0) { timeout = RETAIN([NSTimer scheduledTimerWithTimeInterval: (NSTimeInterval)aTimeout target: self selector: @selector(timeoutReceived:) userInfo: nil repeats: NO]); } return self; } - connectingFailed: (NSString *)error { if ([netObject conformsToProtocol: @protocol(TCPConnecting)]) { [netObject connectingFailed: error]; } [timeout invalidate]; [transport close]; [[NetApplication sharedInstance] disconnectObject: self]; return self; } - connectingSucceeded { id newTrans = AUTORELEASE([[TCPTransport alloc] initWithDesc: dup([transport desc]) withRemoteHost: [transport remoteHost]]); id buffer = RETAIN([(TCPConnectingTransport *)transport writeBuffer]); [timeout invalidate]; [[NetApplication sharedInstance] disconnectObject: self]; [netObject connectionEstablished: newTrans]; [newTrans writeData: buffer]; RELEASE(buffer); return self; } - timeoutReceived: (NSTimer *)aTimer { if (aTimer != timeout) { [aTimer invalidate]; } [self connectingFailed: NetclassesErrorTimeout]; return self; } @end @implementation TCPConnecting - (void)dealloc { RELEASE(netObject); RELEASE(timeout); [super dealloc]; } - (id )netObject { return netObject; } - (void)abortConnection { [self connectingFailed: NetclassesErrorAborted]; } - (void)connectionLost { DESTROY(transport); } - connectionEstablished: (id )aTransport { transport = RETAIN(aTransport); [[NetApplication sharedInstance] connectObject: self]; [[NetApplication sharedInstance] transportNeedsToWrite: transport]; if ([netObject conformsToProtocol: @protocol(TCPConnecting)]) { [netObject connectingStarted: self]; } return self; } - dataReceived: (NSData *)data { return self; } - (id )transport { return transport; } @end @implementation TCPSystem (InternalTCPSystem) - (int)openPort: (uint16_t)portNumber { return [self openPort: portNumber onHost: nil]; } - (int)openPort: (uint16_t)portNumber onHost: (NSHost *)aHost { struct sockaddr_in sin; int temp; int myDesc; memset(&sin, 0, sizeof(struct sockaddr_in)); if (!aHost) { sin.sin_addr.s_addr = htonl(INADDR_ANY); } else { if (inet_aton([[aHost address] cString], (struct in_addr *)(&(sin.sin_addr))) == 0) { [self setErrorString: NetclassesErrorBadAddress withErrno: 0]; return -1; } } sin.sin_port = htons(portNumber); sin.sin_family = AF_INET; if ((myDesc = socket(AF_INET, SOCK_STREAM, 0)) == -1) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } temp = 1; if (setsockopt(myDesc, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(temp)) == -1) { close(myDesc); [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } if (bind(myDesc, (struct sockaddr *) &sin, sizeof(struct sockaddr)) < 0) { close(myDesc); [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } temp = 1; if (setsockopt(myDesc, SOL_SOCKET, SO_KEEPALIVE, &temp, sizeof(temp)) == -1) { close(myDesc); [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } if (listen(myDesc, 5) == -1) { close(myDesc); [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } return myDesc; } - (int)connectToHost: (NSHost *)host onPort: (uint16_t)portNumber withTimeout: (int)timeout inBackground: (BOOL)bck { int myDesc; struct sockaddr_in destAddr; if (!host) { [self setErrorString: NetclassesErrorBadAddress withErrno: 0]; return -1; } if ((myDesc = socket(AF_INET, SOCK_STREAM, 0)) == -1) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; return -1; } destAddr.sin_family = AF_INET; destAddr.sin_port = htons(portNumber); if (!(inet_aton([[host address] cString], (struct in_addr *)(&destAddr.sin_addr)))) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(myDesc); return -1; } memset(&(destAddr.sin_zero), 0, sizeof(destAddr.sin_zero)); if (timeout > 0 || bck) { if (fcntl(myDesc, F_SETFL, O_NONBLOCK) == -1) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(myDesc); return -1; } } if (connect(myDesc, (struct sockaddr *)&destAddr, sizeof(destAddr)) == -1) { if (errno == EINPROGRESS) // Need to work with timeout now. { fd_set fdset; struct timeval selectTime; int selectReturn; if (bck) { return myDesc; } FD_ZERO(&fdset); FD_SET(myDesc, &fdset); selectTime.tv_sec = timeout; selectTime.tv_usec = 0; selectReturn = select(myDesc + 1, 0, &fdset, 0, &selectTime); if (selectReturn == -1) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(myDesc); return -1; } if (selectReturn > 0) { char buffer[1]; if (recv(myDesc, buffer, sizeof(buffer), MSG_PEEK) == -1) { if (errno != EAGAIN) { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(myDesc); return -1; } } } else { [self setErrorString: NetclassesErrorTimeout withErrno: 0]; close(myDesc); return -1; } } else // connect failed with something other than EINPROGRESS { [self setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(myDesc); return -1; } } return myDesc; } - setErrorString: (NSString *)anError withErrno: (int)aErrno { errorNumber = aErrno; if (anError == errorString) return self; RELEASE(errorString); errorString = RETAIN(anError); return self; } @end @implementation TCPSystem + sharedInstance { return (default_system) ? default_system : [[self alloc] init]; } - init { if (!(self = [super init])) return nil; if (default_system) { [self release]; return nil; } default_system = RETAIN(self); return self; } - (NSString *)errorString { return errorString; } - (int)errorNumber { return errorNumber; } - (id )connectNetObject: (id )netObject toHost: (NSHost *)aHost onPort: (uint16_t)aPort withTimeout: (int)aTimeout { int desc; id transport; desc = [self connectToHost: aHost onPort: aPort withTimeout: aTimeout inBackground: NO]; if (desc < 0) { return nil; } transport = AUTORELEASE([[TCPTransport alloc] initWithDesc: desc withRemoteHost: aHost]); if (!(transport)) { close(desc); return nil; } [netObject connectionEstablished: transport]; return netObject; } - (TCPConnecting *)connectNetObjectInBackground: (id )netObject toHost: (NSHost *)aHost onPort: (uint16_t)aPort withTimeout: (int)aTimeout { int desc; id transport; id object; desc = [self connectToHost: aHost onPort: aPort withTimeout: 0 inBackground: YES]; if (desc < 0) { return nil; } object = AUTORELEASE([[TCPConnecting alloc] initWithNetObject: netObject withTimeout: aTimeout]); transport = AUTORELEASE([[TCPConnectingTransport alloc] initWithDesc: desc withRemoteHost: aHost withOwner: object]); if (!transport) { close(desc); return nil; } [object connectionEstablished: transport]; return object; } - (BOOL)hostOrderInteger: (uint32_t *)aNumber fromHost: (NSHost *)aHost { struct in_addr addr; if (!aHost) return NO; if (![aHost address]) return NO; if (inet_aton([[aHost address] cString], &addr) != 0) { if (aNumber) { *aNumber = ntohl(addr.s_addr); return YES; } } return NO; } - (BOOL)networkOrderInteger: (uint32_t *)aNumber fromHost: (NSHost *)aHost { struct in_addr addr; if (!aHost) return NO; if (![aHost address]) return NO; if (inet_aton([[aHost address] cString], &addr) != 0) { if (aNumber) { *aNumber = addr.s_addr; return YES; } } return NO; } - (NSHost *)hostFromNetworkOrderInteger: (uint32_t)ip { struct in_addr addr; char *temp; addr.s_addr = ip; temp = inet_ntoa(addr); if (temp) { return [NSHost hostWithAddress: [NSString stringWithCString: temp]]; } return nil; } - (NSHost *)hostFromHostOrderInteger: (uint32_t)ip { struct in_addr addr; char *temp; addr.s_addr = htonl(ip); temp = inet_ntoa(addr); if (temp) { return [NSHost hostWithAddress: [NSString stringWithCString: temp]]; } return nil; } @end @implementation TCPPort - initOnHost: (NSHost *)aHost onPort: (uint16_t)aPort { struct sockaddr_in x; socklen_t address_length = sizeof(x); if (!(self = [super init])) return nil; desc = [[TCPSystem sharedInstance] openPort: aPort onHost: aHost]; if (desc < 0) { [self release]; return nil; } if (getsockname(desc, (struct sockaddr *)&x, &address_length) != 0) { [[TCPSystem sharedInstance] setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; close(desc); [self release]; return nil; } connected = YES; port = ntohs(x.sin_port); [[NetApplication sharedInstance] connectObject: self]; return self; } - initOnPort: (uint16_t)aPort { return [self initOnHost: nil onPort: aPort]; } - setNetObject: (Class)aClass { if (![aClass conformsToProtocol: @protocol(NetObject)]) { [NSException raise: FatalNetException format: @"%@ does not conform to < NetObject >", NSStringFromClass(aClass)]; } netObjectClass = aClass; return self; } - (int)desc { return desc; } - (void)close { if (!connected) return; close(desc); connected = NO; } - (void)connectionLost { } - newConnection { int newDesc; struct sockaddr_in sin; unsigned temp; TCPTransport *transport; NSHost *newAddress; temp = sizeof(struct sockaddr_in); if ((newDesc = accept(desc, (struct sockaddr *)&sin, &temp)) == -1) { [NSException raise: FatalNetException format: @"%s", strerror(errno)]; } newAddress = [[TCPSystem sharedInstance] hostFromNetworkOrderInteger: sin.sin_addr.s_addr]; transport = AUTORELEASE([[TCPTransport alloc] initWithDesc: newDesc withRemoteHost: newAddress]); if (!transport) { close(newDesc); return self; } [AUTORELEASE([netObjectClass new]) connectionEstablished: transport]; return self; } - (uint16_t)port { return port; } - (void)dealloc { [self close]; [super dealloc]; } @end static NetApplication *net_app = nil; @implementation TCPTransport + (void)initialize { net_app = RETAIN([NetApplication sharedInstance]); } - initWithDesc: (int)aDesc withRemoteHost: (NSHost *)theAddress { struct sockaddr_in x; socklen_t address_length = sizeof(x); if (!(self = [super init])) return nil; desc = aDesc; writeBuffer = RETAIN([NSMutableData dataWithCapacity: 2000]); remoteHost = RETAIN(theAddress); if (getsockname(desc, (struct sockaddr *)&x, &address_length) != 0) { [[TCPSystem sharedInstance] setErrorString: [NSString stringWithFormat: @"%s", strerror(errno)] withErrno: errno]; [self release]; return nil; } localHost = RETAIN([[TCPSystem sharedInstance] hostFromNetworkOrderInteger: x.sin_addr.s_addr]); connected = YES; return self; } - (void)dealloc { [self close]; RELEASE(writeBuffer); RELEASE(localHost); RELEASE(remoteHost); [super dealloc]; } #define READ_BLOCK_SIZE 65530 - (NSData *)readData: (int)maxDataSize { char *buffer; int readReturn; NSMutableData *data; int remaining; int bufsize; fd_set readSet; int toRead; int loops = 8; struct timeval zeroTime = { 0, 0 }; if (!connected) { [NSException raise: FatalNetException format: @"Not connected"]; } if (maxDataSize <= 0) { remaining = -1; bufsize = READ_BLOCK_SIZE; } else { remaining = maxDataSize; bufsize = (READ_BLOCK_SIZE < remaining ? READ_BLOCK_SIZE : remaining); } buffer = malloc(bufsize); if (!buffer) { [NSException raise: NSMallocException format: @"%s", strerror(errno)]; } data = [NSMutableData dataWithCapacity: bufsize]; do { if (remaining == -1) { toRead = bufsize; } else { toRead = bufsize < remaining ? bufsize : remaining; } readReturn = read(desc, buffer, toRead); if (readReturn == 0) { id except; free(buffer); except = [NSException exceptionWithName: NetException reason: @"Socket closed" userInfo: [NSDictionary dictionaryWithObjectsAndKeys: data, @"Data", nil]]; [except raise]; } if (readReturn == -1) { id except; free(buffer); except = [NSException exceptionWithName: NetException reason: [NSString stringWithCString: strerror(errno)] userInfo: [NSDictionary dictionaryWithObjectsAndKeys: data, @"Data", nil]]; [except raise]; } [data appendBytes: buffer length: readReturn]; if (readReturn < bufsize) { break; } if (remaining != -1) { remaining -= readReturn; if (remaining == 0) { break; } } FD_ZERO(&readSet); FD_SET(desc, &readSet); select(desc + 1, &readSet, NULL, NULL, &zeroTime); --loops; } while (loops && FD_ISSET(desc, &readSet)); free(buffer); return data; } #undef READ_BLOCK_SIZE - (BOOL)isDoneWriting { if (!connected) { [NSException raise: FatalNetException format: @"Not connected"]; } return ([writeBuffer length]) ? NO : YES; } - writeData: (NSData *)aData { int writeReturn; char *bytes; int length; if (aData) { if ([aData length] == 0) { return self; } if ([writeBuffer length] == 0) { [net_app transportNeedsToWrite: self]; } [writeBuffer appendData: aData]; return self; } if (!connected) { [NSException raise: FatalNetException format: @"Not connected"]; } if ([writeBuffer length] == 0) { return self; } writeReturn = write(desc, [writeBuffer mutableBytes], [writeBuffer length]); if (writeReturn == -1) { [NSException raise: FatalNetException format: @"%s", strerror(errno)]; } if (writeReturn == 0) { return self; } bytes = (char *)[writeBuffer mutableBytes]; length = [writeBuffer length] - writeReturn; memmove(bytes, bytes + writeReturn, length); [writeBuffer setLength: length]; return self; } - (id)localHost { return localHost; } - (id)remoteHost { return remoteHost; } - (int)desc { return desc; } - (void)close { if (!connected) return; connected = NO; close(desc); } @end