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