Files
firebase-ios-sdk/Example/Messaging/Tests/FIRMessagingSecureSocketTest.m
T
2017-05-15 12:27:07 -07:00

324 lines
11 KiB
Objective-C

/*
* 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 XCTest;
#import <OCMock/OCMock.h>
#import "Protos/GtalkCore.pbobjc.h"
#import "FIRMessagingConnection.h"
#import "FIRMessagingFakeSocket.h"
#import "FIRMessagingSecureSocket.h"
#import "FIRMessagingUtilities.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