TCP/TCPConnection.m
author Jens Alfke <jens@mooseyard.com>
Sat May 24 17:25:06 2008 -0700 (2008-05-24)
changeset 2 9fdd8dba529c
parent 0 9d67172bb323
child 7 5936db2c1987
permissions -rw-r--r--
* Added more documentation.
* Minor API changes.
jens@0
     1
//
jens@0
     2
//  TCPConnection.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/18/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#import "TCP_Internal.h"
jens@0
    10
#import "IPAddress.h"
jens@0
    11
jens@1
    12
#import "Logging.h"
jens@1
    13
#import "Test.h"
jens@0
    14
#import "ExceptionUtils.h"
jens@0
    15
jens@0
    16
jens@0
    17
NSString* const TCPErrorDomain = @"TCP";
jens@0
    18
jens@0
    19
jens@0
    20
@interface TCPConnection ()
jens@0
    21
@property TCPConnectionStatus status;
jens@0
    22
- (BOOL) _checkIfClosed;
jens@0
    23
- (void) _closed;
jens@0
    24
@end
jens@0
    25
jens@0
    26
jens@0
    27
@implementation TCPConnection
jens@0
    28
jens@0
    29
jens@0
    30
static NSMutableArray *sAllConnections;
jens@0
    31
jens@0
    32
jens@0
    33
- (Class) readerClass   {return [TCPReader class];}
jens@0
    34
- (Class) writerClass   {return [TCPWriter class];}
jens@0
    35
jens@0
    36
jens@0
    37
- (id) _initWithAddress: (IPAddress*)address
jens@0
    38
            inputStream: (NSInputStream*)input
jens@0
    39
           outputStream: (NSOutputStream*)output
jens@0
    40
{
jens@0
    41
    self = [super init];
jens@0
    42
    if (self != nil) {
jens@0
    43
        if( !address || !input || !output ) {
jens@0
    44
            LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
jens@0
    45
                  self.class,address,input,output);
jens@0
    46
            [self release];
jens@0
    47
            return nil;
jens@0
    48
        }
jens@0
    49
        _address = [address copy];
jens@0
    50
        _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
jens@0
    51
        _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
jens@0
    52
        LogTo(TCP,@"%@ initialized",self);
jens@0
    53
    }
jens@0
    54
    return self;
jens@0
    55
}
jens@0
    56
jens@0
    57
jens@0
    58
jens@0
    59
- (id) initToAddress: (IPAddress*)address
jens@0
    60
           localPort: (UInt16)localPort
jens@0
    61
{
jens@0
    62
    NSInputStream *input = nil;
jens@0
    63
    NSOutputStream *output = nil;
jens@0
    64
    [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
jens@0
    65
                          port: address.port 
jens@0
    66
                   inputStream: &input 
jens@0
    67
                  outputStream: &output];
jens@0
    68
    return [self _initWithAddress: address inputStream: input outputStream: output];
jens@0
    69
    //FIX: Support localPort!
jens@0
    70
}
jens@0
    71
jens@0
    72
- (id) initToAddress: (IPAddress*)address
jens@0
    73
{
jens@0
    74
    return [self initToAddress: address localPort: 0];
jens@0
    75
}
jens@0
    76
jens@0
    77
jens@0
    78
