meteor-motion 0.1.0

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