TCP/TCPStream.m
author Jens Alfke <jens@mooseyard.com>
Sun Jul 13 10:42:50 2008 -0700 (2008-07-13)
changeset 22 8b883753394a
parent 18 3be241de1630
child 26 cb9cdf247239
permissions -rw-r--r--
* Fixed: Responses still pending when a connection closed were not calling their onComplete targets.
* Fixed: BLIPTestClient target failed to build because it didn't link against zlib.
* If TCPListener.bonjourServiceName is changed while the listener is open, it now re-publishes the service with the new name.
* Added a TCPListener.bonjourService property.
* Added a BLIPMessage.representedObject property.
* Fixed a memory leak.
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@19
   124
    if( ! _shouldClose ) {
jens@19
   125
        _shouldClose = YES;
jens@19
   126
        LogTo(TCP,@"Request to close %@",self);
jens@19
   127
    }
jens@0
   128
    if( self.isBusy ) {
jens@0
   129
        return NO;
jens@0
   130
    } else {
jens@18
   131
        [[self retain] autorelease];        // don't let myself be dealloced in the midst of this
jens@18
   132
        [_conn _streamCanClose: self];
jens@0
   133
        return YES;
jens@0
   134
    }
jens@0
   135
}
jens@0
   136
jens@19
   137
- (void) _unclose
jens@19
   138
{
jens@19
   139
    _shouldClose = NO;
jens@19
   140
}
jens@19
   141
jens@0
   142
jens@0
   143
- (BOOL) isOpen
jens@0
   144
{
jens@0
   145
    NSStreamStatus status = _stream.streamStatus;
jens@0
   146
    return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
jens@0
   147
}
jens@0
   148
jens@0
   149
- (BOOL) isBusy
jens@0
   150
{
jens@0
   151
    return NO;  // abstract
jens@0
   152
}
jens@0
   153
jens@18
   154
- (BOOL) isActive
jens@18
   155
{
jens@18
   156
    return !_shouldClose || self.isBusy;
jens@18
   157
}
jens@18
   158
jens@0
   159
jens@0
   160
- (void) _opened
jens@0
   161
{
jens@0
   162
    [_conn _streamOpened: self];
jens@0
   163
}
jens@0
   164
jens@0
   165
- (void) _canRead
jens@0
   166
{
jens@0
   167
    // abstract
jens@0
   168
}
jens@0
   169
jens@0
   170
- (void) _canWrite
jens@0
   171
{
jens@0
   172
    // abstract
jens@0
   173
}
jens@0
   174
jens@0
   175
- (void) _gotEOF
jens@0
   176
{
jens@18
   177
    [_conn _streamGotEOF: self];
jens@0
   178
}
jens@0
   179
jens@0
   180
- (BOOL) _gotError: (NSError*)error
jens@0
   181
{
jens@0
   182
    [_conn _stream: self gotError: fixStreamError(error)];
jens@0
   183
    return NO;
jens@0
   184
}
jens@0
   185
jens@0
   186
- (BOOL) _gotError
jens@0
   187
{
jens@0
   188
    NSError *error = _stream.streamError;
jens@0
   189
    if( ! error )
jens@0
   190
        error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
jens@0
   191
    return [self _gotError: error];
jens@0
   192
}
jens@0
   193
jens@0
   194
jens@0
   195
- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
jens@0
   196
{
jens@0
   197
    [[self retain] autorelease];
jens@0
   198
    switch(streamEvent) {
jens@0
   199
        case NSStreamEventOpenCompleted:
jens@0
   200
            LogTo(TCPVerbose,@"%@ opened",self);
jens@0
   201
            [self _opened];
jens@0
   202
            break;
jens@0
   203
        case NSStreamEventHasBytesAvailable:
jens@0
   204
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   205
                return;
jens@0
   206
            LogTo(TCPVerbose,@"%@ can read",self);
jens@0
   207
            [self _canRead];
jens@0
   208
            break;
jens@0
   209
        case NSStreamEventHasSpaceAvailable:
jens@0
   210
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   211
                return;
jens@0
   212
            LogTo(TCPVerbose,@"%@ can write",self);
jens@0
   213
            [self _canWrite];
jens@0
   214
            break;
jens@0
   215
        case NSStreamEventErrorOccurred:
jens@0
   216
            LogTo(TCPVerbose,@"%@ got error",self);
jens@0
   217
            [self _gotError];
jens@0
   218
            break;
jens@0
   219
        case NSStreamEventEndEncountered:
jens@0
   220
            LogTo(TCPVerbose,@"%@ got EOF",self);
jens@0
   221
            [self _gotEOF];
jens@0
   222
            break;
jens@0
   223
        default:
jens@0
   224
            Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
jens@0
   225
            break;
jens@0
   226
    }
jens@0
   227
    
jens@0
   228
    // If I was previously asked to close, try again in case I'm no longer busy
jens@0
   229
    if( _shouldClose )
jens@0
   230
        [self close];
jens@0
   231
}
jens@0
   232
jens@0
   233
jens@0
   234
@end
jens@0
   235
jens@0
   236
jens@0
   237
jens@0
   238
jens@0
   239
@implementation TCPReader
jens@0
   240
jens@0
   241
jens@0
   242
- (TCPWriter*) writer
jens@0
   243
{
jens@0
   244
    return _conn.writer;
jens@0
   245
}
jens@0
   246
jens@2
   247
- (NSInteger) read: (void*)dst maxLength: (NSUInteger)maxLength
jens@2
   248
{
jens@2
   249
    NSInteger bytesRead = [(NSInputStream*)_stream read:dst maxLength: maxLength];
jens@2
   250
    if( bytesRead < 0 )
jens@2
   251
        [self _gotError];
jens@2
   252
    return bytesRead;
jens@2
   253
}
jens@2
   254
jens@2
   255
jens@0
   256
@end
jens@0
   257
jens@0
   258
jens@0
   259
jens@0
   260
jens@0
   261
static NSError* fixStreamError( NSError *error )
jens@0
   262
{
jens@0
   263
    // NSStream incorrectly returns SSL errors without the correct error domain:
jens@0
   264
    if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
jens@0
   265
        int code = error.code;
jens@0
   266
        if( -9899 <= code && code <= -9800 ) {
jens@0
   267
            NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
jens@0
   268
            if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
jens@0
   269
                // look up error message:
jens@0
   270
                NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
jens@0
   271
                NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
jens@0
   272
                                                               value: nil
jens@0
   273
                                                               table: @"SecErrorMessages"];
jens@0
   274
                if( message ) {
jens@0
   275
                    if( ! userInfo ) userInfo = $mdict();
jens@0
   276
                    [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
jens@0
   277
                }
jens@0
   278
            }
jens@0
   279
            error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
jens@0
   280
                                        code: code userInfo: userInfo];
jens@0
   281
        } else
jens@0
   282
            Warn(@"NSStream returned error with unknown domain: %@",error);
jens@0
   283
    }
jens@0
   284
    return error;
jens@0
   285
}
jens@0
   286
jens@0
   287
/*
jens@0
   288
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   289
 
jens@0
   290
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   291
 provided that the following conditions are met:
jens@0
   292
 
jens@0
   293
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   294
 and the following disclaimer.
jens@0
   295
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   296
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   297
 distribution.
jens@0
   298
 
jens@0
   299
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   300
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   301
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   302
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   303
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   304
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   305
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   306
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   307
 */