- (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
jens@0
    79
                     listener: (TCPListener*)listener
jens@0
    80
{
jens@0
    81
    CFReadStreamRef readStream = NULL;
jens@0
    82
    CFWriteStreamRef writeStream = NULL;
jens@0
    83
    CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
jens@0
    84
    self = [self _initWithAddress: [IPAddress addressOfSocket: socket] 
jens@0
    85
                      inputStream: (NSInputStream*)readStream
jens@0
    86
                     outputStream: (NSOutputStream*)writeStream];
jens@0
    87
    if( self ) {
jens@0
    88
        _isIncoming = YES;
jens@0
    89
        _server = [listener retain];
jens@0
    90
        CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
jens@0
    91
        CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
jens@0
    92
    }
jens@0
    93
    return self;
jens@0
    94
}    
jens@0
    95
jens@0
    96
jens@0
    97
- (void) dealloc
jens@0
    98
{
jens@0
    99
    LogTo(TCP,@"DEALLOC %@",self);
jens@0
   100
    [_reader release];
jens@0
   101
    [_writer release];
jens@0
   102
    [_address release];
jens@0
   103
    [super dealloc];
jens@0
   104
}
jens@0
   105
jens@0
   106
jens@0
   107
- (NSString*) description
jens@0
   108
{
jens@0
   109
    return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
jens@0
   110
}
jens@0
   111
jens@0
   112
jens@0
   113
@synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
jens@0
   114
            reader=_reader, writer=_writer, server=_server;
jens@0
   115
jens@0
   116
jens@0
   117
- (NSError*) error
jens@0
   118
{
jens@0
   119
    return _error;
jens@0
   120
}
jens@0
   121
jens@0
   122
jens@0
   123
- (NSString*) actualSecurityLevel
jens@0
   124
{
jens@0
   125
    return _reader.securityLevel;
jens@0
   126
jens@0
   127
}
jens@0
   128
jens@0
   129
- (NSArray*) peerSSLCerts
jens@0
   130
{
jens@0
   131
    return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
jens@0
   132
}
jens@0
   133
jens@0
   134
jens@0
   135
- (void) _setStreamProperty: (id)value forKey: (NSString*)key
jens@0
   136
{
jens@0
   137
    [_reader setProperty: value forKey: (CFStringRef)key];
jens@0
   138
    [_writer setProperty: value forKey: (CFStringRef)key];
jens@0
   139
}
jens@0
   140
jens@0
   141
jens@0
   142
#pragma mark -
jens@0
   143
#pragma mark OPENING / CLOSING:
jens@0
   144
jens@0
   145
jens@0
   146
- (void) open
jens@0
   147
{
jens@0
   148
    if( _status<=kTCP_Closed && _reader ) {
jens@0
   149
        _reader.SSLProperties = _sslProperties;
jens@0
   150
        _writer.SSLProperties = _sslProperties;
jens@0
   151
        [_reader open];
jens@0
   152
        [_writer open];
jens@0
   153
        if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
jens@0
   154
            [sAllConnections addObject: self];
jens@0
   155
        self.status = kTCP_Opening;
jens@0
   156
    }
jens@0
   157
}
jens@0
   158
jens@0
   159
jens@0
   160
- (void) disconnect
jens@0
   161
{
jens@0
   162
    if( _status > kTCP_Closed ) {
jens@0
   163
        LogTo(TCP,@"%@ disconnecting",self);
jens@0
   164
        [_writer disconnect];
jens@0
   165
        setObj(&_writer,nil);
jens@0
   166
        [_reader disconnect];
jens@0
   167
        setObj(&_reader,nil);
jens@0
   168
        self.status = kTCP_Disconnected;
jens@0
   169
    }
jens@0
   170
}
jens@0
   171
jens@0
   172
jens@0
   173
- (void) close
jens@0
   174
{
jens@0
   175
    [self closeWithTimeout: 60.0];
jens@0
   176
}
jens@0
   177
jens@0
   178
- (void) closeWithTimeout: (NSTimeInterval)timeout
jens@0
   179
{
jens@0
   180
    if( _status == kTCP_Opening ) {
jens@0
   181
        LogTo(TCP,@"%@ canceling open",self);
jens@0
   182
        [self _closed];
jens@0
   183
    } else if( _status == kTCP_Open ) {
jens@0
   184
        LogTo(TCP,@"%@ closing",self);
jens@0
   185
        self.status = kTCP_Closing;
jens@0
   186
        [self retain];
jens@0
   187
        [_reader close];
jens@0
   188
        [_writer close];
jens@0
   189
        if( ! [self _checkIfClosed] ) {
jens@0
   190
            if( timeout <= 0.0 )
jens@0
   191
                [self disconnect];
jens@0
   192
            else if( timeout != INFINITY )
jens@0
   193
                [self performSelector: @selector(_closeTimeoutExpired)
jens@0
   194
                           withObject: nil afterDelay: timeout];
jens@0
   195
        }
jens@0
   196
        [self release];
jens@0
   197
    }
jens@0
   198
}
jens@0
   199
jens@0
   200
- (void) _closeTimeoutExpired
jens@0
   201
{
jens@0
   202
    if( _status==kTCP_Closing )
jens@0
   203
        [self disconnect];
jens@0
   204
}
jens@0
   205
jens@0
   206
jens@0
   207
- (BOOL) _checkIfClosed
jens@0
   208
{
jens@0
   209
    if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
jens@0
   210
        [self _closed];
jens@0
   211
        return YES;
jens@0
   212
    } else
jens@0
   213
        return NO;
jens@0
   214
}
jens@0
   215
jens@0
   216
jens@0
   217
// called by my streams when they close (after my -close is called)
jens@0
   218
- (void) _closed
jens@0
   219
{
jens@0
   220
    if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
jens@0
   221
        LogTo(TCP,@"%@ is now closed",self);
jens@0
   222
        self.status = (_status==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
jens@0
   223
        [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
jens@0
   224
    }
jens@0
   225
    [NSObject cancelPreviousPerformRequestsWithTarget: self
jens@0
   226
                                             selector: @selector(_closeTimeoutExpired)
jens@0
   227
                                               object: nil];
jens@0
   228
    [sAllConnections removeObjectIdenticalTo: self];
jens@0
   229
}
jens@0
   230
jens@0
   231
jens@0
   232
+ (void) closeAllWithTimeout: (NSTimeInterval)timeout
jens@0
   233
{
jens@0
   234
    NSArray *connections = [sAllConnections copy];
jens@0
   235
    for( TCPConnection *conn in connections )
jens@0
   236
        [conn closeWithTimeout: timeout];
jens@0
   237
    [connections release];
jens@0
   238
}
jens@0
   239
jens@0
   240
+ (void) waitTillAllClosed
jens@0
   241
{
jens@0
   242
    while( sAllConnections.count ) {
jens@0
   243
        if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
jens@0
   244
                                       beforeDate: [NSDate distantFuture]] )
jens@0
   245
            break;
jens@0
   246
    }
