FIRMessagingSecureSocketTest.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright 2017 Google
  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. #import <XCTest/XCTest.h>
  17. #import <OCMock/OCMock.h>
  18. #import "Protos/GtalkCore.pbobjc.h"
  19. #import "FIRMessagingConnection.h"
  20. #import "FIRMessagingFakeSocket.h"
  21. #import "FIRMessagingSecureSocket.h"
  22. #import "FIRMessagingUtilities.h"
  23. @interface FIRMessagingConnection ()
  24. + (GtalkLoginRequest *)loginRequestWithToken:(NSString *)token authID:(NSString *)authID;
  25. @end
  26. @interface FIRMessagingSecureSocket() <NSStreamDelegate>
  27. @property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
  28. @property(nonatomic, readwrite, strong) NSInputStream *inStream;
  29. @property(nonatomic, readwrite, strong) NSOutputStream *outStream;
  30. @property(nonatomic, readwrite, assign) BOOL isVersionSent;
  31. @property(nonatomic, readwrite, assign) BOOL isVersionReceived;
  32. @property(nonatomic, readwrite, assign) BOOL isInStreamOpen;
  33. @property(nonatomic, readwrite, assign) BOOL isOutStreamOpen;
  34. @property(nonatomic, readwrite, strong) NSRunLoop *runLoop;
  35. - (BOOL)performRead;
  36. @end
  37. typedef void(^FIRMessagingTestSocketDisconnectHandler)(void);
  38. typedef void(^FIRMessagingTestSocketConnectHandler)(void);
  39. @interface FIRMessagingSecureSocketTest : XCTestCase <FIRMessagingSecureSocketDelegate>
  40. @property(nonatomic, readwrite, strong) FIRMessagingFakeSocket *socket;
  41. @property(nonatomic, readwrite, strong) id mockSocket;
  42. @property(nonatomic, readwrite, strong) NSError *protoParseError;
  43. @property(nonatomic, readwrite, strong) GPBMessage *protoReceived;
  44. @property(nonatomic, readwrite, assign) int8_t protoTagReceived;
  45. @property(nonatomic, readwrite, copy) FIRMessagingTestSocketDisconnectHandler disconnectHandler;
  46. @property(nonatomic, readwrite, copy) FIRMessagingTestSocketConnectHandler connectHandler;
  47. @end
  48. static BOOL isSafeToDisconnectSocket = NO;
  49. @implementation FIRMessagingSecureSocketTest
  50. - (void)setUp {
  51. [super setUp];
  52. isSafeToDisconnectSocket = NO;
  53. self.protoParseError = nil;
  54. self.protoReceived = nil;
  55. self.protoTagReceived = 0;
  56. }
  57. - (void)tearDown {
  58. self.disconnectHandler = nil;
  59. self.connectHandler = nil;
  60. isSafeToDisconnectSocket = YES;
  61. [self.socket disconnect];
  62. [super tearDown];
  63. }
  64. #pragma mark - Test Reading
  65. - (void)testSendingVersion {
  66. // read as soon as 1 byte is written
  67. [self createAndConnectSocketWithBufferSize:1];
  68. uint8_t versionByte = 40;
  69. [self.socket.outStream write:&versionByte maxLength:1];
  70. [[[self.mockSocket stub] andDo:^(NSInvocation *invocation) {
  71. XCTAssertTrue(isSafeToDisconnectSocket,
  72. @"Should not disconnect socket now");
  73. }] disconnect];
  74. XCTestExpectation *shouldAcceptVersionExpectation =
  75. [self expectationWithDescription:@"Socket should accept version"];
  76. dispatch_time_t delay =
  77. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
  78. dispatch_after(delay, dispatch_get_main_queue(), ^{
  79. XCTAssertTrue(self.socket.isVersionReceived);
  80. [shouldAcceptVersionExpectation fulfill];
  81. });
  82. [self waitForExpectationsWithTimeout:3.0
  83. handler:^(NSError *error) {
  84. XCTAssertNil(error);
  85. }];
  86. }
  87. - (void)testReceivingDataMessage {
  88. [self createAndConnectSocketWithBufferSize:61];
  89. [self writeVersionToOutStream];
  90. GtalkDataMessageStanza *message = [[GtalkDataMessageStanza alloc] init];
  91. [message setCategory:@"socket-test-category"];
  92. [message setFrom:@"socket-test-from"];
  93. FIRMessagingSetLastStreamId(message, 2);
  94. FIRMessagingSetRmq2Id(message, @"socket-test-rmq");
  95. XCTestExpectation *dataExpectation = [self
  96. expectationWithDescription:@"FIRMessaging socket should receive data message"];
  97. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
  98. dispatch_get_main_queue(), ^{
  99. [self.socket sendData:[message data]
  100. withTag:kFIRMessagingProtoTagDataMessageStanza
  101. rmqId:FIRMessagingGetRmq2Id(message)];
  102. });
  103. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)),
  104. dispatch_get_main_queue(), ^{
  105. XCTAssertEqual(self.protoTagReceived, kFIRMessagingProtoTagDataMessageStanza);
  106. [dataExpectation fulfill];
  107. });
  108. [self waitForExpectationsWithTimeout:5.0
  109. handler:^(NSError *error) {
  110. XCTAssertNil(error);
  111. }];
  112. }
  113. #pragma mark - Writing
  114. - (void)testLoginRequest {
  115. [self createAndConnectSocketWithBufferSize:99];
  116. XCTestExpectation *loginExpectation =
  117. [self expectationWithDescription:@"Socket send valid login proto"];
  118. [self writeVersionToOutStream];
  119. GtalkLoginRequest *loginRequest =
  120. [FIRMessagingConnection loginRequestWithToken:@"gcmtoken" authID:@"gcmauthid"];
  121. FIRMessagingSetLastStreamId(loginRequest, 1);
  122. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
  123. dispatch_get_main_queue(), ^{
  124. [self.socket sendData:[loginRequest data]
  125. withTag:FIRMessagingGetTagForProto(loginRequest)
  126. rmqId:FIRMessagingGetRmq2Id(loginRequest)];
  127. });
  128. dispatch_time_t delay =
  129. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
  130. dispatch_after(delay, dispatch_get_main_queue(), ^{
  131. XCTAssertTrue(self.socket.isVersionReceived);
  132. XCTAssertEqual(self.protoTagReceived, kFIRMessagingProtoTagLoginRequest);
  133. [loginExpectation fulfill];
  134. });
  135. [self waitForExpectationsWithTimeout:6.0
  136. handler:^(NSError *error) {
  137. XCTAssertNil(error);
  138. }];
  139. }
  140. - (void)testSendingImproperData {
  141. [self createAndConnectSocketWithBufferSize:124];
  142. [self writeVersionToOutStream];
  143. NSString *randomString = @"some random data string";
  144. NSData *randomData = [randomString dataUsingEncoding:NSUTF8StringEncoding];
  145. XCTestExpectation *parseErrorExpectation =
  146. [self expectationWithDescription:@"Sending improper data results in a parse error"];
  147. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
  148. dispatch_get_main_queue(), ^{
  149. [self.socket sendData:randomData withTag:3 rmqId:@"some-random-rmq-id"];
  150. });
  151. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.5 * NSEC_PER_SEC)),
  152. dispatch_get_main_queue(), ^{
  153. if (self.protoParseError != nil) {
  154. [parseErrorExpectation fulfill];
  155. }
  156. });
  157. [self waitForExpectationsWithTimeout:3.0 handler:nil];
  158. }
  159. - (void)testSendingDataWithImproperTag {
  160. [self createAndConnectSocketWithBufferSize:124];
  161. [self writeVersionToOutStream];
  162. const char dataString[] = { 0x02, 0x02, 0x11, 0x11, 0x11, 0x11 }; // tag 10, random data
  163. NSData *randomData = [NSData dataWithBytes:dataString length:6];
  164. // Create an expectation for a method which should not be invoked during this test.
  165. // This is required to allow us to wait for the socket stream to be read and
  166. // processed by FIRMessagingSecureSocket
  167. OCMExpect([self.mockSocket disconnect]);
  168. NSTimeInterval sendDelay = 2.0;
  169. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(sendDelay * NSEC_PER_SEC)),
  170. dispatch_get_main_queue(), ^{
  171. [self.socket sendData:randomData withTag:10 rmqId:@"some-random-rmq-id"];
  172. });
  173. @try {
  174. // While waiting to verify this call, an exception should be thrown
  175. // trying to parse the random data in our delegate.
  176. // Wait slightly longer than the sendDelay, to allow for the parsing
  177. OCMVerifyAllWithDelay(self.mockSocket, sendDelay+0.25);
  178. XCTFail(@"Invalid data being read should have thrown an exception.");
  179. }
  180. @catch (NSException *exception) {
  181. XCTAssertNotNil(exception);
  182. }
  183. @finally { }
  184. }
  185. - (void)testDisconnect {
  186. [self createAndConnectSocketWithBufferSize:1];
  187. [self writeVersionToOutStream];
  188. // version read and written let's disconnect
  189. XCTestExpectation *disconnectExpectation =
  190. [self expectationWithDescription:@"socket should disconnect properly"];
  191. self.disconnectHandler = ^{
  192. [disconnectExpectation fulfill];
  193. };
  194. [self.socket disconnect];
  195. [self waitForExpectationsWithTimeout:5.0
  196. handler:^(NSError *error) {
  197. XCTAssertNil(error);
  198. }];
  199. XCTAssertNil(self.socket.inStream);
  200. XCTAssertNil(self.socket.outStream);
  201. XCTAssertEqual(self.socket.state, kFIRMessagingSecureSocketClosed);
  202. }
  203. - (void)testSocketOpening {
  204. XCTestExpectation *openSocketExpectation =
  205. [self expectationWithDescription:@"Socket should open properly"];
  206. self.connectHandler = ^{
  207. [openSocketExpectation fulfill];
  208. };
  209. [self createAndConnectSocketWithBufferSize:1];
  210. [self writeVersionToOutStream];
  211. [self waitForExpectationsWithTimeout:10.0
  212. handler:^(NSError *error) {
  213. XCTAssertNil(error);
  214. }];
  215. XCTAssertTrue(self.socket.isInStreamOpen);
  216. XCTAssertTrue(self.socket.isOutStreamOpen);
  217. }
  218. #pragma mark - FIRMessagingSecureSocketDelegate protocol
  219. - (void)secureSocket:(FIRMessagingSecureSocket *)socket
  220. didReceiveData:(NSData *)data
  221. withTag:(int8_t)tag {
  222. NSError *error;
  223. GPBMessage *proto =
  224. [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data
  225. error:&error];
  226. self.protoParseError = error;
  227. self.protoReceived = proto;
  228. self.protoTagReceived = tag;
  229. }
  230. - (void)secureSocket:(FIRMessagingSecureSocket *)socket
  231. didSendProtoWithTag:(int8_t)tag
  232. rmqId:(NSString *)rmqId {
  233. // do nothing
  234. }
  235. - (void)secureSocketDidConnect:(FIRMessagingSecureSocket *)socket {
  236. if (self.connectHandler) {
  237. self.connectHandler();
  238. }
  239. }
  240. - (void)didDisconnectWithSecureSocket:(FIRMessagingSecureSocket *)socket {
  241. if (self.disconnectHandler) {
  242. self.disconnectHandler();
  243. }
  244. }
  245. #pragma mark - Private Helpers
  246. - (void)createAndConnectSocketWithBufferSize:(uint8_t)bufferSize {
  247. self.socket = [[FIRMessagingFakeSocket alloc] initWithBufferSize:bufferSize];
  248. self.mockSocket = OCMPartialMock(self.socket);
  249. self.socket.delegate = self;
  250. [self.socket connectToHost:@"localhost"
  251. port:6234
  252. onRunLoop:[NSRunLoop mainRunLoop]];
  253. }
  254. - (void)writeVersionToOutStream {
  255. uint8_t versionByte = 40;
  256. [self.socket.outStream write:&versionByte maxLength:1];
  257. // don't resend the version
  258. self.socket.isVersionSent = YES;
  259. }
  260. @end