jens@0: // jens@0: // TCPStream.m jens@0: // MYNetwork jens@0: // jens@0: // Created by Jens Alfke on 5/10/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // jens@0: jens@0: #import "TCPStream.h" jens@0: #import "TCP_Internal.h" jens@0: jens@1: #import "Logging.h" jens@1: #import "Test.h" jens@1: jens@0: jens@0: extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork jens@0: jens@0: static NSError* fixStreamError( NSError *error ); jens@0: jens@0: jens@0: @implementation TCPStream jens@0: jens@0: jens@0: - (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: _conn = [conn retain]; jens@0: _stream = [stream retain]; jens@0: _stream.delegate = self; jens@0: [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes]; jens@0: LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus); jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: - (void) dealloc jens@0: { jens@0: LogTo(TCP,@"DEALLOC %@",self); jens@0: if( _stream ) jens@0: [self disconnect]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: jens@0: - (id) propertyForKey: (CFStringRef)cfStreamProperty jens@0: { jens@0: return nil; // abstract -- overridden by TCPReader and TCPWriter jens@0: } jens@0: jens@0: - (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty jens@0: { // abstract -- overridden by TCPReader and TCPWriter jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark SSL: jens@0: jens@0: jens@0: - (NSString*) securityLevel {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];} jens@0: jens@0: - (NSDictionary*) SSLProperties {return [self propertyForKey: kCFStreamPropertySSLSettings];} jens@0: jens@0: - (void) setSSLProperties: (NSDictionary*)p jens@0: { jens@0: LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p); jens@0: [self setProperty: p forKey: kCFStreamPropertySSLSettings]; jens@0: jens@0: id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication]; jens@0: if( clientAuth ) jens@0: [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication]; jens@0: } jens@0: jens@0: - (NSArray*) peerSSLCerts jens@0: { jens@0: Assert(self.isOpen); jens@0: return [self propertyForKey: kCFStreamPropertySSLPeerCertificates]; jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark OPENING/CLOSING: jens@0: jens@0: jens@0: - (void) open jens@0: { jens@0: Assert(_stream); jens@0: AssertEq(_stream.streamStatus,NSStreamStatusNotOpen); jens@0: LogTo(TCP,@"Opening %@",self); jens@0: [_stream open]; jens@0: } jens@0: jens@0: jens@0: - (void) disconnect jens@0: { jens@0: if( _stream ) { jens@0: LogTo(TCP,@"Disconnect %@",self); jens@0: _stream.delegate = nil; jens@0: [_stream close]; jens@0: setObj(&_stream,nil); jens@0: } jens@0: setObj(&_conn,nil); jens@0: } jens@0: jens@0: jens@0: - (BOOL) close jens@0: { jens@0: if( self.isBusy ) { jens@0: _shouldClose = YES; jens@0: return NO; jens@0: } else { jens@0: LogTo(TCP,@"Closing %@",self); jens@0: [[self retain] autorelease]; // don't let myself be dealloced in the midst of this jens@0: [_conn _streamClosed: self]; // have to do this before disconnect jens@0: [self disconnect]; jens@0: return YES; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (BOOL) isOpen jens@0: { jens@0: NSStreamStatus status = _stream.streamStatus; jens@0: return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd; jens@0: } jens@0: jens@0: - (BOOL) isBusy jens@0: { jens@0: return NO; // abstract jens@0: } jens@0: jens@0: jens@0: - (void) _opened jens@0: { jens@0: [_conn _streamOpened: self]; jens@0: } jens@0: jens@0: - (void) _canRead jens@0: { jens@0: // abstract jens@0: } jens@0: jens@0: - (void) _canWrite jens@0: { jens@0: // abstract jens@0: } jens@0: jens@0: - (void) _gotEOF jens@0: { jens@0: if( self.isBusy ) jens@0: [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]]; jens@0: else { jens@0: [self retain]; jens@0: [_conn _streamGotEOF: self]; jens@0: [self disconnect]; jens@0: [self release]; jens@0: } jens@0: } jens@0: jens@0: - (BOOL) _gotError: (NSError*)error jens@0: { jens@0: [_conn _stream: self gotError: fixStreamError(error)]; jens@0: return NO; jens@0: } jens@0: jens@0: - (BOOL) _gotError jens@0: { jens@0: NSError *error = _stream.streamError; jens@0: if( ! error ) jens@0: error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback jens@0: return [self _gotError: error]; jens@0: } jens@0: jens@0: jens@0: - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent jens@0: { jens@0: [[self retain] autorelease]; jens@0: switch(streamEvent) { jens@0: case NSStreamEventOpenCompleted: jens@0: LogTo(TCPVerbose,@"%@ opened",self); jens@0: [self _opened]; jens@0: break; jens@0: case NSStreamEventHasBytesAvailable: jens@0: if( ! [_conn _streamPeerCertAvailable: self] ) jens@0: return; jens@0: LogTo(TCPVerbose,@"%@ can read",self); jens@0: [self _canRead]; jens@0: break; jens@0: case NSStreamEventHasSpaceAvailable: jens@0: if( ! [_conn _streamPeerCertAvailable: self] ) jens@0: return; jens@0: LogTo(TCPVerbose,@"%@ can write",self); jens@0: [self _canWrite]; jens@0: break; jens@0: case NSStreamEventErrorOccurred: jens@0: LogTo(TCPVerbose,@"%@ got error",self); jens@0: [self _gotError]; jens@0: break; jens@0: case NSStreamEventEndEncountered: jens@0: LogTo(TCPVerbose,@"%@ got EOF",self); jens@0: [self _gotEOF]; jens@0: break; jens@0: default: jens@0: Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent); jens@0: break; jens@0: } jens@0: jens@0: // If I was previously asked to close, try again in case I'm no longer busy jens@0: if( _shouldClose ) jens@0: [self close]; jens@0: } jens@0: jens@0: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: @implementation TCPReader jens@0: jens@0: jens@0: - (TCPWriter*) writer jens@0: { jens@0: return _conn.writer; jens@0: } jens@0: jens@0: jens@0: - (id) propertyForKey: (CFStringRef)cfStreamProperty jens@0: { jens@0: CFTypeRef value = CFReadStreamCopyProperty((CFReadStreamRef)_stream,cfStreamProperty); jens@0: return [(id)CFMakeCollectable(value) autorelease]; jens@0: } jens@0: jens@0: - (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty jens@0: { jens@0: if( ! CFReadStreamSetProperty((CFReadStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) ) jens@0: Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty); jens@0: } jens@0: jens@0: jens@2: - (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength jens@2: { jens@2: NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength]; jens@2: if( bytesRead < 0 ) jens@2: [self _gotError]; jens@2: return bytesRead; jens@2: } jens@2: jens@2: jens@0: @end jens@0: jens@0: jens@0: jens@0: jens@0: static NSError* fixStreamError( NSError *error ) jens@0: { jens@0: // NSStream incorrectly returns SSL errors without the correct error domain: jens@0: if( $equal(error.domain,@"NSUnknownErrorDomain") ) { jens@0: int code = error.code; jens@0: if( -9899 <= code && code <= -9800 ) { jens@0: NSMutableDictionary *userInfo = error.userInfo.mutableCopy; jens@0: if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) { jens@0: // look up error message: jens@0: NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"]; jens@0: NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code) jens@0: value: nil jens@0: table: @"SecErrorMessages"]; jens@0: if( message ) { jens@0: if( ! userInfo ) userInfo = $mdict(); jens@0: [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey]; jens@0: } jens@0: } jens@0: error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain jens@0: code: code userInfo: userInfo]; jens@0: } else jens@0: Warn(@"NSStream returned error with unknown domain: %@",error); jens@0: } jens@0: return error; jens@0: } jens@0: jens@0: /* jens@0: Copyright (c) 2008, Jens Alfke . All rights reserved. jens@0: jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted jens@0: provided that the following conditions are met: jens@0: jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@0: and the following disclaimer. jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@0: and the following disclaimer in the documentation and/or other materials provided with the jens@0: distribution. jens@0: jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@0: */