jens@0
   247
}
jens@0
   248
jens@0
   249
jens@0
   250
#pragma mark -
jens@0
   251
#pragma mark STREAM CALLBACKS:
jens@0
   252
jens@0
   253
jens@0
   254
- (void) _streamOpened: (TCPStream*)stream
jens@0
   255
{
jens@0
   256
    if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
jens@0
   257
        LogTo(TCP,@"%@ opened",self);
jens@0
   258
        self.status = kTCP_Open;
jens@0
   259
        [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
jens@0
   260
    }
jens@0
   261
}
jens@0
   262
jens@0
   263
jens@0
   264
- (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
jens@0
   265
{
jens@0
   266
    BOOL allow = YES;
jens@0
   267
    if( ! _checkedPeerCert ) {
jens@0
   268
        @try{
jens@0
   269
            _checkedPeerCert = YES;
jens@0
   270
            if( stream.securityLevel != nil ) {
jens@0
   271
                NSArray *certs = stream.peerSSLCerts;
jens@0
   272
                if( ! certs && ! _isIncoming )
jens@0
   273
                    allow = NO; // Server MUST have a cert!
jens@0
   274
                else {
jens@0
   275
                    SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
jens@0
   276
                    LogTo(TCP,@"%@: Peer cert = %@",self,cert);
jens@0
   277
                    if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
jens@0
   278
                        allow = [_delegate connection: self authorizeSSLPeer: cert];
jens@0
   279
                }
jens@0
   280
            }
jens@0
   281
        }@catch( NSException *x ) {
jens@0
   282
            MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
jens@0
   283
            _checkedPeerCert = NO;
jens@0
   284
            allow = NO;
jens@0
   285
        }
jens@0
   286
        if( ! allow )
jens@0
   287
            [self _stream: stream 
jens@0
   288
                 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
jens@0
   289
                                               code: errSSLClosedAbort
jens@0
   290
                                           userInfo: nil]];
jens@0
   291
    }
jens@0
   292
    return allow;
jens@0
   293
}
jens@0
   294
jens@0
   295
jens@0
   296
- (void) _stream: (TCPStream*)stream gotError: (NSError*)error
jens@0
   297
{
jens@0
   298
    LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
jens@0
   299
    Assert(error);
jens@0
   300
    setObj(&_error,error);
jens@0
   301
    [_reader disconnect];
jens@0
   302
    setObj(&_reader,nil);
jens@0
   303
    [_writer disconnect];
jens@0
   304
    setObj(&_writer,nil);
jens@0
   305
    [self _closed];
jens@0
   306
}
jens@0
   307
jens@0
   308
- (void) _streamGotEOF: (TCPStream*)stream
jens@0
   309
{
jens@0
   310
    LogTo(TCP,@"%@ got EOF on %@",self,stream);
jens@0
   311
    if( stream == _reader ) {
jens@0
   312
        setObj(&_reader,nil);
jens@0
   313
        // This is the expected way for he peer to initiate closing the connection.
jens@0
   314
        if( _status==kTCP_Open ) {
jens@0
   315
            [self closeWithTimeout: INFINITY];
jens@0
   316
            return;
jens@0
   317
        }
jens@0
   318
    } else if( stream == _writer ) {
jens@0
   319
        setObj(&_writer,nil);
jens@0
   320
    }
jens@0
   321
    
jens@0
   322
    if( _status == kTCP_Closing ) {
jens@0
   323
        [self _checkIfClosed];
jens@0
   324
    } else {
jens@0
   325
        [self _stream: stream 
jens@0
   326
             gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
jens@0
   327
    }
jens@0
   328
}
jens@0
   329
jens@0
   330
jens@0
   331
// Called after I called -close on a stream and it finished closing:
jens@0
   332
- (void) _streamClosed: (TCPStream*)stream
jens@0
   333
{
jens@0
   334
    LogTo(TCP,@"%@ finished closing %@",self,stream);
jens@0
   335
    if( stream == _reader )
jens@0
   336
        setObj(&_reader,nil);
jens@0
   337
    else if( stream == _writer )
jens@0
   338
        setObj(&_writer,nil);
jens@0
   339
    if( !_reader.isOpen && !_writer.isOpen )
jens@0
   340
        [self _closed];
jens@0
   341
}
jens@0
   342
jens@0
   343
jens@0
   344
@end
jens@0
   345
jens@0
   346
jens@0
   347
/*
jens@0
   348
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   349
 
jens@0
   350
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   351
 provided that the following conditions are met:
jens@0
   352
 
jens@0
   353
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   354
 and the following disclaimer.
jens@0
   355
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   356
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   357
 distribution.
jens@0
   358
 
jens@0
   359
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   360
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   361
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   362
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   363
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   364
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   365
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   366
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   367
 */