* Added more documentation.
* Minor API changes.
5 // Created by Jens Alfke on 5/18/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "TCP_Internal.h"
14 #import "ExceptionUtils.h"
17 NSString* const TCPErrorDomain = @"TCP";
20 @interface TCPConnection ()
21 @property TCPConnectionStatus status;
22 - (BOOL) _checkIfClosed;
27 @implementation TCPConnection
30 static NSMutableArray *sAllConnections;
33 - (Class) readerClass {return [TCPReader class];}
34 - (Class) writerClass {return [TCPWriter class];}
37 - (id) _initWithAddress: (IPAddress*)address
38 inputStream: (NSInputStream*)input
39 outputStream: (NSOutputStream*)output
43 if( !address || !input || !output ) {
44 LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
45 self.class,address,input,output);
49 _address = [address copy];
50 _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
51 _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
52 LogTo(TCP,@"%@ initialized",self);
59 - (id) initToAddress: (IPAddress*)address
60 localPort: (UInt16)localPort
62 NSInputStream *input = nil;
63 NSOutputStream *output = nil;
64 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
67 outputStream: &output];
68 return [self _initWithAddress: address inputStream: input outputStream: output];
69 //FIX: Support localPort!
72 - (id) initToAddress: (IPAddress*)address
74 return [self initToAddress: address localPort: 0];
78 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
79 listener: (TCPListener*)listener
81 CFReadStreamRef readStream = NULL;
82 CFWriteStreamRef writeStream = NULL;
83 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
84 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
85 inputStream: (NSInputStream*)readStream
86 outputStream: (NSOutputStream*)writeStream];
89 _server = [listener retain];
90 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
91 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
99 LogTo(TCP,@"DEALLOC %@",self);
107 - (NSString*) description
109 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
113 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
114 reader=_reader, writer=_writer, server=_server;
123 - (NSString*) actualSecurityLevel
125 return _reader.securityLevel;
129 - (NSArray*) peerSSLCerts
131 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
135 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
137 [_reader setProperty: value forKey: (CFStringRef)key];
138 [_writer setProperty: value forKey: (CFStringRef)key];
143 #pragma mark OPENING / CLOSING:
148 if( _status<=kTCP_Closed && _reader ) {
149 _reader.SSLProperties = _sslProperties;
150 _writer.SSLProperties = _sslProperties;
153 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
154 [sAllConnections addObject: self];
155 self.status = kTCP_Opening;
162 if( _status > kTCP_Closed ) {
163 LogTo(TCP,@"%@ disconnecting",self);
164 [_writer disconnect];
165 setObj(&_writer,nil);
166 [_reader disconnect];
167 setObj(&_reader,nil);
168 self.status = kTCP_Disconnected;
175 [self closeWithTimeout: 60.0];
178 - (void) closeWithTimeout: (NSTimeInterval)timeout
180 if( _status == kTCP_Opening ) {
181 LogTo(TCP,@"%@ canceling open",self);
183 } else if( _status == kTCP_Open ) {
184 LogTo(TCP,@"%@ closing",self);
185 self.status = kTCP_Closing;
189 if( ! [self _checkIfClosed] ) {
192 else if( timeout != INFINITY )
193 [self performSelector: @selector(_closeTimeoutExpired)
194 withObject: nil afterDelay: timeout];
200 - (void) _closeTimeoutExpired
202 if( _status==kTCP_Closing )
207 - (BOOL) _checkIfClosed
209 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
217 // called by my streams when they close (after my -close is called)
220 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
221 LogTo(TCP,@"%@ is now closed",self);
222 self.status = (_status==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
223 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
225 [NSObject cancelPreviousPerformRequestsWithTarget: self
226 selector: @selector(_closeTimeoutExpired)
228 [sAllConnections removeObjectIdenticalTo: self];
232 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
234 NSArray *connections = [sAllConnections copy];
235 for( TCPConnection *conn in connections )
236 [conn closeWithTimeout: timeout];
237 [connections release];
240 + (void) waitTillAllClosed
242 while( sAllConnections.count ) {
243 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
244 beforeDate: [NSDate distantFuture]] )
251 #pragma mark STREAM CALLBACKS:
254 - (void) _streamOpened: (TCPStream*)stream
256 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
257 LogTo(TCP,@"%@ opened",self);
258 self.status = kTCP_Open;
259 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
264 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
267 if( ! _checkedPeerCert ) {
269 _checkedPeerCert = YES;
270 if( stream.securityLevel != nil ) {
271 NSArray *certs = stream.peerSSLCerts;
272 if( ! certs && ! _isIncoming )
273 allow = NO; // Server MUST have a cert!
275 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
276 LogTo(TCP,@"%@: Peer cert = %@",self,cert);
277 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
278 allow = [_delegate connection: self authorizeSSLPeer: cert];
281 }@catch( NSException *x ) {
282 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
283 _checkedPeerCert = NO;
287 [self _stream: stream
288 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
289 code: errSSLClosedAbort
296 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
298 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
300 setObj(&_error,error);
301 [_reader disconnect];
302 setObj(&_reader,nil);
303 [_writer disconnect];
304 setObj(&_writer,nil);
308 - (void) _streamGotEOF: (TCPStream*)stream
310 LogTo(TCP,@"%@ got EOF on %@",self,stream);
311 if( stream == _reader ) {
312 setObj(&_reader,nil);
313 // This is the expected way for he peer to initiate closing the connection.
314 if( _status==kTCP_Open ) {
315 [self closeWithTimeout: INFINITY];
318 } else if( stream == _writer ) {
319 setObj(&_writer,nil);
322 if( _status == kTCP_Closing ) {
323 [self _checkIfClosed];
325 [self _stream: stream
326 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
331 // Called after I called -close on a stream and it finished closing:
332 - (void) _streamClosed: (TCPStream*)stream
334 LogTo(TCP,@"%@ finished closing %@",self,stream);
335 if( stream == _reader )
336 setObj(&_reader,nil);
337 else if( stream == _writer )
338 setObj(&_writer,nil);
339 if( !_reader.isOpen && !_writer.isOpen )
348 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
350 Redistribution and use in source and binary forms, with or without modification, are permitted
351 provided that the following conditions are met:
353 * Redistributions of source code must retain the above copyright notice, this list of conditions
354 and the following disclaimer.
355 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
356 and the following disclaimer in the documentation and/or other materials provided with the
359 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
360 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
361 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
362 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
363 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
364 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
365 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
366 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.