OLD | NEW |
(Empty) | |
| 1 // |
| 2 // Copyright 2012 Square Inc. |
| 3 // |
| 4 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 // you may not use this file except in compliance with the License. |
| 6 // You may obtain a copy of the License at |
| 7 // |
| 8 // http://www.apache.org/licenses/LICENSE-2.0 |
| 9 // |
| 10 // Unless required by applicable law or agreed to in writing, software |
| 11 // distributed under the License is distributed on an "AS IS" BASIS, |
| 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 // See the License for the specific language governing permissions and |
| 14 // limitations under the License. |
| 15 // |
| 16 |
| 17 |
| 18 #import "SRWebSocket.h" |
| 19 |
| 20 #if TARGET_OS_IPHONE |
| 21 #define HAS_ICU |
| 22 #endif |
| 23 |
| 24 #ifdef HAS_ICU |
| 25 #import <unicode/utf8.h> |
| 26 #endif |
| 27 |
| 28 #if TARGET_OS_IPHONE |
| 29 #import <Endian.h> |
| 30 #else |
| 31 #import <CoreServices/CoreServices.h> |
| 32 #endif |
| 33 |
| 34 #import <CommonCrypto/CommonDigest.h> |
| 35 #import <Security/SecRandom.h> |
| 36 |
| 37 #if OS_OBJECT_USE_OBJC_RETAIN_RELEASE |
| 38 #define sr_dispatch_retain(x) |
| 39 #define sr_dispatch_release(x) |
| 40 #define maybe_bridge(x) ((__bridge void *) x) |
| 41 #else |
| 42 #define sr_dispatch_retain(x) dispatch_retain(x) |
| 43 #define sr_dispatch_release(x) dispatch_release(x) |
| 44 #define maybe_bridge(x) (x) |
| 45 #endif |
| 46 |
| 47 #if !__has_feature(objc_arc) |
| 48 #error SocketRocket must be compiled with ARC enabled |
| 49 #endif |
| 50 |
| 51 |
| 52 typedef enum { |
| 53 SROpCodeTextFrame = 0x1, |
| 54 SROpCodeBinaryFrame = 0x2, |
| 55 // 3-7 reserved. |
| 56 SROpCodeConnectionClose = 0x8, |
| 57 SROpCodePing = 0x9, |
| 58 SROpCodePong = 0xA, |
| 59 // B-F reserved. |
| 60 } SROpCode; |
| 61 |
| 62 typedef struct { |
| 63 BOOL fin; |
| 64 // BOOL rsv1; |
| 65 // BOOL rsv2; |
| 66 // BOOL rsv3; |
| 67 uint8_t opcode; |
| 68 BOOL masked; |
| 69 uint64_t payload_length; |
| 70 } frame_header; |
| 71 |
| 72 static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95
CA-C5AB0DC85B11"; |
| 73 |
| 74 static inline int32_t validate_dispatch_data_partial_string(NSData *data); |
| 75 static inline void SRFastLog(NSString *format, ...); |
| 76 |
| 77 @interface NSData (SRWebSocket) |
| 78 |
| 79 - (NSString *)stringBySHA1ThenBase64Encoding; |
| 80 |
| 81 @end |
| 82 |
| 83 |
| 84 @interface NSString (SRWebSocket) |
| 85 |
| 86 - (NSString *)stringBySHA1ThenBase64Encoding; |
| 87 |
| 88 @end |
| 89 |
| 90 |
| 91 @interface NSURL (SRWebSocket) |
| 92 |
| 93 // The origin isn't really applicable for a native application. |
| 94 // So instead, just map ws -> http and wss -> https. |
| 95 - (NSString *)SR_origin; |
| 96 |
| 97 @end |
| 98 |
| 99 |
| 100 @interface _SRRunLoopThread : NSThread |
| 101 |
| 102 @property (nonatomic, readonly) NSRunLoop *runLoop; |
| 103 |
| 104 @end |
| 105 |
| 106 |
| 107 static NSString *newSHA1String(const char *bytes, size_t length) { |
| 108 uint8_t md[CC_SHA1_DIGEST_LENGTH]; |
| 109 |
| 110 assert(length >= 0); |
| 111 assert(length <= UINT32_MAX); |
| 112 CC_SHA1(bytes, (CC_LONG)length, md); |
| 113 |
| 114 NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; |
| 115 |
| 116 if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { |
| 117 return [data base64EncodedStringWithOptions:0]; |
| 118 } |
| 119 |
| 120 return [data base64Encoding]; |
| 121 } |
| 122 |
| 123 @implementation NSData (SRWebSocket) |
| 124 |
| 125 - (NSString *)stringBySHA1ThenBase64Encoding; |
| 126 { |
| 127 return newSHA1String(self.bytes, self.length); |
| 128 } |
| 129 |
| 130 @end |
| 131 |
| 132 |
| 133 @implementation NSString (SRWebSocket) |
| 134 |
| 135 - (NSString *)stringBySHA1ThenBase64Encoding; |
| 136 { |
| 137 return newSHA1String(self.UTF8String, self.length); |
| 138 } |
| 139 |
| 140 @end |
| 141 |
| 142 NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; |
| 143 NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; |
| 144 |
| 145 // Returns number of bytes consumed. Returning 0 means you didn't match. |
| 146 // Sends bytes to callback handler; |
| 147 typedef size_t (^stream_scanner)(NSData *collected_data); |
| 148 |
| 149 typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); |
| 150 |
| 151 @interface SRIOConsumer : NSObject { |
| 152 stream_scanner _scanner; |
| 153 data_callback _handler; |
| 154 size_t _bytesNeeded; |
| 155 BOOL _readToCurrentFrame; |
| 156 BOOL _unmaskBytes; |
| 157 } |
| 158 @property (nonatomic, copy, readonly) stream_scanner consumer; |
| 159 @property (nonatomic, copy, readonly) data_callback handler; |
| 160 @property (nonatomic, assign) size_t bytesNeeded; |
| 161 @property (nonatomic, assign, readonly) BOOL readToCurrentFrame; |
| 162 @property (nonatomic, assign, readonly) BOOL unmaskBytes; |
| 163 |
| 164 @end |
| 165 |
| 166 // This class is not thread-safe, and is expected to always be run on the same q
ueue. |
| 167 @interface SRIOConsumerPool : NSObject |
| 168 |
| 169 - (id)initWithBufferCapacity:(NSUInteger)poolSize; |
| 170 |
| 171 - (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_call
back)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurr
entFrame unmaskBytes:(BOOL)unmaskBytes; |
| 172 - (void)returnConsumer:(SRIOConsumer *)consumer; |
| 173 |
| 174 @end |
| 175 |
| 176 @interface SRWebSocket () <NSStreamDelegate> |
| 177 |
| 178 - (void)_writeData:(NSData *)data; |
| 179 - (void)_closeWithProtocolError:(NSString *)message; |
| 180 - (void)_failWithError:(NSError *)error; |
| 181 |
| 182 - (void)_disconnect; |
| 183 |
| 184 - (void)_readFrameNew; |
| 185 - (void)_readFrameContinue; |
| 186 |
| 187 - (void)_pumpScanner; |
| 188 |
| 189 - (void)_pumpWriting; |
| 190 |
| 191 - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback
)callback; |
| 192 - (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)ca
llback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes
; |
| 193 - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback
)callback dataLength:(size_t)dataLength; |
| 194 - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data
_callback)dataHandler; |
| 195 - (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; |
| 196 |
| 197 - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; |
| 198 |
| 199 - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; |
| 200 - (void)_SR_commonInit; |
| 201 |
| 202 - (void)_initializeStreams; |
| 203 - (void)_connect; |
| 204 |
| 205 @property (nonatomic) SRReadyState readyState; |
| 206 |
| 207 @property (nonatomic) NSOperationQueue *delegateOperationQueue; |
| 208 @property (nonatomic) dispatch_queue_t delegateDispatchQueue; |
| 209 |
| 210 @end |
| 211 |
| 212 |
| 213 @implementation SRWebSocket { |
| 214 NSInteger _webSocketVersion; |
| 215 |
| 216 NSOperationQueue *_delegateOperationQueue; |
| 217 dispatch_queue_t _delegateDispatchQueue; |
| 218 |
| 219 dispatch_queue_t _workQueue; |
| 220 NSMutableArray *_consumers; |
| 221 |
| 222 NSInputStream *_inputStream; |
| 223 NSOutputStream *_outputStream; |
| 224 |
| 225 NSMutableData *_readBuffer; |
| 226 NSUInteger _readBufferOffset; |
| 227 |
| 228 NSMutableData *_outputBuffer; |
| 229 NSUInteger _outputBufferOffset; |
| 230 |
| 231 uint8_t _currentFrameOpcode; |
| 232 size_t _currentFrameCount; |
| 233 size_t _readOpCount; |
| 234 uint32_t _currentStringScanPosition; |
| 235 NSMutableData *_currentFrameData; |
| 236 |
| 237 NSString *_closeReason; |
| 238 |
| 239 NSString *_secKey; |
| 240 |
| 241 BOOL _pinnedCertFound; |
| 242 |
| 243 uint8_t _currentReadMaskKey[4]; |
| 244 size_t _currentReadMaskOffset; |
| 245 |
| 246 BOOL _consumerStopped; |
| 247 |
| 248 BOOL _closeWhenFinishedWriting; |
| 249 BOOL _failed; |
| 250 |
| 251 BOOL _secure; |
| 252 NSURLRequest *_urlRequest; |
| 253 |
| 254 CFHTTPMessageRef _receivedHTTPHeaders; |
| 255 |
| 256 BOOL _sentClose; |
| 257 BOOL _didFail; |
| 258 int _closeCode; |
| 259 |
| 260 BOOL _isPumping; |
| 261 |
| 262 NSMutableSet *_scheduledRunloops; |
| 263 |
| 264 // We use this to retain ourselves. |
| 265 __strong SRWebSocket *_selfRetain; |
| 266 |
| 267 NSArray *_requestedProtocols; |
| 268 SRIOConsumerPool *_consumerPool; |
| 269 } |
| 270 |
| 271 @synthesize delegate = _delegate; |
| 272 @synthesize url = _url; |
| 273 @synthesize readyState = _readyState; |
| 274 @synthesize protocol = _protocol; |
| 275 |
| 276 static __strong NSData *CRLFCRLF; |
| 277 |
| 278 + (void)initialize; |
| 279 { |
| 280 CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; |
| 281 } |
| 282 |
| 283 - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; |
| 284 { |
| 285 self = [super init]; |
| 286 if (self) { |
| 287 assert(request.URL); |
| 288 _url = request.URL; |
| 289 _urlRequest = request; |
| 290 |
| 291 _requestedProtocols = [protocols copy]; |
| 292 |
| 293 [self _SR_commonInit]; |
| 294 } |
| 295 |
| 296 return self; |
| 297 } |
| 298 |
| 299 - (id)initWithURLRequest:(NSURLRequest *)request; |
| 300 { |
| 301 return [self initWithURLRequest:request protocols:nil]; |
| 302 } |
| 303 |
| 304 - (id)initWithURL:(NSURL *)url; |
| 305 { |
| 306 return [self initWithURL:url protocols:nil]; |
| 307 } |
| 308 |
| 309 - (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; |
| 310 { |
| 311 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]
; |
| 312 return [self initWithURLRequest:request protocols:protocols]; |
| 313 } |
| 314 |
| 315 - (void)_SR_commonInit; |
| 316 { |
| 317 |
| 318 NSString *scheme = _url.scheme.lowercaseString; |
| 319 assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] ||
[scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); |
| 320 |
| 321 if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { |
| 322 _secure = YES; |
| 323 } |
| 324 |
| 325 _readyState = SR_CONNECTING; |
| 326 _consumerStopped = YES; |
| 327 _webSocketVersion = 13; |
| 328 |
| 329 _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); |
| 330 |
| 331 // Going to set a specific on the queue so we can validate we're on the work
queue |
| 332 dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(
_workQueue), NULL); |
| 333 |
| 334 _delegateDispatchQueue = dispatch_get_main_queue(); |
| 335 sr_dispatch_retain(_delegateDispatchQueue); |
| 336 |
| 337 _readBuffer = [[NSMutableData alloc] init]; |
| 338 _outputBuffer = [[NSMutableData alloc] init]; |
| 339 |
| 340 _currentFrameData = [[NSMutableData alloc] init]; |
| 341 |
| 342 _consumers = [[NSMutableArray alloc] init]; |
| 343 |
| 344 _consumerPool = [[SRIOConsumerPool alloc] init]; |
| 345 |
| 346 _scheduledRunloops = [[NSMutableSet alloc] init]; |
| 347 |
| 348 [self _initializeStreams]; |
| 349 |
| 350 // default handlers |
| 351 } |
| 352 |
| 353 - (void)assertOnWorkQueue; |
| 354 { |
| 355 assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQue
ue)); |
| 356 } |
| 357 |
| 358 - (void)dealloc |
| 359 { |
| 360 _inputStream.delegate = nil; |
| 361 _outputStream.delegate = nil; |
| 362 |
| 363 [_inputStream close]; |
| 364 [_outputStream close]; |
| 365 |
| 366 sr_dispatch_release(_workQueue); |
| 367 _workQueue = NULL; |
| 368 |
| 369 if (_receivedHTTPHeaders) { |
| 370 CFRelease(_receivedHTTPHeaders); |
| 371 _receivedHTTPHeaders = NULL; |
| 372 } |
| 373 |
| 374 if (_delegateDispatchQueue) { |
| 375 sr_dispatch_release(_delegateDispatchQueue); |
| 376 _delegateDispatchQueue = NULL; |
| 377 } |
| 378 } |
| 379 |
| 380 #ifndef NDEBUG |
| 381 |
| 382 - (void)setReadyState:(SRReadyState)aReadyState; |
| 383 { |
| 384 [self willChangeValueForKey:@"readyState"]; |
| 385 assert(aReadyState > _readyState); |
| 386 _readyState = aReadyState; |
| 387 [self didChangeValueForKey:@"readyState"]; |
| 388 } |
| 389 |
| 390 #endif |
| 391 |
| 392 - (void)open; |
| 393 { |
| 394 assert(_url); |
| 395 NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSoc
ket more than once"); |
| 396 |
| 397 _selfRetain = self; |
| 398 |
| 399 [self _connect]; |
| 400 } |
| 401 |
| 402 // Calls block on delegate queue |
| 403 - (void)_performDelegateBlock:(dispatch_block_t)block; |
| 404 { |
| 405 if (_delegateOperationQueue) { |
| 406 [_delegateOperationQueue addOperationWithBlock:block]; |
| 407 } else { |
| 408 assert(_delegateDispatchQueue); |
| 409 dispatch_async(_delegateDispatchQueue, block); |
| 410 } |
| 411 } |
| 412 |
| 413 - (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; |
| 414 { |
| 415 if (queue) { |
| 416 sr_dispatch_retain(queue); |
| 417 } |
| 418 |
| 419 if (_delegateDispatchQueue) { |
| 420 sr_dispatch_release(_delegateDispatchQueue); |
| 421 } |
| 422 |
| 423 _delegateDispatchQueue = queue; |
| 424 } |
| 425 |
| 426 - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; |
| 427 { |
| 428 NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue
(httpMessage, CFSTR("Sec-WebSocket-Accept"))); |
| 429 |
| 430 if (acceptHeader == nil) { |
| 431 return NO; |
| 432 } |
| 433 |
| 434 NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppe
ndToSecKeyString]; |
| 435 NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; |
| 436 |
| 437 return [acceptHeader isEqualToString:expectedAccept]; |
| 438 } |
| 439 |
| 440 - (void)_HTTPHeadersDidFinish; |
| 441 { |
| 442 NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHea
ders); |
| 443 |
| 444 if (responseCode >= 400) { |
| 445 SRFastLog(@"Request failed with response code %d", responseCode); |
| 446 [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain cod
e:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"receive
d bad response code from server %ld", (long)responseCode], SRHTTPResponseErrorKe
y:@(responseCode)}]]; |
| 447 return; |
| 448 } |
| 449 |
| 450 if(![self _checkHandshake:_receivedHTTPHeaders]) { |
| 451 [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain cod
e:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"
Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; |
| 452 return; |
| 453 } |
| 454 |
| 455 NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFiel
dValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); |
| 456 if (negotiatedProtocol) { |
| 457 // Make sure we requested the protocol |
| 458 if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound
) { |
| 459 [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain
code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithForma
t:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLoc
alizedDescriptionKey]]]; |
| 460 return; |
| 461 } |
| 462 |
| 463 _protocol = negotiatedProtocol; |
| 464 } |
| 465 |
| 466 self.readyState = SR_OPEN; |
| 467 |
| 468 if (!_didFail) { |
| 469 [self _readFrameNew]; |
| 470 } |
| 471 |
| 472 [self _performDelegateBlock:^{ |
| 473 if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { |
| 474 [self.delegate webSocketDidOpen:self]; |
| 475 }; |
| 476 }]; |
| 477 } |
| 478 |
| 479 |
| 480 - (void)_readHTTPHeader; |
| 481 { |
| 482 if (_receivedHTTPHeaders == NULL) { |
| 483 _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); |
| 484 } |
| 485 |
| 486 [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *dat
a) { |
| 487 CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes
, data.length); |
| 488 |
| 489 if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { |
| 490 SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMe
ssageCopyAllHeaderFields(_receivedHTTPHeaders))); |
| 491 [self _HTTPHeadersDidFinish]; |
| 492 } else { |
| 493 [self _readHTTPHeader]; |
| 494 } |
| 495 }]; |
| 496 } |
| 497 |
| 498 - (void)didConnect |
| 499 { |
| 500 SRFastLog(@"Connected"); |
| 501 CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (_
_bridge CFURLRef)_url, kCFHTTPVersion1_1); |
| 502 |
| 503 // Set host first so it defaults |
| 504 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringR
ef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _ur
l.host)); |
| 505 |
| 506 NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; |
| 507 SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes
); |
| 508 |
| 509 if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]
) { |
| 510 _secKey = [keyBytes base64EncodedStringWithOptions:0]; |
| 511 } else { |
| 512 _secKey = [keyBytes base64Encoding]; |
| 513 } |
| 514 |
| 515 assert([_secKey length] == 24); |
| 516 |
| 517 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket
")); |
| 518 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrad
e")); |
| 519 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bri
dge CFStringRef)_secKey); |
| 520 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (_
_bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion])
; |
| 521 |
| 522 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStrin
gRef)_url.SR_origin); |
| 523 |
| 524 if (_requestedProtocols) { |
| 525 CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"
), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); |
| 526 } |
| 527 |
| 528 [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key,
id obj, BOOL *stop) { |
| 529 CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__
bridge CFStringRef)obj); |
| 530 }]; |
| 531 |
| 532 NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(reque
st)); |
| 533 |
| 534 CFRelease(request); |
| 535 |
| 536 [self _writeData:message]; |
| 537 [self _readHTTPHeader]; |
| 538 } |
| 539 |
| 540 - (void)_initializeStreams; |
| 541 { |
| 542 assert(_url.port.unsignedIntValue <= UINT32_MAX); |
| 543 uint32_t port = _url.port.unsignedIntValue; |
| 544 if (port == 0) { |
| 545 if (!_secure) { |
| 546 port = 80; |
| 547 } else { |
| 548 port = 443; |
| 549 } |
| 550 } |
| 551 NSString *host = _url.host; |
| 552 |
| 553 CFReadStreamRef readStream = NULL; |
| 554 CFWriteStreamRef writeStream = NULL; |
| 555 |
| 556 CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &
readStream, &writeStream); |
| 557 |
| 558 _outputStream = CFBridgingRelease(writeStream); |
| 559 _inputStream = CFBridgingRelease(readStream); |
| 560 |
| 561 |
| 562 if (_secure) { |
| 563 NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; |
| 564 |
| 565 [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNego
tiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; |
| 566 |
| 567 // If we're using pinned certs, don't validate the certificate chain |
| 568 if ([_urlRequest SR_SSLPinnedCertificates].count) { |
| 569 [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge i
d)kCFStreamSSLValidatesCertificateChain]; |
| 570 } |
| 571 |
| 572 #if DEBUG |
| 573 [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kC
FStreamSSLValidatesCertificateChain]; |
| 574 NSLog(@"SocketRocket: In debug mode. Allowing connection to any root ce
rt"); |
| 575 #endif |
| 576 |
| 577 [_outputStream setProperty:SSLOptions |
| 578 forKey:(__bridge id)kCFStreamPropertySSLSettings]; |
| 579 } |
| 580 |
| 581 _inputStream.delegate = self; |
| 582 _outputStream.delegate = self; |
| 583 } |
| 584 |
| 585 - (void)_connect; |
| 586 { |
| 587 if (!_scheduledRunloops.count) { |
| 588 [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultR
unLoopMode]; |
| 589 } |
| 590 |
| 591 |
| 592 [_outputStream open]; |
| 593 [_inputStream open]; |
| 594 } |
| 595 |
| 596 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; |
| 597 { |
| 598 [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; |
| 599 [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; |
| 600 |
| 601 [_scheduledRunloops addObject:@[aRunLoop, mode]]; |
| 602 } |
| 603 |
| 604 - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; |
| 605 { |
| 606 [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; |
| 607 [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; |
| 608 |
| 609 [_scheduledRunloops removeObject:@[aRunLoop, mode]]; |
| 610 } |
| 611 |
| 612 - (void)close; |
| 613 { |
| 614 [self closeWithCode:SRStatusCodeNormal reason:nil]; |
| 615 } |
| 616 |
| 617 - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; |
| 618 { |
| 619 assert(code); |
| 620 dispatch_async(_workQueue, ^{ |
| 621 if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { |
| 622 return; |
| 623 } |
| 624 |
| 625 BOOL wasConnecting = self.readyState == SR_CONNECTING; |
| 626 |
| 627 self.readyState = SR_CLOSING; |
| 628 |
| 629 SRFastLog(@"Closing with code %d reason %@", code, reason); |
| 630 |
| 631 if (wasConnecting) { |
| 632 [self _disconnect]; |
| 633 return; |
| 634 } |
| 635 |
| 636 size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8Stri
ngEncoding]; |
| 637 NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:si
zeof(uint16_t) + maxMsgSize]; |
| 638 NSData *payload = mutablePayload; |
| 639 |
| 640 ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); |
| 641 |
| 642 if (reason) { |
| 643 NSRange remainingRange = {0}; |
| 644 |
| 645 NSUInteger usedLength = 0; |
| 646 |
| 647 BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes
+ sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedL
ength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRe
presentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]
; |
| 648 |
| 649 assert(success); |
| 650 assert(remainingRange.length == 0); |
| 651 |
| 652 if (usedLength != maxMsgSize) { |
| 653 payload = [payload subdataWithRange:NSMakeRange(0, usedLength +
sizeof(uint16_t))]; |
| 654 } |
| 655 } |
| 656 |
| 657 |
| 658 [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; |
| 659 }); |
| 660 } |
| 661 |
| 662 - (void)_closeWithProtocolError:(NSString *)message; |
| 663 { |
| 664 // Need to shunt this on the _callbackQueue first to see if they received an
y messages |
| 665 [self _performDelegateBlock:^{ |
| 666 [self closeWithCode:SRStatusCodeProtocolError reason:message]; |
| 667 dispatch_async(_workQueue, ^{ |
| 668 [self _disconnect]; |
| 669 }); |
| 670 }]; |
| 671 } |
| 672 |
| 673 - (void)_failWithError:(NSError *)error; |
| 674 { |
| 675 dispatch_async(_workQueue, ^{ |
| 676 if (self.readyState != SR_CLOSED) { |
| 677 _failed = YES; |
| 678 [self _performDelegateBlock:^{ |
| 679 if ([self.delegate respondsToSelector:@selector(webSocket:didFai
lWithError:)]) { |
| 680 [self.delegate webSocket:self didFailWithError:error]; |
| 681 } |
| 682 }]; |
| 683 |
| 684 self.readyState = SR_CLOSED; |
| 685 _selfRetain = nil; |
| 686 |
| 687 SRFastLog(@"Failing with error %@", error.localizedDescription); |
| 688 |
| 689 [self _disconnect]; |
| 690 } |
| 691 }); |
| 692 } |
| 693 |
| 694 - (void)_writeData:(NSData *)data; |
| 695 { |
| 696 [self assertOnWorkQueue]; |
| 697 |
| 698 if (_closeWhenFinishedWriting) { |
| 699 return; |
| 700 } |
| 701 [_outputBuffer appendData:data]; |
| 702 [self _pumpWriting]; |
| 703 } |
| 704 |
| 705 - (void)send:(id)data; |
| 706 { |
| 707 NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send
: until connection is open"); |
| 708 // TODO: maybe not copy this for performance |
| 709 data = [data copy]; |
| 710 dispatch_async(_workQueue, ^{ |
| 711 if ([data isKindOfClass:[NSString class]]) { |
| 712 [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data
dataUsingEncoding:NSUTF8StringEncoding]]; |
| 713 } else if ([data isKindOfClass:[NSData class]]) { |
| 714 [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; |
| 715 } else if (data == nil) { |
| 716 [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; |
| 717 } else { |
| 718 assert(NO); |
| 719 } |
| 720 }); |
| 721 } |
| 722 |
| 723 - (void)sendPing:(NSData *)data; |
| 724 { |
| 725 NSAssert(self.readyState == SR_OPEN, @"Invalid State: Cannot call send: unti
l connection is open"); |
| 726 // TODO: maybe not copy this for performance |
| 727 data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty |
| 728 dispatch_async(_workQueue, ^{ |
| 729 [self _sendFrameWithOpcode:SROpCodePing data:data]; |
| 730 }); |
| 731 } |
| 732 |
| 733 - (void)handlePing:(NSData *)pingData; |
| 734 { |
| 735 // Need to pingpong this off _callbackQueue first to make sure messages happ
en in order |
| 736 [self _performDelegateBlock:^{ |
| 737 dispatch_async(_workQueue, ^{ |
| 738 [self _sendFrameWithOpcode:SROpCodePong data:pingData]; |
| 739 }); |
| 740 }]; |
| 741 } |
| 742 |
| 743 - (void)handlePong:(NSData *)pongData; |
| 744 { |
| 745 SRFastLog(@"Received pong"); |
| 746 [self _performDelegateBlock:^{ |
| 747 if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong
:)]) { |
| 748 [self.delegate webSocket:self didReceivePong:pongData]; |
| 749 } |
| 750 }]; |
| 751 } |
| 752 |
| 753 - (void)_handleMessage:(id)message |
| 754 { |
| 755 SRFastLog(@"Received message"); |
| 756 [self _performDelegateBlock:^{ |
| 757 [self.delegate webSocket:self didReceiveMessage:message]; |
| 758 }]; |
| 759 } |
| 760 |
| 761 |
| 762 static inline BOOL closeCodeIsValid(int closeCode) { |
| 763 if (closeCode < 1000) { |
| 764 return NO; |
| 765 } |
| 766 |
| 767 if (closeCode >= 1000 && closeCode <= 1011) { |
| 768 if (closeCode == 1004 || |
| 769 closeCode == 1005 || |
| 770 closeCode == 1006) { |
| 771 return NO; |
| 772 } |
| 773 return YES; |
| 774 } |
| 775 |
| 776 if (closeCode >= 3000 && closeCode <= 3999) { |
| 777 return YES; |
| 778 } |
| 779 |
| 780 if (closeCode >= 4000 && closeCode <= 4999) { |
| 781 return YES; |
| 782 } |
| 783 |
| 784 return NO; |
| 785 } |
| 786 |
| 787 // Note from RFC: |
| 788 // |
| 789 // If there is a body, the first two |
| 790 // bytes of the body MUST be a 2-byte unsigned integer (in network byte |
| 791 // order) representing a status code with value /code/ defined in |
| 792 // Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 |
| 793 // encoded data with value /reason/, the interpretation of which is not |
| 794 // defined by this specification. |
| 795 |
| 796 - (void)handleCloseWithData:(NSData *)data; |
| 797 { |
| 798 size_t dataSize = data.length; |
| 799 __block uint16_t closeCode = 0; |
| 800 |
| 801 SRFastLog(@"Received close frame"); |
| 802 |
| 803 if (dataSize == 1) { |
| 804 // TODO handle error |
| 805 [self _closeWithProtocolError:@"Payload for close must be larger than 2
bytes"]; |
| 806 return; |
| 807 } else if (dataSize >= 2) { |
| 808 [data getBytes:&closeCode length:sizeof(closeCode)]; |
| 809 _closeCode = EndianU16_BtoN(closeCode); |
| 810 if (!closeCodeIsValid(_closeCode)) { |
| 811 [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot ha
ve close code of %d", _closeCode]]; |
| 812 return; |
| 813 } |
| 814 if (dataSize > 2) { |
| 815 _closeReason = [[NSString alloc] initWithData:[data subdataWithRange
:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; |
| 816 if (!_closeReason) { |
| 817 [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8
"]; |
| 818 return; |
| 819 } |
| 820 } |
| 821 } else { |
| 822 _closeCode = SRStatusNoStatusReceived; |
| 823 } |
| 824 |
| 825 [self assertOnWorkQueue]; |
| 826 |
| 827 if (self.readyState == SR_OPEN) { |
| 828 [self closeWithCode:1000 reason:nil]; |
| 829 } |
| 830 dispatch_async(_workQueue, ^{ |
| 831 [self _disconnect]; |
| 832 }); |
| 833 } |
| 834 |
| 835 - (void)_disconnect; |
| 836 { |
| 837 [self assertOnWorkQueue]; |
| 838 SRFastLog(@"Trying to disconnect"); |
| 839 _closeWhenFinishedWriting = YES; |
| 840 [self _pumpWriting]; |
| 841 } |
| 842 |
| 843 - (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; |
| 844 { |
| 845 // Check that the current data is valid UTF8 |
| 846 |
| 847 BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || o
pcode == SROpCodeConnectionClose); |
| 848 if (!isControlFrame) { |
| 849 [self _readFrameNew]; |
| 850 } else { |
| 851 dispatch_async(_workQueue, ^{ |
| 852 [self _readFrameContinue]; |
| 853 }); |
| 854 } |
| 855 |
| 856 switch (opcode) { |
| 857 case SROpCodeTextFrame: { |
| 858 NSString *str = [[NSString alloc] initWithData:frameData encoding:NS
UTF8StringEncoding]; |
| 859 if (str == nil && frameData) { |
| 860 [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames
must be valid UTF-8"]; |
| 861 dispatch_async(_workQueue, ^{ |
| 862 [self _disconnect]; |
| 863 }); |
| 864 |
| 865 return; |
| 866 } |
| 867 [self _handleMessage:str]; |
| 868 break; |
| 869 } |
| 870 case SROpCodeBinaryFrame: |
| 871 [self _handleMessage:[frameData copy]]; |
| 872 break; |
| 873 case SROpCodeConnectionClose: |
| 874 [self handleCloseWithData:frameData]; |
| 875 break; |
| 876 case SROpCodePing: |
| 877 [self handlePing:frameData]; |
| 878 break; |
| 879 case SROpCodePong: |
| 880 [self handlePong:frameData]; |
| 881 break; |
| 882 default: |
| 883 [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown o
pcode %ld", (long)opcode]]; |
| 884 // TODO: Handle invalid opcode |
| 885 break; |
| 886 } |
| 887 } |
| 888 |
| 889 - (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; |
| 890 { |
| 891 assert(frame_header.opcode != 0); |
| 892 |
| 893 if (self.readyState != SR_OPEN) { |
| 894 return; |
| 895 } |
| 896 |
| 897 |
| 898 BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.o
pcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); |
| 899 |
| 900 if (isControlFrame && !frame_header.fin) { |
| 901 [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; |
| 902 return; |
| 903 } |
| 904 |
| 905 if (isControlFrame && frame_header.payload_length >= 126) { |
| 906 [self _closeWithProtocolError:@"Control frames cannot have payloads larg
er than 126 bytes"]; |
| 907 return; |
| 908 } |
| 909 |
| 910 if (!isControlFrame) { |
| 911 _currentFrameOpcode = frame_header.opcode; |
| 912 _currentFrameCount += 1; |
| 913 } |
| 914 |
| 915 if (frame_header.payload_length == 0) { |
| 916 if (isControlFrame) { |
| 917 [self _handleFrameWithData:curData opCode:frame_header.opcode]; |
| 918 } else { |
| 919 if (frame_header.fin) { |
| 920 [self _handleFrameWithData:_currentFrameData opCode:frame_header
.opcode]; |
| 921 } else { |
| 922 // TODO add assert that opcode is not a control; |
| 923 [self _readFrameContinue]; |
| 924 } |
| 925 } |
| 926 } else { |
| 927 assert(frame_header.payload_length <= SIZE_T_MAX); |
| 928 [self _addConsumerWithDataLength:(size_t)frame_header.payload_length cal
lback:^(SRWebSocket *self, NSData *newData) { |
| 929 if (isControlFrame) { |
| 930 [self _handleFrameWithData:newData opCode:frame_header.opcode]; |
| 931 } else { |
| 932 if (frame_header.fin) { |
| 933 [self _handleFrameWithData:self->_currentFrameData opCode:fr
ame_header.opcode]; |
| 934 } else { |
| 935 // TODO add assert that opcode is not a control; |
| 936 [self _readFrameContinue]; |
| 937 } |
| 938 |
| 939 } |
| 940 } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; |
| 941 } |
| 942 } |
| 943 |
| 944 /* From RFC: |
| 945 |
| 946 0 1 2 3 |
| 947 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 948 +-+-+-+-+-------+-+-------------+-------------------------------+ |
| 949 |F|R|R|R| opcode|M| Payload len | Extended payload length | |
| 950 |I|S|S|S| (4) |A| (7) | (16/64) | |
| 951 |N|V|V|V| |S| | (if payload len==126/127) | |
| 952 | |1|2|3| |K| | | |
| 953 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + |
| 954 | Extended payload length continued, if payload len == 127 | |
| 955 + - - - - - - - - - - - - - - - +-------------------------------+ |
| 956 | |Masking-key, if MASK set to 1 | |
| 957 +-------------------------------+-------------------------------+ |
| 958 | Masking-key (continued) | Payload Data | |
| 959 +-------------------------------- - - - - - - - - - - - - - - - + |
| 960 : Payload Data continued ... : |
| 961 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
| 962 | Payload Data continued ... | |
| 963 +---------------------------------------------------------------+ |
| 964 */ |
| 965 |
| 966 static const uint8_t SRFinMask = 0x80; |
| 967 static const uint8_t SROpCodeMask = 0x0F; |
| 968 static const uint8_t SRRsvMask = 0x70; |
| 969 static const uint8_t SRMaskMask = 0x80; |
| 970 static const uint8_t SRPayloadLenMask = 0x7F; |
| 971 |
| 972 |
| 973 - (void)_readFrameContinue; |
| 974 { |
| 975 assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFra
meCount > 0 && _currentFrameOpcode > 0)); |
| 976 |
| 977 [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *dat
a) { |
| 978 __block frame_header header = {0}; |
| 979 |
| 980 const uint8_t *headerBuffer = data.bytes; |
| 981 assert(data.length >= 2); |
| 982 |
| 983 if (headerBuffer[0] & SRRsvMask) { |
| 984 [self _closeWithProtocolError:@"Server used RSV bits"]; |
| 985 return; |
| 986 } |
| 987 |
| 988 uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); |
| 989 |
| 990 BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode
== SROpCodePong || receivedOpcode == SROpCodeConnectionClose); |
| 991 |
| 992 if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount >
0) { |
| 993 [self _closeWithProtocolError:@"all data frames after the initial da
ta frame must have opcode 0"]; |
| 994 return; |
| 995 } |
| 996 |
| 997 if (receivedOpcode == 0 && self->_currentFrameCount == 0) { |
| 998 [self _closeWithProtocolError:@"cannot continue a message"]; |
| 999 return; |
| 1000 } |
| 1001 |
| 1002 header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receiv
edOpcode; |
| 1003 |
| 1004 header.fin = !!(SRFinMask & headerBuffer[0]); |
| 1005 |
| 1006 |
| 1007 header.masked = !!(SRMaskMask & headerBuffer[1]); |
| 1008 header.payload_length = SRPayloadLenMask & headerBuffer[1]; |
| 1009 |
| 1010 headerBuffer = NULL; |
| 1011 |
| 1012 if (header.masked) { |
| 1013 [self _closeWithProtocolError:@"Client must receive unmasked data"]; |
| 1014 } |
| 1015 |
| 1016 size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey)
: 0; |
| 1017 |
| 1018 if (header.payload_length == 126) { |
| 1019 extra_bytes_needed += sizeof(uint16_t); |
| 1020 } else if (header.payload_length == 127) { |
| 1021 extra_bytes_needed += sizeof(uint64_t); |
| 1022 } |
| 1023 |
| 1024 if (extra_bytes_needed == 0) { |
| 1025 [self _handleFrameHeader:header curData:self->_currentFrameData]; |
| 1026 } else { |
| 1027 [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWeb
Socket *self, NSData *data) { |
| 1028 size_t mapped_size = data.length; |
| 1029 const void *mapped_buffer = data.bytes; |
| 1030 size_t offset = 0; |
| 1031 |
| 1032 if (header.payload_length == 126) { |
| 1033 assert(mapped_size >= sizeof(uint16_t)); |
| 1034 uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer
)); |
| 1035 header.payload_length = newLen; |
| 1036 offset += sizeof(uint16_t); |
| 1037 } else if (header.payload_length == 127) { |
| 1038 assert(mapped_size >= sizeof(uint64_t)); |
| 1039 header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_
buffer)); |
| 1040 offset += sizeof(uint64_t); |
| 1041 } else { |
| 1042 assert(header.payload_length < 126 && header.payload_length
>= 0); |
| 1043 } |
| 1044 |
| 1045 |
| 1046 if (header.masked) { |
| 1047 assert(mapped_size >= sizeof(_currentReadMaskOffset) + offse
t); |
| 1048 memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer)
+ offset, sizeof(self->_currentReadMaskKey)); |
| 1049 } |
| 1050 |
| 1051 [self _handleFrameHeader:header curData:self->_currentFrameData]
; |
| 1052 } readToCurrentFrame:NO unmaskBytes:NO]; |
| 1053 } |
| 1054 } readToCurrentFrame:NO unmaskBytes:NO]; |
| 1055 } |
| 1056 |
| 1057 - (void)_readFrameNew; |
| 1058 { |
| 1059 dispatch_async(_workQueue, ^{ |
| 1060 [_currentFrameData setLength:0]; |
| 1061 |
| 1062 _currentFrameOpcode = 0; |
| 1063 _currentFrameCount = 0; |
| 1064 _readOpCount = 0; |
| 1065 _currentStringScanPosition = 0; |
| 1066 |
| 1067 [self _readFrameContinue]; |
| 1068 }); |
| 1069 } |
| 1070 |
| 1071 - (void)_pumpWriting; |
| 1072 { |
| 1073 [self assertOnWorkQueue]; |
| 1074 |
| 1075 NSUInteger dataLength = _outputBuffer.length; |
| 1076 if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable)
{ |
| 1077 NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _out
putBufferOffset maxLength:dataLength - _outputBufferOffset]; |
| 1078 if (bytesWritten == -1) { |
| 1079 [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain
code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream
" forKey:NSLocalizedDescriptionKey]]]; |
| 1080 return; |
| 1081 } |
| 1082 |
| 1083 _outputBufferOffset += bytesWritten; |
| 1084 |
| 1085 if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.l
ength >> 1)) { |
| 1086 _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_output
Buffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOf
fset]; |
| 1087 _outputBufferOffset = 0; |
| 1088 } |
| 1089 } |
| 1090 |
| 1091 if (_closeWhenFinishedWriting && |
| 1092 _outputBuffer.length - _outputBufferOffset == 0 && |
| 1093 (_inputStream.streamStatus != NSStreamStatusNotOpen && |
| 1094 _inputStream.streamStatus != NSStreamStatusClosed) && |
| 1095 !_sentClose) { |
| 1096 _sentClose = YES; |
| 1097 |
| 1098 [_outputStream close]; |
| 1099 [_inputStream close]; |
| 1100 |
| 1101 |
| 1102 for (NSArray *runLoop in [_scheduledRunloops copy]) { |
| 1103 [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLo
op objectAtIndex:1]]; |
| 1104 } |
| 1105 |
| 1106 if (!_failed) { |
| 1107 [self _performDelegateBlock:^{ |
| 1108 if ([self.delegate respondsToSelector:@selector(webSocket:didClo
seWithCode:reason:wasClean:)]) { |
| 1109 [self.delegate webSocket:self didCloseWithCode:_closeCode re
ason:_closeReason wasClean:YES]; |
| 1110 } |
| 1111 }]; |
| 1112 } |
| 1113 |
| 1114 _selfRetain = nil; |
| 1115 } |
| 1116 } |
| 1117 |
| 1118 - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback
)callback; |
| 1119 { |
| 1120 [self assertOnWorkQueue]; |
| 1121 [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; |
| 1122 } |
| 1123 |
| 1124 - (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)ca
llback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes
; |
| 1125 { |
| 1126 [self assertOnWorkQueue]; |
| 1127 assert(dataLength); |
| 1128 |
| 1129 [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callbac
k bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmas
kBytes]]; |
| 1130 [self _pumpScanner]; |
| 1131 } |
| 1132 |
| 1133 - (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback
)callback dataLength:(size_t)dataLength; |
| 1134 { |
| 1135 [self assertOnWorkQueue]; |
| 1136 [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:ca
llback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; |
| 1137 [self _pumpScanner]; |
| 1138 } |
| 1139 |
| 1140 |
| 1141 static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; |
| 1142 |
| 1143 - (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; |
| 1144 { |
| 1145 [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:da
taHandler]; |
| 1146 } |
| 1147 |
| 1148 - (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data
_callback)dataHandler; |
| 1149 { |
| 1150 // TODO optimize so this can continue from where we last searched |
| 1151 stream_scanner consumer = ^size_t(NSData *data) { |
| 1152 __block size_t found_size = 0; |
| 1153 __block size_t match_count = 0; |
| 1154 |
| 1155 size_t size = data.length; |
| 1156 const unsigned char *buffer = data.bytes; |
| 1157 for (size_t i = 0; i < size; i++ ) { |
| 1158 if (((const unsigned char *)buffer)[i] == ((const unsigned char *)by
tes)[match_count]) { |
| 1159 match_count += 1; |
| 1160 if (match_count == length) { |
| 1161 found_size = i + 1; |
| 1162 break; |
| 1163 } |
| 1164 } else { |
| 1165 match_count = 0; |
| 1166 } |
| 1167 } |
| 1168 return found_size; |
| 1169 }; |
| 1170 [self _addConsumerWithScanner:consumer callback:dataHandler]; |
| 1171 } |
| 1172 |
| 1173 |
| 1174 // Returns true if did work |
| 1175 - (BOOL)_innerPumpScanner { |
| 1176 |
| 1177 BOOL didWork = NO; |
| 1178 |
| 1179 if (self.readyState >= SR_CLOSING) { |
| 1180 return didWork; |
| 1181 } |
| 1182 |
| 1183 if (!_consumers.count) { |
| 1184 return didWork; |
| 1185 } |
| 1186 |
| 1187 size_t curSize = _readBuffer.length - _readBufferOffset; |
| 1188 if (!curSize) { |
| 1189 return didWork; |
| 1190 } |
| 1191 |
| 1192 SRIOConsumer *consumer = [_consumers objectAtIndex:0]; |
| 1193 |
| 1194 size_t bytesNeeded = consumer.bytesNeeded; |
| 1195 |
| 1196 size_t foundSize = 0; |
| 1197 if (consumer.consumer) { |
| 1198 NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes
+ _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:
NO]; |
| 1199 foundSize = consumer.consumer(tempView); |
| 1200 } else { |
| 1201 assert(consumer.bytesNeeded); |
| 1202 if (curSize >= bytesNeeded) { |
| 1203 foundSize = bytesNeeded; |
| 1204 } else if (consumer.readToCurrentFrame) { |
| 1205 foundSize = curSize; |
| 1206 } |
| 1207 } |
| 1208 |
| 1209 NSData *slice = nil; |
| 1210 if (consumer.readToCurrentFrame || foundSize) { |
| 1211 NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); |
| 1212 slice = [_readBuffer subdataWithRange:sliceRange]; |
| 1213 |
| 1214 _readBufferOffset += foundSize; |
| 1215 |
| 1216 if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length
>> 1)) { |
| 1217 _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuff
er.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset];
_readBufferOffset = 0; |
| 1218 } |
| 1219 |
| 1220 if (consumer.unmaskBytes) { |
| 1221 NSMutableData *mutableSlice = [slice mutableCopy]; |
| 1222 |
| 1223 NSUInteger len = mutableSlice.length; |
| 1224 uint8_t *bytes = mutableSlice.mutableBytes; |
| 1225 |
| 1226 for (NSUInteger i = 0; i < len; i++) { |
| 1227 bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset
% sizeof(_currentReadMaskKey)]; |
| 1228 _currentReadMaskOffset += 1; |
| 1229 } |
| 1230 |
| 1231 slice = mutableSlice; |
| 1232 } |
| 1233 |
| 1234 if (consumer.readToCurrentFrame) { |
| 1235 [_currentFrameData appendData:slice]; |
| 1236 |
| 1237 _readOpCount += 1; |
| 1238 |
| 1239 if (_currentFrameOpcode == SROpCodeTextFrame) { |
| 1240 // Validate UTF8 stuff. |
| 1241 size_t currentDataSize = _currentFrameData.length; |
| 1242 if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize
> 0) { |
| 1243 // TODO: Optimize the crap out of this. Don't really have t
o copy all the data each time |
| 1244 |
| 1245 size_t scanSize = currentDataSize - _currentStringScanPositi
on; |
| 1246 |
| 1247 NSData *scan_data = [_currentFrameData subdataWithRange:NSMa
keRange(_currentStringScanPosition, scanSize)]; |
| 1248 int32_t valid_utf8_size = validate_dispatch_data_partial_str
ing(scan_data); |
| 1249 |
| 1250 if (valid_utf8_size == -1) { |
| 1251 [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Tex
t frames must be valid UTF-8"]; |
| 1252 dispatch_async(_workQueue, ^{ |
| 1253 [self _disconnect]; |
| 1254 }); |
| 1255 return didWork; |
| 1256 } else { |
| 1257 _currentStringScanPosition += valid_utf8_size; |
| 1258 } |
| 1259 } |
| 1260 |
| 1261 } |
| 1262 |
| 1263 consumer.bytesNeeded -= foundSize; |
| 1264 |
| 1265 if (consumer.bytesNeeded == 0) { |
| 1266 [_consumers removeObjectAtIndex:0]; |
| 1267 consumer.handler(self, nil); |
| 1268 [_consumerPool returnConsumer:consumer]; |
| 1269 didWork = YES; |
| 1270 } |
| 1271 } else if (foundSize) { |
| 1272 [_consumers removeObjectAtIndex:0]; |
| 1273 consumer.handler(self, slice); |
| 1274 [_consumerPool returnConsumer:consumer]; |
| 1275 didWork = YES; |
| 1276 } |
| 1277 } |
| 1278 return didWork; |
| 1279 } |
| 1280 |
| 1281 -(void)_pumpScanner; |
| 1282 { |
| 1283 [self assertOnWorkQueue]; |
| 1284 |
| 1285 if (!_isPumping) { |
| 1286 _isPumping = YES; |
| 1287 } else { |
| 1288 return; |
| 1289 } |
| 1290 |
| 1291 while ([self _innerPumpScanner]) { |
| 1292 |
| 1293 } |
| 1294 |
| 1295 _isPumping = NO; |
| 1296 } |
| 1297 |
| 1298 //#define NOMASK |
| 1299 |
| 1300 static const size_t SRFrameHeaderOverhead = 32; |
| 1301 |
| 1302 - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; |
| 1303 { |
| 1304 [self assertOnWorkQueue]; |
| 1305 |
| 1306 if (nil == data) { |
| 1307 return; |
| 1308 } |
| 1309 |
| 1310 NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSStrin
g class]], @"NSString or NSData"); |
| 1311 |
| 1312 size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)
data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; |
| 1313 |
| 1314 NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength +
SRFrameHeaderOverhead]; |
| 1315 if (!frame) { |
| 1316 [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]
; |
| 1317 return; |
| 1318 } |
| 1319 uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; |
| 1320 |
| 1321 // set fin |
| 1322 frame_buffer[0] = SRFinMask | opcode; |
| 1323 |
| 1324 BOOL useMask = YES; |
| 1325 #ifdef NOMASK |
| 1326 useMask = NO; |
| 1327 #endif |
| 1328 |
| 1329 if (useMask) { |
| 1330 // set the mask and header |
| 1331 frame_buffer[1] |= SRMaskMask; |
| 1332 } |
| 1333 |
| 1334 size_t frame_buffer_size = 2; |
| 1335 |
| 1336 const uint8_t *unmasked_payload = NULL; |
| 1337 if ([data isKindOfClass:[NSData class]]) { |
| 1338 unmasked_payload = (uint8_t *)[data bytes]; |
| 1339 } else if ([data isKindOfClass:[NSString class]]) { |
| 1340 unmasked_payload = (const uint8_t *)[data UTF8String]; |
| 1341 } else { |
| 1342 return; |
| 1343 } |
| 1344 |
| 1345 if (payloadLength < 126) { |
| 1346 frame_buffer[1] |= payloadLength; |
| 1347 } else if (payloadLength <= UINT16_MAX) { |
| 1348 frame_buffer[1] |= 126; |
| 1349 *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint
16_t)payloadLength); |
| 1350 frame_buffer_size += sizeof(uint16_t); |
| 1351 } else { |
| 1352 frame_buffer[1] |= 127; |
| 1353 *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint
64_t)payloadLength); |
| 1354 frame_buffer_size += sizeof(uint64_t); |
| 1355 } |
| 1356 |
| 1357 if (!useMask) { |
| 1358 for (size_t i = 0; i < payloadLength; i++) { |
| 1359 frame_buffer[frame_buffer_size] = unmasked_payload[i]; |
| 1360 frame_buffer_size += 1; |
| 1361 } |
| 1362 } else { |
| 1363 uint8_t *mask_key = frame_buffer + frame_buffer_size; |
| 1364 SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_
key); |
| 1365 frame_buffer_size += sizeof(uint32_t); |
| 1366 |
| 1367 // TODO: could probably optimize this with SIMD |
| 1368 for (size_t i = 0; i < payloadLength; i++) { |
| 1369 frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i %
sizeof(uint32_t)]; |
| 1370 frame_buffer_size += 1; |
| 1371 } |
| 1372 } |
| 1373 |
| 1374 assert(frame_buffer_size <= [frame length]); |
| 1375 frame.length = frame_buffer_size; |
| 1376 |
| 1377 [self _writeData:frame]; |
| 1378 } |
| 1379 |
| 1380 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; |
| 1381 { |
| 1382 if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvail
able || eventCode == NSStreamEventHasSpaceAvailable)) { |
| 1383 |
| 1384 NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; |
| 1385 if (sslCerts) { |
| 1386 SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey
:(__bridge id)kCFStreamPropertySSLPeerTrust]; |
| 1387 if (secTrust) { |
| 1388 NSInteger numCerts = SecTrustGetCertificateCount(secTrust); |
| 1389 for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { |
| 1390 SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTr
ust, i); |
| 1391 NSData *certData = CFBridgingRelease(SecCertificateCopyData(
cert)); |
| 1392 |
| 1393 for (id ref in sslCerts) { |
| 1394 SecCertificateRef trustedCert = (__bridge SecCertificate
Ref)ref; |
| 1395 NSData *trustedCertData = CFBridgingRelease(SecCertifica
teCopyData(trustedCert)); |
| 1396 |
| 1397 if ([trustedCertData isEqualToData:certData]) { |
| 1398 _pinnedCertFound = YES; |
| 1399 break; |
| 1400 } |
| 1401 } |
| 1402 } |
| 1403 } |
| 1404 |
| 1405 if (!_pinnedCertFound) { |
| 1406 dispatch_async(_workQueue, ^{ |
| 1407 [self _failWithError:[NSError errorWithDomain:SRWebSocketErr
orDomain code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString string
WithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; |
| 1408 }); |
| 1409 return; |
| 1410 } |
| 1411 } |
| 1412 } |
| 1413 |
| 1414 dispatch_async(_workQueue, ^{ |
| 1415 switch (eventCode) { |
| 1416 case NSStreamEventOpenCompleted: { |
| 1417 SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); |
| 1418 if (self.readyState >= SR_CLOSING) { |
| 1419 return; |
| 1420 } |
| 1421 assert(_readBuffer); |
| 1422 |
| 1423 if (self.readyState == SR_CONNECTING && aStream == _inputStream)
{ |
| 1424 [self didConnect]; |
| 1425 } |
| 1426 [self _pumpWriting]; |
| 1427 [self _pumpScanner]; |
| 1428 break; |
| 1429 } |
| 1430 |
| 1431 case NSStreamEventErrorOccurred: { |
| 1432 SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStrea
m streamError] copy]); |
| 1433 /// TODO specify error better! |
| 1434 [self _failWithError:aStream.streamError]; |
| 1435 _readBufferOffset = 0; |
| 1436 [_readBuffer setLength:0]; |
| 1437 break; |
| 1438 |
| 1439 } |
| 1440 |
| 1441 case NSStreamEventEndEncountered: { |
| 1442 [self _pumpScanner]; |
| 1443 SRFastLog(@"NSStreamEventEndEncountered %@", aStream); |
| 1444 if (aStream.streamError) { |
| 1445 [self _failWithError:aStream.streamError]; |
| 1446 } else { |
| 1447 if (self.readyState != SR_CLOSED) { |
| 1448 self.readyState = SR_CLOSED; |
| 1449 _selfRetain = nil; |
| 1450 } |
| 1451 |
| 1452 if (!_sentClose && !_failed) { |
| 1453 _sentClose = YES; |
| 1454 // If we get closed in this state it's probably not clea
n because we should be sending this when we send messages |
| 1455 [self _performDelegateBlock:^{ |
| 1456 if ([self.delegate respondsToSelector:@selector(webS
ocket:didCloseWithCode:reason:wasClean:)]) { |
| 1457 [self.delegate webSocket:self didCloseWithCode:S
RStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO]; |
| 1458 } |
| 1459 }]; |
| 1460 } |
| 1461 } |
| 1462 |
| 1463 break; |
| 1464 } |
| 1465 |
| 1466 case NSStreamEventHasBytesAvailable: { |
| 1467 SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); |
| 1468 const int bufferSize = 2048; |
| 1469 uint8_t buffer[bufferSize]; |
| 1470 |
| 1471 while (_inputStream.hasBytesAvailable) { |
| 1472 NSInteger bytes_read = [_inputStream read:buffer maxLength:b
ufferSize]; |
| 1473 |
| 1474 if (bytes_read > 0) { |
| 1475 [_readBuffer appendBytes:buffer length:bytes_read]; |
| 1476 } else if (bytes_read < 0) { |
| 1477 [self _failWithError:_inputStream.streamError]; |
| 1478 } |
| 1479 |
| 1480 if (bytes_read != bufferSize) { |
| 1481 break; |
| 1482 } |
| 1483 }; |
| 1484 [self _pumpScanner]; |
| 1485 break; |
| 1486 } |
| 1487 |
| 1488 case NSStreamEventHasSpaceAvailable: { |
| 1489 SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); |
| 1490 [self _pumpWriting]; |
| 1491 break; |
| 1492 } |
| 1493 |
| 1494 default: |
| 1495 SRFastLog(@"(default) %@", aStream); |
| 1496 break; |
| 1497 } |
| 1498 }); |
| 1499 } |
| 1500 |
| 1501 @end |
| 1502 |
| 1503 |
| 1504 @implementation SRIOConsumer |
| 1505 |
| 1506 @synthesize bytesNeeded = _bytesNeeded; |
| 1507 @synthesize consumer = _scanner; |
| 1508 @synthesize handler = _handler; |
| 1509 @synthesize readToCurrentFrame = _readToCurrentFrame; |
| 1510 @synthesize unmaskBytes = _unmaskBytes; |
| 1511 |
| 1512 - (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler
bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unma
skBytes:(BOOL)unmaskBytes; |
| 1513 { |
| 1514 _scanner = [scanner copy]; |
| 1515 _handler = [handler copy]; |
| 1516 _bytesNeeded = bytesNeeded; |
| 1517 _readToCurrentFrame = readToCurrentFrame; |
| 1518 _unmaskBytes = unmaskBytes; |
| 1519 assert(_scanner || _bytesNeeded); |
| 1520 } |
| 1521 |
| 1522 |
| 1523 @end |
| 1524 |
| 1525 |
| 1526 @implementation SRIOConsumerPool { |
| 1527 NSUInteger _poolSize; |
| 1528 NSMutableArray *_bufferedConsumers; |
| 1529 } |
| 1530 |
| 1531 - (id)initWithBufferCapacity:(NSUInteger)poolSize; |
| 1532 { |
| 1533 self = [super init]; |
| 1534 if (self) { |
| 1535 _poolSize = poolSize; |
| 1536 _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; |
| 1537 } |
| 1538 return self; |
| 1539 } |
| 1540 |
| 1541 - (id)init |
| 1542 { |
| 1543 return [self initWithBufferCapacity:8]; |
| 1544 } |
| 1545 |
| 1546 - (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_call
back)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurr
entFrame unmaskBytes:(BOOL)unmaskBytes; |
| 1547 { |
| 1548 SRIOConsumer *consumer = nil; |
| 1549 if (_bufferedConsumers.count) { |
| 1550 consumer = [_bufferedConsumers lastObject]; |
| 1551 [_bufferedConsumers removeLastObject]; |
| 1552 } else { |
| 1553 consumer = [[SRIOConsumer alloc] init]; |
| 1554 } |
| 1555 |
| 1556 [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded r
eadToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; |
| 1557 |
| 1558 return consumer; |
| 1559 } |
| 1560 |
| 1561 - (void)returnConsumer:(SRIOConsumer *)consumer; |
| 1562 { |
| 1563 if (_bufferedConsumers.count < _poolSize) { |
| 1564 [_bufferedConsumers addObject:consumer]; |
| 1565 } |
| 1566 } |
| 1567 |
| 1568 @end |
| 1569 |
| 1570 |
| 1571 @implementation NSURLRequest (CertificateAdditions) |
| 1572 |
| 1573 - (NSArray *)SR_SSLPinnedCertificates; |
| 1574 { |
| 1575 return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:s
elf]; |
| 1576 } |
| 1577 |
| 1578 @end |
| 1579 |
| 1580 @implementation NSMutableURLRequest (CertificateAdditions) |
| 1581 |
| 1582 - (NSArray *)SR_SSLPinnedCertificates; |
| 1583 { |
| 1584 return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:s
elf]; |
| 1585 } |
| 1586 |
| 1587 - (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; |
| 1588 { |
| 1589 [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCer
tificates" inRequest:self]; |
| 1590 } |
| 1591 |
| 1592 @end |
| 1593 |
| 1594 @implementation NSURL (SRWebSocket) |
| 1595 |
| 1596 - (NSString *)SR_origin; |
| 1597 { |
| 1598 NSString *scheme = [self.scheme lowercaseString]; |
| 1599 |
| 1600 if ([scheme isEqualToString:@"wss"]) { |
| 1601 scheme = @"https"; |
| 1602 } else if ([scheme isEqualToString:@"ws"]) { |
| 1603 scheme = @"http"; |
| 1604 } |
| 1605 |
| 1606 if (self.port) { |
| 1607 return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, sel
f.port]; |
| 1608 } else { |
| 1609 return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; |
| 1610 } |
| 1611 } |
| 1612 |
| 1613 @end |
| 1614 |
| 1615 //#define SR_ENABLE_LOG |
| 1616 |
| 1617 static inline void SRFastLog(NSString *format, ...) { |
| 1618 #ifdef SR_ENABLE_LOG |
| 1619 __block va_list arg_list; |
| 1620 va_start (arg_list, format); |
| 1621 |
| 1622 NSString *formattedString = [[NSString alloc] initWithFormat:format argument
s:arg_list]; |
| 1623 |
| 1624 va_end(arg_list); |
| 1625 |
| 1626 NSLog(@"[SR] %@", formattedString); |
| 1627 #endif |
| 1628 } |
| 1629 |
| 1630 |
| 1631 #ifdef HAS_ICU |
| 1632 |
| 1633 static inline int32_t validate_dispatch_data_partial_string(NSData *data) { |
| 1634 if ([data length] > INT32_MAX) { |
| 1635 // INT32_MAX is the limit so long as this Framework is using 32 bit ints
everywhere. |
| 1636 return -1; |
| 1637 } |
| 1638 |
| 1639 int32_t size = (int32_t)[data length]; |
| 1640 |
| 1641 const void * contents = [data bytes]; |
| 1642 const uint8_t *str = (const uint8_t *)contents; |
| 1643 |
| 1644 UChar32 codepoint = 1; |
| 1645 int32_t offset = 0; |
| 1646 int32_t lastOffset = 0; |
| 1647 while(offset < size && codepoint > 0) { |
| 1648 lastOffset = offset; |
| 1649 U8_NEXT(str, offset, size, codepoint); |
| 1650 } |
| 1651 |
| 1652 if (codepoint == -1) { |
| 1653 // Check to see if the last byte is valid or whether it was just continu
ing |
| 1654 if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]
) + lastOffset < (int32_t)size) { |
| 1655 |
| 1656 size = -1; |
| 1657 } else { |
| 1658 uint8_t leadByte = str[lastOffset]; |
| 1659 U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); |
| 1660 |
| 1661 for (int i = lastOffset + 1; i < offset; i++) { |
| 1662 if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(s
tr[i])) { |
| 1663 size = -1; |
| 1664 } |
| 1665 } |
| 1666 |
| 1667 if (size != -1) { |
| 1668 size = lastOffset; |
| 1669 } |
| 1670 } |
| 1671 } |
| 1672 |
| 1673 if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes
] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { |
| 1674 size = -1; |
| 1675 } |
| 1676 |
| 1677 return size; |
| 1678 } |
| 1679 |
| 1680 #else |
| 1681 |
| 1682 // This is a hack, and probably not optimal |
| 1683 static inline int32_t validate_dispatch_data_partial_string(NSData *data) { |
| 1684 static const int maxCodepointSize = 3; |
| 1685 |
| 1686 for (int i = 0; i < maxCodepointSize; i++) { |
| 1687 NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes
length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; |
| 1688 if (str) { |
| 1689 return data.length - i; |
| 1690 } |
| 1691 } |
| 1692 |
| 1693 return -1; |
| 1694 } |
| 1695 |
| 1696 #endif |
| 1697 |
| 1698 static _SRRunLoopThread *networkThread = nil; |
| 1699 static NSRunLoop *networkRunLoop = nil; |
| 1700 |
| 1701 @implementation NSRunLoop (SRWebSocket) |
| 1702 |
| 1703 + (NSRunLoop *)SR_networkRunLoop { |
| 1704 static dispatch_once_t onceToken; |
| 1705 dispatch_once(&onceToken, ^{ |
| 1706 networkThread = [[_SRRunLoopThread alloc] init]; |
| 1707 networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; |
| 1708 [networkThread start]; |
| 1709 networkRunLoop = networkThread.runLoop; |
| 1710 }); |
| 1711 |
| 1712 return networkRunLoop; |
| 1713 } |
| 1714 |
| 1715 @end |
| 1716 |
| 1717 |
| 1718 @implementation _SRRunLoopThread { |
| 1719 dispatch_group_t _waitGroup; |
| 1720 } |
| 1721 |
| 1722 @synthesize runLoop = _runLoop; |
| 1723 |
| 1724 - (void)dealloc |
| 1725 { |
| 1726 sr_dispatch_release(_waitGroup); |
| 1727 } |
| 1728 |
| 1729 - (id)init |
| 1730 { |
| 1731 self = [super init]; |
| 1732 if (self) { |
| 1733 _waitGroup = dispatch_group_create(); |
| 1734 dispatch_group_enter(_waitGroup); |
| 1735 } |
| 1736 return self; |
| 1737 } |
| 1738 |
| 1739 - (void)main; |
| 1740 { |
| 1741 @autoreleasepool { |
| 1742 _runLoop = [NSRunLoop currentRunLoop]; |
| 1743 dispatch_group_leave(_waitGroup); |
| 1744 |
| 1745 NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture
] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; |
| 1746 [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; |
| 1747 |
| 1748 while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distant
Future]]) { |
| 1749 |
| 1750 } |
| 1751 assert(NO); |
| 1752 } |
| 1753 } |
| 1754 |
| 1755 - (NSRunLoop *)runLoop; |
| 1756 { |
| 1757 dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); |
| 1758 return _runLoop; |
| 1759 } |
| 1760 |
| 1761 @end |
OLD | NEW |