* 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.
5 // Created by Jens Alfke on 5/13/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
12 #import "BLIPRequest.h"
13 #import "BLIPProperties.h"
14 #import "BLIPConnection.h"
18 #import "CollectionUtils.h"
22 #import <Security/Security.h>
23 #import <SecurityInterface/SFChooseIdentityPanel.h>
25 @interface TCPEndpoint ()
26 + (NSString*) describeCert: (SecCertificateRef)cert;
27 + (NSString*) describeIdentity: (SecIdentityRef)identity;
31 #define kListenerHost @"localhost"
32 #define kListenerPort 46353
33 #define kSendInterval 0.5
34 #define kNBatchedMessages 20
35 #define kUseCompression YES
36 #define kUrgentEvery 4
37 #define kListenerCloseAfter 50
38 #define kClientAcceptCloseRequest YES
40 #define kListenerUsesSSL YES // Does the listener (server) use an SSL connection?
41 #define kListenerRequiresClientCert YES // Does the listener require clients to have an SSL cert?
42 #define kClientRequiresSSL YES // Does the client require the listener to use SSL?
43 #define kClientUsesSSLCert YES // Does the client use an SSL cert?
46 static SecIdentityRef ChooseIdentity( NSString *prompt ) {
47 NSMutableArray *identities = [NSMutableArray array];
49 SecKeychainCopyDefault(&kc);
50 SecIdentitySearchRef search;
51 SecIdentitySearchCreate(kc, CSSM_KEYUSE_ANY, &search);
52 SecIdentityRef identity;
53 while (SecIdentitySearchCopyNext(search, &identity) == noErr) {
54 [identities addObject: (id)identity];
55 CFRelease( identity );
58 Log(@"Found %u identities -- prompting '%@'", identities.count, prompt);
59 if (identities.count > 0) {
60 SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
61 if ([panel runModalForIdentities: identities message: prompt] == NSOKButton) {
62 Log(@"Using SSL identity: %@", panel.identity);
63 return panel.identity;
69 static SecIdentityRef GetClientIdentity(void) {
70 return ChooseIdentity(@"Choose an identity for the BLIP Client Test:");
73 static SecIdentityRef GetListenerIdentity(void) {
74 return ChooseIdentity(@"Choose an identity for the BLIP Listener Test:");
79 #pragma mark CLIENT TEST:
82 @interface BLIPConnectionTester : NSObject <BLIPConnectionDelegate>
84 BLIPConnection *_conn;
85 NSMutableDictionary *_pending;
91 @implementation BLIPConnectionTester
97 Log(@"** INIT %@",self);
98 _pending = [[NSMutableDictionary alloc] init];
99 IPAddress *addr = [[[IPAddress alloc] initWithHostname: kListenerHost port: kListenerPort] autorelease];
100 _conn = [[BLIPConnection alloc] initToAddress: addr];
105 if( kClientUsesSSLCert ) {
106 [_conn setPeerToPeerIdentity: GetClientIdentity()];
107 } else if( kClientRequiresSSL ) {
108 _conn.SSLProperties = $mdict({kTCPPropertySSLAllowsAnyRoot, $true},
109 {(id)kCFStreamSSLPeerName, [NSNull null]});
111 _conn.delegate = self;
112 Log(@"** Opening connection...");
120 Log(@"** %@ closing",self);
126 - (void) sendAMessage
128 if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) {
129 if(_pending.count<100) {
130 Log(@"** Sending a message that will fail to be handled...");
131 BLIPRequest *q = [_conn requestWithBody: nil
132 properties: $dict({@"Profile", @"BLIPTest/DontHandleMe"},
133 {@"User-Agent", @"BLIPConnectionTester"},
134 {@"Date", [[NSDate date] description]})];
135 BLIPResponse *response = [q send];
138 Assert(response.number==q.number);
139 [_pending setObject: [NSNull null] forKey: $object(q.number)];
140 response.onComplete = $target(self,responseArrived:);
142 Log(@"** Sending another %i messages...", kNBatchedMessages);
143 for( int i=0; i<kNBatchedMessages; i++ ) {
144 size_t size = random() % 32768;
145 NSMutableData *body = [NSMutableData dataWithLength: size];
146 UInt8 *bytes = body.mutableBytes;
147 for( size_t i=0; i<size; i++ )
150 q = [_conn requestWithBody: body
151 properties: $dict({@"Profile", @"BLIPTest/EchoData"},
152 {@"Content-Type", @"application/octet-stream"},
153 {@"User-Agent", @"BLIPConnectionTester"},
154 {@"Date", [[NSDate date] description]},
155 {@"Size",$sprintf(@"%u",size)})];
157 if( kUseCompression && (random()%2==1) )
159 if( random()%16 > 12 )
161 BLIPResponse *response = [q send];
164 Assert(response.number==q.number);
165 [_pending setObject: $object(size) forKey: $object(q.number)];
166 response.onComplete = $target(self,responseArrived:);
169 Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
171 [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
175 - (void) responseArrived: (BLIPResponse*)response
177 Log(@"********** called responseArrived: %@",response);
180 - (void) connectionDidOpen: (TCPConnection*)connection
182 Log(@"** %@ didOpen",connection);
185 - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
187 Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
188 return peerCert != nil;
190 - (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
192 Warn(@"** %@ failedToOpen: %@",connection,error);
193 CFRunLoopStop(CFRunLoopGetCurrent());
195 - (void) connectionDidClose: (TCPConnection*)connection
197 if (connection.error)
198 Warn(@"** %@ didClose: %@", connection,connection.error);
200 Log(@"** %@ didClose", connection);
202 [NSObject cancelPreviousPerformRequestsWithTarget: self];
203 CFRunLoopStop(CFRunLoopGetCurrent());
205 - (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
207 Log(@"***** %@ received %@",connection,request);
208 [request respondWithData: request.body contentType: request.contentType];
212 - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response
214 Log(@"********** %@ received %@",connection,response);
215 id sizeObj = [_pending objectForKey: $object(response.number)];
218 if (sizeObj == [NSNull null]) {
219 AssertEqual(response.error.domain, BLIPErrorDomain);
220 AssertEq(response.error.code, kBLIPError_NotFound);
223 Warn(@"Got error response: %@",response.error);
225 NSData *body = response.body;
226 size_t size = body.length;
228 const UInt8 *bytes = body.bytes;
229 for( size_t i=0; i<size; i++ )
230 AssertEq(bytes[i],i % 256);
231 AssertEq(size,[sizeObj unsignedIntValue]);
234 [_pending removeObjectForKey: $object(response.number)];
235 Log(@"Now %u replies pending", _pending.count);
238 - (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection
240 BOOL response = kClientAcceptCloseRequest;
241 Log(@"***** %@ received a close request; returning %i",connection,response);
249 TestCase(BLIPConnection) {
250 SecKeychainSetUserInteractionAllowed(true);
251 BLIPConnectionTester *tester = [[BLIPConnectionTester alloc] init];
254 [[NSRunLoop currentRunLoop] run];
256 Log(@"** Runloop stopped");
263 #pragma mark LISTENER TEST:
266 @interface BLIPTestListener : NSObject <TCPListenerDelegate, BLIPConnectionDelegate>
268 BLIPListener *_listener;
275 @implementation BLIPTestListener
281 _listener = [[BLIPListener alloc] initWithPort: kListenerPort];
282 _listener.delegate = self;
283 _listener.pickAvailablePort = YES;
284 _listener.bonjourServiceType = @"_bliptest._tcp";
285 if( kListenerUsesSSL ) {
286 [_listener setPeerToPeerIdentity: GetListenerIdentity()];
287 if (!kListenerRequiresClientCert)
288 [_listener setSSLProperty: $object(kTCPTryAuthenticate)
289 forKey: kTCPPropertySSLClientSideAuthentication];
291 Assert( [_listener open] );
292 Log(@"%@ is listening...",self);
299 Log(@"%@ closing",self);
305 - (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection
307 Log(@"** %@ accepted %@",self,connection);
308 connection.delegate = self;
311 - (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error
313 Log(@"** BLIPTestListener failed to open: %@",error);
316 - (void) listenerDidOpen: (TCPListener*)listener {Log(@"** BLIPTestListener did open");}
317 - (void) listenerDidClose: (TCPListener*)listener {Log(@"** BLIPTestListener did close");}
319 - (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address
321 Log(@"** %@ shouldAcceptConnectionFrom: %@",self,address);
326 - (void) connectionDidOpen: (TCPConnection*)connection
328 Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel);
331 - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
333 Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
334 return peerCert != nil || ! kListenerRequiresClientCert;
336 - (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
338 Log(@"** %@ failedToOpen: %@",connection,error);
340 - (void) connectionDidClose: (TCPConnection*)connection
342 if (connection.error)
343 Warn(@"** %@ didClose: %@", connection,connection.error);
345 Log(@"** %@ didClose", connection);
346 [connection release];
348 - (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
350 Log(@"***** %@ received %@",connection,request);
352 if ([request.profile isEqualToString: @"BLIPTest/EchoData"]) {
353 NSData *body = request.body;
354 size_t size = body.length;
356 const UInt8 *bytes = body.bytes;
357 for( size_t i=0; i<size; i++ )
358 AssertEq(bytes[i],i % 256);
360 AssertEqual([request valueOfProperty: @"Content-Type"], @"application/octet-stream");
361 Assert([request valueOfProperty: @"User-Agent"] != nil);
362 AssertEq((size_t)[[request valueOfProperty: @"Size"] intValue], size);
364 [request respondWithData: body contentType: request.contentType];
365 } else if ([request.profile isEqualToString: @"BLIPTest/DontHandleMe"]) {
366 // Deliberately don't handle this, to test unhandled request handling.
369 Assert(NO, @"Unknown profile in request %@", request);
372 if( ++ _nReceived == kListenerCloseAfter ) {
373 Log(@"********** Closing BLIPTestListener after %i requests",_nReceived);
379 - (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
381 Log(@"***** %@ received a close request",connection);
385 - (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error
387 Log(@"***** %@'s close request failed: %@",connection,error);
394 TestCase(BLIPListener) {
395 EnableLogTo(BLIP,YES);
396 EnableLogTo(PortMapper,YES);
397 EnableLogTo(Bonjour,YES);
398 SecKeychainSetUserInteractionAllowed(true);
399 BLIPTestListener *listener = [[BLIPTestListener alloc] init];
401 [[NSRunLoop currentRunLoop] run];
411 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
413 Redistribution and use in source and binary forms, with or without modification, are permitted
414 provided that the following conditions are met:
416 * Redistributions of source code must retain the above copyright notice, this list of conditions
417 and the following disclaimer.
418 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
419 and the following disclaimer in the documentation and/or other materials provided with the
422 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
423 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
424 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
425 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
426 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
427 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
428 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
429 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.