summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2021-04-20 11:54:45 -0400
committerGitHub <noreply@github.com>2021-04-20 11:54:45 -0400
commit5bd0d7ceb196e3d0888f151cde9ccda6ff0f1a71 (patch)
treea02ddf727b484bcb3e5fc81aeef80d88e394e5fb
parent62d185900bae968e7e87e33f7a354bceabc67452 (diff)
parent1fa7c0175753dbed92f5ebe7d464f8bbad395c4b (diff)
downloadsdl_ios-5bd0d7ceb196e3d0888f151cde9ccda6ff0f1a71.tar.gz
Merge pull request #1972 from smartdevicelink/bugfix/issue-1966-rpc-encryption-crash
Fix a crash when RPC encryption starts and callback when RPCs fail due to RPC encryption
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj23
-rw-r--r--SmartDeviceLink/private/SDLError.h1
-rw-r--r--SmartDeviceLink/private/SDLError.m12
-rw-r--r--SmartDeviceLink/private/SDLLifecycleManager.m46
-rw-r--r--SmartDeviceLink/private/SDLProtocol.h4
-rw-r--r--SmartDeviceLink/private/SDLProtocol.m43
-rw-r--r--SmartDeviceLink/private/SDLUploadFileOperation.m2
-rw-r--r--SmartDeviceLink/public/SDLErrorConstants.h5
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m137
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m12
10 files changed, 202 insertions, 83 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 7929c3aa3..8dba00f2c 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -7903,7 +7903,7 @@
TargetAttributes = {
5D4019AE1A76EC350006B0C2 = {
CreatedOnToolsVersion = 6.1.1;
- ProvisioningStyle = Manual;
+ ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
@@ -7916,6 +7916,7 @@
5D61FA1B1A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 1210;
+ ProvisioningStyle = Automatic;
};
5D61FA251A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
@@ -9375,7 +9376,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = NCVC2MHU7M;
INFOPLIST_FILE = "$(SRCROOT)/Example Apps/Example ObjC/SmartDeviceLink-Example-ObjC-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@@ -9383,6 +9385,7 @@
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -9391,7 +9394,8 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = NCVC2MHU7M;
INFOPLIST_FILE = "$(SRCROOT)/Example Apps/Example ObjC/SmartDeviceLink-Example-ObjC-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@@ -9399,6 +9403,7 @@
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -9420,9 +9425,13 @@
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -9442,6 +9451,8 @@
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.smartdevicelink;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
@@ -9470,9 +9481,13 @@
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -9488,6 +9503,8 @@
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.smartdevicelink;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
diff --git a/SmartDeviceLink/private/SDLError.h b/SmartDeviceLink/private/SDLError.h
index 1621d8f1b..f6e7a0bb9 100644
--- a/SmartDeviceLink/private/SDLError.h
+++ b/SmartDeviceLink/private/SDLError.h
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSError *)sdl_encryption_lifecycle_notReadyError;
+ (NSError *)sdl_encryption_lifecycle_encryption_off;
+ (NSError *)sdl_encryption_lifecycle_nak;
++ (NSError *)sdl_encryption_unknown;
#pragma mark SDLManager
diff --git a/SmartDeviceLink/private/SDLError.m b/SmartDeviceLink/private/SDLError.m
index 35818d361..5ee60e6bb 100644
--- a/SmartDeviceLink/private/SDLError.m
+++ b/SmartDeviceLink/private/SDLError.m
@@ -52,6 +52,18 @@ NS_ASSUME_NONNULL_BEGIN
}
++ (NSError *)sdl_encryption_unknown {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: @"Encryption received an unknown error",
+ NSLocalizedFailureReasonErrorKey: @"We don't know the reason for the failure",
+ NSLocalizedRecoverySuggestionErrorKey: @"Ensure that encryption is properly set up"
+ };
+
+ return [NSError errorWithDomain:SDLErrorDomainEncryptionLifecycleManager
+ code:SDLEncryptionLifecycleManagerErrorNAK
+ userInfo:userInfo];
+}
+
#pragma mark - SDLManager
+ (NSError *)sdl_lifecycle_rpcErrorWithDescription:(nullable NSString *)description andReason:(nullable NSString *)reason {
diff --git a/SmartDeviceLink/private/SDLLifecycleManager.m b/SmartDeviceLink/private/SDLLifecycleManager.m
index 9fc7d088d..b3c205b9e 100644
--- a/SmartDeviceLink/private/SDLLifecycleManager.m
+++ b/SmartDeviceLink/private/SDLLifecycleManager.m
@@ -387,7 +387,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
__weak typeof(self) weakSelf = self;
[self sendConnectionManagerRequest:regRequest withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
// If the success BOOL is NO or we received an error at this point, we failed. Call the ready handler and transition to the DISCONNECTED state.
- if (error != nil || ![response.success boolValue]) {
+ if (error != nil || !response.success.boolValue) {
SDLLogE(@"Failed to register the app. Error: %@, Response: %@", error, response);
if (weakSelf.readyHandler) {
weakSelf.readyHandler(NO, error);
@@ -626,7 +626,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
__weak typeof(self) weakSelf = self;
[self sdl_sendConnectionRequest:unregisterRequest
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
- if (error != nil || ![response.success boolValue]) {
+ if (error != nil || !response.success.boolValue) {
SDLLogE(@"SDL Error unregistering, we are going to hard disconnect: %@, response: %@", error, response);
}
@@ -647,12 +647,11 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
[self.fileManager uploadFile:appIcon completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError *_Nullable error) {
// These errors could be recoverable (particularly "cannot overwrite"), so we'll still attempt to set the app icon
if (error != nil) {
- if (error.code == SDLFileManagerErrorCannotOverwrite) {
+ if ([error.domain isEqualToString:SDLErrorDomainFileManager] && error.code == SDLFileManagerErrorCannotOverwrite) {
SDLLogW(@"Failed to upload app icon: A file with this name already exists on the system");
} else {
SDLLogW(@"Unexpected error uploading app icon: %@", error);
- completion();
- return;
+ return completion();
}
}
@@ -666,8 +665,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
SDLLogW(@"Error setting up app icon: %@", error);
}
- // We've succeeded or failed
- completion();
+ return completion();
}];
}];
}
@@ -753,19 +751,6 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
return;
}
-
- if (!request.isPayloadProtected && [self.encryptionLifecycleManager rpcRequiresEncryption:request]) {
- request.payloadProtected = YES;
- }
-
- if (request.isPayloadProtected && !self.encryptionLifecycleManager.isEncryptionReady) {
- SDLLogW(@"Encryption Manager not ready, request not sent (%@)", request);
- if (handler) {
- handler(request, nil, [NSError sdl_encryption_lifecycle_notReadyError]);
- }
-
- return;
- }
[SDLGlobals runSyncOnSerialSubQueue:self.lifecycleQueue block:^{
[self sdl_sendConnectionRequest:request withResponseHandler:handler];
@@ -799,20 +784,29 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
}
// Before we send a message, we have to check if we need to adapt the RPC. When adapting the RPC, there could be multiple RPCs that need to be sent.
+ NSError *error = nil;
NSArray<SDLRPCMessage *> *messages = [SDLLifecycleRPCAdapter adaptRPC:request direction:SDLRPCDirectionOutgoing];
for (SDLRPCMessage *message in messages) {
+ BOOL successfullySent = NO;
if ([request isKindOfClass:SDLRPCRequest.class]) {
// Generate and add a correlation ID to the request. When a response for the request is returned from Core, it will have the same correlation ID
SDLRPCRequest *requestRPC = (SDLRPCRequest *)message;
NSNumber *corrID = [self sdl_getNextCorrelationId];
requestRPC.correlationID = corrID;
[self.responseDispatcher storeRequest:requestRPC handler:handler];
- [self.protocolHandler.protocol sendRPC:requestRPC];
+ successfullySent = [self.protocolHandler.protocol sendRPC:requestRPC error:&error];
} else if ([request isKindOfClass:SDLRPCResponse.class] || [request isKindOfClass:SDLRPCNotification.class]) {
- [self.protocolHandler.protocol sendRPC:message];
+ successfullySent = [self.protocolHandler.protocol sendRPC:message error:&error];
} else {
SDLLogE(@"Will not send an RPC with unknown type, %@. The request should be of type SDLRPCRequest, SDLRPCResponse, or SDLRPCNotification.", request.class);
}
+
+ if (!successfullySent) {
+ if (handler != nil) {
+ handler(request, nil, error);
+ }
+ break;
+ }
}
}
@@ -869,10 +863,14 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
// Ignore the connection while we are reconnecting. The proxy needs to be disposed and restarted in order to ensure correct state. https://github.com/smartdevicelink/sdl_ios/issues/1172
if (![self.lifecycleStateMachine isCurrentState:SDLLifecycleStateReady]
&& ![self.lifecycleStateMachine isCurrentState:SDLLifecycleStateReconnecting]) {
- SDLLogD(@"Transport connected");
+ SDLLogD(@"RPC Service connected");
dispatch_async(self.lifecycleQueue, ^{
- [self sdl_transitionToState:SDLLifecycleStateConnected];
+ if ([self.lifecycleState isEqualToString:SDLLifecycleStateStarted]) {
+ [self sdl_transitionToState:SDLLifecycleStateConnected];
+ } else {
+ SDLLogW(@"RPC service connected while already connected. This is probably an encryption update. We will stay in our current state: %@", self.lifecycleState);
+ }
});
}
}
diff --git a/SmartDeviceLink/private/SDLProtocol.h b/SmartDeviceLink/private/SDLProtocol.h
index 9fdc25192..84537e5b8 100644
--- a/SmartDeviceLink/private/SDLProtocol.h
+++ b/SmartDeviceLink/private/SDLProtocol.h
@@ -136,8 +136,10 @@ extern NSString *const SDLProtocolSecurityErrorDomain;
* Sends an unencrypted RPC to Core
*
* @param message A SDLRPCMessage message
+ * @param error A pass-back error object if the RPC failed to send
+ * @returns YES if the RPC was sent, NO if it was not
*/
-- (void)sendRPC:(SDLRPCMessage *)message;
+- (BOOL)sendRPC:(SDLRPCMessage *)message error:(NSError *__autoreleasing *)error;
/**
* Sends an unencrypted message to Core
diff --git a/SmartDeviceLink/private/SDLProtocol.m b/SmartDeviceLink/private/SDLProtocol.m
index 348543801..a2f8cc021 100644
--- a/SmartDeviceLink/private/SDLProtocol.m
+++ b/SmartDeviceLink/private/SDLProtocol.m
@@ -14,6 +14,7 @@
#import "SDLControlFramePayloadRPCStartServiceAck.h"
#import "SDLControlFramePayloadVideoStartServiceAck.h"
#import "SDLEncryptionLifecycleManager.h"
+#import "SDLError.h"
#import "SDLLogMacros.h"
#import "SDLGlobals.h"
#import "SDLPrioritizedObjectCollection.h"
@@ -280,25 +281,33 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Send Data
-- (void)sendRPC:(SDLRPCMessage *)message {
+- (BOOL)sendRPC:(SDLRPCMessage *)message error:(NSError *__autoreleasing *)error {
if (!message.isPayloadProtected && [self.encryptionLifecycleManager rpcRequiresEncryption:message]) {
message.payloadProtected = YES;
}
-
- if (message.isPayloadProtected && !self.encryptionLifecycleManager.isEncryptionReady) {
- SDLLogW(@"Encryption Manager not ready, request not sent (%@)", message);
- return;
- }
- [self sendRPC:message encrypted:message.isPayloadProtected error:nil];
+ return [self sendRPC:message encrypted:message.isPayloadProtected error:error];
}
- (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSError *__autoreleasing *)error {
NSParameterAssert(message != nil);
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[message serializeAsDictionary:(Byte)[SDLGlobals sharedGlobals].protocolVersion.major] options:kNilOptions error:error];
- if (error != nil) {
- SDLLogW(@"Error encoding JSON data: %@", *error);
+ // Check that we can send the message over encryption and fail early if we cannot
+ if (message.isPayloadProtected && !self.encryptionLifecycleManager.isEncryptionReady) {
+ SDLLogE(@"Encryption Manager not ready, message not sent (%@)", message);
+ if (error != nil) {
+ *error = [NSError sdl_encryption_lifecycle_notReadyError];
+ }
+ return NO;
+ }
+
+ // Convert the message dictionary to JSON and return early if it fails
+ NSError *jsonError = nil;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[message serializeAsDictionary:(Byte)[SDLGlobals sharedGlobals].protocolVersion.major] options:kNilOptions error:&jsonError];
+ if (jsonError != nil) {
+ SDLLogE(@"Error encoding JSON data: %@", jsonError);
+ *error = jsonError;
+ return NO;
}
NSData *messagePayload = nil;
@@ -333,27 +342,31 @@ NS_ASSUME_NONNULL_BEGIN
rpcPayload.rpcType = SDLRPCMessageTypeNotification;
} else {
NSAssert(NO, @"Unknown message type attempted to send. Type: %@", [message class]);
+ *error = [NSError sdl_lifecycle_rpcErrorWithDescription:@"Unknown message type" andReason:@"An unknown RPC message type was attempted."];
return NO;
}
// If we're trying to encrypt, try to have the security manager encrypt it. Return if it fails.
- // TODO: (Joel F.)[2016-02-09] We should assert if the service isn't setup for encryption. See [#350](https://github.com/smartdevicelink/sdl_ios/issues/350)
+ NSError *encryptError = nil;
if (encryption) {
- NSError *encryptError = nil;
-
messagePayload = [self.securityManager encryptData:rpcPayload.data withError:&encryptError];
- if (encryptError) {
+ if (encryptError != nil) {
SDLLogE(@"Error encrypting request! %@", encryptError);
}
} else {
messagePayload = rpcPayload.data;
}
+ // If the encryption failed, pass back the error and return false
if (!messagePayload) {
+ if (encryptError != nil) {
+ *error = encryptError;
+ } else {
+ *error = [NSError sdl_encryption_unknown];
+ }
return NO;
}
-
} break;
default: {
NSAssert(NO, @"Attempting to send an RPC based on an unknown version number: %@, message: %@", @([SDLGlobals sharedGlobals].protocolVersion.major), message);
diff --git a/SmartDeviceLink/private/SDLUploadFileOperation.m b/SmartDeviceLink/private/SDLUploadFileOperation.m
index 8149c56fc..aa476964a 100644
--- a/SmartDeviceLink/private/SDLUploadFileOperation.m
+++ b/SmartDeviceLink/private/SDLUploadFileOperation.m
@@ -153,7 +153,7 @@ static NSUInteger const MaxCRCValue = UINT32_MAX;
}
// If the SDL Core returned an error, cancel the upload the process in the future
- if (error != nil || response == nil || ![response.success boolValue] || strongself.isCancelled) {
+ if (error != nil || response == nil || !response.success.boolValue || strongself.isCancelled) {
[strongself cancel];
streamError = error;
dispatch_group_leave(putFileGroup);
diff --git a/SmartDeviceLink/public/SDLErrorConstants.h b/SmartDeviceLink/public/SDLErrorConstants.h
index da4757ec1..e653ad2d4 100644
--- a/SmartDeviceLink/public/SDLErrorConstants.h
+++ b/SmartDeviceLink/public/SDLErrorConstants.h
@@ -73,7 +73,10 @@ typedef NS_ENUM(NSInteger, SDLEncryptionLifecycleManagerError) {
/**
* Received NAK from the remote head unit.
*/
- SDLEncryptionLifecycleManagerErrorNAK = -3
+ SDLEncryptionLifecycleManagerErrorNAK = -3,
+
+ /// An unknown error occurred
+ SDLEncryptionLifecycleManagerErrorUnknown = -4
};
/**
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
index 3d2f16faf..3fb48e811 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
@@ -100,6 +100,7 @@ QuickConfigurationEnd
QuickSpecBegin(SDLLifecycleManagerSpec)
+// test lifecycle manager internals
describe(@"test lifecycle manager internals", ^{
context(@"init and assign version", ^{
SDLLifecycleTestManager *manager = [[SDLLifecycleTestManager alloc] init];
@@ -115,6 +116,7 @@ describe(@"test lifecycle manager internals", ^{
});
});
+// a lifecycle manager
describe(@"a lifecycle manager", ^{
__block SDLLifecycleManager *testManager = nil;
__block SDLConfiguration *testConfig = nil;
@@ -175,7 +177,8 @@ describe(@"a lifecycle manager", ^{
[SDLGlobals sharedGlobals].protocolVersion = [SDLVersion versionWithMajor:3 minor:0 patch:0];
[SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:3 minor:0 patch:0];
});
-
+
+ // should initialize properties
it(@"should initialize properties", ^{
expect(testManager.configuration).toNot(equal(testConfig)); // This is copied
expect(testManager.delegate).toNot(beNil());
@@ -197,7 +200,8 @@ describe(@"a lifecycle manager", ^{
});
itBehavesLike(@"unable to send an RPC", ^{ return @{ @"manager": testManager }; });
-
+
+ // after receiving an HMI Status
describe(@"after receiving an HMI Status", ^{
__block SDLOnHMIStatus *testHMIStatus = nil;
__block SDLHMILevel testHMILevel = nil;
@@ -205,7 +209,8 @@ describe(@"a lifecycle manager", ^{
beforeEach(^{
testHMIStatus = [[SDLOnHMIStatus alloc] init];
});
-
+
+ // a non-none hmi level
context(@"a non-none hmi level", ^{
beforeEach(^{
testHMILevel = SDLHMILevelNone;
@@ -218,7 +223,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.hmiLevel).toEventually(equal(testHMILevel));
});
});
-
+
+ // a non-full, non-none hmi level
context(@"a non-full, non-none hmi level", ^{
beforeEach(^{
testHMILevel = SDLHMILevelBackground;
@@ -231,7 +237,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.hmiLevel).toEventually(equal(testHMILevel));
});
});
-
+
+ // a full hmi level
context(@"a full hmi level", ^{
beforeEach(^{
testHMILevel = SDLHMILevelFull;
@@ -245,7 +252,8 @@ describe(@"a lifecycle manager", ^{
});
});
});
-
+
+ // calling stop
describe(@"calling stop", ^{
beforeEach(^{
[testManager stop];
@@ -256,7 +264,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.lifecycleState).toEventuallyNot(match(SDLLifecycleStateStarted));
});
});
-
+
+ // when started
describe(@"when started", ^{
__block BOOL readyHandlerSuccess = NO;
__block NSError *readyHandlerError = nil;
@@ -279,26 +288,32 @@ describe(@"a lifecycle manager", ^{
expect(testManager.secondaryTransportManager).toNot(beNil());
});
+ // after receiving a connect notification
describe(@"after receiving a connect notification", ^{
- beforeEach(^{
+ // should send a register app interface request and be in the connected state
+ it(@"should send a register app interface request and be in the connected state", ^{
+ OCMStub([protocolMock sendRPC:[OCMArg any] error:[OCMArg setTo:nil]]).andReturn(YES);
// When we connect, we should be creating an sending an RAI
- OCMExpect([protocolMock sendRPC:[OCMArg isKindOfClass:[SDLRegisterAppInterface class]]]);
+
+ [testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
+ expect(testManager.lifecycleState).toEventually(equal(SDLLifecycleStateConnected));
});
+ itBehavesLike(@"unable to send an RPC", ^{ return @{ @"manager": testManager }; });
+ // when the protocol system info is set
context(@"when the protocol system info is set", ^{
SDLSystemInfo *testSystemInfo = [[SDLSystemInfo alloc] initWithVehicleType:vehicleType softwareVersion:softwareVersion hardwareVersion:hardwareVersion];
- beforeEach(^{
+ it(@"should call the delegate handler", ^{
OCMStub(protocolMock.systemInfo).andReturn(testSystemInfo);
OCMExpect([sdlManagerDelegateProtocolMock didReceiveSystemInfo:[OCMArg isEqual:testSystemInfo]]).andReturn(YES);
[testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
- });
- it(@"should call the delegate handler", ^{
OCMVerifyAllWithDelay(sdlManagerDelegateProtocolMock, 1.0);
});
});
+ // when the protocol system info is not set
context(@"when the protocol system info is not set", ^{
beforeEach(^{
OCMStub(protocolMock.systemInfo).andReturn(nil);
@@ -309,17 +324,11 @@ describe(@"a lifecycle manager", ^{
OCMReject([sdlManagerDelegateProtocolMock didReceiveSystemInfo:[OCMArg isNil]]);
});
});
-
- it(@"should send a register app interface request and be in the connected state", ^{
- [testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
- OCMVerifyAllWithDelay(protocolMock, 1.0);
- expect(testManager.lifecycleState).toEventually(equal(SDLLifecycleStateConnected));
- });
-
- itBehavesLike(@"unable to send an RPC", ^{ return @{ @"manager": testManager }; });
-
+
+ // after receiving a disconnect notification"
describe(@"after receiving a disconnect notification", ^{
beforeEach(^{
+ OCMStub([protocolMock sendRPC:[OCMArg any] error:[OCMArg setTo:nil]]).andReturn(YES);
[testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
[testManager.notificationDispatcher postNotificationName:SDLTransportDidDisconnect infoObject:nil];
});
@@ -328,7 +337,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.lifecycleState).toEventually(equal(SDLLifecycleStateReconnecting));
});
});
-
+
+ // stopping the manager
describe(@"stopping the manager", ^{
it(@"should simply stop", ^{
[testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
@@ -339,6 +349,7 @@ describe(@"a lifecycle manager", ^{
});
});
+ // in the connected state when the minimum protocol version is in effect
describe(@"in the connected state when the minimum protocol version is in effect", ^{
beforeEach(^{
[SDLGlobals sharedGlobals].protocolVersion = [SDLVersion versionWithMajor:1 minor:0 patch:0];
@@ -351,13 +362,24 @@ describe(@"a lifecycle manager", ^{
OCMVerifyAll(protocolMock);
});
});
-
+
+ // in the connected state
describe(@"in the connected state", ^{
beforeEach(^{
[testManager.lifecycleStateMachine setToState:SDLLifecycleStateConnected fromOldState:nil callEnterTransition:NO];
});
- describe(@"after receiving a register app interface response", ^{
+ // after receiving another connect notification for encryption
+ describe(@"after receiving another connect notification for encryption", ^{
+ it(@"should not crash", ^{
+ expectAction(^{
+ [testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
+ }).toNot(raiseException());
+ });
+ });
+
+ // after receiving a register app interface response
+ describe(@"after receiving a register app interface response", ^{
it(@"should eventually reach the ready state", ^{
NSError *fileManagerStartError = [NSError errorWithDomain:@"testDomain" code:0 userInfo:nil];
NSError *permissionManagerStartError = [NSError errorWithDomain:@"testDomain" code:0 userInfo:nil];
@@ -380,6 +402,7 @@ describe(@"a lifecycle manager", ^{
itBehavesLike(@"unable to send an RPC", ^{ return @{ @"manager": testManager }; });
});
+ // when the protocol system info is not set
context(@"when the protocol system info is not set", ^{
beforeEach(^{
testManager.systemInfo = nil;
@@ -401,6 +424,7 @@ describe(@"a lifecycle manager", ^{
});
});
+ // when the protocol system info is set
context(@"when the protocol system info is set", ^{
SDLSystemInfo *testSystemInfo = [[SDLSystemInfo alloc] initWithVehicleType:vehicleType softwareVersion:softwareVersion hardwareVersion:hardwareVersion];
@@ -422,6 +446,7 @@ describe(@"a lifecycle manager", ^{
});
});
+ // when the register response returns different language than the one passed with the lifecycle configuration
context(@"when the register response returns different language than the one passed with the lifecycle configuration", ^{
it(@"should should update the configuration when the app supports the head unit language", ^{
SDLRegisterAppInterfaceResponse *registerAppInterfaceResponse = [[SDLRegisterAppInterfaceResponse alloc] init];
@@ -442,7 +467,7 @@ describe(@"a lifecycle manager", ^{
expect(changeRegistration.ttsName).to(equal(update.ttsName));
expect(changeRegistration.vrSynonyms).to(equal(@[@"EnGb", @"Gb"]));
return [value isKindOfClass:[SDLChangeRegistration class]];
- }]]);
+ }] error:[OCMArg anyObjectRef]]);
setToStateWithEnterTransition(SDLLifecycleStateRegistered, SDLLifecycleStateUpdatingConfiguration);
@@ -498,7 +523,7 @@ describe(@"a lifecycle manager", ^{
expect(changeRegistration.ttsName).to(beNil());
expect(changeRegistration.vrSynonyms).to(beNil());
return [value isKindOfClass:[SDLChangeRegistration class]];
- }]]);
+ }] error:[OCMArg anyObjectRef]]);
setToStateWithEnterTransition(SDLLifecycleStateRegistered, SDLLifecycleStateUpdatingConfiguration);
@@ -511,7 +536,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.configuration.lifecycleConfig.ttsName).toEventually(beNil());
});
});
-
+
+ // after receiving a disconnect notification
describe(@"after receiving a disconnect notification", ^{
beforeEach(^{
OCMStub([protocolMock stopWithCompletionHandler:[OCMArg invokeBlock]]);
@@ -523,7 +549,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.lifecycleState).withTimeout(3.0).toEventually(equal(SDLLifecycleStateStarted));
});
});
-
+
+ // stopping the manager
describe(@"stopping the manager", ^{
beforeEach(^{
[testManager stop];
@@ -535,10 +562,11 @@ describe(@"a lifecycle manager", ^{
});
});
+ // transitioning to the registered state when the minimum RPC version is in effect
describe(@"transitioning to the registered state when the minimum RPC version is in effect", ^{
beforeEach(^{
+ OCMStub([protocolMock sendRPC:[OCMArg any] error:[OCMArg setTo:nil]]).andReturn(YES);
[SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:1 minor:0 patch:0];
-
[testManager.lifecycleStateMachine setToState:SDLLifecycleStateRegistered fromOldState:nil callEnterTransition:YES];
});
@@ -546,7 +574,8 @@ describe(@"a lifecycle manager", ^{
expect(testManager.lifecycleState).to(equal(SDLLifecycleStateUnregistering));
});
});
-
+
+ // transitioning from setting app icon state to the Setting Up HMI state
describe(@"transitioning from setting app icon state to the Setting Up HMI state", ^{
context(@"before register response is a success", ^{
it(@"ready handler should not be called yet", ^{
@@ -585,11 +614,13 @@ describe(@"a lifecycle manager", ^{
});
});
+ // transitioning from the registered state to the ready state
describe(@"transitioning from the registered state to the ready state", ^{
beforeEach(^{
[testManager.lifecycleStateMachine setToState:SDLLifecycleStateRegistered fromOldState:nil callEnterTransition:NO];
});
+ // when the register response is a success
context(@"when the register response is a success", ^{
it(@"should call the ready handler with success", ^{
SDLRegisterAppInterfaceResponse *response = [[SDLRegisterAppInterfaceResponse alloc] init];
@@ -603,6 +634,7 @@ describe(@"a lifecycle manager", ^{
});
});
+ // when the register response is a warning
context(@"when the register response is a warning", ^{
it(@"should call the ready handler with success but error", ^{
SDLRegisterAppInterfaceResponse *response = [[SDLRegisterAppInterfaceResponse alloc] init];
@@ -619,23 +651,57 @@ describe(@"a lifecycle manager", ^{
});
});
});
-
+
+ // in the ready state
describe(@"in the ready state", ^{
beforeEach(^{
[testManager.lifecycleStateMachine setToState:SDLLifecycleStateReady fromOldState:nil callEnterTransition:NO];
});
+ // after receiving another connect notification for encryption
+ describe(@"after receiving another connect notification for encryption", ^{
+ it(@"should not crash and remain in the same state", ^{
+ expectAction(^{
+ [testManager.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
+ }).toNot(raiseException());
+
+ expect(testManager.lifecycleState).to(equal(SDLLifecycleStateReady));
+ });
+ });
+
it(@"can send an RPC of type Request", ^{
SDLShow *testShow = [[SDLShow alloc] initWithMainField1:@"test" mainField2:nil mainField3:nil mainField4:nil alignment:nil statusBar:nil mediaTrack:nil graphic:nil secondaryGraphic:nil softButtons:nil customPresets:nil metadataTags:nil templateTitle:nil windowID:nil templateConfiguration:nil];
- OCMExpect([protocolMock sendRPC:testShow]);
+ OCMExpect([protocolMock sendRPC:testShow error:[OCMArg anyObjectRef]]);
[testManager sendRPC:testShow];
OCMVerifyAllWithDelay(protocolMock, 0.1);
});
+ it(@"should call the callback if the protocol fails to send a request", ^{
+ NSError *testError = [NSError sdl_lifecycle_notReadyError];
+ OCMStub([protocolMock sendRPC:[OCMArg any] error:[OCMArg setTo:testError]]).andReturn(NO);
+
+ SDLShow *testShow = [[SDLShow alloc] init];
+ testShow.mainField1 = @"Test";
+
+ __block SDLRPCRequest *returnRequest = nil;
+ __block SDLRPCResponse *returnResponse = nil;
+ __block NSError *returnError = nil;
+ [testManager sendRequest:testShow withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ returnRequest = request;
+ returnResponse = response;
+ returnError = error;
+ }];
+
+ expect(returnRequest).toEventuallyNot(beNil());
+ expect(returnRequest).toEventually(beAnInstanceOf([SDLShow class]));
+ expect(returnResponse).toEventually(beNil());
+ expect(returnError).toEventuallyNot(beNil());
+ });
+
it(@"can send an RPC of type Response", ^{
SDLPerformAppServiceInteractionResponse *testResponse = [[SDLPerformAppServiceInteractionResponse alloc] init];
- OCMExpect([protocolMock sendRPC:testResponse]);
+ OCMExpect([protocolMock sendRPC:testResponse error:[OCMArg anyObjectRef]]);
[testManager sendRPC:testResponse];
testResponse.correlationID = @(2);
testResponse.success = @(true);
@@ -647,7 +713,7 @@ describe(@"a lifecycle manager", ^{
it(@"can send an RPC of type Notification", ^{
SDLOnAppServiceData *testNotification = [[SDLOnAppServiceData alloc] initWithServiceData:[[SDLAppServiceData alloc] init]];
- OCMExpect([protocolMock sendRPC:testNotification]);
+ OCMExpect([protocolMock sendRPC:testNotification error:[OCMArg anyObjectRef]]);
[testManager sendRPC:testNotification];
OCMVerifyAllWithDelay(protocolMock, 0.1);
@@ -687,11 +753,12 @@ describe(@"a lifecycle manager", ^{
describe(@"stopping the manager", ^{
beforeEach(^{
+ OCMStub([protocolMock sendRPC:[OCMArg any] error:[OCMArg setTo:nil]]).andReturn(YES);
[testManager stop];
});
it(@"should attempt to unregister", ^{
- OCMVerify([protocolMock sendRPC:[OCMArg isKindOfClass:[SDLUnregisterAppInterface class]]]);
+ OCMVerify([protocolMock sendRPC:[OCMArg isKindOfClass:[SDLUnregisterAppInterface class]] error:[OCMArg anyObjectRef]]);
expect(testManager.lifecycleState).toEventually(match(SDLLifecycleStateUnregistering));
});
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
index 27de73b2a..137006e6b 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
@@ -237,9 +237,12 @@ describe(@"SendRPCRequest Tests", ^{
testHeader.sessionID = 0xFF;
[testProtocol protocol:testProtocol didReceiveStartServiceACK:[SDLProtocolMessage messageWithHeader:testHeader andPayload:nil]];
- [testProtocol sendRPC:mockRequest];
+ NSError *error = nil;
+ BOOL sent = [testProtocol sendRPC:mockRequest error:&error];
expect(verified).toEventually(beTrue());
+ expect(sent).to(beTrue());
+ expect(error).to(beNil());
});
});
@@ -282,10 +285,13 @@ describe(@"SendRPCRequest Tests", ^{
testHeader.serviceType = SDLServiceTypeRPC;
testHeader.sessionID = 0x01;
[testProtocol protocol:testProtocol didReceiveStartServiceACK:[SDLProtocolMessage messageWithHeader:testHeader andPayload:nil]];
-
- [testProtocol sendRPC:mockRequest];
+
+ NSError *error = nil;
+ BOOL sent = [testProtocol sendRPC:mockRequest error:&error];
expect(verified).toEventually(beTrue());
+ expect(sent).to(beTrue());
+ expect(error).to(beNil());
});
});
});