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