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