TCP/TCPStream.m
author Jens Alfke <jens@mooseyard.com>
Thu Jun 19 16:22:05 2008 -0700 (2008-06-19)
changeset 18 3be241de1630
parent 7 5936db2c1987
child 19 16454d63d4c2
permissions -rw-r--r--
Implemented new close protocol with 'bye' meta-message.
     1 //
     2 //  TCPStream.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 5/10/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "TCPStream.h"
    10 #import "TCP_Internal.h"
    11 #import "IPAddress.h"
    12 
    13 #import "Logging.h"
    14 #import "Test.h"
    15 
    16 
    17 extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork
    18 
    19 static NSError* fixStreamError( NSError *error );
    20 
    21 
    22 @implementation TCPStream
    23 
    24 
    25 - (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream
    26 {
    27     self = [super init];
    28     if (self != nil) {
    29         _conn = [conn retain];
    30         _stream = [stream retain];
    31         _stream.delegate = self;
    32         [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
    33         LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus);
    34     }
    35     return self;
    36 }
    37 
    38 
    39 - (void) dealloc
    40 {
    41     LogTo(TCP,@"DEALLOC %@",self);
    42     if( _stream )
    43         [self disconnect];
    44     [super dealloc];
    45 }
    46 
    47 
    48 - (id) propertyForKey: (CFStringRef)cfStreamProperty
    49 {
    50     return [_stream propertyForKey: (NSString*)cfStreamProperty];
    51 }
    52 
    53 - (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
    54 {
    55     if( ! [_stream setProperty: value forKey: (NSString*)cfStreamProperty] )
    56         Warn(@"Failed to set property %@ on %@",cfStreamProperty,self);
    57 }
    58 
    59 
    60 - (IPAddress*) peerAddress
    61 {
    62     const CFSocketNativeHandle *socketPtr = [[self propertyForKey: kCFStreamPropertySocketNativeHandle] bytes];
    63     return socketPtr ?[IPAddress addressOfSocket: *socketPtr] :nil;
    64 }
    65 
    66 
    67 #pragma mark -
    68 #pragma mark SSL:
    69 
    70 
    71 - (NSString*) securityLevel                 {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];}
    72 
    73 - (NSDictionary*) SSLProperties             {return [self propertyForKey: kCFStreamPropertySSLSettings];}
    74 
    75 - (void) setSSLProperties: (NSDictionary*)p
    76 {
    77     LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p);
    78     [self setProperty: p forKey: kCFStreamPropertySSLSettings];
    79     
    80     id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication];
    81     if( clientAuth )
    82         [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication];
    83 }
    84 
    85 - (NSArray*) peerSSLCerts
    86 {
    87     Assert(self.isOpen);
    88     return [self propertyForKey: kCFStreamPropertySSLPeerCertificates];
    89 }
    90 
    91 
    92 #pragma mark -
    93 #pragma mark OPENING/CLOSING:
    94 
    95 
    96 - (void) open
    97 {
    98     Assert(_stream);
    99     AssertEq(_stream.streamStatus,NSStreamStatusNotOpen);
   100     LogTo(TCP,@"Opening %@",self);
   101     [_stream open];
   102 }
   103 
   104 
   105 - (void) disconnect
   106 {
   107     if( _stream ) {
   108         LogTo(TCP,@"Disconnect %@",self);
   109         _stream.delegate = nil;
   110         [_stream close];
   111         setObj(&_stream,nil);
   112     }
   113     if( _conn ) {
   114         [self retain];
   115         [_conn _streamDisconnected: self];
   116         setObj(&_conn,nil);
   117         [self release];
   118     }
   119 }
   120 
   121 
   122 - (BOOL) close
   123 {
   124     _shouldClose = YES;
   125     if( self.isBusy ) {
   126         return NO;
   127     } else {
   128         LogTo(TCP,@"Request to close %@",self);
   129         [[self retain] autorelease];        // don't let myself be dealloced in the midst of this
   130         [_conn _streamCanClose: self];
   131         return YES;
   132     }
   133 }
   134 
   135 
   136 - (BOOL) isOpen
   137 {
   138     NSStreamStatus status = _stream.streamStatus;
   139     return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
   140 }
   141 
   142 - (BOOL) isBusy
   143 {
   144     return NO;  // abstract
   145 }
   146 
   147 - (BOOL) isActive
   148 {
   149     return !_shouldClose || self.isBusy;
   150 }
   151 
   152 
   153 - (void) _opened
   154 {
   155     [_conn _streamOpened: self];
   156 }
   157 
   158 - (void) _canRead
   159 {
   160     // abstract
   161 }
   162 
   163 - (void) _canWrite
   164 {
   165     // abstract
   166 }
   167 
   168 - (void) _gotEOF
   169 {
   170     [_conn _streamGotEOF: self];
   171 }
   172 
   173 - (BOOL) _gotError: (NSError*)error
   174 {
   175     [_conn _stream: self gotError: fixStreamError(error)];
   176     return NO;
   177 }
   178 
   179 - (BOOL) _gotError
   180 {
   181     NSError *error = _stream.streamError;
   182     if( ! error )
   183         error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
   184     return [self _gotError: error];
   185 }
   186 
   187 
   188 - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
   189 {
   190     [[self retain] autorelease];
   191     switch(streamEvent) {
   192         case NSStreamEventOpenCompleted:
   193             LogTo(TCPVerbose,@"%@ opened",self);
   194             [self _opened];
   195             break;
   196         case NSStreamEventHasBytesAvailable:
   197             if( ! [_conn _streamPeerCertAvailable: self] )
   198                 return;
   199             LogTo(TCPVerbose,@"%@ can read",self);
   200             [self _canRead];
   201             break;
   202         case NSStreamEventHasSpaceAvailable:
   203             if( ! [_conn _streamPeerCertAvailable: self] )
   204                 return;
   205             LogTo(TCPVerbose,@"%@ can write",self);
   206             [self _canWrite];
   207             break;
   208         case NSStreamEventErrorOccurred:
   209             LogTo(TCPVerbose,@"%@ got error",self);
   210             [self _gotError];
   211             break;
   212         case NSStreamEventEndEncountered:
   213             LogTo(TCPVerbose,@"%@ got EOF",self);
   214             [self _gotEOF];
   215             break;
   216         default:
   217             Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
   218             break;
   219     }
   220     
   221     // If I was previously asked to close, try again in case I'm no longer busy
   222     if( _shouldClose )
   223         [self close];
   224 }
   225 
   226 
   227 @end
   228 
   229 
   230 
   231 
   232 @implementation TCPReader
   233 
   234 
   235 - (TCPWriter*) writer
   236 {
   237     return _conn.writer;
   238 }
   239 
   240 - (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength
   241 {
   242     NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength];
   243     if( bytesRead < 0 )
   244         [self _gotError];
   245     return bytesRead;
   246 }
   247 
   248 
   249 @end
   250 
   251 
   252 
   253 
   254 static NSError* fixStreamError( NSError *error )
   255 {
   256     // NSStream incorrectly returns SSL errors without the correct error domain:
   257     if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
   258         int code = error.code;
   259         if( -9899 <= code && code <= -9800 ) {
   260             NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
   261             if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
   262                 // look up error message:
   263                 NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
   264                 NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
   265                                                                value: nil
   266                                                                table: @"SecErrorMessages"];
   267                 if( message ) {
   268                     if( ! userInfo ) userInfo = $mdict();
   269                     [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
   270                 }
   271             }
   272             error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
   273                                         code: code userInfo: userInfo];
   274         } else
   275             Warn(@"NSStream returned error with unknown domain: %@",error);
   276     }
   277     return error;
   278 }
   279 
   280 /*
   281  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   282  
   283  Redistribution and use in source and binary forms, with or without modification, are permitted
   284  provided that the following conditions are met:
   285  
   286  * Redistributions of source code must retain the above copyright notice, this list of conditions
   287  and the following disclaimer.
   288  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   289  and the following disclaimer in the documentation and/or other materials provided with the
   290  distribution.
   291  
   292  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   293  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   294  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   295  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   296  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   297   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   298  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   299  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   300  */