TCP/TCPStream.m
author Jens Alfke <jens@mooseyard.com>
Sun May 25 10:28:26 2008 -0700 (2008-05-25)
changeset 4 2bd9d60a2c46
parent 1 8267d5c429c4
child 7 5936db2c1987
permissions -rw-r--r--
More documentation.
jens@0
     1
//
jens@0
     2
//  TCPStream.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/10/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#import "TCPStream.h"
jens@0
    10
#import "TCP_Internal.h"
jens@0
    11
jens@1
    12
#import "Logging.h"
jens@1
    13
#import "Test.h"
jens@1
    14
jens@0
    15
jens@0
    16
extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork
jens@0
    17
jens@0
    18
static NSError* fixStreamError( NSError *error );
jens@0
    19
jens@0
    20
jens@0
    21
@implementation TCPStream
jens@0
    22
jens@0
    23
jens@0
    24
- (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream
jens@0
    25
{
jens@0
    26
    self = [super init];
jens@0
    27
    if (self != nil) {
jens@0
    28
        _conn = [conn retain];
jens@0
    29
        _stream = [stream retain];
jens@0
    30
        _stream.delegate = self;
jens@0
    31
        [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
jens@0
    32
        LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus);
jens@0
    33
    }
jens@0
    34
    return self;
jens@0
    35
}
jens@0
    36
jens@0
    37
jens@0
    38
- (void) dealloc
jens@0
    39
{
jens@0
    40
    LogTo(TCP,@"DEALLOC %@",self);
jens@0
    41
    if( _stream )
jens@0
    42
        [self disconnect];
jens@0
    43
    [super dealloc];
jens@0
    44
}
jens@0
    45
jens@0
    46
jens@0
    47
- (id) propertyForKey: (CFStringRef)cfStreamProperty
jens@0
    48
{
jens@0
    49
    return nil; // abstract -- overridden by TCPReader and TCPWriter
jens@0
    50
}
jens@0
    51
jens@0
    52
- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
jens@0
    53
{ // abstract -- overridden by TCPReader and TCPWriter
jens@0
    54
}
jens@0
    55
jens@0
    56
jens@0
    57
#pragma mark -
jens@0
    58
#pragma mark SSL:
jens@0
    59
jens@0
    60
jens@0
    61
- (NSString*) securityLevel                 {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];}
jens@0
    62
jens@0
    63
- (NSDictionary*) SSLProperties             {return [self propertyForKey: kCFStreamPropertySSLSettings];}
jens@0
    64
jens@0
    65
- (void) setSSLProperties: (NSDictionary*)p
jens@0
    66
{
jens@0
    67
    LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p);
jens@0
    68
    [self setProperty: p forKey: kCFStreamPropertySSLSettings];
jens@0
    69
    
jens@0
    70
    id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication];
jens@0
    71
    if( clientAuth )
jens@0
    72
        [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication];
jens@0
    73
}
jens@0
    74
jens@0
    75
- (NSArray*) peerSSLCerts
jens@0
    76
{
jens@0
    77
    Assert(self.isOpen);
jens@0
    78
    return [self propertyForKey: kCFStreamPropertySSLPeerCertificates];
jens@0
    79
}
jens@0
    80
jens@0
    81
jens@0
    82
#pragma mark -
jens@0
    83
#pragma mark OPENING/CLOSING:
jens@0
    84
jens@0
    85
jens@0
    86
- (void) open
jens@0
    87
{
jens@0
    88
    Assert(_stream);
jens@0
    89
    AssertEq(_stream.streamStatus,NSStreamStatusNotOpen);
jens@0
    90
    LogTo(TCP,@"Opening %@",self);
jens@0
    91
    [_stream open];
jens@0
    92
}
jens@0
    93
jens@0
    94
jens@0
    95
- (void) disconnect
jens@0
    96
{
jens@0
    97
    if( _stream ) {
jens@0
    98
        LogTo(TCP,@"Disconnect %@",self);
jens@0
    99
        _stream.delegate = nil;
jens@0
   100
        [_stream close];
jens@0
   101
        setObj(&_stream,nil);
jens@0
   102
    }
jens@0
   103
    setObj(&_conn,nil);
jens@0
   104
}
jens@0
   105
jens@0
   106
jens@0
   107
- (BOOL) close
jens@0
   108
{
jens@0
   109
    if( self.isBusy ) {
jens@0
   110
        _shouldClose = YES;
jens@0
   111
        return NO;
jens@0
   112
    } else {
jens@0
   113
        LogTo(TCP,@"Closing %@",self);
jens@0
   114
        [[self retain] autorelease];    // don't let myself be dealloced in the midst of this
jens@0
   115
        [_conn _streamClosed: self];    // have to do this before disconnect
jens@0
   116
        [self disconnect];
jens@0
   117
        return YES;
jens@0
   118
    }
jens@0
   119
}
jens@0
   120
jens@0
   121
jens@0
   122
- (BOOL) isOpen
jens@0
   123
{
jens@0
   124
    NSStreamStatus status = _stream.streamStatus;
jens@0
   125
    return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
jens@0
   126
}
jens@0
   127
jens@0
   128
- (BOOL) isBusy
jens@0
   129
{
jens@0
   130
    return NO;  // abstract
jens@0
   131
}
jens@0
   132
jens@0
   133
jens@0
   134
- (void) _opened
jens@0
   135
{
jens@0
   136
    [_conn _streamOpened: self];
jens@0
   137
}
jens@0
   138
jens@0
   139
- (void) _canRead
jens@0
   140
{
jens@0
   141
    // abstract
jens@0
   142
}
jens@0
   143
jens@0
   144
- (void) _canWrite
jens@0
   145
{
jens@0
   146
    // abstract
jens@0
   147
}
jens@0
   148
