Got it to build on iPhone. (Haven't tried running it yet.)
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"
18 // SecureTransport.h is missing on iPhone, with its SSL constants:
20 errSSLClosedAbort = -9806, /* connection closed via error */
26 NSString* const TCPErrorDomain = @"TCP";
29 @interface TCPConnection ()
30 @property TCPConnectionStatus status;
31 @property (retain) IPAddress *address;
32 - (BOOL) _checkIfClosed;
37 @implementation TCPConnection
40 static NSMutableArray *sAllConnections;
43 - (Class) readerClass {return [TCPReader class];}
44 - (Class) writerClass {return [TCPWriter class];}
47 - (id) _initWithAddress: (IPAddress*)address
48 inputStream: (NSInputStream*)input
49 outputStream: (NSOutputStream*)output
53 if( !input || !output ) {
54 LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
55 self.class,address,input,output);
59 _address = [address copy];
60 _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
61 _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
62 LogTo(TCP,@"%@ initialized, address=%@",self,address);
69 - (id) initToAddress: (IPAddress*)address
70 localPort: (UInt16)localPort
72 NSInputStream *input = nil;
73 NSOutputStream *output = nil;
75 // +getStreamsToHost: is missing for some stupid reason on iPhone. Grrrrrrrrrr.
76 CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)address.hostname, address.port,
77 (CFReadStreamRef*)&input, (CFWriteStreamRef*)&output);
79 [(id)CFMakeCollectable(input) autorelease];
81 [(id)CFMakeCollectable(output) autorelease];
83 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
86 outputStream: &output];
88 return [self _initWithAddress: address inputStream: input outputStream: output];
89 //FIX: Support localPort!
92 - (id) initToAddress: (IPAddress*)address
94 return [self initToAddress: address localPort: 0];
97 - (id) initToNetService: (NSNetService*)service
99 IPAddress *address = nil;
100 NSInputStream *input;
101 NSOutputStream *output;
102 if( [service getInputStream: &input outputStream: &output] ) {
103 NSArray *addresses = service.addresses;
104 if( addresses.count > 0 )
105 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
110 return [self _initWithAddress: address inputStream: input outputStream: output];
114 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
115 listener: (TCPListener*)listener
117 CFReadStreamRef readStream = NULL;
118 CFWriteStreamRef writeStream = NULL;
119 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
120 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
121 inputStream: (NSInputStream*)readStream
122 outputStream: (NSOutputStream*)writeStream];
125 _server = [listener retain];
126 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
127 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
135 LogTo(TCP,@"DEALLOC %@",self);
143 - (NSString*) description
145 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
149 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
150 reader=_reader, writer=_writer, server=_server;
159 - (NSString*) actualSecurityLevel
161 return _reader.securityLevel;
165 - (NSArray*) peerSSLCerts
167 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
171 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
173 [_reader setProperty: value forKey: (CFStringRef)key];
174 [_writer setProperty: value forKey: (CFStringRef)key];
179 #pragma mark OPENING / CLOSING:
184 if( _status<=kTCP_Closed && _reader ) {
185 _reader.SSLProperties = _sslProperties;
186 _writer.SSLProperties = _sslProperties;
189 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
190 [sAllConnections addObject: self];
191 self.status = kTCP_Opening;
198 if( _status > kTCP_Closed ) {
199 LogTo(TCP,@"%@ disconnecting",self);
200 [_writer disconnect];
201 setObj(&_writer,nil);
202 [_reader disconnect];
203 setObj(&_reader,nil);
204 self.status = kTCP_Disconnected;
211 [self closeWithTimeout: 60.0];
214 - (void) closeWithTimeout: (NSTimeInterval)timeout
216 if( _status == kTCP_Opening ) {
217 LogTo(TCP,@"%@ canceling open",self);
219 } else if( _status == kTCP_Open ) {
220 LogTo(TCP,@"%@ closing",self);
221 self.status = kTCP_Closing;
225 if( ! [self _checkIfClosed] ) {
228 else if( timeout != INFINITY )
229 [self performSelector: @selector(_closeTimeoutExpired)
230 withObject: nil afterDelay: timeout];
236 - (void) _closeTimeoutExpired
238 if( _status==kTCP_Closing )
243 - (BOOL) _checkIfClosed
245 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
253 // called by my streams when they close (after my -close is called)
256 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
257 LogTo(TCP,@"%@ is now closed",self);
258 self.status = (_status==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
259 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
261 [NSObject cancelPreviousPerformRequestsWithTarget: self
262 selector: @selector(_closeTimeoutExpired)
264 [sAllConnections removeObjectIdenticalTo: self];
268 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
270 NSArray *connections = [sAllConnections copy];
271 for( TCPConnection *conn in connections )
272 [conn closeWithTimeout: timeout];
273 [connections release];
276 + (void) waitTillAllClosed
278 while( sAllConnections.count ) {
279 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
280 beforeDate: [NSDate distantFuture]] )
287 #pragma mark STREAM CALLBACKS:
290 - (void) _streamOpened: (TCPStream*)stream
293 self.address = stream.peerAddress;
294 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
295 LogTo(TCP,@"%@ opened; address=%@",self,_address);
296 self.status = kTCP_Open;
297 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
302 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
305 if( ! _checkedPeerCert ) {
307 _checkedPeerCert = YES;
308 if( stream.securityLevel != nil ) {
309 NSArray *certs = stream.peerSSLCerts;
310 if( ! certs && ! _isIncoming )
311 allow = NO; // Server MUST have a cert!
313 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
314 LogTo(TCP,@"%@: Peer cert = %@",self,cert);
315 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
316 allow = [_delegate connection: self authorizeSSLPeer: cert];
319 }@catch( NSException *x ) {
320 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
321 _checkedPeerCert = NO;
325 [self _stream: stream
326 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
327 code: errSSLClosedAbort
334 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
336 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
338 setObj(&_error,error);
339 [_reader disconnect];
340 setObj(&_reader,nil);
341 [_writer disconnect];
342 setObj(&_writer,nil);
346 - (void) _streamGotEOF: (TCPStream*)stream
348 LogTo(TCP,@"%@ got EOF on %@",self,stream);
349 if( stream == _reader ) {
350 setObj(&_reader,nil);
351 // This is the expected way for he peer to initiate closing the connection.
352 if( _status==kTCP_Open ) {
353 [self closeWithTimeout: INFINITY];
356 } else if( stream == _writer ) {
357 setObj(&_writer,nil);
360 if( _status == kTCP_Closing ) {
361 [self _checkIfClosed];
363 [self _stream: stream
364 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
369 // Called after I called -close on a stream and it finished closing:
370 - (void) _streamClosed: (TCPStream*)stream
372 LogTo(TCP,@"%@ finished closing %@",self,stream);
373 if( stream == _reader )
374 setObj(&_reader,nil);
375 else if( stream == _writer )
376 setObj(&_writer,nil);
377 if( !_reader.isOpen && !_writer.isOpen )
386 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
388 Redistribution and use in source and binary forms, with or without modification, are permitted
389 provided that the following conditions are met:
391 * Redistributions of source code must retain the above copyright notice, this list of conditions
392 and the following disclaimer.
393 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
394 and the following disclaimer in the documentation and/or other materials provided with the
397 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
398 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
399 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
400 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
401 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
402 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
403 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
404 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.