diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2021-04-20 11:54:45 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-20 11:54:45 -0400 |
commit | 5bd0d7ceb196e3d0888f151cde9ccda6ff0f1a71 (patch) | |
tree | a02ddf727b484bcb3e5fc81aeef80d88e394e5fb | |
parent | 62d185900bae968e7e87e33f7a354bceabc67452 (diff) | |
parent | 1fa7c0175753dbed92f5ebe7d464f8bbad395c4b (diff) | |
download | sdl_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.pbxproj | 23 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLError.h | 1 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLError.m | 12 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLLifecycleManager.m | 46 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLProtocol.h | 4 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLProtocol.m | 43 | ||||
-rw-r--r-- | SmartDeviceLink/private/SDLUploadFileOperation.m | 2 | ||||
-rw-r--r-- | SmartDeviceLink/public/SDLErrorConstants.h | 5 | ||||
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m | 137 | ||||
-rw-r--r-- | SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m | 12 |
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()); }); }); }); |