| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <OCMock/OCMock.h>
- #import <XCTest/XCTest.h>
- #import "Example/Messaging/Tests/FIRMessagingFakeSocket.h"
- #import "Firebase/Messaging/FIRMessagingConnection.h"
- #import "Firebase/Messaging/FIRMessagingSecureSocket.h"
- #import "Firebase/Messaging/FIRMessagingUtilities.h"
- #import "Firebase/Messaging/Protos/GtalkCore.pbobjc.h"
- @interface FIRMessagingConnection ()
- + (GtalkLoginRequest *)loginRequestWithToken:(NSString *)token authID:(NSString *)authID;
- @end
- @interface FIRMessagingSecureSocket() <NSStreamDelegate>
- @property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
- @property(nonatomic, readwrite, strong) NSInputStream *inStream;
- @property(nonatomic, readwrite, strong) NSOutputStream *outStream;
- @property(nonatomic, readwrite, assign) BOOL isVersionSent;
- @property(nonatomic, readwrite, assign) BOOL isVersionReceived;
- @property(nonatomic, readwrite, assign) BOOL isInStreamOpen;
- @property(nonatomic, readwrite, assign) BOOL isOutStreamOpen;
- @property(nonatomic, readwrite, strong) NSRunLoop *runLoop;
- - (BOOL)performRead;
- @end
- typedef void(^FIRMessagingTestSocketDisconnectHandler)(void);
- typedef void(^FIRMessagingTestSocketConnectHandler)(void);
- @interface FIRMessagingSecureSocketTest : XCTestCase <FIRMessagingSecureSocketDelegate>
- @property(nonatomic, readwrite, strong) FIRMessagingFakeSocket *socket;
- @property(nonatomic, readwrite, strong) id mockSocket;
- @property(nonatomic, readwrite, strong) NSError *protoParseError;
- @property(nonatomic, readwrite, strong) GPBMessage *protoReceived;
- @property(nonatomic, readwrite, assign) int8_t protoTagReceived;
- @property(nonatomic, readwrite, copy) FIRMessagingTestSocketDisconnectHandler disconnectHandler;
- @property(nonatomic, readwrite, copy) FIRMessagingTestSocketConnectHandler connectHandler;
- @end
- static BOOL isSafeToDisconnectSocket = NO;
- @implementation FIRMessagingSecureSocketTest
- - (void)setUp {
- [super setUp];
- isSafeToDisconnectSocket = NO;
- self.protoParseError = nil;
- self.protoReceived = nil;
- self.protoTagReceived = 0;
- }
- - (void)tearDown {
- self.disconnectHandler = nil;
- self.connectHandler = nil;
- isSafeToDisconnectSocket = YES;
- [self.socket disconnect];
- [super tearDown];
- }
- #pragma mark - Test Reading
- - (void)testSendingVersion {
- // read as soon as 1 byte is written
- [self createAndConnectSocketWithBufferSize:1];
- uint8_t versionByte = 40;
- [self.socket.outStream write:&versionByte maxLength:1];
- [[[self.mockSocket stub] andDo:^(NSInvocation *invocation) {
- XCTAssertTrue(isSafeToDisconnectSocket,
- @"Should not disconnect socket now");
- }] disconnect];
- XCTestExpectation *shouldAcceptVersionExpectation =
- [self expectationWithDescription:@"Socket should accept version"];
- dispatch_time_t delay =
- dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
- dispatch_after(delay, dispatch_get_main_queue(), ^{
- XCTAssertTrue(self.socket.isVersionReceived);
- [shouldAcceptVersionExpectation fulfill];
- });
- [self waitForExpectationsWithTimeout:3.0
- handler:^(NSError *error) {
- XCTAssertNil(error);
- }];
- }
- - (void)testReceivingDataMessage {
- [self createAndConnectSocketWithBufferSize:61];
- [self writeVersionToOutStream];
- GtalkDataMessageStanza *message = [[GtalkDataMessageStanza alloc] init];
- [message setCategory:@"socket-test-category"];
- [message setFrom:@"socket-test-from"];
- FIRMessagingSetLastStreamId(message, 2);
- FIRMessagingSetRmq2Id(message, @"socket-test-rmq");
- XCTestExpectation *dataExpectation = [self
- expectationWithDescription:@"FIRMessaging socket should receive data message"];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.socket sendData:[message data]
- withTag:kFIRMessagingProtoTagDataMessageStanza
- rmqId:FIRMessagingGetRmq2Id(message)];
- });
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- XCTAssertEqual(self.protoTagReceived, kFIRMessagingProtoTagDataMessageStanza);
- [dataExpectation fulfill];
- });
- [self waitForExpectationsWithTimeout:5.0
- handler:^(NSError *error) {
- XCTAssertNil(error);
- }];
- }
- #pragma mark - Writing
- - (void)testLoginRequest {
- [self createAndConnectSocketWithBufferSize:99];
- XCTestExpectation *loginExpectation =
- [self expectationWithDescription:@"Socket send valid login proto"];
- [self writeVersionToOutStream];
- GtalkLoginRequest *loginRequest =
- [FIRMessagingConnection loginRequestWithToken:@"gcmtoken" authID:@"gcmauthid"];
- FIRMessagingSetLastStreamId(loginRequest, 1);
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.socket sendData:[loginRequest data]
- withTag:FIRMessagingGetTagForProto(loginRequest)
- rmqId:FIRMessagingGetRmq2Id(loginRequest)];
- });
- dispatch_time_t delay =
- dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
- dispatch_after(delay, dispatch_get_main_queue(), ^{
- XCTAssertTrue(self.socket.isVersionReceived);
- XCTAssertEqual(self.protoTagReceived, kFIRMessagingProtoTagLoginRequest);
- [loginExpectation fulfill];
- });
- [self waitForExpectationsWithTimeout:6.0
- handler:^(NSError *error) {
- XCTAssertNil(error);
- }];
- }
- - (void)testSendingImproperData {
- [self createAndConnectSocketWithBufferSize:124];
- [self writeVersionToOutStream];
- NSString *randomString = @"some random data string";
- NSData *randomData = [randomString dataUsingEncoding:NSUTF8StringEncoding];
- XCTestExpectation *parseErrorExpectation =
- [self expectationWithDescription:@"Sending improper data results in a parse error"];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.socket sendData:randomData withTag:3 rmqId:@"some-random-rmq-id"];
- });
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- if (self.protoParseError != nil) {
- [parseErrorExpectation fulfill];
- }
- });
- [self waitForExpectationsWithTimeout:3.0 handler:nil];
- }
- - (void)testSendingDataWithImproperTag {
- [self createAndConnectSocketWithBufferSize:124];
- [self writeVersionToOutStream];
- const char dataString[] = { 0x02, 0x02, 0x11, 0x11, 0x11, 0x11 }; // tag 10, random data
- NSData *randomData = [NSData dataWithBytes:dataString length:6];
- // Create an expectation for a method which should not be invoked during this test.
- // This is required to allow us to wait for the socket stream to be read and
- // processed by FIRMessagingSecureSocket
- OCMExpect([self.mockSocket disconnect]);
- NSTimeInterval sendDelay = 2.0;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(sendDelay * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.socket sendData:randomData withTag:10 rmqId:@"some-random-rmq-id"];
- });
- @try {
- // While waiting to verify this call, an exception should be thrown
- // trying to parse the random data in our delegate.
- // Wait slightly longer than the sendDelay, to allow for the parsing
- OCMVerifyAllWithDelay(self.mockSocket, sendDelay+0.25);
- XCTFail(@"Invalid data being read should have thrown an exception.");
- }
- @catch (NSException *exception) {
- XCTAssertNotNil(exception);
- }
- @finally { }
- }
- - (void)testDisconnect {
- [self createAndConnectSocketWithBufferSize:1];
- [self writeVersionToOutStream];
- // version read and written let's disconnect
- XCTestExpectation *disconnectExpectation =
- [self expectationWithDescription:@"socket should disconnect properly"];
- self.disconnectHandler = ^{
- [disconnectExpectation fulfill];
- };
- [self.socket disconnect];
- [self waitForExpectationsWithTimeout:5.0
- handler:^(NSError *error) {
- XCTAssertNil(error);
- }];
- XCTAssertNil(self.socket.inStream);
- XCTAssertNil(self.socket.outStream);
- XCTAssertEqual(self.socket.state, kFIRMessagingSecureSocketClosed);
- }
- - (void)testSocketOpening {
- XCTestExpectation *openSocketExpectation =
- [self expectationWithDescription:@"Socket should open properly"];
- self.connectHandler = ^{
- [openSocketExpectation fulfill];
- };
- [self createAndConnectSocketWithBufferSize:1];
- [self writeVersionToOutStream];
- [self waitForExpectationsWithTimeout:10.0
- handler:^(NSError *error) {
- XCTAssertNil(error);
- }];
- XCTAssertTrue(self.socket.isInStreamOpen);
- XCTAssertTrue(self.socket.isOutStreamOpen);
- }
- #pragma mark - FIRMessagingSecureSocketDelegate protocol
- - (void)secureSocket:(FIRMessagingSecureSocket *)socket
- didReceiveData:(NSData *)data
- withTag:(int8_t)tag {
- NSError *error;
- GPBMessage *proto =
- [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data
- error:&error];
- self.protoParseError = error;
- self.protoReceived = proto;
- self.protoTagReceived = tag;
- }
- - (void)secureSocket:(FIRMessagingSecureSocket *)socket
- didSendProtoWithTag:(int8_t)tag
- rmqId:(NSString *)rmqId {
- // do nothing
- }
- - (void)secureSocketDidConnect:(FIRMessagingSecureSocket *)socket {
- if (self.connectHandler) {
- self.connectHandler();
- }
- }
- - (void)didDisconnectWithSecureSocket:(FIRMessagingSecureSocket *)socket {
- if (self.disconnectHandler) {
- self.disconnectHandler();
- }
- }
- #pragma mark - Private Helpers
- - (void)createAndConnectSocketWithBufferSize:(uint8_t)bufferSize {
- self.socket = [[FIRMessagingFakeSocket alloc] initWithBufferSize:bufferSize];
- self.mockSocket = OCMPartialMock(self.socket);
- self.socket.delegate = self;
- [self.socket connectToHost:@"localhost"
- port:6234
- onRunLoop:[NSRunLoop mainRunLoop]];
- }
- - (void)writeVersionToOutStream {
- uint8_t versionByte = 40;
- [self.socket.outStream write:&versionByte maxLength:1];
- // don't resend the version
- self.socket.isVersionSent = YES;
- }
- @end
|