TCP/TCPStream.m
author Jens Alfke <jens@mooseyard.com>
Sun May 25 13:43:03 2008 -0700 (2008-05-25)
changeset 7 5936db2c1987
parent 2 9fdd8dba529c
child 18 3be241de1630
permissions -rw-r--r--
Added -[TCPConnection initToNetService:] to make it easier to use with Bonjour. This allowed me to simplify BLIPEchoClient quite a lot.
     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     setObj(&_conn,nil);
   114 }
   115 
   116 
   117 - (BOOL) close
   118 {
   119     if( self.isBusy ) {
   120         _shouldClose = YES;
   121         return NO;
   122     } else {
   123         LogTo(TCP,@"Closing %@",self);
   124         [[self retain] autorelease];    // don't let myself be dealloced in the midst of this
   125         [_conn _streamClosed: self];    // have to do this before disconnect
   126         [self disconnect];
   127         return YES;
   128     }
   129 }
   130 
   131 
   132 - (BOOL) isOpen
   133 {
   134     NSStreamStatus status = _stream.streamStatus;
   135     return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
   136 }
   137 
   138 - (BOOL) isBusy
   139 {
   140     return NO;  // abstract
   141 }
   142 
   143 
   144 - (void) _opened
   145 {
   146     [_conn _streamOpened: self];
   147 }
   148 
   149 - (void) _canRead
   150 {
   151     // abstract
   152 }
   153 
   154 - (void) _canWrite
   155 {
   156     // abstract
   157 }
   158 
   159 - (void) _gotEOF
   160 {
   161     if( self.isBusy )
   162         [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
   163     else {
   164         [self retain];
   165         [_conn _streamGotEOF: self];
   166         [self disconnect];
   167         [self release];
   168     }
   169 }
   170 
   171 - (BOOL) _gotError: (NSError*)error
   172 {
   173     [_conn _stream: self gotError: fixStreamError(error)];
   174     return NO;
   175 }
   176 
   177 - (BOOL) _gotError
   178 {
   179     NSError *error = _stream.streamError;
   180     if( ! error )
   181         error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
   182     return [self _gotError: error];
   183 }
   184 
   185 
   186 - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
   187 {
   188     [[self retain] autorelease];
   189     switch(streamEvent) {
   190         case NSStreamEventOpenCompleted:
   191             LogTo(TCPVerbose,@"%@ opened",self);
   192             [self _opened];
   193             break;
   194         case NSStreamEventHasBytesAvailable:
   195             if( ! [_conn _streamPeerCertAvailable: self] )
   196                 return;
   197             LogTo(TCPVerbose,@"%@ can read",self);
   198             [self _canRead];
   199             break;
   200         case NSStreamEventHasSpaceAvailable:
   201             if( ! [_conn _streamPeerCertAvailable: self] )
   202                 return;
   203             LogTo(TCPVerbose,@"%@ can write",self);
   204             [self _canWrite];
   205             break;
   206         case NSStreamEventErrorOccurred:
   207             LogTo(TCPVerbose,@"%@ got error",self);
   208             [self _gotError];
   209             break;
   210         case NSStreamEventEndEncountered:
   211             LogTo(TCPVerbose,@"%@ got EOF",self);
   212             [self _gotEOF];
   213             break;
   214         default:
   215             Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
   216             break;
   217     }
   218     
   219     // If I was previously asked to close, try again in case I'm no longer busy
   220     if( _shouldClose )
   221         [self close];
   222 }
   223 
   224 
   225 @end
   226 
   227 
   228 
   229 
   230 @implementation TCPReader
   231 
   232 
   233 - (TCPWriter*) writer
   234 {
   235     return _conn.writer;
   236 }
   237 
   238 - (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength
   239 {
   240     NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength];
   241     if( bytesRead < 0 )
   242         [self _gotError];
   243     return bytesRead;
   244 }
   245 
   246 
   247 @end
   248 
   249 
   250 
   251 
   252 static NSError* fixStreamError( NSError *error )
   253 {
   254     // NSStream incorrectly returns SSL errors without the correct error domain:
   255     if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
   256         int code = error.code;
   257         if( -9899 <= code && code <= -9800 ) {
   258             NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
   259             if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
   260                 // look up error message:
   261                 NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
   262                 NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
   263                                                                value: nil
   264                                                                table: @"SecErrorMessages"];
   265                 if( message ) {
   266                     if( ! userInfo ) userInfo = $mdict();
   267                     [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
   268                 }
   269             }
   270             error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
   271                                         code: code userInfo: userInfo];
   272         } else
   273             Warn(@"NSStream returned error with unknown domain: %@",error);
   274     }
   275     return error;
   276 }
   277 
   278 /*
   279  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   280  
   281  Redistribution and use in source and binary forms, with or without modification, are permitted
   282  provided that the following conditions are met:
   283  
   284  * Redistributions of source code must retain the above copyright notice, this list of conditions
   285  and the following disclaimer.
   286  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   287  and the following disclaimer in the documentation and/or other materials provided with the
   288  distribution.
   289  
   290  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   291  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   292  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   293  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   294  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   295   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   296  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   297  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   298  */