jens@0: // jens@0: // TCPConnection.m jens@0: // MYNetwork jens@0: // jens@0: // Created by Jens Alfke on 5/18/08. jens@0: // Copyright 2008 Jens Alfke. All rights reserved. jens@0: // jens@0: jens@0: #import "TCP_Internal.h" jens@0: #import "IPAddress.h" jens@0: jens@1: #import "Logging.h" jens@1: #import "Test.h" jens@0: #import "ExceptionUtils.h" jens@0: jens@0: jens@0: NSString* const TCPErrorDomain = @"TCP"; jens@0: jens@0: jens@0: @interface TCPConnection () jens@0: @property TCPConnectionStatus status; jens@0: - (BOOL) _checkIfClosed; jens@0: - (void) _closed; jens@0: @end jens@0: jens@0: jens@0: @implementation TCPConnection jens@0: jens@0: jens@0: static NSMutableArray *sAllConnections; jens@0: jens@0: jens@0: - (Class) readerClass {return [TCPReader class];} jens@0: - (Class) writerClass {return [TCPWriter class];} jens@0: jens@0: jens@0: - (id) _initWithAddress: (IPAddress*)address jens@0: inputStream: (NSInputStream*)input jens@0: outputStream: (NSOutputStream*)output jens@0: { jens@0: self = [super init]; jens@0: if (self != nil) { jens@0: if( !address || !input || !output ) { jens@0: LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@", jens@0: self.class,address,input,output); jens@0: [self release]; jens@0: return nil; jens@0: } jens@0: _address = [address copy]; jens@0: _reader = [[[self readerClass] alloc] initWithConnection: self stream: input]; jens@0: _writer = [[[self writerClass] alloc] initWithConnection: self stream: output]; jens@0: LogTo(TCP,@"%@ initialized",self); jens@0: } jens@0: return self; jens@0: } jens@0: jens@0: jens@0: jens@0: - (id) initToAddress: (IPAddress*)address jens@0: localPort: (UInt16)localPort jens@0: { jens@0: NSInputStream *input = nil; jens@0: NSOutputStream *output = nil; jens@0: [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name] jens@0: port: address.port jens@0: inputStream: &input jens@0: outputStream: &output]; jens@0: return [self _initWithAddress: address inputStream: input outputStream: output]; jens@0: //FIX: Support localPort! jens@0: } jens@0: jens@0: - (id) initToAddress: (IPAddress*)address jens@0: { jens@0: return [self initToAddress: address localPort: 0]; jens@0: } jens@0: jens@0: jens@0: - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket jens@0: listener: (TCPListener*)listener jens@0: { jens@0: CFReadStreamRef readStream = NULL; jens@0: CFWriteStreamRef writeStream = NULL; jens@0: CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream); jens@0: self = [self _initWithAddress: [IPAddress addressOfSocket: socket] jens@0: inputStream: (NSInputStream*)readStream jens@0: outputStream: (NSOutputStream*)writeStream]; jens@0: if( self ) { jens@0: _isIncoming = YES; jens@0: _server = [listener retain]; jens@0: CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); jens@0: CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 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: [_reader release]; jens@0: [_writer release]; jens@0: [_address release]; jens@0: [super dealloc]; jens@0: } jens@0: jens@0: jens@0: - (NSString*) description jens@0: { jens@0: return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address); jens@0: } jens@0: jens@0: jens@0: @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate, jens@0: reader=_reader, writer=_writer, server=_server; jens@0: jens@0: jens@0: - (NSError*) error jens@0: { jens@0: return _error; jens@0: } jens@0: jens@0: jens@0: - (NSString*) actualSecurityLevel jens@0: { jens@0: return _reader.securityLevel; jens@0: jens@0: } jens@0: jens@0: - (NSArray*) peerSSLCerts jens@0: { jens@0: return _reader.peerSSLCerts ?: _writer.peerSSLCerts; jens@0: } jens@0: jens@0: jens@0: - (void) _setStreamProperty: (id)value forKey: (NSString*)key jens@0: { jens@0: [_reader setProperty: value forKey: (CFStringRef)key]; jens@0: [_writer setProperty: value forKey: (CFStringRef)key]; 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: if( _status<=kTCP_Closed && _reader ) { jens@0: _reader.SSLProperties = _sslProperties; jens@0: _writer.SSLProperties = _sslProperties; jens@0: [_reader open]; jens@0: [_writer open]; jens@0: if( ! [sAllConnections my_containsObjectIdenticalTo: self] ) jens@0: [sAllConnections addObject: self]; jens@0: self.status = kTCP_Opening; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (void) disconnect jens@0: { jens@0: if( _status > kTCP_Closed ) { jens@0: LogTo(TCP,@"%@ disconnecting",self); jens@0: [_writer disconnect]; jens@0: setObj(&_writer,nil); jens@0: [_reader disconnect]; jens@0: setObj(&_reader,nil); jens@0: self.status = kTCP_Disconnected; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (void) close jens@0: { jens@0: [self closeWithTimeout: 60.0]; jens@0: } jens@0: jens@0: - (void) closeWithTimeout: (NSTimeInterval)timeout jens@0: { jens@0: if( _status == kTCP_Opening ) { jens@0: LogTo(TCP,@"%@ canceling open",self); jens@0: [self _closed]; jens@0: } else if( _status == kTCP_Open ) { jens@0: LogTo(TCP,@"%@ closing",self); jens@0: self.status = kTCP_Closing; jens@0: [self retain]; jens@0: [_reader close]; jens@0: [_writer close]; jens@0: if( ! [self _checkIfClosed] ) { jens@0: if( timeout <= 0.0 ) jens@0: [self disconnect]; jens@0: else if( timeout != INFINITY ) jens@0: [self performSelector: @selector(_closeTimeoutExpired) jens@0: withObject: nil afterDelay: timeout]; jens@0: } jens@0: [self release]; jens@0: } jens@0: } jens@0: jens@0: - (void) _closeTimeoutExpired jens@0: { jens@0: if( _status==kTCP_Closing ) jens@0: [self disconnect]; jens@0: } jens@0: jens@0: jens@0: - (BOOL) _checkIfClosed jens@0: { jens@0: if( _status == kTCP_Closing && _writer==nil && _reader==nil ) { jens@0: [self _closed]; jens@0: return YES; jens@0: } else jens@0: return NO; jens@0: } jens@0: jens@0: jens@0: // called by my streams when they close (after my -close is called) jens@0: - (void) _closed jens@0: { jens@0: if( _status != kTCP_Closed && _status != kTCP_Disconnected ) { jens@0: LogTo(TCP,@"%@ is now closed",self); jens@0: self.status = (_status==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected); jens@0: [self tellDelegate: @selector(connectionDidClose:) withObject: nil]; jens@0: } jens@0: [NSObject cancelPreviousPerformRequestsWithTarget: self jens@0: selector: @selector(_closeTimeoutExpired) jens@0: object: nil]; jens@0: [sAllConnections removeObjectIdenticalTo: self]; jens@0: } jens@0: jens@0: jens@0: + (void) closeAllWithTimeout: (NSTimeInterval)timeout jens@0: { jens@0: NSArray *connections = [sAllConnections copy]; jens@0: for( TCPConnection *conn in connections ) jens@0: [conn closeWithTimeout: timeout]; jens@0: [connections release]; jens@0: } jens@0: jens@0: + (void) waitTillAllClosed jens@0: { jens@0: while( sAllConnections.count ) { jens@0: if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode jens@0: beforeDate: [NSDate distantFuture]] ) jens@0: break; jens@0: } jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark STREAM CALLBACKS: jens@0: jens@0: jens@0: - (void) _streamOpened: (TCPStream*)stream jens@0: { jens@0: if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) { jens@0: LogTo(TCP,@"%@ opened",self); jens@0: self.status = kTCP_Open; jens@0: [self tellDelegate: @selector(connectionDidOpen:) withObject: nil]; jens@0: } jens@0: } jens@0: jens@0: jens@0: - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream jens@0: { jens@0: BOOL allow = YES; jens@0: if( ! _checkedPeerCert ) { jens@0: @try{ jens@0: _checkedPeerCert = YES; jens@0: if( stream.securityLevel != nil ) { jens@0: NSArray *certs = stream.peerSSLCerts; jens@0: if( ! certs && ! _isIncoming ) jens@0: allow = NO; // Server MUST have a cert! jens@0: else { jens@0: SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL; jens@0: LogTo(TCP,@"%@: Peer cert = %@",self,cert); jens@0: if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] ) jens@0: allow = [_delegate connection: self authorizeSSLPeer: cert]; jens@0: } jens@0: } jens@0: }@catch( NSException *x ) { jens@0: MYReportException(x,@"TCPConnection _streamPeerCertAvailable"); jens@0: _checkedPeerCert = NO; jens@0: allow = NO; jens@0: } jens@0: if( ! allow ) jens@0: [self _stream: stream jens@0: gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain jens@0: code: errSSLClosedAbort jens@0: userInfo: nil]]; jens@0: } jens@0: return allow; jens@0: } jens@0: jens@0: jens@0: - (void) _stream: (TCPStream*)stream gotError: (NSError*)error jens@0: { jens@0: LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class); jens@0: Assert(error); jens@0: setObj(&_error,error); jens@0: [_reader disconnect]; jens@0: setObj(&_reader,nil); jens@0: [_writer disconnect]; jens@0: setObj(&_writer,nil); jens@0: [self _closed]; jens@0: } jens@0: jens@0: - (void) _streamGotEOF: (TCPStream*)stream jens@0: { jens@0: LogTo(TCP,@"%@ got EOF on %@",self,stream); jens@0: if( stream == _reader ) { jens@0: setObj(&_reader,nil); jens@0: // This is the expected way for he peer to initiate closing the connection. jens@0: if( _status==kTCP_Open ) { jens@0: [self closeWithTimeout: INFINITY]; jens@0: return; jens@0: } jens@0: } else if( stream == _writer ) { jens@0: setObj(&_writer,nil); jens@0: } jens@0: jens@0: if( _status == kTCP_Closing ) { jens@0: [self _checkIfClosed]; jens@0: } else { jens@0: [self _stream: stream jens@0: gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]]; jens@0: } jens@0: } jens@0: jens@0: jens@0: // Called after I called -close on a stream and it finished closing: jens@0: - (void) _streamClosed: (TCPStream*)stream jens@0: { jens@0: LogTo(TCP,@"%@ finished closing %@",self,stream); jens@0: if( stream == _reader ) jens@0: setObj(&_reader,nil); jens@0: else if( stream == _writer ) jens@0: setObj(&_writer,nil); jens@0: if( !_reader.isOpen && !_writer.isOpen ) jens@0: [self _closed]; jens@0: } jens@0: jens@0: jens@0: @end 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: */