FIRMessagingSecureSocketTest.m 11 KB

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