home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / Foundation / TCPTransport / TCPPort.m < prev    next >
Text File  |  1997-02-19  |  25KB  |  803 lines

  1. /* TCPPort.m created by blaine on Wed 03-Apr-1996
  2.    Copyright 1996, NeXT Computer, Inc.
  3.    Blaine Garst
  4.    Marc Majka
  5.  */
  6.  
  7.  
  8. #import "TCPPort.h"
  9.  
  10. #if defined(WIN32)
  11. #import <windows.h>
  12. #import <winsock.h>
  13. #import <winnt-pdo.h>
  14. #else
  15. #import <sys/types.h>
  16. #import <sys/socket.h>
  17. #import <netinet/in.h>
  18. #import <netdb.h>
  19.  
  20. #define SOCKET int
  21. #define INVALID_SOCKET -1
  22. #define closesocket(x) close(x)
  23.  
  24. #endif
  25.  
  26. #if !defined(__svr4__) && !defined(WIN32)
  27. #import <libc.h>
  28. #endif
  29.  
  30. #define ProtocolNameTCP "tcp"
  31. #define MaxTCPListenQueue 5
  32.  
  33. static int enableLogging = 0;
  34.  
  35.  
  36. /*************** Hashtable stuff *********************/
  37. /*
  38.  * Keep a table of TCPPorts so that if you're sent a port that
  39.  * you already know about, you don't connect, etc. again.
  40.  */
  41.  
  42. static NSHashTable *TCPPorts = NULL;
  43.  
  44. /* create a fake TCPPort to see if its in the table already */
  45. static TCPPort *lookupSocketPort(unsigned short tcpPort, unsigned long addr, TCPPort *listener) {
  46.     struct { @defs(TCPPort) } shadow;
  47.  
  48.     shadow.isa = [TCPPort class];
  49.     shadow.internetAddr = addr;
  50.     shadow.tcpPort = tcpPort;
  51.     shadow.listener = listener;
  52.     // [tableLock lock];
  53.     return NSHashGet(TCPPorts, &shadow);
  54.     // [tableLock unlock];
  55. }
  56.  
  57. /*********** Utilities ********************************/
  58. /*
  59.  * A little function to canonicalize byte order dependent data.
  60.  */
  61.  
  62. static unsigned int sanitize(unsigned int x) {
  63.     return NSSwapBigIntToHost(x);  // no-op on BigEndians
  64. }        
  65.  
  66. // Two socket utilities: tcpp and addrp are supposed to be inout, but
  67. // experience with bind doesn't seem to indicate that they actually change.
  68. // To wit, INADDR_ANY (0) stays that way...
  69. static BOOL tryBind(int fd, unsigned short *tcpp, unsigned long *addrp) {
  70.     struct sockaddr_in addr;
  71.     int len, retval;
  72.  
  73.     // Set up my address
  74.     len = sizeof(struct sockaddr_in);
  75.     memset((char *)&addr, 0, len);
  76.     addr.sin_family = AF_INET;
  77.     addr.sin_addr.s_addr = *addrp; // INADDR_ANY;
  78.     addr.sin_port = htons(*tcpp);
  79.  
  80.     // Give the socket my address and port 
  81.     retval = bind(fd, (struct sockaddr *)&addr, len);
  82.     *addrp = addr.sin_addr.s_addr;
  83.     *tcpp = ntohs(addr.sin_port);
  84.     if (retval < 0) NSLog(@"bind failed");
  85.     return (retval >= 0);
  86. }
  87.  
  88. // again, the out case doesn't seem to do anything XXX
  89. static BOOL tryConnect(int fd, unsigned short *tcpp, unsigned long *addrp) {
  90.     struct sockaddr_in addr;
  91.     int len, retval;
  92.     extern int errno;
  93.  
  94.     // Set up address
  95.     len = sizeof(struct sockaddr_in);
  96.     memset((char *)&addr, 0, len);
  97.     addr.sin_family = AF_INET;
  98.     addr.sin_addr.s_addr = *addrp;
  99.     addr.sin_port = htons(*tcpp);
  100.  
  101.     retval = connect(fd, (struct sockaddr *)&addr, len);
  102.     *addrp = addr.sin_addr.s_addr;
  103.     *tcpp = ntohs(addr.sin_port);
  104.     if (retval != 0) NSLog(@"connect failed, errno %d", errno);
  105.     return (retval == 0);
  106. }
  107.  
  108. static unsigned short getNumber(int s1) {
  109.     struct sockaddr_in mine;
  110.     int len, ret;
  111.  
  112.     len = sizeof(struct sockaddr_in);
  113.     ret = getsockname(s1, (struct sockaddr *)&mine, &len);
  114.     if (ret < 0) printf("socket %d has no name!\n", s1);
  115.     return ntohs(mine.sin_port);
  116. }
  117.  
  118. static SOCKET makeSocket() {
  119.     static struct protoent *proto = NULL;
  120.     SOCKET result;
  121.  
  122. #if 1
  123.     if (!proto) {
  124.     // Get the TCP protocol number
  125.     proto = getprotobyname(ProtocolNameTCP);
  126.     if (proto == NULL) {
  127.           NSLog(@"failed to getproto");
  128.         return INVALID_SOCKET;
  129.     }
  130.     }
  131. #endif
  132.  
  133.     // Create a new TCP socket
  134.     result = socket(PF_INET, SOCK_STREAM, 0 /*proto->p_proto*/);
  135.     if (enableLogging) NSLog(@"created socket %u", result);
  136.     if (result == INVALID_SOCKET)
  137.     if (enableLogging) NSLog(@"socket call failed");
  138.     return result;
  139. }
  140.  
  141. /*******************************************************/
  142. /*********** the real concrete class *******************/
  143. /*******************************************************/
  144.  
  145. @implementation TCPPort
  146.  
  147.  
  148. /*
  149.  * Before anything else, set up the hashtable.  The hashtable
  150.  * doesn't retain its objects because they would never get
  151.  * freed otherwise!
  152.  * Also, watch out for multiple invocations of initialize.  This
  153.  * is a problem for base classes but be careful anyway.
  154.  */
  155. + (void)initialize {
  156. #if defined(WIN32)
  157.     DWORD vR = MAKEWORD(1, 1);
  158.     WSADATA wsaData;
  159. #endif
  160.  
  161.     if (TCPPorts) return;
  162.     TCPPorts = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0);
  163.     // if not multithreaded already, register for multithreaded notification
  164.     // otherwise just dipatch to the notification XXX
  165. #if WATCH_ALL
  166.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readAllNotification:) name:NSFileHandleReadCompletionNotification object:nil];
  167. #endif
  168.  
  169. #if defined(WIN32)
  170.     (void)WSAStartup(vR, &wsaData);
  171. #endif
  172. }
  173.  
  174. #if WATCH_ALL
  175. + (void)readAllNotification:(NSNotification *)note {
  176.     NSLog(@"-- read notification for %d, data length %u", [[note object] fileDescriptor], [[[note userInfo] objectForKey:NSFileHandleNotificationDataItem] length]);
  177. }
  178. #endif
  179.  
  180. + (void)_toggleLogging {
  181.     enableLogging = enableLogging ? 0 : 1;
  182. }
  183.  
  184. /* the machPort cover is grody and should do something radical */
  185. - (TCPPort *)initWithMachPort:(int)machPort {
  186.     // raise or be nicer and return nil
  187.     [self dealloc];
  188.     return nil;    
  189. }
  190.  
  191. /* reimplement hash and isEqual so that we can distinguish
  192.    functional equivalents even if the memory (or socket)
  193.    is different.  Elsewhere we cons up a fake object to see if we can
  194.    find one that already exists so that we can avoid a duplicate.
  195.   */
  196.   
  197. - (unsigned) hash {
  198.     return internetAddr^tcpPort;
  199. }
  200.  
  201. - (BOOL)isEqual:(TCPPort *)other {
  202.     if (!other || other->isa != isa) return NO;
  203.     return other
  204.     && other->isa == isa
  205.     && other->internetAddr == internetAddr
  206.     && other->tcpPort == tcpPort
  207.     && other->listener == listener;
  208.     /* note that the socket itself is defined to be not important!! */
  209. }
  210.  
  211.  
  212.  
  213.  
  214. /*********** initialization stuff **************/
  215.  
  216. - _acceptorWithFileDescriptor:(int)fd {
  217. #if defined(WIN32)
  218.     NSFileHandle *result = [[NSFileHandle alloc] initWithNativeHandle:fd];
  219. #else
  220.     NSFileHandle *result = [[NSFileHandle alloc] initWithFileDescriptor:fd];
  221. #endif
  222.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptNotification:) name:NSFileHandleConnectionAcceptedNotification object:result];
  223.     return result;
  224. }
  225.  
  226. - (void)_readerWithFileHandle:result {
  227.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readNotification:) name:NSFileHandleReadCompletionNotification object:result];
  228.     dataStream = [[DataStream alloc] init];
  229. }
  230.  
  231. - _readerWithFileDescriptor:(int)fd {
  232.     NSFileHandle *result = [[NSFileHandle alloc] initWithFileDescriptor:fd];
  233.     [self _readerWithFileHandle:result];
  234.     return result;
  235. }
  236.  
  237. // build up a tcpPort that will listen()
  238. // tcpPort will indeed be the port we listen on
  239. - (TCPPort *)initWithNumber:(unsigned short)p {
  240.     SOCKET fd = makeSocket();
  241.     int retval;
  242.  
  243.     if (fd == INVALID_SOCKET) {
  244.     [super dealloc];
  245.     return nil;
  246.     }
  247.     internetAddr = INADDR_ANY;
  248.     tcpPort = p;
  249.     if (!tryBind(fd, &tcpPort, &internetAddr)) {
  250.     closesocket(fd);
  251.     [super dealloc];
  252.     return nil;
  253.     }
  254.     if (!p) {
  255.         // XXX untested
  256.         tcpPort = getNumber(fd);
  257.     }
  258.     if (internetAddr == INADDR_ANY) {
  259.         NSHost *me = [NSHost currentHost];
  260.     if (me == nil) {
  261.         closesocket(fd);
  262.         return nil;
  263.     }
  264.     internetAddr = inet_addr([[me address] cString]);
  265.     }
  266.  
  267.     // Put the socket in passive mode (accept connections)
  268.     retval = listen(fd, MaxTCPListenQueue);
  269.     if (retval == INVALID_SOCKET) {
  270.     closesocket(fd);
  271.     [super dealloc];
  272.     return nil;
  273.     }
  274.  
  275.     modes = [NSCountedSet new];
  276.     readWriter = [self _acceptorWithFileDescriptor:fd];
  277.     return self;
  278. }
  279.  
  280.  
  281. - (BOOL)_connect {
  282.     int fd = makeSocket();
  283.     unsigned short myPort = tcpPort;
  284.     unsigned long connectAddr = internetAddr;
  285.     unsigned long tmp;
  286.     
  287.     if (fd == INVALID_SOCKET) {
  288.         return NO;
  289.     }
  290.     if (!listener) return NO;
  291.     if (!tryConnect(fd, &myPort, &connectAddr)) {
  292.         if (enableLogging) NSLog(@"***failed to connect to port %d addr %u", tcpPort, internetAddr);
  293.     closesocket(fd);
  294.     return NO;
  295.     }
  296.  
  297.     if (enableLogging) NSLog(@"connect()ed to port %d addr %u for fd %d", tcpPort, internetAddr, fd);
  298.     tmp = sanitize(listener->tcpPort);
  299.     if (write(fd, &tmp, 4) != 4) {
  300.     closesocket(fd);
  301.     return NO;
  302.     }
  303.     // address is already sanitized
  304.     if (write(fd, &listener->internetAddr, 4) != 4) {
  305.     closesocket(fd);
  306.     return NO;
  307.     }
  308.     readWriter = [self _readerWithFileDescriptor:fd];
  309.     return YES;
  310. }
  311.  
  312.  
  313. //
  314. // creates a connected tcp port suitable for DO (if necessary)
  315. // if one already exists, use it
  316. //
  317. - (TCPPort *)connectToNumber:(unsigned short)p address:(unsigned long)addr {
  318.     TCPPort *sender = lookupSocketPort(p, addr, self);
  319.  
  320.     if (sender) {
  321.         [sender retain];
  322.     }
  323.     if (!sender) {
  324.         sender = [isa alloc];
  325.     sender->internetAddr = addr;
  326.     sender->tcpPort = p;
  327.         sender->listener = [self retain];
  328.     NSHashInsertKnownAbsent(TCPPorts, sender);
  329.     sender->modes = [NSCountedSet new];
  330.     }
  331.  
  332.     if (!sender->readWriter && ![sender _connect]) {
  333.     [sender release];
  334.     sender = nil;
  335.     return nil;
  336.     }
  337.     return [sender autorelease];
  338. }
  339.  
  340. - (TCPPort *)connectToNumber:(unsigned short)p host:(NSString *)hostname {
  341.     NSHost *her;
  342.     unsigned long addr;
  343.  
  344.  
  345.     // Get the server's address
  346.     // XXX this stuff should be a category on NSHost...
  347.     her = [NSHost hostWithName:hostname];
  348.     if (her == nil) {
  349.         return nil;
  350.     }
  351.     addr = inet_addr([[her address] cString]);
  352.  
  353.     return [self connectToNumber:p address:addr];
  354. }
  355.  
  356.  
  357. //
  358. // Create a tcpPort from an existing listen()ing socket
  359. //
  360.  
  361. - (TCPPort *)tcpPortWithAcceptedSocket:handle {
  362.     TCPPort *source;
  363.     unsigned long logicalPort;
  364.     unsigned long destAddr;
  365.     int s = [handle fileDescriptor];
  366.  
  367.     if (enableLogging) NSLog(@"accept()ed socket %d, reading...", s);
  368.     if (read(s, &logicalPort, 4) != 4) {
  369.     if (enableLogging) NSLog(@"didn't read send logicalPort");
  370.     return nil;
  371.     }
  372.     logicalPort = sanitize(logicalPort);
  373.     if (read(s, &destAddr, 4) != 4) {
  374.     if (enableLogging) NSLog(@"didn't read sender's addr");
  375.     return nil;
  376.     }
  377.     source = lookupSocketPort(logicalPort, destAddr, self);
  378.     if (source) {
  379.     if (enableLogging) NSLog(@"found accepted port %d addr %u", logicalPort, destAddr);
  380.         [source retain];
  381.     if (source->reader) {
  382.         if (enableLogging) NSLog(@"***got 2nd reader!");
  383.         [source->reader release];
  384.     }
  385.     source->reader = [handle retain];
  386.     [source _readerWithFileHandle:handle];
  387.     // sync up with any already established connections
  388.     [source->reader readInBackgroundAndNotifyForModes:[modes allObjects]];
  389.     }
  390.     else {
  391.         source = [isa alloc];
  392.     if (enableLogging) NSLog(@"creating accepted port %d addr %u", logicalPort, destAddr);
  393.     source->listener = [self retain];
  394.     source->tcpPort = logicalPort;
  395.     source->internetAddr = destAddr;
  396.     NSHashInsertKnownAbsent(TCPPorts, source);
  397.     source->readWriter = [handle retain];
  398.         source->modes = [NSCountedSet new];
  399.     [source _readerWithFileHandle:handle];
  400.     }
  401.     return [source autorelease];
  402. }
  403.  
  404. // 
  405. // Create a TCP server on an arbitrary socket
  406. //
  407.  
  408. - init {
  409.     return [[isa alloc] initWithNumber:0];
  410. }
  411.    
  412. + (NSPort *)port {
  413.     return [[[self alloc] initWithNumber:0] autorelease];
  414. }
  415.  
  416. /*****************************************************/
  417. /************ Coding behavior ************************/
  418. /*****************************************************/
  419.  
  420. /* The good news is that we don't have to do anything!
  421.  * This works because ports are treated very specially by the
  422.  * portcoder - as long as they internally use the
  423.  * {encode/decode}PortObject: methods.  These methods simply
  424.  * stuff the port supplied into the portCoders array of
  425.  * components.  We'll see these objects later when the portCoder
  426.  * asks us to send a message along, or when we feed components
  427.  * into a portCoder when we get a message off the wire.  All I'm
  428.  * trying to say is that portCoder doesn't care how ports are
  429.  * constructed internally.
  430.  */
  431.  
  432.  
  433. - (void)encodeWithCoder:(NSCoder *)coder {
  434. if (enableLogging) NSLog(@"encoding port %d addr %d (out %d, in %d)", tcpPort, internetAddr, [readWriter fileDescriptor], [reader fileDescriptor]);
  435.     [super encodeWithCoder:coder];
  436. }
  437.  
  438. /*****************************************************/
  439. /********* Deallocation behavior *********************/
  440. /*****************************************************/
  441.  
  442. /* invalidate the port and inform everyone of such a circumstance */
  443. - (void)invalidate {
  444.     id reader2 = reader;
  445.     id writer2 = readWriter;
  446.     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  447.  
  448.     if (invalid) return;
  449.     invalid = 1;
  450.     reader = nil;
  451.     readWriter = nil;
  452.     [connection release]; connection = nil;
  453.  
  454.     NSHashRemove(TCPPorts, self);
  455.     [listener release]; listener = nil;
  456.     [center removeObserver:self];
  457.     [center postNotificationName:NSPortDidBecomeInvalidNotification object:self];
  458.     // we may be free already, so operate on stack variables !!!
  459.     if (reader2) [center removeObserver:reader2];
  460.     if (writer2) [center removeObserver:writer2];
  461.     [reader2 release];
  462.     [writer2 release];
  463. }
  464.  
  465. - (void)dealloc {
  466.     [modes release];
  467.  
  468.     /** partial stuff **/
  469.     [items release];
  470.     [dataItem release];
  471.     [dataStream release];
  472.  
  473.     [super dealloc];
  474. }
  475.  
  476. - (BOOL)isValid {
  477.     return !invalid;
  478. }
  479.  
  480. /********* retain/release/retainCount must all be overridden */
  481. - retain {
  482.    ++refCount;
  483.    return self;
  484. }
  485.  
  486. /* Dereference is tricky.  Since many people can hold references to
  487.    ports we need a way to inform them of our potential invalidity.
  488.    We do this with a notification.  Unfortunately, this bumps our
  489.    reference count.  So if our reference count goes to zip, we
  490.    form and send a notification, bumping it, it then again drops to
  491.    zero and attempts to call dealloc twice.  Not good.
  492.    Keep an isDying bit to catch the final ref==0 case.
  493.  */
  494. - (void)release {
  495.     if (refCount == 0) {
  496.         // invalidate calls postNotification which retain/releases
  497.         // but its correspondent might retain & autorrelease!
  498.         if (!isDying) {
  499.             isDying = 1;
  500.             [self invalidate];
  501.             if (refCount == 0) {  // don't do this now if retain-autoreleased
  502.                 [self dealloc];
  503.             }
  504.         }
  505.         else {    // the autorelease case comes here
  506.             [self dealloc];
  507.         }
  508.     }
  509.     else {
  510.         --refCount;
  511.     }
  512. }
  513.  
  514. - (unsigned)retainCount {
  515.     return refCount + 1;
  516. }
  517.  
  518. /****************************************************************/
  519. /******************* general instance behavior ******************/
  520. /****************************************************************/
  521.  
  522. /********* NSPort overrides ************************************/
  523. /********* needed by all subclassers ***************************/
  524.  
  525. /* Give something useful. FD and tcpPort are equal contenders */
  526. - (int)machPort {
  527.    return tcpPort;
  528. }
  529.  
  530. - (void)setDelegate:(id)anId {
  531.    delegate = anId;
  532. }
  533.  
  534. - (id)delegate {
  535.    return delegate;
  536. }
  537.  
  538. /*** runLoop interactions ****/
  539.  
  540. void doBackgroundForConnection(NSConnection *conn, NSString *mode, BOOL add) {
  541.     TCPPort *listener = [conn receivePort];
  542.     TCPPort *sender = [conn sendPort];
  543.     
  544.     if (add) {
  545.         [listener->modes addObject:mode];
  546.        [sender->modes addObject:mode];
  547.     }
  548.     else {
  549.     [listener->modes removeObject:mode];
  550.      [sender->modes removeObject:mode];
  551.     }
  552.     if (enableLogging) NSLog(@"accept()ing on %@ for modes %@", listener, listener->modes);
  553.     [listener->readWriter acceptConnectionInBackgroundAndNotifyForModes:[listener->modes allObjects]];
  554.     
  555.     if (listener != sender) {
  556.     if (enableLogging) NSLog(@"read()ing on %@ for modes %@", sender, sender->modes);
  557.     [sender->readWriter readInBackgroundAndNotifyForModes:[sender->modes allObjects]];
  558.     [sender->reader readInBackgroundAndNotifyForModes:[sender->modes allObjects]];
  559.     }
  560. }
  561.  
  562.    
  563. - (void)addConnection:(NSConnection *)conn toRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode {
  564.     doBackgroundForConnection(conn, mode, 1);
  565. }
  566.  
  567. - (void)removeConnection:(NSConnection *)conn fromRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode {
  568.     doBackgroundForConnection(conn, mode, 0);
  569. }
  570.  
  571.  
  572. #define MAGIC_NUMBER 19560610
  573. #define DATA_TYPE 1
  574. #define PORT_TYPE 2
  575.  
  576. // this method causes portCoder to reserve space in the first data
  577. // component for (surprise) necessary headers.  This avoids a message
  578. // construction copy in many many cases.
  579.  
  580. - (unsigned)reservedSpaceLength {
  581.     return 16;    // magic, component count, DATA_TYPE, data length
  582. }
  583.  
  584. // portCoder tells us to send a message over the wire.  We're the
  585. // sendPort so go ahead and use our socket.
  586.  
  587. - (BOOL) sendBeforeDate:(NSDate *)limitDate components:(NSMutableArray *)components from:(NSPort *)receivePort reserved:(unsigned)rsvd {
  588.     unsigned counter;
  589.     NSData *firstComponent = [components objectAtIndex:0];
  590.     int *header = (int *)[firstComponent bytes];
  591.     int length = [firstComponent length];
  592.     int written;
  593.  
  594.     if (rsvd != 16) {
  595.         if (enableLogging) NSLog(@"whoops, no header space reserved");
  596.     // XXX be friendlier for more general use
  597.         return NO;
  598.     }
  599.     // prepare magic number
  600.     header[0] = sanitize(MAGIC_NUMBER);
  601.     // prepare number of components, etc.
  602.     header[1] = sanitize([components count]);
  603.     header[2] = sanitize(DATA_TYPE);
  604.     header[3] = sanitize(length-rsvd);
  605.     written = write([readWriter fileDescriptor], [firstComponent bytes], length);
  606.     if (written != length) {
  607.         if (enableLogging) NSLog(@"oops, didn't send the entire first hunk");
  608.         return NO;
  609.     }
  610.     // better schemes might be to collect several of these, use
  611.     // a gathered write, etc.
  612.     for (counter = 1; counter < [components count]; ++counter) {
  613.         id item = [components objectAtIndex:counter];
  614.  
  615.         if ([item isMemberOfClass:[NSData class]]) {
  616.             int length = [item length];
  617.             int stuff[2] = { sanitize(DATA_TYPE), sanitize(length) };
  618.  
  619.             written = write([readWriter fileDescriptor], &stuff, sizeof(stuff));
  620.             if (written != sizeof(stuff)) {
  621.                 if (enableLogging) NSLog(@"oops, didn't send the type");
  622.                 return NO;
  623.             }
  624.             written = write([readWriter fileDescriptor], [item bytes], length);
  625.             if (written != length) {
  626.                 if (enableLogging) NSLog(@"oops, didn't send the data");
  627.                 return NO;
  628.             }
  629.         }
  630.         else if ([item isMemberOfClass:[TCPPort class]]) {
  631.             TCPPort *sp = item;
  632.             int stuff[3] = { sanitize(PORT_TYPE), sanitize(sp->tcpPort), sp->internetAddr };
  633.         if (enableLogging) NSLog(@"writing sanitized port %d addr %d", stuff[1], stuff[2]);           
  634.             written = write([readWriter fileDescriptor], &stuff, sizeof(stuff));
  635.             if (written != sizeof(stuff)) {
  636.                 if (enableLogging) NSLog(@"oops, didn't send the port stuff");
  637.                 return NO;
  638.             }
  639.  
  640.         }
  641.         else {
  642.             if (enableLogging) NSLog(@"can't send component of type %@", [item class]);
  643.             return NO;
  644.         }
  645.     }
  646.     if (enableLogging) NSLog(@"successfully wrote message on %d", [readWriter fileDescriptor]);
  647.     return YES;
  648. }
  649.  
  650. enum PartialState {
  651.     READING_HEADER,
  652.     READING_TYPE,
  653.     READING_DATA_LENGTH,
  654.     READING_DATA,
  655.     READING_PORT,
  656.     DISPATCHING,
  657. };
  658.  
  659. - (void)readNotification:(NSNotification *)note {
  660.     NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
  661.     if ([data length]) {
  662.     if (enableLogging) NSLog(@"read notification to %@; restarting with %@", self, modes);
  663.         [[note object] readInBackgroundAndNotifyForModes:[modes allObjects]];
  664.     }
  665.     else {
  666.     if (enableLogging) NSLog(@"failed read notification %@", data);
  667.         [self invalidate];
  668.     return;
  669.     }
  670.     [dataStream appendData:[[note userInfo] objectForKey:NSFileHandleNotificationDataItem]];
  671.     for(;;) switch (partialState) {
  672.         case READING_HEADER: {
  673.             int stuff[2];
  674.             if (![dataStream getBytes:&stuff[0] length:sizeof(stuff)]) return;
  675.             if (sanitize(stuff[0]) != MAGIC_NUMBER) {
  676.                 if (enableLogging) NSLog(@"**** didn't get magic number");
  677.                 [self invalidate];
  678.                 return;
  679.             }
  680.             nItems = sanitize(stuff[1]);
  681.             itemCounter = 0;
  682.             if (!items) items = [[NSMutableArray alloc] init];
  683.             partialState = READING_TYPE;
  684.         }
  685.         /* fall through */
  686.     case READING_TYPE: {
  687.             if (![dataStream getBytes:&itemType length:sizeof(itemType)]) return;
  688.             itemType = sanitize(itemType);
  689.             if (itemType == DATA_TYPE)
  690.                 partialState = READING_DATA_LENGTH;
  691.             else if (itemType == PORT_TYPE)
  692.                 partialState = READING_PORT;
  693.             else {
  694.                 if (enableLogging) NSLog(@"**bad type %u", itemType);
  695.                 [self invalidate];
  696.                 return;
  697.             }
  698.             break;
  699.         }
  700.  
  701.     case READING_DATA_LENGTH: {
  702.             if (![dataStream getBytes:&dataLength length:sizeof(dataLength)]) return;
  703.             dataLength = sanitize(dataLength);
  704.             if (dataLength > 64*1024*1024) {
  705.                 if (enableLogging) NSLog(@"**huge data item (%u bytes) error");
  706.                 [self invalidate];
  707.                 return;
  708.             }
  709.             dataItem = [[NSMutableData alloc] init];
  710.             partialState = READING_DATA;
  711.             break;
  712.         }
  713.  
  714.     case READING_DATA: {
  715.             unsigned int available = [dataStream length];
  716.             if (!available) return;
  717.             if (available > dataLength)
  718.                 available = dataLength;
  719.             [dataStream fillData:dataItem length:available];
  720.             dataLength -= available;
  721.             if (dataLength == 0) {
  722.                 [items addObject:dataItem];
  723.                 [dataItem release];
  724.         dataItem = nil;
  725.                 ++itemCounter;
  726.                 if (itemCounter == nItems)
  727.                     partialState = DISPATCHING;
  728.                 else
  729.                     partialState = READING_TYPE;
  730.             }
  731.             break;
  732.         }
  733.  
  734.     case READING_PORT: {
  735.             int stuff[2];
  736.             TCPPort *sp;
  737.  
  738.             if (![dataStream getBytes:stuff length:sizeof(stuff)]) return;
  739.             if (enableLogging) NSLog(@"got sanitized port %d addr %u", sanitize(stuff[0]), stuff[1]);
  740.             sp = [listener connectToNumber:sanitize(stuff[0]) address:stuff[1]];
  741.             if (!sp) {
  742.                 if (enableLogging) NSLog(@"*** didn't get port %d addr %u", sanitize(stuff[0]), stuff[1]);
  743.                 [self invalidate];
  744.                 return;
  745.             }
  746.             [items addObject:sp];
  747.             ++itemCounter;
  748.             if (itemCounter == nItems)
  749.                 partialState = DISPATCHING;
  750.             else
  751.                 partialState = READING_TYPE;
  752.             break;
  753.         }
  754.  
  755.     case DISPATCHING: {
  756.             NSPortCoder *pc = [NSPortCoder portCoderWithReceivePort:listener sendPort:self components:items];
  757.  
  758.         if (enableLogging) NSLog(@"dispatching %@!", pc);
  759.             [pc dispatch];
  760.             [items release];
  761.             items = nil;
  762.             partialState = READING_HEADER;
  763.             if (connection) {
  764.                 [connection release];
  765.                 connection = nil;
  766.             }
  767.             break;
  768.         }
  769.     
  770.     default:
  771.         NSLog(@"bad case %u", partialState);
  772.         return;
  773.     }
  774. }
  775.  
  776. - (void)acceptNotification:(NSNotification *)note {
  777.     NSFileHandle *socket = [[note userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
  778.     TCPPort *sp;
  779.  
  780.     if (!socket) {
  781.         if (enableLogging) NSLog(@"** no socket in notification info %@", [note userInfo]);
  782.         [self invalidate];
  783.         return;
  784.     }
  785.     else {
  786.         [readWriter acceptConnectionInBackgroundAndNotifyForModes:[modes allObjects]];
  787.     if (enableLogging) NSLog(@"got socket %d from background, accepting for modes %@", [socket fileDescriptor], modes);
  788.     }
  789.     sp = [self tcpPortWithAcceptedSocket:socket];
  790.     if (!sp) {
  791.         if (enableLogging) NSLog(@"didn't set up socket port");
  792.         return;
  793.     }
  794.     connection = [[NSConnection alloc] initWithReceivePort:self sendPort:sp];
  795. }
  796.  
  797. - (NSString *)description {
  798.     return [NSString stringWithFormat:@"<TCPPort %u, addr %u, reader %d, readWriter %d>", tcpPort, internetAddr, [reader fileDescriptor], [readWriter fileDescriptor]];
  799. }
  800. @end
  801.  
  802.  
  803.