jens@0
   149
- (void) _gotEOF
jens@0
   150
{
jens@0
   151
    if( self.isBusy )
jens@0
   152
        [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
jens@0
   153
    else {
jens@0
   154
        [self retain];
jens@0
   155
        [_conn _streamGotEOF: self];
jens@0
   156
        [self disconnect];
jens@0
   157
        [self release];
jens@0
   158
    }
jens@0
   159
}
jens@0
   160
jens@0
   161
- (BOOL) _gotError: (NSError*)error
jens@0
   162
{
jens@0
   163
    [_conn _stream: self gotError: fixStreamError(error)];
jens@0
   164
    return NO;
jens@0
   165
}
jens@0
   166
jens@0
   167
- (BOOL) _gotError
jens@0
   168
{
jens@0
   169
    NSError *error = _stream.streamError;
jens@0
   170
    if( ! error )
jens@0
   171
        error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
jens@0
   172
    return [self _gotError: error];
jens@0
   173
}
jens@0
   174
jens@0
   175
jens@0
   176
- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
jens@0
   177
{
jens@0
   178
    [[self retain] autorelease];
jens@0
   179
    switch(streamEvent) {
jens@0
   180
        case NSStreamEventOpenCompleted:
jens@0
   181
            LogTo(TCPVerbose,@"%@ opened",self);
jens@0
   182
            [self _opened];
jens@0
   183
            break;
jens@0
   184
        case NSStreamEventHasBytesAvailable:
jens@0
   185
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   186
                return;
jens@0
   187
            LogTo(TCPVerbose,@"%@ can read",self);
jens@0
   188
            [self _canRead];
jens@0
   189
            break;
jens@0
   190
        case NSStreamEventHasSpaceAvailable:
jens@0
   191
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   192
                return;
jens@0
   193
            LogTo(TCPVerbose,@"%@ can write",self);
jens@0
   194
            [self _canWrite];
jens@0
   195
            break;
jens@0
   196
        case NSStreamEventErrorOccurred:
jens@0
   197
            LogTo(TCPVerbose,@"%@ got error",self);
jens@0
   198
            [self _gotError];
jens@0
   199
            break;
jens@0
   200
        case NSStreamEventEndEncountered:
jens@0
   201
            LogTo(TCPVerbose,@"%@ got EOF",self);
jens@0
   202
            [self _gotEOF];
jens@0
   203
            break;
jens@0
   204
        default:
jens@0
   205
            Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
jens@0
   206
            break;
jens@0
   207
    }
jens@0
   208
    
jens@0
   209
    // If I was previously asked to close, try again in case I'm no longer busy
jens@0
   210
    if( _shouldClose )
jens@0
   211
        [self close];
jens@0
   212
}
jens@0
   213
jens@0
   214
jens@0
   215
@end
jens@0
   216
jens@0
   217
jens@0
   218
jens@0
   219
jens@0
   220
@implementation TCPReader
jens@0
   221
jens@0
   222
jens@0
   223
- (TCPWriter*) writer
jens@0
   224
{
jens@0
   225
    return _conn.writer;
jens@0
   226
}
jens@0
   227
jens@0
   228
jens@0
   229
- (id) propertyForKey: (CFStringRef)cfStreamProperty
jens@0
   230
{
jens@0
   231
    CFTypeRef value = CFReadStreamCopyProperty((CFReadStreamRef)_stream,cfStreamProperty);
jens@0
   232
    return [(id)CFMakeCollectable(value) autorelease];
jens@0
   233
}
jens@0
   234
jens@0
   235
- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
jens@0
   236
{
jens@0
   237
    if( ! CFReadStreamSetProperty((CFReadStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) )
jens@0
   238
        Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty);
jens@0
   239
}
jens@0
   240
jens@0
   241
jens@2
   242
- (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength
jens@2
   243
{
jens@2
   244
    NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength];
jens@2
   245
    if( bytesRead < 0 )
jens@2
   246
        [self _gotError];
jens@2
   247
    return bytesRead;
jens@2
   248
}
jens@2
   249
jens@2
   250
jens@0
   251
@end
jens@0
   252
jens@0
   253
jens@0
   254
jens@0
   255
jens@0
   256
static NSError* fixStreamError( NSError *error )
jens@0
   257
{
jens@0
   258
    // NSStream incorrectly returns SSL errors without the correct error domain:
jens@0
   259
    if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
jens@0
   260
        int code = error.code;
jens@0
   261
        if( -9899 <= code && code <= -9800 ) {
jens@0
   262
            NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
jens@0
   263
            if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
jens@0
   264
                // look up error message:
jens@0
   265
                NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
jens@0
   266
                NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
jens@0
   267
                                                               value: nil
jens@0
   268
                                                               table: @"SecErrorMessages"];
jens@0
   269
                if( message ) {
jens@0
   270
                    if( ! userInfo ) userInfo = $mdict();
jens@0
   271
                    [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
jens@0
   272
                }
jens@0
   273
            }
jens@0
   274
            error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
jens@0
   275
                                        code: code userInfo: userInfo];
jens@0
   276
        } else
jens@0
   277
            Warn(@"NSStream returned error with unknown domain: %@",error);
jens@0
   278
    }
jens@0
   279
    return error;
jens@0
   280
}
jens@0
   281
jens@0
   282
/*
jens@0
   283
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   284
 
jens@0
   285
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   286
 provided that the following conditions are met:
jens@0
   287
 
jens@0
   288
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   289
 and the following disclaimer.
jens@0
   290
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   291
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   292
 distribution.
jens@0
   293
 
jens@0
   294
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   295
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   296
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   297
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   298
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   299
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   300
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   301
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   302
 */