BLIP/BLIPTest.m
author Jens Alfke <jens@mooseyard.com>
Fri Jul 24 14:06:28 2009 -0700 (2009-07-24)
changeset 63 5e4855a592ee
parent 37 7c7d5a0cb4d6
permissions -rw-r--r--
* The BLIPConnection receivedRequest: delegate method now returns BOOL. If the method returns NO (or if the method isn't implemented in the delegate), that means it didn't handle the message at all; an error will be returned to the sender.
* If the connection closes unexpectedly due to an error, then the auto-generated responses to pending requests will contain that error. This makes it easier to display a meaningful error message in the handler for the request.
jens@0
     1
//
jens@0
     2
//  BLIPTest.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/13/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#ifndef NDEBUG
jens@0
    10
jens@0
    11
jens@0
    12
#import "BLIPRequest.h"
jens@0
    13
#import "BLIPProperties.h"
jens@0
    14
#import "BLIPConnection.h"
jens@1
    15
jens@0
    16
#import "IPAddress.h"
jens@0
    17
#import "Target.h"
jens@1
    18
#import "CollectionUtils.h"
jens@1
    19
#import "Logging.h"
jens@1
    20
#import "Test.h"
jens@0
    21
jens@26
    22
#import <Security/Security.h>
jens@26
    23
#import <SecurityInterface/SFChooseIdentityPanel.h>
jens@26
    24
jens@26
    25
@interface TCPEndpoint ()
jens@26
    26
+ (NSString*) describeCert: (SecCertificateRef)cert;
jens@26
    27
+ (NSString*) describeIdentity: (SecIdentityRef)identity;
jens@26
    28
@end
jens@0
    29
jens@1
    30
jens@11
    31
#define kListenerHost               @"localhost"
jens@0
    32
#define kListenerPort               46353
jens@0
    33
#define kSendInterval               0.5
jens@0
    34
#define kNBatchedMessages           20
jens@0
    35
#define kUseCompression             YES
jens@0
    36
#define kUrgentEvery                4
jens@18
    37
#define kListenerCloseAfter         50
jens@22
    38
#define kClientAcceptCloseRequest   YES
jens@0
    39
jens@26
    40
#define kListenerUsesSSL            YES     // Does the listener (server) use an SSL connection?
jens@26
    41
#define kListenerRequiresClientCert YES     // Does the listener require clients to have an SSL cert?
jens@26
    42
#define kClientRequiresSSL          YES     // Does the client require the listener to use SSL?
jens@26
    43
#define kClientUsesSSLCert          YES     // Does the client use an SSL cert?
jens@26
    44
jens@26
    45
jens@26
    46
static SecIdentityRef ChooseIdentity( NSString *prompt ) {
jens@26
    47
    NSMutableArray *identities = [NSMutableArray array];
jens@26
    48
    SecKeychainRef kc;
jens@26
    49
    SecKeychainCopyDefault(&kc);
jens@26
    50
    SecIdentitySearchRef search;
jens@26
    51
    SecIdentitySearchCreate(kc, CSSM_KEYUSE_ANY, &search);
jens@26
    52
    SecIdentityRef identity;
danpreston@37
    53
    while (SecIdentitySearchCopyNext(search, &identity) == noErr) {
jens@26
    54
        [identities addObject: (id)identity];
danpreston@37
    55
		CFRelease( identity );
danpreston@37
    56
	}
jens@29
    57
    CFRelease(search);
jens@26
    58
    Log(@"Found %u identities -- prompting '%@'", identities.count, prompt);
jens@26
    59
    if (identities.count > 0) {
jens@26
    60
        SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
jens@26
    61
        if ([panel runModalForIdentities: identities message: prompt] == NSOKButton) {
jens@26
    62
            Log(@"Using SSL identity: %@", panel.identity);
jens@26
    63
            return panel.identity;
jens@26
    64
        }
jens@26
    65
    }
jens@26
    66
    return NULL;
jens@26
    67
}
jens@0
    68
jens@0
    69
static SecIdentityRef GetClientIdentity(void) {
jens@26
    70
    return ChooseIdentity(@"Choose an identity for the BLIP Client Test:");
jens@0
    71
}
jens@0
    72
jens@0
    73
static SecIdentityRef GetListenerIdentity(void) {
jens@26
    74
    return ChooseIdentity(@"Choose an identity for the BLIP Listener Test:");
jens@0
    75
}
jens@0
    76
jens@0
    77
jens@0
    78
#pragma mark -
jens@0
    79
#pragma mark CLIENT TEST:
jens@0
    80
jens@0
    81
jens@0
    82
@interface BLIPConnectionTester : NSObject <BLIPConnectionDelegate>
jens@0
    83
{
jens@0
    84
    BLIPConnection *_conn;
jens@0
    85
    NSMutableDictionary *_pending;
jens@0
    86
}
jens@0
    87
jens@0
    88
@end
jens@0
    89
jens@0
    90
jens@0
    91
@implementation BLIPConnectionTester
jens@0
    92
jens@0
    93
- (id) init
jens@0
    94
{
jens@0
    95
    self = [super init];
jens@0
    96
    if (self != nil) {
jens@0
    97
        Log(@"** INIT %@",self);
jens@0
    98
        _pending = [[NSMutableDictionary alloc] init];
danpreston@35
    99
        IPAddress *addr = [[[IPAddress alloc] initWithHostname: kListenerHost port: kListenerPort] autorelease];
jens@0
   100
        _conn = [[BLIPConnection alloc] initToAddress: addr];
jens@0
   101
        if( ! _conn ) {
jens@0
   102
            [self release];
jens@0
   103
            return nil;
jens@0
   104
        }
jens@26
   105
        if( kClientUsesSSLCert ) {
jens@26
   106
            [_conn setPeerToPeerIdentity: GetClientIdentity()];
jens@26
   107
        } else if( kClientRequiresSSL ) {
jens@26
   108
            _conn.SSLProperties = $mdict({kTCPPropertySSLAllowsAnyRoot, $true},
jens@26
   109
                                        {(id)kCFStreamSSLPeerName, [NSNull null]});
jens@0
   110
        }
jens@0
   111
        _conn.delegate = self;
jens@0
   112
        Log(@"** Opening connection...");
jens@0
   113
        [_conn open];
jens@0
   114
    }
jens@0
   115
    return self;
jens@0
   116
}
jens@0
   117
jens@0
   118
- (void) dealloc
jens@0
   119
{
jens@0
   120
    Log(@"** %@ closing",self);
jens@0
   121
    [_conn close];
jens@0
   122
    [_conn release];
jens@0
   123
    [super dealloc];
jens@0
   124
}
jens@0
   125
jens@0
   126
- (void) sendAMessage
jens@0
   127
{
jens@18
   128
    if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) {
jens@18
   129
        if(_pending.count<100) {
jens@63
   130
            Log(@"** Sending a message that will fail to be handled...");
jens@63
   131
            BLIPRequest *q = [_conn requestWithBody: nil
jens@63
   132
                                         properties: $dict({@"Profile", @"BLIPTest/DontHandleMe"},
jens@63
   133
                                                           {@"User-Agent", @"BLIPConnectionTester"},
jens@63
   134
                                                           {@"Date", [[NSDate date] description]})];
jens@63
   135
            BLIPResponse *response = [q send];
jens@63
   136
            Assert(response);
jens@63
   137
            Assert(q.number>0);
jens@63
   138
            Assert(response.number==q.number);
jens@63
   139
            [_pending setObject: [NSNull null] forKey: $object(q.number)];
jens@63
   140
            response.onComplete = $target(self,responseArrived:);
jens@63
   141
            
jens@18
   142
            Log(@"** Sending another %i messages...", kNBatchedMessages);
jens@18
   143
            for( int i=0; i<kNBatchedMessages; i++ ) {
jens@18
   144
                size_t size = random() % 32768;
jens@18
   145
                NSMutableData *body = [NSMutableData dataWithLength: size];
jens@18
   146
                UInt8 *bytes = body.mutableBytes;
jens@18
   147
                for( size_t i=0; i<size; i++ )
jens@18
   148
                    bytes[i] = i % 256;
jens@18
   149
                
jens@63
   150
                q = [_conn requestWithBody: body
jens@63
   151
                                 properties: $dict({@"Profile", @"BLIPTest/EchoData"},
jens@63
   152
                                                   {@"Content-Type", @"application/octet-stream"},
jens@63
   153
                                                   {@"User-Agent", @"BLIPConnectionTester"},
jens@63
   154
                                                   {@"Date", [[NSDate date] description]},
jens@63
   155
                                                   {@"Size",$sprintf(@"%u",size)})];
jens@18
   156
                Assert(q);
jens@18
   157
                if( kUseCompression && (random()%2==1) )
jens@18
   158
                    q.compressed = YES;
jens@18
   159
                if( random()%16 > 12 )
jens@18
   160
                    q.urgent = YES;
jens@18
   161
                BLIPResponse *response = [q send];
jens@18
   162
                Assert(response);
jens@18
   163
                Assert(q.number>0);
jens@18
   164
                Assert(response.number==q.number);
jens@18
   165
                [_pending setObject: $object(size) forKey: $object(q.number)];
jens@18
   166
                response.onComplete = $target(self,responseArrived:);
jens@18
   167
            }
jens@18
   168
        } else {
jens@18
   169
            Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
jens@11
   170
        }
jens@18
   171
        [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
jens@0
   172
    }
jens@0
   173
}
jens@0
   174
jens@0
   175
- (void) responseArrived: (BLIPResponse*)response
jens@0
   176
{
jens@0
   177
    Log(@"********** called responseArrived: %@",response);
jens@0
   178
}
jens@0
   179
jens@0
   180
- (void) connectionDidOpen: (TCPConnection*)connection
jens@0
   181
{
jens@0
   182
    Log(@"** %@ didOpen",connection);
jens@0
   183
    [self sendAMessage];
jens@0
   184
}
jens@0
   185
- (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
jens@0
   186
{
jens@26
   187
    Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
jens@0
   188
    return peerCert != nil;
jens@0
   189
}
jens@0
   190
- (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
jens@0
   191
{
jens@26
   192
    Warn(@"** %@ failedToOpen: %@",connection,error);
jens@0
   193
    CFRunLoopStop(CFRunLoopGetCurrent());
jens@0
   194
}
jens@0
   195
- (void) connectionDidClose: (TCPConnection*)connection
jens@0
   196
{
jens@26
   197
    if (connection.error)
jens@26
   198
        Warn(@"** %@ didClose: %@", connection,connection.error);
jens@26
   199
    else
jens@26
   200
        Log(@"** %@ didClose", connection);
jens@0
   201
    setObj(&_conn,nil);
jens@0
   202
    [NSObject cancelPreviousPerformRequestsWithTarget: self];
jens@0
   203
    CFRunLoopStop(CFRunLoopGetCurrent());
jens@0
   204
}
jens@63
   205
- (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
jens@0
   206
{
jens@0
   207
    Log(@"***** %@ received %@",connection,request);
jens@2
   208
    [request respondWithData: request.body contentType: request.contentType];
jens@63
   209
    return YES;
jens@0
   210
}
jens@0
   211
jens@0
   212
- (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response
jens@0
   213
{
jens@0
   214
    Log(@"********** %@ received %@",connection,response);
jens@63
   215
    id sizeObj = [_pending objectForKey: $object(response.number)];
jens@63
   216
    Assert(sizeObj);
jens@63
   217
    
jens@63
   218
    if (sizeObj == [NSNull null]) {
jens@63
   219
        AssertEqual(response.error.domain, BLIPErrorDomain);
jens@63
   220
        AssertEq(response.error.code, kBLIPError_NotFound);
jens@63
   221
    } else {
jens@63
   222
        if( response.error )
jens@63
   223
            Warn(@"Got error response: %@",response.error);
jens@63
   224
        else {
jens@63
   225
            NSData *body = response.body;
jens@63
   226
            size_t size = body.length;
jens@63
   227
            Assert(size<32768);
jens@63
   228
            const UInt8 *bytes = body.bytes;
jens@63
   229
            for( size_t i=0; i<size; i++ )
jens@63
   230
                AssertEq(bytes[i],i % 256);
jens@63
   231
            AssertEq(size,[sizeObj unsignedIntValue]);
jens@63
   232
        }
jens@0
   233
    }
jens@0
   234
    [_pending removeObjectForKey: $object(response.number)];
jens@0
   235
    Log(@"Now %u replies pending", _pending.count);
jens@0
   236
}
jens@0
   237
jens@19
   238
- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection
jens@19
   239
{
jens@22
   240
    BOOL response = kClientAcceptCloseRequest;
jens@19
   241
    Log(@"***** %@ received a close request; returning %i",connection,response);
jens@19
   242
    return response;
jens@19
   243
}
jens@19
   244
jens@0
   245
jens@0
   246
@end
jens@0
   247
jens@0
   248
jens@0
   249
TestCase(BLIPConnection) {
jens@26
   250
    SecKeychainSetUserInteractionAllowed(true);
jens@0
   251
    BLIPConnectionTester *tester = [[BLIPConnectionTester alloc] init];
jens@0
   252
    CAssert(tester);
jens@0
   253
    
jens@0
   254
    [[NSRunLoop currentRunLoop] run];
jens@0
   255
    
jens@0
   256
    Log(@"** Runloop stopped");
jens@0
   257
    [tester release];
jens@0
   258
}
jens@0
   259
jens@0
   260
jens@0
   261
jens@0
   262
jens@0
   263
#pragma mark LISTENER TEST:
jens@0
   264
jens@0
   265
jens@0
   266
@interface BLIPTestListener : NSObject <TCPListenerDelegate, BLIPConnectionDelegate>
jens@0
   267
{
jens@0
   268
    BLIPListener *_listener;
jens@18
   269
    int _nReceived;
jens@0
   270
}
jens@0
   271
jens@0
   272
@end
jens@0
   273
jens@0
   274
jens@0
   275
@implementation BLIPTestListener
jens@0
   276
jens@0
   277
- (id) init
jens@0
   278
{
jens@0
   279
    self = [super init];
jens@0
   280
    if (self != nil) {
jens@0
   281
        _listener = [[BLIPListener alloc] initWithPort: kListenerPort];
jens@0
   282
        _listener.delegate = self;
jens@0
   283
        _listener.pickAvailablePort = YES;
jens@0
   284
        _listener.bonjourServiceType = @"_bliptest._tcp";
jens@26
   285
        if( kListenerUsesSSL ) {
jens@26
   286
            [_listener setPeerToPeerIdentity: GetListenerIdentity()];
jens@26
   287
            if (!kListenerRequiresClientCert)
jens@26
   288
                [_listener setSSLProperty: $object(kTCPTryAuthenticate) 
jens@26
   289
                                   forKey: kTCPPropertySSLClientSideAuthentication];
jens@0
   290
        }
jens@0
   291
        Assert( [_listener open] );
jens@0
   292
        Log(@"%@ is listening...",self);
jens@0
   293
    }
jens@0
   294
    return self;
jens@0
   295
}
jens@0
   296
jens@0
   297
- (void) dealloc
jens@0
   298
{
jens@0
   299
    Log(@"%@ closing",self);
jens@0
   300
    [_listener close];
jens@0
   301
    [_listener release];
jens@0
   302
    [super dealloc];
jens@0
   303
}
jens@0
   304
jens@0
   305
- (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection
jens@0
   306
{
jens@0
   307
    Log(@"** %@ accepted %@",self,connection);
jens@0
   308
    connection.delegate = self;
jens@0
   309
}
jens@0
   310
jens@0
   311
- (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error
jens@0
   312
{
jens@0
   313
    Log(@"** BLIPTestListener failed to open: %@",error);
jens@0
   314
}
jens@0
   315
jens@0
   316
- (void) listenerDidOpen: (TCPListener*)listener   {Log(@"** BLIPTestListener did open");}
jens@0
   317
- (void) listenerDidClose: (TCPListener*)listener   {Log(@"** BLIPTestListener did close");}
jens@0
   318
jens@0
   319
- (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address
jens@0
   320
{
jens@0
   321
    Log(@"** %@ shouldAcceptConnectionFrom: %@",self,address);
jens@0
   322
    return YES;
jens@0
   323
}
jens@0
   324
jens@0
   325
jens@0
   326
- (void) connectionDidOpen: (TCPConnection*)connection
jens@0
   327
{
jens@0
   328
    Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel);
jens@18
   329
    _nReceived = 0;
jens@0
   330
}
jens@0
   331
- (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
jens@0
   332
{
jens@26
   333
    Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
jens@0
   334
    return peerCert != nil || ! kListenerRequiresClientCert;
jens@0
   335
}
jens@0
   336
- (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
jens@0
   337
{
jens@0
   338
    Log(@"** %@ failedToOpen: %@",connection,error);
jens@0
   339
}
jens@0
   340
- (void) connectionDidClose: (TCPConnection*)connection
jens@0
   341
{
jens@26
   342
    if (connection.error)
jens@26
   343
        Warn(@"** %@ didClose: %@", connection,connection.error);
jens@26
   344
    else
jens@26
   345
        Log(@"** %@ didClose", connection);
jens@0
   346
    [connection release];
jens@0
   347
}
jens@63
   348
- (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
jens@0
   349
{
jens@0
   350
    Log(@"***** %@ received %@",connection,request);
jens@0
   351
    
jens@63
   352
    if ([request.profile isEqualToString: @"BLIPTest/EchoData"]) {
jens@63
   353
        NSData *body = request.body;
jens@63
   354
        size_t size = body.length;
jens@63
   355
        Assert(size<32768);
jens@63
   356
        const UInt8 *bytes = body.bytes;
jens@63
   357
        for( size_t i=0; i<size; i++ )
jens@63
   358
            AssertEq(bytes[i],i % 256);
jens@63
   359
        
jens@63
   360
        AssertEqual([request valueOfProperty: @"Content-Type"], @"application/octet-stream");
jens@63
   361
        Assert([request valueOfProperty: @"User-Agent"] != nil);
jens@63
   362
        AssertEq((size_t)[[request valueOfProperty: @"Size"] intValue], size);
jens@0
   363
jens@63
   364
        [request respondWithData: body contentType: request.contentType];
jens@63
   365
    } else if ([request.profile isEqualToString: @"BLIPTest/DontHandleMe"]) {
jens@63
   366
        // Deliberately don't handle this, to test unhandled request handling.
jens@63
   367
        return NO;
jens@63
   368
    } else {
jens@63
   369
        Assert(NO, @"Unknown profile in request %@", request);
jens@63
   370
    }
jens@18
   371
    
jens@18
   372
    if( ++ _nReceived == kListenerCloseAfter ) {
jens@18
   373
        Log(@"********** Closing BLIPTestListener after %i requests",_nReceived);
jens@18
   374
        [connection close];
jens@18
   375
    }
jens@63
   376
    return YES;
jens@0
   377
}
jens@0
   378
jens@19
   379
- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
jens@19
   380
{
jens@19
   381
    Log(@"***** %@ received a close request",connection);
jens@19
   382
    return YES;
jens@19
   383
}
jens@19
   384
jens@19
   385
- (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error
jens@19
   386
{
jens@19
   387
    Log(@"***** %@'s close request failed: %@",connection,error);
jens@19
   388
}
jens@19
   389
jens@0
   390
jens@0
   391
@end
jens@0
   392
jens@0
   393
jens@0
   394
TestCase(BLIPListener) {
jens@0
   395
    EnableLogTo(BLIP,YES);
jens@0
   396
    EnableLogTo(PortMapper,YES);
jens@0
   397
    EnableLogTo(Bonjour,YES);
jens@26
   398
    SecKeychainSetUserInteractionAllowed(true);
jens@0
   399
    BLIPTestListener *listener = [[BLIPTestListener alloc] init];
jens@0
   400
    
jens@0
   401
    [[NSRunLoop currentRunLoop] run];
jens@0
   402
    
jens@0
   403
    [listener release];
jens@0
   404
}
jens@0
   405
jens@0
   406
jens@0
   407
#endif
jens@0
   408
jens@0
   409
jens@0
   410
/*
jens@0
   411
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   412
 
jens@0
   413
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   414
 provided that the following conditions are met:
jens@0
   415
 
jens@0
   416
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   417
 and the following disclaimer.
jens@0
   418
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   419
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   420
 distribution.
jens@0
   421
 
jens@0
   422
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   423
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   424
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   425
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   426
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   427
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   428
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   429
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   430
 */